Skip to content

Commit a255d2d

Browse files
committed
Merge branch 'v2.x-rework-uuid' of github.com:johnnyshields/ruby-saml into v2.x-rework-uuid
2 parents 3be5172 + 83c3310 commit a255d2d

File tree

5 files changed

+154
-7
lines changed

5 files changed

+154
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* [#715](https://github.com/SAML-Toolkits/ruby-saml/pull/715) Fix typo in error when SPNameQualifier value does not match the SP entityID.
1515
* [#718](https://github.com/SAML-Toolkits/ruby-saml/pull/718) Add support to retrieve from SAMLResponse the AuthnInstant and AuthnContextClassRef values
1616
* [#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::Utils#format_cert` and `#format_private_key` methods.
17+
* [#732](https://github.com/SAML-Toolkits/ruby-saml/pull/732) Return original certificate and private key objects from `RubySaml::Utils` build functions.
18+
* [#732](https://github.com/SAML-Toolkits/ruby-saml/pull/732) Allow SP `certificate`, `certificate_new`, and `private_key` settings to be `OpenSSL::X509::Certificate` and `OpenSSL::PKey::PKey` objects respectively.
1719
* [#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.
1820
* [#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.
1921
* [#735](https://github.com/SAML-Toolkits/ruby-saml/pull/735) Add `Settings#sp_uuid_prefix` and deprecate `Utils#set_prefix`.

lib/ruby_saml/settings.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,24 @@ def compress_deprecation(old_param, new_param)
364364
'"HTTP-Redirect" will always be compressed, and "HTTP-POST" will always be uncompressed.'
365365
end
366366

367+
# Quick check if a certificate value is present as a string or OpenSSL::X509::Certificate.
368+
# Does not check if the string can actually be parsed.
369+
#
370+
# @param cert [OpenSSL::X509::Certificate|String] The x509 certificate.
371+
# @return [true|false] Whether the certificate value is present.
372+
def cert?(cert)
373+
!!cert && (cert.is_a?(OpenSSL::X509::Certificate) || !cert.empty?)
374+
end
375+
376+
# Quick check if a private key value is present as a string or OpenSSL::PKey::PKey.
377+
# Does not check if the string can actually be parsed.
378+
#
379+
# @param private_key [OpenSSL::PKey::PKey|String] The private key.
380+
# @return [true|false] Whether the private key value is present.
381+
def private_key?(private_key)
382+
!!private_key && (private_key.is_a?(OpenSSL::PKey::PKey) || !private_key.empty?)
383+
end
384+
367385
# @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
368386
# Build the SP certificates and private keys from the settings. Returns all
369387
# certificates and private keys, even if they are expired.
@@ -374,11 +392,8 @@ def get_all_sp_certs
374392

375393
# Validate certificate, certificate_new, private_key, and sp_cert_multi params.
376394
def validate_sp_certs_params!
377-
multi = sp_cert_multi && !sp_cert_multi.empty?
378-
cert = certificate && !certificate.empty?
379-
cert_new = certificate_new && !certificate_new.empty?
380-
pk = private_key && !private_key.empty?
381-
if multi && (cert || cert_new || pk)
395+
has_multi = sp_cert_multi && !sp_cert_multi.empty?
396+
if has_multi && (cert?(certificate) || cert?(certificate_new) || private_key?(private_key))
382397
raise ArgumentError.new("Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters")
383398
end
384399
end

lib/ruby_saml/utils.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module Utils # rubocop:disable Metrics/ModuleLength
4343
# @return [true|false] Whether the certificate is expired.
4444
def is_cert_expired(cert, now = Time.now)
4545
now = Time.at(now) if now.is_a?(Integer)
46-
cert = build_cert_object(cert) if cert.is_a?(String)
46+
cert = build_cert_object(cert)
4747
cert.not_after < now
4848
end
4949

@@ -54,7 +54,7 @@ def is_cert_expired(cert, now = Time.now)
5454
# @return [true|false] Whether the certificate is currently active.
5555
def is_cert_active(cert, now = Time.now)
5656
now = Time.at(now) if now.is_a?(Integer)
57-
cert = build_cert_object(cert) if cert.is_a?(String)
57+
cert = build_cert_object(cert)
5858
cert.not_before <= now && cert.not_after >= now
5959
end
6060

@@ -123,6 +123,7 @@ def format_private_key(key, multi: false)
123123
# @param pem [String] The original certificate
124124
# @return [OpenSSL::X509::Certificate] The certificate object
125125
def build_cert_object(pem)
126+
return pem if pem.is_a?(OpenSSL::X509::Certificate)
126127
return unless (pem = PemFormatter.format_cert(pem, multi: false))
127128

128129
OpenSSL::X509::Certificate.new(pem)
@@ -133,6 +134,7 @@ def build_cert_object(pem)
133134
# @param pem [String] The original private key.
134135
# @return [OpenSSL::PKey::PKey] The private key object.
135136
def build_private_key_object(pem)
137+
return pem if pem.is_a?(OpenSSL::PKey::PKey)
136138
return unless (pem = PemFormatter.format_private_key(pem, multi: false))
137139

138140
error = nil

test/settings_test.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,78 @@ class SettingsTest < Minitest::Test
515515
end
516516
end
517517

518+
it 'handles OpenSSL::X509::Certificate objects for single case' do
519+
@settings.certificate = OpenSSL::X509::Certificate.new(cert_text1)
520+
@settings.private_key = key_text1
521+
522+
actual = @settings.get_sp_certs
523+
expected = [[cert_text1, key_text1]]
524+
assert_equal [:signing, :encryption], actual.keys
525+
assert_equal expected, actual[:signing].map { |ary| ary.map(&:to_pem) }
526+
assert_equal expected, actual[:encryption].map { |ary| ary.map(&:to_pem) }
527+
end
528+
529+
it 'handles OpenSSL::X509::Certificate objects for single case with new cert' do
530+
@settings.certificate = cert_text1
531+
@settings.certificate_new = OpenSSL::X509::Certificate.new(cert_text2)
532+
@settings.private_key = key_text1
533+
534+
actual = @settings.get_sp_certs
535+
expected = [[cert_text1, key_text1], [cert_text2, key_text1]]
536+
assert_equal [:signing, :encryption], actual.keys
537+
assert_equal expected, actual[:signing].map { |ary| ary.map(&:to_pem) }
538+
assert_equal expected, actual[:encryption].map { |ary| ary.map(&:to_pem) }
539+
end
540+
541+
it 'handles OpenSSL::X509::Certificate objects for multi case' do
542+
x509_certificate1 = OpenSSL::X509::Certificate.new(cert_text1)
543+
x509_certificate2 = OpenSSL::X509::Certificate.new(cert_text2)
544+
@settings.sp_cert_multi = {
545+
signing: [{ certificate: x509_certificate1, private_key: key_text1 },
546+
{ certificate: cert_text2, private_key: key_text1 }],
547+
encryption: [{ certificate: x509_certificate2, private_key: key_text1 },
548+
{ certificate: cert_text3, private_key: key_text2 }]
549+
}
550+
551+
actual = @settings.get_sp_certs
552+
expected_signing = [[cert_text1, key_text1], [cert_text2, key_text1]]
553+
expected_encryption = [[cert_text2, key_text1], [cert_text3, key_text2]]
554+
assert_equal [:signing, :encryption], actual.keys
555+
assert_equal expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }
556+
assert_equal expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }
557+
end
558+
559+
560+
it 'handles OpenSSL::PKey::PKey objects for single case' do
561+
@settings.certificate = cert_text1
562+
@settings.private_key = OpenSSL::PKey::RSA.new(key_text1)
563+
564+
actual = @settings.get_sp_certs
565+
expected = [[cert_text1, key_text1]]
566+
assert_equal [:signing, :encryption], actual.keys
567+
assert_equal expected, actual[:signing].map { |ary| ary.map(&:to_pem) }
568+
assert_equal expected, actual[:encryption].map { |ary| ary.map(&:to_pem) }
569+
end
570+
571+
it 'handles OpenSSL::PKey::PKey objects for multi case' do
572+
pkey2 = OpenSSL::PKey::RSA.new(key_text2)
573+
pkey3 = CertificateHelper.generate_private_key(:dsa)
574+
pkey4 = CertificateHelper.generate_private_key(:ecdsa)
575+
@settings.sp_cert_multi = {
576+
signing: [{ certificate: cert_text1, private_key: pkey3 },
577+
{ certificate: cert_text2, private_key: pkey4 }],
578+
encryption: [{ certificate: cert_text2, private_key: key_text1 },
579+
{ certificate: cert_text3, private_key: pkey2 }]
580+
}
581+
582+
actual = @settings.get_sp_certs
583+
expected_signing = [[cert_text1, pkey3.to_pem], [cert_text2, pkey4.to_pem]]
584+
expected_encryption = [[cert_text2, key_text1], [cert_text3, key_text2]]
585+
assert_equal [:signing, :encryption], actual.keys
586+
assert_equal expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }
587+
assert_equal expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }
588+
end
589+
518590
it "sp_cert_multi allows sending only signing" do
519591
@settings.sp_cert_multi = {
520592
signing: [{ certificate: cert_text1, private_key: key_text1 },
@@ -920,5 +992,49 @@ class SettingsTest < Minitest::Test
920992
end
921993
end
922994
end
995+
996+
describe '#cert?' do
997+
it 'returns true for a valid OpenSSL::X509::Certificate object' do
998+
cert = CertificateHelper.generate_cert
999+
assert @settings.send(:cert?, cert)
1000+
end
1001+
1002+
it 'returns true for a non-empty certificate string' do
1003+
cert_string = '-----BEGIN CERTIFICATE-----\nVALID_CERTIFICATE\n-----END CERTIFICATE-----'
1004+
assert @settings.send(:cert?, cert_string)
1005+
end
1006+
1007+
it 'returns false for an empty certificate string' do
1008+
cert_string = ''
1009+
refute @settings.send(:cert?, cert_string)
1010+
end
1011+
1012+
it 'returns false for nil' do
1013+
cert = nil
1014+
refute @settings.send(:cert?, cert)
1015+
end
1016+
end
1017+
1018+
describe '#private_key?' do
1019+
it 'returns true for a valid OpenSSL::PKey::PKey object' do
1020+
private_key = CertificateHelper.generate_private_key
1021+
assert @settings.send(:private_key?, private_key)
1022+
end
1023+
1024+
it 'returns true for a non-empty private key string' do
1025+
private_key_string = '-----BEGIN PRIVATE KEY-----\nVALID_PRIVATE_KEY\n-----END PRIVATE KEY-----'
1026+
assert @settings.send(:private_key?, private_key_string)
1027+
end
1028+
1029+
it 'returns false for an empty private key string' do
1030+
private_key_string = ''
1031+
refute @settings.send(:private_key?, private_key_string)
1032+
end
1033+
1034+
it 'returns false for nil' do
1035+
private_key = nil
1036+
refute @settings.send(:private_key?, private_key)
1037+
end
1038+
end
9231039
end
9241040
end

test/utils_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ def result(duration, reference = 0)
156156
end
157157
end
158158

159+
it 'returns the original certificate when an OpenSSL::X509::Certificate is given' do
160+
certificate = OpenSSL::X509::Certificate.new
161+
assert_same certificate, RubySaml::Utils.build_cert_object(certificate)
162+
end
163+
159164
it 'returns nil for nil certificate string' do
160165
assert_nil RubySaml::Utils.build_cert_object(nil)
161166
end
@@ -180,6 +185,13 @@ def result(duration, reference = 0)
180185
end
181186
end
182187

188+
[OpenSSL::PKey::RSA, OpenSSL::PKey::DSA, OpenSSL::PKey::EC].each do |key_class|
189+
it 'returns the original private key when an instance of OpenSSL::PKey::PKey is given' do
190+
private_key = key_class.new
191+
assert_same private_key, RubySaml::Utils.build_private_key_object(private_key)
192+
end
193+
end
194+
183195
it 'returns nil for nil private key string' do
184196
assert_nil RubySaml::Utils.build_private_key_object(nil)
185197
end

0 commit comments

Comments
 (0)