saml_idp 0.7.2 → 1.0.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.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +1 -1
  3. data/README.md +71 -55
  4. data/lib/saml_idp/assertion_builder.rb +28 -3
  5. data/lib/saml_idp/configurator.rb +9 -3
  6. data/lib/saml_idp/controller.rb +27 -16
  7. data/lib/saml_idp/encryptor.rb +0 -1
  8. data/lib/saml_idp/fingerprint.rb +19 -0
  9. data/lib/saml_idp/incoming_metadata.rb +31 -1
  10. data/lib/saml_idp/metadata_builder.rb +25 -9
  11. data/lib/saml_idp/persisted_metadata.rb +4 -0
  12. data/lib/saml_idp/request.rb +103 -13
  13. data/lib/saml_idp/response_builder.rb +26 -6
  14. data/lib/saml_idp/saml_response.rb +62 -28
  15. data/lib/saml_idp/service_provider.rb +16 -6
  16. data/lib/saml_idp/signable.rb +1 -2
  17. data/lib/saml_idp/signature_builder.rb +2 -1
  18. data/lib/saml_idp/signed_info_builder.rb +2 -2
  19. data/lib/saml_idp/version.rb +1 -1
  20. data/lib/saml_idp/xml_security.rb +20 -15
  21. data/lib/saml_idp.rb +4 -3
  22. data/saml_idp.gemspec +46 -42
  23. data/spec/acceptance/idp_controller_spec.rb +5 -4
  24. data/spec/lib/saml_idp/algorithmable_spec.rb +6 -6
  25. data/spec/lib/saml_idp/assertion_builder_spec.rb +151 -8
  26. data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
  27. data/spec/lib/saml_idp/configurator_spec.rb +45 -7
  28. data/spec/lib/saml_idp/controller_spec.rb +86 -25
  29. data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
  30. data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
  31. data/spec/lib/saml_idp/incoming_metadata_spec.rb +134 -0
  32. data/spec/lib/saml_idp/metadata_builder_spec.rb +30 -17
  33. data/spec/lib/saml_idp/name_id_formatter_spec.rb +3 -3
  34. data/spec/lib/saml_idp/request_spec.rb +153 -64
  35. data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
  36. data/spec/lib/saml_idp/saml_response_spec.rb +146 -12
  37. data/spec/lib/saml_idp/service_provider_spec.rb +2 -2
  38. data/spec/lib/saml_idp/signable_spec.rb +1 -1
  39. data/spec/lib/saml_idp/signature_builder_spec.rb +2 -2
  40. data/spec/lib/saml_idp/signed_info_builder_spec.rb +3 -3
  41. data/spec/rails_app/app/controllers/saml_controller.rb +1 -1
  42. data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
  43. data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +3 -4
  44. data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
  45. data/spec/rails_app/config/application.rb +1 -6
  46. data/spec/rails_app/config/boot.rb +1 -1
  47. data/spec/rails_app/config/environments/development.rb +2 -5
  48. data/spec/rails_app/config/environments/production.rb +1 -0
  49. data/spec/rails_app/config/environments/test.rb +1 -0
  50. data/spec/spec_helper.rb +23 -1
  51. data/spec/support/certificates/sp_cert_req.csr +12 -0
  52. data/spec/support/certificates/sp_private_key.pem +16 -0
  53. data/spec/support/certificates/sp_x509_cert.crt +18 -0
  54. data/spec/support/saml_request_macros.rb +107 -5
  55. data/spec/support/security_helpers.rb +12 -2
  56. data/spec/xml_security_spec.rb +19 -15
  57. metadata +146 -80
  58. data/app/controllers/saml_idp/idp_controller.rb +0 -59
  59. data/spec/lib/saml_idp/.assertion_builder_spec.rb.swp +0 -0
@@ -1,8 +1,11 @@
1
1
  require 'saml_idp/xml_security'
2
2
  require 'saml_idp/service_provider'
