ruby-saml 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
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 [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](http://travis-ci.org/onelogin/ruby-saml) [![Coverage Status](https://coveralls.io/repos/onelogin/ruby-saml/badge.svg?branch=master%0A)](https://coveralls.io/r/onelogin/ruby-saml?branch=master%0A) [![Gem Version](https://badge.fury.io/rb/ruby-saml.svg)](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
|