Skip to content

Commit 1cf9ad2

Browse files
committed
Fix verification of SAML message signatures
Old behaviour is preserved if the correct (raw URI-encoded) parts are not provided.
1 parent b3ac5bd commit 1cf9ad2

File tree

2 files changed

+53
-5
lines changed

2 files changed

+53
-5
lines changed

lib/onelogin/ruby-saml/slo_logoutrequest.rb

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,18 +229,48 @@ def validate_signature
229229
return true unless options.has_key? :get_params
230230
return true unless options[:get_params].has_key? 'Signature'
231231

232+
# SAML specifies that the signature should be derived from a concatenation
233+
# of URI-encoded values _as sent by the IDP_:
234+
#
235+
# > Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given
236+
# > value. The relying party MUST therefore perform the verification step using the original URL-encoded
237+
# > values it received on the query string. It is not sufficient to re-encode the parameters after they have been
238+
# > processed by software because the resulting encoding may not match the signer's encoding.
239+
#
240+
# <http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf>
241+
#
242+
# If we don't have the original parts (for backward compatibility) required to correctly verify the signature,
243+
# then fabricate them by re-encoding the parsed URI parameters, and hope that we're lucky enough to use
244+
# the exact same URI-encoding as the IDP. (This is not the case if the IDP is ADFS!)
245+
options[:raw_get_params] ||= {}
246+
if options[:raw_get_params]['SAMLRequest'].nil? && !options[:get_params]['SAMLRequest'].nil?
247+
options[:raw_get_params]['SAMLRequest'] = CGI.escape(options[:get_params]['SAMLRequest'])
248+
end
249+
if options[:raw_get_params]['RelayState'].nil? && !options[:get_params]['RelayState'].nil?
250+
options[:raw_get_params]['RelayState'] = CGI.escape(options[:get_params]['RelayState'])
251+
end
252+
if options[:raw_get_params]['SigAlg'].nil? && !options[:get_params]['SigAlg'].nil?
253+
options[:raw_get_params]['SigAlg'] = CGI.escape(options[:get_params]['SigAlg'])
254+
end
255+
256+
# If we only received the raw version of SigAlg,
257+
# then parse it back into the decoded params hash for convenience.
258+
if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
259+
options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
260+
end
261+
232262
idp_cert = settings.get_idp_cert
233263
idp_certs = settings.get_idp_cert_multi
234264

235265
if idp_cert.nil? && (idp_certs.nil? || idp_certs[:signing].empty?)
236266
return options.has_key? :relax_signature_validation
237267
end
238268

239-
query_string = OneLogin::RubySaml::Utils.build_query(
240-
:type => 'SAMLRequest',
241-
:data => options[:get_params]['SAMLRequest'],
242-
:relay_state => options[:get_params]['RelayState'],
243-
:sig_alg => options[:get_params]['SigAlg']
269+
query_string = OneLogin::RubySaml::Utils.build_query_from_raw_parts(
270+
:type => 'SAMLRequest',
271+
:raw_data => options[:raw_get_params]['SAMLRequest'],
272+
:raw_relay_state => options[:raw_get_params]['RelayState'],
273+
:raw_sig_alg => options[:raw_get_params]['SigAlg']
244274
)
245275

246276
if idp_certs.nil? || idp_certs[:signing].empty?

lib/onelogin/ruby-saml/utils.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,24 @@ def self.build_query(params)
6767
url_string << "&SigAlg=#{CGI.escape(sig_alg)}"
6868
end
6969

70+
# Reconstruct a canonical query string from raw URI-encoded parts, to be used in verifying a signature
71+
# sent by an IDP.
72+
#
73+
# @param params [Hash] Parameters to build the Query String
74+
# @option params [String] :type 'SAMLRequest' or 'SAMLResponse'
75+
# @option params [String] :raw_data URI-encoded, base64 encoded SAMLRequest or SAMLResponse, as sent by IDP
76+
# @option params [String] :raw_relay_state URI-encoded RelayState parameter, as sent by IDP
77+
# @option params [String] :raw_sig_alg URI-encoded SigAlg parameter, as sent by IDP
78+
# @return [String] The Query String
79+
#
80+
def self.build_query_from_raw_parts(params)
81+
type, raw_data, raw_relay_state, raw_sig_alg = [:type, :raw_data, :raw_relay_state, :raw_sig_alg].map { |k| params[k]}
82+
83+
url_string = "#{type}=#{raw_data}"
84+
url_string << "&RelayState=#{raw_relay_state}" if raw_relay_state
85+
url_string << "&SigAlg=#{raw_sig_alg}"
86+
end
87+
7088
# Validate the Signature parameter sent on the HTTP-Redirect binding
7189
# @param params [Hash] Parameters to be used in the validation process
7290
# @option params [OpenSSL::X509::Certificate] cert The Identity provider public certtificate

0 commit comments

Comments
 (0)