encrypted_cookie 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.markdown +29 -8
- data/Rakefile +2 -1
- data/encrypted_cookie.gemspec +23 -14
- data/lib/encrypted_cookie.rb +42 -56
- data/lib/encrypted_cookie/encryptor.rb +123 -0
- data/spec/encrypted_cookie_spec.rb +47 -23
- metadata +89 -70
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0352358d7b487ba0caebdc794eb34832213b01a8
|
4
|
+
data.tar.gz: 017a9b0e14be94789516403de3e3a13c511b4445
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c011384dc4191e17eec7e598b6b89b5e4c327202b9b78decb8d26dcf2f8fceb0416c8c3182b744bd17819f188f09c56dc5add8a64542d2792e9e4b8d7103935f
|
7
|
+
data.tar.gz: 59f8667546f117c5786e8ed9230f8757bf91cc4f012a5975440c3dce5fb18054eeda0d08ade721190a6392e95f9c8f64f7640fe5e386851a184bf220b356cdb0
|
data/README.markdown
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
## Encrypted session cookies for Rack (and therefore Sinatra)
|
2
2
|
|
3
|
-
The `encrypted_cookie` gem provides
|
3
|
+
The `encrypted_cookie` gem provides 256-bit-AES-encrypted, tamper-proof cookies
|
4
4
|
for Rack through the class `Rack::Session::EncryptedCookie`.
|
5
5
|
|
6
6
|
## How to use encrypted\_cookie
|
@@ -20,16 +20,37 @@ Sinatra example:
|
|
20
20
|
"session: " + session.inspect
|
21
21
|
end
|
22
22
|
|
23
|
-
_*_ Your `:secret` must be at least
|
23
|
+
_*_ Your `:secret` must be at least 32 bytes long and should be really random.
|
24
|
+
Don't use a password or passphrase, generate something random (see below).
|
24
25
|
|
25
26
|
## Encryption and integrity protection
|
26
27
|
|
27
|
-
The cookie
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
The cookie is encrypted with 256-bit AES in CBC mode (with random IV). The
|
29
|
+
encrypted cookie is then signed with a HMAC, to prevent tampering and chosen
|
30
|
+
ciphertext attacks. Any attempt at tampering with the cookie will reset the
|
31
|
+
user to `{}` (empty hash).
|
31
32
|
|
32
33
|
## Generating a good secret
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
Run this in a terminal and paste the output into your script:
|
36
|
+
|
37
|
+
$ ruby -rsecurerandom -e "puts SecureRandom.hex(32)"
|
38
|
+
|
39
|
+
## Developing
|
40
|
+
|
41
|
+
To get the specs running:
|
42
|
+
|
43
|
+
```bash
|
44
|
+
$ cd path-to-clone
|
45
|
+
$ gem install bundler # if not already installed
|
46
|
+
$ bundle install
|
47
|
+
$ bundle exec rspec
|
48
|
+
```
|
49
|
+
|
50
|
+
# Thanks
|
51
|
+
|
52
|
+
- [@namelessjon](https://github.com/namelessjon) - Jon - For the massive crypto improvements!
|
53
|
+
- [@mkristian](https://github.com/mkristian) - Christian Meier
|
54
|
+
- [@danp](https://github.com/danp) - Dan Peterson
|
55
|
+
- [@stmllr](https://github.com/stmllr) - Steffen Müller
|
56
|
+
- [@andrhamm](https://github.com/andrhamm) - Andrew Hammond
|
data/Rakefile
CHANGED
@@ -8,5 +8,6 @@ Echoe.new('encrypted_cookie', '0.0.4') do |p|
|
|
8
8
|
p.author = "Christian von Kleist"
|
9
9
|
p.email = "cvonkleist at-a-place-called gmail.com"
|
10
10
|
p.ignore_pattern = ["tmp/*", "script/*"]
|
11
|
-
p.
|
11
|
+
p.runtime_dependencies = ["rack >=1.1 <3"]
|
12
|
+
p.development_dependencies = ["rack-test ~>0.6.2", "sinatra ~>1.3.4", "rspec ~>2.14.1"]
|
12
13
|
end
|
data/encrypted_cookie.gemspec
CHANGED
@@ -1,32 +1,41 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
|
-
s.name =
|
5
|
-
s.version = "0.0.
|
4
|
+
s.name = "encrypted_cookie"
|
5
|
+
s.version = "0.0.5"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Christian von Kleist"]
|
9
|
-
s.
|
10
|
-
s.
|
11
|
-
s.
|
12
|
-
s.email = %q{cvonkleist at-a-place-called gmail.com}
|
9
|
+
s.date = "2017-11-27"
|
10
|
+
s.description = "Encrypted session cookies for Rack"
|
11
|
+
s.email = "cvonkleist at-a-place-called gmail.com"
|
13
12
|
s.extra_rdoc_files = ["README.markdown", "lib/encrypted_cookie.rb"]
|
14
|
-
s.files = ["Manifest", "README.markdown", "Rakefile", "encrypted_cookie.gemspec", "lib/encrypted_cookie.rb", "spec/encrypted_cookie_spec.rb"]
|
15
|
-
s.homepage =
|
13
|
+
s.files = ["Manifest", "README.markdown", "Rakefile", "encrypted_cookie.gemspec", "lib/encrypted_cookie.rb", "lib/encrypted_cookie/encryptor.rb", "spec/encrypted_cookie_spec.rb"]
|
14
|
+
s.homepage = "http://github.com/cvonkleist/encrypted_cookie"
|
16
15
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Encrypted_cookie", "--main", "README.markdown"]
|
17
16
|
s.require_paths = ["lib"]
|
18
|
-
s.rubyforge_project =
|
19
|
-
s.rubygems_version =
|
20
|
-
s.
|
21
|
-
s.summary = %q{Encrypted session cookies for Rack}
|
17
|
+
s.rubyforge_project = "encrypted_cookie"
|
18
|
+
s.rubygems_version = "2.0.3"
|
19
|
+
s.summary = "Encrypted session cookies for Rack"
|
22
20
|
|
23
21
|
if s.respond_to? :specification_version then
|
24
|
-
|
25
|
-
s.specification_version = 3
|
22
|
+
s.specification_version = 4
|
26
23
|
|
27
24
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
25
|
+
s.add_runtime_dependency(%q<rack>, ["< 3", ">= 1.1"])
|
26
|
+
s.add_development_dependency(%q<rack-test>, ["~> 0.6.2"])
|
27
|
+
s.add_development_dependency(%q<sinatra>, ["~> 1.3.4"])
|
28
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.14.1"])
|
28
29
|
else
|
30
|
+
s.add_dependency(%q<rack>, ["< 3", ">= 1.1"])
|
31
|
+
s.add_dependency(%q<rack-test>, ["~> 0.6.2"])
|
32
|
+
s.add_dependency(%q<sinatra>, ["~> 1.3.4"])
|
33
|
+
s.add_dependency(%q<rspec>, ["~> 2.14.1"])
|
29
34
|
end
|
30
35
|
else
|
36
|
+
s.add_dependency(%q<rack>, ["< 3", ">= 1.1"])
|
37
|
+
s.add_dependency(%q<rack-test>, ["~> 0.6.2"])
|
38
|
+
s.add_dependency(%q<sinatra>, ["~> 1.3.4"])
|
39
|
+
s.add_dependency(%q<rspec>, ["~> 2.14.1"])
|
31
40
|
end
|
32
41
|
end
|
data/lib/encrypted_cookie.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require 'openssl'
|
2
1
|
require 'rack/request'
|
3
2
|
require 'rack/response'
|
3
|
+
require 'encrypted_cookie/encryptor'
|
4
4
|
|
5
5
|
module Rack
|
6
6
|
|
7
7
|
module Session
|
8
8
|
|
9
|
-
# Rack::Session::EncryptedCookie provides AES-
|
9
|
+
# Rack::Session::EncryptedCookie provides AES-256-encrypted, tamper-proof
|
10
10
|
# cookie-based session management.
|
11
11
|
#
|
12
|
-
# The session is Marshal'd, HMAC'd
|
12
|
+
# The session is Marshal'd, encrypted and HMAC'd.
|
13
13
|
#
|
14
14
|
# Example:
|
15
15
|
#
|
@@ -19,11 +19,22 @@ module Rack
|
|
19
19
|
# :domain => 'foo.com',
|
20
20
|
# :path => '/',
|
21
21
|
# :expire_after => 2592000
|
22
|
+
# :time_to_live => 600
|
22
23
|
#
|
23
24
|
# All parameters are optional except :secret.
|
24
|
-
|
25
|
+
#
|
26
|
+
# The default for the session time-to-live is 30 minutes. You can set
|
27
|
+
# the timeout on per session base by adding the expiration time in the
|
28
|
+
# session:
|
29
|
+
# session[Rack::Session::EncryptedCookie::EXPIRES] = Time.now + 120
|
30
|
+
#
|
31
|
+
# Note that you shouldn't trust the expire_after parameter in the cookie
|
32
|
+
# for session expiry as that can be altered by the recipient. Instead,
|
33
|
+
# use time_to_live which is server side check.
|
25
34
|
class EncryptedCookie
|
26
35
|
|
36
|
+
EXPIRES = '_encrypted_cookie_expires_'
|
37
|
+
|
27
38
|
def initialize(app, options={})
|
28
39
|
@app = app
|
29
40
|
@key = options[:key] || "rack.session"
|
@@ -31,7 +42,9 @@ module Rack
|
|
31
42
|
fail "Error! A secret is required to use encrypted cookies. Do something like this:\n\nuse Rack::Session::EncryptedCookie, :secret => YOUR_VERY_LONG_VERY_RANDOM_SECRET_KEY_HERE" unless @secret
|
32
43
|
@default_options = {:domain => nil,
|
33
44
|
:path => "/",
|
45
|
+
:time_to_live => 1800,
|
34
46
|
:expire_after => nil}.merge(options)
|
47
|
+
@encryptor = Encryptor.new(@secret)
|
35
48
|
end
|
36
49
|
|
37
50
|
def call(env)
|
@@ -42,40 +55,45 @@ module Rack
|
|
42
55
|
|
43
56
|
private
|
44
57
|
|
58
|
+
def remove_expiration(session_data)
|
59
|
+
expires = session_data.delete(EXPIRES)
|
60
|
+
if expires and expires < Time.now
|
61
|
+
session_data.clear
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
45
65
|
def load_session(env)
|
46
66
|
request = Rack::Request.new(env)
|
67
|
+
env["rack.session.options"] = @default_options.dup
|
68
|
+
|
47
69
|
session_data = request.cookies[@key]
|
70
|
+
session_data = @encryptor.decrypt(session_data)
|
71
|
+
session_data = Marshal.load(session_data)
|
72
|
+
remove_expiration(session_data)
|
48
73
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
end
|
74
|
+
env["rack.session"] = session_data
|
75
|
+
rescue
|
76
|
+
env["rack.session"] = Hash.new
|
77
|
+
end
|
55
78
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
rescue
|
61
|
-
env["rack.session"] = Hash.new
|
79
|
+
def add_expiration(session_data, options)
|
80
|
+
if options[:time_to_live] && !session_data.key?(EXPIRES)
|
81
|
+
expires = Time.now + options[:time_to_live]
|
82
|
+
session_data.merge!({EXPIRES => expires})
|
62
83
|
end
|
63
|
-
|
64
|
-
env["rack.session.options"] = @default_options.dup
|
65
84
|
end
|
66
85
|
|
67
86
|
def commit_session(env, status, headers, body)
|
68
|
-
|
69
|
-
session_data = [session_data].pack("m*")
|
87
|
+
options = env["rack.session.options"]
|
70
88
|
|
71
|
-
session_data = "
|
72
|
-
|
73
|
-
session_data =
|
89
|
+
session_data = env["rack.session"]
|
90
|
+
add_expiration(session_data, options)
|
91
|
+
session_data = Marshal.dump(session_data)
|
92
|
+
session_data = @encryptor.encrypt(session_data)
|
74
93
|
|
75
94
|
if session_data.size > (4096 - @key.size)
|
76
95
|
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
|
77
96
|
else
|
78
|
-
options = env["rack.session.options"]
|
79
97
|
cookie = Hash.new
|
80
98
|
cookie[:value] = session_data
|
81
99
|
cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
|
@@ -84,38 +102,6 @@ module Rack
|
|
84
102
|
|
85
103
|
[status, headers, body]
|
86
104
|
end
|
87
|
-
|
88
|
-
def generate_hmac(data)
|
89
|
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
|
90
|
-
end
|
91
|
-
|
92
|
-
def encrypt(str)
|
93
|
-
aes = OpenSSL::Cipher::Cipher.new('aes-128-cbc').encrypt
|
94
|
-
aes.key = @secret
|
95
|
-
iv = OpenSSL::Random.random_bytes(aes.iv_len)
|
96
|
-
aes.iv = iv
|
97
|
-
[iv + (aes.update(str) << aes.final)].pack('m0')
|
98
|
-
end
|
99
|
-
|
100
|
-
# decrypts string. returns nil if an error occurs
|
101
|
-
#
|
102
|
-
# returns nil if openssl raises an error during decryption (likely
|
103
|
-
# someone is tampering with the session data, or the sinatra user was
|
104
|
-
# previously using Cookie and has just switched to EncryptedCookie), and
|
105
|
-
# will also return nil if the text to decrypt is too short to possibly be
|
106
|
-
# good aes data.
|
107
|
-
def decrypt(str)
|
108
|
-
str = str.unpack('m0').first
|
109
|
-
aes = OpenSSL::Cipher::Cipher.new('aes-128-cbc').decrypt
|
110
|
-
aes.key = @secret
|
111
|
-
iv = str[0, aes.iv_len]
|
112
|
-
aes.iv = iv
|
113
|
-
crypted_text = str[aes.iv_len..-1]
|
114
|
-
return nil if crypted_text.nil? || iv.nil?
|
115
|
-
aes.update(crypted_text) << aes.final
|
116
|
-
rescue
|
117
|
-
nil
|
118
|
-
end
|
119
105
|
end
|
120
106
|
end
|
121
107
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'rack/utils'
|
3
|
+
module Rack
|
4
|
+
module Session
|
5
|
+
class EncryptedCookie
|
6
|
+
# Encrypts messages with authentication
|
7
|
+
#
|
8
|
+
# The use of authentication is essential to avoid Chosen Ciphertext
|
9
|
+
# Attacks. By using this in an encrypt then MAC form, we avoid some
|
10
|
+
# attacks such as e.g. being used as a CBC padding oracle to decrypt
|
11
|
+
# the ciphertext.
|
12
|
+
class Encryptor
|
13
|
+
# Create the encryptor
|
14
|
+
#
|
15
|
+
# Pass in the secret, which should be at least 32-bytes worth of
|
16
|
+
# entropy, e.g. a string generated by `SecureRandom.hex(32)`.
|
17
|
+
# This also allows specification of the algorithm for the cipher
|
18
|
+
# and MAC. But don't change that unless you're very sure.
|
19
|
+
def initialize(secret, cipher = 'aes-256-cbc', hmac = 'SHA256')
|
20
|
+
@cipher = cipher
|
21
|
+
@hmac = hmac
|
22
|
+
|
23
|
+
# use the HMAC to derive two independent keys for the encryption and
|
24
|
+
# authentication of ciphertexts It is bad practice to use the same key
|
25
|
+
# for encryption and authentication. This also allows us to use all
|
26
|
+
# of the entropy in a long key (e.g. 64 hex bytes) when straight
|
27
|
+
# assignement would could result in assigning a key with a much
|
28
|
+
# reduced key space. Also, the personalisation strings further help
|
29
|
+
# reduce the possibility of key reuse by ensuring it should be unique
|
30
|
+
# to this gem, even with shared secrets.
|
31
|
+
@encryption_key = hmac("EncryptedCookie Encryption", secret)
|
32
|
+
@authentication_key = hmac("EncryptedCookie Authentication", secret)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Encrypts message
|
36
|
+
#
|
37
|
+
# Returns the base64 encoded ciphertext plus IV. In addtion, the
|
38
|
+
# message is prepended with a MAC code to prevent chosen ciphertext
|
39
|
+
# attacks.
|
40
|
+
def encrypt(message)
|
41
|
+
# encrypt the message
|
42
|
+
encrypted = encrypt_message(message)
|
43
|
+
|
44
|
+
[authenticate_message(encrypted) + encrypted].pack('m0')
|
45
|
+
end
|
46
|
+
|
47
|
+
# decrypts base64 encoded ciphertext
|
48
|
+
#
|
49
|
+
# First, it checks the message tag and returns nil if that fails to verify.
|
50
|
+
# Otherwise, the data is passed on to the AES function for decryption.
|
51
|
+
def decrypt(ciphertext)
|
52
|
+
ciphertext = ciphertext.unpack('m').first
|
53
|
+
tag = ciphertext[0, hmac_length]
|
54
|
+
ciphertext = ciphertext[hmac_length..-1]
|
55
|
+
|
56
|
+
# make sure we actually had enough data for the tag too.
|
57
|
+
if tag && ciphertext && verify_message(tag, ciphertext)
|
58
|
+
decrypt_ciphertext(ciphertext)
|
59
|
+
else
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# HMAC digest of the message using the given secret
|
67
|
+
def hmac(secret, message)
|
68
|
+
OpenSSL::HMAC.digest(@hmac, secret, message)
|
69
|
+
end
|
70
|
+
|
71
|
+
def hmac_length
|
72
|
+
OpenSSL::Digest.new(@hmac).size
|
73
|
+
end
|
74
|
+
|
75
|
+
# returns the message authentication tag
|
76
|
+
#
|
77
|
+
# This is computed as HMAC(authentication_key, message)
|
78
|
+
def authenticate_message(message)
|
79
|
+
hmac(@authentication_key, message)
|
80
|
+
end
|
81
|
+
|
82
|
+
# verifies the message
|
83
|
+
#
|
84
|
+
# This does its best to be constant time, by use of the rack secure compare
|
85
|
+
# function.
|
86
|
+
def verify_message(tag, message)
|
87
|
+
own_tag = authenticate_message(message)
|
88
|
+
Rack::Utils.secure_compare(tag, own_tag)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Encrypt
|
92
|
+
#
|
93
|
+
# Encrypts the given message with a random IV, then returns the ciphertext
|
94
|
+
# with the IV prepended.
|
95
|
+
def encrypt_message(message)
|
96
|
+
aes = OpenSSL::Cipher.new(@cipher).encrypt
|
97
|
+
aes.key = @encryption_key
|
98
|
+
iv = aes.random_iv
|
99
|
+
aes.iv = iv
|
100
|
+
iv + (aes.update(message) << aes.final)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Decrypt
|
104
|
+
#
|
105
|
+
# Pulls the IV off the front of the message and decrypts. Catches
|
106
|
+
# OpenSSL errors and returns nil. But this should never happen, as the
|
107
|
+
# verify method should catch all corrupted ciphertexts.
|
108
|
+
def decrypt_ciphertext(ciphertext)
|
109
|
+
aes = OpenSSL::Cipher.new(@cipher).decrypt
|
110
|
+
aes.key = @encryption_key
|
111
|
+
iv = ciphertext[0, aes.iv_len]
|
112
|
+
aes.iv = iv
|
113
|
+
crypted_text = ciphertext[aes.iv_len..-1]
|
114
|
+
return nil if crypted_text.nil? || iv.nil?
|
115
|
+
aes.update(crypted_text) << aes.final
|
116
|
+
rescue
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -1,20 +1,36 @@
|
|
1
1
|
require 'rack/test'
|
2
|
-
require 'sinatra'
|
2
|
+
require 'sinatra/base'
|
3
3
|
require 'rspec'
|
4
4
|
require 'cgi'
|
5
|
-
require
|
5
|
+
require './lib/encrypted_cookie'
|
6
6
|
|
7
7
|
include Rack::Session
|
8
8
|
|
9
|
+
|
10
|
+
module Helper
|
11
|
+
def unpack_cookie
|
12
|
+
data = last_response.headers['Set-Cookie'][/rack.session=(.*?);/, 1]
|
13
|
+
data = data.split('--').first
|
14
|
+
str = CGI.unescape(data).unpack('m').first
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
9
19
|
RSpec.configure do |conf|
|
10
20
|
conf.include Rack::Test::Methods
|
21
|
+
conf.include Helper
|
11
22
|
end
|
12
23
|
|
13
24
|
class EncryptedApp < Sinatra::Application
|
14
|
-
|
25
|
+
disable :show_exceptions
|
26
|
+
use Rack::Session::EncryptedCookie, :secret => 'foo' * 10, :time_to_live => 0.1
|
15
27
|
get '/' do
|
16
28
|
"session: " + session.inspect
|
17
29
|
end
|
30
|
+
get '/timeout/:value' do
|
31
|
+
session[Rack::Session::EncryptedCookie::EXPIRES] = Time.now + params[:value].to_i
|
32
|
+
"timeout set"
|
33
|
+
end
|
18
34
|
get '/set/:key/:value' do
|
19
35
|
session[params[:key]] = params[:value]
|
20
36
|
"all set"
|
@@ -23,8 +39,10 @@ end
|
|
23
39
|
|
24
40
|
# this app has cookie integrity protection, but not encryption
|
25
41
|
class UnencryptedApp < Sinatra::Application
|
42
|
+
disable :show_exceptions
|
26
43
|
use Rack::Session::Cookie, :secret => 'foo' * 10
|
27
44
|
get '/' do
|
45
|
+
session[:foo] = 'bar'
|
28
46
|
"session: " + session.inspect
|
29
47
|
end
|
30
48
|
end
|
@@ -32,7 +50,7 @@ end
|
|
32
50
|
describe EncryptedCookie do
|
33
51
|
it 'should fail if no secret is specified' do
|
34
52
|
lambda { EncryptedCookie.new(nil) }.should raise_error(/A secret is required/)
|
35
|
-
lambda { EncryptedCookie.new(nil, :secret => 'foo') }.should_not raise_error
|
53
|
+
lambda { EncryptedCookie.new(nil, :secret => 'foo') }.should_not raise_error
|
36
54
|
end
|
37
55
|
end
|
38
56
|
|
@@ -51,18 +69,6 @@ describe EncryptedApp do
|
|
51
69
|
get '/'
|
52
70
|
last_response.body.should == 'session: {"foo"=>"bar"}'
|
53
71
|
last_response.headers['Set-Cookie'].should_not include("BAh7AA")
|
54
|
-
|
55
|
-
data = last_response.headers['Set-Cookie'][/rack.session=(.*?);/, 1]
|
56
|
-
str = CGI.unescape(data).unpack('m0').first
|
57
|
-
aes = OpenSSL::Cipher::Cipher.new('aes-128-cbc').decrypt
|
58
|
-
aes.key = 'foo' * 10
|
59
|
-
aes.iv = str[0, aes.iv_len]
|
60
|
-
crypted_text = str[aes.iv_len..-1]
|
61
|
-
|
62
|
-
plaintext = (aes.update(crypted_text) << aes.final)
|
63
|
-
base64_marshal_data, hmac = plaintext.split('--')
|
64
|
-
session_hash = Marshal.load(base64_marshal_data.unpack('m0').first)
|
65
|
-
session_hash.should == {"foo" => "bar"}
|
66
72
|
end
|
67
73
|
it "should make encrypted session data that can't be decrypted with the wrong key" do
|
68
74
|
get '/set/foo/bar'
|
@@ -71,12 +77,11 @@ describe EncryptedApp do
|
|
71
77
|
last_response.body.should == 'session: {"foo"=>"bar"}'
|
72
78
|
last_response.headers['Set-Cookie'].should_not include("BAh7AA")
|
73
79
|
|
74
|
-
data =
|
75
|
-
|
76
|
-
aes = OpenSSL::Cipher::Cipher.new('aes-128-cbc').decrypt
|
80
|
+
data = unpack_cookie
|
81
|
+
aes = OpenSSL::Cipher.new('aes-128-cbc').decrypt
|
77
82
|
aes.key = 'bar' * 10
|
78
|
-
iv =
|
79
|
-
crypted_text =
|
83
|
+
iv = data[0, aes.iv_len]
|
84
|
+
crypted_text = data[aes.iv_len..-1]
|
80
85
|
|
81
86
|
lambda { plaintext = (aes.update(crypted_text) << aes.final) }.should raise_error("bad decrypt")
|
82
87
|
end
|
@@ -98,6 +103,25 @@ describe EncryptedApp do
|
|
98
103
|
get '/'
|
99
104
|
last_response.body.should == 'session: {}'
|
100
105
|
end
|
106
|
+
it "should reset the session on timeout" do
|
107
|
+
get '/set/foo/bar'
|
108
|
+
last_response.body.should == 'all set'
|
109
|
+
get '/'
|
110
|
+
last_response.body.should == 'session: {"foo"=>"bar"}'
|
111
|
+
sleep 0.08
|
112
|
+
get '/'
|
113
|
+
last_response.body.should == 'session: {"foo"=>"bar"}'
|
114
|
+
sleep 0.08
|
115
|
+
get '/'
|
116
|
+
last_response.body.should == 'session: {"foo"=>"bar"}'
|
117
|
+
sleep 0.1
|
118
|
+
get '/'
|
119
|
+
last_response.body.should == 'session: {}'
|
120
|
+
get '/timeout/0'
|
121
|
+
last_response.body.should == 'timeout set'
|
122
|
+
get '/'
|
123
|
+
last_response.body.should == 'session: {}'
|
124
|
+
end
|
101
125
|
end
|
102
126
|
|
103
127
|
describe UnencryptedApp do
|
@@ -106,7 +130,7 @@ describe UnencryptedApp do
|
|
106
130
|
end
|
107
131
|
it "should include unencrypted marshal'd data" do
|
108
132
|
get '/'
|
109
|
-
|
110
|
-
|
133
|
+
cookie = Marshal.load(unpack_cookie)
|
134
|
+
cookie['foo'].should == 'bar'
|
111
135
|
end
|
112
136
|
end
|
metadata
CHANGED
@@ -1,100 +1,119 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: encrypted_cookie
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease: false
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 4
|
10
|
-
version: 0.0.4
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Christian von Kleist
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
|
-
cert_chain:
|
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
|
-
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "<"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '1.1'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "<"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.1'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rack-test
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.6.2
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.6.2
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sinatra
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.3.4
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.3.4
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 2.14.1
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 2.14.1
|
43
75
|
description: Encrypted session cookies for Rack
|
44
76
|
email: cvonkleist at-a-place-called gmail.com
|
45
77
|
executables: []
|
46
|
-
|
47
78
|
extensions: []
|
48
|
-
|
49
|
-
extra_rdoc_files:
|
79
|
+
extra_rdoc_files:
|
50
80
|
- README.markdown
|
51
81
|
- lib/encrypted_cookie.rb
|
52
|
-
files:
|
82
|
+
files:
|
53
83
|
- Manifest
|
54
84
|
- README.markdown
|
55
85
|
- Rakefile
|
56
86
|
- encrypted_cookie.gemspec
|
57
87
|
- lib/encrypted_cookie.rb
|
88
|
+
- lib/encrypted_cookie/encryptor.rb
|
58
89
|
- spec/encrypted_cookie_spec.rb
|
59
|
-
has_rdoc: true
|
60
90
|
homepage: http://github.com/cvonkleist/encrypted_cookie
|
61
91
|
licenses: []
|
62
|
-
|
92
|
+
metadata: {}
|
63
93
|
post_install_message:
|
64
|
-
rdoc_options:
|
65
|
-
- --line-numbers
|
66
|
-
- --inline-source
|
67
|
-
- --title
|
94
|
+
rdoc_options:
|
95
|
+
- "--line-numbers"
|
96
|
+
- "--inline-source"
|
97
|
+
- "--title"
|
68
98
|
- Encrypted_cookie
|
69
|
-
- --main
|
99
|
+
- "--main"
|
70
100
|
- README.markdown
|
71
|
-
require_paths:
|
101
|
+
require_paths:
|
72
102
|
- lib
|
73
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
-
|
75
|
-
requirements:
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
76
105
|
- - ">="
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
version: "0"
|
82
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
-
none: false
|
84
|
-
requirements:
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
85
110
|
- - ">="
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
|
88
|
-
segments:
|
89
|
-
- 1
|
90
|
-
- 2
|
91
|
-
version: "1.2"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '1.2'
|
92
113
|
requirements: []
|
93
|
-
|
94
114
|
rubyforge_project: encrypted_cookie
|
95
|
-
rubygems_version:
|
115
|
+
rubygems_version: 2.4.5.1
|
96
116
|
signing_key:
|
97
|
-
specification_version:
|
117
|
+
specification_version: 4
|
98
118
|
summary: Encrypted session cookies for Rack
|
99
119
|
test_files: []
|
100
|
-
|
data.tar.gz.sig
DELETED
Binary file
|
metadata.gz.sig
DELETED
Binary file
|