forked from omniauth/omniauth-saml
-
Notifications
You must be signed in to change notification settings - Fork 1
/
xml_security.rb
120 lines (101 loc) · 4.9 KB
/
xml_security.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# The contents of this file are subject to the terms
# of the Common Development and Distribution License
# (the License). You may not use this file except in
# compliance with the License.
#
# You can obtain a copy of the License at
# https://opensso.dev.java.net/public/CDDLv1.0.html or
# opensso/legal/CDDLv1.0.txt
# See the License for the specific language governing
# permission and limitations under the License.
#
# When distributing Covered Code, include this CDDL
# Header Notice in each file and include the License file
# at opensso/legal/CDDLv1.0.txt.
# If applicable, add the following below the CDDL Header,
# with the fields enclosed by brackets [] replaced by
# your own identifying information:
# "Portions Copyrighted [year] [name of copyright owner]"
#
# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
#
# Copyright 2007 Sun Microsystems Inc. All Rights Reserved
# Portions Copyrighted 2007 Todd W Saxton.
require 'rubygems'
require "rexml/document"
require "rexml/xpath"
require "openssl"
require "nokogiri"
require "digest/sha1"
module OmniAuth
module Strategies
class SAML
module XMLSecurity
class SignedDocument < REXML::Document
DSIG = "http://www.w3.org/2000/09/xmldsig#"
EC = "http://www.w3.org/2001/10/xml-exc-c14n#"
attr_accessor :signed_element_id
def initialize(response)
super(response)
@doc = Nokogiri::XML.parse(response)
extract_signed_element_id
end
def validate(idp_cert_fingerprint, soft = true)
# Get certificate from response
base64_cert = self.elements["//ds:X509Certificate"].text
cert_text = Base64.decode64(base64_cert)
cert = OpenSSL::X509::Certificate.new(cert_text)
# Check certificate matches registered IdP certificate
fingerprint = Digest::SHA1.hexdigest(cert.to_der)
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
SAML::log :error, "Fingerprint Mismatch"
return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Fingerprint mismatch"))
end
validate_doc(base64_cert, soft)
end
def validate_doc(base64_cert, soft = true)
# Check for inclusive namespaces
inclusive_namespaces = []
inclusive_namespace_element = @doc.at_xpath(".//ec:InclusiveNamespaces", { "ec" => EC })
if inclusive_namespace_element
prefix_list = inclusive_namespace_element.attributes['PrefixList'].value
inclusive_namespaces = prefix_list.split(" ")
end
# Verify signature
signed_info_element = @doc.at_xpath(".//ds:SignedInfo", { "ds" => DSIG })
canon_string = signed_info_element.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0)
base64_signature = @doc.at_xpath(".//ds:SignatureValue", { "ds" => DSIG }).text
signature = Base64.decode64(base64_signature)
cert_text = Base64.decode64(base64_cert)
cert = OpenSSL::X509::Certificate.new(cert_text)
if !cert.public_key.verify(OpenSSL::Digest::SHA1.new, signature, canon_string)
SAML::log :error, "Key Validation Error."
return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Key validation error"))
end
# Remove Signature Node (must be done after signature verification)
sig_element = @doc.at_xpath(".//ds:Signature", { "ds" => DSIG })
sig_element.remove
# Check Digests (must be done after sig_element removal)
sig_element.xpath(".//ds:Reference", { "ds" => DSIG }).each do |ref|
uri = ref.attributes["URI"].value
hashed_element = @doc.at_xpath(".//*[@ID='#{uri[1..-1]}']")
canon_hashed_element = hashed_element.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0, inclusive_namespaces)
hash = Base64.encode64(Digest::SHA1.digest(canon_hashed_element)).chomp
digest_value = ref.at_xpath(".//ds:DigestValue", { "ds" => DSIG }).text
if hash != digest_value
SAML::log :error, "Digest Mismatch."
return soft ? false : (raise OmniAuth::Strategies::SAML::ValidationError.new("Digest mismatch"))
end
end
return true
end
private
def extract_signed_element_id
reference_element = @doc.at_xpath("//ds:Signature/ds:SignedInfo/ds:Reference", { "ds" => DSIG })
self.signed_element_id = reference_element.attribute("URI").value unless reference_element.nil?
end
end
end
end
end
end