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 +4 -4
- data/lib/saml2/key.rb +38 -4
- data/lib/saml2/response.rb +7 -4
- data/lib/saml2/signable.rb +22 -19
- data/lib/saml2/version.rb +1 -1
- data/spec/fixtures/response_with_rsa_key_value.xml +1 -0
- data/spec/lib/key_spec.rb +10 -0
- data/spec/lib/signable_spec.rb +15 -0
- data/spec/spec_helper.rb +1 -0
- metadata +9 -5
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
@@ -127,14 +127,16 @@ module SAML2
|
|
127
127
|
return errors
|
128
128
|
end
|
129
129
|
|
130
|
-
certificates = idp.signing_keys.map(&:certificate)
|
131
|
-
|
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(
|
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(
|
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
|
data/lib/saml2/signable.rb
CHANGED
@@ -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
@@ -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
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
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
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: '
|
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
|
@@ -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.
|
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
|