samlsso 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/CODE_OF_CONDUCT.md +49 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +21 -0
  6. data/README.md +36 -0
  7. data/Rakefile +2 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/lib/samlsso.rb +16 -0
  11. data/lib/samlsso/attribute_service.rb +32 -0
  12. data/lib/samlsso/attributes.rb +107 -0
  13. data/lib/samlsso/authrequest.rb +124 -0
  14. data/lib/samlsso/idp_metadata_parser.rb +85 -0
  15. data/lib/samlsso/logging.rb +20 -0
  16. data/lib/samlsso/logoutrequest.rb +100 -0
  17. data/lib/samlsso/logoutresponse.rb +110 -0
  18. data/lib/samlsso/metadata.rb +94 -0
  19. data/lib/samlsso/response.rb +271 -0
  20. data/lib/samlsso/saml_message.rb +117 -0
  21. data/lib/samlsso/settings.rb +115 -0
  22. data/lib/samlsso/slo_logoutrequest.rb +64 -0
  23. data/lib/samlsso/slo_logoutresponse.rb +99 -0
  24. data/lib/samlsso/utils.rb +42 -0
  25. data/lib/samlsso/validation_error.rb +5 -0
  26. data/lib/samlsso/version.rb +3 -0
  27. data/lib/schemas/saml-schema-assertion-2.0.xsd +283 -0
  28. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  29. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  30. data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
  31. data/lib/schemas/saml-schema-protocol-2.0.xsd +302 -0
  32. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  33. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  34. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  35. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  36. data/lib/schemas/xenc-schema.xsd +136 -0
  37. data/lib/schemas/xml.xsd +287 -0
  38. data/lib/schemas/xmldsig-core-schema.xsd +309 -0
  39. data/lib/xml_security.rb +276 -0
  40. data/samlsso.gemspec +44 -0
  41. metadata +168 -0
