ruby-saml 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +10 -0
- data/Gemfile +1 -1
- data/README.md +31 -7
- data/changelog.md +18 -0
- data/gemfiles/nokogiri-1.5.gemfile +5 -0
- data/lib/onelogin/ruby-saml.rb +16 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +10 -5
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +20 -1
- data/lib/onelogin/ruby-saml/logoutrequest.rb +9 -5
- data/lib/onelogin/ruby-saml/logoutresponse.rb +2 -0
- data/lib/onelogin/ruby-saml/metadata.rb +16 -6
- data/lib/onelogin/ruby-saml/response.rb +17 -15
- data/lib/onelogin/ruby-saml/saml_message.rb +44 -30
- data/lib/onelogin/ruby-saml/settings.rb +17 -7
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +2 -0
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +9 -5
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +1 -16
- data/lib/xml_security.rb +39 -17
- data/ruby-saml.gemspec +5 -6
- data/test/idp_metadata_parser_test.rb +6 -2
- data/test/logoutrequest_test.rb +50 -22
- data/test/logoutresponse_test.rb +2 -1
- data/test/metadata_test.rb +100 -55
- data/test/request_test.rb +49 -24
- data/test/response_test.rb +41 -8
- data/test/responses/test_sign.xml +43 -0
- data/test/settings_test.rb +16 -0
- data/test/slo_logoutrequest_test.rb +2 -0
- data/test/slo_logoutresponse_test.rb +51 -21
- data/test/test_helper.rb +0 -1
- data/test/xml_security_test.rb +41 -4
- metadata +10 -6
@@ -1,3 +1,7 @@
|
|
1
|
+
require "xml_security"
|
2
|
+
require "onelogin/ruby-saml/attribute_service"
|
3
|
+
require "onelogin/ruby-saml/utils"
|
4
|
+
|
1
5
|
module OneLogin
|
2
6
|
module RubySaml
|
3
7
|
class Settings
|
@@ -5,7 +9,10 @@ module OneLogin
|
|
5
9
|
config = DEFAULTS.merge(overrides)
|
6
10
|
config.each do |k,v|
|
7
11
|
acc = "#{k.to_s}=".to_sym
|
8
|
-
|
12
|
+
if self.respond_to? acc
|
13
|
+
value = v.is_a?(Hash) ? v.dup : v
|
14
|
+
self.send(acc, value)
|
15
|
+
end
|
9
16
|
end
|
10
17
|
@attribute_consuming_service = AttributeService.new
|
11
18
|
end
|
@@ -16,6 +23,7 @@ module OneLogin
|
|
16
23
|
attr_accessor :idp_slo_target_url
|
17
24
|
attr_accessor :idp_cert
|
18
25
|
attr_accessor :idp_cert_fingerprint
|
26
|
+
attr_accessor :idp_cert_fingerprint_algorithm
|
19
27
|
# SP Data
|
20
28
|
attr_accessor :issuer
|
21
29
|
attr_accessor :assertion_consumer_service_url
|
@@ -97,20 +105,22 @@ module OneLogin
|
|
97
105
|
private
|
98
106
|
|
99
107
|
DEFAULTS = {
|
100
|
-
:assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
101
|
-
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
108
|
+
:assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
|
109
|
+
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
|
110
|
+
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
|
102
111
|
:compress_request => true,
|
103
112
|
:compress_response => true,
|
104
113
|
:security => {
|
105
114
|
:authn_requests_signed => false,
|
106
115
|
:logout_requests_signed => false,
|
107
|
-
:logout_responses_signed
|
116
|
+
:logout_responses_signed => false,
|
117
|
+
:metadata_signed => false,
|
108
118
|
:embed_sign => false,
|
109
119
|
:digest_method => XMLSecurity::Document::SHA1,
|
110
|
-
:signature_method => XMLSecurity::Document::
|
111
|
-
},
|
120
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1
|
121
|
+
}.freeze,
|
112
122
|
:double_quote_xml_attribute_values => false,
|
113
|
-
}
|
123
|
+
}.freeze
|
114
124
|
end
|
115
125
|
end
|
116
126
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require "uuid"
|
2
2
|
|
3
3
|
require "onelogin/ruby-saml/logging"
|
4
|
+
require "onelogin/ruby-saml/saml_message"
|
4
5
|
|
5
6
|
module OneLogin
|
6
7
|
module RubySaml
|
@@ -25,7 +26,10 @@ module OneLogin
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
28
|
-
|
29
|
+
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
30
|
+
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
31
|
+
# conflicts so this line will solve them.
|
32
|
+
relay_state = params[:RelayState] || params['RelayState']
|
29
33
|
|
30
34
|
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
31
35
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
@@ -40,10 +44,10 @@ module OneLogin
|
|
40
44
|
response_params = {"SAMLResponse" => base64_response}
|
41
45
|
|
42
46
|
if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key
|
43
|
-
params['SigAlg'] =
|
47
|
+
params['SigAlg'] = settings.security[:signature_method]
|
44
48
|
url_string = "SAMLResponse=#{CGI.escape(base64_response)}"
|
45
|
-
url_string
|
46
|
-
url_string
|
49
|
+
url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
|
50
|
+
url_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
47
51
|
private_key = settings.get_sp_key()
|
48
52
|
signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string)
|
49
53
|
params['Signature'] = encode(signature)
|
@@ -86,7 +90,7 @@ module OneLogin
|
|
86
90
|
issuer.text = settings.issuer
|
87
91
|
end
|
88
92
|
|
89
|
-
#
|
93
|
+
# embed signature
|
90
94
|
if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
91
95
|
private_key = settings.get_sp_key()
|
92
96
|
cert = settings.get_sp_cert()
|
data/lib/ruby-saml.rb
CHANGED
@@ -1,16 +1 @@
|
|
1
|
-
require 'onelogin/ruby-saml
|
2
|
-
require 'onelogin/ruby-saml/saml_message'
|
3
|
-
require 'onelogin/ruby-saml/authrequest'
|
4
|
-
require 'onelogin/ruby-saml/logoutrequest'
|
5
|
-
require 'onelogin/ruby-saml/logoutresponse'
|
6
|
-
require 'onelogin/ruby-saml/attributes'
|
7
|
-
require 'onelogin/ruby-saml/slo_logoutrequest'
|
8
|
-
require 'onelogin/ruby-saml/slo_logoutresponse'
|
9
|
-
require 'onelogin/ruby-saml/response'
|
10
|
-
require 'onelogin/ruby-saml/settings'
|
11
|
-
require 'onelogin/ruby-saml/attribute_service'
|
12
|
-
require 'onelogin/ruby-saml/validation_error'
|
13
|
-
require 'onelogin/ruby-saml/metadata'
|
14
|
-
require 'onelogin/ruby-saml/idp_metadata_parser'
|
15
|
-
require 'onelogin/ruby-saml/utils'
|
16
|
-
require 'onelogin/ruby-saml/version'
|
1
|
+
require 'onelogin/ruby-saml'
|
data/lib/xml_security.rb
CHANGED
@@ -56,9 +56,10 @@ module XMLSecurity
|
|
56
56
|
algorithm = element
|
57
57
|
if algorithm.is_a?(REXML::Element)
|
58
58
|
algorithm = element.attribute("Algorithm").value
|
59
|
-
algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
|
60
59
|
end
|
61
60
|
|
61
|
+
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
62
|
+
|
62
63
|
case algorithm
|
63
64
|
when 256 then OpenSSL::Digest::SHA256
|
64
65
|
when 384 then OpenSSL::Digest::SHA384
|
@@ -71,15 +72,25 @@ module XMLSecurity
|
|
71
72
|
end
|
72
73
|
|
73
74
|
class Document < BaseDocument
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
76
|
+
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
77
|
+
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
78
|
+
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
79
|
+
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
80
|
+
SHA256 = "http://www.w3.org/2001/04/xmldsig-more#sha256"
|
81
|
+
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
|
82
|
+
SHA512 = "http://www.w3.org/2001/04/xmldsig-more#sha512"
|
78
83
|
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
79
|
-
INC_PREFIX_LIST = "#default samlp saml ds xs xsi"
|
84
|
+
INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
|
80
85
|
|
81
86
|
attr_accessor :uuid
|
82
87
|
|
88
|
+
def uuid
|
89
|
+
@uuid ||= begin
|
90
|
+
document.root.nil? ? nil : document.root.attributes['ID']
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
83
94
|
#<Signature>
|
84
95
|
#<SignedInfo>
|
85
96
|
#<CanonicalizationMethod />
|
@@ -95,9 +106,8 @@ module XMLSecurity
|
|
95
106
|
#<KeyInfo />
|
96
107
|
#<Object />
|
97
108
|
#</Signature>
|
98
|
-
def sign_document(private_key, certificate, signature_method =
|
109
|
+
def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
|
99
110
|
noko = Nokogiri.parse(self.to_s)
|
100
|
-
canon_doc = noko.canonicalize(canon_algorithm(C14N))
|
101
111
|
|
102
112
|
signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
|
103
113
|
signed_info_element = signature_element.add_element("ds:SignedInfo")
|
@@ -110,16 +120,19 @@ module XMLSecurity
|
|
110
120
|
# Add Transforms
|
111
121
|
transforms_element = reference_element.add_element("ds:Transforms")
|
112
122
|
transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
|
113
|
-
transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
|
114
|
-
|
123
|
+
c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
|
124
|
+
c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
|
115
125
|
|
116
126
|
digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
|
127
|
+
inclusive_namespaces = INC_PREFIX_LIST.split(" ")
|
128
|
+
canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
|
117
129
|
reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
|
118
130
|
|
119
131
|
# add SignatureValue
|
120
132
|
noko_sig_element = Nokogiri.parse(signature_element.to_s)
|
121
133
|
noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
|
122
134
|
canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
|
135
|
+
|
123
136
|
signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
|
124
137
|
signature_element.add_element("ds:SignatureValue").text = signature
|
125
138
|
|
@@ -137,7 +150,11 @@ module XMLSecurity
|
|
137
150
|
if issuer_element
|
138
151
|
self.root.insert_after issuer_element, signature_element
|
139
152
|
else
|
140
|
-
self.
|
153
|
+
if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
|
154
|
+
self.root.insert_before sp_sso_descriptor, signature_element
|
155
|
+
else
|
156
|
+
self.root.add_element(signature_element)
|
157
|
+
end
|
141
158
|
end
|
142
159
|
end
|
143
160
|
|
@@ -165,7 +182,7 @@ module XMLSecurity
|
|
165
182
|
extract_signed_element_id
|
166
183
|
end
|
167
184
|
|
168
|
-
def validate_document(idp_cert_fingerprint, soft = true)
|
185
|
+
def validate_document(idp_cert_fingerprint, soft = true, options = {})
|
169
186
|
# get cert from response
|
170
187
|
cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
|
171
188
|
unless cert_element
|
@@ -175,13 +192,18 @@ module XMLSecurity
|
|
175
192
|
raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)")
|
176
193
|
end
|
177
194
|
end
|
178
|
-
base64_cert
|
179
|
-
cert_text
|
180
|
-
cert
|
195
|
+
base64_cert = cert_element.text
|
196
|
+
cert_text = Base64.decode64(base64_cert)
|
197
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
181
198
|
|
182
|
-
|
183
|
-
|
199
|
+
if options[:fingerprint_alg]
|
200
|
+
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
|
201
|
+
else
|
202
|
+
fingerprint_alg = OpenSSL::Digest::SHA1.new
|
203
|
+
end
|
204
|
+
fingerprint = fingerprint_alg.hexdigest(cert.to_der)
|
184
205
|
|
206
|
+
# check cert matches registered idp cert
|
185
207
|
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
186
208
|
@errors << "Fingerprint mismatch"
|
187
209
|
return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
|
data/ruby-saml.gemspec
CHANGED
@@ -26,12 +26,11 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.test_files = `git ls-files test/*`.split("\n")
|
27
27
|
|
28
28
|
s.add_runtime_dependency('uuid', '~> 2.3')
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
29
|
+
|
30
|
+
# Because runtime dependencies are determined at build time, we cannot make
|
31
|
+
# Nokogiri's version dependent on the Ruby version, even though we would
|
32
|
+
# have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions.
|
33
|
+
s.add_runtime_dependency('nokogiri', '>= 1.5.10')
|
35
34
|
|
36
35
|
s.add_development_dependency('minitest', '~> 5.5')
|
37
36
|
s.add_development_dependency('mocha', '~> 0.14')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
|
-
|
3
|
-
require '
|
2
|
+
|
3
|
+
require 'onelogin/ruby-saml/idp_metadata_parser'
|
4
4
|
|
5
5
|
class IdpMetadataParserTest < Minitest::Test
|
6
6
|
|
@@ -14,9 +14,11 @@ class IdpMetadataParserTest < Minitest::Test
|
|
14
14
|
|
15
15
|
settings = idp_metadata_parser.parse(idp_metadata)
|
16
16
|
|
17
|
+
assert_equal "https://example.hello.com/access/saml/idp.xml", settings.idp_entity_id
|
17
18
|
assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url
|
18
19
|
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
|
19
20
|
assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url
|
21
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
@@ -37,9 +39,11 @@ class IdpMetadataParserTest < Minitest::Test
|
|
37
39
|
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
38
40
|
settings = idp_metadata_parser.parse_remote(@url)
|
39
41
|
|
42
|
+
assert_equal "https://example.hello.com/access/saml/idp.xml", settings.idp_entity_id
|
40
43
|
assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url
|
41
44
|
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
|
42
45
|
assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url
|
46
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
|
43
47
|
assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode
|
44
48
|
end
|
45
49
|
|
data/test/logoutrequest_test.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
2
|
|
3
|
+
require 'onelogin/ruby-saml/logoutrequest'
|
4
|
+
|
3
5
|
class RequestTest < Minitest::Test
|
4
6
|
|
5
7
|
describe "Logoutrequest" do
|
@@ -88,7 +90,7 @@ class RequestTest < Minitest::Test
|
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
91
|
-
describe "when the settings indicate to sign (
|
93
|
+
describe "when the settings indicate to sign (embedded) logout request" do
|
92
94
|
it "created a signed logout request" do
|
93
95
|
settings = OneLogin::RubySaml::Settings.new
|
94
96
|
settings.idp_slo_target_url = "http://example.com?field=value"
|
@@ -104,6 +106,8 @@ class RequestTest < Minitest::Test
|
|
104
106
|
|
105
107
|
inflated = decode_saml_request_payload(unauth_url)
|
106
108
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
109
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
110
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
107
111
|
end
|
108
112
|
|
109
113
|
it "create a signed logout request with 256 digest and signature methods" do
|
@@ -114,7 +118,7 @@ class RequestTest < Minitest::Test
|
|
114
118
|
# sign the logout request
|
115
119
|
settings.security[:logout_requests_signed] = true
|
116
120
|
settings.security[:embed_sign] = true
|
117
|
-
settings.security[:signature_method] = XMLSecurity::Document::
|
121
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
118
122
|
settings.security[:digest_method] = XMLSecurity::Document::SHA512
|
119
123
|
settings.certificate = ruby_saml_cert_text
|
120
124
|
settings.private_key = ruby_saml_key_text
|
@@ -123,34 +127,58 @@ class RequestTest < Minitest::Test
|
|
123
127
|
request_xml = Base64.decode64(params["SAMLRequest"])
|
124
128
|
|
125
129
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
126
|
-
|
127
|
-
|
130
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
|
131
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#sha512'/>], request_xml
|
128
132
|
end
|
129
133
|
end
|
130
134
|
|
131
|
-
describe "when the settings indicate to sign the logout request" do
|
132
|
-
|
133
|
-
settings = OneLogin::RubySaml::Settings.new
|
134
|
-
settings.compress_request = false
|
135
|
-
settings.
|
136
|
-
settings.name_identifier_value = "f00f00"
|
137
|
-
settings.security[:logout_requests_signed] = true
|
138
|
-
settings.security[:embed_sign] = false
|
139
|
-
settings.
|
140
|
-
settings.
|
141
|
-
|
135
|
+
describe "#create_params when the settings indicate to sign the logout request" do
|
136
|
+
def setup
|
137
|
+
@settings = OneLogin::RubySaml::Settings.new
|
138
|
+
@settings.compress_request = false
|
139
|
+
@settings.idp_sso_target_url = "http://example.com?field=value"
|
140
|
+
@settings.name_identifier_value = "f00f00"
|
141
|
+
@settings.security[:logout_requests_signed] = true
|
142
|
+
@settings.security[:embed_sign] = false
|
143
|
+
@settings.certificate = ruby_saml_cert_text
|
144
|
+
@settings.private_key = ruby_saml_key_text
|
145
|
+
@cert = OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
|
146
|
+
end
|
142
147
|
|
143
|
-
|
148
|
+
it "create a signature parameter with RSA_SHA1 and validate it" do
|
149
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
150
|
+
|
151
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(@settings, :RelayState => 'http://example.com')
|
152
|
+
assert params['SAMLRequest']
|
153
|
+
assert params[:RelayState]
|
144
154
|
assert params['Signature']
|
145
|
-
|
155
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
|
146
156
|
|
147
|
-
|
148
|
-
|
149
|
-
|
157
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
158
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
159
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
160
|
+
|
161
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
162
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA1
|
163
|
+
assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "create a signature parameter with RSA_SHA256 and validate it" do
|
167
|
+
@settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
168
|
+
|
169
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(@settings, :RelayState => 'http://example.com')
|
150
170
|
assert params['Signature']
|
151
|
-
|
171
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
|
172
|
+
|
173
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
174
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
175
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
176
|
+
|
177
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
178
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA256
|
179
|
+
assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
152
180
|
end
|
153
|
-
end
|
154
181
|
|
182
|
+
end
|
155
183
|
end
|
156
184
|
end
|
data/test/logoutresponse_test.rb
CHANGED
data/test/metadata_test.rb
CHANGED
@@ -1,88 +1,133 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
2
|
|
3
|
+
require 'onelogin/ruby-saml/metadata'
|
4
|
+
|
3
5
|
class MetadataTest < Minitest::Test
|
4
6
|
|
5
7
|
describe 'Metadata' do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
let(:settings) { OneLogin::RubySaml::Settings.new }
|
9
|
+
let(:xml_text) { OneLogin::RubySaml::Metadata.new.generate(settings, false) }
|
10
|
+
let(:xml_doc) { REXML::Document.new(xml_text) }
|
11
|
+
let(:spsso_descriptor) { REXML::XPath.first(xml_doc, "//md:SPSSODescriptor") }
|
12
|
+
let(:acs) { REXML::XPath.first(xml_doc, "//md:AssertionConsumerService") }
|
13
|
+
|
14
|
+
before do
|
15
|
+
settings.issuer = "https://example.com"
|
16
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
17
|
+
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
12
18
|
end
|
13
19
|
|
14
|
-
it "generates Service Provider Metadata
|
15
|
-
|
16
|
-
|
20
|
+
it "generates Pretty Print Service Provider Metadata" do
|
21
|
+
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings, true)
|
22
|
+
# assert correct xml declaration
|
23
|
+
start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor"
|
24
|
+
assert_equal xml_text[0..start.length-1],start
|
17
25
|
|
18
|
-
|
26
|
+
assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
|
19
27
|
|
20
|
-
|
21
|
-
|
28
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
|
29
|
+
assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
30
|
+
assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
|
22
31
|
|
23
|
-
|
24
|
-
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
32
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
|
25
33
|
|
26
|
-
|
27
|
-
|
28
|
-
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
29
|
-
})
|
30
|
-
cert_text = cert_node.text
|
31
|
-
cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_text))
|
32
|
-
assert_equal ruby_saml_cert.to_der, cert.to_der
|
34
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
|
35
|
+
assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
|
33
36
|
end
|
34
37
|
|
35
38
|
it "generates Service Provider Metadata" do
|
36
|
-
settings = OneLogin::RubySaml::Settings.new
|
37
|
-
settings.issuer = "https://example.com"
|
38
|
-
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
39
|
-
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
40
|
-
settings.security[:authn_requests_signed] = false
|
41
|
-
|
42
|
-
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
|
43
|
-
|
44
39
|
# assert correct xml declaration
|
45
|
-
start = "<?xml version='1.0' encoding='UTF-8'
|
46
|
-
|
47
|
-
|
48
|
-
# assert xml_text can be parsed into an xml doc
|
49
|
-
xml_doc = REXML::Document.new(xml_text)
|
40
|
+
start = "<?xml version='1.0' encoding='UTF-8'?><md:EntityDescriptor"
|
41
|
+
assert_equal xml_text[0..start.length-1], start
|
50
42
|
|
51
43
|
assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
|
52
44
|
|
53
|
-
spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
|
54
45
|
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
|
55
46
|
assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
56
47
|
assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
|
57
48
|
|
58
49
|
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
|
59
50
|
|
60
|
-
acs = REXML::XPath.first(xml_doc, "//md:AssertionConsumerService")
|
61
51
|
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
|
62
52
|
assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
|
63
53
|
end
|
64
54
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
55
|
+
describe "when auth requests are signed" do
|
56
|
+
let(:cert_node) do
|
57
|
+
REXML::XPath.first(
|
58
|
+
xml_doc,
|
59
|
+
"//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
60
|
+
"md" => "urn:oasis:names:tc:SAML:2.0:metadata",
|
61
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_node.text)) }
|
65
|
+
|
66
|
+
before do
|
67
|
+
settings.security[:authn_requests_signed] = true
|
68
|
+
settings.certificate = ruby_saml_cert_text
|
73
69
|
end
|
74
70
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
71
|
+
it "generates Service Provider Metadata with X509Certificate" do
|
72
|
+
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
73
|
+
assert_equal ruby_saml_cert.to_der, cert.to_der
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "when attribute service is configured" do
|
78
|
+
let(:attr_svc) { REXML::XPath.first(xml_doc, "//md:AttributeConsumingService") }
|
79
|
+
let(:req_attr) { REXML::XPath.first(xml_doc, "//md:RequestedAttribute") }
|
80
|
+
|
81
|
+
before do
|
82
|
+
settings.attribute_consuming_service.configure do
|
83
|
+
service_name "Test Service"
|
84
|
+
add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "generates attribute service" do
|
89
|
+
assert_equal "true", attr_svc.attribute("isDefault").value
|
90
|
+
assert_equal "1", attr_svc.attribute("index").value
|
91
|
+
assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
|
92
|
+
|
93
|
+
assert_equal "Name", req_attr.attribute("Name").value
|
94
|
+
assert_equal "Name Format", req_attr.attribute("NameFormat").value
|
95
|
+
assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
|
96
|
+
assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "when the settings indicate to sign (embedded) metadata" do
|
101
|
+
before do
|
102
|
+
settings.security[:metadata_signed] = true
|
103
|
+
settings.certificate = ruby_saml_cert_text
|
104
|
+
settings.private_key = ruby_saml_key_text
|
105
|
+
end
|
106
|
+
|
107
|
+
it "creates a signed metadata" do
|
108
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
|
109
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], xml_text
|
110
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], xml_text
|
111
|
+
signed_metadata = XMLSecurity::SignedDocument.new(xml_text)
|
112
|
+
assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false)
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "when digest and signature methods are specified" do
|
116
|
+
before do
|
117
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
118
|
+
settings.security[:digest_method] = XMLSecurity::Document::SHA512
|
119
|
+
end
|
120
|
+
|
121
|
+
it "creates a signed metadata with specified digest and signature methods" do
|
122
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
|
123
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], xml_text
|
124
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#sha512'/>], xml_text
|
125
|
+
|
126
|
+
signed_metadata_2 = XMLSecurity::SignedDocument.new(xml_text)
|
127
|
+
|
128
|
+
assert signed_metadata_2.validate_document(ruby_saml_cert_fingerprint, false)
|
129
|
+
end
|
130
|
+
end
|
86
131
|
end
|
87
132
|
end
|
88
133
|
end
|