saml2 3.0.8 → 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: 1dff92fc9d0d5ebac2fdef1ec37c5359a0d7bbde83d0d6faa0a8dbacedcf538a
4
- data.tar.gz: 3222ef01c4bb421bf27df8e308b64a03fc8c31749283f3ade35d64d425667bcf
3
+ metadata.gz: 1a9f01162da7309af02b28de55593de014c32eba85d87a7af95360423cd03550
4
+ data.tar.gz: 71b3e1f1309c840e649e8d6a3dfad701b01349c5c8b3c3378b96733ef62718cf
5
5
  SHA512:
6
- metadata.gz: 1e4dd06e08cc35fb4e7b6a519d66c097bb0c94fab0c8a6a6bc5a417772c737ea03c577f68f69ce49a271e091b48dad7efb9f664f05c11b8a7b961ad965b8a83a
7
- data.tar.gz: 44edac0a66662fc20cde06931927bed8f308d0842e03de1223bb4c73d7defbb016dbd6f55b37c1f2a5de1e8c0800413db4f7175c77a1b7a80b7763ee8557b831
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
@@ -13,6 +13,8 @@ module SAML2
13
13
  attr_reader :assertions
14
14
 
15
15
  # Respond to an {AuthnRequest}
16
+ #
17
+ # {AuthnRequest#resolve} needs to have been previously called on the {AuthnRequest}.
16
18
  # @param authn_request [AuthnRequest]
17
19
  # @param issuer [NameID]
18
20
  # @param name_id [NameID] The Subject
@@ -125,14 +127,16 @@ module SAML2
125
127
  return errors
126
128
  end
127
129
 
128
- certificates = idp.signing_keys.map(&:certificate)
129
- 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?
130
133
  errors << "could not find certificate to validate message"
131
134
  return errors
132
135
  end
133
136
 
134
137
  if signed?