3
+ require 'logger'
3
4
  module SamlIdp
4
5
  class Request
5
- def self.from_deflated_request(raw)
6
+ attr_accessor :errors
7
+
8
+ def self.from_deflated_request(raw, external_attributes = {})
6
9
  if raw
7
10
  decoded = Base64.decode64(raw)
8
11
  zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
@@ -17,18 +20,23 @@ module SamlIdp
17
20
  else
18
21
  inflated = ""
19
22
  end
20
- new(inflated)
23
+ new(inflated, external_attributes)
21
24
  end
22
25
 
23
- attr_accessor :raw_xml
26
+ attr_accessor :raw_xml, :saml_request, :signature, :sig_algorithm, :relay_state
24
27
 
25
28
  delegate :config, to: :SamlIdp
26
29
  private :config
27
30
  delegate :xpath, to: :document
28
31
  private :xpath
29
32
 
30
- def initialize(raw_xml = "")
33
+ def initialize(raw_xml = "", external_attributes = {})
31
34
  self.raw_xml = raw_xml
35
+ self.saml_request = external_attributes[:saml_request]
36
+ self.relay_state = external_attributes[:relay_state]
37
+ self.sig_algorithm = external_attributes[:sig_algorithm]
38
+ self.signature = external_attributes[:signature]
39
+ self.errors = []
32
40
  end
33
41
 
34
42
  def logout_request?
@@ -77,31 +85,60 @@ module SamlIdp
77
85
  end
78
86
 
79
87
  def log(msg)
80
- if defined?(::Rails) && Rails.logger
81
- Rails.logger.info msg
88
+ if config.logger.respond_to?(:call)
89
+ config.logger.call msg
82
90
  else
83
- puts msg
91
+ config.logger.info msg
84
92
  end
85
93
  end
86
94
 
87
- def valid?
95
+ def collect_errors(error_type)
96
+ errors.push(error_type)
97
+ end
98
+
99
+ def valid?(external_attributes = {})
88
100
  unless service_provider?
89
101
  log "Unable to find service provider for issuer #{issuer}"
102
+ collect_errors(:sp_not_found)
90
103
  return false
91
104
  end
92
105
 
93
106
  unless (authn_request? ^ logout_request?)
94
107
  log "One and only one of authnrequest and logout request is required. authnrequest: #{authn_request?} logout_request: #{logout_request?} "
108
+ collect_errors(:unaccepted_request)
109
+ return false
110
+ end
111
+
112
+ if (logout_request? || validate_auth_request_signature?) && (service_provider.cert.to_s.empty? || !!service_provider.fingerprint.to_s.empty?)
113
+ log "Verifying request signature is required. But certificate and fingerprint was empty."
114
+ collect_errors(:empty_certificate)
95
115
  return false
96
116
  end
97
117
 
98
- unless valid_signature?
99
- log "Signature is invalid in #{raw_xml}"
118
+ # XML embedded signature
119
+ if signature.nil? && !valid_signature?
120
+ log "Requested document signature is invalid in #{raw_xml}"
121
+ collect_errors(:invalid_embedded_signature)
122
+ return false
123
+ end
124
+
125
+ # URI query signature
126
+ if signature.present? && !valid_external_signature?
127
+ log "Requested URI signature is invalid in #{raw_xml}"
128
+ collect_errors(:invalid_external_signature)
100
129
  return false
101
130
  end
102
131
 
103
132
  if response_url.nil?
104
133
  log "Unable to find response url for #{issuer}: #{raw_xml}"
134
+ collect_errors(:empty_response_url)
135
+ return false
136
+ end
137
+
138
+ if !service_provider.acceptable_response_hosts.include?(response_host)
139
+ log "#{service_provider.acceptable_response_hosts} compare to #{response_host}"
140
+ log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host"
141
+ collect_errors(:not_allowed_host)
105
142
  return false
106
143
  end
107
144
 
@@ -109,9 +146,38 @@ module SamlIdp
109
146
  end
110
147
 
111
148
  def valid_signature?
112
- # Force signatures for logout requests because there is no other
113
- # protection against a cross-site DoS.
114
- service_provider.valid_signature?(document, logout_request?)
149
+ # Force signatures for logout requests because there is no other protection against a cross-site DoS.
150
+ if logout_request? || authn_request? && validate_auth_request_signature?
151
+ document.valid_signature?(service_provider.cert, service_provider.fingerprint)
152
+ else
153
+ true
154
+ end
155
+ end
156
+
157
+ def valid_external_signature?
158
+ return true if authn_request? && !validate_auth_request_signature?
159
+
160
+ cert = OpenSSL::X509::Certificate.new(service_provider.cert)
161
+
162
+ sha_version = sig_algorithm =~ /sha(.*?)$/i && $1.to_i
163
+ raw_signature = Base64.decode64(signature)
164
+
165
+ signature_algorithm = case sha_version
166
+ when 256 then OpenSSL::Digest::SHA256
167
+ when 384 then OpenSSL::Digest::SHA384
168
+ when 512 then OpenSSL::Digest::SHA512
169
+ else
170
+ OpenSSL::Digest::SHA1
171
+ end
172
+
173
+ result = cert.public_key.verify(signature_algorithm.new, raw_signature, query_request_string)
174
+ # Match all percent-encoded sequences (e.g., %20, %2B) and convert them to lowercase
175
+ # Upper case is recommended for consistency but some services such as MS Entra Id not follows it
176
+ # https://datatracker.ietf.org/doc/html/rfc3986#section-2.1
177
+ result || cert.public_key.verify(signature_algorithm.new, raw_signature, query_request_string.gsub(/%[A-F0-9]{2}/) { |match| match.downcase })
178
+ rescue OpenSSL::X509::CertificateError => e
179
+ log e.message
180
+ collect_errors(:cert_format_error)
115
181
  end
116
182
 
117
183
  def service_provider?
@@ -136,6 +202,21 @@ module SamlIdp
136
202
  @_session_index ||= xpath("//samlp:SessionIndex", samlp: samlp).first.try(:content)
137
203
  end
138
204
 
205
+ def query_request_string
206
+ url_string = "SAMLRequest=#{CGI.escape(saml_request)}"
207
+ url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
208
+ url_string << "&SigAlg=#{CGI.escape(sig_algorithm)}"
209
+ end
210
+ private :query_request_string
211
+
212
+ def response_host
213
+ uri = URI(response_url)
214
+ if uri
215
+ uri.host
216
+ end
217
+ end
218
+ private :response_host
219
+
139
220
  def document
140
221
  @_document ||= Saml::XML::Document.parse(raw_xml)
141
222
  end
@@ -177,5 +258,14 @@ module SamlIdp
177
258
  config.service_provider.finder
178
259
  end
179
260
  private :service_provider_finder
261
+
262
+ def validate_auth_request_signature?
263
+ # Validate signature when metadata specify AuthnRequest should be signed
264
+ metadata = service_provider.current_metadata
265
+ sign_authn_request = metadata.respond_to?(:sign_authn_request?) && metadata.sign_authn_request?
266
+ sign_authn_request = service_provider.sign_authn_request unless service_provider.sign_authn_request.nil?
267
+ sign_authn_request
268
+ end
269
+ private :validate_auth_request_signature?
180
270
  end
181
271
  end
@@ -1,32 +1,45 @@
1
1
  require 'builder'
2
+ require 'saml_idp/algorithmable'
3
+ require 'saml_idp/signable'
2
4
  module SamlIdp
3
5
  class ResponseBuilder
6
+ include Algorithmable
7
+ include Signable
4
8
  attr_accessor :response_id
5
9
  attr_accessor :issuer_uri
6
10
  attr_accessor :saml_acs_url
7
11
  attr_accessor :saml_request_id
8
12
  attr_accessor :assertion_and_signature
13
+ attr_accessor :raw_algorithm
9
14
 
10
- def initialize(response_id, issuer_uri, saml_acs_url, saml_request_id, assertion_and_signature)
15
+ alias_method :reference_id, :response_id
16
+
17
+ def initialize(response_id, issuer_uri, saml_acs_url, saml_request_id, assertion_and_signature, raw_algorithm)
11
18
  self.response_id = response_id
12
19
  self.issuer_uri = issuer_uri
13
20
  self.saml_acs_url = saml_acs_url
14
21
  self.saml_request_id = saml_request_id
15
22
  self.assertion_and_signature = assertion_and_signature
23
+ self.raw_algorithm = raw_algorithm
16
24
  end
17
25
 
18
- def encoded
19
- @encoded ||= encode
26
+ def encoded(signed_message: false, compress: false)
27
+ @encoded ||= signed_message ? encode_signed_message(compress) : encode_raw_message(compress)
20
28
  end
21
29
 
22
30
  def raw
23
31
  build
24
32
  end
25
33
 
26
- def encode
27
- Base64.strict_encode64(raw)
34
+ def encode_raw_message(compress)
35
+ Base64.strict_encode64(compress ? deflate(raw) : raw)
36
+ end
37
+ private :encode_raw_message
38
+
39
+ def encode_signed_message(compress)
40
+ Base64.strict_encode64(compress ? deflate(signed) : signed)
28
41
  end
29
- private :encode
42
+ private :encode_signed_message
30
43
 
31
44
  def build
32
45
  resp_options = {}
@@ -41,6 +54,7 @@ module SamlIdp
41
54
  builder = Builder::XmlMarkup.new
42
55
  builder.tag! "samlp:Response", resp_options do |response|
43
56
  response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
57
+ sign response
44
58
  response.tag! "samlp:Status" do |status|
45
59
  status.tag! "samlp:StatusCode", Value: Saml::XML::Namespaces::Statuses::SUCCESS
46
60
  end
@@ -52,11 +66,17 @@ module SamlIdp
52
66
  def response_id_string
53
67
  "_#{response_id}"
54
68
  end
69
+ alias_method :reference_id, :response_id
55
70
  private :response_id_string
56
71
 
57
72
  def now_iso
58
73
  Time.now.utc.iso8601
59
74
  end
60
75
  private :now_iso
76
+
77
+ def deflate(inflated)
78
+ Zlib::Deflate.deflate(inflated, 9)[2..-5]
79
+ end
80
+ private :deflate
61
81
  end
62
82
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml_idp/assertion_builder'
2
4
  require 'saml_idp/response_builder'
3
5
  module SamlIdp
4
6
  class SamlResponse
5
- attr_accessor :assertion_with_signature
6
7
  attr_accessor :reference_id
7
8
  attr_accessor :response_id
8
9
  attr_accessor :issuer_uri
@@ -17,20 +18,32 @@ module SamlIdp
17
18
  attr_accessor :expiry
18
19
  attr_accessor :encryption_opts
19
20
  attr_accessor :session_expiry
21
+ attr_accessor :name_id_formats_opts
22
+ attr_accessor :asserted_attributes_opts
23
+ attr_accessor :signed_message_opts
24
+ attr_accessor :signed_assertion_opts
25
+ attr_accessor :compression_opts
26
+
27
+ def initialize(
28
+ reference_id,
29
+ response_id,
30
+ issuer_uri,
31
+ principal,
32
+ audience_uri,
33
+ saml_request_id,
34
+ saml_acs_url,
35
+ algorithm,
36
+ authn_context_classref,
37
+ expiry = 60 * 60,
38
+ encryption_opts = nil,
39
+ session_expiry = 0,
40
+ name_id_formats_opts = nil,
41
+ asserted_attributes_opts = nil,
42
+ signed_message_opts = false,
43
+ signed_assertion_opts = true,
44
+ compression_opts = false
45
+ )
20
46
 
