ruby-saml 0.8.9 → 0.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

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