saml2 3.0.11 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 953f52449cc1cbab86280c3265ecf74cde4eb8db37946a99aa2d85f59d7a4754
4
- data.tar.gz: f211ab22f71726b292c5182fba1b5040284c4a243f03458b84f33983f95dadbc
3
+ metadata.gz: 1a9f01162da7309af02b28de55593de014c32eba85d87a7af95360423cd03550
4
+ data.tar.gz: 71b3e1f1309c840e649e8d6a3dfad701b01349c5c8b3c3378b96733ef62718cf
5
5
  SHA512:
6
- metadata.gz: 271dd3d0bd8325c59b61ed416fd0ed16d1cbc284d957593220f905c3f5c5f8d001bdcc125fcd96e1d28ffe3ad2ec22a6fec027c768df82ddb5dca609ba435c58
7
- data.tar.gz: 87337ae1fd36828023da486e932582009b76f666e04fd336e4d4b4e0a244366c62c7491255b320b2575c6439d56c856200f883a2087fdc70b82f746927fef8db
6
+ metadata.gz: 0b8ff067a719d4e450a94b23967b6519a2c51ed806decb7b342f06c29ace840625b2d03056a986fbd54c12fe9adc2997ab24731be40341d52f01d34d5e14f220
7
+ data.tar.gz: 2de7817d5dda92bacd3e87fe302a7f7f35e70ca7a2234b4237a45951456ab19c66da8ef836c9004ae289f4d7f8d38ef47ff4f6efc21a2d641bc8d0c62a00f1df
data/lib/saml2/key.rb CHANGED
@@ -11,6 +11,8 @@ module SAML2
11
11
  class KeyInfo < Base
12
12
  # @return [String] The PEM encoded certificate.
13
13
  attr_reader :x509
14
+ # @return [OpenSSL::PKey::PKey] An RSA Public Key
15
+ attr_accessor :key
14
16
 
15
17
  # @param x509 [String] The PEM encoded certificate.
16
18
  def initialize(x509 = nil)
@@ -19,7 +21,15 @@ module SAML2
19
21
 
20
22
  # (see Base#from_xml)
21
23
  def from_xml(node)
22
- self.x509 = node.at_xpath('dsig:KeyInfo/dsig:X509Data/dsig:X509Certificate', Namespaces::ALL)&.content&.strip
24
+ self.x509 = node.at_xpath('dsig:X509Data/dsig:X509Certificate', Namespaces::ALL)&.content&.strip
25
+ if (rsa_key_value = node.at_xpath('dsig:KeyValue/dsig:RSAKeyValue', Namespaces::ALL))
26
+ modulus = crypto_binary_to_integer(rsa_key_value.at_xpath('dsig:Modulus', Namespaces::ALL)&.content&.strip)
27
+ exponent = crypto_binary_to_integer(rsa_key_value.at_xpath('dsig:Exponent', Namespaces::ALL)&.content&.strip)
28
+ if modulus && exponent
29
+ @key = OpenSSL::PKey::RSA.new
30
+ key.set_key(modulus, exponent, nil)
31
+ end
32
+ end
23
33
  end
24
34
 
25
35
  def x509=(value)
@@ -28,9 +38,15 @@ module SAML2
28
38
 
29
39
  # @return [OpenSSL::X509::Certificate]
30
40
  def certificate
41
+ return nil if x509.nil?
31
42
  @certificate ||= OpenSSL::X509::Certificate.new(Base64.decode64(x509))
32
43
  end
33
44
 
45
+ # @return [OpenSSL::PKey::PKey]
46
+ def public_key
47
+ key || certificate&.public_key
48
+ end
49
+
34
50
  # Formats a fingerprint as all lowercase, with a : every two characters,
35
51
  # stripping all non-hexadecimal characters.
36
52
  # @param fingerprint [String]
@@ -41,17 +57,35 @@ module SAML2
41
57
 
42
58
  # @return [String]
43
59
  def fingerprint
60
+ return nil unless certificate
44
61
  @fingerprint ||= self.class.format_fingerprint(Digest::SHA1.hexdigest(certificate.to_der))
45
62
  end
46
63
 
47
64
  # (see Base#build)
48
65
  def build(builder)
49
66
  builder['dsig'].KeyInfo do |key_info|
50
- key_info['dsig'].X509Data do |x509_data|
51
- x509_data['dsig'].X509Certificate(x509)
67
+ if x509
68
+ key_info['dsig'].X509Data do |x509_data|
69
+ x509_data['dsig'].X509Certificate(x509)
70
+ end
71
+ end
72
+ if key.is_a?(OpenSSL::PKey::RSA)
73
+ key_info['dsig'].KeyValue do |key_value|
74
+ key_value['dsig'].RSAKeyValue do |rsa_key_value|
75
+ rsa_key_value['dsig'].Modulus(Base64.encode64(key.n.to_s(2)))
76
+ rsa_key_value['dsig'].Exponent(Base64.encode64(key.e.to_s(2)))
77
+ end
78
+ end
52
79
  end
53
80
  end
54
81
  end
82
+
83
+ private
84
+
85
+ def crypto_binary_to_integer(str)
86
+ return nil unless str
87
+ OpenSSL::BN.new(Base64.decode64(str), 2)
88
+ end
55
89
  end
56
90
 
57
91
  class KeyDescriptor < KeyInfo
@@ -99,7 +133,7 @@ module SAML2
99
133
 
100
134
  # (see Base#from_xml)
101
135
  def from_xml(node)
102
- super
136
+ super(node.at_xpath('dsig:KeyInfo', Namespaces::ALL))
103
137
  self.use = node['use']
104
138
  self.encryption_methods = load_object_array(node, 'md:EncryptionMethod', EncryptionMethod)
105
139
  end
@@ -127,14 +127,16 @@ module SAML2
127
127
  return errors
128
128
  end
129
129
 
130
- certificates = idp.signing_keys.map(&:certificate)
131
- if idp.fingerprints.empty? && certificates.empty?
130
+ certificates = idp.signing_keys.map(&:certificate).compact
131
+ keys = idp.signing_keys.map(&:key).compact
132
+ if idp.fingerprints.empty? && certificates.empty? && keys.empty?
132
133
  errors << "could not find certificate to validate message"
133
134
  return errors
134
135
  end
135
136
 
136
137
  if signed?
137
- unless (signature_errors = validate_signature(fingerprint: idp.fingerprints,
138
+ unless (signature_errors = validate_signature(key: keys,
139
+ fingerprint: idp.fingerprints,
138
140
  cert: certificates)).empty?
139
141
  return errors.concat(signature_errors)
140
142
  end
@@ -145,7 +147,8 @@ module SAML2
145
147
 
146
148
  # this might be nil, if the assertion was encrypted
147
149
  if assertion&.signed?
148
- unless (signature_errors = assertion.validate_signature(fingerprint: idp.fingerprints,
150
+ unless (signature_errors = assertion.validate_signature(key: keys,
151
+ fingerprint: idp.fingerprints,
149
152
  cert: certificates)).empty?
150
153
  return errors.concat(signature_errors)
151
154
  end
@@ -27,7 +27,7 @@ module SAML2
27
27
  def signing_key
28
28
  unless instance_variable_defined?(:@signing_key)
29
29
  # don't use `... if signature.at_xpath(...)` - we need to make sure we assign the nil
30
- @signing_key = signature.at_xpath('dsig:KeyInfo', Namespaces::ALL) ? KeyInfo.from_xml(signature) : nil
30
+ @signing_key = (key_info = signature.at_xpath('dsig:KeyInfo', Namespaces::ALL)) ? KeyInfo.from_xml(key_info) : nil
31
31
  end
32
32
  @signing_key
33
33
  end
@@ -38,20 +38,21 @@ module SAML2
38
38
 
39
39
  # Validate the signature on this object.
40
40
  #
41
- # At least one of +key+, +fingerprint+ or +cert+ must be provided.
41
+ # At least one of +key+, +fingerprint+ or +cert+ must be provided. If the signature
42
+ # doesn't specify which key to use, the first provided key will be used.
42
43
  #
43
- # @param key optional [String, OpenSSL::PKey::PKey]
44
- # The exact public key to use. +fingerprint+ and +cert+ are ignored.
44
+ # @param key optional [String, OpenSSL::PKey::PKey, Array<String>, Array<OpenSSL::PKey::PKey>]
45
+ # Public keys that are allowed to be the valid key.
45
46
  # @param fingerprint optional [Array<String>, String]
46
47
  # SHA1 fingerprints of trusted certificates. If provided, they will be
47
48
  # checked against the {#signing_key} embedded in the {#signature}, and if
48
- # a match is found, the certificate embedded in the signature will be
49
- # added to the list of certificates used for verifying the signature.
49
+ # a match is found, the key embedded in the signature will be
50
+ # used for verifying the signature.
50
51
  # @param cert optional [Array<String>, String]
51
52
  # A single or array of trusted certificates. If provided, they will be
52
- # check against the {#signing_key} embedded in the {#signature}, and if
53
- # a match of public keys only is found, that key will be considered trusted
54
- # and used to verify the signature.
53
+ # checked against the {#signing_key} embedded in the {#signature}, and if
54
+ # a match is found, the key embedded in the signature will be used for
55
+ # verifying the signature.
55
56
  # @return [Array<String>] An empty array on success, details of errors on failure.
56
57
  def validate_signature(key: nil,
57
58
  fingerprint: nil,
@@ -67,23 +68,25 @@ module SAML2
67
68
  end
68
69
  certs = certs.uniq
69
70
 
70
- trusted_keys = certs.map do |cert|
71
+ trusted_keys = Array.wrap(key).map(&:to_s)
72
+ trusted_keys.concat(certs.map do |cert|
71
73
  cert = cert.is_a?(String) ? OpenSSL::X509::Certificate.new(cert) : cert
72
74
  cert.public_key.to_s
73
- end
74
- if signing_key&.certificate && trusted_keys.include?(signing_key.certificate.public_key.to_s)
75
- key ||= signing_key.certificate.public_key.to_s
75
+ end)
76
+
77
+ if trusted_keys.include?(signing_key&.public_key&.to_s)
78
+ verification_key = signing_key.public_key.to_s
76
79
  end
77
80
  # signature doesn't say who signed it. hope and pray it's with the only certificate
78
81
  # we know about
79
- if signing_key.nil? && key.nil? && trusted_keys.length == 1
80
- key = trusted_keys.first
82
+ if signing_key.nil?
83
+ verification_key = trusted_keys.first
81
84
  end
82
85
 
83
- return ["no trusted signing key found"] if key.nil?
86
+ return ["no trusted signing key found"] if verification_key.nil?
84
87
 
85
88
  begin
86
- result = signature.verify_with(key: key)
89
+ result = signature.verify_with(key: verification_key)
87
90
  result ? [] : ["signature is invalid"]
88
91
  rescue XMLSec::VerificationError => e
89
92
  [e.message]
@@ -96,8 +99,8 @@ module SAML2
96
99
  #
97
100
  # @param (see #validate_signature)
98
101
  # @return [Boolean]
99
- def valid_signature?(fingerprint: nil, cert: nil)
100
- validate_signature(fingerprint: fingerprint, cert: cert).empty?
102
+ def valid_signature?(**kwargs)
103
+ validate_signature(**kwargs).empty?
101
104
  end
102
105
 
103
106
  # Sign this object.
data/lib/saml2/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SAML2
4
- VERSION = '3.0.11'
4
+ VERSION = '3.1.0'
5
5
  end
@@ -0,0 +1 @@
1
+ <Response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="_f0b41a2d2b524b8c92027ef426b3cf3b" Version="2.0" IssueInstant="2021-08-02T16:53:20Z" Destination="https://whartononline.instructure.com/login/saml" xmlns="urn:oasis:names:tc:SAML:2.0:protocol"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://test.pep.siemens.knowledgeanywhere.com</Issuer><Status><StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></Status><Assertion Version="2.0" ID="_8be8f34e76e14fe3a990dcc00e60d13d" IssueInstant="2021-08-02T16:53:20Z" xmlns="urn:oasis:names:tc:SAML:2.0:assertion"><Issuer>https://test.pep.siemens.knowledgeanywhere.com</Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI="#_8be8f34e76e14fe3a990dcc00e60d13d"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>VQGa66oHsKF7zCXzob8WTCBLHUI=</DigestValue></Reference></SignedInfo><SignatureValue>S+He/m2bwH+QiGmR/eH9K71TXCQ2hC8BI5pgKWyN3gFfCfEvUYgnksXu/oE1wVxvpp2dtWPiT/8vejTJWfl4Yz8VT8gDcH9xKWhPzsHba8qCHui3U486dR1SwkwII3H0cK0Xhr/dKvco85QPAUsZTKZ+dq5HdeLu6n6aERzC/136iuw/eyCFDfFS2E3dfTHd2u+E9uUh6wI/nCpSzxMxQw9Wz9i941jdLKovuqILEVbbV33sg9VZRFNAs+D92YYiegKnnBBP2+H2fXFA2Wc5uP8yAAsrFMvHA7R1tIP7jci8vEplK/Hlkdta8KQVq4fIKsXUeYu17uNFfbSIXkL5oQ==</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>vyYuRxa/PI/r3N4lxOxL/O1OtwYexfmY3mF6mvjMr4YD762UZWNNhLZsFaWqUs3HQQwAZCg0sU65s4a+t4XANuRX1k3E1oPHBmqfLZ8spBeRfyHxc0KSah10RFfCO1LZdte2Avtpvh5RMkGkMhbWFAYQmc24u6sHvI1OVUkKmBiwss6QJAi6bttMsp0VNuW7TmmsPjxmaq5dddSryLHpVP/VbnnLu4fWOnLBMTtDY37l6ZAyqKW8PEaXWSFJSjGlA+9D76+bf5C+snk0yooARZduPny5biXtoHbC3DnPDBbCN70bvkxGNqMBiqPQrI2B7zZUXA1nDiK/iHPxl5X3ZQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue></KeyInfo></Signature><Subject><NameID>chad@instructure.com</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData Recipient="https://whartononline.instructure.com/login/saml" NotOnOrAfter="2021-08-02T16:58:20Z" /></SubjectConfirmation></Subject><Conditions NotBefore="2021-08-02T16:48:20Z" NotOnOrAfter="2021-08-02T16:58:20Z"><AudienceRestriction><Audience>http://whartononline.instructure.com/saml2</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant="2021-08-02T16:53:20Z"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name="FIRST_NAME"><AttributeValue>Chad</AttributeValue></Attribute><Attribute Name="LAST_NAME"><AttributeValue>Broadhead</AttributeValue></Attribute><Attribute Name="EMAIL"><AttributeValue>chad@instructure.com</AttributeValue></Attribute><Attribute Name="UserGuid"><AttributeValue>c555db14-fc81-4836-98de-62f592f36ede</AttributeValue></Attribute></AttributeStatement></Assertion></Response>
data/spec/lib/key_spec.rb CHANGED
@@ -9,5 +9,15 @@ module SAML2
9
9
  expect(KeyInfo.format_fingerprint("\u200F abcdefghijklmnop 1234567890-\n a1")).to eq("ab:cd:ef:12:34:56:78:90:a1")
10
10
  end
11
11
  end
12
+
13
+ describe '#certificate' do
14
+ it "doesn't asplode if the keyinfo is just an rsa key value" do
15
+ response = Nokogiri::XML(fixture("response_with_rsa_key_value.xml"))
16
+ key = KeyInfo.from_xml(response.at_xpath('//dsig:KeyInfo', Namespaces::ALL))
17
+ expect(key.certificate).to be_nil
18
+ expect(key.fingerprint).to be_nil
19
+ expect(key.key).not_to be_nil
20
+ end
21
+ end
12
22
  end
13
23
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ module SAML2
6
+ describe Signable do
7
+ describe '#valid_signature?' do
8
+ it 'can work with an explicit key from metadata' do
9
+ response = Response.parse(fixture("response_with_rsa_key_value.xml"))
10
+ key = response.assertions.first.signing_key.key
11
+ expect(response.assertions.first.valid_signature?(key: [key])).to eq true
12
+ end
13
+ end
14
+ end
15
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'byebug'
3
4
  require 'saml2'
4
5
 
5
6
  def fixture(name)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saml2
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.11
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-08 00:00:00.000000000 Z
11
+ date: 2021-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -76,14 +76,14 @@ dependencies:
76
76
  requirements:
77
77
  - - "~>"
78
78
  - !ruby/object:Gem::Version
79
- version: '10.0'
79
+ version: '11.0'
80
80
  type: :development
81
81
  prerelease: false
82
82
  version_requirements: !ruby/object:Gem::Requirement
83
83
  requirements:
84
84
  - - "~>"
85
85
  - !ruby/object:Gem::Version
86
- version: '10.0'
86
+ version: '11.0'
87
87
  - !ruby/object:Gem::Dependency
88
88
  name: rake
89
89
  requirement: !ruby/object:Gem::Requirement
@@ -197,6 +197,7 @@ files:
197
197
  - spec/fixtures/response_tampered_signature.xml
198
198
  - spec/fixtures/response_with_attribute_signed.xml
199
199
  - spec/fixtures/response_with_encrypted_assertion.xml
200
+ - spec/fixtures/response_with_rsa_key_value.xml
200
201
  - spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml
201
202
  - spec/fixtures/response_without_keyinfo.xml
202
203
  - spec/fixtures/service_provider.xml
@@ -222,6 +223,7 @@ files:
222
223
  - spec/lib/message_spec.rb
223
224
  - spec/lib/response_spec.rb
224
225
  - spec/lib/service_provider_spec.rb
226
+ - spec/lib/signable_spec.rb
225
227
  - spec/spec_helper.rb
226
228
  homepage: https://github.com/instructure/ruby-saml2
227
229
  licenses:
@@ -242,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
244
  - !ruby/object:Gem::Version
243
245
  version: '0'
244
246
  requirements: []
245
- rubygems_version: 3.1.4
247
+ rubygems_version: 3.2.24
246
248
  signing_key:
247
249
  specification_version: 4
248
250
  summary: SAML 2.0 Library
@@ -251,6 +253,7 @@ test_files:
251
253
  - spec/lib/logout_response_spec.rb
252
254
  - spec/lib/indexed_object_spec.rb
253
255
  - spec/lib/attribute_spec.rb
256
+ - spec/lib/signable_spec.rb
254
257
  - spec/lib/entity_spec.rb
255
258
  - spec/lib/attribute_consuming_service_spec.rb
256
259
  - spec/lib/key_spec.rb
@@ -271,6 +274,7 @@ test_files:
271
274
  - spec/fixtures/xml_missigned_assertion.xml
272
275
  - spec/fixtures/certificate.pem
273
276
  - spec/fixtures/noconditions_response.xml
277
+ - spec/fixtures/response_with_rsa_key_value.xml
274
278
  - spec/fixtures/entities.xml
275
279
  - spec/fixtures/response_assertion_signed_reffed_from_response.xml
276
280
  - spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml