[DRE-commits] [ruby-saml] 01/07: Imported Upstream version 1.1.2
Praveen Arimbrathodiyil
praveen at moszumanska.debian.org
Tue Mar 15 19:11:55 UTC 2016
This is an automated email from the git hooks/post-receive script.
praveen pushed a commit to branch master
in repository ruby-saml.
commit 89ee9a86739ea3618142c442d7554ebba2b0c463
Author: Praveen Arimbrathodiyil <praveen at debian.org>
Date: Tue Mar 15 22:48:16 2016 +0530
Imported Upstream version 1.1.2
---
.travis.yml | 7 +
README.md | 26 +-
Rakefile | 14 -
changelog.md | 20 +
lib/onelogin/ruby-saml/authrequest.rb | 13 +-
lib/onelogin/ruby-saml/idp_metadata_parser.rb | 22 +-
lib/onelogin/ruby-saml/response.rb | 103 +++--
lib/onelogin/ruby-saml/saml_message.rb | 2 +-
lib/onelogin/ruby-saml/settings.rb | 8 +-
lib/onelogin/ruby-saml/utils.rb | 2 +-
lib/onelogin/ruby-saml/version.rb | 2 +-
lib/xml_security.rb | 173 +++++----
metadata.yml | 414 ---------------------
ruby-saml.gemspec | 14 +-
test/response_test.rb | 153 +++++++-
.../invalids/signature_wrapping_attack.xml.base64 | 1 +
...sponse_with_ds_namespace_at_the_root.xml.base64 | 1 +
.../response_with_signed_message_and_assertion.xml | 34 ++
.../response_without_reference_uri.xml.base64 | 1 +
test/responses/signed_nameid_in_atts.xml | 47 +++
test/responses/signed_unqual_nameid_in_atts.xml | 47 +++
...lid_response_without_x509certificate.xml.base64 | 1 +
test/test_helper.rb | 13 +
test/xml_security_test.rb | 42 ++-
24 files changed, 587 insertions(+), 573 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 5c7a02f..c7ad410 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,4 @@
+sudo: false
language: ruby
rvm:
- 1.8.7
@@ -6,6 +7,8 @@ rvm:
- 2.1.5
- 2.2.0
- ree
+ - jruby-1.7.21
+ - jruby-9.0.0.0
gemfile:
- Gemfile
- gemfiles/nokogiri-1.5.gemfile
@@ -15,3 +18,7 @@ matrix:
gemfile: Gemfile
- rvm: ree
gemfile: Gemfile
+ - rvm: jruby-9.0.0.0
+ gemfile: gemfiles/nokogiri-1.5.gemfile
+ - rvm: jruby-1.7.21
+ gemfile: gemfiles/nokogiri-1.5.gemfile
diff --git a/README.md b/README.md
index a51496c..64d4ecb 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,18 @@
-# Ruby SAML [](http://travis-ci.org/onelogin/ruby-saml)
+# 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)
+## Updating from 1.0.x to 1.1.X
+
+Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
+
+For more details, please review [the changelog](changelog.md).
+
## Updating from 0.9.x to 1.0.X
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
-For more details, please review [the changelog](changelog.md).
-
### Important Changes
Please note the `get_idp_metadata` method raises an exception when it is not able to fetch the idp metadata, so review your integration if you are using this functionality.
@@ -31,6 +35,8 @@ We created a demo project for Rails4 that uses the latest version of this librar
* 1.9.x
* 2.1.x
* 2.2.x
+* JRuby 1.7.19
+* JRuby 9.0.0.0
## Adding Features, Pull Requests
* Fork the repository
@@ -96,7 +102,7 @@ To override the default behavior and control the destination of log messages, pr
a ruby Logger object to the gem's logging singleton:
```ruby
-OneLogin::RubySaml::Logging.logger = Logger.new(File.open('/var/log/ruby-saml.log', 'w')
+OneLogin::RubySaml::Logging.logger = Logger.new(File.open('/var/log/ruby-saml.log', 'w'))
```
## The Initialization Phase
@@ -164,6 +170,13 @@ def saml_settings
end
```
+Some assertion validations can be skipped by passing parameters to OneLogin::RubySaml::Response.new(). For example, you can skip the Conditions validation or the SubjectConfirmation validations by initializing the response with different options:
+
+```ruby
+response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
+response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
+```
+
What's left at this point, is to wrap it all up in a controller and point the initialization and consumption URLs in OneLogin at that. A full controller example could look like this:
```ruby
@@ -239,9 +252,9 @@ def saml_settings
end
```
The following attributes are set:
- * id_sso_target_url
+ * idp_sso_target_url
* idp_slo_target_url
- * id_cert_fingerpint
+ * idp_cert_fingerpint
If you are using saml:AttributeStatement to transfer metadata, like the user name, you can access all the attributes through response.attributes. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key the one/more saml:AttributeValue as value. The value returned depends on the value of the
`single_value_compatibility` (when activate, only one value returned, the first one)
@@ -394,6 +407,7 @@ Service Provider.
Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and the decrypt process.
+Enable/disable the soft mode by the settings.soft parameter. When is set false, the saml validations errors will raise an exception.
## Decrypting
diff --git a/Rakefile b/Rakefile
index 7c2e2c4..40fa256 100644
--- a/Rakefile
+++ b/Rakefile
@@ -25,17 +25,3 @@ end
task :test
task :default => :test
-
-# require 'rake/rdoctask'
-# Rake::RDocTask.new do |rdoc|
-# if File.exist?('VERSION')
-# version = File.read('VERSION')
-# else
-# version = ""
-# end
-
-# rdoc.rdoc_dir = 'rdoc'
-# rdoc.title = "ruby-saml #{version}"
-# rdoc.rdoc_files.include('README*')
-# rdoc.rdoc_files.include('lib/**/*.rb')
-#end
diff --git a/changelog.md b/changelog.md
index 0033df5..97e2a03 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,25 @@
# RubySaml Changelog
+### 1.1.2 (February 15, 2015)
+* Improve signature validation. Add tests.
+ [#302](https://github.com/onelogin/ruby-saml/pull/302) Add Destination validation.
+* [#292](https://github.com/onelogin/ruby-saml/pull/292) Improve the error message when validating the audience.
+* [#287](https://github.com/onelogin/ruby-saml/pull/287) Keep the extracted certificate when parsing IdP metadata.
+
+### 1.1.1 (November 10, 2015)
+* [#275](https://github.com/onelogin/ruby-saml/pull/275) Fix a bug on signature validations that invalidates valid SAML messages.
+
+### 1.1.0 (October 27, 2015)
+* [#273](https://github.com/onelogin/ruby-saml/pull/273) Support SAMLResponse without ds:x509certificate
+* [#270](https://github.com/onelogin/ruby-saml/pull/270) Allow SAML elements to come from any namespace (at decryption process)
+* [#261](https://github.com/onelogin/ruby-saml/pull/261) Allow validate_subject_confirmation Response validation to be skipped
+* [#258](https://github.com/onelogin/ruby-saml/pull/258) Fix allowed_clock_drift on the validate_session_expiration test
+* [#256](https://github.com/onelogin/ruby-saml/pull/256) Separate the create_authentication_xml_doc in two methods.
+* [#255](https://github.com/onelogin/ruby-saml/pull/255) Refactor validate signature.
+* [#254](https://github.com/onelogin/ruby-saml/pull/254) Handle empty URI references
+* [#251](https://github.com/onelogin/ruby-saml/pull/251) Support qualified and unqualified NameID in attributes
+* [#234](https://github.com/onelogin/ruby-saml/pull/234) Add explicit support for JRuby
+
### 1.0.0 (June 30, 2015)
* [#247](https://github.com/onelogin/ruby-saml/pull/247) Avoid entity expansion (XEE attacks)
* [#246](https://github.com/onelogin/ruby-saml/pull/246) Fix bug generating Logout Response (issuer was at wrong order)
diff --git a/lib/onelogin/ruby-saml/authrequest.rb b/lib/onelogin/ruby-saml/authrequest.rb
index 2b38ce9..b5108f4 100644
--- a/lib/onelogin/ruby-saml/authrequest.rb
+++ b/lib/onelogin/ruby-saml/authrequest.rb
@@ -87,6 +87,11 @@ module OneLogin
# @return [String] The SAMLRequest String.
#
def create_authentication_xml_doc(settings)
+ document = create_xml_document(settings)
+ sign_document(document, settings)
+ end
+
+ def create_xml_document(settings)
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
request_doc = XMLSecurity::Document.new
@@ -141,14 +146,18 @@ module OneLogin
end
end
+ request_doc
+ end
+
+ def sign_document(document, settings)
# embed signature
if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
private_key = settings.get_sp_key
cert = settings.get_sp_cert
- request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
+ document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
end
- request_doc
+ document
end
end
diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb
index 42d4f02..7e48af9 100644
--- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb
+++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb
@@ -44,6 +44,7 @@ module OneLogin
settings.name_identifier_format = idp_name_id_format
settings.idp_sso_target_url = single_signon_service_url
settings.idp_slo_target_url = single_logout_service_url
+ settings.idp_cert = certificate_base64
settings.idp_cert_fingerprint = fingerprint
end
end
@@ -133,19 +134,28 @@ module OneLogin
node.value if node
end
+ # @return [String|nil] Unformatted Certificate if exists
+ #
+ def certificate_base64
+ @certificate_base64 ||= begin
+ node = REXML::XPath.first(
+ document,
+ "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
+ { "md" => METADATA, "ds" => DSIG }
+ )
+ node.text if node
+ end
+ end
+
# @return [String|nil] X509Certificate if exists
#
def certificate
@certificate ||= begin
- node = REXML::XPath.first(
- document,
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
- { "md" => METADATA, "ds" => DSIG }
- )
- Base64.decode64(node.text) if node
+ Base64.decode64(certificate_base64) if certificate_base64
end
end
+
# @return [String|nil] the SHA-1 fingerpint of the X509Certificate if it exists
#
def fingerprint
diff --git a/lib/onelogin/ruby-saml/response.rb b/lib/onelogin/ruby-saml/response.rb
index 3f58b23..f18c92b 100644
--- a/lib/onelogin/ruby-saml/response.rb
+++ b/lib/onelogin/ruby-saml/response.rb
@@ -36,7 +36,8 @@ module OneLogin
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
# Or some options for the response validation process like skip the conditions validation
# with the :skip_conditions, or allow a clock_drift when checking dates with :allowed_clock_drift
- # or :matches_request_id that will validate that the response matches the ID of the request.
+ # or :matches_request_id that will validate that the response matches the ID of the request,
+ # or skip the subject confirmation validation with the :skip_subject_confirmation option
def initialize(response, options = {})
@errors = []
@@ -94,6 +95,7 @@ module OneLogin
alias_method :nameid, :name_id
+
# Gets the SessionIndex from the AuthnStatement.
# Could be used to be stored in the local session in order
# to be used in a future Logout Request that the SP could
@@ -131,12 +133,22 @@ module OneLogin
stmt_element.elements.each do |attr_element|
name = attr_element.attributes["Name"]
values = attr_element.elements.collect{|e|
- # SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
- # otherwise the value is to be regarded as empty.
- ["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s
+ if (e.elements.nil? || e.elements.size == 0)
+ # SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
+ # otherwise the value is to be regarded as empty.
+ ["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s
+ # explicitly support saml2:NameID with saml2:NameQualifier if supplied in attributes
+ # this is useful for allowing eduPersonTargetedId to be passed as an opaque identifier to use to
+ # identify the subject in an SP rather than email or other less opaque attributes
+ # NameQualifier, if present is prefixed with a "/" to the value
+ else
+ REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect{|n|
+ (n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + n.text.to_s
+ }
+ end
}
- attributes.add(name, values)
+ attributes.add(name, values.flatten)
end
attributes
@@ -242,6 +254,19 @@ module OneLogin
end
end
+ # @return [String|nil] Destination attribute from the SAML Response.
+ #
+ def destination
+ @destination ||= begin
+ node = REXML::XPath.first(
+ document,
+ "/p:Response",
+ { "p" => PROTOCOL }
+ )
+ node.nil? ? nil : node.attributes['Destination']
+ end
+ end
+
# @return [Array] The Audience elements from the Contitions of the SAML Response.
#
def audiences
@@ -283,6 +308,7 @@ module OneLogin
validate_in_response_to &&
validate_conditions &&
validate_audience &&
+ validate_destination &&
validate_issuer &&
validate_session_expiration &&
validate_subject_confirmation &&
@@ -316,11 +342,9 @@ module OneLogin
# Validates that the SAML Response provided in the initialization is not empty,
# also check that the setting and the IdP cert were also provided
- # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not)
- # @return [Boolean] True if the required info is found, otherwise False if soft=True
- # @raise [ValidationError] if soft == false and validation fails
+ # @return [Boolean] True if the required info is found, false otherwise
#
- def validate_response_state(soft = true)
+ def validate_response_state
return append_error("Blank response") if response.nil? || response.empty?
return append_error("No settings on response") if settings.nil?
@@ -444,7 +468,22 @@ module OneLogin
return true if audiences.empty? || settings.issuer.nil? || settings.issuer.empty?
unless audiences.include? settings.issuer
- error_msg = "#{settings.issuer} is not a valid audience for this Response"
+ error_msg = "#{settings.issuer} is not a valid audience for this Response - Valid audiences: #{audiences.join(',')}"
+ return append_error(error_msg)
+ end
+
+ true
+ end
+
+ # Validates the Destination, (If the SAML Response is received where expected)
+ # If fails, the error is added to the errors array
+ # @return [Boolean] True if there is a Destination element that matches the Consumer Service URL, otherwise False
+ #
+ def validate_destination
+ return true if destination.nil? || destination.empty? || settings.assertion_consumer_service_url.nil? || settings.assertion_consumer_service_url.empty?
+
+ unless destination == settings.assertion_consumer_service_url
+ error_msg = "The response was received at #{destination} instead of #{settings.assertion_consumer_service_url}"
return append_error(error_msg)
end
@@ -504,7 +543,7 @@ module OneLogin
return true if session_expires_at.nil?
now = Time.now.utc
- unless session_expires_at > (now + allowed_clock_drift)
+ unless (session_expires_at + allowed_clock_drift) > now
error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response"
return append_error(error_msg)
end
@@ -513,12 +552,14 @@ module OneLogin
end
# Validates if exists valid SubjectConfirmation (If the response was initialized with the :allowed_clock_drift option,
- # timimg validation are relaxed by the allowed_clock_drift value)
+ # timimg validation are relaxed by the allowed_clock_drift value. If the response was initialized with the
+ # :skip_subject_confirmation option, this validation is skipped)
# If fails, the error is added to the errors array
# @return [Boolean] True if exists a valid SubjectConfirmation, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_subject_confirmation
+ return true if options[:skip_subject_confirmation]
valid_subject_confirmation = false
subject_confirmation_nodes = xpath_from_signed_assertion('/a:Subject/a:SubjectConfirmation')
@@ -559,20 +600,38 @@ module OneLogin
# @raise [ValidationError] if soft == false and validation fails
#
def validate_signature
- fingerprint = settings.get_fingerprint
+ error_msg = "Invalid Signature on SAML Response"
# If the response contains the signature, and the assertion was encrypted, validate the original SAML Response
# otherwise, review if the decrypted assertion contains a signature
- response_signed = REXML::XPath.first(
+ sig_elements = REXML::XPath.match(
document,
- "/p:Response[@ID=$id]",
- { "p" => PROTOCOL, "ds" => DSIG },
- { 'id' => document.signed_element_id }
+ "/p:Response/ds:Signature]",
+ { "p" => PROTOCOL, "ds" => DSIG }
)
- doc = (response_signed || decrypted_document.nil?) ? document : decrypted_document
- unless fingerprint && doc.validate_document(fingerprint, :fingerprint_alg => settings.idp_cert_fingerprint_algorithm)
- error_msg = "Invalid Signature on SAML Response"
+ use_original = sig_elements.size == 1 || decrypted_document.nil?
+ doc = use_original ? document : decrypted_document
+
+ # Check signature nodes
+ if sig_elements.nil? || sig_elements.size == 0
+ sig_elements = REXML::XPath.match(
+ doc,
+ "/p:Response/a:Assertion/ds:Signature",
+ {"p" => PROTOCOL, "a" => ASSERTION, "ds"=>DSIG}
+ )
+ end
+
+ if sig_elements.size != 1
+ return append_error(error_msg)
+ end
+
+ opts = {}
+ opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
+ opts[:cert] = settings.get_idp_cert
+ fingerprint = settings.get_fingerprint
+
+ unless fingerprint && doc.validate_document(fingerprint, @soft, opts)
return append_error(error_msg)
end
@@ -676,7 +735,7 @@ module OneLogin
# @return [REXML::Document] The decrypted EncryptedAssertion element
#
def decrypt_assertion(encrypted_assertion_node)
- decrypt_element(encrypted_assertion_node, /(.*<\/(saml2*:|)Assertion>)/m)
+ decrypt_element(encrypted_assertion_node, /(.*<\/(\w+:)?Assertion>)/m)
end
# Decrypts an EncryptedID element
@@ -684,7 +743,7 @@ module OneLogin
# @return [REXML::Document] The decrypted EncrypedtID element
#
def decrypt_nameid(encryptedid_node)
- decrypt_element(encryptedid_node, /(.*<\/(saml2*:|)NameID>)/m)
+ decrypt_element(encryptedid_node, /(.*<\/(\w+:)?NameID>)/m)
end
# Decrypt an element
diff --git a/lib/onelogin/ruby-saml/saml_message.rb b/lib/onelogin/ruby-saml/saml_message.rb
index 89948a7..9739564 100644
--- a/lib/onelogin/ruby-saml/saml_message.rb
+++ b/lib/onelogin/ruby-saml/saml_message.rb
@@ -23,7 +23,7 @@ module OneLogin
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
#
def self.schema
- @schema ||= Mutex.new.synchronize do
+ Mutex.new.synchronize do
Dir.chdir(File.expand_path("../../../schemas", __FILE__)) do
::Nokogiri::XML::Schema(File.read("saml-schema-protocol-2.0.xsd"))
end
diff --git a/lib/onelogin/ruby-saml/settings.rb b/lib/onelogin/ruby-saml/settings.rb
index 56a2ca6..b587ffa 100644
--- a/lib/onelogin/ruby-saml/settings.rb
+++ b/lib/onelogin/ruby-saml/settings.rb
@@ -118,8 +118,8 @@ module OneLogin
def get_idp_cert
return nil if idp_cert.nil? || idp_cert.empty?
- formated_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
- OpenSSL::X509::Certificate.new(formated_cert)
+ formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
+ OpenSSL::X509::Certificate.new(formatted_cert)
end
# @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
@@ -127,8 +127,8 @@ module OneLogin
def get_sp_cert
return nil if certificate.nil? || certificate.empty?
- formated_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
- OpenSSL::X509::Certificate.new(formated_cert)
+ formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
+ OpenSSL::X509::Certificate.new(formatted_cert)
end
# @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
diff --git a/lib/onelogin/ruby-saml/utils.rb b/lib/onelogin/ruby-saml/utils.rb
index 430ade0..4d7161d 100644
--- a/lib/onelogin/ruby-saml/utils.rb
+++ b/lib/onelogin/ruby-saml/utils.rb
@@ -65,7 +65,7 @@ module OneLogin
# @option params [OpenSSL::X509::Certificate] cert The Identity provider public certtificate
# @option params [String] sig_alg The SigAlg parameter
# @option params [String] signature The Signature parameter (base64 encoded)
- # @option params [String] query_string The SigAlg parameter
+ # @option params [String] query_string The full GET Query String to be compared
# @return [Boolean] True if the Signature is valid, False otherwise
#
def self.verify_signature(params)
diff --git a/lib/onelogin/ruby-saml/version.rb b/lib/onelogin/ruby-saml/version.rb
index 8ed121a..8a10ff4 100644
--- a/lib/onelogin/ruby-saml/version.rb
+++ b/lib/onelogin/ruby-saml/version.rb
@@ -1,5 +1,5 @@
module OneLogin
module RubySaml
- VERSION = '1.0.0'
+ VERSION = '1.1.2'
end
end
diff --git a/lib/xml_security.rb b/lib/xml_security.rb
index 4136504..3e83698 100644
--- a/lib/xml_security.rb
+++ b/lib/xml_security.rb
@@ -199,123 +199,128 @@ module XMLSecurity
"//ds:X509Certificate",
{ "ds"=>DSIG }
)
- unless cert_element
- if soft
- return false
+
+ if cert_element
+ base64_cert = cert_element.text
+ cert_text = Base64.decode64(base64_cert)
+ cert = OpenSSL::X509::Certificate.new(cert_text)
+
+ if options[:fingerprint_alg]
+ fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
else
- raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)")
+ fingerprint_alg = OpenSSL::Digest::SHA1.new
end
- end
- base64_cert = cert_element.text
- cert_text = Base64.decode64(base64_cert)
- cert = OpenSSL::X509::Certificate.new(cert_text)
+ fingerprint = fingerprint_alg.hexdigest(cert.to_der)
- if options[:fingerprint_alg]
- fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
+ # check cert matches registered idp cert
+ if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
+ @errors << "Fingerprint mismatch"
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
+ end
else
- fingerprint_alg = OpenSSL::Digest::SHA1.new
- end
- fingerprint = fingerprint_alg.hexdigest(cert.to_der)
-
- # check cert matches registered idp cert
- if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
- @errors << "Fingerprint mismatch"
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
+ if options[:cert]
+ base64_cert = Base64.encode64(options[:cert].to_pem)
+ else
+ if soft
+ return false
+ else
+ raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings")
+ end
+ end
end
-
validate_signature(base64_cert, soft)
end
def validate_signature(base64_cert, soft = true)
- # validate references
-
- # check for inclusive namespaces
- inclusive_namespaces = extract_inclusive_namespaces
document = Nokogiri.parse(self.to_s) do |options|
options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
- # create a working copy so we don't modify the original
+ # create a rexml document
@working_copy ||= REXML::Document.new(self.to_s).root
- # store and remove signature node
- @sig_element ||= begin
- element = REXML::XPath.first(
+ # get signature node
+ sig_element = REXML::XPath.first(
@working_copy,
"//ds:Signature",
{"ds"=>DSIG}
- )
- element.remove
- end
+ )
- # verify signature
- signed_info_element = REXML::XPath.first(
- @sig_element,
- "//ds:SignedInfo",
+ # signature method
+ sig_alg_value = REXML::XPath.first(
+ sig_element,
+ "./ds:SignedInfo/ds:SignatureMethod",
{"ds"=>DSIG}
)
- noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
- noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
+ signature_algorithm = algorithm(sig_alg_value)
+
+ # get signature
+ base64_signature = REXML::XPath.first(
+ sig_element,
+ "./ds:SignatureValue",
+ {"ds" => DSIG}
+ ).text
+ signature = Base64.decode64(base64_signature)
+
+ # canonicalization method
canon_algorithm = canon_algorithm REXML::XPath.first(
- @sig_element,
- '//ds:CanonicalizationMethod',
+ sig_element,
+ './ds:SignedInfo/ds:CanonicalizationMethod',
'ds' => DSIG
)
+
+ noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
+ noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
+
+ # Handle when no URI
+ noko_signed_info_reference_element_uri_attr = noko_signed_info_element.at_xpath('./ds:Reference', 'ds' => DSIG).attributes["URI"]
+ if (noko_signed_info_reference_element_uri_attr.value.empty?)
+ noko_signed_info_reference_element_uri_attr.value = "##{document.root.attribute('ID')}"
+ end
+
canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
noko_sig_element.remove
+ # get inclusive namespaces
+ inclusive_namespaces = extract_inclusive_namespaces
+
# check digests
- REXML::XPath.each(@sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
- uri = ref.attributes.get_attribute("URI").value
-
- hashed_element = document.at_xpath("//*[@ID=$uri]", nil, { 'uri' => uri[1..-1] })
- canon_algorithm = canon_algorithm REXML::XPath.first(
- ref,
- '//ds:CanonicalizationMethod',
- { "ds" => DSIG }
- )
- canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
-
- digest_algorithm = algorithm(REXML::XPath.first(
- ref,
- "//ds:DigestMethod",
- { "ds" => DSIG }
- ))
- hash = digest_algorithm.digest(canon_hashed_element)
- encoded_digest_value = REXML::XPath.first(
- ref,
- "//ds:DigestValue",
- { "ds" => DSIG }
- ).text
- digest_value = Base64.decode64(encoded_digest_value)
-
- unless digests_match?(hash, digest_value)
- @errors << "Digest mismatch"
- return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch"))
- end
- end
+ ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
+ uri = ref.attributes.get_attribute("URI").value
- base64_signature = REXML::XPath.first(
- @sig_element,
- "//ds:SignatureValue",
- {"ds" => DSIG}
+ hashed_element = uri.empty? ? document : document.at_xpath("//*[@ID=$uri]", nil, { 'uri' => uri[1..-1] })
+ # hashed_element = document.at_xpath("//*[@ID=$uri]", nil, { 'uri' => uri[1..-1] })
+ canon_algorithm = canon_algorithm REXML::XPath.first(
+ ref,
+ '//ds:CanonicalizationMethod',
+ { "ds" => DSIG }
+ )
+ canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
+
+ digest_algorithm = algorithm(REXML::XPath.first(
+ ref,
+ "//ds:DigestMethod",
+ { "ds" => DSIG }
+ ))
+ hash = digest_algorithm.digest(canon_hashed_element)
+ encoded_digest_value = REXML::XPath.first(
+ ref,
+ "//ds:DigestValue",
+ { "ds" => DSIG }
).text
+ digest_value = Base64.decode64(encoded_digest_value)
- signature = Base64.decode64(base64_signature)
+ unless digests_match?(hash, digest_value)
+ @errors << "Digest mismatch"
+ return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch"))
+ end
# get certificate object
cert_text = Base64.decode64(base64_cert)
cert = OpenSSL::X509::Certificate.new(cert_text)
- # signature method
- sig_alg_value = REXML::XPath.first(
- signed_info_element,
- "//ds:SignatureMethod",
- {"ds"=>DSIG}
- )
- signature_algorithm = algorithm(sig_alg_value)
-
+ # verify signature
unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
@errors << "Key validation error"
return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Key validation error"))
@@ -336,7 +341,11 @@ module XMLSecurity
"//ds:Signature/ds:SignedInfo/ds:Reference",
{"ds"=>DSIG}
)
- self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
+
+ return nil if reference_element.nil?
+
+ sei = reference_element.attribute("URI").value[1..-1]
+ sei.nil? ? self.root.attribute("ID") : sei
end
def extract_inclusive_namespaces
@@ -349,7 +358,7 @@ module XMLSecurity
prefix_list = element.attributes.get_attribute("PrefixList").value
prefix_list.split(" ")
else
- []
+ nil
end
end
diff --git a/metadata.yml b/metadata.yml
deleted file mode 100644
index a321dc3..0000000
--- a/metadata.yml
+++ /dev/null
@@ -1,414 +0,0 @@
---- !ruby/object:Gem::Specification
-name: ruby-saml
-version: !ruby/object:Gem::Version
- version: 1.0.0
-platform: ruby
-authors:
-- OneLogin LLC
-autorequire:
-bindir: bin
-cert_chain: []
-date: 2015-07-07 00:00:00.000000000 Z
-dependencies:
-- !ruby/object:Gem::Dependency
- name: uuid
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '2.3'
- type: :runtime
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '2.3'
-- !ruby/object:Gem::Dependency
- name: nokogiri
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: 1.5.10
- type: :runtime
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: 1.5.10
-- !ruby/object:Gem::Dependency
- name: minitest
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '5.5'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '5.5'
-- !ruby/object:Gem::Dependency
- name: mocha
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '0.14'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '0.14'
-- !ruby/object:Gem::Dependency
- name: rake
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '10'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '10'
-- !ruby/object:Gem::Dependency
- name: shoulda
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '2.11'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '2.11'
-- !ruby/object:Gem::Dependency
- name: simplecov
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: 0.9.0
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: 0.9.0
-- !ruby/object:Gem::Dependency
- name: systemu
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '2'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '2'
-- !ruby/object:Gem::Dependency
- name: timecop
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "<="
- - !ruby/object:Gem::Version
- version: 0.6.0
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "<="
- - !ruby/object:Gem::Version
- version: 0.6.0
-- !ruby/object:Gem::Dependency
- name: pry-byebug
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: '0'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: '0'
-description: SAML toolkit for Ruby on Rails
-email: support at onelogin.com
-executables: []
-extensions: []
-extra_rdoc_files:
-- LICENSE
-- README.md
-files:
-- ".document"
-- ".gitignore"
-- ".travis.yml"
-- Gemfile
-- LICENSE
-- README.md
-- Rakefile
-- changelog.md
-- gemfiles/nokogiri-1.5.gemfile
-- lib/onelogin/ruby-saml.rb
-- lib/onelogin/ruby-saml/attribute_service.rb
-- lib/onelogin/ruby-saml/attributes.rb
-- lib/onelogin/ruby-saml/authrequest.rb
-- lib/onelogin/ruby-saml/http_error.rb
-- lib/onelogin/ruby-saml/idp_metadata_parser.rb
-- lib/onelogin/ruby-saml/logging.rb
-- lib/onelogin/ruby-saml/logoutrequest.rb
-- lib/onelogin/ruby-saml/logoutresponse.rb
-- lib/onelogin/ruby-saml/metadata.rb
-- lib/onelogin/ruby-saml/response.rb
-- lib/onelogin/ruby-saml/saml_message.rb
-- lib/onelogin/ruby-saml/settings.rb
-- lib/onelogin/ruby-saml/slo_logoutrequest.rb
-- lib/onelogin/ruby-saml/slo_logoutresponse.rb
-- lib/onelogin/ruby-saml/utils.rb
-- lib/onelogin/ruby-saml/validation_error.rb
-- lib/onelogin/ruby-saml/version.rb
-- lib/ruby-saml.rb
-- lib/schemas/saml-schema-assertion-2.0.xsd
-- lib/schemas/saml-schema-authn-context-2.0.xsd
-- lib/schemas/saml-schema-authn-context-types-2.0.xsd
-- lib/schemas/saml-schema-metadata-2.0.xsd
-- lib/schemas/saml-schema-protocol-2.0.xsd
-- lib/schemas/sstc-metadata-attr.xsd
-- lib/schemas/sstc-saml-attribute-ext.xsd
-- lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd
-- lib/schemas/sstc-saml-metadata-ui-v1.0.xsd
-- lib/schemas/xenc-schema.xsd
-- lib/schemas/xml.xsd
-- lib/schemas/xmldsig-core-schema.xsd
-- lib/xml_security.rb
-- ruby-saml.gemspec
-- test/certificates/certificate1
-- test/certificates/certificate_without_head_foot
-- test/certificates/formatted_certificate
-- test/certificates/formatted_private_key
-- test/certificates/formatted_rsa_private_key
-- test/certificates/invalid_certificate1
-- test/certificates/invalid_certificate2
-- test/certificates/invalid_certificate3
-- test/certificates/invalid_private_key1
-- test/certificates/invalid_private_key2
-- test/certificates/invalid_private_key3
-- test/certificates/invalid_rsa_private_key1
-- test/certificates/invalid_rsa_private_key2
-- test/certificates/invalid_rsa_private_key3
-- test/certificates/ruby-saml.crt
-- test/certificates/ruby-saml.key
-- test/idp_metadata_parser_test.rb
-- test/logging_test.rb
-- test/logout_requests/invalid_slo_request.xml
-- test/logout_requests/slo_request.xml
-- test/logout_requests/slo_request.xml.base64
-- test/logout_requests/slo_request_deflated.xml.base64
-- test/logout_requests/slo_request_with_session_index.xml
-- test/logout_responses/logoutresponse_fixtures.rb
-- test/logoutrequest_test.rb
-- test/logoutresponse_test.rb
-- test/metadata_test.rb
-- test/request_test.rb
-- test/response_test.rb
-- test/responses/adfs_response_sha1.xml
-- test/responses/adfs_response_sha256.xml
-- test/responses/adfs_response_sha384.xml
-- test/responses/adfs_response_sha512.xml
-- test/responses/adfs_response_xmlns.xml
-- test/responses/attackxee.xml
-- test/responses/idp_descriptor.xml
-- test/responses/invalids/invalid_audience.xml.base64
-- test/responses/invalids/invalid_issuer_assertion.xml.base64
-- test/responses/invalids/invalid_issuer_message.xml.base64
-- test/responses/invalids/invalid_signature_position.xml.base64
-- test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64
-- test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64
-- test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64
-- test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64
-- test/responses/invalids/multiple_assertions.xml.base64
-- test/responses/invalids/multiple_signed.xml.base64
-- test/responses/invalids/no_id.xml.base64
-- test/responses/invalids/no_saml2.xml.base64
-- test/responses/invalids/no_signature.xml.base64
-- test/responses/invalids/no_status.xml.base64
-- test/responses/invalids/no_status_code.xml.base64
-- test/responses/invalids/no_subjectconfirmation_data.xml.base64
-- test/responses/invalids/no_subjectconfirmation_method.xml.base64
-- test/responses/invalids/response_encrypted_attrs.xml.base64
-- test/responses/invalids/response_invalid_signed_element.xml.base64
-- test/responses/invalids/status_code_responder.xml.base64
-- test/responses/invalids/status_code_responer_and_msg.xml.base64
-- test/responses/no_signature_ns.xml
-- test/responses/open_saml_response.xml
-- test/responses/response_assertion_wrapped.xml.base64
-- test/responses/response_encrypted_nameid.xml.base64
-- test/responses/response_eval.xml
-- test/responses/response_no_cert_and_encrypted_attrs.xml
-- test/responses/response_unsigned_xml_base64
-- test/responses/response_with_ampersands.xml
-- test/responses/response_with_ampersands.xml.base64
-- test/responses/response_with_multiple_attribute_values.xml
-- test/responses/response_with_saml2_namespace.xml.base64
-- test/responses/response_with_signed_assertion.xml.base64
-- test/responses/response_with_signed_assertion_2.xml.base64
-- test/responses/response_with_undefined_recipient.xml.base64
-- test/responses/response_without_attributes.xml.base64
-- test/responses/response_wrapped.xml.base64
-- test/responses/signed_message_encrypted_signed_assertion.xml.base64
-- test/responses/signed_message_encrypted_unsigned_assertion.xml.base64
-- test/responses/simple_saml_php.xml
-- test/responses/starfield_response.xml.base64
-- test/responses/test_sign.xml
-- test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64
-- test/responses/unsigned_message_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64
-- test/responses/valid_response.xml.base64
-- test/saml_message_test.rb
-- test/settings_test.rb
-- test/slo_logoutrequest_test.rb
-- test/slo_logoutresponse_test.rb
-- test/test_helper.rb
-- test/utils_test.rb
-- test/xml_security_test.rb
-homepage: http://github.com/onelogin/ruby-saml
-licenses:
-- MIT
-metadata: {}
-post_install_message:
-rdoc_options:
-- "--charset=UTF-8"
-require_paths:
-- lib
-required_ruby_version: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: 1.8.7
-required_rubygems_version: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: '0'
-requirements: []
-rubyforge_project: http://www.rubygems.org/gems/ruby-saml
-rubygems_version: 2.4.5
-signing_key:
-specification_version: 4
-summary: SAML Ruby Tookit
-test_files:
-- test/certificates/certificate1
-- test/certificates/certificate_without_head_foot
-- test/certificates/formatted_certificate
-- test/certificates/formatted_private_key
-- test/certificates/formatted_rsa_private_key
-- test/certificates/invalid_certificate1
-- test/certificates/invalid_certificate2
-- test/certificates/invalid_certificate3
-- test/certificates/invalid_private_key1
-- test/certificates/invalid_private_key2
-- test/certificates/invalid_private_key3
-- test/certificates/invalid_rsa_private_key1
-- test/certificates/invalid_rsa_private_key2
-- test/certificates/invalid_rsa_private_key3
-- test/certificates/ruby-saml.crt
-- test/certificates/ruby-saml.key
-- test/idp_metadata_parser_test.rb
-- test/logging_test.rb
-- test/logout_requests/invalid_slo_request.xml
-- test/logout_requests/slo_request.xml
-- test/logout_requests/slo_request.xml.base64
-- test/logout_requests/slo_request_deflated.xml.base64
-- test/logout_requests/slo_request_with_session_index.xml
-- test/logout_responses/logoutresponse_fixtures.rb
-- test/logoutrequest_test.rb
-- test/logoutresponse_test.rb
-- test/metadata_test.rb
-- test/request_test.rb
-- test/response_test.rb
-- test/responses/adfs_response_sha1.xml
-- test/responses/adfs_response_sha256.xml
-- test/responses/adfs_response_sha384.xml
-- test/responses/adfs_response_sha512.xml
-- test/responses/adfs_response_xmlns.xml
-- test/responses/attackxee.xml
-- test/responses/idp_descriptor.xml
-- test/responses/invalids/invalid_audience.xml.base64
-- test/responses/invalids/invalid_issuer_assertion.xml.base64
-- test/responses/invalids/invalid_issuer_message.xml.base64
-- test/responses/invalids/invalid_signature_position.xml.base64
-- test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64
-- test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64
-- test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64
-- test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64
-- test/responses/invalids/multiple_assertions.xml.base64
-- test/responses/invalids/multiple_signed.xml.base64
-- test/responses/invalids/no_id.xml.base64
-- test/responses/invalids/no_saml2.xml.base64
-- test/responses/invalids/no_signature.xml.base64
-- test/responses/invalids/no_status.xml.base64
-- test/responses/invalids/no_status_code.xml.base64
-- test/responses/invalids/no_subjectconfirmation_data.xml.base64
-- test/responses/invalids/no_subjectconfirmation_method.xml.base64
-- test/responses/invalids/response_encrypted_attrs.xml.base64
-- test/responses/invalids/response_invalid_signed_element.xml.base64
-- test/responses/invalids/status_code_responder.xml.base64
-- test/responses/invalids/status_code_responer_and_msg.xml.base64
-- test/responses/no_signature_ns.xml
-- test/responses/open_saml_response.xml
-- test/responses/response_assertion_wrapped.xml.base64
-- test/responses/response_encrypted_nameid.xml.base64
-- test/responses/response_eval.xml
-- test/responses/response_no_cert_and_encrypted_attrs.xml
-- test/responses/response_unsigned_xml_base64
-- test/responses/response_with_ampersands.xml
-- test/responses/response_with_ampersands.xml.base64
-- test/responses/response_with_multiple_attribute_values.xml
-- test/responses/response_with_saml2_namespace.xml.base64
-- test/responses/response_with_signed_assertion.xml.base64
-- test/responses/response_with_signed_assertion_2.xml.base64
-- test/responses/response_with_undefined_recipient.xml.base64
-- test/responses/response_without_attributes.xml.base64
-- test/responses/response_wrapped.xml.base64
-- test/responses/signed_message_encrypted_signed_assertion.xml.base64
-- test/responses/signed_message_encrypted_unsigned_assertion.xml.base64
-- test/responses/simple_saml_php.xml
-- test/responses/starfield_response.xml.base64
-- test/responses/test_sign.xml
-- test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64
-- test/responses/unsigned_message_encrypted_signed_assertion.xml.base64
-- test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64
-- test/responses/valid_response.xml.base64
-- test/saml_message_test.rb
-- test/settings_test.rb
-- test/slo_logoutrequest_test.rb
-- test/slo_logoutresponse_test.rb
-- test/test_helper.rb
-- test/utils_test.rb
-- test/xml_security_test.rb
diff --git a/ruby-saml.gemspec b/ruby-saml.gemspec
index ca66d88..fbb3fd1 100644
--- a/ruby-saml.gemspec
+++ b/ruby-saml.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.license = 'MIT'
s.extra_rdoc_files = [
"LICENSE",
- "README.md"
+ "README.md"
]
s.files = `git ls-files`.split("\n")
s.homepage = %q{http://github.com/onelogin/ruby-saml}
@@ -30,7 +30,12 @@ Gem::Specification.new do |s|
# Because runtime dependencies are determined at build time, we cannot make
# Nokogiri's version dependent on the Ruby version, even though we would
# have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions.
- s.add_runtime_dependency('nokogiri', '>= 1.5.10')
+ if defined?(JRUBY_VERSION)
+ s.add_runtime_dependency('nokogiri', '>= 1.6.0')
+ s.add_runtime_dependency('jruby-openssl', '>= 0.9.8')
+ else
+ s.add_runtime_dependency('nokogiri', '>= 1.5.10')
+ end
s.add_development_dependency('minitest', '~> 5.5')
s.add_development_dependency('mocha', '~> 0.14')
@@ -40,7 +45,10 @@ Gem::Specification.new do |s|
s.add_development_dependency('systemu', '~> 2')
s.add_development_dependency('timecop', '<= 0.6.0')
- if RUBY_VERSION < '1.9'
+ if defined?(JRUBY_VERSION)
+ # All recent versions of JRuby play well with pry
+ s.add_development_dependency('pry')
+ elsif RUBY_VERSION < '1.9'
# 1.8.7
s.add_development_dependency('ruby-debug', '~> 0.10.4')
elsif RUBY_VERSION < '2.0'
diff --git a/test/response_test.rb b/test/response_test.rb
index 0e81cb1..13b6f66 100644
--- a/test/response_test.rb
+++ b/test/response_test.rb
@@ -9,11 +9,14 @@ class RubySamlTest < Minitest::Test
let(:settings) { OneLogin::RubySaml::Settings.new }
let(:response) { OneLogin::RubySaml::Response.new(response_document_without_recipient) }
let(:response_without_attributes) { OneLogin::RubySaml::Response.new(response_document_without_attributes) }
+ let(:response_without_reference_uri) { OneLogin::RubySaml::Response.new(response_document_without_reference_uri) }
let(:response_with_signed_assertion) { OneLogin::RubySaml::Response.new(response_document_with_signed_assertion) }
+ let(:response_with_ds_namespace_at_the_root) { OneLogin::RubySaml::Response.new(response_document_with_ds_namespace_at_the_root)}
let(:response_unsigned) { OneLogin::RubySaml::Response.new(response_document_unsigned) }
let(:response_wrapped) { OneLogin::RubySaml::Response.new(response_document_wrapped) }
let(:response_multiple_attr_values) { OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) }
let(:response_valid_signed) { OneLogin::RubySaml::Response.new(response_document_valid_signed) }
+ let(:response_valid_signed_without_x509certificate) { OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate) }
let(:response_no_id) { OneLogin::RubySaml::Response.new(read_invalid_response("no_id.xml.base64")) }
let(:response_no_version) { OneLogin::RubySaml::Response.new(read_invalid_response("no_saml2.xml.base64")) }
let(:response_multi_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_assertions.xml.base64")) }
@@ -55,7 +58,7 @@ class RubySamlTest < Minitest::Test
describe "Prevent XEE attack" do
before do
- @response = OneLogin::RubySaml::Response.new(fixture(:attackxee))
+ @response = OneLogin::RubySaml::Response.new(fixture(:attackxee))
end
it "false when evil attack vector is present, soft = true" do
@@ -201,7 +204,7 @@ class RubySamlTest < Minitest::Test
settings.issuer = 'invalid'
response_valid_signed.settings = settings
response_valid_signed.soft = false
- error_msg = "#{response_valid_signed.settings.issuer} is not a valid audience for this Response"
+ error_msg = "#{response_valid_signed.settings.issuer} is not a valid audience for this Response - Valid audiences: https://someone.example.com/audience"
assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
response_valid_signed.is_valid?
end
@@ -365,7 +368,7 @@ class RubySamlTest < Minitest::Test
settings.issuer = 'invalid'
response_valid_signed.settings = settings
response_valid_signed.is_valid?
- assert_includes response_valid_signed.errors, "#{response_valid_signed.settings.issuer} is not a valid audience for this Response"
+ assert_includes response_valid_signed.errors, "#{response_valid_signed.settings.issuer} is not a valid audience for this Response - Valid audiences: https://someone.example.com/audience"
end
it "return false when no ID present in the SAML Response" do
@@ -384,6 +387,15 @@ class RubySamlTest < Minitest::Test
response_no_version.is_valid?
assert_includes response_no_version.errors, "Unsupported SAML version"
end
+
+ it "return true when a nil URI is given in the ds:Reference" do
+
+ response_without_reference_uri.stubs(:conditions).returns(nil)
+ response_without_reference_uri.settings = settings
+ response_without_reference_uri.settings.idp_cert_fingerprint = "19:4D:97:E4:D8:C9:C8:CF:A4:B7:21:E5:EE:49:7F:D9:66:0E:52:13"
+ assert response_without_reference_uri.is_valid?
+ assert_empty response_without_reference_uri.errors
+ end
end
end
@@ -399,7 +411,22 @@ class RubySamlTest < Minitest::Test
response.settings = settings
response.settings.issuer = 'invalid_audience'
assert !response.send(:validate_audience)
- assert_includes response.errors, "#{response.settings.issuer} is not a valid audience for this Response"
+ assert_includes response.errors, "#{response.settings.issuer} is not a valid audience for this Response - Valid audiences: {audience}"
+ end
+ end
+
+ describe "#validate_destination" do
+ it "return true when the destination of the SAML Response matches the assertion consumer service url" do
+ response.settings = settings
+ assert response.send(:validate_destination)
+ assert_empty response.errors
+ end
+
+ it "return false when the destination of the SAML Response does not match the assertion consumer service url" do
+ response.settings = settings
+ response.settings.assertion_consumer_service_url = 'invalid_acs'
+ assert !response.send(:validate_destination)
+ assert_includes response.errors, "The response was received at #{response.destination} instead of #{response.settings.assertion_consumer_service_url}"
end
end
@@ -409,7 +436,7 @@ class RubySamlTest < Minitest::Test
assert response_valid_signed.send(:validate_issuer)
response_valid_signed.settings.idp_entity_id = 'https://app.onelogin.com/saml2'
- assert response_valid_signed.send(:validate_issuer)
+ assert response_valid_signed.send(:validate_issuer)
end
it "return false when the issuer of the Message does not match the IdP entityId" do
@@ -491,7 +518,7 @@ class RubySamlTest < Minitest::Test
response = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings, :matches_request_id => "_fc4a34b0-7efb-012e-caae-782bcb13bb38")
assert response.send(:validate_in_response_to)
assert_empty response.errors
- end
+ end
it "return true when no Request ID is provided for checking" do
response = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings)
@@ -539,7 +566,7 @@ class RubySamlTest < Minitest::Test
response_invalid_audience.settings = settings
response_invalid_audience.settings.issuer = "https://invalid.example.com/audience"
assert !response_invalid_audience.send(:validate_audience)
- assert_includes response_invalid_audience.errors, "#{response_invalid_audience.settings.issuer} is not a valid audience for this Response"
+ assert_includes response_invalid_audience.errors, "#{response_invalid_audience.settings.issuer} is not a valid audience for this Response - Valid audiences: http://invalid.audience.com"
end
end
@@ -606,6 +633,27 @@ class RubySamlTest < Minitest::Test
assert !response_invalid_subjectconfirmation_noa.send(:validate_subject_confirmation)
assert_includes response_invalid_subjectconfirmation_noa.errors, "A valid SubjectConfirmation was not found on this Response"
end
+
+ it "return true when the skip_subject_confirmation option is passed and the subject confirmation is valid" do
+ opts = {}
+ opts[:skip_subject_confirmation] = true
+ response_with_skip = OneLogin::RubySaml::Response.new(response_document_valid_signed, opts)
+ response_with_skip.settings = settings
+ response_with_skip.settings.assertion_consumer_service_url = 'recipient'
+ Time.expects(:now).times(0) # ensures the test isn't run and thus Time.now.utc is never called within the test
+ assert response_with_skip.send(:validate_subject_confirmation)
+ assert_empty response_with_skip.errors
+ end
+
+ it "return true when the skip_subject_confirmation option is passed and the response has an invalid subject confirmation" do
+ opts = {}
+ opts[:skip_subject_confirmation] = true
+ response_with_skip = OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_noa.xml.base64"), opts)
+ response_with_skip.settings = settings
+ Time.expects(:now).times(0) # ensures the test isn't run and thus Time.now.utc is never called within the test
+ assert response_with_skip.send(:validate_subject_confirmation)
+ assert_empty response_with_skip.errors
+ end
end
describe "#validate_session_expiration" do
@@ -620,6 +668,18 @@ class RubySamlTest < Minitest::Test
assert !response.send(:validate_session_expiration)
assert_includes response.errors, "The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response"
end
+
+ it "returns true when the session has expired, but is still within the allowed_clock_drift" do
+ drift = (Time.now - Time.parse("2010-11-19T21:57:37Z")) * 60 # seconds ago that this assertion expired
+ drift += 10 # add a buffer of 10 seconds to make sure the test passes
+ opts = {}
+ opts[:allowed_clock_drift] = drift
+
+ response_with_drift = OneLogin::RubySaml::Response.new(response_document_without_recipient, opts)
+ response_with_drift.settings = settings
+ assert response_with_drift.send(:validate_session_expiration)
+ assert_empty response_with_drift.errors
+ end
end
describe "#validate_signature" do
@@ -630,6 +690,22 @@ class RubySamlTest < Minitest::Test
assert_empty response_valid_signed.errors
end
+ it "return true when the signature is valid and ds namespace is at the root" do
+ settings.idp_cert_fingerprint = '5614657ab692b960480389723a36446a5fe1f7ec'
+ response_with_ds_namespace_at_the_root.settings = settings
+ assert response_with_ds_namespace_at_the_root.send(:validate_signature)
+ assert_empty response_with_ds_namespace_at_the_root.errors
+ end
+
+ it "return true when the signature is valid and fingerprint provided" do
+ settings.idp_cert_fingerprint = '49:EC:3F:A4:71:8A:1E:C9:DB:70:A7:CC:33:36:96:F0:48:8C:4E:DA'
+ xml = 'PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIERlc3RpbmF0aW9uPSJodHRwczovL2NvZGVycGFkLmlvL3NhbWwvYWNzIiBJRD0iXzEwOGE1ZTg0MDllYzRjZjlhY2QxYzQ2OWU5ZDcxNGFkIiBJblJlc3BvbnNlVG89Il80ZmZmYWE2MC02OTZiLTAxMzMtMzg4Ni0wMjQxZjY1YzA2OTMiIElzc3VlSW5zdGFudD0iMjAxNS0xMS0wOVQyMzo1NTo0M1oiIFZlcnNpb249IjIuMCI+PHNhbWw6SXNzdWVyIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHBzOi8vbG9naW4uaHVsdS5jb208L3NhbWw6SXNzdWVyPjxkczpTa [...]
+ response_x = OneLogin::RubySaml::Response.new(xml)
+ response_x.settings = settings
+ assert response_x.send(:validate_signature)
+ assert_empty response_x.errors
+ end
+
it "return false when no fingerprint" do
settings.idp_cert_fingerprint = nil
settings.idp_cert = nil
@@ -644,6 +720,41 @@ class RubySamlTest < Minitest::Test
assert !response.send(:validate_signature)
assert_includes response.errors, "Invalid Signature on SAML Response"
end
+
+ it "return false when no X509Certificate and not cert provided at settings" do
+ settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
+ settings.idp_cert = nil
+ response_valid_signed_without_x509certificate.settings = settings
+ assert !response_valid_signed_without_x509certificate.send(:validate_signature)
+ assert_includes response_valid_signed_without_x509certificate.errors, "Invalid Signature on SAML Response"
+ end
+
+ it "return false when no X509Certificate and the cert provided at settings mismatches" do
+ settings.idp_cert_fingerprint = nil
+ settings.idp_cert = signature_1
+ response_valid_signed_without_x509certificate.settings = settings
+ assert !response_valid_signed_without_x509certificate.send(:validate_signature)
+ assert_includes response_valid_signed_without_x509certificate.errors, "Invalid Signature on SAML Response"
+ end
+
+ it "return true when no X509Certificate and the cert provided at settings matches" do
+ settings.idp_cert_fingerprint = nil
+ settings.idp_cert = ruby_saml_cert_text
+ response_valid_signed_without_x509certificate.settings = settings
+ assert response_valid_signed_without_x509certificate.send(:validate_signature)
+ assert_empty response_valid_signed_without_x509certificate.errors
+ end
+
+ it "return false when signature wrapping attack" do
+ signature_wrapping_attack = read_invalid_response("signature_wrapping_attack.xml.base64")
+ response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack)
+ response_wrapped.stubs(:conditions).returns(nil)
+ response_wrapped.stubs(:validate_subject_confirmation).returns(true)
+ settings.idp_cert_fingerprint = "afe71c28ef740bc87425be13a2263d37971da1f9"
+ response_wrapped.settings = settings
+ assert !response_wrapped.send(:validate_signature)
+ assert_includes response_wrapped.errors, "Invalid Signature on SAML Response"
+ end
end
describe "#nameid" do
@@ -888,7 +999,7 @@ class RubySamlTest < Minitest::Test
it 'is not possible when encryptID inside the assertion but no private key' do
response_encrypted_nameid.settings = settings
assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do
- assert_equal "test at onelogin.com", response_encrypted_nameid.nameid
+ assert_equal "test at onelogin.com", response_encrypted_nameid.nameid
end
end
@@ -1007,7 +1118,7 @@ class RubySamlTest < Minitest::Test
describe "check right settings" do
it "is not possible to decrypt the assertion if no private key" do
- response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
encrypted_assertion_node = REXML::XPath.first(
response.document,
@@ -1023,7 +1134,7 @@ class RubySamlTest < Minitest::Test
end
it "is possible to decrypt the assertion if private key" do
- response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
+ response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings)
encrypted_assertion_node = REXML::XPath.first(
response.document,
@@ -1090,5 +1201,27 @@ class RubySamlTest < Minitest::Test
assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid
end
end
+
+ end
+ describe "test qualified name id in attributes" do
+
+ it "parsed the nameid" do
+ response = OneLogin::RubySaml::Response.new(read_response("signed_nameid_in_atts.xml"), :settings => settings)
+ response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783'
+ assert_empty response.errors
+ assert_equal "test", response.attributes[:uid]
+ assert_equal "http://idp.example.com/metadata.php/ZdrjpwEdw22vKoxWAbZB78/gQ7s=", response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10')
+ end
+ end
+
+ describe "test unqualified name id in attributes" do
+
+ it "parsed the nameid" do
+ response = OneLogin::RubySaml::Response.new(read_response("signed_unqual_nameid_in_atts.xml"), :settings => settings)
+ response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783'
+ assert_empty response.errors
+ assert_equal "test", response.attributes[:uid]
+ assert_equal "ZdrjpwEdw22vKoxWAbZB78/gQ7s=", response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10')
+ end
end
end
diff --git a/test/responses/invalids/signature_wrapping_attack.xml.base64 b/test/responses/invalids/signature_wrapping_attack.xml.base64
new file mode 100644
index 0000000..dc2c9ca
--- /dev/null
+++ b/test/responses/invalids/signature_wrapping_attack.xml.base64
@@ -0,0 +1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJwZnhjM2QyYjU0Mi0wZjdlLTg3NjctOGU4Ny01YjBkYzY5MTMzNzUiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAzLTIxVDEzOjQxOjA5WiIgRGVzdGluYXRpb249Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZDllMzE5YzFiOGE2N2RhNDgyMjc5NjRjMjhkMjgwZTc4NjBmODA0Ij48c2FtbDpJc3N1ZXI+aHR0cHM6 [...]
\ No newline at end of file
diff --git a/test/responses/response_with_ds_namespace_at_the_root.xml.base64 b/test/responses/response_with_ds_namespace_at_the_root.xml.base64
new file mode 100644
index 0000000..48893cd
--- /dev/null
+++ b/test/responses/response_with_ds_namespace_at_the_root.xml.base64
@@ -0,0 +1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiDQp4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il84ZThkYzVmNjlhOThjYzRjMWZmMzQyN2U1Y2UzNDYwNmZkNjcyZjkxZTYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiIgRGVzdGluYXRpb249Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzRmZWUzYjA0NjM5NWM0ZTc1MTAxMWU5N2Y4 [...]
\ No newline at end of file
diff --git a/test/responses/response_with_signed_message_and_assertion.xml b/test/responses/response_with_signed_message_and_assertion.xml
new file mode 100644
index 0000000..a5bca26
--- /dev/null
+++ b/test/responses/response_with_signed_message_and_assertion.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="pfx0a3cfa31-f178-71f2-9b94-ad4047591acc" Version="2.0" IssueInstant="2012-04-04T07:33:10.921Z" Destination="https://example.com/endpoint">
+ <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">idp.example.com</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+ <ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+ <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+ <ds:Reference URI="#pfx0a3cfa31-f178-71f2-9b94-ad4047591acc"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>hi2Ouec0ovl90Cz+OXAP6FD5X70=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>tJiaa5aZNzLFbBiIsyc0MBI4G1caG+gOW0joGlbMAyY86ERaDwDi1sz98+vykZOgjwkfZL [...]
+<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpA [...]
+ <samlp:Status>
+ <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+ </samlp:Status>
+ <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" IssueInstant="2012-04-04T07:33:10.923Z" ID="pfx7fca52d6-8991-5d99-3147-4f9d7c278d78">
+ <saml:Issuer>idp.myexample.org</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+ <ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+ <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+ <ds:Reference URI="#pfx7fca52d6-8991-5d99-3147-4f9d7c278d78"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>FA0AbR4w9oYdx7MFjERARVJAHps=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>GDH5jhCNX9PFxW+71SOJPyusAOwzECwmd57NDhvA/VKWHnV3PpvpNkOLyamoBNdZ4qxpon [...]
+<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpA [...]
+ <saml:Subject>
+ <saml:NameID NameQualifier="idp.example.com" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">someone at example.org</saml:NameID>
+ <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+ <saml:SubjectConfirmationData Recipient="https://example.com/endpoint" InResponseTo="_f7201940-6055-012f-3bc1-782bcb13c426"/>
+ </saml:SubjectConfirmation>
+ </saml:Subject>
+ <saml:Conditions NotBefore="2012-04-04T07:28:11.442Z" NotOnOrAfter="2012-04-04T07:38:11.442Z">
+ <saml:AudienceRestriction>
+ <saml:Audience>example.com</saml:Audience>
+ </saml:AudienceRestriction>
+ </saml:Conditions>
+ <saml:AuthnStatement AuthnInstant="2012-04-04T07:33:11.442Z">
+ <saml:AuthnContext>
+ <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
+ </saml:AuthnContext>
+ </saml:AuthnStatement>
+ </saml:Assertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/test/responses/response_without_reference_uri.xml.base64 b/test/responses/response_without_reference_uri.xml.base64
new file mode 100644
index 0000000..dd5f7b5
--- /dev/null
+++ b/test/responses/response_without_reference_uri.xml.base64
@@ -0,0 +1 @@
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGQ1OTQzNDdkLTQ5NWYtYjhkMS0wZWUyLTQxY2ZkYTE0ZGQzNSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDEtMDJUMjI6NDg6NDhaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo5MDAxL3YxL3VzZXJzL2F1dGhvcml6ZS9zYW1sIiBDb25zZW50PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y29uc2VudDp1bnNwZWNpZmllZCIgSW5SZXNwb25zZVRvPSJfZWQ5MTVhNDAtNzRmYi0wMTMyLTViMTYtNDhlMGViMTRhMWM3Ij4NCiAgPElzc3VlciB4 [...]
diff --git a/test/responses/signed_nameid_in_atts.xml b/test/responses/signed_nameid_in_atts.xml
new file mode 100644
index 0000000..5e7ec59
--- /dev/null
+++ b/test/responses/signed_nameid_in_atts.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfx3437f097-cd95-f187-4501-0bce24d4d6f8" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
+ <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+ <ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+ <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+ <ds:Reference URI="#pfx3437f097-cd95-f187-4501-0bce24d4d6f8"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>7IMOicPUD8hBuAv8HeVDrUErclY=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>jPBUr/iVsSXvMeT6PhQxSTbi3j6M34OQiyAKLPyPQWypX0uJ04UbC2J4v1DqFqC3OJYyep [...]
+<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpA [...]
+ <samlp:Status>
+ <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+ </samlp:Status>
+ <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
+ <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
+ <saml:Subject>
+ <saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
+ <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+ <saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
+ </saml:SubjectConfirmation>
+ </saml:Subject>
+ <saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
+ <saml:AudienceRestriction>
+ <saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
+ </saml:AudienceRestriction>
+ </saml:Conditions>
+ <saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
+ <saml:AuthnContext>
+ <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
+ </saml:AuthnContext>
+ </saml:AuthnStatement>
+ <saml:AttributeStatement>
+ <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <saml:AttributeValue xsi:type="xs:string">test at example.com</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
+ <saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute FriendlyName="eduPersonTargetedID" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>
+ <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" NameQualifier="http://idp.example.com/metadata.php" SPNameQualifier="http://sp.example.com/demo1/metadata.php">ZdrjpwEdw22vKoxWAbZB78/gQ7s=</saml:NameID>
+ </saml:AttributeValue>
+ </saml:Attribute>
+ </saml:AttributeStatement>
+ </saml:Assertion>
+</samlp:Response>
diff --git a/test/responses/signed_unqual_nameid_in_atts.xml b/test/responses/signed_unqual_nameid_in_atts.xml
new file mode 100644
index 0000000..66a0beb
--- /dev/null
+++ b/test/responses/signed_unqual_nameid_in_atts.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfx852f7331-dd1c-95c3-9cbe-0ab1ebf9c538" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
+ <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+ <ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+ <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+ <ds:Reference URI="#pfx852f7331-dd1c-95c3-9cbe-0ab1ebf9c538"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>LQa/IrEdbLE1BHEP3B7KfvOABpg=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>fWg7OauBy7cExPo+GbNVb1e1OopaYc0ke+BYF1N4bpoej86o7U75xOttDN7oz58LhpDZYW [...]
+<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpA [...]
+ <samlp:Status>
+ <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+ </samlp:Status>
+ <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
+ <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
+ <saml:Subject>
+ <saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
+ <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+ <saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
+ </saml:SubjectConfirmation>
+ </saml:Subject>
+ <saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
+ <saml:AudienceRestriction>
+ <saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
+ </saml:AudienceRestriction>
+ </saml:Conditions>
+ <saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
+ <saml:AuthnContext>
+ <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
+ </saml:AuthnContext>
+ </saml:AuthnStatement>
+ <saml:AttributeStatement>
+ <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <saml:AttributeValue xsi:type="xs:string">test at example.com</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+ <saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
+ <saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute FriendlyName="eduPersonTargetedID" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>
+ <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">ZdrjpwEdw22vKoxWAbZB78/gQ7s=</saml:NameID>
+ </saml:AttributeValue>
+ </saml:Attribute>
+ </saml:AttributeStatement>
+ </saml:Assertion>
+</samlp:Response>
diff --git a/test/responses/valid_response_without_x509certificate.xml.base64 b/test/responses/valid_response_without_x509certificate.xml.base64
new file mode 100644
index 0000000..55f5139
--- /dev/null
+++ b/test/responses/valid_response_without_x509certificate.xml.base64
@@ -0,0 +1 @@
+pVZdd9o4EH3fc/Y/+LiPOcayDTb4BLoU0oSWfBDTbpuXPbI0Bie25Egi0Pz6lQ04kJI03X2CGY/u3LkjjXT8fpVnxgMImXLWNZ0GMt/3/vzjWOI8K8JrkAVnEgwdxGRYOrvmQrCQY5nKkOEcZKhIGPXPx6HbQCGWEoTSUObOkuL1NYXgihOemcZo2DWLZBWTtuvjhFrQScCiXhJbtB00LdIMECIeJIi0TePrlrPG0EulXMCISYWZ0i7kNC3kW6g5RW7ouiFyb0xjCFKlDKtq1VypIrRtXBSNfEFxg3FbSm4TXe4iBw3ItsVPedf8JyFN7DVjZAWg6SDHBYtgDFbQdmMSO14ce22zV8kWVlxEr8wgNyk4g4zPUtYgPLfLIPfY3o09pjKM0pkmtxBbtamsWS6Xy8bSa3Axs12EkI06to6hMp29M3W3DGO7HuiIJbyCG2DGWUpwlj5WJZ+DmnNq9LMZF6ma5y+AO7aDSnALVsQi [...]
\ No newline at end of file
diff --git a/test/test_helper.rb b/test/test_helper.rb
index c6488f1..9c36685 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -10,6 +10,7 @@ require 'rubygems'
require 'bundler'
require 'minitest/autorun'
require 'mocha/setup'
+require 'timecop'
Bundler.require :default, :test
@@ -51,6 +52,10 @@ class Minitest::Test
@response_document_valid_signed ||= read_response("valid_response.xml.base64")
end
+ def response_document_valid_signed_without_x509certificate
+ @response_document_valid_signed_without_x509certificate ||= read_response("valid_response_without_x509certificate.xml.base64")
+ end
+
def response_document_without_recipient
@response_document_without_recipient ||= read_response("response_with_undefined_recipient.xml.base64")
end
@@ -66,6 +71,10 @@ class Minitest::Test
@response_document_without_attributes ||= read_response("response_without_attributes.xml.base64")
end
+ def response_document_without_reference_uri
+ @response_document_without_reference_uri ||= read_response("response_without_reference_uri.xml.base64")
+ end
+
def response_document_with_signed_assertion
@response_document_with_signed_assertion ||= read_response("response_with_signed_assertion.xml.base64")
end
@@ -74,6 +83,10 @@ class Minitest::Test
@response_document_with_signed_assertion_2 ||= read_response("response_with_signed_assertion_2.xml.base64")
end
+ def response_document_with_ds_namespace_at_the_root
+ @response_document_with_ds_namespace_at_the_root ||= read_response("response_with_ds_namespace_at_the_root.xml.base64")
+ end
+
def response_document_unsigned
@response_document_unsigned ||= read_response("response_unsigned_xml_base64")
end
diff --git a/test/xml_security_test.rb b/test/xml_security_test.rb
index 0bf4ccf..fb54077 100644
--- a/test/xml_security_test.rb
+++ b/test/xml_security_test.rb
@@ -1,6 +1,5 @@
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
require 'xml_security'
-require 'timecop'
class XmlSecurityTest < Minitest::Test
include XMLSecurity
@@ -69,13 +68,20 @@ class XmlSecurityTest < Minitest::Test
assert adfs_document.validate_signature(base64cert, false)
end
- it "raise validation error when the X509Certificate is missing" do
+ it "raise validation error when the X509Certificate is missing and no cert provided" do
decoded_response.sub!(/<ds:X509Certificate>.*<\/ds:X509Certificate>/, "")
mod_document = XMLSecurity::SignedDocument.new(decoded_response)
exception = assert_raises(OneLogin::RubySaml::ValidationError) do
mod_document.validate_document("a fingerprint", false) # The fingerprint isn't relevant to this test
end
- assert_equal("Certificate element missing in response (ds:X509Certificate)", exception.message)
+ assert_equal("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", exception.message)
+ end
+
+ it "invalidaties when the X509Certificate is missing and the cert is provided but mismatches" do
+ decoded_response.sub!(/<ds:X509Certificate>.*<\/ds:X509Certificate>/, "")
+ mod_document = XMLSecurity::SignedDocument.new(decoded_response)
+ cert = OpenSSL::X509::Certificate.new(ruby_saml_cert)
+ assert !mod_document.validate_document("a fingerprint", true, :cert => cert) # The fingerprint isn't relevant to this test
end
end
@@ -215,14 +221,14 @@ class XmlSecurityTest < Minitest::Test
assert response.is_valid?
end
- it "return an empty list when inclusive namespace element is missing" do
+ it "return nil when inclusive namespace element is missing" do
response = fixture(:no_signature_ns, false)
response.slice! %r{<InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="#default saml ds xs xsi"/>}
document = XMLSecurity::SignedDocument.new(response)
inclusive_namespaces = document.send(:extract_inclusive_namespaces)
- assert inclusive_namespaces.empty?
+ assert inclusive_namespaces.nil?
end
end
@@ -234,8 +240,7 @@ class XmlSecurityTest < Minitest::Test
settings.issuer = "https://sp.example.com/saml2"
settings.assertion_consumer_service_url = "https://sp.example.com/acs"
settings.single_logout_service_url = "https://sp.example.com/sls"
- end
-
+ end
it "sign an AuthNRequest" do
request = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
@@ -325,5 +330,28 @@ class XmlSecurityTest < Minitest::Test
end
end
end
+
+ describe '#validate_document' do
+ describe 'with valid document' do
+ describe 'when response has signed message and assertion' do
+ let(:document_data) { read_response('response_with_signed_message_and_assertion.xml') }
+ let(:document) { OneLogin::RubySaml::Response.new(document_data).document }
+ let(:fingerprint) { '4b68c453c7d994aad9025c99d5efcf566287fe8d' }
+
+ it 'is valid' do
+ assert document.validate_document(fingerprint, true), 'Document should be valid'
+ end
+ end
+ end
+ describe 'signature_wrapping_attack' do
+ let(:document_data) { read_invalid_response("signature_wrapping_attack.xml.base64") }
+ let(:document) { OneLogin::RubySaml::Response.new(document_data).document }
+ let(:fingerprint) { 'afe71c28ef740bc87425be13a2263d37971da1f9' }
+
+ it 'is invalid' do
+ assert !document.validate_document(fingerprint, true), 'Document should be invalid'
+ end
+ end
+ end
end
end
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-saml.git
More information about the Pkg-ruby-extras-commits
mailing list