ruby-saml-mod 0.1.27 → 0.1.28
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 +4 -4
- data/lib/onelogin/saml/response.rb +72 -32
- data/lib/xml_sec.rb +46 -0
- data/ruby-saml-mod.gemspec +1 -1
- metadata +1 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 364210a5dc8d2c29558b9b0a19c258f64cdb1c9b
|
4
|
+
data.tar.gz: 5c389976863d3d599ea7fdcf1c91932f0418b169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b025fe482372d414d6fc263f5bbc2623e7070daa3d5fe8e2f6004b9e51494260f0746339d71791e7911c10be75b5f692069f2a98dc3d8e99985bf99c39cdab63
|
7
|
+
data.tar.gz: 5826d046b3eccb253f38611afde71d6e602778b1e998d3baf3c7ba1629505821b60bfc4687f8f9e5e63164a305b429adfd39b6ccbd2358450636161eb6a73cdd
|
@@ -2,7 +2,7 @@ module Onelogin::Saml
|
|
2
2
|
class Response
|
3
3
|
|
4
4
|
attr_accessor :settings
|
5
|
-
attr_reader :document, :
|
5
|
+
attr_reader :document, :xml, :response
|
6
6
|
attr_reader :name_id, :name_qualifier, :session_index, :saml_attributes
|
7
7
|
attr_reader :status_code, :status_message
|
8
8
|
attr_reader :in_response_to, :destination, :issuer
|
@@ -14,14 +14,14 @@ module Onelogin::Saml
|
|
14
14
|
@xml = Base64.decode64(@response)
|
15
15
|
@document = LibXML::XML::Document.string(@xml)
|
16
16
|
@document.extend(XMLSecurity::SignedDocument)
|
17
|
-
rescue
|
17
|
+
rescue
|
18
18
|
# could not parse document, everything is invalid
|
19
19
|
@response = nil
|
20
20
|
return
|
21
21
|
end
|
22
22
|
|
23
|
-
@issuer =
|
24
|
-
@status_code =
|
23
|
+
@issuer = document.find_first("/samlp:Response/saml:Issuer", Onelogin::NAMESPACES).content rescue nil
|
24
|
+
@status_code = document.find_first("/samlp:Response/samlp:Status/samlp:StatusCode", Onelogin::NAMESPACES)["Value"] rescue nil
|
25
25
|
|
26
26
|
process(settings) if settings
|
27
27
|
end
|
@@ -30,79 +30,119 @@ module Onelogin::Saml
|
|
30
30
|
@settings = settings
|
31
31
|
return unless @response
|
32
32
|
|
33
|
-
@
|
34
|
-
@
|
35
|
-
@
|
33
|
+
@in_response_to = untrusted_find_first("/samlp:Response")['InResponseTo'] rescue nil
|
34
|
+
@destination = untrusted_find_first("/samlp:Response")['Destination'] rescue nil
|
35
|
+
@status_message = untrusted_find_first("/samlp:Response/samlp:Status/samlp:StatusCode").content rescue nil
|
36
|
+
|
37
|
+
@name_id = trusted_find_first("saml:Assertion/saml:Subject/saml:NameID").content rescue nil
|
38
|
+
@name_qualifier = trusted_find_first("saml:Assertion/saml:Subject/saml:NameID")["NameQualifier"] rescue nil
|
39
|
+
@session_index = trusted_find_first("saml:Assertion/saml:AuthnStatement")["SessionIndex"] rescue nil
|
36
40
|
|
37
|
-
@in_response_to = @decrypted_document.find_first("/samlp:Response", Onelogin::NAMESPACES)['InResponseTo'] rescue nil
|
38
|
-
@destination = @decrypted_document.find_first("/samlp:Response", Onelogin::NAMESPACES)['Destination'] rescue nil
|
39
|
-
@name_id = @decrypted_document.find_first("/samlp:Response/saml:Assertion/saml:Subject/saml:NameID", Onelogin::NAMESPACES).content rescue nil
|
40
41
|
@saml_attributes = {}
|
41
|
-
|
42
|
+
trusted_find("saml:Attribute").each do |attr|
|
42
43
|
attrname = attr['FriendlyName'] || Onelogin::ATTRIBUTES[attr['Name']] || attr['Name']
|
43
44
|
@saml_attributes[attrname] = attr.content.strip rescue nil
|
44
45
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
end
|
47
|
+
|
48
|
+
def disable_signature_validation!(settings)
|
49
|
+
@settings = settings
|
50
|
+
@is_valid = true
|
51
|
+
@trusted_roots = [decrypted_document.root]
|
52
|
+
end
|
53
|
+
|
54
|
+
def decrypted_document
|
55
|
+
@decrypted_document ||= LibXML::XML::Document.document(document).tap do |doc|
|
56
|
+
doc.extend(XMLSecurity::SignedDocument)
|
57
|
+
doc.decrypt!(settings)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def untrusted_find_first(xpath)
|
62
|
+
decrypted_document.find(xpath, Onelogin::NAMESPACES).first
|
63
|
+
end
|
64
|
+
|
65
|
+
def trusted_find_first(xpath)
|
66
|
+
trusted_find(xpath).first
|
67
|
+
end
|
68
|
+
|
69
|
+
def trusted_find(xpath)
|
70
|
+
trusted_roots.map do |trusted_root|
|
71
|
+
trusted_root.find("descendant-or-self::#{xpath}", Onelogin::NAMESPACES).to_a
|
72
|
+
end.flatten.compact
|
48
73
|
end
|
49
74
|
|
50
75
|
def logger=(val)
|
51
76
|
@logger = val
|
52
77
|
end
|
53
|
-
|
78
|
+
|
54
79
|
def is_valid?
|
55
|
-
|
80
|
+
@is_valid ||= validate
|
81
|
+
end
|
82
|
+
|
83
|
+
def validate
|
84
|
+
if response.nil? || response == ""
|
56
85
|
@validation_error = "No response to validate"
|
57
86
|
return false
|
58
87
|
end
|
59
|
-
|
60
|
-
if
|
88
|
+
|
89
|
+
if !settings.idp_cert_fingerprint
|
61
90
|
@validation_error = "No fingerprint configured in SAML settings"
|
62
91
|
return false
|
63
92
|
end
|
64
|
-
|
93
|
+
|
65
94
|
# Verify the original document if it has a signature, otherwise verify the signature
|
66
95
|
# in the encrypted portion. If there is no signature, then we can't verify.
|
67
96
|
verified = false
|
68
|
-
|
69
|
-
|
97
|
+
|
98
|
+
if document.has_signature?
|
99
|
+
verified = document.validate(settings.idp_cert_fingerprint, @logger)
|
70
100
|
if !verified
|
71
|
-
@validation_error =
|
101
|
+
@validation_error = document.validation_error
|
72
102
|
return false
|
73
103
|
end
|
74
104
|
end
|
75
105
|
|
76
|
-
if !verified &&
|
77
|
-
verified =
|
106
|
+
if !verified && decrypted_document.has_signature?
|
107
|
+
verified = decrypted_document.validate(settings.idp_cert_fingerprint, @logger)
|
78
108
|
if !verified
|
79
|
-
@validation_error =
|
109
|
+
@validation_error = decrypted_document.validation_error
|
80
110
|
return false
|
81
111
|
end
|
82
112
|
end
|
83
|
-
|
113
|
+
|
84
114
|
if !verified
|
85
115
|
@validation_error = "No signature found in the response"
|
86
116
|
return false
|
87
117
|
end
|
88
|
-
|
118
|
+
|
119
|
+
# If we get here, validation has succeeded, and we can trust all
|
120
|
+
# <ds:Signature> elements. Each of those has a <ds:Reference> which
|
121
|
+
# points to the root of the root of the NodeSet it signs.
|
122
|
+
@trusted_roots = decrypted_document.signed_roots
|
123
|
+
|
89
124
|
true
|
90
125
|
end
|
91
|
-
|
126
|
+
|
127
|
+
# triggers validation
|
128
|
+
def trusted_roots
|
129
|
+
is_valid? ? @trusted_roots : []
|
130
|
+
end
|
131
|
+
|
92
132
|
def success_status?
|
93
133
|
@status_code == Onelogin::Saml::StatusCodes::SUCCESS_URI
|
94
134
|
end
|
95
|
-
|
135
|
+
|
96
136
|
def auth_failure?
|
97
137
|
@status_code == Onelogin::Saml::StatusCodes::AUTHN_FAILED_URI
|
98
138
|
end
|
99
|
-
|
139
|
+
|
100
140
|
def no_authn_context?
|
101
141
|
@status_code == Onelogin::Saml::StatusCodes::NO_AUTHN_CONTEXT_URI
|
102
142
|
end
|
103
|
-
|
143
|
+
|
104
144
|
def fingerprint_from_idp
|
105
|
-
if base64_cert =
|
145
|
+
if base64_cert = decrypted_document.find_first("//ds:X509Certificate", Onelogin::NAMESPACES)
|
106
146
|
cert_text = Base64.decode64(base64_cert.content)
|
107
147
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
108
148
|
Digest::SHA1.hexdigest(cert.to_der)
|
data/lib/xml_sec.rb
CHANGED
@@ -79,6 +79,9 @@ module XMLSecurity
|
|
79
79
|
:xmlSecDSigStatusInvalid
|
80
80
|
]
|
81
81
|
|
82
|
+
XMLSEC_ERRORS_R_INVALID_DATA = 12
|
83
|
+
class XmlSecError < ::RuntimeError; end
|
84
|
+
|
82
85
|
class XmlSecPtrList < FFI::Struct
|
83
86
|
layout \
|
84
87
|
:id, :string,
|
@@ -170,6 +173,12 @@ module XMLSecurity
|
|
170
173
|
:reserved1, :pointer
|
171
174
|
end
|
172
175
|
|
176
|
+
ErrorCallback = FFI::Function.new(:void,
|
177
|
+
[ :string, :int, :string, :string, :string, :int, :string ]
|
178
|
+
) do |file, line, func, errorObject, errorSubject, reason, msg |
|
179
|
+
XMLSecurity.handle_xmlsec_error_callback(file, line, func, errorObject, errorSubject, reason, msg)
|
180
|
+
end
|
181
|
+
|
173
182
|
# xmlsec functions
|
174
183
|
attach_function :xmlSecInit, [], :int
|
175
184
|
attach_function :xmlSecParseMemory, [ :pointer, :uint, :int ], :pointer
|
@@ -193,7 +202,9 @@ module XMLSecurity
|
|
193
202
|
attach_function :xmlSecEncCtxDecrypt, [ :pointer, :pointer ], :int
|
194
203
|
attach_function :xmlSecEncCtxDestroy, [ :pointer ], :void
|
195
204
|
|
205
|
+
attach_function :xmlSecErrorsDefaultCallback, [ :string, :int, :string, :string, :string, :int, :string ], :void
|
196
206
|
attach_function :xmlSecErrorsDefaultCallbackEnableOutput, [ :bool ], :void
|
207
|
+
attach_function :xmlSecErrorsSetCallback, [:pointer], :void
|
197
208
|
|
198
209
|
attach_function :xmlSecTransformExclC14NGetKlass, [], :pointer
|
199
210
|
attach_function :xmlSecOpenSSLTransformRsaSha1GetKlass, [], :pointer
|
@@ -238,6 +249,18 @@ module XMLSecurity
|
|
238
249
|
raise "Failed initializing XMLSec" if self.xmlSecInit < 0
|
239
250
|
raise "Failed initializing app crypto" if self.xmlSecOpenSSLAppInit(nil) < 0
|
240
251
|
raise "Failed initializing crypto" if self.xmlSecOpenSSLInit < 0
|
252
|
+
self.xmlSecErrorsSetCallback(ErrorCallback)
|
253
|
+
|
254
|
+
def self.handle_xmlsec_error_callback(*args)
|
255
|
+
raise_exception_if_necessary(*args)
|
256
|
+
xmlSecErrorsDefaultCallback(*args)
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.raise_exception_if_necessary(file, line, func, errorObject, errorSubject, reason, msg)
|
260
|
+
if reason == XMLSEC_ERRORS_R_INVALID_DATA
|
261
|
+
raise XmlSecError.new(msg)
|
262
|
+
end
|
263
|
+
end
|
241
264
|
|
242
265
|
|
243
266
|
def self.mute(&block)
|
@@ -267,6 +290,29 @@ module XMLSecurity
|
|
267
290
|
"-----BEGIN PUBLIC KEY-----\n#{base64}-----END PUBLIC KEY-----"
|
268
291
|
end
|
269
292
|
|
293
|
+
def has_signature?
|
294
|
+
signatures.any?
|
295
|
+
end
|
296
|
+
|
297
|
+
def signed_roots
|
298
|
+
signatures.map do |sig|
|
299
|
+
ref = sig.find('.//ds:Reference', Onelogin::NAMESPACES).first
|
300
|
+
signed_element_id = ref['URI'].sub(/^#/, '')
|
301
|
+
|
302
|
+
if signed_element_id.empty?
|
303
|
+
self.root
|
304
|
+
else
|
305
|
+
xpath_id_query = %Q(ancestor::*[@ID = "#{signed_element_id}"])
|
306
|
+
|
307
|
+
ref.find(xpath_id_query, Onelogin::NAMESPACES).first
|
308
|
+
end
|
309
|
+
end.compact
|
310
|
+
end
|
311
|
+
|
312
|
+
def signatures
|
313
|
+
@signatures ||= self.find("//ds:Signature", Onelogin::NAMESPACES)
|
314
|
+
end
|
315
|
+
|
270
316
|
def validate(idp_cert_fingerprint, logger = nil)
|
271
317
|
# get cert from response
|
272
318
|
base64_cert = self.find_first("//ds:X509Certificate", Onelogin::NAMESPACES).content
|
data/ruby-saml-mod.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-saml-mod
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OneLogin LLC
|
@@ -89,4 +89,3 @@ signing_key:
|
|
89
89
|
specification_version: 4
|
90
90
|
summary: Ruby library for SAML service providers
|
91
91
|
test_files: []
|
92
|
-
has_rdoc:
|