Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2.x - Allow RubySaml::Utils.is_cert_expired and is_cert_active to accept an optional time argument #733

Merged
merged 4 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* [#715](https://github.com/SAML-Toolkits/ruby-saml/pull/715) Fix typo in error when SPNameQualifier value does not match the SP entityID.
* [#718](https://github.com/SAML-Toolkits/ruby-saml/pull/718) Add support to retrieve from SAMLResponse the AuthnInstant and AuthnContextClassRef values
* [#711](https://github.com/SAML-Toolkits/ruby-saml/pull/711) Standardize how RubySaml reads and formats certificate and private_key PEM values, including the `RubySaml::Util#format_cert` and `#format_private_key` methods.
* [#733](https://github.com/SAML-Toolkits/ruby-saml/pull/733) Allow `RubySaml::Utils.is_cert_expired` and `is_cert_active` to accept an optional time argument.
* [#731](https://github.com/SAML-Toolkits/ruby-saml/pull/731) Add CI coverage for Ruby 3.4. Remove CI coverage for Ruby 1.x and 2.x.

### 1.18.0 (???)
Expand Down
22 changes: 11 additions & 11 deletions lib/ruby_saml/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module RubySaml

# SAML2 Auxiliary class
#
module Utils
module Utils # rubocop:disable Metrics/ModuleLength
extend self

BINDINGS = { post: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
Expand All @@ -35,32 +35,32 @@ module Utils
# Checks if the x509 cert provided is expired.
#
# @param cert [OpenSSL::X509::Certificate|String] The x509 certificate.
# @param now [Time|Integer] The time to compare.
# @return [true|false] Whether the certificate is expired.
def is_cert_expired(cert)
def is_cert_expired(cert, now = Time.now)
now = Time.at(now) if now.is_a?(Integer)
cert = build_cert_object(cert) if cert.is_a?(String)
cert.not_after < Time.now
cert.not_after < now
end

# Checks if the x509 cert provided has both started and has not expired.
#
# @param cert [OpenSSL::X509::Certificate|String] The x509 certificate.
# @param now [Time|Integer] The time to compare.
# @return [true|false] Whether the certificate is currently active.
def is_cert_active(cert)
def is_cert_active(cert, now = Time.now)
now = Time.at(now) if now.is_a?(Integer)
cert = build_cert_object(cert) if cert.is_a?(String)
now = Time.now
cert.not_before <= now && cert.not_after >= now
end

# Interprets a ISO8601 duration value relative to a given timestamp.
#
# @param duration [String] The duration, as a string.
# @param timestamp [Integer] The unix timestamp we should apply the
# duration to. Optional, default to the
# current time.
#
# @param timestamp [Time|Integer] The unix timestamp we should apply the
# duration to. Optional, default to the current time.
# @return [Integer] The new timestamp, after the duration is applied.
#
def parse_duration(duration, timestamp=Time.now.utc)
def parse_duration(duration, timestamp = Time.now)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safe to do Time.now without .utc here because it's converted to UTC below.

matches = duration.match(DURATION_FORMAT)

if matches.nil?
Expand Down
186 changes: 144 additions & 42 deletions test/utils_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -408,66 +408,168 @@ def result(duration, reference = 0)
end

describe '.is_cert_expired' do
it 'returns true for expired certificate' do
expired_cert = CertificateHelper.generate_cert(not_after: Time.now - 60)
assert RubySaml::Utils.is_cert_expired(expired_cert)
end

it 'returns false for not-started certificate' do
not_started_cert = CertificateHelper.generate_cert(not_before: Time.now + 60)
refute RubySaml::Utils.is_cert_active(not_started_cert)
end
describe 'time argument not specified' do
it 'returns true for expired certificate' do
expired_cert = CertificateHelper.generate_cert(not_after: Time.now - 60)
assert RubySaml::Utils.is_cert_expired(expired_cert)
end

it 'returns false for active certificate' do
valid_cert = CertificateHelper.generate_cert
refute RubySaml::Utils.is_cert_expired(valid_cert)
end
it 'returns false for not-started certificate' do
not_started_cert = CertificateHelper.generate_cert(not_before: Time.now + 60)
refute RubySaml::Utils.is_cert_active(not_started_cert)
end

it 'returns false for active certificate' do
valid_cert = CertificateHelper.generate_cert
refute RubySaml::Utils.is_cert_expired(valid_cert)
end

it 'returns true for expired certificate string' do
expired_cert_string = CertificateHelper.generate_cert(not_after: Time.now - 60).to_pem
assert RubySaml::Utils.is_cert_expired(expired_cert_string)
it 'returns true for expired certificate string' do
expired_cert_string = CertificateHelper.generate_cert(not_after: Time.now - 60).to_pem
assert RubySaml::Utils.is_cert_expired(expired_cert_string)
end

it 'returns false for not-started certificate string' do
not_started_cert_string = CertificateHelper.generate_cert(not_before: Time.now + 60).to_pem
refute RubySaml::Utils.is_cert_active(not_started_cert_string)
end

it 'returns false for active certificate string' do
valid_cert_string = CertificateHelper.generate_cert.to_pem
refute RubySaml::Utils.is_cert_expired(valid_cert_string)
end
end

it 'returns false for not-started certificate string' do
not_started_cert_string = CertificateHelper.generate_cert(not_before: Time.now + 60).to_pem
refute RubySaml::Utils.is_cert_active(not_started_cert_string)
describe 'time argument specified as Time' do
let(:now) { Time.at(10000) }

it 'returns true for expired certificate' do
expired_cert = CertificateHelper.generate_cert(not_after: now - 60)
assert RubySaml::Utils.is_cert_expired(expired_cert, now)
end

it 'returns false for not-started certificate' do
not_started_cert = CertificateHelper.generate_cert(not_before: now + 60)
refute RubySaml::Utils.is_cert_active(not_started_cert, now)
end

it 'returns false for active certificate' do
valid_cert = CertificateHelper.generate_cert(not_before: now - 60, not_after: now + 60)
refute RubySaml::Utils.is_cert_expired(valid_cert, now)
end

it 'returns true for expired certificate string' do
expired_cert_string = CertificateHelper.generate_cert(not_after: now - 60).to_pem
assert RubySaml::Utils.is_cert_expired(expired_cert_string, now)
end

it 'returns false for not-started certificate string' do
not_started_cert_string = CertificateHelper.generate_cert(not_before: now + 60).to_pem
refute RubySaml::Utils.is_cert_active(not_started_cert_string, now)
end

it 'returns false for active certificate string' do
valid_cert_string = CertificateHelper.generate_cert(not_before: now - 60, not_after: now + 60).to_pem
refute RubySaml::Utils.is_cert_expired(valid_cert_string, now)
end
end

it 'returns false for active certificate string' do
valid_cert_string = CertificateHelper.generate_cert.to_pem
refute RubySaml::Utils.is_cert_expired(valid_cert_string)
describe 'time argument specified as Integer' do
let(:int) { 10000 }

it 'returns true for expired certificate' do
expired_cert = CertificateHelper.generate_cert(not_after: Time.at(int) - 60)
assert RubySaml::Utils.is_cert_expired(expired_cert, int)
end

it 'returns false for not-started certificate' do
not_started_cert = CertificateHelper.generate_cert(not_before: Time.at(int) + 60)
refute RubySaml::Utils.is_cert_active(not_started_cert, int)
end
end
end

describe '.is_cert_active' do
it 'returns true for active certificate' do
valid_cert = CertificateHelper.generate_cert
assert RubySaml::Utils.is_cert_active(valid_cert)
end

it 'returns false for not-started certificate' do
not_started_cert = CertificateHelper.generate_cert(not_before: Time.now + 60)
refute RubySaml::Utils.is_cert_active(not_started_cert)
end
describe 'time argument not specified' do
it 'returns true for active certificate' do
valid_cert = CertificateHelper.generate_cert
assert RubySaml::Utils.is_cert_active(valid_cert)
end

it 'returns false for expired certificate' do
expired_cert = CertificateHelper.generate_cert(not_after: Time.now - 60)
refute RubySaml::Utils.is_cert_active(expired_cert)
end
it 'returns false for not-started certificate' do
not_started_cert = CertificateHelper.generate_cert(not_before: Time.now + 60)
refute RubySaml::Utils.is_cert_active(not_started_cert)
end

it 'returns false for expired certificate' do
expired_cert = CertificateHelper.generate_cert(not_after: Time.now - 60)
refute RubySaml::Utils.is_cert_active(expired_cert)
end

it 'returns true for active certificate string' do
valid_cert_string = CertificateHelper.generate_cert.to_pem
assert RubySaml::Utils.is_cert_active(valid_cert_string)
it 'returns true for active certificate string' do
valid_cert_string = CertificateHelper.generate_cert.to_pem
assert RubySaml::Utils.is_cert_active(valid_cert_string)
end

it 'returns false for not-started certificate string' do
not_started_cert_string = CertificateHelper.generate_cert(not_before: Time.now + 60).to_pem
refute RubySaml::Utils.is_cert_active(not_started_cert_string)
end

it 'returns false for expired certificate string' do
expired_cert_string = CertificateHelper.generate_cert(not_after: Time.now - 60).to_pem
refute RubySaml::Utils.is_cert_active(expired_cert_string)
end
end

it 'returns false for not-started certificate string' do
not_started_cert_string = CertificateHelper.generate_cert(not_before: Time.now + 60).to_pem
refute RubySaml::Utils.is_cert_active(not_started_cert_string)
describe 'time argument specified as Time' do
let(:now) { Time.at(10000) }

it 'returns true for active certificate' do
valid_cert = CertificateHelper.generate_cert(not_before: now - 60, not_after: now + 60)
assert RubySaml::Utils.is_cert_active(valid_cert, now)
end

it 'returns false for not-started certificate' do
not_started_cert = CertificateHelper.generate_cert(not_before: now + 60)
refute RubySaml::Utils.is_cert_active(not_started_cert, now)
end

it 'returns false for expired certificate' do
expired_cert = CertificateHelper.generate_cert(not_after: now - 60)
refute RubySaml::Utils.is_cert_active(expired_cert, now)
end

it 'returns true for active certificate string' do
valid_cert_string = CertificateHelper.generate_cert(not_before: now - 60, not_after: now + 60).to_pem
assert RubySaml::Utils.is_cert_active(valid_cert_string, now)
end

it 'returns false for not-started certificate string' do
not_started_cert_string = CertificateHelper.generate_cert(not_before: now + 60).to_pem
refute RubySaml::Utils.is_cert_active(not_started_cert_string, now)
end

it 'returns false for expired certificate string' do
expired_cert_string = CertificateHelper.generate_cert(not_after: now - 60).to_pem
refute RubySaml::Utils.is_cert_active(expired_cert_string, now)
end
end

it 'returns false for expired certificate string' do
expired_cert_string = CertificateHelper.generate_cert(not_after: Time.now - 60).to_pem
refute RubySaml::Utils.is_cert_active(expired_cert_string)
describe 'time argument specified as Integer' do
let(:int) { 10000 }

it 'returns true for active certificate' do
valid_cert = CertificateHelper.generate_cert(not_before: Time.at(int) - 60, not_after: Time.at(int) + 60)
assert RubySaml::Utils.is_cert_active(valid_cert, int)
end

it 'returns false for expired certificate' do
expired_cert = CertificateHelper.generate_cert(not_after: Time.at(int) - 60)
refute RubySaml::Utils.is_cert_active(expired_cert, int)
end
end
end
end
Loading