Module: SAML2::Bindings::HTTPRedirect

Defined in:
lib/saml2/bindings/http_redirect.rb

Defined Under Namespace

Modules: SigAlgs

Constant Summary collapse

URN =
"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze

Class Method Summary collapse

Class Method Details

.decode(url, public_key: nil, public_key_used: nil) ⇒ Object

Raises:



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/saml2/bindings/http_redirect.rb', line 21

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?
  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|
      if key.verify(OpenSSL::Digest::SHA1.new, signature, base_string)
        # notify the caller which certificate was used
        public_key_used&.call(key)
        valid_signature = true
        break
      end
    end
    raise InvalidSignature unless valid_signature
  end
  [message, relay_state]
end

.encode(message, relay_state: nil, private_key: nil) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/saml2/bindings/http_redirect.rb', line 101

def encode(message, relay_state: nil, private_key: nil)
  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
  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
    query << ['SigAlg', SigAlgs::RSA_SHA1]
    base_string = URI.encode_www_form(query)
    signature = private_key.sign(OpenSSL::Digest::SHA1.new, base_string)
    query << ['Signature', Base64.strict_encode64(signature)]
  end

  result.query = URI.encode_www_form(original_query + query)
  result.to_s
end