icn_saml_idp 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +238 -0
- data/app/controllers/saml_idp/idp_controller.rb +53 -0
- data/app/views/saml_idp/idp/new.html.erb +22 -0
- data/app/views/saml_idp/idp/saml_post.html.erb +14 -0
- data/lib/saml_idp.rb +92 -0
- data/lib/saml_idp/algorithmable.rb +19 -0
- data/lib/saml_idp/assertion_builder.rb +172 -0
- data/lib/saml_idp/attribute_decorator.rb +27 -0
- data/lib/saml_idp/attributeable.rb +24 -0
- data/lib/saml_idp/configurator.rb +48 -0
- data/lib/saml_idp/controller.rb +128 -0
- data/lib/saml_idp/default.rb +49 -0
- data/lib/saml_idp/encryptor.rb +86 -0
- data/lib/saml_idp/engine.rb +5 -0
- data/lib/saml_idp/hashable.rb +26 -0
- data/lib/saml_idp/incoming_metadata.rb +144 -0
- data/lib/saml_idp/logout_builder.rb +42 -0
- data/lib/saml_idp/logout_request_builder.rb +34 -0
- data/lib/saml_idp/logout_response_builder.rb +35 -0
- data/lib/saml_idp/metadata_builder.rb +160 -0
- data/lib/saml_idp/name_id_formatter.rb +68 -0
- data/lib/saml_idp/persisted_metadata.rb +10 -0
- data/lib/saml_idp/request.rb +180 -0
- data/lib/saml_idp/response_builder.rb +62 -0
- data/lib/saml_idp/saml_response.rb +79 -0
- data/lib/saml_idp/service_provider.rb +76 -0
- data/lib/saml_idp/signable.rb +131 -0
- data/lib/saml_idp/signature_builder.rb +42 -0
- data/lib/saml_idp/signed_info_builder.rb +88 -0
- data/lib/saml_idp/version.rb +4 -0
- data/lib/saml_idp/xml_security.rb +181 -0
- data/saml_idp.gemspec +65 -0
- data/spec/acceptance/acceptance_helper.rb +9 -0
- data/spec/acceptance/idp_controller_spec.rb +16 -0
- data/spec/lib/saml_idp/algorithmable_spec.rb +48 -0
- data/spec/lib/saml_idp/assertion_builder_spec.rb +106 -0
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +53 -0
- data/spec/lib/saml_idp/configurator_spec.rb +43 -0
- data/spec/lib/saml_idp/controller_spec.rb +94 -0
- data/spec/lib/saml_idp/encryptor_spec.rb +27 -0
- data/spec/lib/saml_idp/logout_request_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/logout_response_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +42 -0
- data/spec/lib/saml_idp/request_spec.rb +106 -0
- data/spec/lib/saml_idp/response_builder_spec.rb +42 -0
- data/spec/lib/saml_idp/saml_response_spec.rb +68 -0
- data/spec/lib/saml_idp/service_provider_spec.rb +27 -0
- data/spec/lib/saml_idp/signable_spec.rb +77 -0
- data/spec/lib/saml_idp/signature_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/signed_info_builder_spec.rb +25 -0
- data/spec/rails_app/.gitignore +15 -0
- data/spec/rails_app/README.rdoc +261 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/assets/images/rails.png +0 -0
- data/spec/rails_app/app/assets/javascripts/application.js +15 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +13 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/controllers/saml_controller.rb +8 -0
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +11 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/.gitkeep +0 -0
- data/spec/rails_app/app/models/.gitkeep +0 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +60 -0
- data/spec/rails_app/config/boot.rb +6 -0
- data/spec/rails_app/config/database.yml +25 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +37 -0
- data/spec/rails_app/config/environments/production.rb +67 -0
- data/spec/rails_app/config/environments/test.rb +37 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/inflections.rb +15 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +6 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/rails_app/doc/README_FOR_APP +2 -0
- data/spec/rails_app/lib/assets/.gitkeep +0 -0
- data/spec/rails_app/lib/tasks/.gitkeep +0 -0
- data/spec/rails_app/log/.gitkeep +0 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +25 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/index.html +241 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/rails_app/test/fixtures/.gitkeep +0 -0
- data/spec/rails_app/test/functional/.gitkeep +0 -0
- data/spec/rails_app/test/integration/.gitkeep +0 -0
- data/spec/rails_app/test/performance/browsing_test.rb +12 -0
- data/spec/rails_app/test/test_helper.rb +13 -0
- data/spec/rails_app/test/unit/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/support/certificates/certificate1 +12 -0
- data/spec/support/certificates/r1_certificate2_base64 +1 -0
- data/spec/support/responses/adfs_response_sha1.xml +46 -0
- data/spec/support/responses/adfs_response_sha256.xml +46 -0
- data/spec/support/responses/adfs_response_sha384.xml +46 -0
- data/spec/support/responses/adfs_response_sha512.xml +46 -0
- data/spec/support/responses/logoutresponse_fixtures.rb +67 -0
- data/spec/support/responses/no_signature_ns.xml +48 -0
- data/spec/support/responses/open_saml_response.xml +56 -0
- data/spec/support/responses/r1_response6.xml.base64 +1 -0
- data/spec/support/responses/response1.xml.base64 +1 -0
- data/spec/support/responses/response2.xml.base64 +79 -0
- data/spec/support/responses/response3.xml.base64 +66 -0
- data/spec/support/responses/response4.xml.base64 +93 -0
- data/spec/support/responses/response5.xml.base64 +102 -0
- data/spec/support/responses/response_with_ampersands.xml +139 -0
- data/spec/support/responses/response_with_ampersands.xml.base64 +93 -0
- data/spec/support/responses/simple_saml_php.xml +71 -0
- data/spec/support/responses/starfield_response.xml.base64 +1 -0
- data/spec/support/responses/wrapped_response_2.xml.base64 +150 -0
- data/spec/support/saml_request_macros.rb +38 -0
- data/spec/support/security_helpers.rb +61 -0
- data/spec/xml_security_spec.rb +137 -0
- metadata +465 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
# Requires methods:
|
2
|
+
# * reference_id
|
3
|
+
# * raw
|
4
|
+
# * digest
|
5
|
+
# * algorithm
|
6
|
+
require 'saml_idp/signed_info_builder'
|
7
|
+
require 'saml_idp/signature_builder'
|
8
|
+
module SamlIdp
|
9
|
+
module Signable
|
10
|
+
def self.included(base)
|
11
|
+
base.extend ClassMethods
|
12
|
+
base.send :attr_accessor, :reference_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def signed
|
16
|
+
generated_reference_id do
|
17
|
+
with_signature do
|
18
|
+
send(self.class.raw_method)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def sign(el)
|
24
|
+
el << signature if sign?
|
25
|
+
end
|
26
|
+
|
27
|
+
def generated_reference_id
|
28
|
+
if reference_id
|
29
|
+
fin = yield reference_id if block_given?
|
30
|
+
else
|
31
|
+
self.reference_id = ref = reference_id_generator.call
|
32
|
+
fin = yield reference_id if block_given?
|
33
|
+
self.reference_id = nil
|
34
|
+
end
|
35
|
+
block_given? ? fin : ref
|
36
|
+
end
|
37
|
+
private :generated_reference_id
|
38
|
+
|
39
|
+
def reference_id_generator
|
40
|
+
SamlIdp.config.reference_id_generator
|
41
|
+
end
|
42
|
+
private :reference_id_generator
|
43
|
+
|
44
|
+
def with_signature
|
45
|
+
original = @sign
|
46
|
+
@sign = true
|
47
|
+
yield.tap do
|
48
|
+
@sign = original
|
49
|
+
end
|
50
|
+
end
|
51
|
+
private :with_signature
|
52
|
+
|
53
|
+
def without_signature
|
54
|
+
original = @sign
|
55
|
+
@sign = false
|
56
|
+
yield.tap do
|
57
|
+
@sign = original
|
58
|
+
end
|
59
|
+
end
|
60
|
+
private :without_signature
|
61
|
+
|
62
|
+
def sign?
|
63
|
+
!!@sign
|
64
|
+
end
|
65
|
+
private :sign?
|
66
|
+
|
67
|
+
def signature
|
68
|
+
SignatureBuilder.new(signed_info_builder).raw
|
69
|
+
end
|
70
|
+
private :signature
|
71
|
+
|
72
|
+
def signed_info_builder
|
73
|
+
SignedInfoBuilder.new(get_reference_id, get_digest, get_algorithm)
|
74
|
+
end
|
75
|
+
private :signed_info_builder
|
76
|
+
|
77
|
+
def get_reference_id
|
78
|
+
send(self.class.reference_id_method)
|
79
|
+
end
|
80
|
+
private :get_reference_id
|
81
|
+
|
82
|
+
def get_digest
|
83
|
+
without_signature do
|
84
|
+
send(self.class.digest_method)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
private :get_digest
|
88
|
+
|
89
|
+
def get_algorithm
|
90
|
+
send(self.class.algorithm_method)
|
91
|
+
end
|
92
|
+
private :get_algorithm
|
93
|
+
|
94
|
+
def get_raw
|
95
|
+
send(self.class.raw_method)
|
96
|
+
end
|
97
|
+
private :get_raw
|
98
|
+
|
99
|
+
def noko_raw
|
100
|
+
Nokogiri::XML::Document.parse(get_raw)
|
101
|
+
end
|
102
|
+
private :noko_raw
|
103
|
+
|
104
|
+
def digest
|
105
|
+
# Make it check for inclusive at some point (https://github.com/onelogin/ruby-saml/blob/master/lib/xml_security.rb#L159)
|
106
|
+
inclusive_namespaces = []
|
107
|
+
# Also make customizable
|
108
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
109
|
+
canon_hashed_element = noko_raw.canonicalize(canon_algorithm, inclusive_namespaces)
|
110
|
+
digest_algorithm = get_algorithm
|
111
|
+
|
112
|
+
hash = digest_algorithm.digest(canon_hashed_element)
|
113
|
+
Base64.strict_encode64(hash).gsub(/\n/, '')
|
114
|
+
end
|
115
|
+
private :digest
|
116
|
+
|
117
|
+
module ClassMethods
|
118
|
+
def self.module_method(name, default = nil)
|
119
|
+
default ||= name
|
120
|
+
define_method "#{name}_method" do |new_method_name = nil|
|
121
|
+
instance_variable_set("@#{name}", new_method_name) if new_method_name
|
122
|
+
instance_variable_get("@#{name}") || default
|
123
|
+
end
|
124
|
+
end
|
125
|
+
module_method :raw
|
126
|
+
module_method :digest
|
127
|
+
module_method :algorithm
|
128
|
+
module_method :reference_id
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module SamlIdp
|
3
|
+
class SignatureBuilder
|
4
|
+
attr_accessor :signed_info_builder
|
5
|
+
|
6
|
+
def initialize(signed_info_builder)
|
7
|
+
self.signed_info_builder = signed_info_builder
|
8
|
+
end
|
9
|
+
|
10
|
+
def raw
|
11
|
+
builder = Builder::XmlMarkup.new
|
12
|
+
builder.tag! "ds:Signature", "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#" do |signature|
|
13
|
+
signature << signed_info
|
14
|
+
signature.tag! "ds:SignatureValue", signature_value
|
15
|
+
signature.KeyInfo xmlns: "http://www.w3.org/2000/09/xmldsig#" do |key_info|
|
16
|
+
key_info.tag! "ds:X509Data" do |x509|
|
17
|
+
x509.tag! "ds:X509Certificate", x509_certificate
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def x509_certificate
|
24
|
+
SamlIdp.config.x509_certificate
|
25
|
+
.to_s
|
26
|
+
.gsub(/-----BEGIN CERTIFICATE-----/,"")
|
27
|
+
.gsub(/-----END CERTIFICATE-----/,"")
|
28
|
+
.gsub(/\n/, "")
|
29
|
+
end
|
30
|
+
private :x509_certificate
|
31
|
+
|
32
|
+
def signed_info
|
33
|
+
signed_info_builder.raw
|
34
|
+
end
|
35
|
+
private :signed_info
|
36
|
+
|
37
|
+
def signature_value
|
38
|
+
signed_info_builder.signed
|
39
|
+
end
|
40
|
+
private :signature_value
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module SamlIdp
|
3
|
+
class SignedInfoBuilder
|
4
|
+
include Algorithmable
|
5
|
+
|
6
|
+
SIGNATURE_METHODS = {
|
7
|
+
"sha1" => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
|
8
|
+
"sha224" => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224",
|
9
|
+
"sha256" => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
10
|
+
"sha384" => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
|
11
|
+
"sha512" => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
|
12
|
+
}
|
13
|
+
DIGEST_METHODS = {
|
14
|
+
"sha1" => "http://www.w3.org/2000/09/xmldsig#sha1",
|
15
|
+
"sha224" => "http://www.w3.org/2001/04/xmldsig-more#sha224",
|
16
|
+
"sha256" => "http://www.w3.org/2001/04/xmlenc#sha256",
|
17
|
+
"sha384" => "http://www.w3.org/2001/04/xmldsig-more#sha384",
|
18
|
+
"sha512" => "http://www.w3.org/2001/04/xmlenc#sha512",
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
attr_accessor :reference_id
|
23
|
+
attr_accessor :digest_value
|
24
|
+
attr_accessor :raw_algorithm
|
25
|
+
|
26
|
+
def initialize(reference_id, digest_value, raw_algorithm)
|
27
|
+
self.reference_id = reference_id
|
28
|
+
self.digest_value = digest_value
|
29
|
+
self.raw_algorithm = raw_algorithm
|
30
|
+
end
|
31
|
+
|
32
|
+
def raw
|
33
|
+
builder = Builder::XmlMarkup.new
|
34
|
+
builder.tag! "ds:SignedInfo", "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#" do |signed_info|
|
35
|
+
signed_info.tag!("ds:CanonicalizationMethod", Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#") {}
|
36
|
+
signed_info.tag!("ds:SignatureMethod", Algorithm: signature_method ) {}
|
37
|
+
signed_info.tag! "ds:Reference", URI: reference_string do |reference|
|
38
|
+
reference.tag! "ds:Transforms" do |transforms|
|
39
|
+
transforms.tag!("ds:Transform", Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature") {}
|
40
|
+
transforms.tag!("ds:Transform", Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#") {}
|
41
|
+
end
|
42
|
+
reference.tag!("ds:DigestMethod", Algorithm: digest_method) {}
|
43
|
+
reference.tag! "ds:DigestValue", digest_value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def signed
|
49
|
+
encoded.gsub(/\n/, "")
|
50
|
+
end
|
51
|
+
|
52
|
+
def digest_method
|
53
|
+
DIGEST_METHODS.fetch(clean_algorithm_name, DIGEST_METHODS["sha1"])
|
54
|
+
end
|
55
|
+
private :digest_method
|
56
|
+
|
57
|
+
def signature_method
|
58
|
+
SIGNATURE_METHODS.fetch(clean_algorithm_name, SIGNATURE_METHODS["sha1"])
|
59
|
+
end
|
60
|
+
private :signature_method
|
61
|
+
|
62
|
+
def clean_algorithm_name
|
63
|
+
algorithm_name.to_s.downcase
|
64
|
+
end
|
65
|
+
private :clean_algorithm_name
|
66
|
+
|
67
|
+
def secret_key
|
68
|
+
SamlIdp.config.secret_key
|
69
|
+
end
|
70
|
+
private :secret_key
|
71
|
+
|
72
|
+
def password
|
73
|
+
SamlIdp.config.password
|
74
|
+
end
|
75
|
+
private :password
|
76
|
+
|
77
|
+
def encoded
|
78
|
+
key = OpenSSL::PKey::RSA.new(secret_key, password)
|
79
|
+
Base64.strict_encode64(key.sign(algorithm.new, raw))
|
80
|
+
end
|
81
|
+
private :encoded
|
82
|
+
|
83
|
+
def reference_string
|
84
|
+
"#_#{reference_id}"
|
85
|
+
end
|
86
|
+
private :reference_string
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,181 @@
|
|
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 "rexml/document"
|
26
|
+
require "rexml/xpath"
|
27
|
+
require "openssl"
|
28
|
+
require 'nokogiri'
|
29
|
+
require "digest/sha1"
|
30
|
+
require "digest/sha2"
|
31
|
+
|
32
|
+
module SamlIdp
|
33
|
+
module XMLSecurity
|
34
|
+
class SignedDocument < REXML::Document
|
35
|
+
ValidationError = Class.new(StandardError)
|
36
|
+
C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
37
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
38
|
+
|
39
|
+
attr_accessor :signed_element_id
|
40
|
+
|
41
|
+
def initialize(response)
|
42
|
+
super(response)
|
43
|
+
extract_signed_element_id
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate(idp_cert_fingerprint, soft = true)
|
47
|
+
# get cert from response
|
48
|
+
cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
|
49
|
+
raise ValidationError.new("Certificate element missing in response (ds:X509Certificate)") unless cert_element
|
50
|
+
base64_cert = cert_element.text
|
51
|
+
cert_text = Base64.decode64(base64_cert)
|
52
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
53
|
+
|
54
|
+
# check cert matches registered idp cert
|
55
|
+
fingerprint = fingerprint_cert(cert)
|
56
|
+
sha1_fingerprint = fingerprint_cert_sha1(cert)
|
57
|
+
plain_idp_cert_fingerprint = idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
58
|
+
|
59
|
+
if fingerprint != plain_idp_cert_fingerprint && sha1_fingerprint != plain_idp_cert_fingerprint
|
60
|
+
return soft ? false : (raise ValidationError.new("Fingerprint mismatch"))
|
61
|
+
end
|
62
|
+
|
63
|
+
validate_doc(base64_cert, soft)
|
64
|
+
end
|
65
|
+
|
66
|
+
def fingerprint_cert(cert)
|
67
|
+
# pick algorithm based on the doc's digest algorithm
|
68
|
+
ref_elem = REXML::XPath.first(self, "//ds:Reference", {"ds"=>DSIG})
|
69
|
+
digest_algorithm = algorithm(REXML::XPath.first(ref_elem, "//ds:DigestMethod"))
|
70
|
+
digest_algorithm.hexdigest(cert.to_der)
|
71
|
+
end
|
72
|
+
|
73
|
+
def fingerprint_cert_sha1(cert)
|
74
|
+
OpenSSL::Digest::SHA1.hexdigest(cert.to_der)
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_doc(base64_cert, soft = true)
|
78
|
+
# validate references
|
79
|
+
|
80
|
+
# check for inclusive namespaces
|
81
|
+
inclusive_namespaces = extract_inclusive_namespaces
|
82
|
+
|
83
|
+
document = Nokogiri.parse(self.to_s)
|
84
|
+
|
85
|
+
# create a working copy so we don't modify the original
|
86
|
+
@working_copy ||= REXML::Document.new(self.to_s).root
|
87
|
+
|
88
|
+
# store and remove signature node
|
89
|
+
@sig_element ||= begin
|
90
|
+
element = REXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
|
91
|
+
element.remove
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
# verify signature
|
96
|
+
signed_info_element = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
|
97
|
+
noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
|
98
|
+
noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
|
99
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(@sig_element, '//ds:CanonicalizationMethod', 'ds' => DSIG)
|
100
|
+
canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
|
101
|
+
noko_sig_element.remove
|
102
|
+
|
103
|
+
# check digests
|
104
|
+
REXML::XPath.each(@sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
|
105
|
+
uri = ref.attributes.get_attribute("URI").value
|
106
|
+
|
107
|
+
hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
|
108
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG)
|
109
|
+
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
|
110
|
+
|
111
|
+
digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod"))
|
112
|
+
|
113
|
+
hash = digest_algorithm.digest(canon_hashed_element)
|
114
|
+
digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
|
115
|
+
|
116
|
+
unless digests_match?(hash, digest_value)
|
117
|
+
return soft ? false : (raise ValidationError.new("Digest mismatch"))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
base64_signature = REXML::XPath.first(@sig_element, "//ds:SignatureValue", {"ds"=>DSIG}).text
|
122
|
+
signature = Base64.decode64(base64_signature)
|
123
|
+
|
124
|
+
# get certificate object
|
125
|
+
cert_text = Base64.decode64(base64_cert)
|
126
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
127
|
+
|
128
|
+
# signature method
|
129
|
+
signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG}))
|
130
|
+
|
131
|
+
unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
|
132
|
+
return soft ? false : (raise ValidationError.new("Key validation error"))
|
133
|
+
end
|
134
|
+
|
135
|
+
return true
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def digests_match?(hash, digest_value)
|
141
|
+
hash == digest_value
|
142
|
+
end
|
143
|
+
|
144
|
+
def extract_signed_element_id
|
145
|
+
reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG})
|
146
|
+
self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil?
|
147
|
+
end
|
148
|
+
|
149
|
+
def canon_algorithm(element)
|
150
|
+
algorithm = element.attribute('Algorithm').value if element
|
151
|
+
case algorithm
|
152
|
+
when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
153
|
+
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
|
154
|
+
when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1
|
155
|
+
else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def algorithm(element)
|
160
|
+
algorithm = element.attribute("Algorithm").value if element
|
161
|
+
algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
|
162
|
+
case algorithm
|
163
|
+
when 256 then OpenSSL::Digest::SHA256
|
164
|
+
when 384 then OpenSSL::Digest::SHA384
|
165
|
+
when 512 then OpenSSL::Digest::SHA512
|
166
|
+
else
|
167
|
+
OpenSSL::Digest::SHA1
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def extract_inclusive_namespaces
|
172
|
+
if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N })
|
173
|
+
prefix_list = element.attributes.get_attribute("PrefixList").value
|
174
|
+
prefix_list.split(" ")
|
175
|
+
else
|
176
|
+
[]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|