Class: BreachMitigation::MaskingSecrets

Inherits:
Object
  • Object
show all
Defined in:
lib/breach_mitigation/masking_secrets.rb

Constant Summary collapse

AUTHENTICITY_TOKEN_LENGTH =
32

Class Method Summary collapse

Class Method Details

.masked_authenticity_token(session) ⇒ Object

Sets the token value for the current session and returns it in a masked form that’s safe to send to the client. See section 3.4 of “BREACH: Reviving the CRIME attack”.



12
13
14
15
16
17
# File 'lib/breach_mitigation/masking_secrets.rb', line 12

def masked_authenticity_token(session)
  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
  encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
  masked_token = one_time_pad + encrypted_csrf_token
  Base64.strict_encode64(masked_token)
end

.valid_authenticity_token?(session, encoded_masked_token) ⇒ Boolean

Checks the client’s masked token to see if it matches the session token. Essentially the inverse of masked_authenticity_token.

Returns:

  • (Boolean)


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
# File 'lib/breach_mitigation/masking_secrets.rb', line 22

def valid_authenticity_token?(session, encoded_masked_token)
  return false if encoded_masked_token.nil? || encoded_masked_token.empty?

  begin
    masked_token = Base64.strict_decode64(encoded_masked_token)
  rescue ArgumentError # encoded_masked_token is invalid Base64
    return false
  end

  # See if it's actually a masked token or not. In order to
  # deploy this code, we should be able to handle any unmasked
  # tokens that we've issued without error.

  if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
    # This is actually an unmasked token. This is expected if
    # you have just upgraded to masked tokens, but should stop
    # happening shortly after installing this gem
    compare_with_real_token masked_token, session

  elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
    # Split the token into the one-time pad and the encrypted
    # value and decrypt it
    one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
    encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
    csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)

    compare_with_real_token csrf_token, session

  else
    false # Token is malformed
  end
end