class Rack::Protection::EncryptedCookie


})
}.new
def decode(str); str.reverse; end
def encode(str); str.reverse; end
:coder => Class.new {
Rack::Protection::EncryptedCookie.new(application, {
Example of a cookie with custom encoding:
})
:coder => Rack::Protection::EncryptedCookie::Identity.new
Rack::Protection::EncryptedCookie.new(application, {
Example of a cookie with no encoding:
})
legacy_hmac: OpenSSL::Digest::SHA256
# legacy_hmac will default to OpenSSL::Digest::SHA1
legacy_hmac_coder: Rack::Protection::EncryptedCookie::Identity.new,
# legacy_hmac_coder will default to Rack::Protection::EncryptedCookie::Base64::Marshal
legacy_hmac_secret: ‘legacy secret’,
# The secret used for legacy HMAC cookies
Rack::Protection:EncryptedCookie.new(application, {
Example using legacy HMAC options
All parameters are optional.
:old_secret => ‘old_secret’
:secret => ‘change_me’,
:expire_after => 2592000,
:path => ‘/’,
:domain => ‘foo.com’,
:key => ‘rack.session’,
use Rack::Protection::EncryptedCookie,
Example:
coder was used for legacy session cookies.
There is also a legacy_hmac_coder option which can be set if a non-default
sessions to the new encryption scheme.
A legacy_hmac_secret is also accepted and is used to upgrade existing
The old_secret key is also accepted and allows graceful secret rotation.
When the secret key is set, cookie data is checked for data integrity.
Both methods must take a string and return a string.
session data is configurable and must respond to encode and decode.
data set to :key (default: rack.session). The object that encodes the
By default, the session is a Ruby Hash stored as base64 encoded marshalled
Rack::Protection::EncryptedCookie provides simple cookie based session management.

def delete_session(_req, _session_id, options)

def delete_session(_req, _session_id, options)
  # Nothing to do here, data is in the client
  generate_sid unless options[:drop]
end

def digest_match?(data, digest)

def digest_match?(data, digest)
  return false unless data && digest
  Rack::Utils.secure_compare(digest, generate_hmac(data))
end

def extract_session_id(request)

def extract_session_id(request)
  unpacked_cookie_data(request)['session_id']
end

def find_session(req, _sid)

def find_session(req, _sid)
  data = unpacked_cookie_data(req)
  data = persistent_session_id!(data)
  [data['session_id'], data]
end

def generate_hmac(data)

def generate_hmac(data)
  OpenSSL::HMAC.hexdigest(@legacy_hmac.new, @legacy_hmac_secret, data)
end

def initialize(app, options = {})

def initialize(app, options = {})
  # Assume keys are hex strings and convert them to raw byte strings for
  # actual key material
  @secrets = options.values_at(:secret, :old_secret).compact.map do |secret|
    [secret].pack('H*')
  end
  warn <<-MSG unless secure?(options)
  SECURITY WARNING: No secret option provided to Rack::Protection::EncryptedCookie.
  This poses a security threat. It is strongly recommended that you
  provide a secret to prevent exploits that may be possible from crafted
  cookies. This will not be supported in future versions of Rack, and
  future versions will even invalidate your existing user cookies.
  Called from: #{caller[0]}.
  MSG
  warn <<-MSG if @secrets.first && @secrets.first.length < 32
  SECURITY WARNING: Your secret is not long enough. It must be at least
  32 bytes long and securely random. To generate such a key for use
  you can run the following command:
  ruby -rsecurerandom -e 'p SecureRandom.hex(32)'
  Called from: #{caller[0]}.
  MSG
  if options.key?(:legacy_hmac_secret)
    @legacy_hmac = options.fetch(:legacy_hmac, OpenSSL::Digest::SHA1)
    # Multiply the :digest_length: by 2 because this value is the length of
    # the digest in bytes but session digest strings are encoded as hex
    # strings
    @legacy_hmac_length = @legacy_hmac.new.digest_length * 2
    @legacy_hmac_secret = options[:legacy_hmac_secret]
    @legacy_hmac_coder  = (options[:legacy_hmac_coder] ||= Base64::Marshal.new)
  else
    @legacy_hmac = false
  end
  # If encryption is used we can just use a default Marshal encoder
  # without Base64 encoding the results.
  #
  # If no encryption is used, rely on the previous default (Base64::Marshal)
  @coder = (options[:coder] ||= (@secrets.any? ? Marshal.new : Base64::Marshal.new))
  super(app, options.merge!(cookie_only: true))
end

def persistent_session_id!(data, sid = nil)

def persistent_session_id!(data, sid = nil)
  data ||= {}
  data['session_id'] ||= sid || generate_sid
  data
end

def secure?(options)

def secure?(options)
  @secrets.size >= 1 ||
    (options[:coder] && options[:let_coder_handle_secure_encoding])
end

def unpacked_cookie_data(request)

def unpacked_cookie_data(request)
  request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
    session_data = cookie_data = request.cookies[@key]
    # Try to decrypt with the first secret, if that returns nil, try
    # with old_secret
    unless @secrets.empty?
      session_data = Rack::Protection::Encryptor.decrypt_message(cookie_data, @secrets.first)
      session_data ||= Rack::Protection::Encryptor.decrypt_message(cookie_data, @secrets[1]) if @secrets.size > 1
    end
    # If session_data is still nil, are there is a legacy HMAC
    # configured, try verify and parse the cookie that way
    if !session_data && @legacy_hmac
      digest = cookie_data.slice!(-@legacy_hmac_length..-1)
      cookie_data.slice!(-2..-1) # remove double dash
      session_data = cookie_data if digest_match?(cookie_data, digest)
      # Decode using legacy HMAC decoder
      request.set_header(k, @legacy_hmac_coder.decode(session_data) || {})
    else
      request.set_header(k, coder.decode(session_data) || {})
    end
  end
end

def write_session(req, session_id, session, _options)

def write_session(req, session_id, session, _options)
  session = session.merge('session_id' => session_id)
  session_data = coder.encode(session)
  unless @secrets.empty?
    session_data = Rack::Protection::Encryptor.encrypt_message(session_data, @secrets.first)
  end
  if session_data.size > (4096 - @key.size)
    req.get_header(RACK_ERRORS).puts('Warning! Rack::Protection::EncryptedCookie data size exceeds 4K.')
    nil
  else
    session_data
  end
end