encrypted_cookie_store-instructure 1.0.8 → 1.1.0

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.
@@ -1,9 +1,9 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{encrypted_cookie_store-instructure}
3
- s.version = "1.0.8"
3
+ s.version = "1.1.0"
4
4
 
5
5
  s.authors = ["Cody Cutrer", "Jacob Fugal", "James Williams"]
6
- s.date = %q{2013-12-20}
6
+ s.date = %q{2013-11-13}
7
7
  s.extra_rdoc_files = [
8
8
  "LICENSE.txt"
9
9
  ]
@@ -15,6 +15,12 @@ Gem::Specification.new do |s|
15
15
  ]
16
16
  s.homepage = %q{http://github.com/ccutrer/encrypted_cookie_store}
17
17
  s.require_paths = ["lib"]
18
- s.summary = %q{EncryptedCookieStore for Ruby on Rails 2.3}
18
+ s.summary = %q{EncryptedCookieStore for Ruby on Rails 3.2}
19
19
  s.description = %q{A secure version of Rails' built in CookieStore}
20
+
21
+ s.add_dependency "actionpack", "~> 3.2"
22
+ s.add_development_dependency "bundler", "~> 1.3"
23
+ s.add_development_dependency "rake"
24
+ s.add_development_dependency "rspec-rails", "~> 2.0"
25
+ s.add_development_dependency "debugger"
20
26
  end
@@ -1,204 +1,194 @@
1
1
  require 'openssl'
2
2
  require 'zlib'
3
3
 
4
- class EncryptedCookieStore < ActionController::Session::CookieStore
5
- OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
4
+ require 'active_support/core_ext/hash/deep_dup'
5
+ require 'active_support/core_ext/numeric/time'
6
+ require 'action_dispatch'
7
+
8
+ module ActionDispatch
9
+ module Session
10
+ class EncryptedCookieStore < CookieStore
11
+ class << self
12
+ attr_accessor :data_cipher_type
13
+ end
14
+ self.data_cipher_type = "aes-128-cbc".freeze
6
15
 
7
- EXPIRE_AFTER_KEY = "encrypted_cookie_store.session_expire_after"
16
+ OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
8
17
 
9
- class << self
10
- attr_accessor :data_cipher_type
11
- end
18
+ def initialize(app, options = {})
19
+ @digest = options.delete(:digest) || 'SHA1'
12
20
 
13
- self.data_cipher_type = "aes-128-cbc".freeze
14
-
15
- def initialize(app, options = {})
16
- options[:secret] = options[:secret].call if options[:secret].respond_to?(:call)
17
- @logger = options[:logger]
18
- ensure_encryption_key_secure(options[:secret])
19
- @encryption_key = unhex(options[:secret]).freeze
20
- @compress = options[:compress]
21
- @compress = true if @compress.nil?
22
- @data_cipher = OpenSSL::Cipher::Cipher.new(EncryptedCookieStore.data_cipher_type)
23
- @options = options
24
- options[:refresh_interval] ||= 5.minutes
25
- super(app, options)
26
- end
21
+ @compress = options[:compress]
22
+ @compress = true if @compress.nil?
27
23
 
28
- def call(env)
29
- @options[:expire_after] = env[EXPIRE_AFTER_KEY] || @options[:expire_after]
30
- prepare!(env)
24
+ @secret = options.delete(:secret)
25
+ @secret = @secret.call if @secret.respond_to?(:call)
26
+ @secret.freeze
27
+ @encryption_key = unhex(@secret).freeze
28
+ ensure_encryption_key_secure
31
29
 
32
- old_session_data, raw_old_session_data, old_timestamp = all_unpacked_cookie_data(env)
33
- # make sure we have a deep copy
34
- old_session_data = Marshal.load(raw_old_session_data) if raw_old_session_data
35
- env['encrypted_cookie_store.session_refreshed_at'] ||= session_refreshed_at(old_timestamp)
30
+ @data_cipher = OpenSSL::Cipher::Cipher.new(EncryptedCookieStore.data_cipher_type)
31
+ options[:refresh_interval] ||= 5.minutes
36
32
 
37
- status, headers, body = @app.call(env)
33
+ super(app, options)
34
+ end
38
35
 
39
- session_data = env[ENV_SESSION_KEY]
40
- options = env[ENV_SESSION_OPTIONS_KEY]
41
- request = ActionController::Request.new(env)
36
+ private
37
+
38
+ # overrides method in ActionDispatch::Session::CookieStore
39
+ def unpacked_cookie_data(env)
40
+ env['encrypted_cookie_store.cookie'] ||= begin
41
+ stale_session_check! do
42
+ request = ActionDispatch::Request.new(env)
43
+ if data = unmarshal(request.cookie_jar.signed[@key])
44
+ data.stringify_keys!
45
+ end
46
+ data ||= {}
47
+ env['encrypted_cookie_store.original_cookie'] = data.deep_dup.except(:timestamp)
48
+ data
49
+ end
50
+ end
51
+ end
42
52
 
43
- @options[:expire_after] = env[EXPIRE_AFTER_KEY] || options[:expire_after] || @options[:expire_after]
44
- options[:expire_after] = @options[:expire_after]
53
+ # overrides method in ActionDispatch::Session::CookieStore
54
+ def set_session(env, sid, session_data, options)
55
+ session_data = super
56
+ session_data.delete(:timestamp)
57
+ marshal(session_data, options)
58
+ end
45
59
 
46
- if !(options[:secure] && !request.ssl?) && (!session_data.is_a?(ActionController::Session::AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after])
47
- session_data.send(:load!) if session_data.is_a?(ActionController::Session::AbstractStore::SessionHash) && !session_data.loaded?
60
+ # overrides method in Rack::Session::Cookie
61
+ def load_session(env)
62
+ if time = timestamp(env)
63
+ env['encrypted_cookie_store.session_refreshed_at'] ||= Time.at(time).utc
64
+ end
65
+ super
66
+ end
48
67
 
49
- persistent_session_id!(session_data)
68
+ # overrides method in Rack::Session::Abstract::ID
69
+ def commit_session?(env, session, options)
70
+ can_commit = super
71
+ can_commit && (session_changed?(env, session) || refresh_session?(env, options))
72
+ end
50
73
 
51
- old_session_data = nil if options[:expire_after] && old_timestamp && Time.now.utc.to_i > old_timestamp + options[:refresh_interval]
52
- return [status, headers, body] if session_data == old_session_data
53
74
 
54
- session_data = marshal(session_data.to_hash)
75
+ def timestamp(env)
76
+ unpacked_cookie_data(env)["timestamp"]
77
+ end
55
78
 
56
- raise CookieOverflow if session_data.size > MAX
79
+ def session_changed?(env, session)
80
+ (session || {}).to_hash.stringify_keys.except(:timestamp) != (env['encrypted_cookie_store.original_cookie'] || {})
81
+ end
57
82
 
58
- cookie = Hash.new
59
- cookie[:value] = session_data
60
- unless options[:expire_after].nil?
61
- cookie[:expires] = Time.now + options[:expire_after]
83
+ def refresh_session?(env, options)
84
+ if options[:expire_after] && options[:refresh_interval] && time = timestamp(env)
85
+ Time.now.utc.to_i > time + options[:refresh_interval]
86
+ else
87
+ false
88
+ end
62
89
  end
63
90
 
64
- Rack::Utils.set_cookie_header!(headers, @key, cookie.merge(options))
65
- end
91
+ def marshal(data, options={})
92
+ @data_cipher.encrypt
93
+ @data_cipher.key = @encryption_key
66
94
 
67
- [status, headers, body]
68
- end
69
- private
70
- def secret
71
- @secret
72
- end
95
+ session_data = Marshal.dump(data)
96
+ iv = @data_cipher.random_iv
97
+ if @compress
98
+ compressed_session_data = deflate(session_data, 5)
99
+ compressed_session_data = session_data if compressed_session_data.length >= session_data.length
100
+ else
101
+ compressed_session_data = session_data
102
+ end
103
+ encrypted_session_data = @data_cipher.update(compressed_session_data) << @data_cipher.final
104
+ timestamp = Time.now.utc.to_i if options[:expire_after]
105
+ digest = OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(@digest), @secret, session_data + timestamp.to_s)
73
106
 
74
- def marshal(session)
75
- @data_cipher.encrypt
76
- @data_cipher.key = @encryption_key
77
-
78
- session_data = Marshal.dump(session)
79
- iv = @data_cipher.random_iv
80
- if @compress
81
- compressed_session_data = deflate(session_data, 5)
82
- compressed_session_data = session_data if compressed_session_data.length >= session_data.length
83
- else
84
- compressed_session_data = session_data
85
- end
86
- encrypted_session_data = @data_cipher.update(compressed_session_data) << @data_cipher.final
87
- timestamp = Time.now.utc.to_i if @options[:expire_after]
88
- digest = OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(@digest), secret, session_data + timestamp.to_s)
107
+ result = "#{base64(iv)}#{compressed_session_data == session_data ? '.' : ' '}#{base64(encrypted_session_data)}.#{base64(digest)}"
108
+ result << ".#{base64([timestamp].pack('N'))}" if options[:expire_after]
109
+ result
110
+ end
89
111
 
90
- result = "#{base64(iv)}#{compressed_session_data == session_data ? '.' : ' '}#{base64(encrypted_session_data)}.#{base64(digest)}"
91
- result << ".#{base64([timestamp].pack('N'))}" if @options[:expire_after]
92
- result
93
- end
112
+ def unmarshal(data, options={})
113
+ return nil unless data
114
+ compressed = !!data.index(' ')
115
+ b64_iv, b64_encrypted_session_data, b64_digest, b64_timestamp = data.split(/\.| /, 4)
116
+ if b64_iv && b64_encrypted_session_data && b64_digest
117
+ iv = unbase64(b64_iv)
118
+ encrypted_session_data = unbase64(b64_encrypted_session_data)
119
+ digest = unbase64(b64_digest)
120
+ timestamp = unbase64(b64_timestamp).unpack('N').first if b64_timestamp
121
+
122
+ @data_cipher.decrypt
123
+ @data_cipher.key = @encryption_key
124
+ @data_cipher.iv = iv
125
+ session_data = @data_cipher.update(encrypted_session_data) << @data_cipher.final
126
+ session_data = inflate(session_data) if compressed
127
+ return nil unless digest == OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(@digest), @secret, session_data + timestamp.to_s)
128
+ if options[:expire_after]
129
+ return nil unless timestamp && Time.now.utc.to_i <= timestamp + options[:expire_after]
130
+ end
131
+
132
+ loaded_data = Marshal.load(session_data) || nil
133
+ loaded_data[:timestamp] = timestamp if loaded_data && timestamp
134
+ loaded_data
135
+ else
136
+ nil
137
+ end
138
+ rescue Zlib::DataError, OpenSSLCipherError
139
+ nil
140
+ end
94
141
 
95
- def unmarshal(cookie)
96
- if cookie
97
- compressed = !!cookie.index(' ')
98
- b64_iv, b64_encrypted_session_data, b64_digest, b64_timestamp = cookie.split(/\.| /, 4)
99
- if b64_iv && b64_encrypted_session_data && b64_digest
100
- iv = unbase64(b64_iv)
101
- encrypted_session_data = unbase64(b64_encrypted_session_data)
102
- digest = unbase64(b64_digest)
103
- timestamp = unbase64(b64_timestamp).unpack('N').first if b64_timestamp
104
-
105
- @data_cipher.decrypt
106
- @data_cipher.key = @encryption_key
107
- @data_cipher.iv = iv
108
- session_data = @data_cipher.update(encrypted_session_data) << @data_cipher.final
109
- session_data = inflate(session_data) if compressed
110
- return [nil, nil, nil] unless digest == OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(@digest), secret, session_data + timestamp.to_s)
111
- if @options[:expire_after]
112
- return [nil, nil, nil] unless timestamp
113
- return [nil, nil, timestamp] unless Time.now.utc.to_i - timestamp < @options[:expire_after]
142
+ # To prevent users from using an insecure encryption key like "Password" we make sure that the
143
+ # encryption key they've provided is at least 30 characters in length.
144
+ def ensure_encryption_key_secure
145
+ if @encryption_key.blank?
146
+ raise ArgumentError, "An encryption key is required for encrypting the " +
147
+ "cookie session data. Please set config.action_controller.session = { " +
148
+ "..., :encryption_key => \"some random string of at least " +
149
+ "16 bytes\", ... } in config/environment.rb"
114
150
  end
115
- loaded_data = nil
116
- begin
117
- loaded_data = Marshal.load(session_data)
118
- rescue
119
- @logger.error("Could not unmarshal session_data: #{session_data.inspect}") if @logger
151
+
152
+ if @encryption_key.size < 16 * 2
153
+ raise ArgumentError, "The EncryptedCookieStore encryption key must be a " +
154
+ "hexadecimal string of at least 16 bytes. " +
155
+ "The value that you've provided, \"#{@encryption_key}\", is " +
156
+ "#{@encryption_key.size / 2} bytes. You could use the following (randomly " +
157
+ "generated) string as encryption key: " +
158
+ ActiveSupport::SecureRandom.hex(16)
120
159
  end
121
- [loaded_data, session_data, timestamp]
122
- else
123
- [nil, nil, nil]
124
160
  end
125
- else
126
- [nil, nil, nil]
127
- end
128
- rescue Zlib::DataError
129
- [nil, nil, nil]
130
- rescue OpenSSLCipherError
131
- [nil, nil, nil]
132
- end
133
161
 
134
- def all_unpacked_cookie_data(env)
135
- env["action_dispatch.request.unsigned_session_cookie"] ||= begin
136
- stale_session_check! do
137
- request = Rack::Request.new(env)
138
- session_data = request.cookies[@key]
139
- unmarshal(session_data) || {}
162
+ def base64(data)
163
+ ::Base64.encode64(data).tr('+/', '-_').gsub(/=|\n/, '')
140
164
  end
141
- end
142
- end
143
165
 
144
- def unpacked_cookie_data(env)
145
- all_unpacked_cookie_data(env).first
146
- end
166
+ def unbase64(data)
167
+ ::Base64.decode64(data.tr('-_', '+/').ljust((data.length + 4 - 1) / 4 * 4, '='))
168
+ end
147
169
 
148
- def session_refreshed_at(timestamp)
149
- Time.at(timestamp).utc if timestamp
150
- end
170
+ # compress
171
+ def deflate(string, level)
172
+ z = Zlib::Deflate.new(level)
173
+ dst = z.deflate(string, Zlib::FINISH)
174
+ z.close
175
+ dst
176
+ end
151
177
 
152
- # To prevent users from using an insecure encryption key like "Password" we make sure that the
153
- # encryption key they've provided is at least 30 characters in length.
154
- def ensure_encryption_key_secure(encryption_key)
155
- if encryption_key.blank?
156
- raise ArgumentError, "An encryption key is required for encrypting the " +
157
- "cookie session data. Please set config.action_controller.session = { " +
158
- "..., :encryption_key => \"some random string of at least " +
159
- "16 bytes\", ... } in config/environment.rb"
160
- end
178
+ # decompress
179
+ def inflate(string)
180
+ zstream = Zlib::Inflate.new
181
+ buf = zstream.inflate(string)
182
+ zstream.finish
183
+ zstream.close
184
+ buf
185
+ end
161
186
 
162
- if encryption_key.size < 16 * 2
163
- raise ArgumentError, "The EncryptedCookieStore encryption key must be a " +
164
- "hexadecimal string of at least 16 bytes. " +
165
- "The value that you've provided, \"#{encryption_key}\", is " +
166
- "#{encryption_key.size / 2} bytes. You could use the following (randomly " +
167
- "generated) string as encryption key: " +
168
- ActiveSupport::SecureRandom.hex(16)
187
+ def unhex(hex_data)
188
+ [hex_data].pack("H*")
189
+ end
169
190
  end
170
191
  end
171
-
172
- def verifier_for(secret, digest)
173
- nil
174
- end
175
-
176
- def base64(data)
177
- ActiveSupport::Base64.encode64(data).tr('+/', '-_').gsub(/=|\n/, '')
178
- end
179
-
180
- def unbase64(data)
181
- ActiveSupport::Base64.decode64(data.tr('-_', '+/').ljust((data.length + 4 - 1) / 4 * 4, '='))
182
- end
183
-
184
- # aka compress
185
- def deflate(string, level)
186
- z = Zlib::Deflate.new(level)
187
- dst = z.deflate(string, Zlib::FINISH)
188
- z.close
189
- dst
190
- end
191
-
192
- # aka decompress
193
- def inflate(string)
194
- zstream = Zlib::Inflate.new
195
- buf = zstream.inflate(string)
196
- zstream.finish
197
- zstream.close
198
- buf
199
- end
200
-
201
- def unhex(hex_data)
202
- [hex_data].pack("H*")
203
- end
204
192
  end
193
+
194
+ EncryptedCookieStore = ActionDispatch::Session::EncryptedCookieStore
metadata CHANGED
@@ -1,7 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: encrypted_cookie_store-instructure
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.8
4
+ version: 1.1.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Cody Cutrer
@@ -10,8 +11,88 @@ authors:
10
11
  autorequire:
11
12
  bindir: bin
12
13
  cert_chain: []
13
- date: 2013-12-20 00:00:00.000000000 Z
14
- dependencies: []
14
+ date: 2013-11-13 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: actionpack
18
+ requirement: !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: '3.2'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ~>
30
+ - !ruby/object:Gem::Version
31
+ version: '3.2'
32
+ - !ruby/object:Gem::Dependency
33
+ name: bundler
34
+ requirement: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: '1.3'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ - !ruby/object:Gem::Dependency
49
+ name: rake
50
+ requirement: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ - !ruby/object:Gem::Dependency
65
+ name: rspec-rails
66
+ requirement: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ version: '2.0'
72
+ type: :development
73
+ prerelease: false
74
+ version_requirements: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ version: '2.0'
80
+ - !ruby/object:Gem::Dependency
81
+ name: debugger
82
+ requirement: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
15
96
  description: A secure version of Rails' built in CookieStore
16
97
  email:
17
98
  executables: []
@@ -21,30 +102,31 @@ extra_rdoc_files:
21
102
  files:
22
103
  - LICENSE.txt
23
104
  - README.markdown
24
- - encrypted_cookie_store-instructure.gemspec
25
105
  - lib/encrypted_cookie_store.rb
106
+ - encrypted_cookie_store-instructure.gemspec
26
107
  homepage: http://github.com/ccutrer/encrypted_cookie_store
27
108
  licenses: []
28
- metadata: {}
29
109
  post_install_message:
30
110
  rdoc_options: []
31
111
  require_paths:
32
112
  - lib
33
113
  required_ruby_version: !ruby/object:Gem::Requirement
114
+ none: false
34
115
  requirements:
35
116
  - - ! '>='
36
117
  - !ruby/object:Gem::Version
37
118
  version: '0'
38
119
  required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
39
121
  requirements:
40
122
  - - ! '>='
41
123
  - !ruby/object:Gem::Version
42
124
  version: '0'
43
125
  requirements: []
44
126
  rubyforge_project:
45
- rubygems_version: 2.3.0
127
+ rubygems_version: 1.8.23
46
128
  signing_key:
47
- specification_version: 4
48
- summary: EncryptedCookieStore for Ruby on Rails 2.3
129
+ specification_version: 3
130
+ summary: EncryptedCookieStore for Ruby on Rails 3.2
49
131
  test_files: []
50
132
  has_rdoc:
checksums.yaml DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- OTNlMTI1ODU0MzA1OWU5NGFmOTFkMTk4NjEyNTVjNzVkZjAxOTAxNA==
5
- data.tar.gz: !binary |-
6
- N2Q3MGExZTg4MDgyMWZhMmMxNjc2ZWFlNGVhY2I5MTRmZmRjNDM5OQ==
7
- SHA512:
8
- metadata.gz: !binary |-
9
- MDNiZjdkOTQ3YzMyMGU2N2I5NzI0Zjg5N2ExNGJmM2M5ZjdkNjFlNTFlZThm
10
- MWRjNjU1ZGFiNWZlNjRlZWFjMWMzY2VlYjVjZjZlOGQzMWNjMjdjMzM4Zjk1
11
- MDA5Yzc5YmFhZDg2YzE5ZTA1MGExOWYyODEyZDY0ZDc4YWU1MGI=
12
- data.tar.gz: !binary |-
13
- ZWQwNzg1MjFlZWY0ZGRjYzBlZWM0MWZlMzJiNmJhNDc4NDY0OGIwMTJmYzRi
14
- NzU1OTlhYzQ5Yzg0MDQ5NDFlMjM2NTE4OTFhODE2MGU4ZTU4ZDVjZDlkMTk1
15
- ODExNjQzZjg1MDcwOTM1MzdhM2JiOTNlODM2NDc0YTFlYjRlMTg=