@@ -0,0 +1,309 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+
3
+ <!-- Schema for XML Signatures
4
+ http://www.w3.org/2000/09/xmldsig#
5
+ $Revision: 1.1 $ on $Date: 2002/02/08 20:32:26 $ by $Author: reagle $
6
+
7
+ Copyright 2001 The Internet Society and W3C (Massachusetts Institute
8
+ of Technology, Institut National de Recherche en Informatique et en
9
+ Automatique, Keio University). All Rights Reserved.
10
+ http://www.w3.org/Consortium/Legal/
11
+
12
+ This document is governed by the W3C Software License [1] as described
13
+ in the FAQ [2].
14
+
15
+ [1] http://www.w3.org/Consortium/Legal/copyright-software-19980720
16
+ [2] http://www.w3.org/Consortium/Legal/IPR-FAQ-20000620.html#DTD
17
+ -->
18
+
19
+
20
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
21
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
22
+ targetNamespace="http://www.w3.org/2000/09/xmldsig#"
23
+ version="0.1" elementFormDefault="qualified">
24
+
25
+ <!-- Basic Types Defined for Signatures -->
26
+
27
+ <simpleType name="CryptoBinary">
28
+ <restriction base="base64Binary">
29
+ </restriction>
30
+ </simpleType>
31
+
32
+ <!-- Start Signature -->
33
+
34
+ <element name="Signature" type="ds:SignatureType"/>
35
+ <complexType name="SignatureType">
36
+ <sequence>
37
+ <element ref="ds:SignedInfo"/>
38
+ <element ref="ds:SignatureValue"/>
39
+ <element ref="ds:KeyInfo" minOccurs="0"/>
40
+ <element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/>
41
+ </sequence>
42
+ <attribute name="Id" type="ID" use="optional"/>
43
+ </complexType>
44
+
45
+ <element name="SignatureValue" type="ds:SignatureValueType"/>
46
+ <complexType name="SignatureValueType">
47
+ <simpleContent>
48
+ <extension base="base64Binary">
49
+ <attribute name="Id" type="ID" use="optional"/>
50
+ </extension>
51
+ </simpleContent>
52
+ </complexType>
53
+
54
+ <!-- Start SignedInfo -->
55
+
56
+ <element name="SignedInfo" type="ds:SignedInfoType"/>
57
+ <complexType name="SignedInfoType">
58
+ <sequence>
59
+ <element ref="ds:CanonicalizationMethod"/>
60
+ <element ref="ds:SignatureMethod"/>
61
+ <element ref="ds:Reference" maxOccurs="unbounded"/>
62
+ </sequence>
63
+ <attribute name="Id" type="ID" use="optional"/>
64
+ </complexType>
65
+
66
+ <element name="CanonicalizationMethod" type="ds:CanonicalizationMethodType"/>
67
+ <complexType name="CanonicalizationMethodType" mixed="true">
68
+ <sequence>
69
+ <any namespace="##any" minOccurs="0" maxOccurs="unbounded"/>
70
+ <!-- (0,unbounded) elements from (1,1) namespace -->
71
+ </sequence>
72
+ <attribute name="Algorithm" type="anyURI" use="required"/>
73
+ </complexType>
74
+
75
+ <element name="SignatureMethod" type="ds:SignatureMethodType"/>
76
+ <complexType name="SignatureMethodType" mixed="true">
77
+ <sequence>
78
+ <element name="HMACOutputLength" minOccurs="0" type="ds:HMACOutputLengthType"/>
79
+ <any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
80
+ <!-- (0,unbounded) elements from (1,1) external namespace -->
81
+ </sequence>
82
+ <attribute name="Algorithm" type="anyURI" use="required"/>
83
+ </complexType>
84
+
85
+ <!-- Start Reference -->
86
+
87
+ <element name="Reference" type="ds:ReferenceType"/>
88
+ <complexType name="ReferenceType">
89
+ <sequence>
90
+ <element ref="ds:Transforms" minOccurs="0"/>
91
+ <element ref="ds:DigestMethod"/>
92
+ <element ref="ds:DigestValue"/>
93
+ </sequence>
94
+ <attribute name="Id" type="ID" use="optional"/>
95
+ <attribute name="URI" type="anyURI" use="optional"/>
96
+ <attribute name="Type" type="anyURI" use="optional"/>
97
+ </complexType>
98
+
99
+ <element name="Transforms" type="ds:TransformsType"/>
100
+ <complexType name="TransformsType">
101
+ <sequence>
102
+ <element ref="ds:Transform" maxOccurs="unbounded"/>
103
+ </sequence>
104
+ </complexType>
105
+
106
+ <element name="Transform" type="ds:TransformType"/>
107
+ <complexType name="TransformType" mixed="true">
108
+ <choice minOccurs="0" maxOccurs="unbounded">
109
+ <any namespace="##other" processContents="lax"/>
110
+ <!-- (1,1) elements from (0,unbounded) namespaces -->
111
+ <element name="XPath" type="string"/>
112
+ </choice>
113
+ <attribute name="Algorithm" type="anyURI" use="required"/>
114
+ </complexType>
115
+
116
+ <!-- End Reference -->
117
+
118
+ <element name="DigestMethod" type="ds:DigestMethodType"/>
119
+ <complexType name="DigestMethodType" mixed="true">
120
+ <sequence>
121
+ <any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
122
+ </sequence>
123
+ <attribute name="Algorithm" type="anyURI" use="required"/>
124
+ </complexType>
125
+
126
+ <element name="DigestValue" type="ds:DigestValueType"/>
127
+ <simpleType name="DigestValueType">
128
+ <restriction base="base64Binary"/>
129
+ </simpleType>
130
+
131
+ <!-- End SignedInfo -->
132
+
133
+ <!-- Start KeyInfo -->
134
+
135
+ <element name="KeyInfo" type="ds:KeyInfoType"/>
136
+ <complexType name="KeyInfoType" mixed="true">
137
+ <choice maxOccurs="unbounded">
138
+ <element ref="ds:KeyName"/>
139
+ <element ref="ds:KeyValue"/>
140
+ <element ref="ds:RetrievalMethod"/>
141
+ <element ref="ds:X509Data"/>
142
+ <element ref="ds:PGPData"/>
143
+ <element ref="ds:SPKIData"/>
144
+ <element ref="ds:MgmtData"/>
145
+ <any processContents="lax" namespace="##other"/>
146
+ <!-- (1,1) elements from (0,unbounded) namespaces -->
147
+ </choice>
148
+ <attribute name="Id" type="ID" use="optional"/>
149
+ </complexType>
150
+
151
+ <element name="KeyName" type="string"/>
152
+ <element name="MgmtData" type="string"/>
153
+
154
+ <element name="KeyValue" type="ds:KeyValueType"/>
155
+ <complexType name="KeyValueType" mixed="true">
156
+ <choice>
157
+ <element ref="ds:DSAKeyValue"/>
158
+ <element ref="ds:RSAKeyValue"/>
159
+ <any namespace="##other" processContents="lax"/>
160
+ </choice>
161
+ </complexType>
162
+
163
+ <element name="RetrievalMethod" type="ds:RetrievalMethodType"/>
164
+ <complexType name="RetrievalMethodType">
165
+ <sequence>
166
+ <element ref="ds:Transforms" minOccurs="0"/>
167
+ </sequence>
168
+ <attribute name="URI" type="anyURI"/>
169
+ <attribute name="Type" type="anyURI" use="optional"/>
170
+ </complexType>
171
+
172
+ <!-- Start X509Data -->
173
+
174
+ <element name="X509Data" type="ds:X509DataType"/>
175
+ <complexType name="X509DataType">
176
+ <sequence maxOccurs="unbounded">
177
+ <choice>
178
+ <element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/>
179
+ <element name="X509SKI" type="base64Binary"/>
180
+ <element name="X509SubjectName" type="string"/>
181
+ <element name="X509Certificate" type="base64Binary"/>
182
+ <element name="X509CRL" type="base64Binary"/>
183
+ <any namespace="##other" processContents="lax"/>
184
+ </choice>
185
+ </sequence>
186
+ </complexType>
187
+
188
+ <complexType name="X509IssuerSerialType">
189
+ <sequence>
190
+ <element name="X509IssuerName" type="string"/>
191
+ <element name="X509SerialNumber" type="integer"/>
192
+ </sequence>
193
+ </complexType>
194
+
195
+ <!-- End X509Data -->
196
+
197
+ <!-- Begin PGPData -->
198
+
199
+ <element name="PGPData" type="ds:PGPDataType"/>
200
+ <complexType name="PGPDataType">
201
+ <choice>
202
+ <sequence>
203
+ <element name="PGPKeyID" type="base64Binary"/>
204
+ <element name="PGPKeyPacket" type="base64Binary" minOccurs="0"/>
205
+ <any namespace="##other" processContents="lax" minOccurs="0"
206
+ maxOccurs="unbounded"/>
207
+ </sequence>
208
+ <sequence>
209
+ <element name="PGPKeyPacket" type="base64Binary"/>
210
+ <any namespace="##other" processContents="lax" minOccurs="0"
211
+ maxOccurs="unbounded"/>
212
+ </sequence>
213
+ </choice>
214
+ </complexType>
215
+
216
+ <!-- End PGPData -->
217
+
218
+ <!-- Begin SPKIData -->
219
+
220
+ <element name="SPKIData" type="ds:SPKIDataType"/>
221
+ <complexType name="SPKIDataType">
222
+ <sequence maxOccurs="unbounded">
223
+ <element name="SPKISexp" type="base64Binary"/>
224
+ <any namespace="##other" processContents="lax" minOccurs="0"/>
225
+ </sequence>
226
+ </complexType>
227
+
228
+ <!-- End SPKIData -->
229
+
230
+ <!-- End KeyInfo -->
231
+
232
+ <!-- Start Object (Manifest, SignatureProperty) -->
233
+
234
+ <element name="Object" type="ds:ObjectType"/>
235
+ <complexType name="ObjectType" mixed="true">
236
+ <sequence minOccurs="0" maxOccurs="unbounded">
237
+ <any namespace="##any" processContents="lax"/>
238
+ </sequence>
239
+ <attribute name="Id" type="ID" use="optional"/>
240
+ <attribute name="MimeType" type="string" use="optional"/> <!-- add a grep facet -->
241
+ <attribute name="Encoding" type="anyURI" use="optional"/>
242
+ </complexType>
243
+
244
+ <element name="Manifest" type="ds:ManifestType"/>
245
+ <complexType name="ManifestType">
246
+ <sequence>
247
+ <element ref="ds:Reference" maxOccurs="unbounded"/>
248
+ </sequence>
249
+ <attribute name="Id" type="ID" use="optional"/>
250
+ </complexType>
251
+
252
+ <element name="SignatureProperties" type="ds:SignaturePropertiesType"/>
253
+ <complexType name="SignaturePropertiesType">
254
+ <sequence>
255
+ <element ref="ds:SignatureProperty" maxOccurs="unbounded"/>
256
+ </sequence>
257
+ <attribute name="Id" type="ID" use="optional"/>
258
+ </complexType>
259
+
260
+ <element name="SignatureProperty" type="ds:SignaturePropertyType"/>
261
+ <complexType name="SignaturePropertyType" mixed="true">
262
+ <choice maxOccurs="unbounded">
263
+ <any namespace="##other" processContents="lax"/>
264
+ <!-- (1,1) elements from (1,unbounded) namespaces -->
265
+ </choice>
266
+ <attribute name="Target" type="anyURI" use="required"/>
267
+ <attribute name="Id" type="ID" use="optional"/>
268
+ </complexType>
269
+
270
+ <!-- End Object (Manifest, SignatureProperty) -->
271
+
272
+ <!-- Start Algorithm Parameters -->
273
+
274
+ <simpleType name="HMACOutputLengthType">
275
+ <restriction base="integer"/>
276
+ </simpleType>
277
+
278
+ <!-- Start KeyValue Element-types -->
279
+
280
+ <element name="DSAKeyValue" type="ds:DSAKeyValueType"/>
281
+ <complexType name="DSAKeyValueType">
282
+ <sequence>
283
+ <sequence minOccurs="0">
284
+ <element name="P" type="ds:CryptoBinary"/>
285
+ <element name="Q" type="ds:CryptoBinary"/>
286
+ </sequence>
287
+ <element name="G" type="ds:CryptoBinary" minOccurs="0"/>
288
+ <element name="Y" type="ds:CryptoBinary"/>
289
+ <element name="J" type="ds:CryptoBinary" minOccurs="0"/>
290
+ <sequence minOccurs="0">
291
+ <element name="Seed" type="ds:CryptoBinary"/>
292
+ <element name="PgenCounter" type="ds:CryptoBinary"/>
293
+ </sequence>
294
+ </sequence>
295
+ </complexType>
296
+
297
+ <element name="RSAKeyValue" type="ds:RSAKeyValueType"/>
298
+ <complexType name="RSAKeyValueType">
299
+ <sequence>
300
+ <element name="Modulus" type="ds:CryptoBinary"/>
301
+ <element name="Exponent" type="ds:CryptoBinary"/>
302
+ </sequence>
303
+ </complexType>
304
+
305
+ <!-- End KeyValue Element-types -->
306
+
307
+ <!-- End Signature -->
308
+
309
+ </schema>
@@ -0,0 +1,276 @@
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 "samlsso/validation_error"
33
+
34
+ module XMLSecurity
35
+
36
+ class BaseDocument < REXML::Document
37
+
38
+ C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
39
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
40
+
41
+ def canon_algorithm(element)
42
+ algorithm = element
43
+ if algorithm.is_a?(REXML::Element)
44
+ algorithm = element.attribute('Algorithm').value
45
+ end
46
+
47
+ case algorithm
48
+ when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
49
+ when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
50
+ when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1
51
+ else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
52
+ end
53
+ end
54
+
55
+ def algorithm(element)
56
+ algorithm = element
57
+ if algorithm.is_a?(REXML::Element)
58
+ algorithm = element.attribute("Algorithm").value
59
+ algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
60
+ end
61
+
62
+ case algorithm
63
+ when 256 then OpenSSL::Digest::SHA256
64
+ when 384 then OpenSSL::Digest::SHA384
65
+ when 512 then OpenSSL::Digest::SHA512
66
+ else
67
+ OpenSSL::Digest::SHA1
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ class Document < BaseDocument
74
+ SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
75
+ SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
76
+ SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
77
+ SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
78
+ ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
79
+ INC_PREFIX_LIST = "#default samlp saml ds xs xsi"
80
+
81
+ attr_accessor :uuid
82
+
83
+ #<Signature>
84
+ #<SignedInfo>
85
+ #<CanonicalizationMethod />
86
+ #<SignatureMethod />
87
+ #<Reference>
88
+ #<Transforms>
89
+ #<DigestMethod>
90
+ #<DigestValue>
91
+ #</Reference>
92
+ #<Reference /> etc.
93
+ #</SignedInfo>
94
+ #<SignatureValue />
95
+ #<KeyInfo />
96
+ #<Object />
97
+ #</Signature>
98
+ def sign_document(private_key, certificate, signature_method = SHA1, digest_method = SHA1)
99
+ noko = Nokogiri.parse(self.to_s)
100
+ canon_doc = noko.canonicalize(canon_algorithm(C14N))
101
+
102
+ signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
103
+ signed_info_element = signature_element.add_element("ds:SignedInfo")
104
+ signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
105
+ signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
106
+
107
+ # Add Reference
108
+ reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
109
+
110
+ # Add Transforms
111
+ transforms_element = reference_element.add_element("ds:Transforms")
112
+ transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
113
+ transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
114
+ transforms_element.add_element("ds:InclusiveNamespaces", {"xmlns" => C14N, "PrefixList" => INC_PREFIX_LIST})
115
+
116
+ digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
117
+ reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
118
+
119
+ # add SignatureValue
120
+ noko_sig_element = Nokogiri.parse(signature_element.to_s)
121
+ noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
122
+ canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
123
+ signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
124
+ signature_element.add_element("ds:SignatureValue").text = signature
125
+
126
+ # add KeyInfo
127
+ key_info_element = signature_element.add_element("ds:KeyInfo")
128
+ x509_element = key_info_element.add_element("ds:X509Data")
129
+ x509_cert_element = x509_element.add_element("ds:X509Certificate")
130
+ if certificate.is_a?(String)
131
+ certificate = OpenSSL::X509::Certificate.new(certificate)
132
+ end
133
+ x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
134
+
135
+ # add the signature
136
+ issuer_element = self.elements["//saml:Issuer"]
137
+ if issuer_element
138
+ self.root.insert_after issuer_element, signature_element
139
+ else
140
+ self.root.add_element(signature_element)
141
+ end
142
+ end
143
+
144
+ protected
145
+
146
+ def compute_signature(private_key, signature_algorithm, document)
147
+ Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
148
+ end
149
+
150
+ def compute_digest(document, digest_algorithm)
151
+ digest = digest_algorithm.digest(document)
152
+ Base64.encode64(digest).strip!
153
+ end
154
+
155
+ end
156
+
157
+ class SignedDocument < BaseDocument
158
+
159
+ attr_accessor :signed_element_id
160
+ attr_accessor :errors
161
+
162
+ def initialize(response, errors = [])
163
+ super(response)
164
+ @errors = errors
165
+ extract_signed_element_id
166
+ end
167
+
168
+ def validate_document(idp_cert_fingerprint, soft = true)
169
+ # get cert from response
170
+ cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
171
+ unless cert_element
172
+ if soft
173
+ return true # temp if no x509Ceritificate then whitelist this.
174
+ else
175
+ raise Samlsso::ValidationError.new("Certificate element missing in response (ds:X509Certificate)")
176
+ end
177
+ end
178
+ base64_cert = cert_element.text
179
+ cert_text = Base64.decode64(base64_cert)
180
+ cert = OpenSSL::X509::Certificate.new(cert_text)
181
+
182
+ # check cert matches registered idp cert
183
+ fingerprint = Digest::SHA1.hexdigest(cert.to_der)
184
+
185
+ if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
186
+ @errors << "Fingerprint mismatch"
187
+ return soft ? false : (raise Samlsso::ValidationError.new("Fingerprint mismatch"))
188
+ end
189
+
190
+ validate_signature(base64_cert, soft)
191
+ end
192
+
193
+ def validate_signature(base64_cert, soft = true)
194
+ # validate references
195
+
196
+ # check for inclusive namespaces
197
+ inclusive_namespaces = extract_inclusive_namespaces
198
+
199
+ document = Nokogiri.parse(self.to_s)
200
+
201
+ # create a working copy so we don't modify the original
202
+ @working_copy ||= REXML::Document.new(self.to_s).root
203
+
204
+ # store and remove signature node
205
+ @sig_element ||= begin
206
+ element = REXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
207
+ element.remove
208
+ end
209
+
210
+ # verify signature
211
+ signed_info_element = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
212
+ noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
213
+ noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
214
+ canon_algorithm = canon_algorithm REXML::XPath.first(@sig_element, '//ds:CanonicalizationMethod', 'ds' => DSIG)
215
+ canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
216
+ noko_sig_element.remove
217
+
218
+ # check digests
219
+ REXML::XPath.each(@sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
220
+ uri = ref.attributes.get_attribute("URI").value
221
+
222
+ hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
223
+ canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG)
224
+ canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
225
+
226
+ digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod", 'ds' => DSIG))
227
+
228
+ hash = digest_algorithm.digest(canon_hashed_element)
229
+ digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
230
+
231
+ unless digests_match?(hash, digest_value)
232
+ @errors << "Digest mismatch"
233
+ return soft ? false : (raise Samlsso::ValidationError.new("Digest mismatch"))
234
+ end
235
+ end
236
+
237
+ base64_signature = REXML::XPath.first(@sig_element, "//ds:SignatureValue", {"ds"=>DSIG}).text
238
+ signature = Base64.decode64(base64_signature)
239
+
240
+ # get certificate object
241
+ cert_text = Base64.decode64(base64_cert)
242
+ cert = OpenSSL::X509::Certificate.new(cert_text)
243
+
244
+ # signature method
245
+ signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))
246
+
247
+ unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
248
+ @errors << "Key validation error"
249
+ return soft ? false : (raise Samlsso::ValidationError.new("Key validation error"))
250
+ end
251
+
252
+ return true
253
+ end
254
+
255
+ private
256
+
257
+ def digests_match?(hash, digest_value)
258
+ hash == digest_value
259
+ end
260
+
261
+ def extract_signed_element_id
262
+ reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
263
+ self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
264
+ end
265
+
266
+ def extract_inclusive_namespaces
267
+ if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N })
268
+ prefix_list = element.attributes.get_attribute("PrefixList").value
269
+ prefix_list.split(" ")
270
+ else
271
+ []
272
+ end
273
+ end
274
+
275
+ end
276
+ end