rack-session-encryptedcookie 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +102 -0
- data/lib/rack/session/encryptedcookie.rb +192 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 856832c8f3652b96214a5bd20c2247ab9eef5e11
|
4
|
+
data.tar.gz: cec97853bb6a2d761fcc0b4622d6d56f441f36ef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f738b4841d55c5527f6b33b4518664452d86affee34bc7af6f802544e3fec4f633778a67faa39ef529ec3c5c51698d99d82ee041c62de8d1f91745e74b214659
|
7
|
+
data.tar.gz: ab2bc73c700c5aa333844709c57c11cefd915339830eb95cee80769f0c3a5e80391cfe39821aad901a524facb416641984413893419926215c6ffd12025dc833
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012, Tim Hentenaar
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
rack-session-encryptedcookie
|
2
|
+
============================
|
3
|
+
|
4
|
+
[![Travis CI Status](https://secure.travis-ci.org/thentenaar/rack-session-encryptedcookie.svg?branch=master)](https://travis-ci.org/thentenaar/rack-session-encryptedcookie)
|
5
|
+
|
6
|
+
Rack session handling middleware that serializes the session data into
|
7
|
+
an encrypted cookie; that's also async-aware.
|
8
|
+
|
9
|
+
This is probably not the **most** secure solution, but it's better than storing your session
|
10
|
+
data in a cookie as clear text. That being said, it's _much_ more secure to use a
|
11
|
+
pre-generated key with this module than a password-derived key, but the latter is
|
12
|
+
provided as a convenience option.
|
13
|
+
|
14
|
+
If you have strict security requirements, you really shouldn't be storing sensitive data in
|
15
|
+
the session.
|
16
|
+
|
17
|
+
Licensing
|
18
|
+
=========
|
19
|
+
|
20
|
+
This software is licensed under the [Simplified BSD License](http://en.wikipedia.org/wiki/BSD_licenses#2-clause_license_.28.22Simplified_BSD_License.22_or_.22FreeBSD_License.22.29) as described in the LICENSE file.
|
21
|
+
|
22
|
+
Requirements
|
23
|
+
============
|
24
|
+
|
25
|
+
* rack
|
26
|
+
|
27
|
+
Installation
|
28
|
+
============
|
29
|
+
|
30
|
+
gem install rack-session-encryptedcookie
|
31
|
+
|
32
|
+
Usage
|
33
|
+
=====
|
34
|
+
|
35
|
+
Just add something like this to your _config.ru_:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'rack/session/encryptedcookie'
|
39
|
+
|
40
|
+
use Rack::Session::EncryptedCookie domain: 'domain.name', salt: 'salthere', key: 'my_secret'
|
41
|
+
```
|
42
|
+
|
43
|
+
... and you can access the session hash via ``env['rack.session']`` per
|
44
|
+
usual.
|
45
|
+
|
46
|
+
The full list of options is:
|
47
|
+
| ``cookie_name`` | Cookie name (default: 'rack.session') |
|
48
|
+
| ``domain`` | Domain for the cookie (mandatory) |
|
49
|
+
| ``http_only`` | HttpOnly for the cookie |
|
50
|
+
| ``expires`` | Cookie expiry (in seconds, optional) |
|
51
|
+
| ``cipher`` | OpenSSL cipher to use (default: aes-256-cbc) |
|
52
|
+
| ``salt`` | Salt for the IV (password-derrived key) |
|
53
|
+
| ``rounds`` | Number of salting rounds (password-derrived key) |
|
54
|
+
| ``key`` | Encryption key / password for the cookie |
|
55
|
+
| ``tag_len`` | Tag length (for GCM/CCM ciphers, optional) |
|
56
|
+
|
57
|
+
Generating your own Key
|
58
|
+
=======================
|
59
|
+
|
60
|
+
You can generate a key using something like:
|
61
|
+
```ruby
|
62
|
+
SecureRandom.random_bytes(key_size_in_bytes)
|
63
|
+
```
|
64
|
+
or anything else, as long as the key is the proper size for the cipher.
|
65
|
+
|
66
|
+
Using a pre-generated Key
|
67
|
+
=========================
|
68
|
+
|
69
|
+
To use a pre-generated key, you must specify the following options:
|
70
|
+
```ruby
|
71
|
+
cipher: 'aes-256-cbc', # The cipher algorithm to use (defaults to aes-256-cbc)
|
72
|
+
key: your_key_here, # Your pre-generated key
|
73
|
+
```
|
74
|
+
|
75
|
+
Examples:
|
76
|
+
```ruby
|
77
|
+
# Using the default cipher
|
78
|
+
use Rack::Session::EncryptedCookie, key: your_key
|
79
|
+
|
80
|
+
# Using the specified cipher
|
81
|
+
use Rack::Session::EncryptedCookie, cipher: your_cipher, key: your_key
|
82
|
+
```
|
83
|
+
|
84
|
+
Using a password-derived key
|
85
|
+
=============================
|
86
|
+
|
87
|
+
You can derive a key by specifying the following options:
|
88
|
+
```ruby
|
89
|
+
cipher 'aes-256-cbc', # The cipher algorithm to use (default aes-256-cbc)
|
90
|
+
salt 'salthere', # Salt to use for key generation
|
91
|
+
rounds: 2000, # Number of cipher rounds for key generation (default: 2000)
|
92
|
+
key: 'yoursecret', # A password from which to generate the key
|
93
|
+
```
|
94
|
+
|
95
|
+
``crypto_key`` and ``salt`` must be specified in order to enable encryption.
|
96
|
+
All other options have defaults available.
|
97
|
+
|
98
|
+
Example:
|
99
|
+
```ruby
|
100
|
+
use Rack::Session::EncryptedCookie, salt: 'salthere', crypto_key: 'my_secret'
|
101
|
+
```
|
102
|
+
|
@@ -0,0 +1,192 @@
|
|
1
|
+
#
|
2
|
+
# Rack::Session::EncryptedCookie - Encrypted session middleware for Rack
|
3
|
+
#
|
4
|
+
# Copyright (C) 2013 - 2017 Tim Hentenaar. All Rights Reserved.
|
5
|
+
#
|
6
|
+
# Licensed under the Simplified BSD License.
|
7
|
+
# See the LICENSE file for details.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'rack/request'
|
11
|
+
require 'rack/utils'
|
12
|
+
require 'openssl'
|
13
|
+
|
14
|
+
module Rack
|
15
|
+
module Session
|
16
|
+
class EncryptedCookie
|
17
|
+
NOT_FOUND = [ 404, {}, [ 'Not found' ]].freeze
|
18
|
+
|
19
|
+
# @param [Hash] opts Session options
|
20
|
+
# @option opts [String] :cookie_name Cookie name
|
21
|
+
# @option opts [String] :domain Domain for the cookie
|
22
|
+
# @option opts [Boolean] :http_only HttpOnly for the cookie
|
23
|
+
# @option opts [Integer] :expires Cookie expiry (in seconds)
|
24
|
+
# @option opts [String] :cipher OpenSSL cipher to use
|
25
|
+
# @option opts [String] :salt Salt for the IV
|
26
|
+
# @optons opts [Integer] :rounds Number of salting rounds
|
27
|
+
# @option opts [String] :key Encryption key for the data
|
28
|
+
# @option opts [Integer] :tag_len Tag length (for GCM/CCM ciphers)
|
29
|
+
def initialize(app, opts={})
|
30
|
+
@app = app
|
31
|
+
@hash = {}
|
32
|
+
@opts = {
|
33
|
+
cookie_name: 'rack.session',
|
34
|
+
domain: nil,
|
35
|
+
http_only: false,
|
36
|
+
expires: (15 * 60),
|
37
|
+
cipher: 'aes-256-cbc',
|
38
|
+
salt: '3@bG>B@J5vy-FeXJ',
|
39
|
+
rounds: 2000,
|
40
|
+
key: 'r`*BqnG:c^;AL{k97=KYN!#',
|
41
|
+
tag_len: 16
|
42
|
+
}.merge(opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
def call(env)
|
46
|
+
dup.call!(env)
|
47
|
+
end
|
48
|
+
|
49
|
+
def call!(env)
|
50
|
+
@cb = env['async.callback']
|
51
|
+
env['async.callback'] = method(:save_session) if @cb
|
52
|
+
env['rack.session'] = self
|
53
|
+
load_session(env)
|
54
|
+
|
55
|
+
if @app
|
56
|
+
@cb ? @app.call(env) : save_session(@app.call(env))
|
57
|
+
else
|
58
|
+
@cb ? @cb.call(NOT_FOUND) : NOT_FOUND
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(method, *args, &block)
|
63
|
+
if @hash.respond_to?(method)
|
64
|
+
@hash.send(method, *args, &block)
|
65
|
+
else
|
66
|
+
raise ArgumentError.new("Method `#{method}` doesn't exist.")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Load the sesssion data from the cookie
|
73
|
+
# @return [Hash, nil] Session data
|
74
|
+
def load_session(env)
|
75
|
+
r = Rack::Request.new(env)
|
76
|
+
cookie = r.cookies[@opts[:cookie_name]]
|
77
|
+
return if cookie.nil?
|
78
|
+
@hash = Marshal.load(cipher(:decrypt, cookie)) rescue {}
|
79
|
+
end
|
80
|
+
|
81
|
+
# Add our cookie to the response
|
82
|
+
#
|
83
|
+
# @param [Array] r Upstream Rack response
|
84
|
+
# @return [Array] Rack response + our cookie
|
85
|
+
def save_session(r)
|
86
|
+
return r if !r.is_a?(Array) || (r.is_a?(Array) && r[0] == -1)
|
87
|
+
|
88
|
+
unless @hash.empty? || @opts[:domain].nil?
|
89
|
+
data = cipher(:encrypt, Marshal.dump(@hash)) rescue nil
|
90
|
+
c = {
|
91
|
+
value: data,
|
92
|
+
domain: @opts[:domain],
|
93
|
+
path: '/',
|
94
|
+
}
|
95
|
+
|
96
|
+
c[:httponly] = @opts[:http_only] === true
|
97
|
+
if @opts.has_key?(:expires)
|
98
|
+
c[:expires] = Time.at(Time.now + @opts[:expires])
|
99
|
+
end
|
100
|
+
|
101
|
+
r[1]['Set-Cookie'] = Rack::Utils.add_cookie_to_header(
|
102
|
+
r[1]['Set-Cookie'], @opts[:cookie_name], c
|
103
|
+
) unless data.nil?
|
104
|
+
end
|
105
|
+
|
106
|
+
@cb.call(r) if @cb
|
107
|
+
r
|
108
|
+
end
|
109
|
+
|
110
|
+
# Warn the user that en/de-cryption failed
|
111
|
+
# @param [String] e Exception message
|
112
|
+
# @return nil
|
113
|
+
def cipher_failed(e='<no message>')
|
114
|
+
warn (<<-XXX.gsub(/^\s*/, ''))
|
115
|
+
SECURITY WARNING: Session cipher failed: #{e}
|
116
|
+
XXX
|
117
|
+
return nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# Handle en/de-cryption
|
121
|
+
# @param [Symbol] :mode :encrypt or :decrypt
|
122
|
+
# @param [String] :str Data to en/de-crypt
|
123
|
+
# @return [String, nil] Encrypted data
|
124
|
+
def cipher(mode, str)
|
125
|
+
return nil if @opts[:key].nil? || str.nil?
|
126
|
+
begin
|
127
|
+
cipher = OpenSSL::Cipher::Cipher.new(@opts[:cipher])
|
128
|
+
cipher.send(mode)
|
129
|
+
rescue
|
130
|
+
return cipher_failed($!.message)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Set the key and IV
|
134
|
+
if @opts[:salt].nil?
|
135
|
+
cipher.key = @opts[:key]
|
136
|
+
else
|
137
|
+
cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(
|
138
|
+
@opts[:key], @opts[:salt],
|
139
|
+
@opts[:rounds], cipher.key_len
|
140
|
+
)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Setup the auth data for GCM/CCM
|
144
|
+
if cipher.name.match(%r{/[CG]CM$/i})
|
145
|
+
cipher.auth_data = ''
|
146
|
+
cipher.tag_len = @opts[:tag_len]
|
147
|
+
end
|
148
|
+
|
149
|
+
iv = cipher.random_iv
|
150
|
+
xstr = str
|
151
|
+
|
152
|
+
if mode == :decrypt
|
153
|
+
str = str.unpack('m').first
|
154
|
+
|
155
|
+
# Set the tag for GCM/CCM
|
156
|
+
if cipher.name.match(%r{/[CG]CM$/i})
|
157
|
+
cipher.auth_tag = str.slice!(0, cipher.tag_len).unpack('C*')
|
158
|
+
end
|
159
|
+
|
160
|
+
# Extract the iv
|
161
|
+
iv_len = iv.length
|
162
|
+
str_b,iv = Array[str[0 ... iv_len << 1].unpack('C*')].transpose.
|
163
|
+
partition.with_index { |x,i| (i&1).zero? }
|
164
|
+
iv.flatten! ; str_b.flatten!
|
165
|
+
|
166
|
+
# Set the IV and buffer
|
167
|
+
iv = iv.pack('C*')
|
168
|
+
xstr = str_b.pack('C*') + str[iv_len << 1 ... str.length]
|
169
|
+
end
|
170
|
+
|
171
|
+
# Call the cipher
|
172
|
+
r = nil
|
173
|
+
cipher.iv = iv
|
174
|
+
begin
|
175
|
+
r = cipher.update(xstr) + cipher.final
|
176
|
+
if mode == :encrypt
|
177
|
+
d = r.bytes.zip(iv.bytes).flatten.compact
|
178
|
+
if cipher.name.match(%r{/[CG]CM$/i})
|
179
|
+
d = cipher.tag.bytes + d.bytes
|
180
|
+
end
|
181
|
+
r = [d.pack('C*')].pack('m').chomp
|
182
|
+
end
|
183
|
+
rescue OpenSSL::Cipher::CipherError
|
184
|
+
return cipher_failed($!.message)
|
185
|
+
end
|
186
|
+
r
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# vi:set ts=2 sw=2 et sta:
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-session-encryptedcookie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Hentenaar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '12.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '12.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rack
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
description: |2
|
56
|
+
Rack middleware that persists session data in an encrypted cookie
|
57
|
+
email: tim.hentenaar@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- LICENSE
|
63
|
+
- README.md
|
64
|
+
- lib/rack/session/encryptedcookie.rb
|
65
|
+
homepage: https://github.com/thentenaar/rack-session-encryptedcookie
|
66
|
+
licenses:
|
67
|
+
- BSD-2-Clause
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.6.14
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Encrypted session middleware for Rack
|
89
|
+
test_files: []
|