ruby-saml-mod 0.1.27 → 0.1.28
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|