encrypted_cookie_store-instructure 1.0.8 → 1.1.0

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