21
- def initialize(reference_id,
22
- response_id,
23
- issuer_uri,
24
- principal,
25
- audience_uri,
26
- saml_request_id,
27
- saml_acs_url,
28
- algorithm,
29
- authn_context_classref,
30
- expiry=60*60,
31
- encryption_opts=nil,
32
- session_expiry=0
33
- )
34
47
  self.reference_id = reference_id
35
48
  self.response_id = response_id
36
49
  self.issuer_uri = issuer_uri
@@ -45,38 +58,59 @@ module SamlIdp
45
58
  self.expiry = expiry
46
59
  self.encryption_opts = encryption_opts
47
60
  self.session_expiry = session_expiry
61
+ self.signed_message_opts = signed_message_opts
62
+ self.name_id_formats_opts = name_id_formats_opts
63
+ self.asserted_attributes_opts = asserted_attributes_opts
64
+ self.signed_assertion_opts = signed_assertion_opts
65
+ self.name_id_formats_opts = name_id_formats_opts
66
+ self.asserted_attributes_opts = asserted_attributes_opts
67
+ self.compression_opts = compression_opts
48
68
  end
49
69
 
50
70
  def build
51
- @built ||= response_builder.encoded
71
+ @build ||= encoded_message
52
72
  end
53
73
 
54
74
  def signed_assertion
55
75
  if encryption_opts
56
76
  assertion_builder.encrypt(sign: true)
57
- else
77
+ elsif signed_assertion_opts
58
78
  assertion_builder.signed
79
+ else
80
+ assertion_builder.raw
59
81
  end
60
82
  end
61
83
  private :signed_assertion
62
84
 
85
+ def encoded_message
86
+ if signed_message_opts
87
+ response_builder.encoded(signed_message: true, compress: compression_opts)
88
+ else
89
+ response_builder.encoded(signed_message: false, compress: compression_opts)
90
+ end
91
+ end
92
+ private :encoded_message
93
+
63
94
  def response_builder
64
- ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion)
95
+ ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion, algorithm)
65
96
  end
66
97
  private :response_builder
67
98
 
68
99
  def assertion_builder
69
- @assertion_builder ||= AssertionBuilder.new reference_id,
70
- issuer_uri,
71
- principal,
72
- audience_uri,
73
- saml_request_id,
74
- saml_acs_url,
75
- algorithm,
76
- authn_context_classref,
77
- expiry,
78
- encryption_opts,
79
- session_expiry
100
+ @assertion_builder ||=
101
+ AssertionBuilder.new(reference_id || SecureRandom.uuid,
102
+ issuer_uri,
103
+ principal,
104
+ audience_uri,
105
+ saml_request_id,
106
+ saml_acs_url,
107
+ algorithm,
108
+ authn_context_classref,
109
+ expiry,
110
+ encryption_opts,
111
+ session_expiry,
112
+ name_id_formats_opts,
113
+ asserted_attributes_opts)
80
114
  end
81
115
  private :assertion_builder
82
116
  end
@@ -11,8 +11,10 @@ module SamlIdp
11
11
  attribute :fingerprint
12
12
  attribute :metadata_url
13
13
  attribute :validate_signature
14
+ attribute :sign_authn_request
14
15
  attribute :acs_url
15
16
  attribute :assertion_consumer_logout_service_url
17
+ attribute :response_hosts
16
18
 
17
19
  delegate :config, to: :SamlIdp
18
20
 
@@ -21,18 +23,13 @@ module SamlIdp
21
23
  end
22
24
 
23
25
  def valid_signature?(doc, require_signature = false)
24
- if require_signature || should_validate_signature?
26
+ if require_signature || attributes[:validate_signature]
25
27
  doc.valid_signature?(fingerprint)
26
28
  else
27
29
  true
28
30
  end
29
31
  end
30
32
 
31
- def should_validate_signature?
32
- attributes[:validate_signature] ||
33
- current_metadata.respond_to?(:sign_assertions?) && current_metadata.sign_assertions?
34
- end
35
-
36
33
  def refresh_metadata
37
34
  fresh = fresh_incoming_metadata
38
35
  if valid_signature?(fresh.document)
@@ -46,6 +43,19 @@ module SamlIdp
46
43
  @current_metadata ||= get_current_or_build
47
44
  end
48
45
 
46
+ def acceptable_response_hosts
47
+ hosts = Array(self.response_hosts)
48
+ hosts.push(metadata_url_host) if metadata_url_host
49
+
50
+ hosts
51
+ end
52
+
53
+ def metadata_url_host
54
+ if metadata_url.present?
55
+ URI(metadata_url).host
56
+ end
57
+ end
58
+
49
59
  def get_current_or_build
50
60
  persisted = metadata_getter[identifier, self]
51
61
  if persisted.is_a? Hash
@@ -108,8 +108,7 @@ module SamlIdp
108
108
  canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
109
109
  canon_hashed_element = noko_raw.canonicalize(canon_algorithm, inclusive_namespaces)
110
110
  digest_algorithm = get_algorithm
111
-
112
- hash = digest_algorithm.digest(canon_hashed_element)
111
+ hash = digest_algorithm.digest(canon_hashed_element)
113
112
  Base64.strict_encode64(hash).gsub(/\n/, '')
114
113
  end
115
114
  private :digest
@@ -21,7 +21,8 @@ module SamlIdp
21
21
  end
22
22
 
23
23
  def x509_certificate
24
- SamlIdp.config.x509_certificate
24
+ certificate = SamlIdp.config.x509_certificate.is_a?(Proc) ? SamlIdp.config.x509_certificate.call : SamlIdp.config.x509_certificate
25
+ certificate
25
26
  .to_s
26
27
  .gsub(/-----BEGIN CERTIFICATE-----/,"")
27
28
  .gsub(/-----END CERTIFICATE-----/,"")
@@ -65,12 +65,12 @@ module SamlIdp
65
65
  private :clean_algorithm_name
66
66
 
67
67
  def secret_key
68
- SamlIdp.config.secret_key
68
+ SamlIdp.config.secret_key.is_a?(Proc) ? SamlIdp.config.secret_key.call : SamlIdp.config.secret_key
69
69
  end
70
70
  private :secret_key
71
71
 
72
72
  def password
73
- SamlIdp.config.password
73
+ SamlIdp.config.password.is_a?(Proc) ? SamlIdp.config.password.call : SamlIdp.config.password
74
74
  end
75
75
  private :password
76
76
 
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module SamlIdp
3
- VERSION = '0.7.2'
3
+ VERSION = '1.0.0'
4
4
  end
@@ -43,24 +43,29 @@ module SamlIdp
43
43
  extract_signed_element_id
44
44
  end
45
45
 
46
- def validate(idp_cert_fingerprint, soft = true)
46
+ def validate(idp_base64_cert, idp_cert_fingerprint, soft = true)
47
47
  # get cert from response
48
48
  cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
49
- raise ValidationError.new("Certificate element missing in response (ds:X509Certificate)") unless cert_element
50
- base64_cert = cert_element.text
51
- cert_text = Base64.decode64(base64_cert)
52
- cert = OpenSSL::X509::Certificate.new(cert_text)
53
-
54
- # check cert matches registered idp cert
55
- fingerprint = fingerprint_cert(cert)
56
- sha1_fingerprint = fingerprint_cert_sha1(cert)
57
- plain_idp_cert_fingerprint = idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
58
-
59
- if fingerprint != plain_idp_cert_fingerprint && sha1_fingerprint != plain_idp_cert_fingerprint
60
- return soft ? false : (raise ValidationError.new("Fingerprint mismatch"))
49
+ if cert_element
50
+ idp_base64_cert = cert_element.text
51
+ cert_text = Base64.decode64(idp_base64_cert)
52
+ cert = OpenSSL::X509::Certificate.new(cert_text)
53
+
54
+ # check cert matches registered idp cert
55
+ fingerprint = fingerprint_cert(cert)
56
+ sha1_fingerprint = fingerprint_cert_sha1(cert)
57
+ plain_idp_cert_fingerprint = idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
58
+
59
+ if fingerprint != plain_idp_cert_fingerprint && sha1_fingerprint != plain_idp_cert_fingerprint
60
+ return soft ? false : (raise ValidationError.new("Fingerprint mismatch"))
61
+ end
62
+ end
63
+
64
+ if idp_base64_cert.nil? || idp_base64_cert.empty?
65
+ raise ValidationError.new("Certificate validation is required, but it doesn't exist.")
61
66
  end
62
67
 
63
- validate_doc(base64_cert, soft)
68
+ validate_doc(idp_base64_cert, soft)
64
69
  end
65
70
 
66
71
  def fingerprint_cert(cert)
@@ -108,7 +113,7 @@ module SamlIdp
108
113
  canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG)
109
114
  canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
110
115
 
111
- digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod"))
116
+ digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod", {'ds' => DSIG}))
112
117
 
113
118
  hash = digest_algorithm.digest(canon_hashed_element)
114
119
  digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
data/lib/saml_idp.rb CHANGED
@@ -8,7 +8,8 @@ module SamlIdp
8
8
  require 'saml_idp/default'
9
9
  require 'saml_idp/metadata_builder'
10
10
  require 'saml_idp/version'
11
- require 'saml_idp/engine' if defined?(::Rails) && Rails::VERSION::MAJOR > 2
11
+ require 'saml_idp/fingerprint'
12
+ require 'saml_idp/engine' if defined?(::Rails::Engine)
12
13
 
13
14
  def self.config
14
15
  @config ||= SamlIdp::Configurator.new
@@ -69,9 +70,9 @@ module Saml
69
70
  !!xpath("//ds:Signature", ds: signature_namespace).first
70
71
  end
71
72
 
72
- def valid_signature?(fingerprint)
73
+ def valid_signature?(certificate, fingerprint)
73
74
  signed? &&
74
- signed_document.validate(fingerprint, :soft)
75
+ signed_document.validate(certificate, fingerprint, :soft)
75
76
  end
76
77
 
77
78
  def signed_document
data/saml_idp.gemspec CHANGED
@@ -1,59 +1,63 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "saml_idp/version"
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'saml_idp/version'
4
5
 
5
6
  Gem::Specification.new do |s|
6
7
  s.name = %q{saml_idp}
7
8
  s.version = SamlIdp::VERSION
