ruby-saml 0.8.9 → 0.8.10

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

@@ -1,15 +1,94 @@
1
+ if RUBY_VERSION < '1.9'
2
+ require 'uuid'
3
+ else
4
+ require 'securerandom'
5
+ end
6
+
1
7
  module OneLogin
2
8
  module RubySaml
3
9
 
4
10
  # SAML2 Auxiliary class
5
11
  #
6
12
  class Utils
13
+ @@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
14
+
7
15
  # Given a REXML::Element instance, return the concatenation of all child text nodes. Assumes
8
16
  # that there all children other than text nodes can be ignored (e.g. comments). If nil is
9
17
  # passed, nil will be returned.
10
18
  def self.element_text(element)
11
19
  element.texts.map(&:value).join if element
12
20
  end
21
+
22
+ # Return a properly formatted x509 certificate
23
+ #
24
+ # @param cert [String] The original certificate
25
+ # @return [String] The formatted certificate
26
+ #
27
+ def self.format_cert(cert)
28
+ # don't try to format an encoded certificate or if is empty or nil
29
+ if cert.respond_to?(:ascii_only?)
30
+ return cert if cert.nil? || cert.empty? || !cert.ascii_only?
31
+ else
32
+ return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)
33
+ end
34
+
35
+ if cert.scan(/BEGIN CERTIFICATE/).length > 1
36
+ formatted_cert = []
37
+ cert.scan(/-{5}BEGIN CERTIFICATE-{5}[\n\r]?.*?-{5}END CERTIFICATE-{5}[\n\r]?/m) {|c|
38
+ formatted_cert << format_cert(c)
39
+ }
40
+ formatted_cert.join("\n")
41
+ else
42
+ cert = cert.gsub(/\-{5}\s?(BEGIN|END) CERTIFICATE\s?\-{5}/, "")
43
+ cert = cert.gsub(/\r/, "")
44
+ cert = cert.gsub(/\n/, "")
45
+ cert = cert.gsub(/\s/, "")
46
+ cert = cert.scan(/.{1,64}/)
47
+ cert = cert.join("\n")
48
+ "-----BEGIN CERTIFICATE-----\n#{cert}\n-----END CERTIFICATE-----"
49
+ end
50
+ end
51
+
52
+ # Return a properly formatted private key
53
+ #
54
+ # @param key [String] The original private key
55
+ # @return [String] The formatted private key
56
+ #
57
+ def self.format_private_key(key)
58
+ # don't try to format an encoded private key or if is empty
59
+ return key if key.nil? || key.empty? || key.match(/\x0d/)
60
+
61
+ # is this an rsa key?
62
+ rsa_key = key.match("RSA PRIVATE KEY")
63
+ key = key.gsub(/\-{5}\s?(BEGIN|END)( RSA)? PRIVATE KEY\s?\-{5}/, "")
64
+ key = key.gsub(/\n/, "")
65
+ key = key.gsub(/\r/, "")
66
+ key = key.gsub(/\s/, "")
67
+ key = key.scan(/.{1,64}/)
68
+ key = key.join("\n")
69
+ key_label = rsa_key ? "RSA PRIVATE KEY" : "PRIVATE KEY"
70
+ "-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----"
71
+ end
72
+
73
+ # Build the Query String signature that will be used in the HTTP-Redirect binding
74
+ # to generate the Signature
75
+ # @param params [Hash] Parameters to build the Query String
76
+ # @option params [String] :type 'SAMLRequest' or 'SAMLResponse'
77
+ # @option params [String] :data Base64 encoded SAMLRequest or SAMLResponse
78
+ # @option params [String] :relay_state The RelayState parameter
79
+ # @option params [String] :sig_alg The SigAlg parameter
80
+ # @return [String] The Query String
81
+ #
82
+ def self.build_query(params)
83
+ type, data, relay_state, sig_alg = [:type, :data, :relay_state, :sig_alg].map { |k| params[k]}
84
+ url_string = "#{type}=#{CGI.escape(data)}"
85
+ url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
86
+ url_string << "&SigAlg=#{CGI.escape(sig_alg)}"
87
+ end
88
+
89
+ def self.uuid
90
+ RUBY_VERSION < '1.9' ? "_#{@@uuid_generator.generate}" : "_#{SecureRandom.uuid}"
91
+ end
13
92
  end
14
93
  end
15
94
  end
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '0.8.9'
3
+ VERSION = '0.8.10'
4
4
  end
5
5
  end
data/lib/ruby-saml.rb CHANGED
@@ -2,10 +2,11 @@ require 'onelogin/ruby-saml/logging'
2
2
  require 'onelogin/ruby-saml/authrequest'
3
3
  require 'onelogin/ruby-saml/logoutrequest'
4
4
  require 'onelogin/ruby-saml/logoutresponse'
5
+ require 'onelogin/ruby-saml/slo_logoutresponse'
5
6
  require 'onelogin/ruby-saml/response'
6
7
  require 'onelogin/ruby-saml/settings'
7
8
  require 'onelogin/ruby-saml/utils'
8
9
  require 'onelogin/ruby-saml/validation_error'
9
10
  require 'onelogin/ruby-saml/metadata'
10
11
  require 'onelogin/ruby-saml/version'
11
- require 'onelogin/ruby-saml/attributes'
12
+ require 'onelogin/ruby-saml/attributes'
data/lib/xml_security.rb CHANGED
@@ -34,17 +34,153 @@ require "onelogin/ruby-saml/utils"
34
34
 
35
35
  module XMLSecurity
36
36
 
37
- class SignedDocument < REXML::Document
38
- C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
39
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
37
+ class BaseDocument < REXML::Document
38
+ REXML::Document::entity_expansion_limit = 0
39
+
40
+ C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
41
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
42
+ NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
43
+ Nokogiri::XML::ParseOptions::NONET
44
+
45
+ def canon_algorithm(element)
46
+ algorithm = element
47
+ if algorithm.is_a?(REXML::Element)
48
+ algorithm = element.attribute('Algorithm').value
49
+ end
50
+
51
+ case algorithm
52
+ when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
53
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
54
+ Nokogiri::XML::XML_C14N_1_0
55
+ when "http://www.w3.org/2006/12/xml-c14n11",
56
+ "http://www.w3.org/2006/12/xml-c14n11#WithComments"
57
+ Nokogiri::XML::XML_C14N_1_1
58
+ else
59
+ Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
60
+ end
61
+ end
62
+
63
+ def algorithm(element)
64
+ algorithm = element
65
+ if algorithm.is_a?(REXML::Element)
66
+ algorithm = element.attribute("Algorithm").value
67
+ end
68
+
69
+ algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
70
+
71
+ case algorithm
72
+ when 256 then OpenSSL::Digest::SHA256
73
+ when 384 then OpenSSL::Digest::SHA384
74
+ when 512 then OpenSSL::Digest::SHA512
75
+ else
76
+ OpenSSL::Digest::SHA1
77
+ end
78
+ end
79
+ end
80
+
81
+ class Document < BaseDocument
82
+ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
83
+ RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
84
+ RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
85
+ RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
86
+ SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
87
+ SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
88
+ SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
89
+ SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
90
+ ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
91
+ INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
92
+
93
+ attr_writer :uuid
94
+
95
+ def uuid
96
+ @uuid ||= begin
97
+ document.root.nil? ? nil : document.root.attributes['ID']
98
+ end
99
+ end
100
+
101
+ def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
102
+ noko = Nokogiri::XML(self.to_s) do |config|
103
+ config.options = NOKOGIRI_OPTIONS
104
+ end
105
+
106
+ signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
107
+ signed_info_element = signature_element.add_element("ds:SignedInfo")
108
+ signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
109
+ signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
110
+
111
+ # Add Reference
112
+ reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
113
+
114
+ # Add Transforms
115
+ transforms_element = reference_element.add_element("ds:Transforms")
116
+ transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
117
+ c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
118
+ c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
119
+
120
+ digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
121
+ inclusive_namespaces = INC_PREFIX_LIST.split(" ")
122
+ canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
123
+ reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
124
+
125
+ # add SignatureValue
126
+ noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
127
+ config.options = NOKOGIRI_OPTIONS
128
+ end
129
+
130
+ noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
131
+ canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
132
+
133
+ signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
134
+ signature_element.add_element("ds:SignatureValue").text = signature
135
+
136
+ # add KeyInfo
137
+ key_info_element = signature_element.add_element("ds:KeyInfo")
138
+ x509_element = key_info_element.add_element("ds:X509Data")
139
+ x509_cert_element = x509_element.add_element("ds:X509Certificate")
140
+ if certificate.is_a?(String)
141
+ certificate = OpenSSL::X509::Certificate.new(certificate)
142
+ end
143
+ x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
144
+
145
+ # add the signature
146
+ issuer_element = self.elements["//saml:Issuer"]
147
+ if issuer_element
148
+ self.root.insert_after issuer_element, signature_element
149
+ else
150
+ if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
151
+ self.root.insert_before sp_sso_descriptor, signature_element
152
+ else
153
+ self.root.add_element(signature_element)
154
+ end
155
+ end
156
+ end
157
+
158
+ protected
159
+
160
+ def compute_signature(private_key, signature_algorithm, document)
161
+ Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
162
+ end
163
+
164
+ def compute_digest(document, digest_algorithm)
165
+ digest = digest_algorithm.digest(document)
166
+ Base64.encode64(digest).strip!
167
+ end
168
+
169
+ end
170
+
171
+ class SignedDocument < BaseDocument
40
172
 
41
- attr_accessor :signed_element_id
173
+ attr_writer :signed_element_id
42
174
 
43
175
  def initialize(response)
44
176
  super(response)
45
177
  extract_signed_element_id
46
178
  end
47
179
 
180
+ def signed_element_id
181
+ @signed_element_id ||= extract_signed_element_id
182
+ end
183
+
48
184
  def validate_document(idp_cert_fingerprint, soft = true)
49
185
  # get cert from response
50
186
  cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
@@ -69,7 +205,9 @@ module XMLSecurity
69
205
  # check for inclusive namespaces
70
206
  inclusive_namespaces = extract_inclusive_namespaces
71
207
 
72
- document = Nokogiri.parse(self.to_s)
208
+ document = Nokogiri::XML(self.to_s) do |config|
209
+ config.options = NOKOGIRI_OPTIONS
210
+ end
73
211
 
74
212
  # create a working copy so we don't modify the original
75
213
  @working_copy ||= REXML::Document.new(self.to_s).root
@@ -80,7 +218,6 @@ module XMLSecurity
80
218
  element.remove
81
219
  end
82
220
 
83
-
84
221
  # verify signature
85
222
  signed_info_element = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
86
223
  noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
@@ -131,30 +268,16 @@ module XMLSecurity
131
268
  end
132
269
 
133
270
  def extract_signed_element_id
134
- reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
135
- self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
136
- end
271
+ reference_element = REXML::XPath.first(
272
+ self,
273
+ "//ds:Signature/ds:SignedInfo/ds:Reference",
274
+ {"ds"=>DSIG}
275
+ )
137
276
 
138
- def canon_algorithm(element)
139
- algorithm = element.attribute('Algorithm').value if element
140
- case algorithm
141
- when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
142
- when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
143
- when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1
144
- else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
145
- end
146
- end
277
+ return nil if reference_element.nil?
147
278
 
148
- def algorithm(element)
149
- algorithm = element.attribute("Algorithm").value if element
150
- algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
151
- case algorithm
152
- when 256 then OpenSSL::Digest::SHA256
153
- when 384 then OpenSSL::Digest::SHA384
154
- when 512 then OpenSSL::Digest::SHA512
155
- else
156
- OpenSSL::Digest::SHA1
157
- end
279
+ sei = reference_element.attribute("URI").value[1..-1]
280
+ sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei
158
281
  end
159
282
 
160
283
  def extract_inclusive_namespaces
@@ -0,0 +1,14 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJV
3
+ UzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMI
4
+ T25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMx
5
+ ODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5k
6
+ aWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0G
7
+ CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5Pckw
8
+ sNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl
9
+ 0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA
10
+ 7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zd
11
+ qHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP
12
+ 7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3
13
+ QpQkH57ybF/OoryPe+2h
14
+ -----END CERTIFICATE-----
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXAIBAAKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCH
3
+ k8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G1
4
+ 4jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQAB
5
+ AoGBALGR6bRBit+yV5TUU3MZSrf8WQSLWDLgs/33FQSAEYSib4+DJke2lKbI6jkG
6
+ UoSJgFUXFbaQLtMY2+3VDsMKPBdAge9gIdvbkC4yoKjLGm/FBDOxxZcfLpR+9OPq
7
+ U3qM9D0CNuliBWI7Je+p/zs09HIYucpDXy9E18KA1KNF6rfhAkEA9KoNam6wAKnm
8
+ vMzz31ws3RuIOUeo2rx6aaVY95+P9tTxd6U+pNkwxy1aCGP+InVSwlYNA1aQ4Axi
9
+ /GdMIWMkxwJBAPO1CP7cQNZQmu7yusY+GUObDII5YK9WLaY4RAicn5378crPBFxv
10
+ Ukqf9G6FHo7u88iTCIp+vwa3Hn9Tumg3iP0CQQDgUXWBasCVqzCxU5wY4tMDWjXY
11
+ hpoLCpmVeRML3dDJt004rFm2HKe7Rhpw7PTZNQZOxUSjFeA4e0LaNf838UWLAkB8
12
+ QfbHM3ffjhOg96PhhjINdVWoZCb230LBOHj/xxPfUmFTHcBEfQIBSJMxcrBFAnLL
13
+ 9qPpMXymqOFk3ETz9DTlAj8E0qGbp78aVbTOtuwEwNJII+RPw+Zkc+lKR+yaWkAz
14
+ fIXw527NPHH3+rnBG72wyZr9ud4LAum9jh+5No1LQpk=
15
+ -----END RSA PRIVATE KEY-----
@@ -1,14 +1,17 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+ require 'uuid'
2
3
 
3
- class RequestTest < Test::Unit::TestCase
4
+ class LogoutRequestTest < Minitest::Test
4
5
 
5
- context "Logoutrequest" do
6
- settings = OneLogin::RubySaml::Settings.new
6
+ describe "Logoutrequest" do
7
+ let(:settings) { OneLogin::RubySaml::Settings.new }
7
8
 
8
- should "create the deflated SAMLRequest URL parameter" do
9
+ before do
9
10
  settings.idp_slo_target_url = "http://unauth.com/logout"
10
11
  settings.name_identifier_value = "f00f00"
12
+ end
11
13
 
14
+ it "create the deflated SAMLRequest URL parameter" do
12
15
  unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
13
16
  assert unauth_url =~ /^http:\/\/unauth\.com\/logout\?SAMLRequest=/
14
17
 
@@ -17,8 +20,7 @@ class RequestTest < Test::Unit::TestCase
17
20
  assert_match /^<samlp:LogoutRequest/, inflated
18
21
  end
19
22
 
20
- should "support additional params" do
21
-
23
+ it "support additional params" do
22
24
  unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :hello => nil })
23
25
  assert unauth_url =~ /&hello=$/
24
26
 
@@ -26,8 +28,7 @@ class RequestTest < Test::Unit::TestCase
26
28
  assert unauth_url =~ /&foo=bar$/
27
29
  end
28
30
 
29
- should "set sessionindex" do
30
- settings.idp_slo_target_url = "http://example.com"
31
+ it "set sessionindex" do
31
32
  sessionidx = UUID.new.generate
32
33
  settings.sessionindex = sessionidx
33
34
 
@@ -38,9 +39,7 @@ class RequestTest < Test::Unit::TestCase
38
39
  assert_match %r(#{sessionidx}</samlp:SessionIndex>), inflated
39
40
  end
40
41
 
41
- should "set name_identifier_value" do
42
- settings = OneLogin::RubySaml::Settings.new
43
- settings.idp_slo_target_url = "http://example.com"
42
+ it "set name_identifier_value" do
44
43
  settings.name_identifier_format = "transient"
45
44
  name_identifier_value = "abc123"
46
45
  settings.name_identifier_value = name_identifier_value
@@ -52,41 +51,25 @@ class RequestTest < Test::Unit::TestCase
52
51
  assert_match %r(#{name_identifier_value}</saml:NameID>), inflated
53
52
  end
54
53
 
55
- should "require name_identifier_value" do
56
- settings = OneLogin::RubySaml::Settings.new
57
- settings.idp_slo_target_url = "http://example.com"
58
- settings.name_identifier_format = nil
59
-
60
- assert_raises(OneLogin::RubySaml::ValidationError) { OneLogin::RubySaml::Logoutrequest.new.create(settings) }
61
- end
62
-
63
- context "when the target url doesn't contain a query string" do
64
- should "create the SAMLRequest parameter correctly" do
65
- settings = OneLogin::RubySaml::Settings.new
54
+ describe "when the target url doesn't contain a query string" do
55
+ it "create the SAMLRequest parameter correctly" do
66
56
  settings.idp_slo_target_url = "http://example.com"
67
- settings.name_identifier_value = "f00f00"
68
-
69
57
  unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
70
58
  assert unauth_url =~ /^http:\/\/example.com\?SAMLRequest/
71
59
  end
72
60
  end
73
61
 
74
- context "when the target url contains a query string" do
75
- should "create the SAMLRequest parameter correctly" do
76
- settings = OneLogin::RubySaml::Settings.new
62
+ describe "when the target url contains a query string" do
63
+ it "create the SAMLRequest parameter correctly" do
77
64
  settings.idp_slo_target_url = "http://example.com?field=value"
78
- settings.name_identifier_value = "f00f00"
79
-
80
65
  unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
81
66
  assert unauth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
82
67
  end
83
68
  end
84
69
 
85
- context "consumation of logout may need to track the transaction" do
86
- should "have access to the request uuid" do
87
- settings = OneLogin::RubySaml::Settings.new
70
+ describe "consumation of logout may need to track the transaction" do
71
+ it "have access to the request uuid" do
88
72
  settings.idp_slo_target_url = "http://example.com?field=value"
89
- settings.name_identifier_value = "f00f00"
90
73
 
91
74
  unauth_req = OneLogin::RubySaml::Logoutrequest.new
92
75
  unauth_url = unauth_req.create(settings)
@@ -97,15 +80,167 @@ class RequestTest < Test::Unit::TestCase
97
80
  end
98
81
  end
99
82
 
100
- def decode_saml_request_payload(unauth_url)
101
- payload = CGI.unescape(unauth_url.split("SAMLRequest=").last)
102
- decoded = Base64.decode64(payload)
83
+ describe "when the settings indicate to sign (embedded) logout request" do
84
+
85
+ before do
86
+ # sign the logout request
87
+ settings.security[:logout_requests_signed] = true
88
+ settings.security[:embed_sign] = true
89
+ settings.certificate = ruby_saml_cert_text
90
+ settings.private_key = ruby_saml_key_text
91
+ end
92
+
93
+ it "doesn't sign through create_xml_document" do
94
+ unauth_req = OneLogin::RubySaml::Logoutrequest.new
95
+ inflated = unauth_req.create_xml_document(settings).to_s
96
+
97
+ refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
98
+ refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
99
+ refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
100
+ end
101
+
102
+ it "sign unsigned request" do
103
+ unauth_req = OneLogin::RubySaml::Logoutrequest.new
104
+ unauth_req_doc = unauth_req.create_xml_document(settings)
105
+ inflated = unauth_req_doc.to_s
106
+
107
+ refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
108
+ refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
109
+ refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
110
+
111
+ inflated = unauth_req.sign_document(unauth_req_doc, settings).to_s
112
+
113
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
114
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
115
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
116
+ end
117
+
118
+ it "signs through create_logout_request_xml_doc" do
119
+ unauth_req = OneLogin::RubySaml::Logoutrequest.new
120
+ inflated = unauth_req.create_logout_request_xml_doc(settings).to_s
121
+
122
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
123
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
124
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
125
+ end
126
+
127
+ it "created a signed logout request" do
128
+ settings.compress_request = true
129
+
130
+ unauth_req = OneLogin::RubySaml::Logoutrequest.new
131
+ unauth_url = unauth_req.create(settings)
132
+
133
+ inflated = decode_saml_request_payload(unauth_url)
134
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
135
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
136
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
137
+ end
138
+
139
+ it "create a signed logout request with 256 digest and signature method" do
140
+ settings.compress_request = false
141
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
142
+ settings.security[:digest_method] = XMLSecurity::Document::SHA256
143
+
144
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
145
+ request_xml = Base64.decode64(params["SAMLRequest"])
146
+
147
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
148
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
149
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha256'/>], request_xml
150
+ end
151
+
152
+ it "create a signed logout request with 512 digest and signature method RSA_SHA384" do
153
+ settings.compress_request = false
154
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
155
+ settings.security[:digest_method] = XMLSecurity::Document::SHA512
156
+
157
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
158
+ request_xml = Base64.decode64(params["SAMLRequest"])
159
+
160
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
161
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'/>], request_xml
162
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha512'/>], request_xml
163
+ end
164
+ end
165
+
166
+ describe "#create_params when the settings indicate to sign the logout request" do
167
+
168
+ let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
169
+
170
+ before do
171
+ # sign the logout request
172
+ settings.security[:logout_requests_signed] = true
173
+ settings.security[:embed_sign] = false
174
+ settings.certificate = ruby_saml_cert_text
175
+ settings.private_key = ruby_saml_key_text
176
+ end
177
+
178
+ it "create a signature parameter with RSA_SHA1 / SHA1 and validate it" do
179
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
180
+
181
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
182
+ assert params['SAMLRequest']
183
+ assert params[:RelayState]
184
+ assert params['Signature']
185
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
186
+
187
+ query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
188
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
189
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
190
+
191
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
192
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA1
193
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
194
+ end
195
+
196
+ it "create a signature parameter with RSA_SHA256 / SHA256 and validate it" do
197
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
198
+
199
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
200
+ assert params['Signature']
201
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
202
+
203
+ query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
204
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
205
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
206
+
207
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
208
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA256
209
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
210
+ end
211
+
212
+ it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do
213
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
214
+
215
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
216
+ assert params['Signature']
217
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384
218
+
219
+ query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
220
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
221
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
222
+
223
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
224
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA384
225
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
226
+ end
227
+
228
+ it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do
229
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512
230
+
231
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
232
+ assert params['Signature']
233
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512
234
+
235
+ query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
236
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
237
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
238
+
239
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
240
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA512
241
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
242
+ end
103
243
 
104
- zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
105
- inflated = zstream.inflate(decoded)
106
- zstream.finish
107
- zstream.close
108
- inflated
109
244
  end
110
245
 
111
246
  end