spid-es 0.0.7 → 0.0.8
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.
- checksums.yaml +8 -8
- data/README.md +76 -73
- data/lib/spid/ruby-saml/authrequest.rb +33 -26
- data/lib/spid/ruby-saml/logout_request.rb +2 -2
- data/lib/spid/ruby-saml/logout_response.rb +2 -2
- data/lib/spid/ruby-saml/metadata.rb +12 -11
- data/lib/spid/ruby-saml/response.rb +46 -6
- data/lib/spid/ruby-saml/settings.rb +2 -2
- data/lib/spid/ruby-saml/utils.rb +1 -1
- data/lib/spid/xml_security.rb +166 -0
- data/lib/spid/xml_security_new.rb +373 -0
- data/lib/spid-es.rb +1 -1
- data/spid-es.gemspec +1 -1
- data/test/response_test.rb +2 -2
- data/test/xml_security_test.rb +10 -10
- metadata +4 -4
- data/lib/xml_security.rb +0 -165
- data/lib/xml_security_new.rb +0 -374
@@ -0,0 +1,166 @@
|
|
1
|
+
# The contents of this file are subject to the terms
|
2
|
+
# of the Common Development and Distribution License
|
3
|
+
# (the License). You may not use this file except in
|
4
|
+
# compliance with the License.
|
5
|
+
#
|
6
|
+
# You can obtain a copy of the License at
|
7
|
+
# https://opensso.dev.java.net/public/CDDLv1.0.html or
|
8
|
+
# opensso/legal/CDDLv1.0.txt
|
9
|
+
# See the License for the specific language governing
|
10
|
+
# permission and limitations under the License.
|
11
|
+
#
|
12
|
+
# When distributing Covered Code, include this CDDL
|
13
|
+
# Header Notice in each file and include the License file
|
14
|
+
# at opensso/legal/CDDLv1.0.txt.
|
15
|
+
# If applicable, add the following below the CDDL Header,
|
16
|
+
# with the fields enclosed by brackets [] replaced by
|
17
|
+
# your own identifying information:
|
18
|
+
# "Portions Copyrighted [year] [name of copyright owner]"
|
19
|
+
#
|
20
|
+
# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
|
21
|
+
#
|
22
|
+
# Copyright 2007 Sun Microsystems Inc. All Rights Reserved
|
23
|
+
# Portions Copyrighted 2007 Todd W Saxton.
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require "rexml/document"
|
27
|
+
require "rexml/xpath"
|
28
|
+
require "openssl"
|
29
|
+
require 'nokogiri'
|
30
|
+
require "digest/sha1"
|
31
|
+
require "digest/sha2"
|
32
|
+
require "spid/ruby-saml/validation_error"
|
33
|
+
|
34
|
+
module Spid
|
35
|
+
module XMLSecurity
|
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#"
|
40
|
+
|
41
|
+
attr_accessor :signed_element_id, :sig_element, :noko_sig_element
|
42
|
+
|
43
|
+
def initialize(response)
|
44
|
+
super(response)
|
45
|
+
extract_signed_element_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate(idp_cert_fingerprint, soft = true)
|
49
|
+
# get cert from response
|
50
|
+
cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
|
51
|
+
base64_cert = cert_element.text
|
52
|
+
cert_text = Base64.decode64(base64_cert)
|
53
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
54
|
+
|
55
|
+
# check cert matches registered idp cert
|
56
|
+
fingerprint = Digest::SHA2.hexdigest(cert.to_der)
|
57
|
+
|
58
|
+
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
59
|
+
return soft ? false : (raise Spid::Saml::ValidationError.new("Fingerprint mismatch"))
|
60
|
+
end
|
61
|
+
|
62
|
+
validate_doc(base64_cert, soft)
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate_doc(base64_cert, soft = true)
|
66
|
+
# validate references
|
67
|
+
# check for inclusive namespaces
|
68
|
+
inclusive_namespaces = extract_inclusive_namespaces
|
69
|
+
|
70
|
+
document = Nokogiri.parse(self.to_s)
|
71
|
+
|
72
|
+
# store and remove signature node
|
73
|
+
self.sig_element ||= begin
|
74
|
+
element = REXML::XPath.first(self, "//ds:Signature", {"ds"=>DSIG})
|
75
|
+
element.remove
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# verify signature
|
80
|
+
signed_info_element = REXML::XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
|
81
|
+
self.noko_sig_element ||= document.at_xpath('//ds:Signature', 'ds' => DSIG)
|
82
|
+
noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
|
83
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(sig_element, '//ds:CanonicalizationMethod')
|
84
|
+
canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
|
85
|
+
noko_sig_element.remove
|
86
|
+
|
87
|
+
# check digests
|
88
|
+
REXML::XPath.each(sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
|
89
|
+
uri = ref.attributes.get_attribute("URI").value
|
90
|
+
|
91
|
+
hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
|
92
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod')
|
93
|
+
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces).gsub('&','&')
|
94
|
+
|
95
|
+
digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod"))
|
96
|
+
|
97
|
+
hash = digest_algorithm.digest(canon_hashed_element)
|
98
|
+
digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
|
99
|
+
|
100
|
+
unless digests_match?(hash, digest_value)
|
101
|
+
return soft ? false : (raise Spid::Saml::ValidationError.new("Digest mismatch"))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
base64_signature = REXML::XPath.first(sig_element, "//ds:SignatureValue", {"ds"=>DSIG}).text
|
106
|
+
signature = Base64.decode64(base64_signature)
|
107
|
+
|
108
|
+
# get certificate object
|
109
|
+
cert_text = Base64.decode64(base64_cert)
|
110
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
111
|
+
|
112
|
+
# signature method
|
113
|
+
signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))
|
114
|
+
|
115
|
+
unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
|
116
|
+
return soft ? false : (raise Spid::Saml::ValidationError.new("Key validation error"))
|
117
|
+
end
|
118
|
+
|
119
|
+
return true
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def digests_match?(hash, digest_value)
|
125
|
+
hash == digest_value
|
126
|
+
end
|
127
|
+
|
128
|
+
def extract_signed_element_id
|
129
|
+
reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
|
130
|
+
self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
|
131
|
+
end
|
132
|
+
|
133
|
+
def canon_algorithm(element)
|
134
|
+
algorithm = element.attribute('Algorithm').value if element
|
135
|
+
case algorithm
|
136
|
+
when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
137
|
+
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
|
138
|
+
when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1
|
139
|
+
else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def algorithm(element)
|
144
|
+
algorithm = element.attribute("Algorithm").value if element
|
145
|
+
algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
|
146
|
+
case algorithm
|
147
|
+
when 256 then OpenSSL::Digest::SHA256
|
148
|
+
when 384 then OpenSSL::Digest::SHA384
|
149
|
+
when 512 then OpenSSL::Digest::SHA512
|
150
|
+
else
|
151
|
+
OpenSSL::Digest::SHA1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def extract_inclusive_namespaces
|
156
|
+
if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N })
|
157
|
+
prefix_list = element.attributes.get_attribute("PrefixList").value
|
158
|
+
prefix_list.split(" ")
|
159
|
+
else
|
160
|
+
[]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,373 @@
|
|
1
|
+
# The contents of this file are subject to the terms
|
2
|
+
# of the Common Development and Distribution License
|
3
|
+
# (the License). You may not use this file except in
|
4
|
+
# compliance with the License.
|
5
|
+
#
|
6
|
+
# You can obtain a copy of the License at
|
7
|
+
# https://opensso.dev.java.net/public/CDDLv1.0.html or
|
8
|
+
# opensso/legal/CDDLv1.0.txt
|
9
|
+
# See the License for the specific language governing
|
10
|
+
# permission and limitations under the License.
|
11
|
+
#
|
12
|
+
# When distributing Covered Code, include this CDDL
|
13
|
+
# Header Notice in each file and include the License file
|
14
|
+
# at opensso/legal/CDDLv1.0.txt.
|
15
|
+
# If applicable, add the following below the CDDL Header,
|
16
|
+
# with the fields enclosed by brackets [] replaced by
|
17
|
+
# your own identifying information:
|
18
|
+
# "Portions Copyrighted [year] [name of copyright owner]"
|
19
|
+
#
|
20
|
+
# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
|
21
|
+
#
|
22
|
+
# Copyright 2007 Sun Microsystems Inc. All Rights Reserved
|
23
|
+
# Portions Copyrighted 2007 Todd W Saxton.
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require "rexml/document"
|
27
|
+
require "rexml/xpath"
|
28
|
+
require "openssl"
|
29
|
+
require 'nokogiri'
|
30
|
+
require "digest/sha1"
|
31
|
+
require "digest/sha2"
|
32
|
+
require "spid/ruby-saml/error_handling"
|
33
|
+
|
34
|
+
module Spid
|
35
|
+
module XMLSecurityNew
|
36
|
+
|
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
|
+
Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
62
|
+
end
|
63
|
+
|
64
|
+
def algorithm(element)
|
65
|
+
algorithm = element
|
66
|
+
if algorithm.is_a?(REXML::Element)
|
67
|
+
algorithm = element.attribute("Algorithm").value
|
68
|
+
end
|
69
|
+
|
70
|
+
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
71
|
+
|
72
|
+
case algorithm
|
73
|
+
when 256 then OpenSSL::Digest::SHA256
|
74
|
+
when 384 then OpenSSL::Digest::SHA384
|
75
|
+
when 512 then OpenSSL::Digest::SHA512
|
76
|
+
else
|
77
|
+
OpenSSL::Digest::SHA256
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
class Document < BaseDocument
|
84
|
+
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
85
|
+
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
86
|
+
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
87
|
+
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
88
|
+
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
89
|
+
SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
|
90
|
+
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
|
91
|
+
SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
|
92
|
+
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
93
|
+
INC_PREFIX_LIST = "#default samlp saml2p saml ds xs xsi md"
|
94
|
+
|
95
|
+
attr_accessor :uuid
|
96
|
+
|
97
|
+
def uuid
|
98
|
+
@uuid ||= begin
|
99
|
+
document.root.nil? ? nil : document.root.attributes['ID']
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
#<Signature>
|
104
|
+
#<SignedInfo>
|
105
|
+
#<CanonicalizationMethod />
|
106
|
+
#<SignatureMethod />
|
107
|
+
#<Reference>
|
108
|
+
#<Transforms>
|
109
|
+
#<DigestMethod>
|
110
|
+
#<DigestValue>
|
111
|
+
#</Reference>
|
112
|
+
#<Reference /> etc.
|
113
|
+
#</SignedInfo>
|
114
|
+
#<SignatureValue />
|
115
|
+
#<KeyInfo />
|
116
|
+
#<Object />
|
117
|
+
#</Signature>
|
118
|
+
def sign_document(private_key, certificate, signature_method = RSA_SHA256, digest_method = SHA256)
|
119
|
+
noko = Nokogiri::XML(self.to_s) do |config|
|
120
|
+
config.options = Spid::XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
|
121
|
+
end
|
122
|
+
|
123
|
+
signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
|
124
|
+
signed_info_element = signature_element.add_element("ds:SignedInfo")
|
125
|
+
signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
|
126
|
+
signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
|
127
|
+
|
128
|
+
# Add Reference
|
129
|
+
reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
|
130
|
+
|
131
|
+
# Add Transforms
|
132
|
+
transforms_element = reference_element.add_element("ds:Transforms")
|
133
|
+
transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
|
134
|
+
c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
|
135
|
+
c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
|
136
|
+
|
137
|
+
digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
|
138
|
+
inclusive_namespaces = INC_PREFIX_LIST.split(" ")
|
139
|
+
canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
|
140
|
+
#canon_doc = noko.canonicalize(canon_algorithm(C14N))
|
141
|
+
reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
|
142
|
+
|
143
|
+
# add SignatureValue
|
144
|
+
noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
|
145
|
+
config.options = Spid::XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
|
146
|
+
end
|
147
|
+
|
148
|
+
noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
|
149
|
+
canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
|
150
|
+
|
151
|
+
signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
|
152
|
+
signature_element.add_element("ds:SignatureValue").text = signature.to_s.gsub(/\n/, "").gsub(/\t/, "")
|
153
|
+
|
154
|
+
# add KeyInfo
|
155
|
+
key_info_element = signature_element.add_element("ds:KeyInfo")
|
156
|
+
x509_element = key_info_element.add_element("ds:X509Data")
|
157
|
+
x509_cert_element = x509_element.add_element("ds:X509Certificate")
|
158
|
+
if certificate.is_a?(String)
|
159
|
+
certificate = OpenSSL::X509::Certificate.new(certificate)
|
160
|
+
end
|
161
|
+
x509_cert_element.text = Base64.encode64(certificate.to_der).to_s.gsub(/\n/, "").gsub(/\t/, "")
|
162
|
+
|
163
|
+
# add the signature
|
164
|
+
sp_sso_descriptor = self.root.elements["md:SPSSODescriptor"]
|
165
|
+
unless sp_sso_descriptor.blank?
|
166
|
+
#inserisco firma nei metadata
|
167
|
+
self.root.insert_before sp_sso_descriptor, signature_element
|
168
|
+
else
|
169
|
+
#inserisco firma nella request
|
170
|
+
saml_issuer = self.root.elements["saml:Issuer"]
|
171
|
+
self.root.insert_after saml_issuer, signature_element
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
def compute_signature(private_key, signature_algorithm, document)
|
180
|
+
Base64.encode64(private_key.sign(signature_algorithm, document)).to_s.gsub(/\n/, "").gsub(/\t/, "")
|
181
|
+
end
|
182
|
+
|
183
|
+
def compute_digest(document, digest_algorithm)
|
184
|
+
digest = digest_algorithm.digest(document)
|
185
|
+
Base64.encode64(digest).strip!
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
class SignedDocument < BaseDocument
|
191
|
+
include Spid::Saml::ErrorHandling
|
192
|
+
|
193
|
+
attr_accessor :signed_element_id
|
194
|
+
|
195
|
+
def initialize(response, errors = [])
|
196
|
+
super(response)
|
197
|
+
@errors = errors
|
198
|
+
end
|
199
|
+
|
200
|
+
def signed_element_id
|
201
|
+
@signed_element_id ||= extract_signed_element_id
|
202
|
+
end
|
203
|
+
|
204
|
+
def validate_document(idp_cert_fingerprint, soft = true, options = {})
|
205
|
+
# get cert from response
|
206
|
+
cert_element = REXML::XPath.first(
|
207
|
+
self,
|
208
|
+
"//ds:X509Certificate",
|
209
|
+
{ "ds"=>DSIG }
|
210
|
+
)
|
211
|
+
|
212
|
+
if cert_element
|
213
|
+
base64_cert = cert_element.text
|
214
|
+
cert_text = Base64.decode64(base64_cert)
|
215
|
+
begin
|
216
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
217
|
+
rescue OpenSSL::X509::CertificateError => e
|
218
|
+
return append_error("Certificate Error", soft)
|
219
|
+
end
|
220
|
+
|
221
|
+
if options[:fingerprint_alg]
|
222
|
+
fingerprint_alg = Spid::XMLSecurityNew::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
|
223
|
+
else
|
224
|
+
fingerprint_alg = OpenSSL::Digest::SHA256.new
|
225
|
+
end
|
226
|
+
fingerprint = fingerprint_alg.hexdigest(cert.to_der)
|
227
|
+
|
228
|
+
# check cert matches registered idp cert
|
229
|
+
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
230
|
+
@errors << "Fingerprint mismatch"
|
231
|
+
return append_error("Fingerprint mismatch", soft)
|
232
|
+
end
|
233
|
+
else
|
234
|
+
if options[:cert]
|
235
|
+
base64_cert = Base64.encode64(options[:cert].to_pem)
|
236
|
+
else
|
237
|
+
if soft
|
238
|
+
return false
|
239
|
+
else
|
240
|
+
return append_error("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", soft)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
validate_signature(base64_cert, soft)
|
245
|
+
end
|
246
|
+
|
247
|
+
def validate_signature(base64_cert, soft = true)
|
248
|
+
|
249
|
+
document = Nokogiri::XML(self.to_s) do |config|
|
250
|
+
config.options = Spid::XMLSecurityNew::BaseDocument::NOKOGIRI_OPTIONS
|
251
|
+
end
|
252
|
+
|
253
|
+
# create a rexml document
|
254
|
+
@working_copy ||= REXML::Document.new(self.to_s).root
|
255
|
+
|
256
|
+
# get signature node
|
257
|
+
sig_element = REXML::XPath.first(
|
258
|
+
@working_copy,
|
259
|
+
"//ds:Signature",
|
260
|
+
{"ds"=>DSIG}
|
261
|
+
)
|
262
|
+
|
263
|
+
# signature method
|
264
|
+
sig_alg_value = REXML::XPath.first(
|
265
|
+
sig_element,
|
266
|
+
"./ds:SignedInfo/ds:SignatureMethod",
|
267
|
+
{"ds"=>DSIG}
|
268
|
+
)
|
269
|
+
signature_algorithm = algorithm(sig_alg_value)
|
270
|
+
|
271
|
+
# get signature
|
272
|
+
base64_signature = REXML::XPath.first(
|
273
|
+
sig_element,
|
274
|
+
"./ds:SignatureValue",
|
275
|
+
{"ds" => DSIG}
|
276
|
+
).text
|
277
|
+
signature = Base64.decode64(base64_signature)
|
278
|
+
|
279
|
+
# canonicalization method
|
280
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(
|
281
|
+
sig_element,
|
282
|
+
'./ds:SignedInfo/ds:CanonicalizationMethod',
|
283
|
+
'ds' => DSIG
|
284
|
+
)
|
285
|
+
|
286
|
+
noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
|
287
|
+
noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
|
288
|
+
|
289
|
+
canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
|
290
|
+
noko_sig_element.remove
|
291
|
+
|
292
|
+
# get inclusive namespaces
|
293
|
+
inclusive_namespaces = extract_inclusive_namespaces
|
294
|
+
|
295
|
+
# check digests
|
296
|
+
ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
|
297
|
+
uri = ref.attributes.get_attribute("URI").value
|
298
|
+
|
299
|
+
hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
|
300
|
+
|
301
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(
|
302
|
+
ref,
|
303
|
+
'//ds:CanonicalizationMethod',
|
304
|
+
{ "ds" => DSIG }
|
305
|
+
)
|
306
|
+
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
|
307
|
+
|
308
|
+
digest_algorithm = algorithm(REXML::XPath.first(
|
309
|
+
ref,
|
310
|
+
"//ds:DigestMethod",
|
311
|
+
{ "ds" => DSIG }
|
312
|
+
))
|
313
|
+
hash = digest_algorithm.digest(canon_hashed_element)
|
314
|
+
encoded_digest_value = REXML::XPath.first(
|
315
|
+
ref,
|
316
|
+
"//ds:DigestValue",
|
317
|
+
{ "ds" => DSIG }
|
318
|
+
).text
|
319
|
+
digest_value = Base64.decode64(encoded_digest_value)
|
320
|
+
|
321
|
+
unless digests_match?(hash, digest_value)
|
322
|
+
@errors << "Digest mismatch"
|
323
|
+
return append_error("Digest mismatch", soft)
|
324
|
+
end
|
325
|
+
|
326
|
+
# get certificate object
|
327
|
+
cert_text = Base64.decode64(base64_cert)
|
328
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
329
|
+
|
330
|
+
# verify signature
|
331
|
+
unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
|
332
|
+
return append_error("Key validation error", soft)
|
333
|
+
end
|
334
|
+
|
335
|
+
return true
|
336
|
+
end
|
337
|
+
|
338
|
+
private
|
339
|
+
|
340
|
+
def digests_match?(hash, digest_value)
|
341
|
+
hash == digest_value
|
342
|
+
end
|
343
|
+
|
344
|
+
def extract_signed_element_id
|
345
|
+
reference_element = REXML::XPath.first(
|
346
|
+
self,
|
347
|
+
"//ds:Signature/ds:SignedInfo/ds:Reference",
|
348
|
+
{"ds"=>DSIG}
|
349
|
+
)
|
350
|
+
|
351
|
+
return nil if reference_element.nil?
|
352
|
+
|
353
|
+
sei = reference_element.attribute("URI").value[1..-1]
|
354
|
+
sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei
|
355
|
+
end
|
356
|
+
|
357
|
+
def extract_inclusive_namespaces
|
358
|
+
element = REXML::XPath.first(
|
359
|
+
self,
|
360
|
+
"//ec:InclusiveNamespaces",
|
361
|
+
{ "ec" => C14N }
|
362
|
+
)
|
363
|
+
if element
|
364
|
+
prefix_list = element.attributes.get_attribute("PrefixList").value
|
365
|
+
prefix_list.split(" ")
|
366
|
+
else
|
367
|
+
nil
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
data/lib/spid-es.rb
CHANGED
data/spid-es.gemspec
CHANGED
@@ -2,7 +2,7 @@ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'spid-es'
|
5
|
-
s.version = '0.0.
|
5
|
+
s.version = '0.0.8'
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Fabiano Pavan"]
|
data/test/response_test.rb
CHANGED
@@ -8,7 +8,7 @@ class RubySamlTest < Test::Unit::TestCase
|
|
8
8
|
end
|
9
9
|
|
10
10
|
should "be able to parse a document which contains ampersands" do
|
11
|
-
XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
|
11
|
+
Spid::XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
|
12
12
|
Spid::Saml::Response.any_instance.stubs(:validate_conditions).returns(true)
|
13
13
|
|
14
14
|
response = Spid::Saml::Response.new(ampersands_response)
|
@@ -101,7 +101,7 @@ class RubySamlTest < Test::Unit::TestCase
|
|
101
101
|
settings = Spid::Saml::Settings.new
|
102
102
|
response.settings = settings
|
103
103
|
settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
|
104
|
-
XMLSecurity::SignedDocument.any_instance.expects(:validate_doc).returns(true)
|
104
|
+
Spid::XMLSecurity::SignedDocument.any_instance.expects(:validate_doc).returns(true)
|
105
105
|
assert response.validate!
|
106
106
|
end
|
107
107
|
|
data/test/xml_security_test.rb
CHANGED
@@ -6,7 +6,7 @@ class XmlSecurityTest < Test::Unit::TestCase
|
|
6
6
|
|
7
7
|
context "XmlSecurity" do
|
8
8
|
setup do
|
9
|
-
@document = XMLSecurity::SignedDocument.new(Base64.decode64(response_document))
|
9
|
+
@document = Spid::XMLSecurity::SignedDocument.new(Base64.decode64(response_document))
|
10
10
|
@base64cert = @document.elements["//ds:X509Certificate"].text
|
11
11
|
end
|
12
12
|
|
@@ -44,7 +44,7 @@ class XmlSecurityTest < Test::Unit::TestCase
|
|
44
44
|
response = Base64.decode64(response_document)
|
45
45
|
response.sub!("<ds:DigestValue>pJQ7MS/ek4KRRWGmv/H43ReHYMs=</ds:DigestValue>",
|
46
46
|
"<ds:DigestValue>b9xsAXLsynugg3Wc1CI3kpWku+0=</ds:DigestValue>")
|
47
|
-
document = XMLSecurity::SignedDocument.new(response)
|
47
|
+
document = Spid::XMLSecurity::SignedDocument.new(response)
|
48
48
|
base64cert = document.elements["//ds:X509Certificate"].text
|
49
49
|
exception = assert_raise(Spid::Saml::ValidationError) do
|
50
50
|
document.validate_doc(base64cert, false)
|
@@ -55,32 +55,32 @@ class XmlSecurityTest < Test::Unit::TestCase
|
|
55
55
|
|
56
56
|
context "Algorithms" do
|
57
57
|
should "validate using SHA1" do
|
58
|
-
@document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false))
|
58
|
+
@document = Spid::XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false))
|
59
59
|
assert @document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
|
60
60
|
end
|
61
61
|
|
62
62
|
should "validate using SHA256" do
|
63
|
-
@document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false))
|
63
|
+
@document = Spid::XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false))
|
64
64
|
assert @document.validate("28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA")
|
65
65
|
end
|
66
66
|
|
67
67
|
should "validate using SHA384" do
|
68
|
-
@document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false))
|
68
|
+
@document = Spid::XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false))
|
69
69
|
assert @document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
|
70
70
|
end
|
71
71
|
|
72
72
|
should "validate using SHA512" do
|
73
|
-
@document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false))
|
73
|
+
@document = Spid::XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false))
|
74
74
|
assert @document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
context "XmlSecurity::SignedDocument" do
|
78
|
+
context "Spid::XmlSecurity::SignedDocument" do
|
79
79
|
|
80
80
|
context "#extract_inclusive_namespaces" do
|
81
81
|
should "support explicit namespace resolution for exclusive canonicalization" do
|
82
82
|
response = fixture(:open_saml_response, false)
|
83
|
-
document = XMLSecurity::SignedDocument.new(response)
|
83
|
+
document = Spid::XMLSecurity::SignedDocument.new(response)
|
84
84
|
inclusive_namespaces = document.send(:extract_inclusive_namespaces)
|
85
85
|
|
86
86
|
assert_equal %w[ xs ], inclusive_namespaces
|
@@ -88,7 +88,7 @@ class XmlSecurityTest < Test::Unit::TestCase
|
|
88
88
|
|
89
89
|
should "support implicit namespace resolution for exclusive canonicalization" do
|
90
90
|
response = fixture(:no_signature_ns, false)
|
91
|
-
document = XMLSecurity::SignedDocument.new(response)
|
91
|
+
document = Spid::XMLSecurity::SignedDocument.new(response)
|
92
92
|
inclusive_namespaces = document.send(:extract_inclusive_namespaces)
|
93
93
|
|
94
94
|
assert_equal %w[ #default saml ds xs xsi ], inclusive_namespaces
|
@@ -111,7 +111,7 @@ class XmlSecurityTest < Test::Unit::TestCase
|
|
111
111
|
response = fixture(:no_signature_ns, false)
|
112
112
|
response.slice! %r{<InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="#default saml ds xs xsi"/>}
|
113
113
|
|
114
|
-
document = XMLSecurity::SignedDocument.new(response)
|
114
|
+
document = Spid::XMLSecurity::SignedDocument.new(response)
|
115
115
|
inclusive_namespaces = document.send(:extract_inclusive_namespaces)
|
116
116
|
|
117
117
|
assert inclusive_namespaces.empty?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spid-es
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fabiano Pavan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: canonix
|
@@ -102,8 +102,8 @@ files:
|
|
102
102
|
- lib/spid/ruby-saml/utils.rb
|
103
103
|
- lib/spid/ruby-saml/validation_error.rb
|
104
104
|
- lib/spid/ruby-saml/version.rb
|
105
|
-
- lib/xml_security.rb
|
106
|
-
- lib/xml_security_new.rb
|
105
|
+
- lib/spid/xml_security.rb
|
106
|
+
- lib/spid/xml_security_new.rb
|
107
107
|
- spid-es.gemspec
|
108
108
|
- test/certificates/certificate1
|
109
109
|
- test/logoutrequest_test.rb
|