135
- unless (signature_errors = validate_signature(fingerprint: idp.fingerprints,
138
+ unless (signature_errors = validate_signature(key: keys,
139
+ fingerprint: idp.fingerprints,
136
140
  cert: certificates)).empty?
137
141
  return errors.concat(signature_errors)
138
142
  end
@@ -143,7 +147,8 @@ module SAML2
143
147
 
144
148
  # this might be nil, if the assertion was encrypted
145
149
  if assertion&.signed?
146
- unless (signature_errors = assertion.validate_signature(fingerprint: idp.fingerprints,
150
+ unless (signature_errors = assertion.validate_signature(key: keys,
151
+ fingerprint: idp.fingerprints,
147
152
  cert: certificates)).empty?
148
153
  return errors.concat(signature_errors)
149
154
  end
@@ -20,6 +20,8 @@ module SAML2
20
20
  # (see Base#from_xml)
21
21
  def from_xml(node)
22
22
  super
23
+ remove_instance_variable(:@authn_requests_signed)
24
+ remove_instance_variable(:@want_assertions_signed)
23
25
  @assertion_consumer_services = nil
24
26
  @attribute_consuming_services = nil
25
27
  end
@@ -7,16 +7,16 @@ module SAML2
7
7
  # @return [Nokogiri::XML::Element, nil]
8
8
  def signature
9
9
  unless instance_variable_defined?(:@signature)
10
- @signature = xml.at_xpath('dsig:Signature', Namespaces::ALL)
11
- if @signature
12
- signed_node = @signature.at_xpath('dsig:SignedInfo/dsig:Reference', Namespaces::ALL)['URI']
10
+ @signature = xml.xpath('//dsig:Signature', Namespaces::ALL).find do |signature|
11
+ signed_node = signature.at_xpath('dsig:SignedInfo/dsig:Reference', Namespaces::ALL)['URI']
13
12
  if signed_node == ''
14
- @signature = nil unless xml == xml.document.root
13
+ true if xml == xml.document.root
15
14
  elsif signed_node != "##{xml['ID']}"
16
- @signature = nil
15
+ false
17
16
  else
18
17
  # validating the schema will automatically add ID attributes, so check that first
19
18
  xml.set_id_attribute('ID') unless xml.document.get_id(xml['ID'])
19
+ true
20
20
  end
21
21
  end
22
22
  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.8'
4
+ VERSION = '3.1.0'
5
5
  end
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0"?>
2
2
  <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://sso.school.edu/idp/shibboleth">
3
- <IDPSSODescriptor protocolSupportEnumeration="urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
3
+ <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
4
4
  <KeyDescriptor use="signing">
5
5
  <ds:KeyInfo>
6
6
  <ds:X509Data>
@@ -0,0 +1,6 @@
1
+ <samlp:Response ID="eppcgfbmldefddomokfgiljnkflhppmoflakahld" IssueInstant="2020-08-11T18:19:49Z" Destination="https://wscc.instructure.com/login/saml" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI="#enmnbnkdhfhnbjeifihomffcoanmnjdaocnhgnhc"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>cyBkaF5MxEOSX9hLm0g/BWMJpQA=</DigestValue></Reference></SignedInfo><SignatureValue>BqXuyorfBboZI3sSSi4PC3GnJMKyLSQ/897M1RYmgVHx8Pbg1ANy75mpjRQQxGOIz/nSTh6eTPkkFEAT34nhxBSd+JfHof0RfLl/lBI1klSmpi/YoHCKLdVt+iwAemmBNw5Rxw59EepgrbcVtgjsjWISdvMyY7Wqb3nyJDwTGWw=</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>yPxoJ9DLOTzn9j91xlqGTX/8Hs5hxjImPalS9qTOc6BYJgXSC7HtxBLMc0usJG58/OaHgWFlaDi4HSBlZe2vLzecaWL1HYxJtW6s+UpD5i+uoxGTPM1ITNlZudGQblh3XTUESrPUZVwSt1N+Vqd4AUHux0E078meTqj9+EMcgsk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>MIIB4TCCAU6gAwIBAgIQhv64tDcg/45BI6qmDbJfKDAJBgUrDgMCHQUAMA8xDTALBgNVBAMTBFRFU1QwIBcNMjAwMTI3MTkxNzMxWhgPMjA4MDEyMzEwNTAwMDBaMA8xDTALBgNVBAMTBFRFU1QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMj8aCfQyzk85/Y/dcZahk1//B7OYcYyJj2pUvakznOgWCYF0gux7cQSzHNLrCRufPzmh4FhZWg4uB0gZWXtry83nGli9R2MSbVurPlKQ+YvrqMRkzzNSEzZWbnRkG5Yd101BEqz1GVcErdTflaneAFB7sdBNO/Jnk6o/fhDHILJAgMBAAGjRDBCMEAGA1UdAQQ5MDeAEFm8dl7/zBigioh82gZb6WGhETAPMQ0wCwYDVQQDEwRURVNUghCG/ri0NyD/jkEjqqYNsl8oMAkGBSsOAwIdBQADgYEAotOROUrAiZr7oA3iaZLxq+B6sN+JdWSBquvDUzaMgIWRvUBZPqmOKpXK0+XSLXChgklpVXBXAo78Juy0zza/ZAMyGPbYlSZSME6GlApjp8hi6wi0ti/usi/D8SQSJ9ephwz2JAvI5WP16PzIruYUlf3uI72hKT0NW8Pl3PhT8z8=</X509Certificate></X509Data></KeyInfo></Signature><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status><Assertion ID="enmnbnkdhfhnbjeifihomffcoanmnjdaocnhgnhc" IssueInstant="2020-08-11T18:19:49Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion"><Issuer>
2
+ https://my.wscc.edu/idp
3
+ </Issuer><Subject><NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">narnold@wscc.edu</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData Recipient="" NotOnOrAfter="2020-08-11T18:29:49Z" InResponseTo="_bd878908-34c0-4e6e-b429-90cc8bfae27c" /></SubjectConfirmation></Subject><Conditions NotBefore="2020-08-11T18:14:49Z" NotOnOrAfter="2020-08-11T18:29:49Z"><AudienceRestriction><Audience>http://wscc.instructure.com/saml2</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name="email"><AttributeValue>narnold@wscc.edu</AttributeValue></Attribute><Attribute Name="display_name"><AttributeValue>Nicholas Arnold</AttributeValue></Attribute><Attribute Name="given_name"><AttributeValue>Nicholas</AttributeValue></Attribute><Attribute Name="integration_id"><AttributeValue>Ed18RSTYO0ivqnZuzQPehQ==</AttributeValue></Attribute><Attribute Name="sis_user_id"><AttributeValue>0097365</AttributeValue></Attribute><Attribute Name="sortable_name"><AttributeValue>Arnold, Nicholas</AttributeValue></Attribute><Attribute Name="surname"><AttributeValue>Arnold</AttributeValue></Attribute><Attribute Name="time_zone"><AttributeValue>US/Eastern</AttributeValue></Attribute></AttributeStatement><AuthnStatement AuthnInstant="2020-08-11T18:19:49Z"><AuthnContext><AuthnContextClassRef>
4
+ urn:oasis:names:tc:SAML:2.0:ac:classes:Password
5
+ </AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion></samlp:Response>
6
+
@@ -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>
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0"?>
2
2
  <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://siteadmin.instructure.com/saml2" ID="unique">
3
- <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
3
+ <SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
4
4
 
5
5
  <KeyDescriptor use="encryption">
6
6
  <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../../spec_helper'
2
4
 
3
5
  require 'openssl'
@@ -86,10 +88,10 @@ module SAML2
86
88
  end
87
89
 
88
90
  it "raises on unsupported signature algorithm" do
89
- x = url
91
+ x = url.dup
90
92
  # SigAlg is now sha10
91
93
  x << "0"
92
- expect { Bindings::HTTPRedirect.decode(url, public_key: certificate) }.to raise_error(UnsupportedSignatureAlgorithm)
94
+ expect { Bindings::HTTPRedirect.decode(x, public_key: certificate) }.to raise_error(UnsupportedSignatureAlgorithm)
93
95
  end
94
96
 
95
97
  it "allows the caller to detect an unsigned message" do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -32,6 +34,10 @@ module SAML2
32
34
  it "should find the signing certificate" do
33
35
  expect(idp.keys.first.x509).to match(/MIIE8TCCA9mgAwIBAgIJAITusxON60cKMA0GCSqGSIb3DQEBBQUAMIGrMQswCQYD/)
34
36
  end
37
+
38
+ it "loads identity provider attributes" do
39
+ expect(idp.want_authn_requests_signed?).to be_truthy
40
+ end
35
41
  end
36
42
  end
37
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
data/spec/lib/key_spec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -7,5 +9,15 @@ module SAML2
7
9
  expect(KeyInfo.format_fingerprint("\u200F abcdefghijklmnop 1234567890-\n a1")).to eq("ab:cd:ef:12:34:56:78:90:a1")
8
10
  end
9
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
10
22
  end
11
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -272,6 +274,20 @@ MIIB/jCCAWegAwIBAgIBCjANBgkqhkiG9w0BAQQFADAkMSIwIAYDVQQDExlhZGRlcjEuaXRzLnVuaW1l
272
274
  expect(response.errors).to eq []
273
275
  expect(response.assertions.first.subject.name_id.id).to eq 'testuserint.sso@staff.oimtest.unimelb.edu.au'
274
276
  end
277
+
278
+ it "finds signatures the sign the assertion, not inside the assertion" do
279
+ response = Response.parse(fixture("response_assertion_signed_reffed_from_response.xml"))
280
+ sp_entity.entity_id = 'http://wscc.instructure.com/saml2'
281
+ idp_entity.entity_id = 'https://my.wscc.edu/idp'
282
+ idp_entity.identity_providers.first.keys.clear
283
+ idp_entity.identity_providers.first.fingerprints << "c4f473274116a3cbc295c3abf77c7ed1ade9b904"
284
+
285
+ sp_entity.valid_response?(response, idp_entity, verification_time: response.issue_instant)
286
+ expect(response.errors).to eq []
287
+ expect(response.assertions.first.subject.name_id.id).to eq 'narnold@wscc.edu'
288
+ expect(response).not_to be_signed
289
+ expect(response.assertions.first).to be_signed
290
+ end
275
291
  end
276
292
  end
277
293
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
  module SAML2
@@ -64,6 +66,11 @@ module SAML2
64
66
  expect(sp.keys.first.encryption_methods.first.algorithm).to eq KeyDescriptor::EncryptionMethod::Algorithm::AES128_CBC
65
67
  expect(sp.keys.first.encryption_methods.first.key_size).to eq 128
66
68
  end
69
+
70
+ it "loads service provider attributes" do
71
+ expect(sp.authn_requests_signed?).to be_truthy
72
+ expect(sp.want_assertions_signed?).to be_truthy
73
+ end
67
74
  end
68
75
  end
69
76
  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,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'byebug'
1
4
  require 'saml2'
2
5
 
3
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.8
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-30 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
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 1.5.8
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '1.11'
22
+ version: '1.12'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 1.5.8
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.11'
32
+ version: '1.12'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: nokogiri-xmlsec-instructure
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -59,7 +59,7 @@ dependencies:
59
59
  version: '3.2'
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
- version: '6.1'
62
+ version: '6.2'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
@@ -69,21 +69,21 @@ dependencies:
69
69
  version: '3.2'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '6.1'
72
+ version: '6.2'
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: byebug
75
75
  requirement: !ruby/object:Gem::Requirement
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
@@ -191,11 +191,13 @@ files:
191
191
  - spec/fixtures/noconditions_response.xml
192
192
  - spec/fixtures/othercertificate.pem
193
193
  - spec/fixtures/privatekey.key
194
+ - spec/fixtures/response_assertion_signed_reffed_from_response.xml
194
195
  - spec/fixtures/response_signed.xml
195
196
  - spec/fixtures/response_tampered_certificate.xml
196
197
  - spec/fixtures/response_tampered_signature.xml
197
198
  - spec/fixtures/response_with_attribute_signed.xml
198
199
  - spec/fixtures/response_with_encrypted_assertion.xml
200
+ - spec/fixtures/response_with_rsa_key_value.xml
199
201
  - spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml
200
202
  - spec/fixtures/response_without_keyinfo.xml
201
203
  - spec/fixtures/service_provider.xml
@@ -221,12 +223,13 @@ files:
221
223
  - spec/lib/message_spec.rb
222
224
  - spec/lib/response_spec.rb
223
225
  - spec/lib/service_provider_spec.rb
226
+ - spec/lib/signable_spec.rb
224
227
  - spec/spec_helper.rb
225
228
  homepage: https://github.com/instructure/ruby-saml2
226
229
  licenses:
227
230
  - MIT
228
231
  metadata: {}
229
- post_install_message:
232
+ post_install_message:
230
233
  rdoc_options: []
231
234
  require_paths:
232
235
  - lib
@@ -241,8 +244,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
241
244
  - !ruby/object:Gem::Version
242
245
  version: '0'
243
246
  requirements: []
244
- rubygems_version: 3.0.3
245
- signing_key:
247
+ rubygems_version: 3.2.24
248
+ signing_key:
246
249
  specification_version: 4
247
250
  summary: SAML 2.0 Library
248
251
  test_files:
@@ -250,6 +253,7 @@ test_files:
250
253
  - spec/lib/logout_response_spec.rb
251
254
  - spec/lib/indexed_object_spec.rb
252
255
  - spec/lib/attribute_spec.rb
256
+ - spec/lib/signable_spec.rb
253
257
  - spec/lib/entity_spec.rb
254
258
  - spec/lib/attribute_consuming_service_spec.rb
255
259
  - spec/lib/key_spec.rb
@@ -270,7 +274,9 @@ test_files:
270
274
  - spec/fixtures/xml_missigned_assertion.xml
271
275
  - spec/fixtures/certificate.pem
272
276
  - spec/fixtures/noconditions_response.xml
277
+ - spec/fixtures/response_with_rsa_key_value.xml
273
278
  - spec/fixtures/entities.xml
279
+ - spec/fixtures/response_assertion_signed_reffed_from_response.xml
274
280
  - spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml
275
281
  - spec/fixtures/response_without_keyinfo.xml
276
282
  - spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml