encrypted_cookie 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|