module SAML2::Bindings::HTTPRedirect
Constants
- URN
Public Class Methods
decode(url, public_key: nil, public_key_used: nil) { |message, sig_alg| ... }
click to toggle source
Decode, validate signature, and parse a compressed and Base64 encoded SAML message.
A signature, if present, will be verified only if public_key
is passed.
@param url [String]
The full URL to decode. Will check for both +SAMLRequest+ and +SAMLResponse+ params.
@param public_key optional [Array<OpenSSL::PKey>, OpenSSL::PKey, Proc]
Keys to use to check the signature. If a +Proc+ is provided, it is called with the parsed {Message}, and the +SigAlg+ in order for the caller to find an appropriate key based on the {Message}'s issuer.
@param public_key_used optional [Proc]
Is called with the actual key that was used to validate the signature.
@return [[Message, String]]
The Message and the RelayState.
@raise [UnsignedMessage] If a public_key is provided, but the message
is not signed.
@yield [message, sig_alg]
The same as a +Proc+ provided to +public_key+. Deprecated.
# File lib/saml2/bindings/http_redirect.rb, line 46 def decode(url, public_key: nil, public_key_used: nil) uri = begin URI.parse(url) rescue URI::InvalidURIError raise CorruptMessage end raise MissingMessage unless uri.query query = URI.decode_www_form(uri.query) base64 = query.assoc("SAMLRequest")&.last if base64 message_param = "SAMLRequest" else base64 = query.assoc("SAMLResponse")&.last message_param = "SAMLResponse" end encoding = query.assoc("SAMLEncoding")&.last relay_state = query.assoc("RelayState")&.last signature = query.assoc("Signature")&.last sig_alg = query.assoc("SigAlg")&.last raise MissingMessage unless base64 raise UnsupportedEncoding if encoding && encoding != Encodings::DEFLATE raise MessageTooLarge if base64.bytesize > SAML2.config[:max_message_size] deflated = begin Base64.strict_decode64(base64) rescue ArgumentError raise CorruptMessage end zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS) xml = +"" begin # do it in 1K slices, so we can protect against bombs (0..deflated.bytesize / 1024).each do |i| xml.concat(zstream.inflate(deflated.byteslice(i * 1024, 1024))) raise MessageTooLarge if xml.bytesize > SAML2.config[:max_message_size] end xml.concat(zstream.finish) raise MessageTooLarge if xml.bytesize > SAML2.config[:max_message_size] rescue Zlib::DataError, Zlib::BufError raise CorruptMessage end zstream.close message = Message.parse(xml) # if a block is provided, it's to fetch the proper certificate # based on the contents of the message public_key ||= yield(message, sig_alg) if block_given? public_key = public_key.call(message, sig_alg) if public_key.is_a?(Proc) if public_key raise UnsignedMessage unless signature raise UnsupportedSignatureAlgorithm unless SigAlgs::RECOGNIZED.include?(sig_alg) begin signature = Base64.strict_decode64(signature) rescue ArgumentError raise CorruptMessage end base_string = find_raw_query_param(uri.query, message_param) base_string << "&" << find_raw_query_param(uri.query, "RelayState") if relay_state base_string << "&" << find_raw_query_param(uri.query, "SigAlg") valid_signature = false # there could be multiple certificates to try Array(public_key).each do |key| hash = ((sig_alg == SigAlgs::RSA_SHA256) ? OpenSSL::Digest::SHA256 : OpenSSL::Digest::SHA1) next unless key.verify(hash.new, signature, base_string) # notify the caller which certificate was used public_key_used&.call(key) valid_signature = true break end raise InvalidSignature unless valid_signature end [message, relay_state] end
encode(message, relay_state: nil, private_key: nil, sig_alg: SigAlgs::RSA_SHA1)
click to toggle source
Encode a SAML message into Base64, compressed query params.
@param message [Message]
Note that the base URI is taken from {Message#destination}.
@param relay_state optional [String] @param private_key optional [OpenSSL::PKey::RSA]
A key to use to sign the encoded message.
@param sig_alg optional [String]
The signing algorithm to use. Defaults to RSA-SHA1, as it's the most compatible, and explicitly mentioned in the SAML specs, but you may want to use RSA-SHA256. Values must come from {SigAlgs}.
@return [String]
The full URI to redirect to, including +RelayState+, and +SAMLRequest+ vs. +SAMLResponse+ chosen appropriately, and +Signature+ + +SigAlg+ query params if signing.
# File lib/saml2/bindings/http_redirect.rb, line 144 def encode(message, relay_state: nil, private_key: nil, sig_alg: SigAlgs::RSA_SHA1) result = URI.parse(message.destination) original_query = URI.decode_www_form(result.query) if result.query original_query ||= [] # remove any SAML protocol parameters %w[SAMLEncoding SAMLRequest SAMLResponse RelayState SigAlg Signature].each do |param| original_query.delete_if { |(k, _v)| k == param } end xml = message.to_s(pretty: false) zstream = Zlib::Deflate.new(Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS) deflated = zstream.deflate(xml, Zlib::FINISH) zstream.close base64 = Base64.strict_encode64(deflated) query = [] query << [message.is_a?(Request) ? "SAMLRequest" : "SAMLResponse", base64] query << ["RelayState", relay_state] if relay_state if private_key unless SigAlgs::RECOGNIZED.include?(sig_alg) raise ArgumentError, "Unsupported signature algorithm #{sig_alg}" end query << ["SigAlg", sig_alg] base_string = URI.encode_www_form(query) hash = ((sig_alg == SigAlgs::RSA_SHA256) ? OpenSSL::Digest::SHA256 : OpenSSL::Digest::SHA1) signature = private_key.sign(hash.new, base_string) query << ["Signature", Base64.strict_encode64(signature)] end result.query = URI.encode_www_form(original_query + query) result.to_s end
Private Class Methods
find_raw_query_param(query, param)
click to toggle source
we need to find the param, and return it still encoded from the URL
# File lib/saml2/bindings/http_redirect.rb, line 182 def find_raw_query_param(query, param) start = query.index(param) finish = (query.index("&", start + param.length + 1) || 0) - 1 query[start..finish] end