kl-ruby-saml 0.0.1
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 +7 -0
- data/.document +5 -0
- data/.gitignore +14 -0
- data/.travis.yml +17 -0
- data/Gemfile +9 -0
- data/LICENSE +19 -0
- data/README.md +575 -0
- data/Rakefile +41 -0
- data/changelog.md +75 -0
- data/gemfiles/nokogiri-1.5.gemfile +5 -0
- data/lib/onelogin/ruby-saml.rb +17 -0
- data/lib/onelogin/ruby-saml/attribute_service.rb +57 -0
- data/lib/onelogin/ruby-saml/attributes.rb +128 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +156 -0
- data/lib/onelogin/ruby-saml/http_error.rb +7 -0
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +161 -0
- data/lib/onelogin/ruby-saml/logging.rb +30 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +131 -0
- data/lib/onelogin/ruby-saml/logoutresponse.rb +241 -0
- data/lib/onelogin/ruby-saml/metadata.rb +123 -0
- data/lib/onelogin/ruby-saml/response.rb +722 -0
- data/lib/onelogin/ruby-saml/saml_message.rb +158 -0
- data/lib/onelogin/ruby-saml/settings.rb +165 -0
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +258 -0
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +136 -0
- data/lib/onelogin/ruby-saml/utils.rb +172 -0
- data/lib/onelogin/ruby-saml/validation_error.rb +7 -0
- data/lib/onelogin/ruby-saml/version.rb +5 -0
- data/lib/ruby-saml.rb +1 -0
- data/lib/schemas/saml-schema-assertion-2.0.xsd +283 -0
- data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/schemas/saml-schema-metadata-2.0.xsd +337 -0
- data/lib/schemas/saml-schema-protocol-2.0.xsd +302 -0
- data/lib/schemas/sstc-metadata-attr.xsd +35 -0
- data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/schemas/xenc-schema.xsd +136 -0
- data/lib/schemas/xml.xsd +287 -0
- data/lib/schemas/xmldsig-core-schema.xsd +309 -0
- data/lib/xml_security.rb +358 -0
- data/ruby-saml.gemspec +57 -0
- data/test/certificates/certificate1 +12 -0
- data/test/certificates/certificate_without_head_foot +1 -0
- data/test/certificates/formatted_certificate +14 -0
- data/test/certificates/formatted_private_key +12 -0
- data/test/certificates/formatted_rsa_private_key +12 -0
- data/test/certificates/invalid_certificate1 +1 -0
- data/test/certificates/invalid_certificate2 +1 -0
- data/test/certificates/invalid_certificate3 +12 -0
- data/test/certificates/invalid_private_key1 +1 -0
- data/test/certificates/invalid_private_key2 +1 -0
- data/test/certificates/invalid_private_key3 +10 -0
- data/test/certificates/invalid_rsa_private_key1 +1 -0
- data/test/certificates/invalid_rsa_private_key2 +1 -0
- data/test/certificates/invalid_rsa_private_key3 +10 -0
- data/test/certificates/ruby-saml.crt +14 -0
- data/test/certificates/ruby-saml.key +15 -0
- data/test/idp_metadata_parser_test.rb +95 -0
- data/test/logging_test.rb +62 -0
- data/test/logout_requests/invalid_slo_request.xml +6 -0
- data/test/logout_requests/slo_request.xml +4 -0
- data/test/logout_requests/slo_request.xml.base64 +1 -0
- data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
- data/test/logout_requests/slo_request_with_session_index.xml +5 -0
- data/test/logout_responses/logoutresponse_fixtures.rb +67 -0
- data/test/logoutrequest_test.rb +211 -0
- data/test/logoutresponse_test.rb +258 -0
- data/test/metadata_test.rb +203 -0
- data/test/request_test.rb +282 -0
- data/test/response_test.rb +1094 -0
- data/test/responses/adfs_response_sha1.xml +46 -0
- data/test/responses/adfs_response_sha256.xml +46 -0
- data/test/responses/adfs_response_sha384.xml +46 -0
- data/test/responses/adfs_response_sha512.xml +46 -0
- data/test/responses/adfs_response_xmlns.xml +45 -0
- data/test/responses/attackxee.xml +13 -0
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
- data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
- data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
- data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
- data/test/responses/invalids/no_id.xml.base64 +1 -0
- data/test/responses/invalids/no_saml2.xml.base64 +1 -0
- data/test/responses/invalids/no_signature.xml.base64 +1 -0
- data/test/responses/invalids/no_status.xml.base64 +1 -0
- data/test/responses/invalids/no_status_code.xml.base64 +1 -0
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
- data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
- data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
- data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
- data/test/responses/no_signature_ns.xml +48 -0
- data/test/responses/open_saml_response.xml +56 -0
- data/test/responses/response_assertion_wrapped.xml.base64 +93 -0
- data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
- data/test/responses/response_eval.xml +7 -0
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
- data/test/responses/response_unsigned_xml_base64 +1 -0
- data/test/responses/response_with_ampersands.xml +139 -0
- data/test/responses/response_with_ampersands.xml.base64 +93 -0
- data/test/responses/response_with_multiple_attribute_values.xml +67 -0
- data/test/responses/response_with_saml2_namespace.xml.base64 +102 -0
- data/test/responses/response_with_signed_assertion.xml.base64 +66 -0
- data/test/responses/response_with_signed_assertion_2.xml.base64 +1 -0
- data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
- data/test/responses/response_without_attributes.xml.base64 +79 -0
- data/test/responses/response_wrapped.xml.base64 +150 -0
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
- data/test/responses/simple_saml_php.xml +71 -0
- data/test/responses/starfield_response.xml.base64 +1 -0
- data/test/responses/test_sign.xml +43 -0
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
- data/test/responses/valid_response.xml.base64 +1 -0
- data/test/saml_message_test.rb +56 -0
- data/test/settings_test.rb +218 -0
- data/test/slo_logoutrequest_test.rb +275 -0
- data/test/slo_logoutresponse_test.rb +185 -0
- data/test/test_helper.rb +252 -0
- data/test/utils_test.rb +145 -0
- data/test/xml_security_test.rb +329 -0
- metadata +415 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "uuid"
|
|
2
|
+
|
|
3
|
+
require "onelogin/ruby-saml/logging"
|
|
4
|
+
require "onelogin/ruby-saml/saml_message"
|
|
5
|
+
|
|
6
|
+
# Only supports SAML 2.0
|
|
7
|
+
module OneLogin
|
|
8
|
+
module RubySaml
|
|
9
|
+
|
|
10
|
+
# SAML2 Logout Response (SLO SP initiated, Parser)
|
|
11
|
+
#
|
|
12
|
+
class SloLogoutresponse < SamlMessage
|
|
13
|
+
|
|
14
|
+
# Logout Response ID
|
|
15
|
+
attr_reader :uuid
|
|
16
|
+
|
|
17
|
+
# Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
|
|
18
|
+
# Asigns an ID, a random uuid.
|
|
19
|
+
#
|
|
20
|
+
def initialize
|
|
21
|
+
@uuid = "_" + UUID.new.generate
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Creates the Logout Response string.
|
|
25
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
26
|
+
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
|
27
|
+
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
28
|
+
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
29
|
+
# @return [String] Logout Request string that includes the SAMLRequest
|
|
30
|
+
#
|
|
31
|
+
def create(settings, request_id = nil, logout_message = nil, params = {})
|
|
32
|
+
params = create_params(settings, request_id, logout_message, params)
|
|
33
|
+
params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
|
|
34
|
+
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
|
35
|
+
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
|
36
|
+
params.each_pair do |key, value|
|
|
37
|
+
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
@logout_url = settings.idp_slo_target_url + response_params
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Creates the Get parameters for the logout response.
|
|
44
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
45
|
+
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
|
46
|
+
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
47
|
+
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
48
|
+
# @return [Hash] Parameters
|
|
49
|
+
#
|
|
50
|
+
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
|
51
|
+
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
|
52
|
+
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
|
53
|
+
# conflicts so this line will solve them.
|
|
54
|
+
relay_state = params[:RelayState] || params['RelayState']
|
|
55
|
+
|
|
56
|
+
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
|
57
|
+
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
|
58
|
+
|
|
59
|
+
response = ""
|
|
60
|
+
response_doc.write(response)
|
|
61
|
+
|
|
62
|
+
Logging.debug "Created SLO Logout Response: #{response}"
|
|
63
|
+
|
|
64
|
+
response = deflate(response) if settings.compress_response
|
|
65
|
+
base64_response = encode(response)
|
|
66
|
+
response_params = {"SAMLResponse" => base64_response}
|
|
67
|
+
|
|
68
|
+
if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key
|
|
69
|
+
params['SigAlg'] = settings.security[:signature_method]
|
|
70
|
+
url_string = OneLogin::RubySaml::Utils.build_query(
|
|
71
|
+
:type => 'SAMLResponse',
|
|
72
|
+
:data => base64_response,
|
|
73
|
+
:relay_state => relay_state,
|
|
74
|
+
:sig_alg => params['SigAlg']
|
|
75
|
+
)
|
|
76
|
+
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
|
77
|
+
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
|
|
78
|
+
params['Signature'] = encode(signature)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
params.each_pair do |key, value|
|
|
82
|
+
response_params[key] = value.to_s
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
response_params
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Creates the SAMLResponse String.
|
|
89
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
90
|
+
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
|
91
|
+
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
92
|
+
# @return [String] The SAMLResponse String.
|
|
93
|
+
#
|
|
94
|
+
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
|
95
|
+
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
96
|
+
|
|
97
|
+
response_doc = XMLSecurity::Document.new
|
|
98
|
+
response_doc.uuid = uuid
|
|
99
|
+
|
|
100
|
+
root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
|
101
|
+
root.attributes['ID'] = uuid
|
|
102
|
+
root.attributes['IssueInstant'] = time
|
|
103
|
+
root.attributes['Version'] = '2.0'
|
|
104
|
+
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
|
105
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
|
106
|
+
|
|
107
|
+
if settings.issuer != nil
|
|
108
|
+
issuer = root.add_element "saml:Issuer"
|
|
109
|
+
issuer.text = settings.issuer
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# add success message
|
|
113
|
+
status = root.add_element 'samlp:Status'
|
|
114
|
+
|
|
115
|
+
# success status code
|
|
116
|
+
status_code = status.add_element 'samlp:StatusCode'
|
|
117
|
+
status_code.attributes['Value'] = 'urn:oasis:names:tc:SAML:2.0:status:Success'
|
|
118
|
+
|
|
119
|
+
# success status message
|
|
120
|
+
logout_message ||= 'Successfully Signed Out'
|
|
121
|
+
status_message = status.add_element 'samlp:StatusMessage'
|
|
122
|
+
status_message.text = logout_message
|
|
123
|
+
|
|
124
|
+
# embed signature
|
|
125
|
+
if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
|
126
|
+
private_key = settings.get_sp_key
|
|
127
|
+
cert = settings.get_sp_cert
|
|
128
|
+
response_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
response_doc
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
module OneLogin
|
|
2
|
+
module RubySaml
|
|
3
|
+
|
|
4
|
+
# SAML2 Auxiliary class
|
|
5
|
+
#
|
|
6
|
+
class Utils
|
|
7
|
+
|
|
8
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
|
9
|
+
XENC = "http://www.w3.org/2001/04/xmlenc#"
|
|
10
|
+
|
|
11
|
+
# Return a properly formatted x509 certificate
|
|
12
|
+
#
|
|
13
|
+
# @param cert [String] The original certificate
|
|
14
|
+
# @return [String] The formatted certificate
|
|
15
|
+
#
|
|
16
|
+
def self.format_cert(cert)
|
|
17
|
+
# don't try to format an encoded certificate or if is empty or nil
|
|
18
|
+
return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)
|
|
19
|
+
|
|
20
|
+
cert = cert.gsub(/\-{5}\s?(BEGIN|END) CERTIFICATE\s?\-{5}/, "")
|
|
21
|
+
cert = cert.gsub(/[\n\r\s]/, "")
|
|
22
|
+
cert = cert.scan(/.{1,64}/)
|
|
23
|
+
cert = cert.join("\n")
|
|
24
|
+
"-----BEGIN CERTIFICATE-----\n#{cert}\n-----END CERTIFICATE-----"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Return a properly formatted private key
|
|
28
|
+
#
|
|
29
|
+
# @param key [String] The original private key
|
|
30
|
+
# @return [String] The formatted private key
|
|
31
|
+
#
|
|
32
|
+
def self.format_private_key(key)
|
|
33
|
+
# don't try to format an encoded private key or if is empty
|
|
34
|
+
return key if key.nil? || key.empty? || key.match(/\x0d/)
|
|
35
|
+
|
|
36
|
+
# is this an rsa key?
|
|
37
|
+
rsa_key = key.match("RSA PRIVATE KEY")
|
|
38
|
+
key = key.gsub(/\-{5}\s?(BEGIN|END)( RSA)? PRIVATE KEY\s?\-{5}/, "")
|
|
39
|
+
key = key.gsub(/[\n\r\s]/, "")
|
|
40
|
+
key = key.scan(/.{1,64}/)
|
|
41
|
+
key = key.join("\n")
|
|
42
|
+
key_label = rsa_key ? "RSA PRIVATE KEY" : "PRIVATE KEY"
|
|
43
|
+
"-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Build the Query String signature that will be used in the HTTP-Redirect binding
|
|
47
|
+
# to generate the Signature
|
|
48
|
+
# @param params [Hash] Parameters to build the Query String
|
|
49
|
+
# @option params [String] :type 'SAMLRequest' or 'SAMLResponse'
|
|
50
|
+
# @option params [String] :data Base64 encoded SAMLRequest or SAMLResponse
|
|
51
|
+
# @option params [String] :relay_state The RelayState parameter
|
|
52
|
+
# @option params [String] :sig_alg The SigAlg parameter
|
|
53
|
+
# @return [String] The Query String
|
|
54
|
+
#
|
|
55
|
+
def self.build_query(params)
|
|
56
|
+
type, data, relay_state, sig_alg = [:type, :data, :relay_state, :sig_alg].map { |k| params[k]}
|
|
57
|
+
|
|
58
|
+
url_string = "#{type}=#{CGI.escape(data)}"
|
|
59
|
+
url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
|
|
60
|
+
url_string << "&SigAlg=#{CGI.escape(sig_alg)}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Validate the Signature parameter sent on the HTTP-Redirect binding
|
|
64
|
+
# @param params [Hash] Parameters to be used in the validation process
|
|
65
|
+
# @option params [OpenSSL::X509::Certificate] cert The Identity provider public certtificate
|
|
66
|
+
# @option params [String] sig_alg The SigAlg parameter
|
|
67
|
+
# @option params [String] signature The Signature parameter (base64 encoded)
|
|
68
|
+
# @option params [String] query_string The SigAlg parameter
|
|
69
|
+
# @return [Boolean] True if the Signature is valid, False otherwise
|
|
70
|
+
#
|
|
71
|
+
def self.verify_signature(params)
|
|
72
|
+
cert, sig_alg, signature, query_string = [:cert, :sig_alg, :signature, :query_string].map { |k| params[k]}
|
|
73
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg)
|
|
74
|
+
return cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Build the status error message
|
|
78
|
+
# @param status_code [String] StatusCode value
|
|
79
|
+
# @param status_message [Strig] StatusMessage value
|
|
80
|
+
# @return [String] The status error message
|
|
81
|
+
def self.status_error_msg(error_msg, status_code = nil, status_message = nil)
|
|
82
|
+
unless status_code.nil?
|
|
83
|
+
printable_code = status_code.split(':').last
|
|
84
|
+
error_msg << ', was ' + printable_code
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
unless status_message.nil?
|
|
88
|
+
error_msg << ' -> ' + status_message
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
error_msg
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Obtains the decrypted string from an Encrypted node element in XML
|
|
95
|
+
# @param encrypted_node [REXML::Element] The Encrypted element
|
|
96
|
+
# @param private_key [OpenSSL::PKey::RSA] The Service provider private key
|
|
97
|
+
# @return [String] The decrypted data
|
|
98
|
+
def self.decrypt_data(encrypted_node, private_key)
|
|
99
|
+
encrypt_data = REXML::XPath.first(
|
|
100
|
+
encrypted_node,
|
|
101
|
+
"./xenc:EncryptedData",
|
|
102
|
+
{ 'xenc' => XENC }
|
|
103
|
+
)
|
|
104
|
+
symmetric_key = retrieve_symmetric_key(encrypt_data, private_key)
|
|
105
|
+
cipher_value = REXML::XPath.first(
|
|
106
|
+
encrypt_data,
|
|
107
|
+
"//xenc:EncryptedData/xenc:CipherData/xenc:CipherValue",
|
|
108
|
+
{ 'xenc' => XENC }
|
|
109
|
+
)
|
|
110
|
+
node = Base64.decode64(cipher_value.text)
|
|
111
|
+
encrypt_method = REXML::XPath.first(
|
|
112
|
+
encrypt_data,
|
|
113
|
+
"//xenc:EncryptedData/xenc:EncryptionMethod",
|
|
114
|
+
{ 'xenc' => XENC }
|
|
115
|
+
)
|
|
116
|
+
algorithm = encrypt_method.attributes['Algorithm']
|
|
117
|
+
retrieve_plaintext(node, symmetric_key, algorithm)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Obtains the symmetric key from the EncryptedData element
|
|
121
|
+
# @param encrypt_data [REXML::Element] The EncryptedData element
|
|
122
|
+
# @param private_key [OpenSSL::PKey::RSA] The Service provider private key
|
|
123
|
+
# @return [String] The symmetric key
|
|
124
|
+
def self.retrieve_symmetric_key(encrypt_data, private_key)
|
|
125
|
+
encrypted_symmetric_key_element = REXML::XPath.first(
|
|
126
|
+
encrypt_data,
|
|
127
|
+
"//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:CipherData/xenc:CipherValue",
|
|
128
|
+
{ "ds" => DSIG, "xenc" => XENC }
|
|
129
|
+
)
|
|
130
|
+
cipher_text = Base64.decode64(encrypted_symmetric_key_element.text)
|
|
131
|
+
encrypt_method = REXML::XPath.first(
|
|
132
|
+
encrypt_data,
|
|
133
|
+
"//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:EncryptionMethod",
|
|
134
|
+
{"ds" => DSIG, "xenc" => XENC }
|
|
135
|
+
)
|
|
136
|
+
algorithm = encrypt_method.attributes['Algorithm']
|
|
137
|
+
retrieve_plaintext(cipher_text, private_key, algorithm)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Obtains the deciphered text
|
|
141
|
+
# @param cipher_text [String] The ciphered text
|
|
142
|
+
# @param symmetric_key [String] The symetric key used to encrypt the text
|
|
143
|
+
# @param algorithm [String] The encrypted algorithm
|
|
144
|
+
# @return [String] The deciphered text
|
|
145
|
+
def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm)
|
|
146
|
+
case algorithm
|
|
147
|
+
when 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' then cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
|
|
148
|
+
when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
|
|
149
|
+
when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
|
|
150
|
+
when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
|
|
151
|
+
when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
|
|
152
|
+
when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if cipher
|
|
156
|
+
iv_len = cipher.iv_len
|
|
157
|
+
data = cipher_text[iv_len..-1]
|
|
158
|
+
cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
|
|
159
|
+
assertion_plaintext = cipher.update(data)
|
|
160
|
+
assertion_plaintext << cipher.final
|
|
161
|
+
elsif rsa
|
|
162
|
+
rsa.private_decrypt(cipher_text)
|
|
163
|
+
elsif oaep
|
|
164
|
+
oaep.private_decrypt(cipher_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
|
165
|
+
else
|
|
166
|
+
cipher_text
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/lib/ruby-saml.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'onelogin/ruby-saml'
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="US-ASCII"?>
|
|
2
|
+
<schema
|
|
3
|
+
targetNamespace="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
4
|
+
xmlns="http://www.w3.org/2001/XMLSchema"
|
|
5
|
+
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
6
|
+
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
|
|
7
|
+
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
|
|
8
|
+
elementFormDefault="unqualified"
|
|
9
|
+
attributeFormDefault="unqualified"
|
|
10
|
+
blockDefault="substitution"
|
|
11
|
+
version="2.0">
|
|
12
|
+
<import namespace="http://www.w3.org/2000/09/xmldsig#"
|
|
13
|
+
schemaLocation="xmldsig-core-schema.xsd"/>
|
|
14
|
+
<import namespace="http://www.w3.org/2001/04/xmlenc#"
|
|
15
|
+
schemaLocation="xenc-schema.xsd"/>
|
|
16
|
+
<annotation>
|
|
17
|
+
<documentation>
|
|
18
|
+
Document identifier: saml-schema-assertion-2.0
|
|
19
|
+
Location: http://docs.oasis-open.org/security/saml/v2.0/
|
|
20
|
+
Revision history:
|
|
21
|
+
V1.0 (November, 2002):
|
|
22
|
+
Initial Standard Schema.
|
|
23
|
+
V1.1 (September, 2003):
|
|
24
|
+
Updates within the same V1.0 namespace.
|
|
25
|
+
V2.0 (March, 2005):
|
|
26
|
+
New assertion schema for SAML V2.0 namespace.
|
|
27
|
+
</documentation>
|
|
28
|
+
</annotation>
|
|
29
|
+
<attributeGroup name="IDNameQualifiers">
|
|
30
|
+
<attribute name="NameQualifier" type="string" use="optional"/>
|
|
31
|
+
<attribute name="SPNameQualifier" type="string" use="optional"/>
|
|
32
|
+
</attributeGroup>
|
|
33
|
+
<element name="BaseID" type="saml:BaseIDAbstractType"/>
|
|
34
|
+
<complexType name="BaseIDAbstractType" abstract="true">
|
|
35
|
+
<attributeGroup ref="saml:IDNameQualifiers"/>
|
|
36
|
+
</complexType>
|
|
37
|
+
<element name="NameID" type="saml:NameIDType"/>
|
|
38
|
+
<complexType name="NameIDType">
|
|
39
|
+
<simpleContent>
|
|
40
|
+
<extension base="string">
|
|
41
|
+
<attributeGroup ref="saml:IDNameQualifiers"/>
|
|
42
|
+
<attribute name="Format" type="anyURI" use="optional"/>
|
|
43
|
+
<attribute name="SPProvidedID" type="string" use="optional"/>
|
|
44
|
+
</extension>
|
|
45
|
+
</simpleContent>
|
|
46
|
+
</complexType>
|
|
47
|
+
<complexType name="EncryptedElementType">
|
|
48
|
+
<sequence>
|
|
49
|
+
<element ref="xenc:EncryptedData"/>
|
|
50
|
+
<element ref="xenc:EncryptedKey" minOccurs="0" maxOccurs="unbounded"/>
|
|
51
|
+
</sequence>
|
|
52
|
+
</complexType>
|
|
53
|
+
<element name="EncryptedID" type="saml:EncryptedElementType"/>
|
|
54
|
+
<element name="Issuer" type="saml:NameIDType"/>
|
|
55
|
+
<element name="AssertionIDRef" type="NCName"/>
|
|
56
|
+
<element name="AssertionURIRef" type="anyURI"/>
|
|
57
|
+
<element name="Assertion" type="saml:AssertionType"/>
|
|
58
|
+
<complexType name="AssertionType">
|
|
59
|
+
<sequence>
|
|
60
|
+
<element ref="saml:Issuer"/>
|
|
61
|
+
<element ref="ds:Signature" minOccurs="0"/>
|
|
62
|
+
<element ref="saml:Subject" minOccurs="0"/>
|
|
63
|
+
<element ref="saml:Conditions" minOccurs="0"/>
|
|
64
|
+
<element ref="saml:Advice" minOccurs="0"/>
|
|
65
|
+
<choice minOccurs="0" maxOccurs="unbounded">
|
|
66
|
+
<element ref="saml:Statement"/>
|
|
67
|
+
<element ref="saml:AuthnStatement"/>
|
|
68
|
+
<element ref="saml:AuthzDecisionStatement"/>
|
|
69
|
+
<element ref="saml:AttributeStatement"/>
|
|
70
|
+
</choice>
|
|
71
|
+
</sequence>
|
|
72
|
+
<attribute name="Version" type="string" use="required"/>
|
|
73
|
+
<attribute name="ID" type="ID" use="required"/>
|
|
74
|
+
<attribute name="IssueInstant" type="dateTime" use="required"/>
|
|
75
|
+
</complexType>
|
|
76
|
+
<element name="Subject" type="saml:SubjectType"/>
|
|
77
|
+
<complexType name="SubjectType">
|
|
78
|
+
<choice>
|
|
79
|
+
<sequence>
|
|
80
|
+
<choice>
|
|
81
|
+
<element ref="saml:BaseID"/>
|
|
82
|
+
<element ref="saml:NameID"/>
|
|
83
|
+
<element ref="saml:EncryptedID"/>
|
|
84
|
+
</choice>
|
|
85
|
+
<element ref="saml:SubjectConfirmation" minOccurs="0" maxOccurs="unbounded"/>
|
|
86
|
+
</sequence>
|
|
87
|
+
<element ref="saml:SubjectConfirmation" maxOccurs="unbounded"/>
|
|
88
|
+
</choice>
|
|
89
|
+
</complexType>
|
|
90
|
+
<element name="SubjectConfirmation" type="saml:SubjectConfirmationType"/>
|
|
91
|
+
<complexType name="SubjectConfirmationType">
|
|
92
|
+
<sequence>
|
|
93
|
+
<choice minOccurs="0">
|
|
94
|
+
<element ref="saml:BaseID"/>
|
|
95
|
+
<element ref="saml:NameID"/>
|
|
96
|
+
<element ref="saml:EncryptedID"/>
|
|
97
|
+
</choice>
|
|
98
|
+
<element ref="saml:SubjectConfirmationData" minOccurs="0"/>
|
|
99
|
+
</sequence>
|
|
100
|
+
<attribute name="Method" type="anyURI" use="required"/>
|
|
101
|
+
</complexType>
|
|
102
|
+
<element name="SubjectConfirmationData" type="saml:SubjectConfirmationDataType"/>
|
|
103
|
+
<complexType name="SubjectConfirmationDataType" mixed="true">
|
|
104
|
+
<complexContent>
|
|
105
|
+
<restriction base="anyType">
|
|
106
|
+
<sequence>
|
|
107
|
+
<any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
|
|
108
|
+
</sequence>
|
|
109
|
+
<attribute name="NotBefore" type="dateTime" use="optional"/>
|
|
110
|
+
<attribute name="NotOnOrAfter" type="dateTime" use="optional"/>
|
|
111
|
+
<attribute name="Recipient" type="anyURI" use="optional"/>
|
|
112
|
+
<attribute name="InResponseTo" type="NCName" use="optional"/>
|
|
113
|
+
<attribute name="Address" type="string" use="optional"/>
|
|
114
|
+
<anyAttribute namespace="##other" processContents="lax"/>
|
|
115
|
+
</restriction>
|
|
116
|
+
</complexContent>
|
|
117
|
+
</complexType>
|
|
118
|
+
<complexType name="KeyInfoConfirmationDataType" mixed="false">
|
|
119
|
+
<complexContent>
|
|
120
|
+
<restriction base="saml:SubjectConfirmationDataType">
|
|
121
|
+
<sequence>
|
|
122
|
+
<element ref="ds:KeyInfo" maxOccurs="unbounded"/>
|
|
123
|
+
</sequence>
|
|
124
|
+
</restriction>
|
|
125
|
+
</complexContent>
|
|
126
|
+
</complexType>
|
|
127
|
+
<element name="Conditions" type="saml:ConditionsType"/>
|
|
128
|
+
<complexType name="ConditionsType">
|
|
129
|
+
<choice minOccurs="0" maxOccurs="unbounded">
|
|
130
|
+
<element ref="saml:Condition"/>
|
|
131
|
+
<element ref="saml:AudienceRestriction"/>
|
|
132
|
+
<element ref="saml:OneTimeUse"/>
|
|
133
|
+
<element ref="saml:ProxyRestriction"/>
|
|
134
|
+
</choice>
|
|
135
|
+
<attribute name="NotBefore" type="dateTime" use="optional"/>
|
|
136
|
+
<attribute name="NotOnOrAfter" type="dateTime" use="optional"/>
|
|
137
|
+
</complexType>
|
|
138
|
+
<element name="Condition" type="saml:ConditionAbstractType"/>
|
|
139
|
+
<complexType name="ConditionAbstractType" abstract="true"/>
|
|
140
|
+
<element name="AudienceRestriction" type="saml:AudienceRestrictionType"/>
|
|
141
|
+
<complexType name="AudienceRestrictionType">
|
|
142
|
+
<complexContent>
|
|
143
|
+
<extension base="saml:ConditionAbstractType">
|
|
144
|
+
<sequence>
|
|
145
|
+
<element ref="saml:Audience" maxOccurs="unbounded"/>
|
|
146
|
+
</sequence>
|
|
147
|
+
</extension>
|
|
148
|
+
</complexContent>
|
|
149
|
+
</complexType>
|
|
150
|
+
<element name="Audience" type="anyURI"/>
|
|
151
|
+
<element name="OneTimeUse" type="saml:OneTimeUseType" />
|
|
152
|
+
<complexType name="OneTimeUseType">
|
|
153
|
+
<complexContent>
|
|
154
|
+
<extension base="saml:ConditionAbstractType"/>
|
|
155
|
+
</complexContent>
|
|
156
|
+
</complexType>
|
|
157
|
+
<element name="ProxyRestriction" type="saml:ProxyRestrictionType"/>
|
|
158
|
+
<complexType name="ProxyRestrictionType">
|
|
159
|
+
<complexContent>
|
|
160
|
+
<extension base="saml:ConditionAbstractType">
|
|
161
|
+
<sequence>
|
|
162
|
+
<element ref="saml:Audience" minOccurs="0" maxOccurs="unbounded"/>
|
|
163
|
+
</sequence>
|
|
164
|
+
<attribute name="Count" type="nonNegativeInteger" use="optional"/>
|
|
165
|
+
</extension>
|
|
166
|
+
</complexContent>
|
|
167
|
+
</complexType>
|
|
168
|
+
<element name="Advice" type="saml:AdviceType"/>
|
|
169
|
+
<complexType name="AdviceType">
|
|
170
|
+
<choice minOccurs="0" maxOccurs="unbounded">
|
|
171
|
+
<element ref="saml:AssertionIDRef"/>
|
|
172
|
+
<element ref="saml:AssertionURIRef"/>
|
|
173
|
+
<element ref="saml:Assertion"/>
|
|
174
|
+
<element ref="saml:EncryptedAssertion"/>
|
|
175
|
+
<any namespace="##other" processContents="lax"/>
|
|
176
|
+
</choice>
|
|
177
|
+
</complexType>
|
|
178
|
+
<element name="EncryptedAssertion" type="saml:EncryptedElementType"/>
|
|
179
|
+
<element name="Statement" type="saml:StatementAbstractType"/>
|
|
180
|
+
<complexType name="StatementAbstractType" abstract="true"/>
|
|
181
|
+
<element name="AuthnStatement" type="saml:AuthnStatementType"/>
|
|
182
|
+
<complexType name="AuthnStatementType">
|
|
183
|
+
<complexContent>
|
|
184
|
+
<extension base="saml:StatementAbstractType">
|
|
185
|
+
<sequence>
|
|
186
|
+
<element ref="saml:SubjectLocality" minOccurs="0"/>
|
|
187
|
+
<element ref="saml:AuthnContext"/>
|
|
188
|
+
</sequence>
|
|
189
|
+
<attribute name="AuthnInstant" type="dateTime" use="required"/>
|
|
190
|
+
<attribute name="SessionIndex" type="string" use="optional"/>
|
|
191
|
+
<attribute name="SessionNotOnOrAfter" type="dateTime" use="optional"/>
|
|
192
|
+
</extension>
|
|
193
|
+
</complexContent>
|
|
194
|
+
</complexType>
|
|
195
|
+
<element name="SubjectLocality" type="saml:SubjectLocalityType"/>
|
|
196
|
+
<complexType name="SubjectLocalityType">
|
|
197
|
+
<attribute name="Address" type="string" use="optional"/>
|
|
198
|
+
<attribute name="DNSName" type="string" use="optional"/>
|
|
199
|
+
</complexType>
|
|
200
|
+
<element name="AuthnContext" type="saml:AuthnContextType"/>
|
|
201
|
+
<complexType name="AuthnContextType">
|
|
202
|
+
<sequence>
|
|
203
|
+
<choice>
|
|
204
|
+
<sequence>
|
|
205
|
+
<element ref="saml:AuthnContextClassRef"/>
|
|
206
|
+
<choice minOccurs="0">
|
|
207
|
+
<element ref="saml:AuthnContextDecl"/>
|
|
208
|
+
<element ref="saml:AuthnContextDeclRef"/>
|
|
209
|
+
</choice>
|
|
210
|
+
</sequence>
|
|
211
|
+
<choice>
|
|
212
|
+
<element ref="saml:AuthnContextDecl"/>
|
|
213
|
+
<element ref="saml:AuthnContextDeclRef"/>
|
|
214
|
+
</choice>
|
|
215
|
+
</choice>
|
|
216
|
+
<element ref="saml:AuthenticatingAuthority" minOccurs="0" maxOccurs="unbounded"/>
|
|
217
|
+
</sequence>
|
|
218
|
+
</complexType>
|
|
219
|
+
<element name="AuthnContextClassRef" type="anyURI"/>
|
|
220
|
+
<element name="AuthnContextDeclRef" type="anyURI"/>
|
|
221
|
+
<element name="AuthnContextDecl" type="anyType"/>
|
|
222
|
+
<element name="AuthenticatingAuthority" type="anyURI"/>
|
|
223
|
+
<element name="AuthzDecisionStatement" type="saml:AuthzDecisionStatementType"/>
|
|
224
|
+
<complexType name="AuthzDecisionStatementType">
|
|
225
|
+
<complexContent>
|
|
226
|
+
<extension base="saml:StatementAbstractType">
|
|
227
|
+
<sequence>
|
|
228
|
+
<element ref="saml:Action" maxOccurs="unbounded"/>
|
|
229
|
+
<element ref="saml:Evidence" minOccurs="0"/>
|
|
230
|
+
</sequence>
|
|
231
|
+
<attribute name="Resource" type="anyURI" use="required"/>
|
|
232
|
+
<attribute name="Decision" type="saml:DecisionType" use="required"/>
|
|
233
|
+
</extension>
|
|
234
|
+
</complexContent>
|
|
235
|
+
</complexType>
|
|
236
|
+
<simpleType name="DecisionType">
|
|
237
|
+
<restriction base="string">
|
|
238
|
+
<enumeration value="Permit"/>
|
|
239
|
+
<enumeration value="Deny"/>
|
|
240
|
+
<enumeration value="Indeterminate"/>
|
|
241
|
+
</restriction>
|
|
242
|
+
</simpleType>
|
|
243
|
+
<element name="Action" type="saml:ActionType"/>
|
|
244
|
+
<complexType name="ActionType">
|
|
245
|
+
<simpleContent>
|
|
246
|
+
<extension base="string">
|
|
247
|
+
<attribute name="Namespace" type="anyURI" use="required"/>
|
|
248
|
+
</extension>
|
|
249
|
+
</simpleContent>
|
|
250
|
+
</complexType>
|
|
251
|
+
<element name="Evidence" type="saml:EvidenceType"/>
|
|
252
|
+
<complexType name="EvidenceType">
|
|
253
|
+
<choice maxOccurs="unbounded">
|
|
254
|
+
<element ref="saml:AssertionIDRef"/>
|
|
255
|
+
<element ref="saml:AssertionURIRef"/>
|
|
256
|
+
<element ref="saml:Assertion"/>
|
|
257
|
+
<element ref="saml:EncryptedAssertion"/>
|
|
258
|
+
</choice>
|
|
259
|
+
</complexType>
|
|
260
|
+
<element name="AttributeStatement" type="saml:AttributeStatementType"/>
|
|
261
|
+
<complexType name="AttributeStatementType">
|
|
262
|
+
<complexContent>
|
|
263
|
+
<extension base="saml:StatementAbstractType">
|
|
264
|
+
<choice maxOccurs="unbounded">
|
|
265
|
+
<element ref="saml:Attribute"/>
|
|
266
|
+
<element ref="saml:EncryptedAttribute"/>
|
|
267
|
+
</choice>
|
|
268
|
+
</extension>
|
|
269
|
+
</complexContent>
|
|
270
|
+
</complexType>
|
|
271
|
+
<element name="Attribute" type="saml:AttributeType"/>
|
|
272
|
+
<complexType name="AttributeType">
|
|
273
|
+
<sequence>
|
|
274
|
+
<element ref="saml:AttributeValue" minOccurs="0" maxOccurs="unbounded"/>
|
|
275
|
+
</sequence>
|
|
276
|
+
<attribute name="Name" type="string" use="required"/>
|
|
277
|
+
<attribute name="NameFormat" type="anyURI" use="optional"/>
|
|
278
|
+
<attribute name="FriendlyName" type="string" use="optional"/>
|
|
279
|
+
<anyAttribute namespace="##other" processContents="lax"/>
|
|
280
|
+
</complexType>
|
|
281
|
+
<element name="AttributeValue" type="anyType" nillable="true"/>
|
|
282
|
+
<element name="EncryptedAttribute" type="saml:EncryptedElementType"/>
|
|
283
|
+
</schema>
|