Commit 4b8f032c authored by mh's avatar mh
Browse files

Merge branch 'samlRedirectBind' into 'master'

allow out-of-band saml signature verification

See merge request !58
parents b894e415 a1d99509
Pipeline #8944 passed with stage
in 2 minutes and 37 seconds
......@@ -4,7 +4,25 @@ class SamlManager
class << self
include ApiTokenHelpers
def verify_request(req)
def validate_redirect_binding(cert, sign_info)
sign_info[:sig_alg] =~ /sha(.*?)$/i
signature_algorithm = case $1.to_i
when 256 then OpenSSL::Digest::SHA256
when 384 then OpenSSL::Digest::SHA384
when 512 then OpenSSL::Digest::SHA512
else
OpenSSL::Digest::SHA1
end
signature = Base64.decode64(sign_info[:signature])
verifiable = "SAMLRequest=#{CGI.escape(sign_info[:saml_request])}" +
"&RelayState=#{CGI.escape(sign_info[:relay_state])}" +
"&SigAlg=#{CGI.escape(sign_info[:sig_alg])}"
return cert.public_key.verify(signature_algorithm.new, signature, verifiable)
end
def verify_request(req, saml_sig_info)
unless req
IApiLog.notice("SAML request missing")
return
......@@ -38,7 +56,6 @@ class SamlManager
return
end
attributes[:certs].each do |fp, cert|
if document.valid_signature?(fp)
saml_request.service_provider.cert = cert
......@@ -46,17 +63,37 @@ class SamlManager
end
end
# For unsigned requests the default cert1 will always be used to encrypt
# the response, since we cannot tell from the request, which one is
# the currently active one
if !saml_request.service_provider.cert && saml_sig_info &&
saml_sig_info != :skip && attributes[:redirect_binding_signature]
attributes[:certs].each do |fp, cert|
sig_info = {
sig_alg: saml_sig_info['sig_alg'] || '',
signature: saml_sig_info['signature'] || '',
saml_request: req,
relay_state: saml_sig_info['relay_state'] || '',
}
if validate_redirect_binding(cert, sig_info)
saml_request.service_provider.cert = cert
saml_request.service_provider.fingerprint = fp
end
end
end
unless saml_request.service_provider.cert
# For unsigned requests the default cert1 will always be used to encrypt
# the response, since we cannot tell from the request, which one is
# the currently active one
if attributes[:allow_unsigned_requests]
(fp, cert) = attributes[:certs].first
saml_request.service_provider.cert = cert
saml_request.service_provider.fingerprint = fp
else
IApiLog.warn("SP #{saml_request.issuer} did not sign the request.")
return
# For sp_info with a SP with redirect binding we can only
# validate on the actual request.
unless attributes[:redirect_binding_signature] && saml_sig_info == :skip
IApiLog.warn("SP #{saml_request.issuer} did not sign the request.")
return
end
end
end
......@@ -70,7 +107,7 @@ class SamlManager
end
def sp_info(req)
(saml_request, attributes) = verify_request(req)
(saml_request, attributes) = verify_request(req, :skip)
return unless saml_request
{
issuer: attributes['plain_issuer'],
......@@ -79,8 +116,8 @@ class SamlManager
}
end
def process_saml_request(req, email, application_specific_secret)
(saml_request, attributes) = verify_request(req)
def process_saml_request(req, email, application_specific_secret, saml_sig_info)
(saml_request, attributes) = verify_request(req, saml_sig_info)
return unless saml_request
user = EmailUser.active_by_email(email)
......
......@@ -16,6 +16,7 @@ class IApi < Sinatra::Base
email = validate_email(parsed_body['email'].downcase)
saml_req = parsed_body['saml_request']
saml_sig_info = parsed_body['saml_sig_info']
info = SamlManager.sp_info(saml_req)
sps = application_specific_secret(info)
skip_2fa = api_token_age(email) &&
......@@ -32,7 +33,7 @@ class IApi < Sinatra::Base
skip_2fa: skip_2fa,
get_application_specific_secret: sps)
saml = SamlManager.process_saml_request(saml_req, email, user[:application_specific_secret])
saml = SamlManager.process_saml_request(saml_req, email, user[:application_specific_secret], saml_sig_info)
unless saml
IApiLog.info("master auth failed. No saml request")
......@@ -79,8 +80,9 @@ class IApi < Sinatra::Base
# Reissue does not work for applications that need an application specific
# secret, since that secret can only be unlocked by the password.
if !sps && email && info
saml_sig_info = parsed_body['saml_sig_info']
saml = SamlManager.process_saml_request(parsed_body['saml_request'],
email, nil)
email, nil, saml_sig_info)
if saml
allowed = IApiConf.acl.is_allowed_to_access?(email, saml[:plain_issuer])
......
......@@ -36,6 +36,7 @@ class IApiSamlConfig
max_reissue_time: params['max_reissue_time'],
max_reissue_time_2fa: params['max_reissue_time_2fa'],
allow_unsigned_requests: params['allow_unsigned_requests'],
redirect_binding_signature: params['redirect_binding_signature'],
}
aliases.each do |a|
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment