saml2 2.2.12 → 3.0.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/message.rb +0 -16
- data/lib/saml2/response.rb +3 -16
- data/lib/saml2/signable.rb +21 -38
- data/lib/saml2/version.rb +1 -1
- data/spec/lib/response_spec.rb +4 -43
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e68ac85048800a528987268cfb09cf9ed3adf9e501e14927969970eba7b48d53
|
4
|
+
data.tar.gz: 87edd68a1925ef511840fbff7003cce962ab3fc11bd47e3a9a74cb2300e98136
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45163bf19f9acbf26223766b165d40d62ad8f0fac6fadad901254912064c56db830c66e27a2e2d85bcd07c2be84c62156df516ae35c028b60b4b4ea692ae2645
|
7
|
+
data.tar.gz: 8e7d1b05ea37fc9c39024cfc1c2812170ef09fd4ead00b7b6c174cc9fbf8b0f3e8a8f5c4883ee9257bccfcca79482ffef1d07eac320147f69fed00bd60017480
|
data/lib/saml2/message.rb
CHANGED
@@ -119,22 +119,6 @@ module SAML2
|
|
119
119
|
true
|
120
120
|
end
|
121
121
|
|
122
|
-
# (see Signable#validate_signature)
|
123
|
-
# @param verification_time
|
124
|
-
# Ignored. The message's {issue_instant} is always used.
|
125
|
-
def validate_signature(fingerprint: nil,
|
126
|
-
cert: nil,
|
127
|
-
verification_time: issue_instant,
|
128
|
-
allow_expired_certificate: false,
|
129
|
-
verify_certificate: true)
|
130
|
-
# verify the signature (certificate's validity) as of the time the message was generated
|
131
|
-
super(fingerprint: fingerprint,
|
132
|
-
cert: cert,
|
133
|
-
verification_time: verification_time,
|
134
|
-
allow_expired_certificate: allow_expired_certificate,
|
135
|
-
verify_certificate: verify_certificate)
|
136
|
-
end
|
137
|
-
|
138
122
|
# (see Signable#sign)
|
139
123
|
def sign(x509_certificate, private_key, algorithm_name = :sha256)
|
140
124
|
super
|
data/lib/saml2/response.rb
CHANGED
@@ -89,18 +89,11 @@ module SAML2
|
|
89
89
|
# @param verification_time optional [DateTime]
|
90
90
|
# Validate timestamps (signing certificate validity, issued at, etc.) as of
|
91
91
|
# this point in time.
|
92
|
-
# @param allow_expired_certificate optional [true, false]
|
93
|
-
# Allow signing certificate to be expired.
|
94
|
-
# @param verify_certificate optional [true, false]
|
95
|
-
# Don't validate the trust chain or validity dates of the signing
|
96
|
-
# certificate.
|
97
92
|
# @param ignore_audience_condition optional [true, false]
|
98
93
|
# Don't validate any Audience conditions.
|
99
94
|
def validate(service_provider:,
|
100
95
|
identity_provider:,
|
101
96
|
verification_time: nil,
|
102
|
-
allow_expired_certificate: false,
|
103
|
-
verify_certificate: true,
|
104
97
|
ignore_audience_condition: false)
|
105
98
|
raise ArgumentError, "service_provider should be an Entity object" unless service_provider.is_a?(Entity)
|
106
99
|
raise ArgumentError, "service_provider should have at least one service_provider role" unless (sp = service_provider.service_providers.first)
|
@@ -140,9 +133,7 @@ module SAML2
|
|
140
133
|
|
141
134
|
if signed?
|
142
135
|
unless (signature_errors = validate_signature(fingerprint: idp.fingerprints,
|
143
|
-
cert: certificates
|
144
|
-
allow_expired_certificate: allow_expired_certificate,
|
145
|
-
verify_certificate: verify_certificate)).empty?
|
136
|
+
cert: certificates)).empty?
|
146
137
|
return errors.concat(signature_errors)
|
147
138
|
end
|
148
139
|
response_signed = true
|
@@ -153,9 +144,7 @@ module SAML2
|
|
153
144
|
# this might be nil, if the assertion was encrypted
|
154
145
|
if assertion&.signed?
|
155
146
|
unless (signature_errors = assertion.validate_signature(fingerprint: idp.fingerprints,
|
156
|
-
cert: certificates
|
157
|
-
allow_expired_certificate: allow_expired_certificate,
|
158
|
-
verify_certificate: verify_certificate)).empty?
|
147
|
+
cert: certificates)).empty?
|
159
148
|
return errors.concat(signature_errors)
|
160
149
|
end
|
161
150
|
assertion_signed = true
|
@@ -211,9 +200,7 @@ module SAML2
|
|
211
200
|
# check it now
|
212
201
|
if assertion.signed? && !assertion_signed
|
213
202
|
unless (signature_errors = assertion.validate_signature(fingerprint: idp.fingerprints,
|
214
|
-
cert: certificates
|
215
|
-
allow_expired_certificate: allow_expired_certificate,
|
216
|
-
verify_certificate: verify_certificate)).empty?
|
203
|
+
cert: certificates)).empty?
|
217
204
|
return errors.concat(signature_errors)
|
218
205
|
end
|
219
206
|
assertion_signed = true
|
data/lib/saml2/signable.rb
CHANGED
@@ -34,24 +34,24 @@ module SAML2
|
|
34
34
|
|
35
35
|
# Validate the signature on this object.
|
36
36
|
#
|
37
|
-
#
|
37
|
+
# At least one of +key+, +fingerprint+ or +cert+ must be provided.
|
38
38
|
#
|
39
|
+
# @param key optional [String, OpenSSL::PKey::PKey]
|
40
|
+
# The exact public key to use. +fingerprint+ and +cert+ are ignored.
|
39
41
|
# @param fingerprint optional [Array<String>, String]
|
40
42
|
# SHA1 fingerprints of trusted certificates. If provided, they will be
|
41
43
|
# checked against the {#signing_key} embedded in the {#signature}, and if
|
42
44
|
# a match is found, the certificate embedded in the signature will be
|
43
45
|
# added to the list of certificates used for verifying the signature.
|
44
46
|
# @param cert optional [Array<String>, String]
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
47
|
+
# A single or array of trusted certificates. If provided, they will be
|
48
|
+
# check against the {#signing_key} embedded in the {#signature}, and if
|
49
|
+
# a match of public keys only is found, that key will be considered trusted
|
50
|
+
# and used to verify the signature.
|
49
51
|
# @return [Array<String>] An empty array on success, details of errors on failure.
|
50
|
-
def validate_signature(
|
51
|
-
|
52
|
-
|
53
|
-
allow_expired_certificate: false,
|
54
|
-
verify_certificate: true)
|
52
|
+
def validate_signature(key: nil,
|
53
|
+
fingerprint: nil,
|
54
|
+
cert: nil)
|
55
55
|
return ["not signed"] unless signed?
|
56
56
|
|
57
57
|
certs = Array(cert)
|
@@ -59,39 +59,22 @@ module SAML2
|
|
59
59
|
# see if any given fingerprints match the certificate embedded in the XML;
|
60
60
|
# if so, extract the certificate, and add it to the allowed certificates list
|
61
61
|
Array(fingerprint).each do |fp|
|
62
|
-
certs << signing_key.certificate if signing_key&.fingerprint == KeyInfo.format_fingerprint(fp)
|
62
|
+
certs << signing_key.certificate if signing_key&.fingerprint == SAML2::KeyInfo.format_fingerprint(fp)
|
63
63
|
end
|
64
64
|
certs = certs.uniq
|
65
|
-
return ["no trusted certificate found"] if certs.empty?
|
66
65
|
|
67
|
-
|
68
|
-
|
66
|
+
trusted_keys = certs.map do |cert|
|
67
|
+
cert = cert.is_a?(String) ? OpenSSL::X509::Certificate.new(cert) : cert
|
68
|
+
cert.public_key.to_s
|
69
69
|
end
|
70
|
-
|
71
|
-
|
72
|
-
signing_cert = signing_key.certificate
|
73
|
-
if allow_expired_certificate
|
74
|
-
verification_time = signing_cert.not_after - 1
|
75
|
-
end
|
76
|
-
|
77
|
-
# we explicitly trust the signing certificate, but it's not self-signed;
|
78
|
-
# xmlsec is weird and decides not to trust it in that case, so we skip
|
79
|
-
# certificate verification by xmlsec, and do it ourselves
|
80
|
-
if certs.include?(signing_cert) && signing_cert.issuer != signing_cert.subject
|
81
|
-
verification_time ||= Time.now.utc
|
82
|
-
return ["certificate has expired"] if verification_time > signing_cert.not_after
|
83
|
-
return ["certificate is not yet valid"] if verification_time < signing_cert.not_before
|
84
|
-
|
85
|
-
verify_certificate = false
|
86
|
-
end
|
70
|
+
if signing_key&.certificate && trusted_keys.include?(signing_key.certificate.public_key.to_s)
|
71
|
+
key ||= signing_key.certificate.public_key.to_s
|
87
72
|
end
|
88
|
-
|
73
|
+
|
74
|
+
return ["no trusted signing key found"] if key.nil?
|
89
75
|
|
90
76
|
begin
|
91
|
-
result = signature.verify_with(key: key
|
92
|
-
certs: certs,
|
93
|
-
verification_time: verification_time,
|
94
|
-
verify_certificates: verify_certificate)
|
77
|
+
result = signature.verify_with(key: key)
|
95
78
|
result ? [] : ["signature is invalid"]
|
96
79
|
rescue XMLSec::VerificationError => e
|
97
80
|
[e.message]
|
@@ -104,8 +87,8 @@ module SAML2
|
|
104
87
|
#
|
105
88
|
# @param (see #validate_signature)
|
106
89
|
# @return [Boolean]
|
107
|
-
def valid_signature?(fingerprint: nil, cert: nil
|
108
|
-
validate_signature(fingerprint: fingerprint, cert: cert
|
90
|
+
def valid_signature?(fingerprint: nil, cert: nil)
|
91
|
+
validate_signature(fingerprint: fingerprint, cert: cert).empty?
|
109
92
|
end
|
110
93
|
|
111
94
|
# Sign this object.
|
data/lib/saml2/version.rb
CHANGED
data/spec/lib/response_spec.rb
CHANGED
@@ -104,8 +104,7 @@ module SAML2
|
|
104
104
|
|
105
105
|
# the signature is still valid (we have to set a weird verification time because the response
|
106
106
|
# was signed with an expired signature)
|
107
|
-
expect(response.validate_signature(fingerprint: 'afe71c28ef740bc87425be13a2263d37971da1f9'
|
108
|
-
verification_time: Time.parse("2007-07-14 12:01:34Z"))).to eq []
|
107
|
+
expect(response.validate_signature(fingerprint: 'afe71c28ef740bc87425be13a2263d37971da1f9')).to eq []
|
109
108
|
|
110
109
|
# the comment is ignored, but doesn't truncate the nameid
|
111
110
|
expect(response.assertions.first.subject.name_id.id).to eq 'testuser@example.com'
|
@@ -175,8 +174,7 @@ module SAML2
|
|
175
174
|
|
176
175
|
response = Response.parse(fixture("response_tampered_certificate.xml"))
|
177
176
|
sp_entity.valid_response?(response, idp_entity,
|
178
|
-
verification_time: Time.parse('2015-02-12T22:51:30Z')
|
179
|
-
allow_expired_certificate: true)
|
177
|
+
verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
180
178
|
expect(response.errors).to eq ["signature is invalid"]
|
181
179
|
end
|
182
180
|
|
@@ -193,7 +191,7 @@ module SAML2
|
|
193
191
|
idp_entity.identity_providers.first.keys << KeyDescriptor.new(fixture("othercertificate.pem"))
|
194
192
|
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
195
193
|
expect(response.errors.length).to eq 1
|
196
|
-
expect(response.errors.first).to
|
194
|
+
expect(response.errors.first).to eq("no trusted signing key found")
|
197
195
|
end
|
198
196
|
|
199
197
|
it "validates signature by fingerprint" do
|
@@ -211,7 +209,7 @@ module SAML2
|
|
211
209
|
idp_entity.identity_providers.first.fingerprints << "1c:37:7d:30:c1:83:18:ea:20:8b:dc:d5:35:b6:16:85:17:58:f7:ca"
|
212
210
|
|
213
211
|
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
214
|
-
expect(response.errors).to eq ["no trusted
|
212
|
+
expect(response.errors).to eq ["no trusted signing key found"]
|
215
213
|
end
|
216
214
|
|
217
215
|
it "protects against xml signature wrapping attacks targeting nameid" do
|
@@ -254,43 +252,6 @@ module SAML2
|
|
254
252
|
"43:0: ERROR: Element '{http://www.w3.org/2000/09/xmldsig#}Signature': This element is not expected."]
|
255
253
|
end
|
256
254
|
|
257
|
-
it "errors on expired certificate" do
|
258
|
-
response = Response.parse(fixture("test6-response.xml"))
|
259
|
-
idp_entity.entity_id = 'http://simplesamlphp.dev/simplesaml/saml2/idp/metadata.php'
|
260
|
-
idp_entity.identity_providers.first.keys.clear
|
261
|
-
idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
|
262
|
-
|
263
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse("2012-08-03T20:07:15Z"))
|
264
|
-
expect(response.errors.length).to eq 1
|
265
|
-
expect(response.errors.first).to match(/certificate has expired/)
|
266
|
-
end
|
267
|
-
|
268
|
-
it "ignores expired certificate when requested" do
|
269
|
-
response = Response.parse(fixture("test6-response.xml"))
|
270
|
-
sp_entity.entity_id = 'http://shard-2.canvas.dev/saml2'
|
271
|
-
idp_entity.entity_id = 'http://simplesamlphp.dev/simplesaml/saml2/idp/metadata.php'
|
272
|
-
idp_entity.identity_providers.first.keys.clear
|
273
|
-
idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
|
274
|
-
|
275
|
-
sp_entity.valid_response?(response, idp_entity,
|
276
|
-
verification_time: Time.parse("2014-09-16T22:15:53Z"),
|
277
|
-
allow_expired_certificate: true)
|
278
|
-
expect(response.errors).to eq []
|
279
|
-
end
|
280
|
-
|
281
|
-
it "ignores invalid certificate when requested" do
|
282
|
-
response = Response.parse(fixture("test6-response.xml"))
|
283
|
-
sp_entity.entity_id = 'http://shard-2.canvas.dev/saml2'
|
284
|
-
idp_entity.entity_id = 'http://simplesamlphp.dev/simplesaml/saml2/idp/metadata.php'
|
285
|
-
idp_entity.identity_providers.first.keys.clear
|
286
|
-
idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
|
287
|
-
|
288
|
-
sp_entity.valid_response?(response, idp_entity,
|
289
|
-
verification_time: Time.parse("2014-09-16T22:15:53Z"),
|
290
|
-
verify_certificate: false)
|
291
|
-
expect(response.errors).to eq []
|
292
|
-
end
|
293
|
-
|
294
255
|
it "doesn't break the signature by decrypting elements first" do
|
295
256
|
response = Response.parse(fixture("response_with_signed_assertion_and_encrypted_subject.xml"))
|
296
257
|
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
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:
|
4
|
+
version: 3.0.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: 2018-06-
|
11
|
+
date: 2018-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|