/usr/lib/ruby/vendor_ruby/merb-core/dispatch/session/cookie.rb is in ruby-merb-core 1.1.3+dfsg-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 100 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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | require 'base64' # to convert Marshal.dump to ASCII
require 'openssl' # to generate the HMAC message digest
module Merb
# If you have more than 4K of session data or don't want your data to be
# visible to the user, pick another session store.
#
# CookieOverflow is raised if you attempt to store more than 4K of data.
# TamperedWithCookie is raised if the data integrity check fails.
#
# A message digest is included with the cookie to ensure data integrity:
# a user cannot alter session data without knowing the secret key included
# in the hash.
#
# To use Cookie Sessions, set in config/merb.yml
# :session_secret_key - your secret digest key
# :session_store - cookie
class CookieSession < SessionContainer
# TODO (maybe):
# include request ip address
# AES encrypt marshaled data
# Raised when storing more than 4K of session data.
class CookieOverflow < StandardError; end
# Raised when the cookie fails its integrity check.
class TamperedWithCookie < StandardError; end
# Cookies can typically store 4096 bytes.
MAX = 4096
DIGEST = OpenSSL::Digest::Digest.new('SHA1') # or MD5, RIPEMD160, SHA256?
# :api: private
attr_accessor :_original_session_data
# The session store type
self.session_store_type = :cookie
class << self
# Generates a new session ID and creates a new session.
#
# ==== Returns
# SessionContainer:: The new session.
#
# :api: private
def generate
self.new(Merb::SessionMixin.rand_uuid, "", Merb::Request._session_secret_key)
end
# Set up a new session on request: make it available on request instance.
#
# ==== Parameters
# request<Merb::Request>:: The Merb::Request that came in from Rack.
#
# ==== Returns
# SessionContainer:: a SessionContainer. If no sessions were found,
# a new SessionContainer will be generated.
#
# :api: private
def setup(request)
session = self.new(Merb::SessionMixin.rand_uuid,
request.session_cookie_value, request._session_secret_key)
session._original_session_data = session.to_cookie
request.session = session
end
end
# ==== Parameters
# session_id<String>:: A unique identifier for this session.
# cookie<String>:: The raw cookie data.
# secret<String>:: A session secret.
#
# ==== Raises
# ArgumentError:: blank or insufficiently long secret.
#
# :api: private
def initialize(session_id, cookie, secret)
super session_id
if secret.blank? || secret.length < 16
msg = "You must specify a session_secret_key in your init file, and it must be at least 16 characters"
Merb.logger.warn(msg)
raise ArgumentError, msg
end
@secret = secret
self.update(unmarshal(cookie))
end
# Teardown and/or persist the current session.
#
# If @_destroy is true, clear out the session completely, including
# removal of the session cookie itself.
#
# ==== Parameters
# request<Merb::Request>:: request object created from Rack environment.
#
# :api: private
def finalize(request)
if @_destroy
request.destroy_session_cookie
elsif _original_session_data != (new_session_data = self.to_cookie)
request.set_session_cookie_value(new_session_data)
end
end
# Regenerate the session_id.
#
# :api: private
def regenerate
self.session_id = Merb::SessionMixin.rand_uuid
end
# Create the raw cookie string; includes an HMAC keyed message digest.
#
# ==== Returns
# String:: Cookie value.
#
# ==== Raises
# CookieOverflow:: More than 4K of data put into session.
#
# ==== Notes
# Session data is converted to a Hash first, since a container might
# choose to marshal it, which would make it persist
# attributes like 'needs_new_cookie', which it shouldn't.
#
# :api: private
def to_cookie
unless self.empty?
data = self.serialize
value = Merb::Parse.escape "#{data}--#{generate_digest(data)}"
if value.size > MAX
msg = "Cookies have limit of 4K. Session contents: #{data.inspect}"
Merb.logger.error!(msg)
raise CookieOverflow, msg
end
value
end
end
private
# Generate the HMAC keyed message digest. Uses SHA1.
#
# ==== Returns
# String:: an HMAC digest of the cookie data.
#
# :api: private
def generate_digest(data)
OpenSSL::HMAC.hexdigest(DIGEST, @secret, data)
end
# Securely compare two digests using a constant time algorithm.
# This avoids leaking information about the calculated HMAC
#
# Based on code by Michael Koziarski <michael@koziarski.com>
# http://github.com/rails/rails/commit/674f780d59a5a7ec0301755d43a7b277a3ad2978
#
# ==== Parameters
# a, b<~to_s>:: digests to compare.
#
# ==== Returns
# Boolean:: Do the digests validate?
def secure_compare(a, b)
if a.length == b.length
# unpack to forty characters.
# needed for 1.8 and 1.9 compat
a_bytes = a.unpack('C*')
b_bytes = b.unpack('C*')
result = 0
for i in 0..(a_bytes.length - 1)
result |= a_bytes[i] ^ b_bytes[i]
end
result == 0
else
false
end
end
# Unmarshal cookie data to a hash and verify its integrity.
#
# ==== Parameters
# cookie<~to_s>:: The cookie to unmarshal.
#
# ==== Raises
# TamperedWithCookie:: The digests don't match.
#
# ==== Returns
# Hash:: The stored session data.
#
# :api: private
def unmarshal(cookie)
if cookie.blank?
{}
else
data, digest = Merb::Parse.unescape(cookie).split('--')
return {} if data.blank? || digest.blank?
unless secure_compare(generate_digest(data), digest)
clear
unless Merb::Config[:ignore_tampered_cookies]
raise TamperedWithCookie, "Maybe the site's session_secret_key has changed?"
end
end
unserialize(data)
end
end
protected
# Serialize current session data as a Hash.
# Uses Base64 encoding for integrity.
#
# ==== Returns
# String:: Base64 encoded dump of the session hash.
#
# :api: private
def serialize
Base64.encode64(Marshal.dump(self.to_hash)).chop
end
# Unserialize the raw cookie data to a Hash
#
# ==== Returns
# Hash:: the session hash Base64 decoded from the data dump.
#
# :api: private
def unserialize(data)
Marshal.load(Base64.decode64(data)) rescue {}
end
end
end
|