saml2 3.0.8 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/saml2/key.rb +38 -4
- data/lib/saml2/response.rb +9 -4
- data/lib/saml2/service_provider.rb +2 -0
- data/lib/saml2/signable.rb +27 -24
- data/lib/saml2/version.rb +1 -1
- data/spec/fixtures/identity_provider.xml +1 -1
- data/spec/fixtures/response_assertion_signed_reffed_from_response.xml +6 -0
- data/spec/fixtures/response_with_rsa_key_value.xml +1 -0
- data/spec/fixtures/service_provider.xml +1 -1
- data/spec/lib/attribute_consuming_service_spec.rb +2 -0
- data/spec/lib/attribute_spec.rb +2 -0
- data/spec/lib/authn_request_spec.rb +2 -0
- data/spec/lib/bindings/http_redirect_spec.rb +4 -2
- data/spec/lib/conditions_spec.rb +2 -0
- data/spec/lib/entity_spec.rb +2 -0
- data/spec/lib/identity_provider_spec.rb +6 -0
- data/spec/lib/indexed_object_spec.rb +2 -0
- data/spec/lib/key_spec.rb +12 -0
- data/spec/lib/logout_request_spec.rb +2 -0
- data/spec/lib/logout_response_spec.rb +2 -0
- data/spec/lib/message_spec.rb +2 -0
- data/spec/lib/response_spec.rb +16 -0
- data/spec/lib/service_provider_spec.rb +7 -0
- data/spec/lib/signable_spec.rb +15 -0
- data/spec/spec_helper.rb +3 -0
- metadata +18 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a9f01162da7309af02b28de55593de014c32eba85d87a7af95360423cd03550
|
4
|
+
data.tar.gz: 71b3e1f1309c840e649e8d6a3dfad701b01349c5c8b3c3378b96733ef62718cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
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
|
-
|
51
|
-
|
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
|
data/lib/saml2/response.rb
CHANGED
@@ -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
|
-
|
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(
|
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(
|
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
|
data/lib/saml2/signable.rb
CHANGED
@@ -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.
|
11
|
-
|
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
|
-
|
13
|
+
true if xml == xml.document.root
|
15
14
|
elsif signed_node != "##{xml['ID']}"
|
16
|
-
|
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(
|
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
|
-
#
|
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
|
49
|
-
#
|
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
|
-
#
|
53
|
-
# a match
|
54
|
-
#
|
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 =
|
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
|
-
|
75
|
-
|
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?
|
80
|
-
|
82
|
+
if signing_key.nil?
|
83
|
+
verification_key = trusted_keys.first
|
81
84
|
end
|
82
85
|
|
83
|
-
return ["no trusted signing key found"] if
|
86
|
+
return ["no trusted signing key found"] if verification_key.nil?
|
84
87
|
|
85
88
|
begin
|
86
|
-
result = signature.verify_with(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?(
|
100
|
-
validate_signature(
|
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,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#">
|
data/spec/lib/attribute_spec.rb
CHANGED
@@ -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(
|
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
|
data/spec/lib/conditions_spec.rb
CHANGED
data/spec/lib/entity_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
|
@@ -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
|
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
|
data/spec/lib/message_spec.rb
CHANGED
data/spec/lib/response_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
|
@@ -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
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
|
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:
|
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.
|
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.
|
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.
|
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.
|
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: '
|
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: '
|
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.
|
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
|