8
9
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Jon Phenow"]
10
- s.email = %q{jon.phenow@sportngin.com}
11
- s.homepage = %q{http://github.com/sportngin/saml_idp}
12
- s.summary = %q{SAML Indentity Provider in ruby}
13
- s.description = %q{SAML IdP (Identity Provider) library in ruby}
14
- s.date = Time.now.utc.strftime("%Y-%m-%d")
15
- s.files = Dir.glob("app/**/*") + Dir.glob("lib/**/*") + [
16
- "LICENSE",
17
- "README.md",
18
- "Gemfile",
19
- "saml_idp.gemspec"
20
- ]
21
- s.required_ruby_version = '>= 2.2'
22
- s.license = "LICENSE"
10
+ s.authors = ['Jon Phenow']
11
+ s.email = 'jon.phenow@sportngin.com'
12
+ s.homepage = 'https://github.com/saml-idp/saml_idp'
13
+ s.summary = 'SAML Identity Provider for Ruby'
14
+ s.description = 'SAML IdP (Identity Provider) Library for Ruby'
15
+ s.date = Time.now.utc.strftime('%Y-%m-%d')
16
+ s.files = Dir['lib/**/*', 'LICENSE', 'README.md', 'Gemfile', 'saml_idp.gemspec']
17
+ s.required_ruby_version = '>= 2.5'
18
+ s.license = 'MIT'
23
19
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
20
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
- s.require_paths = ["lib"]
26
- s.rdoc_options = ["--charset=UTF-8"]
21
+ s.require_paths = ['lib']
22
+ s.rdoc_options = ['--charset=UTF-8']
23
+ s.metadata = {
24
+ 'homepage_uri' => 'https://github.com/saml-idp/saml_idp',
25
+ 'source_code_uri' => 'https://github.com/saml-idp/saml_idp',
26
+ 'bug_tracker_uri' => 'https://github.com/saml-idp/saml_idp/issues',
27
+ 'documentation_uri' => "http://rdoc.info/gems/saml_idp/#{SamlIdp::VERSION}"
28
+ }
27
29
 
28
30
  s.post_install_message = <<-INST
29
- If you're just recently updating saml_idp - please be aware we've changed the default
30
- certificate. See the PR and a description of why we've done this here:
31
- https://github.com/sportngin/saml_idp/pull/29
32
-
33
- If you just need to see the certificate `bundle open saml_idp` and go to
34
- `lib/saml_idp/default.rb`
31
+ If you're just recently updating saml_idp - please be aware we've changed the default
32
+ certificate. See the PR and a description of why we've done this here:
33
+ https://github.com/saml-idp/saml_idp/pull/29
35
34
 
36
- Similarly, please see the README about certificates - you should avoid using the
37
- defaults in a Production environment. Post any issues you to github.
35
+ If you just need to see the certificate `bundle open saml_idp` and go to
36
+ `lib/saml_idp/default.rb`
38
37
 
39
- ** New in Version 0.3.0 **
38
+ Similarly, please see the README about certificates - you should avoid using the
39
+ defaults in a Production environment. Post any issues you to github.
40
40
 
41
- Encrypted Assertions require the xmlenc gem. See the example in the Controller
42
- section of the README.
41
+ ** New in Version 0.3.0 **
42
+ Encrypted Assertions require the xmlenc gem. See the example in the Controller
43
+ section of the README.
43
44
  INST
44
45
 
45
- s.add_dependency('activesupport', '>= 3.2')
46
- s.add_dependency('uuid', '~> 2.3')
47
- s.add_dependency('builder', '~> 3.0')
46
+ s.add_dependency('activesupport', '>= 5.2')
47
+ s.add_dependency('builder', '>= 3.0')
48
48
  s.add_dependency('nokogiri', '>= 1.6.2')
49
-
50
- s.add_development_dependency('rake', '~> 10.4.2')
51
- s.add_development_dependency('simplecov', '~> 0.12')
52
- s.add_development_dependency('rspec', '~> 2.5')
53
- s.add_development_dependency('ruby-saml', '~> 1.3')
54
- s.add_development_dependency('rails', '~> 3.2')
55
- s.add_development_dependency('capybara', '~> 2.11.0')
56
- s.add_development_dependency('timecop', '~> 0.8')
57
- s.add_development_dependency('xmlenc', '>= 0.6.4')
49
+ s.add_dependency('ostruct')
50
+ s.add_dependency('rexml')
51
+ s.add_dependency('xmlenc', '>= 0.7.1')
52
+
53
+ s.add_development_dependency('activeresource', '~> 6.1')
54
+ s.add_development_dependency('appraisal')
55
+ s.add_development_dependency('capybara', '>= 2.16')
56
+ s.add_development_dependency('rails', '>= 5.2')
57
+ s.add_development_dependency('debug')
58
+ s.add_development_dependency('rake')
59
+ s.add_development_dependency('rspec', '>= 3.7.0')
60
+ s.add_development_dependency('ruby-saml', '>= 1.7.2')
61
+ s.add_development_dependency('simplecov')
62
+ s.add_development_dependency('timecop', '>= 0.8')
58
63
  end
59
-