rack-session-encryptedcookie 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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: []