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 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