ruby-saml 1.2.0 → 1.3.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.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/changelog.md +5 -0
- data/lib/onelogin/ruby-saml/logoutresponse.rb +11 -13
- data/lib/onelogin/ruby-saml/response.rb +71 -39
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +12 -15
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +2 -2
- data/test/logoutrequest_test.rb +2 -2
- data/test/metadata_test.rb +1 -1
- data/test/request_test.rb +1 -1
- data/test/response_test.rb +13 -0
- data/test/slo_logoutresponse_test.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b01f4f740dfe49133d17e2bfbb42a470a6a0ecd
|
4
|
+
data.tar.gz: 00f47e3eaadebe97288df5daa096a9b0f26f92a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7dd26e1057a4d9e535debae7c91bfef6a34b9d46eb04557684bd53daed1dd00ab5cc52dbe1e4dc73114b2b28d49be8cb18c15b9cab04017aba97c5d1140df2f
|
7
|
+
data.tar.gz: 6aa8e7af6e43749954e8288002d2d7daef999109520888a7f5569dae32c9e0f3c09cece48687ac4e035e61ce14fa8d63a1f22e376a0a321b1775dc7718b94a94
|
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Ruby SAML [](http://travis-ci.org/onelogin/ruby-saml) [](https://coveralls.io/r/onelogin/ruby-saml?branch=master%0A) [](http://badge.fury.io/rb/ruby-saml)
|
2
2
|
|
3
|
+
## Updating from 1.2.x to 1.3.X
|
4
|
+
|
5
|
+
Version `1.3.0` is a recommended update for all Ruby SAML users as it includes security fixes. It adds security improvements in order to prevent Signature wrapping attacks. [CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697)
|
6
|
+
|
3
7
|
## Updating from 1.1.x to 1.2.X
|
4
8
|
|
5
9
|
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, refactor error handling and some minor improvements
|
data/changelog.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# RubySaml Changelog
|
2
2
|
|
3
|
+
### 1.3.0 (June 24, 2016)
|
4
|
+
* [Security Fix](https://github.com/onelogin/ruby-saml/commit/a571f52171e6bfd87db59822d1d9e8c38fb3b995) Add extra validations to prevent Signature wrapping attacks
|
5
|
+
* Fix XMLSecurity SHA256 and SHA512 uris
|
6
|
+
* [#326](https://github.com/onelogin/ruby-saml/pull/326) Fix Destination validation
|
7
|
+
|
3
8
|
### 1.2.0 (April 29, 2016)
|
4
9
|
* [#269](https://github.com/onelogin/ruby-saml/pull/269) Refactor error handling; allow collect error messages when soft=true (normal validation stop after find first error)
|
5
10
|
* [#289](https://github.com/onelogin/ruby-saml/pull/289) Remove uuid gem in favor of SecureRandom
|
@@ -110,22 +110,20 @@ module OneLogin
|
|
110
110
|
def validate(collect_errors = false)
|
111
111
|
reset_errors!
|
112
112
|
|
113
|
-
|
114
|
-
valid_state
|
115
|
-
validate_success_status
|
116
|
-
validate_structure
|
117
|
-
valid_in_response_to
|
118
|
-
valid_issuer
|
119
|
-
validate_signature
|
113
|
+
validations = [
|
114
|
+
:valid_state?,
|
115
|
+
:validate_success_status,
|
116
|
+
:validate_structure,
|
117
|
+
:valid_in_response_to?,
|
118
|
+
:valid_issuer?,
|
119
|
+
:validate_signature
|
120
|
+
]
|
120
121
|
|
122
|
+
if collect_errors
|
123
|
+
validations.each { |validation| send(validation) }
|
121
124
|
@errors.empty?
|
122
125
|
else
|
123
|
-
|
124
|
-
validate_success_status &&
|
125
|
-
validate_structure &&
|
126
|
-
valid_in_response_to? &&
|
127
|
-
valid_issuer? &&
|
128
|
-
validate_signature
|
126
|
+
validations.all? { |validation| send(validation) }
|
129
127
|
end
|
130
128
|
end
|
131
129
|
|
@@ -173,7 +173,7 @@ module OneLogin
|
|
173
173
|
node = REXML::XPath.first(
|
174
174
|
document,
|
175
175
|
"/p:Response/p:Status/p:StatusCode",
|
176
|
-
{ "p" => PROTOCOL
|
176
|
+
{ "p" => PROTOCOL }
|
177
177
|
)
|
178
178
|
node.attributes["Value"] if node && node.attributes
|
179
179
|
end
|
@@ -186,7 +186,7 @@ module OneLogin
|
|
186
186
|
node = REXML::XPath.first(
|
187
187
|
document,
|
188
188
|
"/p:Response/p:Status/p:StatusMessage",
|
189
|
-
{ "p" => PROTOCOL
|
189
|
+
{ "p" => PROTOCOL }
|
190
190
|
)
|
191
191
|
node.text if node
|
192
192
|
end
|
@@ -290,41 +290,32 @@ module OneLogin
|
|
290
290
|
#
|
291
291
|
def validate(collect_errors = false)
|
292
292
|
reset_errors!
|
293
|
+
return false unless validate_response_state
|
294
|
+
|
295
|
+
validations = [
|
296
|
+
:validate_response_state,
|
297
|
+
:validate_version,
|
298
|
+
:validate_id,
|
299
|
+
:validate_success_status,
|
300
|
+
:validate_num_assertion,
|
301
|
+
:validate_no_encrypted_attributes,
|
302
|
+
:validate_signed_elements,
|
303
|
+
:validate_structure,
|
304
|
+
:validate_in_response_to,
|
305
|
+
:validate_conditions,
|
306
|
+
:validate_audience,
|
307
|
+
:validate_destination,
|
308
|
+
:validate_issuer,
|
309
|
+
:validate_session_expiration,
|
310
|
+
:validate_subject_confirmation,
|
311
|
+
:validate_signature
|
312
|
+
]
|
293
313
|
|
294
314
|
if collect_errors
|
295
|
-
|
296
|
-
validate_version
|
297
|
-
validate_id
|
298
|
-
validate_success_status
|
299
|
-
validate_num_assertion
|
300
|
-
validate_no_encrypted_attributes
|
301
|
-
validate_signed_elements
|
302
|
-
validate_structure
|
303
|
-
validate_in_response_to
|
304
|
-
validate_conditions
|
305
|
-
validate_audience
|
306
|
-
validate_issuer
|
307
|
-
validate_session_expiration
|
308
|
-
validate_subject_confirmation
|
309
|
-
validate_signature
|
315
|
+
validations.each { |validation| send(validation) }
|
310
316
|
@errors.empty?
|
311
317
|
else
|
312
|
-
|
313
|
-
validate_version &&
|
314
|
-
validate_id &&
|
315
|
-
validate_success_status &&
|
316
|
-
validate_num_assertion &&
|
317
|
-
validate_no_encrypted_attributes &&
|
318
|
-
validate_signed_elements &&
|
319
|
-
validate_structure &&
|
320
|
-
validate_in_response_to &&
|
321
|
-
validate_conditions &&
|
322
|
-
validate_audience &&
|
323
|
-
validate_destination &&
|
324
|
-
validate_issuer &&
|
325
|
-
validate_session_expiration &&
|
326
|
-
validate_subject_confirmation &&
|
327
|
-
validate_signature
|
318
|
+
validations.all? { |validation| send(validation) }
|
328
319
|
end
|
329
320
|
end
|
330
321
|
|
@@ -346,8 +337,15 @@ module OneLogin
|
|
346
337
|
# @raise [ValidationError] if soft == false and validation fails
|
347
338
|
#
|
348
339
|
def validate_structure
|
340
|
+
structure_error_msg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"
|
349
341
|
unless valid_saml?(document, soft)
|
350
|
-
return append_error(
|
342
|
+
return append_error(structure_error_msg)
|
343
|
+
end
|
344
|
+
|
345
|
+
unless decrypted_document.nil?
|
346
|
+
unless valid_saml?(decrypted_document, soft)
|
347
|
+
return append_error(structure_error_msg)
|
348
|
+
end
|
351
349
|
end
|
352
350
|
|
353
351
|
true
|
@@ -443,11 +441,43 @@ module OneLogin
|
|
443
441
|
{"ds"=>DSIG}
|
444
442
|
)
|
445
443
|
signed_elements = []
|
444
|
+
verified_seis = []
|
445
|
+
verified_ids = []
|
446
446
|
signature_nodes.each do |signature_node|
|
447
447
|
signed_element = signature_node.parent.name
|
448
448
|
if signed_element != 'Response' && signed_element != 'Assertion'
|
449
|
-
return append_error("
|
449
|
+
return append_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
|
450
|
+
end
|
451
|
+
|
452
|
+
if signature_node.parent.attributes['ID'].nil?
|
453
|
+
return append_error("Signed Element must contain an ID. SAML Response rejected")
|
454
|
+
end
|
455
|
+
|
456
|
+
id = signature_node.parent.attributes.get_attribute("ID").value
|
457
|
+
if verified_ids.include?(id)
|
458
|
+
return append_error("Duplicated ID. SAML Response rejected")
|
450
459
|
end
|
460
|
+
verified_ids.push(id)
|
461
|
+
|
462
|
+
# Check that reference URI matches the parent ID and no duplicate References or IDs
|
463
|
+
ref = REXML::XPath.first(signature_node, ".//ds:Reference", {"ds"=>DSIG})
|
464
|
+
if ref
|
465
|
+
uri = ref.attributes.get_attribute("URI")
|
466
|
+
if uri && !uri.value.empty?
|
467
|
+
sei = uri.value[1..-1]
|
468
|
+
|
469
|
+
unless sei == id
|
470
|
+
return append_error("Found an invalid Signed Element. SAML Response rejected")
|
471
|
+
end
|
472
|
+
|
473
|
+
if verified_seis.include?(sei)
|
474
|
+
return append_error("Duplicated Reference URI. SAML Response rejected")
|
475
|
+
end
|
476
|
+
|
477
|
+
verified_seis.push(sei)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
451
481
|
signed_elements << signed_element
|
452
482
|
end
|
453
483
|
|
@@ -623,8 +653,9 @@ module OneLogin
|
|
623
653
|
# otherwise, review if the decrypted assertion contains a signature
|
624
654
|
sig_elements = REXML::XPath.match(
|
625
655
|
document,
|
626
|
-
"/p:Response/ds:Signature]",
|
627
|
-
{ "p" => PROTOCOL, "ds" => DSIG }
|
656
|
+
"/p:Response[@ID=$id]/ds:Signature]",
|
657
|
+
{ "p" => PROTOCOL, "ds" => DSIG },
|
658
|
+
{ 'id' => document.signed_element_id }
|
628
659
|
)
|
629
660
|
|
630
661
|
use_original = sig_elements.size == 1 || decrypted_document.nil?
|
@@ -634,8 +665,9 @@ module OneLogin
|
|
634
665
|
if sig_elements.nil? || sig_elements.size == 0
|
635
666
|
sig_elements = REXML::XPath.match(
|
636
667
|
doc,
|
637
|
-
"/p:Response/a:Assertion/ds:Signature",
|
638
|
-
{"p" => PROTOCOL, "a" => ASSERTION, "ds"=>DSIG}
|
668
|
+
"/p:Response/a:Assertion[@ID=$id]/ds:Signature",
|
669
|
+
{"p" => PROTOCOL, "a" => ASSERTION, "ds"=>DSIG},
|
670
|
+
{ 'id' => doc.signed_element_id }
|
639
671
|
)
|
640
672
|
end
|
641
673
|
|
@@ -126,24 +126,21 @@ module OneLogin
|
|
126
126
|
def validate(collect_errors = false)
|
127
127
|
reset_errors!
|
128
128
|
|
129
|
-
|
130
|
-
validate_request_state
|
131
|
-
validate_id
|
132
|
-
validate_version
|
133
|
-
validate_structure
|
134
|
-
validate_not_on_or_after
|
135
|
-
validate_issuer
|
136
|
-
validate_signature
|
129
|
+
validations = [
|
130
|
+
:validate_request_state,
|
131
|
+
:validate_id,
|
132
|
+
:validate_version,
|
133
|
+
:validate_structure,
|
134
|
+
:validate_not_on_or_after,
|
135
|
+
:validate_issuer,
|
136
|
+
:validate_signature
|
137
|
+
]
|
137
138
|
|
139
|
+
if collect_errors
|
140
|
+
validations.each { |validation| send(validation) }
|
138
141
|
@errors.empty?
|
139
142
|
else
|
140
|
-
|
141
|
-
validate_id &&
|
142
|
-
validate_version &&
|
143
|
-
validate_structure &&
|
144
|
-
validate_not_on_or_after &&
|
145
|
-
validate_issuer &&
|
146
|
-
validate_signature
|
143
|
+
validations.all? { |validation| send(validation) }
|
147
144
|
end
|
148
145
|
end
|
149
146
|
|
data/lib/xml_security.rb
CHANGED
@@ -84,9 +84,9 @@ module XMLSecurity
|
|
84
84
|
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
85
85
|
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
86
86
|
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
87
|
-
SHA256 =
|
87
|
+
SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
|
88
88
|
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
|
89
|
-
SHA512 =
|
89
|
+
SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
|
90
90
|
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
91
91
|
INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
|
92
92
|
|
data/test/logoutrequest_test.rb
CHANGED
@@ -112,7 +112,7 @@ class RequestTest < Minitest::Test
|
|
112
112
|
|
113
113
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
114
114
|
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
|
115
|
-
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/
|
115
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha256'/>], request_xml
|
116
116
|
end
|
117
117
|
|
118
118
|
it "create a signed logout request with 512 digest and signature method RSA_SHA384" do
|
@@ -125,7 +125,7 @@ class RequestTest < Minitest::Test
|
|
125
125
|
|
126
126
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
127
127
|
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'/>], request_xml
|
128
|
-
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/
|
128
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha512'/>], request_xml
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
data/test/metadata_test.rb
CHANGED
@@ -203,7 +203,7 @@ class MetadataTest < Minitest::Test
|
|
203
203
|
it "creates a signed metadata with specified digest and signature methods" do
|
204
204
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
|
205
205
|
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], xml_text
|
206
|
-
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/
|
206
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha512'/>], xml_text
|
207
207
|
|
208
208
|
signed_metadata_2 = XMLSecurity::SignedDocument.new(xml_text)
|
209
209
|
|
data/test/request_test.rb
CHANGED
@@ -199,7 +199,7 @@ class RequestTest < Minitest::Test
|
|
199
199
|
request_xml = Base64.decode64(params["SAMLRequest"])
|
200
200
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
201
201
|
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
|
202
|
-
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/
|
202
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha512'/>], request_xml
|
203
203
|
end
|
204
204
|
end
|
205
205
|
|
data/test/response_test.rb
CHANGED
@@ -1288,4 +1288,17 @@ class RubySamlTest < Minitest::Test
|
|
1288
1288
|
assert_equal "ZdrjpwEdw22vKoxWAbZB78/gQ7s=", response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10')
|
1289
1289
|
end
|
1290
1290
|
end
|
1291
|
+
|
1292
|
+
describe "attack" do
|
1293
|
+
it "should not be valid" do
|
1294
|
+
settings.private_key = ruby_saml_key_text
|
1295
|
+
signature_wrapping_attack = read_invalid_response("encrypted_new_attack.xml.base64")
|
1296
|
+
response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings)
|
1297
|
+
response_wrapped.stubs(:conditions).returns(nil)
|
1298
|
+
response_wrapped.stubs(:validate_subject_confirmation).returns(true)
|
1299
|
+
settings.idp_cert_fingerprint = "385b1eec71143f00db6af936e2ea12a28771d72c"
|
1300
|
+
assert !response_wrapped.is_valid?
|
1301
|
+
end
|
1302
|
+
end
|
1303
|
+
|
1291
1304
|
end
|
@@ -78,7 +78,7 @@ class SloLogoutresponseTest < Minitest::Test
|
|
78
78
|
response_xml = Base64.decode64(params["SAMLResponse"])
|
79
79
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
|
80
80
|
assert_match /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256'\/>/, response_xml
|
81
|
-
assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/
|
81
|
+
assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmlenc#sha256'\/>/, response_xml
|
82
82
|
end
|
83
83
|
|
84
84
|
it "create a signed logout response with 512 digest and signature method RSA_SHA384" do
|
@@ -91,7 +91,7 @@ class SloLogoutresponseTest < Minitest::Test
|
|
91
91
|
response_xml = Base64.decode64(params["SAMLResponse"])
|
92
92
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], response_xml
|
93
93
|
assert_match /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha384'\/>/, response_xml
|
94
|
-
assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/
|
94
|
+
assert_match /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmlenc#sha512'\/>/, response_xml
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OneLogin LLC
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|