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

[WIP - NOT READY] Support DSA and ECDSA signing keys #683

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions lib/onelogin/ruby-saml/authrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ def create_params(settings, params={})
sp_signing_key = settings.get_sp_signing_key

if settings.idp_sso_service_binding == Utils::BINDINGS[:redirect] && settings.security[:authn_requests_signed] && sp_signing_key
params['SigAlg'] = settings.security[:signature_method]
params['SigAlg'] = settings.get_sp_signature_method
url_string = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLRequest',
:data => base64_request,
:relay_state => relay_state,
:sig_alg => params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method)
signature = sp_signing_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
Expand Down Expand Up @@ -182,7 +182,7 @@ def create_xml_document(settings)
def sign_document(document, settings)
cert, private_key = settings.get_sp_signing_pair
if settings.idp_sso_service_binding == Utils::BINDINGS[:post] && settings.security[:authn_requests_signed] && private_key && cert
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
document.sign_document(private_key, cert, settings.get_sp_signature_method, settings.get_sp_digest_method)
end

document
Expand Down
2 changes: 1 addition & 1 deletion lib/onelogin/ruby-saml/idp_metadata_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1

cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))

fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
fingerprint_alg = XMLSecurity::Crypto.hash_algorithm(fingerprint_algorithm).new
fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
end
end
Expand Down
6 changes: 3 additions & 3 deletions lib/onelogin/ruby-saml/logoutrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ def create_params(settings, params={})
sp_signing_key = settings.get_sp_signing_key

if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && sp_signing_key
params['SigAlg'] = settings.security[:signature_method]
params['SigAlg'] = settings.get_sp_signature_method
url_string = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLRequest',
:data => base64_request,
:relay_state => relay_state,
:sig_alg => params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method)
signature = settings.get_sp_signing_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
Expand Down Expand Up @@ -141,7 +141,7 @@ def sign_document(document, settings)
# embed signature
cert, private_key = settings.get_sp_signing_pair
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && private_key && cert
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
document.sign_document(private_key, cert, settings.get_sp_signature_method, settings.get_sp_digest_method)
end

document
Expand Down
2 changes: 1 addition & 1 deletion lib/onelogin/ruby-saml/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def embed_signature(meta_doc, settings)
cert, private_key = settings.get_sp_signing_pair
return unless private_key && cert

meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
meta_doc.sign_document(private_key, cert, settings.get_sp_signature_method, settings.get_sp_digest_method)
end

def output_xml(meta_doc, pretty_print)
Expand Down
43 changes: 40 additions & 3 deletions lib/onelogin/ruby-saml/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def get_fingerprint
idp_cert_fingerprint || begin
idp_cert = get_idp_cert
if idp_cert
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new
fingerprint_alg = XMLSecurity::Crypto.hash_algorithm(idp_cert_fingerprint_algorithm).new
fingerprint_alg.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
end
end
Expand Down Expand Up @@ -205,7 +205,7 @@ def get_idp_cert_multi
certs
end

# @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
# @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::PKey>>>]
# Build the SP certificates and private keys from the settings. If
# check_sp_cert_expiration is true, only returns certificates and private keys
# that are not expired.
Expand All @@ -225,7 +225,7 @@ def get_sp_certs
active_certs.freeze
end

# @return [Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>]
# @return [Array<OpenSSL::X509::Certificate, OpenSSL::PKey::PKey>]
# The SP signing certificate and private key.
def get_sp_signing_pair
get_sp_certs[:signing].first
Expand Down Expand Up @@ -263,6 +263,43 @@ def get_sp_cert_new
node[0] if node
end

