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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fb25d67d2b17dd051b1b4ced52bbbfc8fac8c3f
4
- data.tar.gz: e1576c19390be8d294128d1a5d3ae60a6d7813d2
3
+ metadata.gz: 364210a5dc8d2c29558b9b0a19c258f64cdb1c9b
4
+ data.tar.gz: 5c389976863d3d599ea7fdcf1c91932f0418b169
5
5
  SHA512:
6
- metadata.gz: 96dadc807bb4f4b26c356f991452ee26b2b54251352d996d65f47256621c79a07a1a4c0690505b704d368311bc8ba2951500b66dd65b1472118738671ea103eb
7
- data.tar.gz: e83f4fa8df332d3301898811e00a8952d6b91099b6a9d1d54439ff639c4e300ac5fb518d9c073b22145c93b4e8ab43e8fed8052dc20791e66cb164b68bae7df2
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, :decrypted_document, :xml, :response
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 => e
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 = @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
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
- @decrypted_document = LibXML::XML::Document.document(@document)
34
- @decrypted_document.extend(XMLSecurity::SignedDocument)
35
- @decrypted_document.decrypt!(@settings)
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
- @decrypted_document.find("//saml:Attribute", Onelogin::NAMESPACES).each do |attr|
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
- @name_qualifier = @decrypted_document.find_first("/samlp:Response/saml:Assertion/saml:Subject/saml:NameID", Onelogin::NAMESPACES)["NameQualifier"] rescue nil
46
- @session_index = @decrypted_document.find_first("/samlp:Response/saml:Assertion/saml:AuthnStatement", Onelogin::NAMESPACES)["SessionIndex"] rescue nil
47
- @status_message = @decrypted_document.find_first("/samlp:Response/samlp:Status/samlp:StatusCode", Onelogin::NAMESPACES).content rescue nil
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
- if @response.nil? || @response == ""
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 !@settings.idp_cert_fingerprint
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
- if @document.find_first("//ds:Signature", Onelogin::NAMESPACES)
69
- verified = @document.validate(@settings.idp_cert_fingerprint, @logger)
97
+
98
+ if document.has_signature?
99
+ verified = document.validate(settings.idp_cert_fingerprint, @logger)
70
100
  if !verified
71
- @validation_error = @document.validation_error
101
+ @validation_error = document.validation_error
72
102
  return false
73
103
  end
74
104
  end
75
105
 
76
- if !verified && @decrypted_document.find_first("//ds:Signature", Onelogin::NAMESPACES)
77
- verified = @decrypted_document.validate(@settings.idp_cert_fingerprint, @logger)
106
+ if !verified && decrypted_document.has_signature?
107
+ verified = decrypted_document.validate(settings.idp_cert_fingerprint, @logger)
78
108
  if !verified
79
- @validation_error = @document.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 = @decrypted_document.find_first("//ds:X509Certificate", Onelogin::NAMESPACES)
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)
@@ -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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{ruby-saml-mod}
3
- s.version = "0.1.27"
3
+ s.version = "0.1.28"
4
4
 
5
5
  s.authors = ["OneLogin LLC", "Bracken", "Zach", "Cody", "Jeremy", "Paul"]
6
6
  s.date = %q{2014-05-05}
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.27
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: