ruby-saml 1.11.0 → 1.14.0
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 +5 -5
- data/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +49 -1
- data/README.md +363 -218
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +12 -8
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +154 -83
- data/lib/onelogin/ruby-saml/logoutrequest.rb +13 -7
- data/lib/onelogin/ruby-saml/logoutresponse.rb +6 -2
- data/lib/onelogin/ruby-saml/metadata.rb +62 -17
- data/lib/onelogin/ruby-saml/response.rb +57 -32
- data/lib/onelogin/ruby-saml/saml_message.rb +8 -3
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +92 -50
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +17 -30
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +32 -18
- data/lib/onelogin/ruby-saml/utils.rb +83 -8
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +39 -13
- data/ruby-saml.gemspec +14 -5
- metadata +29 -288
- data/.travis.yml +0 -46
- data/test/certificates/certificate.der +0 -0
- data/test/certificates/certificate1 +0 -12
- data/test/certificates/certificate_without_head_foot +0 -1
- data/test/certificates/formatted_certificate +0 -14
- data/test/certificates/formatted_chained_certificate +0 -42
- data/test/certificates/formatted_private_key +0 -12
- data/test/certificates/formatted_rsa_private_key +0 -12
- data/test/certificates/invalid_certificate1 +0 -1
- data/test/certificates/invalid_certificate2 +0 -1
- data/test/certificates/invalid_certificate3 +0 -12
- data/test/certificates/invalid_chained_certificate1 +0 -1
- data/test/certificates/invalid_private_key1 +0 -1
- data/test/certificates/invalid_private_key2 +0 -1
- data/test/certificates/invalid_private_key3 +0 -10
- data/test/certificates/invalid_rsa_private_key1 +0 -1
- data/test/certificates/invalid_rsa_private_key2 +0 -1
- data/test/certificates/invalid_rsa_private_key3 +0 -10
- data/test/certificates/ruby-saml-2.crt +0 -15
- data/test/certificates/ruby-saml.crt +0 -14
- data/test/certificates/ruby-saml.key +0 -15
- data/test/idp_metadata_parser_test.rb +0 -594
- data/test/logging_test.rb +0 -62
- data/test/logout_requests/invalid_slo_request.xml +0 -6
- data/test/logout_requests/slo_request.xml +0 -4
- data/test/logout_requests/slo_request.xml.base64 +0 -1
- data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
- data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
- data/test/logout_requests/slo_request_with_session_index.xml +0 -5
- data/test/logout_responses/logoutresponse_fixtures.rb +0 -86
- data/test/logoutrequest_test.rb +0 -260
- data/test/logoutresponse_test.rb +0 -427
- data/test/metadata/idp_descriptor.xml +0 -26
- data/test/metadata/idp_descriptor_2.xml +0 -56
- data/test/metadata/idp_descriptor_3.xml +0 -14
- data/test/metadata/idp_descriptor_4.xml +0 -72
- data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
- data/test/metadata/idp_metadata_multi_certs.xml +0 -75
- data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
- data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
- data/test/metadata/idp_multiple_descriptors.xml +0 -59
- data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -340
- data/test/response_test.rb +0 -1629
- data/test/responses/adfs_response_sha1.xml +0 -46
- data/test/responses/adfs_response_sha256.xml +0 -46
- data/test/responses/adfs_response_sha384.xml +0 -46
- data/test/responses/adfs_response_sha512.xml +0 -46
- data/test/responses/adfs_response_xmlns.xml +0 -45
- data/test/responses/attackxee.xml +0 -13
- data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
- data/test/responses/invalids/empty_destination.xml.base64 +0 -1
- data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
- data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
- data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
- data/test/responses/invalids/no_conditions.xml.base64 +0 -1
- data/test/responses/invalids/no_id.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
- data/test/responses/invalids/no_nameid.xml.base64 +0 -1
- data/test/responses/invalids/no_saml2.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/no_status.xml.base64 +0 -1
- data/test/responses/invalids/no_status_code.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
- data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
- data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
- data/test/responses/no_signature_ns.xml +0 -48
- data/test/responses/open_saml_response.xml +0 -56
- data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
- data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
- data/test/responses/response_double_status_code.xml.base64 +0 -1
- data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
- data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
- data/test/responses/response_eval.xml +0 -7
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack2.xml.base64 +0 -1
- data/test/responses/response_node_text_attack3.xml.base64 +0 -1
- data/test/responses/response_unsigned_xml_base64 +0 -1
- data/test/responses/response_with_ampersands.xml +0 -139
- data/test/responses/response_with_ampersands.xml.base64 +0 -93
- data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_multiple_attribute_values.xml +0 -67
- data/test/responses/response_with_retrieval_method.xml +0 -26
- data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
- data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
- data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_without_attributes.xml.base64 +0 -79
- data/test/responses/response_without_reference_uri.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/signed_nameid_in_atts.xml +0 -47
- data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
- data/test/responses/simple_saml_php.xml +0 -71
- data/test/responses/starfield_response.xml.base64 +0 -1
- data/test/responses/test_sign.xml +0 -43
- data/test/responses/unsigned_encrypted_adfs.xml +0 -23
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/saml_message_test.rb +0 -56
- data/test/settings_test.rb +0 -338
- data/test/slo_logoutrequest_test.rb +0 -467
- data/test/slo_logoutresponse_test.rb +0 -233
- data/test/test_helper.rb +0 -333
- data/test/utils_test.rb +0 -259
- data/test/xml_security_test.rb +0 -421
|
@@ -2,6 +2,7 @@ require "onelogin/ruby-saml/logging"
|
|
|
2
2
|
|
|
3
3
|
require "onelogin/ruby-saml/saml_message"
|
|
4
4
|
require "onelogin/ruby-saml/utils"
|
|
5
|
+
require "onelogin/ruby-saml/setting_error"
|
|
5
6
|
|
|
6
7
|
# Only supports SAML 2.0
|
|
7
8
|
module OneLogin
|
|
@@ -12,7 +13,7 @@ module OneLogin
|
|
|
12
13
|
class SloLogoutresponse < SamlMessage
|
|
13
14
|
|
|
14
15
|
# Logout Response ID
|
|
15
|
-
|
|
16
|
+
attr_accessor :uuid
|
|
16
17
|
|
|
17
18
|
# Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
|
|
18
19
|
# Asigns an ID, a random uuid.
|
|
@@ -21,23 +22,30 @@ module OneLogin
|
|
|
21
22
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
def response_id
|
|
26
|
+
@uuid
|
|
27
|
+
end
|
|
28
|
+
|
|
24
29
|
# Creates the Logout Response string.
|
|
25
30
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
26
31
|
# @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
32
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
28
33
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
34
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
|
29
35
|
# @return [String] Logout Request string that includes the SAMLRequest
|
|
30
36
|
#
|
|
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.
|
|
37
|
+
def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
|
38
|
+
params = create_params(settings, request_id, logout_message, params, logout_status_code)
|
|
39
|
+
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
|
|
40
|
+
url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
|
34
41
|
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
|
35
42
|
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
|
36
43
|
params.each_pair do |key, value|
|
|
37
44
|
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
38
45
|
end
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
|
|
48
|
+
@logout_url = url + response_params
|
|
41
49
|
end
|
|
42
50
|
|
|
43
51
|
# Creates the Get parameters for the logout response.
|
|
@@ -45,9 +53,10 @@ module OneLogin
|
|
|
45
53
|
# @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
54
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
47
55
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
56
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
|
48
57
|
# @return [Hash] Parameters
|
|
49
58
|
#
|
|
50
|
-
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
|
59
|
+
def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
|
51
60
|
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
|
52
61
|
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
|
53
62
|
# conflicts so this line will solve them.
|
|
@@ -58,7 +67,7 @@ module OneLogin
|
|
|
58
67
|
params.delete('RelayState')
|
|
59
68
|
end
|
|
60
69
|
|
|
61
|
-
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
|
70
|
+
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
|
|
62
71
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
|
63
72
|
|
|
64
73
|
response = ""
|
|
@@ -70,7 +79,7 @@ module OneLogin
|
|
|
70
79
|
base64_response = encode(response)
|
|
71
80
|
response_params = {"SAMLResponse" => base64_response}
|
|
72
81
|
|
|
73
|
-
if settings.
|
|
82
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && settings.private_key
|
|
74
83
|
params['SigAlg'] = settings.security[:signature_method]
|
|
75
84
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
|
76
85
|
:type => 'SAMLResponse',
|
|
@@ -94,39 +103,44 @@ module OneLogin
|
|
|
94
103
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
95
104
|
# @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
|
|
96
105
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
|
106
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
|
97
107
|
# @return [String] The SAMLResponse String.
|
|
98
108
|
#
|
|
99
|
-
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
|
100
|
-
document = create_xml_document(settings, request_id, logout_message)
|
|
109
|
+
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil)
|
|
110
|
+
document = create_xml_document(settings, request_id, logout_message, logout_status_code)
|
|
101
111
|
sign_document(document, settings)
|
|
102
112
|
end
|
|
103
113
|
|
|
104
|
-
def create_xml_document(settings, request_id = nil, logout_message = nil)
|
|
114
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
|
|
105
115
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
106
116
|
|
|
107
117
|
response_doc = XMLSecurity::Document.new
|
|
108
118
|
response_doc.uuid = uuid
|
|
109
119
|
|
|
120
|
+
destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
|
|
121
|
+
|
|
122
|
+
|
|
110
123
|
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" }
|
|
111
124
|
root.attributes['ID'] = uuid
|
|
112
125
|
root.attributes['IssueInstant'] = time
|
|
113
126
|
root.attributes['Version'] = '2.0'
|
|
114
127
|
root.attributes['InResponseTo'] = request_id unless request_id.nil?
|
|
115
|
-
root.attributes['Destination'] =
|
|
128
|
+
root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
|
|
116
129
|
|
|
117
130
|
if settings.sp_entity_id != nil
|
|
118
131
|
issuer = root.add_element "saml:Issuer"
|
|
119
132
|
issuer.text = settings.sp_entity_id
|
|
120
133
|
end
|
|
121
134
|
|
|
122
|
-
# add
|
|
135
|
+
# add status
|
|
123
136
|
status = root.add_element 'samlp:Status'
|
|
124
137
|
|
|
125
|
-
#
|
|
126
|
-
status_code
|
|
127
|
-
|
|
138
|
+
# status code
|
|
139
|
+
status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
|
|
140
|
+
status_code_elem = status.add_element 'samlp:StatusCode'
|
|
141
|
+
status_code_elem.attributes['Value'] = status_code
|
|
128
142
|
|
|
129
|
-
#
|
|
143
|
+
# status message
|
|
130
144
|
logout_message ||= 'Successfully Signed Out'
|
|
131
145
|
status_message = status.add_element 'samlp:StatusMessage'
|
|
132
146
|
status_message.text = logout_message
|
|
@@ -136,7 +150,7 @@ module OneLogin
|
|
|
136
150
|
|
|
137
151
|
def sign_document(document, settings)
|
|
138
152
|
# embed signature
|
|
139
|
-
if settings.
|
|
153
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.private_key && settings.certificate
|
|
140
154
|
private_key = settings.get_sp_key
|
|
141
155
|
cert = settings.get_sp_cert
|
|
142
156
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
@@ -13,8 +13,26 @@ module OneLogin
|
|
|
13
13
|
class Utils
|
|
14
14
|
@@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
BINDINGS = { :post => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
|
|
17
|
+
:redirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze }.freeze
|
|
18
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
|
|
19
|
+
XENC = "http://www.w3.org/2001/04/xmlenc#".freeze
|
|
20
|
+
DURATION_FORMAT = %r(^
|
|
21
|
+
(-?)P # 1: Duration sign
|
|
22
|
+
(?:
|
|
23
|
+
(?:(\d+)Y)? # 2: Years
|
|
24
|
+
(?:(\d+)M)? # 3: Months
|
|
25
|
+
(?:(\d+)D)? # 4: Days
|
|
26
|
+
(?:T
|
|
27
|
+
(?:(\d+)H)? # 5: Hours
|
|
28
|
+
(?:(\d+)M)? # 6: Minutes
|
|
29
|
+
(?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds
|
|
30
|
+
)?
|
|
31
|
+
|
|
|
32
|
+
(\d+)W # 8: Weeks
|
|
33
|
+
)
|
|
34
|
+
$)x.freeze
|
|
35
|
+
UUID_PREFIX = '_'
|
|
18
36
|
|
|
19
37
|
# Checks if the x509 cert provided is expired
|
|
20
38
|
#
|
|
@@ -28,6 +46,37 @@ module OneLogin
|
|
|
28
46
|
return cert.not_after < Time.now
|
|
29
47
|
end
|
|
30
48
|
|
|
49
|
+
# Interprets a ISO8601 duration value relative to a given timestamp.
|
|
50
|
+
#
|
|
51
|
+
# @param duration [String] The duration, as a string.
|
|
52
|
+
# @param timestamp [Integer] The unix timestamp we should apply the
|
|
53
|
+
# duration to. Optional, default to the
|
|
54
|
+
# current time.
|
|
55
|
+
#
|
|
56
|
+
# @return [Integer] The new timestamp, after the duration is applied.
|
|
57
|
+
#
|
|
58
|
+
def self.parse_duration(duration, timestamp=Time.now.utc)
|
|
59
|
+
return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported
|
|
60
|
+
|
|
61
|
+
matches = duration.match(DURATION_FORMAT)
|
|
62
|
+
|
|
63
|
+
if matches.nil?
|
|
64
|
+
raise Exception.new("Invalid ISO 8601 duration")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
sign = matches[1] == '-' ? -1 : 1
|
|
68
|
+
|
|
69
|
+
durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
|
|
70
|
+
matches[2..8].map { |match| match ? sign * match.tr(',', '.').to_f : 0.0 }
|
|
71
|
+
|
|
72
|
+
initial_datetime = Time.at(timestamp).utc.to_datetime
|
|
73
|
+
final_datetime = initial_datetime.next_year(durYears)
|
|
74
|
+
final_datetime = final_datetime.next_month(durMonths)
|
|
75
|
+
final_datetime = final_datetime.next_day((7*durWeeks) + durDays)
|
|
76
|
+
final_timestamp = final_datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
|
|
77
|
+
return final_timestamp
|
|
78
|
+
end
|
|
79
|
+
|
|
31
80
|
# Return a properly formatted x509 certificate
|
|
32
81
|
#
|
|
33
82
|
# @param cert [String] The original certificate
|
|
@@ -118,27 +167,36 @@ module OneLogin
|
|
|
118
167
|
#
|
|
119
168
|
# @param rawparams [Hash] Raw GET Parameters
|
|
120
169
|
# @param params [Hash] GET Parameters
|
|
170
|
+
# @param lowercase_url_encoding [bool] Lowercase URL Encoding (For ADFS urlencode compatiblity)
|
|
121
171
|
# @return [Hash] New raw parameters
|
|
122
172
|
#
|
|
123
|
-
def self.prepare_raw_get_params(rawparams, params)
|
|
173
|
+
def self.prepare_raw_get_params(rawparams, params, lowercase_url_encoding=false)
|
|
124
174
|
rawparams ||= {}
|
|
125
175
|
|
|
126
176
|
if rawparams['SAMLRequest'].nil? && !params['SAMLRequest'].nil?
|
|
127
|
-
rawparams['SAMLRequest'] =
|
|
177
|
+
rawparams['SAMLRequest'] = escape_request_param(params['SAMLRequest'], lowercase_url_encoding)
|
|
128
178
|
end
|
|
129
179
|
if rawparams['SAMLResponse'].nil? && !params['SAMLResponse'].nil?
|
|
130
|
-
rawparams['SAMLResponse'] =
|
|
180
|
+
rawparams['SAMLResponse'] = escape_request_param(params['SAMLResponse'], lowercase_url_encoding)
|
|
131
181
|
end
|
|
132
182
|
if rawparams['RelayState'].nil? && !params['RelayState'].nil?
|
|
133
|
-
rawparams['RelayState'] =
|
|
183
|
+
rawparams['RelayState'] = escape_request_param(params['RelayState'], lowercase_url_encoding)
|
|
134
184
|
end
|
|
135
185
|
if rawparams['SigAlg'].nil? && !params['SigAlg'].nil?
|
|
136
|
-
rawparams['SigAlg'] =
|
|
186
|
+
rawparams['SigAlg'] = escape_request_param(params['SigAlg'], lowercase_url_encoding)
|
|
137
187
|
end
|
|
138
188
|
|
|
139
189
|
rawparams
|
|
140
190
|
end
|
|
141
191
|
|
|
192
|
+
def self.escape_request_param(param, lowercase_url_encoding)
|
|
193
|
+
CGI.escape(param).tap do |escaped|
|
|
194
|
+
next unless lowercase_url_encoding
|
|
195
|
+
|
|
196
|
+
escaped.gsub!(/%[A-Fa-f0-9]{2}/) { |match| match.downcase }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
142
200
|
# Validate the Signature parameter sent on the HTTP-Redirect binding
|
|
143
201
|
# @param params [Hash] Parameters to be used in the validation process
|
|
144
202
|
# @option params [OpenSSL::X509::Certificate] cert The Identity provider public certtificate
|
|
@@ -253,6 +311,9 @@ module OneLogin
|
|
|
253
311
|
when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
|
|
254
312
|
when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
|
|
255
313
|
when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
|
|
314
|
+
when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt
|
|
315
|
+
when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(192, :GCM).decrypt
|
|
316
|
+
when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt
|
|
256
317
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
|
|
257
318
|
when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
|
|
258
319
|
end
|
|
@@ -263,6 +324,16 @@ module OneLogin
|
|
|
263
324
|
cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
|
|
264
325
|
assertion_plaintext = cipher.update(data)
|
|
265
326
|
assertion_plaintext << cipher.final
|
|
327
|
+
elsif auth_cipher
|
|
328
|
+
iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16
|
|
329
|
+
data = cipher_text[iv_len..text_len-1-tag_len]
|
|
330
|
+
auth_cipher.padding = 0
|
|
331
|
+
auth_cipher.key = symmetric_key
|
|
332
|
+
auth_cipher.iv = cipher_text[0..iv_len-1]
|
|
333
|
+
auth_cipher.auth_data = ''
|
|
334
|
+
auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1]
|
|
335
|
+
assertion_plaintext = auth_cipher.update(data)
|
|
336
|
+
assertion_plaintext << auth_cipher.final
|
|
266
337
|
elsif rsa
|
|
267
338
|
rsa.private_decrypt(cipher_text)
|
|
268
339
|
elsif oaep
|
|
@@ -272,8 +343,12 @@ module OneLogin
|
|
|
272
343
|
end
|
|
273
344
|
end
|
|
274
345
|
|
|
346
|
+
def self.set_prefix(value)
|
|
347
|
+
UUID_PREFIX.replace value
|
|
348
|
+
end
|
|
349
|
+
|
|
275
350
|
def self.uuid
|
|
276
|
-
RUBY_VERSION < '1.9' ? "
|
|
351
|
+
"#{UUID_PREFIX}" + (RUBY_VERSION < '1.9' ? "#{@@uuid_generator.generate}" : "#{SecureRandom.uuid}")
|
|
277
352
|
end
|
|
278
353
|
|
|
279
354
|
# Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed,
|
data/lib/xml_security.rb
CHANGED
|
@@ -159,15 +159,13 @@ module XMLSecurity
|
|
|
159
159
|
x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
|
|
160
160
|
|
|
161
161
|
# add the signature
|
|
162
|
-
issuer_element =
|
|
162
|
+
issuer_element = elements["//saml:Issuer"]
|
|
163
163
|
if issuer_element
|
|
164
|
-
|
|
164
|
+
root.insert_after(issuer_element, signature_element)
|
|
165
|
+
elsif first_child = root.children[0]
|
|
166
|
+
root.insert_before(first_child, signature_element)
|
|
165
167
|
else
|
|
166
|
-
|
|
167
|
-
self.root.insert_before sp_sso_descriptor, signature_element
|
|
168
|
-
else
|
|
169
|
-
self.root.add_element(signature_element)
|
|
170
|
-
end
|
|
168
|
+
root.add_element(signature_element)
|
|
171
169
|
end
|
|
172
170
|
end
|
|
173
171
|
|
|
@@ -212,7 +210,7 @@ module XMLSecurity
|
|
|
212
210
|
begin
|
|
213
211
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
|
214
212
|
rescue OpenSSL::X509::CertificateError => _e
|
|
215
|
-
return append_error("Certificate Error", soft)
|
|
213
|
+
return append_error("Document Certificate Error", soft)
|
|
216
214
|
end
|
|
217
215
|
|
|
218
216
|
if options[:fingerprint_alg]
|
|
@@ -224,7 +222,6 @@ module XMLSecurity
|
|
|
224
222
|
|
|
225
223
|
# check cert matches registered idp cert
|
|
226
224
|
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
|
227
|
-
@errors << "Fingerprint mismatch"
|
|
228
225
|
return append_error("Fingerprint mismatch", soft)
|
|
229
226
|
end
|
|
230
227
|
else
|
|
@@ -241,7 +238,7 @@ module XMLSecurity
|
|
|
241
238
|
validate_signature(base64_cert, soft)
|
|
242
239
|
end
|
|
243
240
|
|
|
244
|
-
def validate_document_with_cert(idp_cert)
|
|
241
|
+
def validate_document_with_cert(idp_cert, soft = true)
|
|
245
242
|
# get cert from response
|
|
246
243
|
cert_element = REXML::XPath.first(
|
|
247
244
|
self,
|
|
@@ -255,12 +252,12 @@ module XMLSecurity
|
|
|
255
252
|
begin
|
|
256
253
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
|
257
254
|
rescue OpenSSL::X509::CertificateError => _e
|
|
258
|
-
return append_error("Certificate Error", soft)
|
|
255
|
+
return append_error("Document Certificate Error", soft)
|
|
259
256
|
end
|
|
260
257
|
|
|
261
258
|
# check saml response cert matches provided idp cert
|
|
262
259
|
if idp_cert.to_pem != cert.to_pem
|
|
263
|
-
return
|
|
260
|
+
return append_error("Certificate of the Signature element does not match provided certificate", soft)
|
|
264
261
|
end
|
|
265
262
|
else
|
|
266
263
|
base64_cert = Base64.encode64(idp_cert.to_pem)
|
|
@@ -326,6 +323,9 @@ module XMLSecurity
|
|
|
326
323
|
'//ds:CanonicalizationMethod',
|
|
327
324
|
{ "ds" => DSIG }
|
|
328
325
|
)
|
|
326
|
+
|
|
327
|
+
canon_algorithm = process_transforms(ref, canon_algorithm)
|
|
328
|
+
|
|
329
329
|
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
|
|
330
330
|
|
|
331
331
|
digest_algorithm = algorithm(REXML::XPath.first(
|
|
@@ -342,7 +342,6 @@ module XMLSecurity
|
|
|
342
342
|
digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
|
|
343
343
|
|
|
344
344
|
unless digests_match?(hash, digest_value)
|
|
345
|
-
@errors << "Digest mismatch"
|
|
346
345
|
return append_error("Digest mismatch", soft)
|
|
347
346
|
end
|
|
348
347
|
|
|
@@ -360,6 +359,33 @@ module XMLSecurity
|
|
|
360
359
|
|
|
361
360
|
private
|
|
362
361
|
|
|
362
|
+
def process_transforms(ref, canon_algorithm)
|
|
363
|
+
transforms = REXML::XPath.match(
|
|
364
|
+
ref,
|
|
365
|
+
"//ds:Transforms/ds:Transform",
|
|
366
|
+
{ "ds" => DSIG }
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
transforms.each do |transform_element|
|
|
370
|
+
if transform_element.attributes && transform_element.attributes["Algorithm"]
|
|
371
|
+
algorithm = transform_element.attributes["Algorithm"]
|
|
372
|
+
case algorithm
|
|
373
|
+
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
|
|
374
|
+
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
|
|
375
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_1_0
|
|
376
|
+
when "http://www.w3.org/2006/12/xml-c14n11",
|
|
377
|
+
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
|
|
378
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_1_1
|
|
379
|
+
when "http://www.w3.org/2001/10/xml-exc-c14n#",
|
|
380
|
+
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
|
|
381
|
+
canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
canon_algorithm
|
|
387
|
+
end
|
|
388
|
+
|
|
363
389
|
def digests_match?(hash, digest_value)
|
|
364
390
|
hash == digest_value
|
|
365
391
|
end
|
data/ruby-saml.gemspec
CHANGED
|
@@ -15,14 +15,13 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
"LICENSE",
|
|
16
16
|
"README.md"
|
|
17
17
|
]
|
|
18
|
-
s.files = `git ls-files`.split("\
|
|
19
|
-
s.homepage = %q{
|
|
18
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
19
|
+
s.homepage = %q{https://github.com/onelogin/ruby-saml}
|
|
20
20
|
s.rdoc_options = ["--charset=UTF-8"]
|
|
21
21
|
s.require_paths = ["lib"]
|
|
22
22
|
s.rubygems_version = %q{1.3.7}
|
|
23
23
|
s.required_ruby_version = '>= 1.8.7'
|
|
24
24
|
s.summary = %q{SAML Ruby Tookit}
|
|
25
|
-
s.test_files = `git ls-files test/*`.split("\n")
|
|
26
25
|
|
|
27
26
|
# Because runtime dependencies are determined at build time, we cannot make
|
|
28
27
|
# Nokogiri's version dependent on the Ruby version, even though we would
|
|
@@ -31,6 +30,7 @@ Gem::Specification.new do |s|
|
|
|
31
30
|
if JRUBY_VERSION < '9.2.0.0'
|
|
32
31
|
s.add_runtime_dependency('nokogiri', '>= 1.8.2', '<= 1.8.5')
|
|
33
32
|
s.add_runtime_dependency('jruby-openssl', '>= 0.9.8')
|
|
33
|
+
s.add_runtime_dependency('json', '< 2.3.0')
|
|
34
34
|
else
|
|
35
35
|
s.add_runtime_dependency('nokogiri', '>= 1.8.2')
|
|
36
36
|
end
|
|
@@ -39,8 +39,12 @@ Gem::Specification.new do |s|
|
|
|
39
39
|
s.add_runtime_dependency('nokogiri', '<= 1.5.11')
|
|
40
40
|
elsif RUBY_VERSION < '2.1'
|
|
41
41
|
s.add_runtime_dependency('nokogiri', '>= 1.5.10', '<= 1.6.8.1')
|
|
42
|
+
s.add_runtime_dependency('json', '< 2.3.0')
|
|
43
|
+
elsif RUBY_VERSION < '2.3'
|
|
44
|
+
s.add_runtime_dependency('nokogiri', '>= 1.9.1', '< 1.10.0')
|
|
42
45
|
else
|
|
43
|
-
s.add_runtime_dependency('nokogiri', '>= 1.5
|
|
46
|
+
s.add_runtime_dependency('nokogiri', '>= 1.10.5')
|
|
47
|
+
s.add_runtime_dependency('rexml')
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
s.add_development_dependency('coveralls')
|
|
@@ -50,7 +54,12 @@ Gem::Specification.new do |s|
|
|
|
50
54
|
s.add_development_dependency('shoulda', '~> 2.11')
|
|
51
55
|
s.add_development_dependency('simplecov')
|
|
52
56
|
s.add_development_dependency('systemu', '~> 2')
|
|
53
|
-
|
|
57
|
+
|
|
58
|
+
if RUBY_VERSION < '2.1'
|
|
59
|
+
s.add_development_dependency('timecop', '<= 0.6.0')
|
|
60
|
+
else
|
|
61
|
+
s.add_development_dependency('timecop', '~> 0.9')
|
|
62
|
+
end
|
|
54
63
|
|
|
55
64
|
if defined?(JRUBY_VERSION)
|
|
56
65
|
# All recent versions of JRuby play well with pry
|