Skip to content

Commit 8a51358

Browse files
committed
- Return the original certificate from Utils.build_cert_object when an instance of OpenSSL::X509::Certificate is given. And return the original key from Utils.build_private_key_object when an instance of OpenSSL::PKey::PKey is given.
- Allow SP certificates / private_key settings to be OpenSSL::X509::Certificate / OpenSSL::PKey::PKey objects.
1 parent 6f73a4f commit 8a51358

File tree

4 files changed

+150
-5
lines changed

4 files changed

+150
-5
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::Util#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

1820
### 1.18.0 (???)
1921
* [#718](https://github.com/SAML-Toolkits/ruby-saml/pull/718) Add support to retrieve from SAMLResponse the AuthnInstant and AuthnContextClassRef values

lib/ruby_saml/settings.rb

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

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

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

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)