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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 162de30b9475ee4bc219f26c544304d051ab5c34
4
- data.tar.gz: 685afabca84ac713ab43a2c848cfdb20dd92f579
3
+ metadata.gz: 0b01f4f740dfe49133d17e2bfbb42a470a6a0ecd
4
+ data.tar.gz: 00f47e3eaadebe97288df5daa096a9b0f26f92a7
5
5
  SHA512:
6
- metadata.gz: e5a3f645b2cf71452e7f22a2bbc853f393978516e1abc185b3cbbfe4aa19403b3b3760c839d72f81fd36128241e09c58dff8c84c5b1b2dc1b0177a85b235cf81
7
- data.tar.gz: 7ef80e8936bda82946041ebc220002b7d7a745819a2a419f190c6a0980449cd00516338df8879dcb67b13dd882ba155a665ed31154113fc2826112629fbbedcc
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
- if collect_errors
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
- valid_state? &&
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, "a" => ASSERTION }
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, "a" => ASSERTION }
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
- return false unless validate_response_state
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
- validate_response_state &&
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("Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd")
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("Found an unexpected Signature Element. SAML Response rejected")
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
- if collect_errors
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
- validate_request_state &&
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
 
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '1.2.0'
3
+ VERSION = '1.3.0'
4
4
  end
5
5
  end
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 = "http://www.w3.org/2001/04/xmldsig-more#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 = "http://www.w3.org/2001/04/xmldsig-more#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
 
@@ -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/xmldsig-more#sha256'/>], request_xml
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/xmldsig-more#sha512'/>], request_xml
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
 
@@ -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/xmldsig-more#sha512'/>], xml_text
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/xmldsig-more#sha512'/>], request_xml
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
 
@@ -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\/xmldsig-more#sha256'\/>/, response_xml
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\/xmldsig-more#sha512'\/>/, response_xml
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.2.0
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-04-29 00:00:00.000000000 Z
11
+ date: 2016-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri