Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Encrypted cookies #8112

Merged
merged 10 commits into from
@spastorino
Owner

1) Sign cookies using key deriver
Rails will derive keys if config.secret_key_base is set instead of the old config.secret_key. If secret_key_base is not set to keep backwards compatibility Rails will not derive and just use the raw secret_key as in previous versions.
Users can change the default salt for signing cookies through config.action_dispatch.signed_cookie_salt setting which is by default 'signed cookie'.

2) cookie.encrypted method => EncryptedCookieJar
This adds a encrypted method to CookieJar that returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
It also prevents from tampering by the user (or a 3rd party) throwing an ActiveSupport::MessageVerifier::InvalidSignature exception.
You must set config.secret_token_base to be able to use this feature.
Users are allowed to change the default salt used for encryption by changing config.action_dispatch.encrypted_cookie_salt which defaults to 'encrypted cookie' and can also change the salt using for signing by changing config.action_dispatch.encrypted_signed_cookie_salt which defaults to 'signed encrypted cookie'

3) encrypted cookie store
This adds encrypted cookie store. You now can set config.session_store :encrypted_cookie_store, key: '_myapp_session' in order to use encrypted cookies to store your session. This also makes new apps to use encrypted cookie store but default.

4) Use derive keys for http authentication
Just that it changes http authentication to use derived keys.
Users are allowed to change the default salt by changing config.action_dispatch.http_auth_salt which defaults to 'http authentication'

In general, please help me reviewing all this. Help me improving the docs for all this. And help me suggesting better names for the options, mostly …

config.secret_key_base
config.action_dispatch.signed_cookie_salt
config.action_dispatch.encrypted_cookie_salt
config.action_dispatch.encrypted_signed_cookie_salt
config.action_dispatch.http_auth_salt

Note to committers: Leave this for me to merge :).

/cc @NZKoz @meder @thaidn @emboss @nahi please help me reviewing giving crypto review :).

@homakov

ah you did it! dreamed about this feature

@scottwb

+1 I love it. I've been wanting this in Rails for a long time, but there seems to have been quite the resistance early on. I've been using encryped_cookie_store all along:

Rails 2.3: https://github.com/FooBarWidget/encrypted_cookie_store
Rails 3.0: https://github.com/scottwb/encrypted_cookie_store
Rails 3.2: https://github.com/validas/encrypted_cookie_store

I never bought into the argument that this would lull me into storing sensitive data in my cookies.

Thanks for making this happen!

/cc @mattsnyder

@thaidn

Thanks for working on this. I will take a look sometime this week.

@spastorino
Owner

@thaidn ok thanks, will wait :)

@NZKoz
Owner

The Short version of the changes is:

  • Rename secret_key to secret_key_base, if you set secret_key then we verify cookies based on the raw secret (and fire a deprecation warning)
  • Use the key_generator code with static salts when deriving keys everywhere we derive keys
  • Provide config options for people to change those salts if they want to.
  • We don't advertise the salt configuration options as they don't seem necessary at all as the entropy comes from the base secret. However the option's there for tinfoil hats
  • Introduce an EncryptedCookieJar which is encrypted (and signed) with keys derived from the base secret.
  • Default to using the encrypted cookie jar for the session data

What we don't have yet, is a configuration option to transparently take an old session, and upgrade it to a new session. Broadly speaking I'm scared of automatic upgrades in crypto systems as I'm too dumb to know if there's an issue here. It might be nice to have this as an option to encourage people to upgrade, but we'd need some guidance on the sanity, or otherwise, of that.

@carlosantoniodasilva carlosantoniodasilva commented on the diff
actionpack/test/controller/flash_test.rb
@@ -217,7 +219,7 @@ def test_redirect_to_with_adding_flash_types
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
- SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+ Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')

Two spaces after = :smile:

@spastorino Owner

will fix it

@spastorino Owner

Done 44f12bb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@carlosantoniodasilva

@spastorino looks awesome bro :+1:

@frodsan frodsan commented on the diff
actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -237,7 +248,23 @@ def permanent
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||= SignedCookieJar.new(self, @secret)
+ @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ end
+
+ # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
+ # If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception
+ # will be raised.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
@frodsan
frodsan added a note

:scissors:

@spastorino Owner

Not sure I follow what do you mean

@rafaelfranca Owner

I think we are not using the Example: label in the documentation anymore. :scissors: == "Remove this line"

@spastorino Owner

About this I started to document copying what he already have here https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/cookies.rb#L244
@frodsan can you take a look at all that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jeremy
Owner
What we don't have yet, is a configuration option to transparently take an old session, and upgrade it to a new session. 

CookieStore does all its work using get_cookie and set_cookie methods that a UpgradeSignatureToEncryptionCookieStore subclass can override to try the signed cookie jar first, then the encrypted jar. It'd need both key generators though.

@rafaelfranca rafaelfranca commented on the diff
activesupport/lib/active_support/key_generator.rb
@@ -20,4 +21,51 @@ def generate_key(salt, key_size=64)
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
end
end
+
+ class CachingKeyGenerator
+ def initialize(key_generator)
+ @key_generator = key_generator
+ @cache_keys = {}.extend(Mutex_m)
+ end
+
+ def generate_key(salt, key_size=64)
+ @cache_keys.synchronize do
+ @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size)
+ end
+ end
+ end
+
+ class DummyKeyGenerator
@rafaelfranca Owner

I think we should put # :nodoc: here since this class is not to be using in the applications

@spastorino Owner

Done d348c43

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rafaelfranca rafaelfranca commented on the diff
activesupport/lib/active_support/key_generator.rb
@@ -20,4 +21,51 @@ def generate_key(salt, key_size=64)
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
end
end
+
+ class CachingKeyGenerator
@rafaelfranca Owner

Documentation to this class would be great

@spastorino Owner

Done e6e3317 review please /cc @frodsan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@spastorino spastorino merged commit ef8b845 into master
@spastorino
Owner

I will check @jeremy and @rafaelfranca points later. If there is some other concern let me know

@emboss emboss commented on the diff
actionpack/lib/action_dispatch/middleware/cookies.rb
((39 lines not shown))
end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature,
+ ActiveSupport::MessageVerifier::InvalidMessage
+ nil
@emboss
emboss added a note

Just for my understanding: Why swallow the errors?

@spastorino Owner

I just wanted to keep the API similar to what we already have https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/cookies.rb#L333-334
Anyway, I'd think about removing this rescue nil thing. @jeremy @josevalim thoughts? do you guys know why this was that way in the first place?

@spastorino Owner

Another discussion related to this thing is if you would return 400 to clients when something goes wrong or if you would just ignore "broken" cookies. This may happen if you change the secret for instance.

@josevalim Owner

Yes, in case someone tempers the cookie or, more important, you changed the secret because you updated a Rails version or the previous one was "stolen", you should be able to change the secret and we will simply discard the invalid cookies.

@emboss
emboss added a note

Ah OK, makes sense now. Thanks, @spastorino & @josevalim !

@spastorino Owner

@emboss Thanks to you for reviewing :)

@emboss
emboss added a note

@spastorino De rien, my pleasure!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@emboss

a) How much of a pain would it be if we switched the key generator from PBKDF2-HMAC-SHA-1 to HMAC-SHA-256?

The reason I'm asking is because using HMAC-SHA-1 in the key generator limits the overall security to the 160 bits output of SHA-1 there. Since we generally use longer keys (for example 32 byte keys for the default AES-256-CBC in the MessageEncryptor) for encryption, it would be nice if we could use something that gives us a security margin of 32 bytes = 256 bits as well, like SHA-256 does. The same argument can be made for MessageVerifier.

It would be nice if the security parameters add up - but I know that especially PBKDF2-HMAC-SHA-256 (using PKCS5::pbkdf2_hmac) can be a bit of a pain, a lot of people would probably have to upgrade their (native) OpenSSL library to make it work?

b) How do you feel about warning or raising an error if user-provided salts are less than eight characters long (the PBKDF#2 recommendation for minimum salt length)? Kudos for adding the possibility to provide your own salts from a professing tinfoil hat ;)

@spastorino
Owner

@jeremy I've added the cookie store to upgrade here 8d06b62...8eefdb6

@spastorino
Owner

Also added this docs d56cfad

@NZKoz
Owner

@emboss I tried that the first time around but I couldn't find a ruby which I deploy on which supported HMAC-SHA-256, so if we did we'd need to fall back to sha1.

And if we did that we'd break people's stuff if they upgraded their openssl suddenly their app's cookies don't comply.

We could add a config option for it perhaps, but not change the defaults.

@emboss

@NZKoz OK, I imagined changing the default would probably break things, but a config option sounds great!

@spastorino
Owner

@emboss can you provide a Pull Request for this?

@emboss

@spastorino Sure, I'll give it a shot! Any deadlines I have to keep an eye on?

@guilleiguaran

@emboss no pressure but...

wut

:trollface:

@guilleiguaran

@emboss nvm, the option is a nice-to-have but isn't a blocker for Rails 4 :smile:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 3, 2012
  1. @spastorino

    Add cookie.encrypted which returns an EncryptedCookieJar

    spastorino authored
    How to use it?
    
    cookies.encrypted[:discount] = 45
    => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
    cookies.encrypted[:discount]
    => 45
  2. @spastorino
  3. @spastorino
  4. @spastorino

    Add encrypted cookie store

    spastorino authored
  5. @spastorino
  6. @spastorino
  7. @spastorino

    Disallow ability to use EncryptedCookieJar with DummyKeyGenerator

    spastorino authored
    Developers must set config.secret_key_base in
    config/initializers/secret_token.rb
  8. @spastorino
  9. @spastorino
  10. @spastorino
This page is out of date. Refresh to see the latest.
Showing with 321 additions and 92 deletions.
  1. +3 −3 actionpack/lib/action_controller/metal/http_authentication.rb
  2. +4 −4 actionpack/lib/action_controller/metal/request_forgery_protection.rb
  3. +5 −4 actionpack/lib/action_dispatch.rb
  4. +75 −31 actionpack/lib/action_dispatch/middleware/cookies.rb
  5. +20 −3 actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
  6. +4 −0 actionpack/lib/action_dispatch/railtie.rb
  7. +4 −2 actionpack/test/controller/flash_test.rb
  8. +4 −2 actionpack/test/controller/http_digest_authentication_test.rb
  9. +26 −6 actionpack/test/dispatch/cookies_test.rb
  10. +4 −1 actionpack/test/dispatch/session/cookie_store_test.rb
  11. +48 −0 activesupport/lib/active_support/key_generator.rb
  12. +5 −2 activesupport/lib/active_support/message_encryptor.rb
  13. +1 −1  activesupport/test/message_encryptor_test.rb
  14. +1 −1  guides/code/getting_started/config/initializers/secret_token.rb
  15. +1 −1  guides/source/action_controller_overview.md
  16. +1 −1  guides/source/configuring.md
  17. +43 −16 railties/lib/rails/application.rb
  18. +3 −1 railties/lib/rails/application/configuration.rb
  19. +2 −2 railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
  20. +1 −1  railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
  21. +1 −0  railties/test/abstract_unit.rb
  22. +7 −5 railties/test/application/configuration_test.rb
  23. +3 −1 railties/test/application/middleware/remote_ip_test.rb
  24. +51 −0 railties/test/application/middleware/session_test.rb
  25. +1 −1  railties/test/application/url_generation_test.rb
  26. +1 −1  railties/test/generators/app_generator_test.rb
  27. +2 −2 railties/test/isolation/abstract_unit.rb
View
6 actionpack/lib/action_controller/metal/http_authentication.rb
@@ -249,9 +249,9 @@ def authentication_request(controller, realm, message = nil)
end
def secret_token(request)
- secret = request.env["action_dispatch.secret_token"]
- raise "You must set config.secret_token in your app's config" if secret.blank?
- secret
+ key_generator = request.env["action_dispatch.key_generator"]
+ http_auth_salt = request.env["action_dispatch.http_auth_salt"]
+ key_generator.generate_key(http_auth_salt)
end
# Uses an MD5 digest based on time to generate a value to be used only once.
View
8 actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -121,11 +121,11 @@ def exists?
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
def self.build(request)
- secret = request.env[ActionDispatch::Cookies::TOKEN_KEY]
- host = request.host
- secure = request.ssl?
+ key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
+ host = request.host
+ secure = request.ssl?
- new(secret, host, secure)
+ new(key_generator, host, secure)
end
def write(*)
View
9 actionpack/lib/action_dispatch.rb
@@ -81,10 +81,11 @@ module Http
end
module Session
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
- autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
- autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
+ autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :EncryptedCookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
mattr_accessor :test_app
View
106 actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/message_verifier'
module ActionDispatch
class Request < Rack::Request
@@ -27,7 +28,7 @@ def cookie_jar
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's <tt>config.secret_token</tt> value.
+ # # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
# # It can be read using the signed method <tt>cookies.signed[:key]</tt>
# cookies.signed[:user_id] = current_user.id
#
@@ -79,8 +80,12 @@ def cookie_jar
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
class Cookies
- HTTP_HEADER = "Set-Cookie".freeze
- TOKEN_KEY = "action_dispatch.secret_token".freeze
+ HTTP_HEADER = "Set-Cookie".freeze
+ GENERATOR_KEY = "action_dispatch.key_generator".freeze
+ SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
+ ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
+ ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
+
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
@@ -103,21 +108,27 @@ class CookieJar #:nodoc:
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
def self.build(request)
- secret = request.env[TOKEN_KEY]
+ env = request.env
+ key_generator = env[GENERATOR_KEY]
+ options = { signed_cookie_salt: env[SIGNED_COOKIE_SALT],
+ encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT],
+ encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] }
+
host = request.host
secure = request.ssl?
- new(secret, host, secure).tap do |hash|
+ new(key_generator, host, secure, options).tap do |hash|
hash.update(request.cookies)
end
end
- def initialize(secret = nil, host = nil, secure = false)
- @secret = secret
+ def initialize(key_generator, host = nil, secure = false, options = {})
+ @key_generator = key_generator
@set_cookies = {}
@delete_cookies = {}
@host = host
@secure = secure
+ @options = options
@cookies = {}
end
@@ -220,7 +231,7 @@ def clear(options = {})
# cookies.permanent.signed[:remember_me] = current_user.id
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent
- @permanent ||= PermanentCookieJar.new(self, @secret)
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
@@ -228,7 +239,7 @@ def permanent
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
# be raised.
#
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
#
# Example:
#
@@ -237,7 +248,23 @@ def permanent
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||= SignedCookieJar.new(self, @secret)
+ @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ end
+
+ # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
+ # If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception
+ # will be raised.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
@frodsan
frodsan added a note

:scissors:

@spastorino Owner

Not sure I follow what do you mean

@rafaelfranca Owner

I think we are not using the Example: label in the documentation anymore. :scissors: == "Remove this line"

@spastorino Owner

About this I started to document copying what he already have here https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/cookies.rb#L244
@frodsan can you take a look at all that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ #
+ # cookies.encrypted[:discount] = 45
+ # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ #
+ # cookies.encrypted[:discount] # => 45
+ def encrypted
+ @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
end
def write(headers)
@@ -261,8 +288,10 @@ def write_cookie?(cookie)
end
class PermanentCookieJar < CookieJar #:nodoc:
- def initialize(parent_jar, secret)
- @parent_jar, @secret = parent_jar, secret
+ def initialize(parent_jar, key_generator, options = {})
+ @parent_jar = parent_jar
+ @key_generator = key_generator
+ @options = options
end
def []=(key, options)
@@ -283,11 +312,11 @@ def method_missing(method, *arguments, &block)
class SignedCookieJar < CookieJar #:nodoc:
MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
- SECRET_MIN_LENGTH = 30 # Characters
- def initialize(parent_jar, secret)
- ensure_secret_secure(secret)
+ def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
+ @options = options
+ secret = key_generator.generate_key(@options[:signed_cookie_salt])
@verifier = ActiveSupport::MessageVerifier.new(secret)
end
@@ -314,26 +343,41 @@ def []=(key, options)
def method_missing(method, *arguments, &block)
@parent_jar.send(method, *arguments, &block)
end
+ end
- protected
-
- # To prevent users from using something insecure like "Password" we make sure that the
- # secret they've provided is at least 30 characters in length.
- def ensure_secret_secure(secret)
- if secret.blank?
- raise ArgumentError, "A secret is required to generate an " +
- "integrity hash for cookie session data. Use " +
- "config.secret_token = \"some secret phrase of at " +
- "least #{SECRET_MIN_LENGTH} characters\"" +
- "in config/initializers/secret_token.rb"
+ class EncryptedCookieJar < SignedCookieJar #:nodoc:
+ def initialize(parent_jar, key_generator, options = {})
+ if ActiveSupport::DummyKeyGenerator === key_generator
+ raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
+ "Set config.secret_key_base in config/initializers/secret_token.rb"
end
- if secret.length < SECRET_MIN_LENGTH
- raise ArgumentError, "Secret should be something secure, " +
- "like \"#{SecureRandom.hex(16)}\". The value you " +
- "provided, \"#{secret}\", is shorter than the minimum length " +
- "of #{SECRET_MIN_LENGTH} characters"
+ @parent_jar = parent_jar
+ @options = options
+ secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
+ sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ end
+
+ def [](name)
+ if encrypted_message = @parent_jar[name]
+ @encryptor.decrypt_and_verify(encrypted_message)
end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature,
+ ActiveSupport::MessageVerifier::InvalidMessage
+ nil
@emboss
emboss added a note

Just for my understanding: Why swallow the errors?

@spastorino Owner

I just wanted to keep the API similar to what we already have https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/cookies.rb#L333-334
Anyway, I'd think about removing this rescue nil thing. @jeremy @josevalim thoughts? do you guys know why this was that way in the first place?

@spastorino Owner

Another discussion related to this thing is if you would return 400 to clients when something goes wrong or if you would just ignore "broken" cookies. This may happen if you change the secret for instance.

@josevalim Owner

Yes, in case someone tempers the cookie or, more important, you changed the secret because you updated a Rails version or the previous one was "stolen", you should be able to change the secret and we will simply discard the invalid cookies.

@emboss
emboss added a note

Ah OK, makes sense now. Thanks, @spastorino & @josevalim !

@spastorino Owner

@emboss Thanks to you for reviewing :)

@emboss
emboss added a note

@spastorino De rien, my pleasure!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ end
+
+ def []=(key, options)
+ if options.is_a?(Hash)
+ options.symbolize_keys!
+ else
+ options = { :value => options }
+ end
+ options[:value] = @encryptor.encrypt_and_sign(options[:value])
+
+ raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
+ @parent_jar[key] = options
end
end
View
23 actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -57,8 +57,7 @@ def destroy_session(env, session_id, options)
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
- request = ActionDispatch::Request.new(env)
- if data = request.cookie_jar.signed[@key]
+ if data = cookie_jar(env)[@key]
data.stringify_keys!
end
data || {}
@@ -72,8 +71,26 @@ def set_session(env, sid, session_data, options)
end
def set_cookie(env, session_id, cookie)
+ cookie_jar(env)[@key] = cookie
+ end
+
+ def get_cookie
+ cookie_jar(env)[@key]
+ end
+
+ def cookie_jar(env)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar.signed
+ end
+ end
+
+ class EncryptedCookieStore < CookieStore
+
+ private
+
+ def cookie_jar(env)
request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed[@key] = cookie
+ request.cookie_jar.encrypted
end
end
end
View
4 actionpack/lib/action_dispatch/railtie.rb
@@ -13,6 +13,10 @@ class Railtie < Rails::Railtie
config.action_dispatch.rescue_responses = { }
config.action_dispatch.default_charset = nil
config.action_dispatch.rack_cache = false
+ config.action_dispatch.http_auth_salt = 'http authentication'
+ config.action_dispatch.signed_cookie_salt = 'signed cookie'
+ config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
+ config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
View
6 actionpack/test/controller/flash_test.rb
@@ -1,4 +1,6 @@
require 'abstract_unit'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
class FlashTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -217,7 +219,7 @@ def test_redirect_to_with_adding_flash_types
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
- SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+ Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')

Two spaces after = :smile:

@spastorino Owner

will fix it

@spastorino Owner

Done 44f12bb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
class TestController < ActionController::Base
add_flash_types :bar
@@ -291,7 +293,7 @@ def test_added_flash_types_method
# Overwrite get to send SessionSecret in env hash
def get(path, parameters = nil, env = {})
- env["action_dispatch.secret_token"] ||= SessionSecret
+ env["action_dispatch.key_generator"] ||= Generator
super
end
View
6 actionpack/test/controller/http_digest_authentication_test.rb
@@ -1,4 +1,6 @@
require 'abstract_unit'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
class HttpDigestAuthenticationTest < ActionController::TestCase
class DummyDigestController < ActionController::Base
@@ -40,8 +42,8 @@ def authenticate_with_request
setup do
# Used as secret in generating nonce to prevent tampering of timestamp
- @secret = "session_options_secret"
- @request.env["action_dispatch.secret_token"] = @secret
+ @secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(@secret)
end
teardown do
View
32 actionpack/test/dispatch/cookies_test.rb
@@ -1,4 +1,6 @@
require 'abstract_unit'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
class CookiesTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -65,6 +67,11 @@ def set_signed_cookie
head :ok
end
+ def set_encrypted_cookie
+ cookies.encrypted[:foo] = 'bar'
+ head :ok
+ end
+
def raise_data_overflow
cookies.signed[:foo] = 'bye!' * 1024
head :ok
@@ -146,7 +153,10 @@ def noop
def setup
super
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
+ @request.env["action_dispatch.signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.host = "www.nextangle.com"
end
@@ -296,6 +306,16 @@ def test_signed_cookie
assert_equal 45, @controller.send(:cookies).signed[:user_id]
end
+ def test_encrypted_cookie
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raises TypeError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
+ end
+
def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
get :set_signed_cookie
assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
@@ -329,29 +349,29 @@ def test_tampered_cookies
def test_raises_argument_error_if_missing_secret
assert_raise(ArgumentError, nil.inspect) {
- @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(nil)
get :set_signed_cookie
}
assert_raise(ArgumentError, ''.inspect) {
- @request.env["action_dispatch.secret_token"] = ""
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("")
get :set_signed_cookie
}
end
def test_raises_argument_error_if_secret_is_probably_insecure
assert_raise(ArgumentError, "password".inspect) {
- @request.env["action_dispatch.secret_token"] = "password"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("password")
get :set_signed_cookie
}
assert_raise(ArgumentError, "secret".inspect) {
- @request.env["action_dispatch.secret_token"] = "secret"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("secret")
get :set_signed_cookie
}
assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
- @request.env["action_dispatch.secret_token"] = "12345678901234567890123456789"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("12345678901234567890123456789")
get :set_signed_cookie
}
end
View
5 actionpack/test/dispatch/session/cookie_store_test.rb
@@ -1,9 +1,12 @@
require 'abstract_unit'
require 'stringio'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
class CookieStoreTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+ Generator = ActiveSupport::DummyKeyGenerator.new(SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1')
SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16))
@@ -330,7 +333,7 @@ def test_session_store_with_all_domains
# Overwrite get to send SessionSecret in env hash
def get(path, parameters = nil, env = {})
- env["action_dispatch.secret_token"] ||= SessionSecret
+ env["action_dispatch.key_generator"] ||= Generator
super
end
View
48 activesupport/lib/active_support/key_generator.rb
@@ -1,3 +1,4 @@
+require 'mutex_m'
require 'openssl'
module ActiveSupport
@@ -20,4 +21,51 @@ def generate_key(salt, key_size=64)
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
end
end
+
+ class CachingKeyGenerator
@rafaelfranca Owner

Documentation to this class would be great

@spastorino Owner

Done e6e3317 review please /cc @frodsan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ def initialize(key_generator)
+ @key_generator = key_generator
+ @cache_keys = {}.extend(Mutex_m)
+ end
+
+ def generate_key(salt, key_size=64)
+ @cache_keys.synchronize do
+ @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size)
+ end
+ end
+ end
+
+ class DummyKeyGenerator
@rafaelfranca Owner

I think we should put # :nodoc: here since this class is not to be using in the applications

@spastorino Owner

Done d348c43

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ SECRET_MIN_LENGTH = 30 # Characters
+
+ def initialize(secret)
+ ensure_secret_secure(secret)
+ @secret = secret
+ end
+
+ def generate_key(salt)
+ @secret
+ end
+
+ private
+
+ # To prevent users from using something insecure like "Password" we make sure that the
+ # secret they've provided is at least 30 characters in length.
+ def ensure_secret_secure(secret)
+ if secret.blank?
+ raise ArgumentError, "A secret is required to generate an " +
+ "integrity hash for cookie session data. Use " +
+ "config.secret_key_base = \"some secret phrase of at " +
+ "least #{SECRET_MIN_LENGTH} characters\"" +
+ "in config/initializers/secret_token.rb"
+ end
+
+ if secret.length < SECRET_MIN_LENGTH
+ raise ArgumentError, "Secret should be something secure, " +
+ "like \"#{SecureRandom.hex(16)}\". The value you " +
+ "provided, \"#{secret}\", is shorter than the minimum length " +
+ "of #{SECRET_MIN_LENGTH} characters"
+ end
+ end
+ end
end
View
7 activesupport/lib/active_support/message_encryptor.rb
@@ -39,10 +39,13 @@ class InvalidMessage < StandardError; end
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
- def initialize(secret, options = {})
+ def initialize(secret, *signature_key_or_options)
+ options = signature_key_or_options.extract_options!
+ sign_secret = signature_key_or_options.first
@secret = secret
+ @sign_secret = sign_secret
@cipher = options[:cipher] || 'aes-256-cbc'
- @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer)
+ @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
@serializer = options[:serializer] || Marshal
end
View
2  activesupport/test/message_encryptor_test.rb
@@ -56,7 +56,7 @@ def test_signed_round_tripping
end
def test_alternative_serialization_method
- encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), :serializer => JSONSerializer.new)
+ encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new)
message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) })
assert_equal encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
end
View
2  guides/code/getting_started/config/initializers/secret_token.rb
@@ -6,4 +6,4 @@
# no regular words or you'll be exposed to dictionary attacks.
# Make sure your secret key is kept private
# if you're sharing your code publicly.
-Blog::Application.config.secret_token = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459'
+Blog::Application.config.secret_key_base = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459'
View
2  guides/source/action_controller_overview.md
@@ -219,7 +219,7 @@ Rails sets up (for the CookieStore) a secret key used for signing the session da
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...'
+YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'
```
NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions.
View
2  guides/source/configuring.md
@@ -113,7 +113,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored.
-* `config.secret_token` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_token` initialized to a random key in `config/initializers/secret_token.rb`.
+* `config.secret_key_base` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`.
* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won´t be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app.
View
59 railties/lib/rails/application.rb
@@ -1,5 +1,7 @@
require 'fileutils'
require 'active_support/queueing'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
require 'rails/engine'
module Rails
@@ -106,32 +108,57 @@ def reload_routes!
def key_generator
# number of iterations selected based on consultation with the google security
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
- @key_generator ||= ActiveSupport::KeyGenerator.new(config.secret_token, iterations: 1000)
+ @caching_key_generator ||= begin
+ if config.secret_key_base
+ key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
+ ActiveSupport::CachingKeyGenerator.new(key_generator)
+ else
+ ActiveSupport::DummyKeyGenerator.new(config.secret_token)
+ end
+ end
end
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
# Currently stores:
#
- # * "action_dispatch.parameter_filter" => config.filter_parameters,
- # * "action_dispatch.secret_token" => config.secret_token,
- # * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
- # * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
- # * "action_dispatch.logger" => Rails.logger,
- # * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
+ # * "action_dispatch.parameter_filter" => config.filter_parameters
+ # * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
+ # * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local
+ # * "action_dispatch.logger" => Rails.logger
+ # * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
+ # * "action_dispatch.key_generator" => key_generator
+ # * "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt
+ # * "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt
+ # * "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt
+ # * "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt
#
# These parameters will be used by middlewares and engines to configure themselves
#
def env_config
- @env_config ||= super.merge({
- "action_dispatch.parameter_filter" => config.filter_parameters,
- "action_dispatch.secret_token" => config.secret_token,
- "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
- "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
- "action_dispatch.logger" => Rails.logger,
- "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner,
- "action_dispatch.key_generator" => key_generator
- })
+ @env_config ||= begin
+ if config.secret_key_base.nil?
+ ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base. " +
+ "This should be used instead of the old deprecated config.secret_token. " +
+ "Set config.secret_key_base instead of config.secret_token in config/initializers/secret_token.rb"
+ if config.secret_token.blank?
+ raise "You must set config.secret_key_base in your app's config"
+ end
+ end
+
+ super.merge({
+ "action_dispatch.parameter_filter" => config.filter_parameters,
+ "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
+ "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
+ "action_dispatch.logger" => Rails.logger,
+ "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner,
+ "action_dispatch.key_generator" => key_generator,
+ "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt,
+ "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt,
+ "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt,
+ "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt
+ })
+ end
end
## Rails internal API
View
4 railties/lib/rails/application/configuration.rb
@@ -10,7 +10,7 @@ class Configuration < ::Rails::Engine::Configuration
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
- :railties_order, :relative_url_root, :secret_token,
+ :railties_order, :relative_url_root, :secret_key_base, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
:queue, :queue_consumer, :beginning_of_week
@@ -46,6 +46,8 @@ def initialize(*)
@queue = ActiveSupport::SynchronousQueue.new
@queue_consumer = nil
@eager_load = nil
+ @secret_token = nil
+ @secret_key_base = nil
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
View
4 railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -7,6 +7,6 @@
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.
-# Make sure your secret_token is kept private
+# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
-<%= app_const %>.config.secret_token = '<%= app_secret %>'
+<%= app_const %>.config.secret_key_base = '<%= app_secret %>'
View
2  railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-<%= app_const %>.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>
+<%= app_const %>.config.session_store :encrypted_cookie_store, key: <%= "'_#{app_name}_session'" %>
View
1  railties/test/abstract_unit.rb
@@ -14,5 +14,6 @@
module TestApp
class Application < Rails::Application
config.root = File.dirname(__FILE__)
+ config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
end
end
View
12 railties/test/application/configuration_test.rb
@@ -225,21 +225,24 @@ def assert_utf8
assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path
end
- test "config.secret_token is sent in env" do
+ test "Use key_generator when secret_key_base is set" do
make_basic_app do |app|
- app.config.secret_token = 'b3c631c314c0bbca50c1b2843150fe33'
+ app.config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
app.config.session_store :disabled
end
class ::OmgController < ActionController::Base
def index
cookies.signed[:some_key] = "some_value"
- render text: env["action_dispatch.secret_token"]
+ render text: cookies[:some_key]
end
end
get "/"
- assert_equal 'b3c631c314c0bbca50c1b2843150fe33', last_response.body
+
+ secret = app.key_generator.generate_key('signed cookie')
+ verifier = ActiveSupport::MessageVerifier.new(secret)
+ assert_equal 'some_value', verifier.verify(last_response.body)
end
test "protect from forgery is the default in a new app" do
@@ -568,7 +571,6 @@ def index
assert_respond_to app, :env_config
assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters
- assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token
assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions
assert_equal app.env_config['action_dispatch.logger'], Rails.logger
assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner
View
4 railties/test/application/middleware/remote_ip_test.rb
@@ -1,4 +1,6 @@
require 'isolation/abstract_unit'
+# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/key_generator'
module ApplicationTests
class RemoteIpTest < ActiveSupport::TestCase
@@ -8,7 +10,7 @@ def remote_ip(env = {})
remote_ip = nil
env = Rack::MockRequest.env_for("/").merge(env).merge!(
'action_dispatch.show_exceptions' => false,
- 'action_dispatch.secret_token' => 'b3c631c314c0bbca50c1b2843150fe33'
+ 'action_dispatch.key_generator' => ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
)
endpoint = Proc.new do |e|
View
51 railties/test/application/middleware/session_test.rb
@@ -128,5 +128,56 @@ def read_cookie
get '/foo/read_cookie' # Cookie shouldn't be changed
assert_equal '"1"', last_response.body
end
+
+ test "session using encrypted cookie store" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_session
+ session[:foo] = 1
+ render nothing: true
+ end
+
+ def read_session
+ render text: session[:foo]
+ end
+
+ def read_encrypted_cookie
+ render text: cookies.encrypted[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render text: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.session_store :encrypted_cookie_store, key: '_myapp_session'
+ config.action_dispatch.derive_signed_cookie_key = true
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get '/foo/write_session'
+ get '/foo/write_session'
+ get '/foo/read_session'
+ assert_equal '1', last_response.body
+
+ get '/foo/read_encrypted_cookie'
+ assert_equal '1', last_response.body
+
+ secret = app.key_generator.generate_key('encrypted cookie')
+ sign_secret = app.key_generator.generate_key('signed encrypted cookie')
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+
+ get '/foo/read_raw_cookie'
+ assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo']
+ end
end
end
View
2  railties/test/application/url_generation_test.rb
@@ -14,7 +14,7 @@ def app
require "action_controller/railtie"
class MyApp < Rails::Application
- config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.eager_load = false
View
2  railties/test/generators/app_generator_test.rb
@@ -341,7 +341,7 @@ def test_no_active_record_or_test_unit_if_skips_given
def test_new_hash_style
run_generator [destination_root]
assert_file "config/initializers/session_store.rb" do |file|
- assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file)
+ assert_match(/config.session_store :encrypted_cookie_store, key: '_.+_session'/, file)
end
end
View
4 railties/test/isolation/abstract_unit.rb
@@ -119,7 +119,7 @@ def build_app(options = {})
add_to_config <<-RUBY
config.eager_load = false
- config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.action_controller.allow_forgery_protection = false
@@ -138,7 +138,7 @@ def make_basic_app
app = Class.new(Rails::Application)
app.config.eager_load = false
- app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ app.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
app.config.session_store :cookie_store, key: "_myapp_session"
app.config.active_support.deprecation = :log
Something went wrong with that request. Please try again.