Skip to content

Commit 407602d

Browse files
Zogoozogoomjobin-mdsolmzappino-nozandnoz
authored
[fix] Configure a certificate and private key for each response (#227)
* Squash commits for saml_idp gem * [feat] Allow SP config force signature validation (#16) * Allow SP config force signature validation * Allow SP config force signature validation Tested with Slack with Authn request signature option --------- Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * [feat] Don’t ignore certificates without usage (#17) I have tested with live SAML SP apps and it works fine * Unspecified certifciate from SP metadata --------- Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * Try with proper way to update helper method (#19) * Set minimum test coverage (#207) * Set minimum test coverage to a very high value for testing * Update minimum coverage to actual current value * Try with proper way to update helper method * Correctly decode and mock with correct REXML class * Drop the min coverage --------- Co-authored-by: Mathieu Jobin <majobin@mdsol.com> Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * [feat] Collect request validation errors (#18) * wip add error collector * Fix type and rewrite request with proper validation test cases * Lead error render decision to gem user * Validate the certificate's existence before verifying the signature. --------- Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * Support lowercase percent-encoded sequences for URL encoding (#20) Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * [fix] Gem CI updates for latest versions (#22) * Remove duplications * Pre-conditions need to be defined in before section * Le's not test logger in here --------- Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * [fix] Allow IdP set reference ID for SAML response (#21) * Pass ref id as Session Index * Official Rails 8 is not released yet to RubyGem until that let's stick official older version --------- Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * Support rails 8 for dev env (#23) Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * Signable logic with given certificate information * Update unit test with new test certificate * Assertion builder with certificate attribute * Response builder with ceritificate * Use directly provided cert and pv key * Remove config dependency from low layer logics * Use correct attribute name * Remove config dependency from low level logics * Remove config dependency from low level logics and fix test * Revert Proc approach * Assertion flag should able switchable by application (#24) Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * concurrent-ruby v1.3.5 has removed the dependency on logger (#27) Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> * MetadataBuilder uses custom configurator (#25) Co-authored-by: Andrea Lorenzetti <64900248+andnoz@users.noreply.github.com> --------- Co-authored-by: zogoo <ch.tsogbadrakh@gmail.com> Co-authored-by: Mathieu Jobin <majobin@mdsol.com> Co-authored-by: Massimo Zappino <99500013+mzappino-noz@users.noreply.github.com> Co-authored-by: Andrea Lorenzetti <64900248+andnoz@users.noreply.github.com>
1 parent abce86e commit 407602d

28 files changed

Lines changed: 541 additions & 313 deletions

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,6 @@ KEY DATA
7878
-----END RSA PRIVATE KEY-----
7979
CERT
8080

81-
# x509_certificate, secret_key, and password may also be set from within a proc, for example:
82-
# config.x509_certificate = -> { File.read("cert.pem") }
83-
# config.secret_key = -> { SecretKeyFinder.key_for(id: 1) }
84-
# config.password = -> { "password" }
85-
8681
# config.password = "secret_key_password"
8782
# config.algorithm = :sha256 # Default: sha1 only for development.
8883
# config.organization_name = "Your Organization"

lib/saml_idp/algorithmable.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
module SamlIdp
22
module Algorithmable
33
def algorithm
4-
algorithm_check = raw_algorithm || SamlIdp.config.algorithm
5-
return algorithm_check if algorithm_check.respond_to?(:digest)
4+
return raw_algorithm if raw_algorithm.respond_to?(:digest)
65
begin
7-
OpenSSL::Digest.const_get(algorithm_check.to_s.upcase)
6+
OpenSSL::Digest.const_get(raw_algorithm.to_s.upcase)
87
rescue NameError
98
OpenSSL::Digest::SHA1
109
end

lib/saml_idp/assertion_builder.rb

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,29 @@ class AssertionBuilder
1818
attr_accessor :session_expiry
1919
attr_accessor :name_id_formats_opts
2020
attr_accessor :asserted_attributes_opts
21+
attr_accessor :public_cert
22+
attr_accessor :private_key
23+
attr_accessor :pv_key_password
2124

2225
delegate :config, to: :SamlIdp
2326

2427
def initialize(
25-
reference_id,
26-
issuer_uri,
27-
principal,
28-
audience_uri,
29-
saml_request_id,
30-
saml_acs_url,
31-
raw_algorithm,
32-
authn_context_classref,
33-
expiry=60*60,
34-
encryption_opts=nil,
35-
session_expiry=nil,
36-
name_id_formats_opts = nil,
37-
asserted_attributes_opts = nil
28+
reference_id:,
29+
issuer_uri:,
30+
principal:,
31+
audience_uri:,
32+
saml_request_id:,
33+
saml_acs_url:,
34+
raw_algorithm:,
35+
authn_context_classref:,
36+
public_cert:,
37+
private_key:,
38+
pv_key_password:,
39+
expiry: 60*60,
40+
encryption_opts: nil,
41+
session_expiry: nil,
42+
name_id_formats_opts: nil,
43+
asserted_attributes_opts: nil
3844
)
3945
self.reference_id = reference_id
4046
self.issuer_uri = issuer_uri
@@ -49,6 +55,17 @@ def initialize(
4955
self.session_expiry = session_expiry.nil? ? config.session_expiry : session_expiry
5056
self.name_id_formats_opts = name_id_formats_opts
5157
self.asserted_attributes_opts = asserted_attributes_opts
58+
self.public_cert = public_cert
59+
self.private_key = private_key
60+
self.pv_key_password = pv_key_password
61+
end
62+
63+
def encrypt(opts = {})
64+
raise "Must set encryption_opts to encrypt" unless encryption_opts
65+
raw_xml = opts[:sign] ? signed : raw
66+
require 'saml_idp/encryptor'
67+
encryptor = Encryptor.new encryption_opts
68+
encryptor.encrypt(raw_xml)
5269
end
5370

5471
def fresh
@@ -105,15 +122,8 @@ def fresh
105122
end
106123
end
107124
alias_method :raw, :fresh
108-
private :fresh
109125

110-
def encrypt(opts = {})
111-
raise "Must set encryption_opts to encrypt" unless encryption_opts
112-
raw_xml = opts[:sign] ? signed : raw
113-
require 'saml_idp/encryptor'
114-
encryptor = Encryptor.new encryption_opts
115-
encryptor.encrypt(raw_xml)
116-
end
126+
private
117127

118128
def asserted_attributes
119129
if asserted_attributes_opts.present? && !asserted_attributes_opts.empty?
@@ -124,7 +134,6 @@ def asserted_attributes
124134
config.attributes
125135
end
126136
end
127-
private :asserted_attributes
128137

129138
def get_values_for(friendly_name, getter)
130139
result = nil
@@ -141,12 +150,10 @@ def get_values_for(friendly_name, getter)
141150
end
142151
Array(result)
143152
end
144-
private :get_values_for
145153

146154
def name_id
147155
name_id_getter.call principal
148156
end
149-
private :name_id
150157

151158
def name_id_getter
152159
getter = name_id_format[:getter]
@@ -156,56 +163,45 @@ def name_id_getter
156163
->(principal) { principal.public_send getter.to_s }
157164
end
158165
end
159-
private :name_id_getter
160166

161167
def name_id_format
162168
@name_id_format ||= NameIdFormatter.new(name_id_formats).chosen
163169
end
164-
private :name_id_format
165170

166171
def name_id_formats
167172
@name_id_formats ||= (name_id_formats_opts || config.name_id.formats)
168173
end
169-
private :name_id_formats
170174

171175
def reference_string
172176
"_#{reference_id}"
173177
end
174-
private :reference_string
175178

176179
def now
177180
@now ||= Time.now.utc
178181
end
179-
private :now
180182

181183
def now_iso
182184
iso { now }
183185
end
184-
private :now_iso
185186

186187
def not_before
187188
iso { now - 5 }
188189
end
189-
private :not_before
190190

191191
def not_on_or_after_condition
192192
iso { now + expiry }
193193
end
194-
private :not_on_or_after_condition
195194

196195
def not_on_or_after_subject
197196
iso { now + 3 * 60 }
198197
end
199-
private :not_on_or_after_subject
200198

201199
def session_not_on_or_after
202200
iso { now + session_expiry }
203201
end
204-
private :session_not_on_or_after
205202

206203
def iso
207204
yield.iso8601
208205
end
209-
private :iso
210206
end
211207
end

lib/saml_idp/configurator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ class Configurator
2525
attr_accessor :logger
2626

2727
def initialize
28-
self.x509_certificate = -> { Default::X509_CERTIFICATE }
29-
self.secret_key = -> { Default::SECRET_KEY }
28+
self.x509_certificate = Default::X509_CERTIFICATE
29+
self.secret_key = Default::SECRET_KEY
3030
self.algorithm = :sha1
3131
self.reference_id_generator = ->() { SecureRandom.uuid }
3232
self.service_provider = OpenStruct.new

lib/saml_idp/controller.rb

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def encode_authn_response(principal, opts = {})
5757
audience_uri = opts[:audience_uri] || saml_request.issuer || saml_acs_url[/^(.*?\/\/.*?\/)/, 1]
5858
opt_issuer_uri = opts[:issuer_uri] || issuer_uri
5959
my_authn_context_classref = opts[:authn_context_classref] || authn_context_classref
60+
public_cert = opts[:public_cert] || SamlIdp.config.x509_certificate
61+
private_key = opts[:private_key] || SamlIdp.config.secret_key
62+
pv_key_password = opts[:pv_key_password] || SamlIdp.config.password
6063
acs_url = opts[:acs_url] || saml_acs_url
6164
expiry = opts[:expiry] || 60*60
6265
session_expiry = opts[:session_expiry]
@@ -70,33 +73,39 @@ def encode_authn_response(principal, opts = {})
7073
compress_opts = opts[:compress] || false
7174

7275
SamlResponse.new(
73-
reference_id,
74-
response_id,
75-
opt_issuer_uri,
76-
principal,
77-
audience_uri,
78-
saml_request_id,
79-
acs_url,
80-
(opts[:algorithm] || algorithm || default_algorithm),
81-
my_authn_context_classref,
82-
expiry,
83-
encryption_opts,
84-
session_expiry,
85-
name_id_formats_opts,
86-
asserted_attributes_opts,
87-
signed_message_opts,
88-
signed_assertion_opts,
89-
compress_opts
76+
reference_id: reference_id,
77+
response_id: response_id,
78+
issuer_uri: opt_issuer_uri,
79+
principal: principal,
80+
audience_uri: audience_uri,
81+
saml_request_id: saml_request_id,
82+
saml_acs_url: acs_url,
83+
algorithm: (opts[:algorithm] || algorithm || default_algorithm),
84+
authn_context_classref: my_authn_context_classref,
85+
public_cert: public_cert,
86+
private_key: private_key,
87+
pv_key_password: pv_key_password,
88+
expiry: expiry,
89+
encryption_opts: encryption_opts,
90+
session_expiry: session_expiry,
91+
name_id_formats_opts: name_id_formats_opts,
92+
asserted_attributes_opts: asserted_attributes_opts,
93+
signed_message_opts: signed_message_opts,
94+
signed_assertion_opts: signed_assertion_opts,
95+
compression_opts: compress_opts
9096
).build
9197
end
9298

9399
def encode_logout_response(_principal, opts = {})
94100
SamlIdp::LogoutResponseBuilder.new(
95-
get_saml_response_id,
96-
(opts[:issuer_uri] || issuer_uri),
97-
saml_logout_url,
98-
saml_request_id,
99-
(opts[:algorithm] || algorithm || default_algorithm)
101+
response_id: get_saml_response_id,
102+
issuer_uri: (opts[:issuer_uri] || issuer_uri),
103+
saml_slo_url: saml_logout_url,
104+
saml_request_id: saml_request_id,
105+
algorithm: (opts[:algorithm] || algorithm || default_algorithm),
106+
public_cert: opts[:public_cert] || SamlIdp.config.x509_certificate,
107+
private_key: opts[:private_key] || SamlIdp.config.secret_key,
108+
pv_key_password: opts[:pv_key_password] || SamlIdp.config.password
100109
).signed
101110
end
102111

lib/saml_idp/logout_builder.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,26 @@ class LogoutBuilder
77
attr_accessor :issuer_uri
88
attr_accessor :saml_slo_url
99
attr_accessor :algorithm
10+
attr_accessor :public_cert
11+
attr_accessor :private_key
12+
attr_accessor :pv_key_password
1013

11-
def initialize(response_id, issuer_uri, saml_slo_url, algorithm)
14+
def initialize(
15+
response_id:,
16+
issuer_uri:,
17+
saml_slo_url:,
18+
algorithm:,
19+
public_cert:,
20+
private_key:,
21+
pv_key_password:
22+
)
1223
self.response_id = response_id
1324
self.issuer_uri = issuer_uri
1425
self.saml_slo_url = saml_slo_url
1526
self.algorithm = algorithm
27+
self.public_cert = public_cert
28+
self.private_key = private_key
29+
self.pv_key_password = pv_key_password
1630
end
1731

1832
# this is an abstract base class.

lib/saml_idp/logout_request_builder.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,25 @@ module SamlIdp
33
class LogoutRequestBuilder < LogoutBuilder
44
attr_accessor :name_id
55

6-
def initialize(response_id, issuer_uri, saml_slo_url, name_id, algorithm)
7-
super(response_id, issuer_uri, saml_slo_url, algorithm)
6+
def initialize(
7+
response_id:,
8+
issuer_uri:,
9+
saml_slo_url:,
10+
name_id:,
11+
algorithm:,
12+
public_cert:,
13+
private_key:,
14+
pv_key_password: nil
15+
)
16+
super(
17+
response_id: response_id,
18+
issuer_uri: issuer_uri,
19+
saml_slo_url: saml_slo_url,
20+
algorithm: algorithm,
21+
public_cert: public_cert,
22+
private_key: private_key,
23+
pv_key_password: pv_key_password
24+
)
825
self.name_id = name_id
926
end
1027

lib/saml_idp/logout_response_builder.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,25 @@ module SamlIdp
33
class LogoutResponseBuilder < LogoutBuilder
44
attr_accessor :saml_request_id
55

6-
def initialize(response_id, issuer_uri, saml_slo_url, saml_request_id, algorithm)
7-
super(response_id, issuer_uri, saml_slo_url, algorithm)
6+
def initialize(
7+
response_id:,
8+
issuer_uri:,
9+
saml_slo_url:,
10+
saml_request_id:,
11+
algorithm:,
12+
public_cert:,
13+
private_key:,
14+
pv_key_password: nil
15+
)
16+
super(
17+
response_id: response_id,
18+
issuer_uri: issuer_uri,
19+
saml_slo_url: saml_slo_url,
20+
algorithm: algorithm,
21+
public_cert: public_cert,
22+
private_key: private_key,
23+
pv_key_password: pv_key_password
24+
)
825
self.saml_request_id = saml_request_id
926
end
1027

lib/saml_idp/metadata_builder.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,24 @@ def raw_algorithm
152152
private :raw_algorithm
153153

154154
def x509_certificate
155-
certificate = SamlIdp.config.x509_certificate.is_a?(Proc) ? SamlIdp.config.x509_certificate.call : SamlIdp.config.x509_certificate
155+
certificate = configurator.x509_certificate.is_a?(Proc) ? configurator.x509_certificate.call : configurator.x509_certificate
156156
certificate
157157
.to_s
158158
.gsub(/-----BEGIN CERTIFICATE-----/,"")
159159
.gsub(/-----END CERTIFICATE-----/,"")
160160
.gsub(/\n/, "")
161161
end
162162

163+
alias_method :public_cert, :x509_certificate
164+
165+
def private_key
166+
configurator.secret_key
167+
end
168+
169+
def pv_key_password
170+
nil
171+
end
172+
163173
%w[
164174
support_email
165175
organization_name

0 commit comments

Comments
 (0)