# @return [String] The XML Signature Algorithm attribute.
#
# This method is intentionally hacky for backwards compatibility of the
# settings.security[:signature_method] parameter. Previously, this parameter
# could have a value such as "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
# which assumes the public key type RSA. To add support for DSA and ECDSA, we will now
# ignore the "rsa-" prefix and only use the "sha256" hash algorithm component.
def get_sp_signature_method
sig_alg = security[:signature_method] || 'sha1' # TODO: change to sha256 by default
hash_alg = sig_alg.to_s.match(/(?:\A|[#_-])(sha\d+)\z/i)[1]
key_alg = case get_sp_signing_key
when OpenSSL::PKey::RSA then 'RSA'
when OpenSSL::PKey::DSA then 'DSA'
when OpenSSL::PKey::EC then 'ECDSA'
else
'RSA'
# raise ArgumentError.new("Unsupported signing key type: #{get_sp_signing_key.class}")
end

begin
XMLSecurity::Crypto.const_get("#{key_alg}_#{hash_alg}".upcase)
rescue NameError
raise ArgumentError.new("Unsupported signature method: #{sig_alg}")
end
end

def get_sp_digest_method
digest_alg = security[:digest_method] || 'sha1' # TODO: change to sha256 by default
alg = digest_alg.to_s.match(/(?:\A|#)(sha\d+)\z/i)[1]

begin
XMLSecurity::Crypto.const_get(alg.upcase)
rescue NameError
raise ArgumentError.new("Unsupported signature method: #{digest_alg}")
end
end

def idp_binding_from_embed_sign
security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
end
Expand Down
7 changes: 3 additions & 4 deletions lib/onelogin/ruby-saml/slo_logoutresponse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {},
sp_signing_key = settings.get_sp_signing_key

if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && sp_signing_key
params['SigAlg'] = settings.security[:signature_method]
params['SigAlg'] = settings.get_sp_signature_method
url_string = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLResponse',
:data => base64_response,
:relay_state => relay_state,
:sig_alg => params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
sign_algorithm = XMLSecurity::Crypto.hash_algorithm(settings.get_sp_signature_method)
signature = sp_signing_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
Expand Down Expand Up @@ -153,12 +153,11 @@ def sign_document(document, settings)
# embed signature
cert, private_key = settings.get_sp_signing_pair
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && private_key && cert
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
document.sign_document(private_key, cert, settings.get_sp_signature_method, settings.get_sp_digest_method)
end

document
end

end
end
end
25 changes: 19 additions & 6 deletions lib/onelogin/ruby-saml/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,25 @@ def self.build_cert_object(cert)
# Given a private key string, return an OpenSSL::PKey::RSA object.
#
# @param cert [String] The original private key
# @return [OpenSSL::PKey::RSA] The private key object
# @return [OpenSSL::PKey::PKey] The private key object
#
def self.build_private_key_object(private_key)
return nil if private_key.nil? || private_key.empty?

OpenSSL::PKey::RSA.new(format_private_key(private_key))
private_key = format_private_key(private_key)
error = nil

[ OpenSSL::PKey::RSA,
OpenSSL::PKey::DSA,
OpenSSL::PKey::EC ].each do |key_class|
begin
return key_class.new(private_key)
rescue OpenSSL::PKey::PKeyError => e
error ||= e
end
end

raise error
end

# Build the Query String signature that will be used in the HTTP-Redirect binding
Expand Down Expand Up @@ -237,7 +250,7 @@ def self.escape_request_param(param, lowercase_url_encoding)
#
def self.verify_signature(params)
cert, sig_alg, signature, query_string = [:cert, :sig_alg, :signature, :query_string].map { |k| params[k]}
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg)
signature_algorithm = XMLSecurity::Crypto.hash_algorithm(sig_alg)
return cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string)
end

Expand Down Expand Up @@ -269,7 +282,7 @@ def self.status_error_msg(error_msg, raw_status_code = nil, status_message = nil
# Obtains the decrypted string from an Encrypted node element in XML,
# given multiple private keys to try.
# @param encrypted_node [REXML::Element] The Encrypted element
# @param private_keys [Array<OpenSSL::PKey::RSA>] The Service provider private key
# @param private_keys [Array<OpenSSL::PKey::PKey>] The Service provider private key
# @return [String] The decrypted data
def self.decrypt_multi(encrypted_node, private_keys)
raise ArgumentError.new('private_keys must be specified') if !private_keys || private_keys.empty?
Expand All @@ -288,7 +301,7 @@ def self.decrypt_multi(encrypted_node, private_keys)

# Obtains the decrypted string from an Encrypted node element in XML
# @param encrypted_node [REXML::Element] The Encrypted element
# @param private_key [OpenSSL::PKey::RSA] The Service provider private key
# @param private_key [OpenSSL::PKey::PKey] The Service provider private key
# @return [String] The decrypted data
def self.decrypt_data(encrypted_node, private_key)
encrypt_data = REXML::XPath.first(
Expand All @@ -314,7 +327,7 @@ def self.decrypt_data(encrypted_node, private_key)

# Obtains the symmetric key from the EncryptedData element
# @param encrypt_data [REXML::Element] The EncryptedData element
# @param private_key [OpenSSL::PKey::RSA] The Service provider private key
# @param private_key [OpenSSL::PKey::PKey] The Service provider private key
# @return [String] The symmetric key
def self.retrieve_symmetric_key(encrypt_data, private_key)
encrypted_key = REXML::XPath.first(
Expand Down
Loading
Loading