Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix] Configure a certificate and private key for each response #227

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3e58ea2
Squash commits for saml_idp gem
Jul 24, 2024
f151f07
[feat] Allow SP config force signature validation (#16)
Zogoo Jul 27, 2024
86a2105
[feat] Don’t ignore certificates without usage (#17)
Zogoo Aug 14, 2024
94196fe
Try with proper way to update helper method (#19)
Zogoo Sep 17, 2024
1071d67
[feat] Collect request validation errors (#18)
Zogoo Sep 26, 2024
ade27c3
Support lowercase percent-encoded sequences for URL encoding (#20)
Zogoo Sep 26, 2024
1029a8c
Merge remote-tracking branch 'saml_idp/master'
Oct 24, 2024
9398b3b
Merge remote-tracking branch 'saml_idp/master'
Oct 24, 2024
d95a9d6
[fix] Gem CI updates for latest versions (#22)
Zogoo Oct 25, 2024
fcb331b
[fix] Allow IdP set reference ID for SAML response (#21)
Zogoo Oct 25, 2024
8cb05ef
Merge remote-tracking branch 'saml_idp/master'
Nov 1, 2024
02f3a7a
Support rails 8 for dev env (#23)
Zogoo Nov 1, 2024
e015f6e
Signable logic with given certificate information
Nov 1, 2024
38b70fd
Update unit test with new test certificate
Nov 4, 2024
6f3b8fa
Assertion builder with certificate attribute
Nov 4, 2024
686cb6a
Response builder with ceritificate
Zogoo Nov 4, 2024
03594c0
Use directly provided cert and pv key
Zogoo Nov 5, 2024
b1c2cc9
Remove config dependency from low layer logics
Zogoo Nov 5, 2024
4189fe1
Use correct attribute name
Zogoo Nov 5, 2024
57254e7
Remove config dependency from low level logics
Zogoo Nov 5, 2024
cf0c9d3
Remove config dependency from low level logics and fix test
Zogoo Nov 5, 2024
58e8831
Merge remote-tracking branch 'saml_idp/master' into feature/thread_sa…
Zogoo Nov 5, 2024
e973604
Revert Proc approach
Zogoo Nov 5, 2024
e3080bf
Merge branch 'master' into feature/thread_safe_config
Zogoo Nov 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@ KEY DATA
-----END RSA PRIVATE KEY-----
CERT

# x509_certificate, secret_key, and password may also be set from within a proc, for example:
# config.x509_certificate = -> { File.read("cert.pem") }
# config.secret_key = -> { SecretKeyFinder.key_for(id: 1) }
# config.password = -> { "password" }

# config.password = "secret_key_password"
# config.algorithm = :sha256 # Default: sha1 only for development.
# config.organization_name = "Your Organization"
Expand Down
5 changes: 2 additions & 3 deletions lib/saml_idp/algorithmable.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module SamlIdp
module Algorithmable
def algorithm
algorithm_check = raw_algorithm || SamlIdp.config.algorithm
return algorithm_check if algorithm_check.respond_to?(:digest)
return raw_algorithm if raw_algorithm.respond_to?(:digest)
begin
OpenSSL::Digest.const_get(algorithm_check.to_s.upcase)
OpenSSL::Digest.const_get(raw_algorithm.to_s.upcase)
rescue NameError
OpenSSL::Digest::SHA1
end
Expand Down
66 changes: 31 additions & 35 deletions lib/saml_idp/assertion_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,29 @@ class AssertionBuilder
attr_accessor :session_expiry
attr_accessor :name_id_formats_opts
attr_accessor :asserted_attributes_opts
attr_accessor :public_cert
attr_accessor :private_key
attr_accessor :pv_key_password

delegate :config, to: :SamlIdp

def initialize(
reference_id,
issuer_uri,
principal,
audience_uri,
saml_request_id,
saml_acs_url,
raw_algorithm,
authn_context_classref,
expiry=60*60,
encryption_opts=nil,
session_expiry=nil,
name_id_formats_opts = nil,
asserted_attributes_opts = nil
reference_id:,
issuer_uri:,
principal:,
audience_uri:,
saml_request_id:,
saml_acs_url:,
raw_algorithm:,
authn_context_classref:,
public_cert:,
private_key:,
pv_key_password:,
expiry: 60*60,
encryption_opts: nil,
session_expiry: nil,
name_id_formats_opts: nil,
asserted_attributes_opts: nil
)
self.reference_id = reference_id
self.issuer_uri = issuer_uri
Expand All @@ -49,6 +55,17 @@ def initialize(
self.session_expiry = session_expiry.nil? ? config.session_expiry : session_expiry
self.name_id_formats_opts = name_id_formats_opts
self.asserted_attributes_opts = asserted_attributes_opts
self.public_cert = public_cert
self.private_key = private_key
self.pv_key_password = pv_key_password
end

def encrypt(opts = {})
raise "Must set encryption_opts to encrypt" unless encryption_opts
raw_xml = opts[:sign] ? signed : raw
require 'saml_idp/encryptor'
encryptor = Encryptor.new encryption_opts
encryptor.encrypt(raw_xml)
end

def fresh
Expand Down Expand Up @@ -105,15 +122,8 @@ def fresh
end
end
alias_method :raw, :fresh
private :fresh

def encrypt(opts = {})
raise "Must set encryption_opts to encrypt" unless encryption_opts
raw_xml = opts[:sign] ? signed : raw
require 'saml_idp/encryptor'
encryptor = Encryptor.new encryption_opts
encryptor.encrypt(raw_xml)
end
private

def asserted_attributes
if asserted_attributes_opts.present? && !asserted_attributes_opts.empty?
Expand All @@ -124,7 +134,6 @@ def asserted_attributes
config.attributes
end
end
private :asserted_attributes

def get_values_for(friendly_name, getter)
result = nil
Expand All @@ -141,12 +150,10 @@ def get_values_for(friendly_name, getter)
end
Array(result)
end
private :get_values_for

def name_id
name_id_getter.call principal
end
private :name_id

def name_id_getter
getter = name_id_format[:getter]
Expand All @@ -156,56 +163,45 @@ def name_id_getter
->(principal) { principal.public_send getter.to_s }
end
end
private :name_id_getter

def name_id_format
@name_id_format ||= NameIdFormatter.new(name_id_formats).chosen
end
private :name_id_format

def name_id_formats
@name_id_formats ||= (name_id_formats_opts || config.name_id.formats)
end
private :name_id_formats

def reference_string
"_#{reference_id}"
end
private :reference_string

def now
@now ||= Time.now.utc
end
private :now

def now_iso
iso { now }
end
private :now_iso

def not_before
iso { now - 5 }
end
private :not_before

def not_on_or_after_condition
iso { now + expiry }
end
private :not_on_or_after_condition

def not_on_or_after_subject
iso { now + 3 * 60 }
end
private :not_on_or_after_subject

def session_not_on_or_after
iso { now + session_expiry }
end
private :session_not_on_or_after

def iso
yield.iso8601
end
private :iso
end
end
4 changes: 2 additions & 2 deletions lib/saml_idp/configurator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class Configurator
attr_accessor :logger

def initialize
self.x509_certificate = -> { Default::X509_CERTIFICATE }
self.secret_key = -> { Default::SECRET_KEY }
self.x509_certificate = Default::X509_CERTIFICATE
self.secret_key = Default::SECRET_KEY
self.algorithm = :sha1
self.reference_id_generator = ->() { SecureRandom.uuid }
self.service_provider = OpenStruct.new
Expand Down
58 changes: 32 additions & 26 deletions lib/saml_idp/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ def acs_url

def validate_saml_request(raw_saml_request = params[:SAMLRequest])
decode_request(raw_saml_request, params[:Signature], params[:SigAlg], params[:RelayState])
return true if valid_saml_request?

head :forbidden if defined?(::Rails)
false
valid_saml_request?
end

def decode_request(raw_saml_request, signature, sig_algorithm, relay_state)
Expand All @@ -60,6 +57,9 @@ def encode_authn_response(principal, opts = {})
audience_uri = opts[:audience_uri] || saml_request.issuer || saml_acs_url[/^(.*?\/\/.*?\/)/, 1]
opt_issuer_uri = opts[:issuer_uri] || issuer_uri
my_authn_context_classref = opts[:authn_context_classref] || authn_context_classref
public_cert = opts[:public_cert] || SamlIdp.config.x509_certificate
private_key = opts[:private_key] || SamlIdp.config.secret_key
pv_key_password = opts[:pv_key_password] || SamlIdp.config.password
acs_url = opts[:acs_url] || saml_acs_url
expiry = opts[:expiry] || 60*60
session_expiry = opts[:session_expiry]
Expand All @@ -73,33 +73,39 @@ def encode_authn_response(principal, opts = {})
compress_opts = opts[:compress] || false

SamlResponse.new(
reference_id,
response_id,
opt_issuer_uri,
principal,
audience_uri,
saml_request_id,
acs_url,
(opts[:algorithm] || algorithm || default_algorithm),
my_authn_context_classref,
expiry,
encryption_opts,
session_expiry,
name_id_formats_opts,
asserted_attributes_opts,
signed_message_opts,
signed_assertion_opts,
compress_opts
reference_id: reference_id,
response_id: response_id,
issuer_uri: opt_issuer_uri,
principal: principal,
audience_uri: audience_uri,
saml_request_id: saml_request_id,
saml_acs_url: acs_url,
algorithm: (opts[:algorithm] || algorithm || default_algorithm),
authn_context_classref: my_authn_context_classref,
public_cert: public_cert,
private_key: private_key,
pv_key_password: pv_key_password,
expiry: expiry,
encryption_opts: encryption_opts,
session_expiry: session_expiry,
name_id_formats_opts: name_id_formats_opts,
asserted_attributes_opts: asserted_attributes_opts,
signed_message_opts: signed_message_opts,
signed_assertion_opts: signed_assertion_opts,
compression_opts: compress_opts
).build
end

def encode_logout_response(_principal, opts = {})
SamlIdp::LogoutResponseBuilder.new(
get_saml_response_id,
(opts[:issuer_uri] || issuer_uri),
saml_logout_url,
saml_request_id,
(opts[:algorithm] || algorithm || default_algorithm)
response_id: get_saml_response_id,
issuer_uri: (opts[:issuer_uri] || issuer_uri),
saml_slo_url: saml_logout_url,
saml_request_id: saml_request_id,
algorithm: (opts[:algorithm] || algorithm || default_algorithm),
public_cert: opts[:public_cert] || SamlIdp.config.x509_certificate,
private_key: opts[:private_key] || SamlIdp.config.secret_key,
pv_key_password: opts[:pv_key_password] || SamlIdp.config.password
).signed
end

Expand Down
16 changes: 15 additions & 1 deletion lib/saml_idp/logout_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,26 @@ class LogoutBuilder
attr_accessor :issuer_uri
attr_accessor :saml_slo_url
attr_accessor :algorithm
attr_accessor :public_cert
attr_accessor :private_key
attr_accessor :pv_key_password

def initialize(response_id, issuer_uri, saml_slo_url, algorithm)
def initialize(
response_id:,
issuer_uri:,
saml_slo_url:,
algorithm:,
public_cert:,
private_key:,
pv_key_password:
)
self.response_id = response_id
self.issuer_uri = issuer_uri
self.saml_slo_url = saml_slo_url
self.algorithm = algorithm
self.public_cert = public_cert
self.private_key = private_key
self.pv_key_password = pv_key_password
end

# this is an abstract base class.
Expand Down
21 changes: 19 additions & 2 deletions lib/saml_idp/logout_request_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,25 @@ module SamlIdp
class LogoutRequestBuilder < LogoutBuilder
attr_accessor :name_id

def initialize(response_id, issuer_uri, saml_slo_url, name_id, algorithm)
super(response_id, issuer_uri, saml_slo_url, algorithm)
def initialize(
response_id:,
issuer_uri:,
saml_slo_url:,
name_id:,
algorithm:,
public_cert:,
private_key:,
pv_key_password: nil
)
super(
response_id: response_id,
issuer_uri: issuer_uri,
saml_slo_url: saml_slo_url,
algorithm: algorithm,
public_cert: public_cert,
private_key: private_key,
pv_key_password: pv_key_password
)
self.name_id = name_id
end

Expand Down
21 changes: 19 additions & 2 deletions lib/saml_idp/logout_response_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,25 @@ module SamlIdp
class LogoutResponseBuilder < LogoutBuilder
attr_accessor :saml_request_id

def initialize(response_id, issuer_uri, saml_slo_url, saml_request_id, algorithm)
super(response_id, issuer_uri, saml_slo_url, algorithm)
def initialize(
response_id:,
issuer_uri:,
saml_slo_url:,
saml_request_id:,
algorithm:,
public_cert:,
private_key:,
pv_key_password: nil
)
super(
response_id: response_id,
issuer_uri: issuer_uri,
saml_slo_url: saml_slo_url,
algorithm: algorithm,
public_cert: public_cert,
private_key: private_key,
pv_key_password: pv_key_password
)
self.saml_request_id = saml_request_id
end

Expand Down
10 changes: 10 additions & 0 deletions lib/saml_idp/metadata_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ def x509_certificate
.gsub(/\n/, "")
end

alias_method :public_cert, :x509_certificate

def private_key
SamlIdp.config.secret_key
end

def pv_key_password
nil
end

%w[
support_email
organization_name
Expand Down
Loading