AEAD encrypted cookies and sessions #28132

Merged
merged 1 commit into from May 28, 2017

Conversation

Projects
None yet
6 participants
@mikeycgto
Contributor

mikeycgto commented Feb 23, 2017

Summary

This PR is the start of migrating from HMAC AES-CBC encrypted cookies to AEAD encrypted cookies. Commit d4ea18a added AES-256-GCM for Authenticated Encryption support. I'm hoping this PR could be the start of migrating cookies and sessions to this form of encryption.

I think it's worth considering to what degree we should be supporting legacy signed and encrypted cookies as well. This PR includes a UpgradeLegacyHmacAesCbcCookieJar class which aims to seamlessly upgrade encrypted cookies. Should we be looking to deprecate older legacy schemes now?

Other Information

This PR comments out some tests that are now broken. Depending on how we move forward with encrypted cookies and to the degree to which legacy cookies are supported, I will update, fix, and add any tests as needed.

@rafaelfranca

This comment has been minimized.

Show comment
Hide comment
@rafaelfranca

rafaelfranca Feb 23, 2017

Member

Could you explain briefly too why migrating to AEAD is good?

Member

rafaelfranca commented Feb 23, 2017

Could you explain briefly too why migrating to AEAD is good?

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto Feb 23, 2017

Contributor

Sure, by using AES with GCM, ciphertexts will be shorter and decryption and encryption will be quicker. With GCM, authentication and encryption are processed together instead of separately like we see with the HMAC and AES CBC authenticated encryption setup.

Additionally, the announcement today from Google about SHA1 collisions somewhat drove me to make this PR. While unrelated to security and encryption features​ in Rails, the announcement motivated me to look into what it would take to update Rails' cookies to use more modern encryption. If we do not go down the AEAD route for encrypted cookies, we should at least be exploring using SHA256 for the digest option for MessageEncryptor.

As an aside, AEAD in general aims to make cryptographic processes simpler and less error prone. I think it may be worthwhile making GCM the default for MessageEncryptor but that is beyond the scope of this PR.

Contributor

mikeycgto commented Feb 23, 2017

Sure, by using AES with GCM, ciphertexts will be shorter and decryption and encryption will be quicker. With GCM, authentication and encryption are processed together instead of separately like we see with the HMAC and AES CBC authenticated encryption setup.

Additionally, the announcement today from Google about SHA1 collisions somewhat drove me to make this PR. While unrelated to security and encryption features​ in Rails, the announcement motivated me to look into what it would take to update Rails' cookies to use more modern encryption. If we do not go down the AEAD route for encrypted cookies, we should at least be exploring using SHA256 for the digest option for MessageEncryptor.

As an aside, AEAD in general aims to make cryptographic processes simpler and less error prone. I think it may be worthwhile making GCM the default for MessageEncryptor but that is beyond the scope of this PR.

@rafaelfranca

This comment has been minimized.

Show comment
Hide comment
@rafaelfranca

rafaelfranca Feb 23, 2017

Member

Thanks for the explanation!

Member

rafaelfranca commented Feb 23, 2017

Thanks for the explanation!

@eugeneius

This comment has been minimized.

Show comment
Hide comment
@eugeneius

eugeneius Feb 23, 2017

Member

we should at least be exploring using SHA256 for the digest option for MessageEncryptor.

There's an open pull request to do just that (#25204), but it's blocked on the ability to transparently upgrade existing cookies to the new format (#18772) which I suspect we'll need for this change too.

Member

eugeneius commented Feb 23, 2017

we should at least be exploring using SHA256 for the digest option for MessageEncryptor.

There's an open pull request to do just that (#25204), but it's blocked on the ability to transparently upgrade existing cookies to the new format (#18772) which I suspect we'll need for this change too.

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto Feb 23, 2017

Contributor

@eugeneius there is a small cookie jar class in this PR to upgrade HMAC CBC encrypted cookies to GCM. This could be altered to handle the upgrade from SHA1 to SHA256 though.

I think in the long run introducing GCM cookies is better though. The smaller ciphertexts is a worthwhile improvement.

Contributor

mikeycgto commented Feb 23, 2017

@eugeneius there is a small cookie jar class in this PR to upgrade HMAC CBC encrypted cookies to GCM. This could be altered to handle the upgrade from SHA1 to SHA256 though.

I think in the long run introducing GCM cookies is better though. The smaller ciphertexts is a worthwhile improvement.

@eugeneius

This comment has been minimized.

Show comment
Hide comment
@eugeneius

eugeneius Feb 23, 2017

Member

Oh, nice! You even mentioned it in the description 🤦‍♂️

Member

eugeneius commented Feb 23, 2017

Oh, nice! You even mentioned it in the description 🤦‍♂️

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto Feb 23, 2017

Contributor

Put together some small benchmarks comparing the two modes. The script also outputs the length of two ciphertexts, one for each mode.

The repo is here: https://github.com/mikeycgto/message_encryptor-benchmark

Contributor

mikeycgto commented Feb 23, 2017

Put together some small benchmarks comparing the two modes. The script also outputs the length of two ciphertexts, one for each mode.

The repo is here: https://github.com/mikeycgto/message_encryptor-benchmark

@bdewater

This comment has been minimized.

Show comment
Hide comment
@bdewater

bdewater Feb 24, 2017

Contributor

Additionally, the announcement today from Google about SHA1 collisions somewhat drove me to make this PR.

Important to note here this finding of a SHA1 collision has nothing to do with the security of HMAC-SHA1, which MessageVerifier uses. So people shouldn't panic if they cannot upgrade straight away.

That said, not only is an AEAD mode faster for encryption/decryption (AES-GCM especially with processor instructions) but it also produces smaller cookies (shown in #25874) so 👍👍👍 if we can transparently upgrade.

Contributor

bdewater commented Feb 24, 2017

Additionally, the announcement today from Google about SHA1 collisions somewhat drove me to make this PR.

Important to note here this finding of a SHA1 collision has nothing to do with the security of HMAC-SHA1, which MessageVerifier uses. So people shouldn't panic if they cannot upgrade straight away.

That said, not only is an AEAD mode faster for encryption/decryption (AES-GCM especially with processor instructions) but it also produces smaller cookies (shown in #25874) so 👍👍👍 if we can transparently upgrade.

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto Feb 24, 2017

Contributor

Agreed @bdewater.

HMACs in general are still secure even when the underlying hash function have known collisions issues. Google's announcement motivated me to look into what it would take to update Rails' cookies to use more modern encryption and security. This is actually something I've been thinking about for a while, today's news just lit the spark for me!

Contributor

mikeycgto commented Feb 24, 2017

Agreed @bdewater.

HMACs in general are still secure even when the underlying hash function have known collisions issues. Google's announcement motivated me to look into what it would take to update Rails' cookies to use more modern encryption and security. This is actually something I've been thinking about for a while, today's news just lit the spark for me!

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto Feb 24, 2017

Contributor

Notes on Legacy Support

Thought more about this and want to clarify somethings as far as legacy support goes. Currently there are two legacy cookie jar classes UpgradeLegacySignedCookieJar and UpgradeLegacyEncryptedCookieJar. Both of these classes include VerifyAndUpgradeLegacySignedMessage which upgrades cookies that are just HMAC'd using the old secret_token approach which was deprecated in Rails v4.

This PR adds a UpgradeLegacyHmacAesCbcCookieJar class for upgrading encrypted cookies to the new GCM encryption scheme. This jar is used when action_dispatch.encrypted_cookie_salt and action_dispatch.action_dispatch.encrypted_signed_cookie_salt are set along with the new action_dispatch.authenticated_encrypted_cookie_salt configuration value. These options should enable developers to finely control the migration path. Applications that need migration can continue to set the encrypted_cookie_salt and encrypted_signed_cookie_salt values. New applications can just have the authenticated_encrypted_cookie_salt value set and no migration will be attempted when decryption fails.

I think it's worth considering at this time if secret_token legacy support should be removed. Doing so will help simplify the cookie jar middleware code base significantly. The tests can also be cleaned up and far less cases will need to be covered.

If support is kept, I fully intend to test all legacy configurations. That is, I want to make sure "encrypted" cookies that are just signed with a secret_token are upgraded to GCM encryption as expected. I believe this already the case but this needs to be more thoroughly tested.

Thus, my main question is, should secret_token legacy support be removed now with these changes?

Possible Major API Changes

I'd also go as far as to suggest some major API changes to the cookie middleware. Specifically, we could deprecate the encrypted and signed methods for a single secured method. The two main reasons for this suggestion are:

  1. From a developer usage security prospective, it is best to limit options. Developers may be unsure if authenticity is sufficient or if confidential is needed for their use cases.
  2. Signed cookies are very large since the message is just base64 encoded. With the introduction of GCM cookies we now have smaller resulting ciphertexts. A single secured cookie method which just uses GCM encrypted cookies could be sufficient for when only authenticity is needed.

This suggestion is obviously a lot more significant than what the PR has initially set out to do. I figured it is worth suggesting and discussing though!

Contributor

mikeycgto commented Feb 24, 2017

Notes on Legacy Support

Thought more about this and want to clarify somethings as far as legacy support goes. Currently there are two legacy cookie jar classes UpgradeLegacySignedCookieJar and UpgradeLegacyEncryptedCookieJar. Both of these classes include VerifyAndUpgradeLegacySignedMessage which upgrades cookies that are just HMAC'd using the old secret_token approach which was deprecated in Rails v4.

This PR adds a UpgradeLegacyHmacAesCbcCookieJar class for upgrading encrypted cookies to the new GCM encryption scheme. This jar is used when action_dispatch.encrypted_cookie_salt and action_dispatch.action_dispatch.encrypted_signed_cookie_salt are set along with the new action_dispatch.authenticated_encrypted_cookie_salt configuration value. These options should enable developers to finely control the migration path. Applications that need migration can continue to set the encrypted_cookie_salt and encrypted_signed_cookie_salt values. New applications can just have the authenticated_encrypted_cookie_salt value set and no migration will be attempted when decryption fails.

I think it's worth considering at this time if secret_token legacy support should be removed. Doing so will help simplify the cookie jar middleware code base significantly. The tests can also be cleaned up and far less cases will need to be covered.

If support is kept, I fully intend to test all legacy configurations. That is, I want to make sure "encrypted" cookies that are just signed with a secret_token are upgraded to GCM encryption as expected. I believe this already the case but this needs to be more thoroughly tested.

Thus, my main question is, should secret_token legacy support be removed now with these changes?

Possible Major API Changes

I'd also go as far as to suggest some major API changes to the cookie middleware. Specifically, we could deprecate the encrypted and signed methods for a single secured method. The two main reasons for this suggestion are:

  1. From a developer usage security prospective, it is best to limit options. Developers may be unsure if authenticity is sufficient or if confidential is needed for their use cases.
  2. Signed cookies are very large since the message is just base64 encoded. With the introduction of GCM cookies we now have smaller resulting ciphertexts. A single secured cookie method which just uses GCM encrypted cookies could be sufficient for when only authenticity is needed.

This suggestion is obviously a lot more significant than what the PR has initially set out to do. I figured it is worth suggesting and discussing though!

@mikeycgto mikeycgto changed the title from Start of AEAD encrypted cookies and sessions to AEAD encrypted cookies and sessions Feb 27, 2017

@bdewater

This comment has been minimized.

Show comment
Hide comment
@bdewater

bdewater Feb 28, 2017

Contributor

Thus, my main question is, should secret_token legacy support be removed now with these changes?

AFAIK Rails usually introduces deprecation warnings in minor versions before removing stuff. With 5.1 already in beta, removal seems more suited when master targets 5.2. Just my $0.02.

Contributor

bdewater commented Feb 28, 2017

Thus, my main question is, should secret_token legacy support be removed now with these changes?

AFAIK Rails usually introduces deprecation warnings in minor versions before removing stuff. With 5.1 already in beta, removal seems more suited when master targets 5.2. Just my $0.02.

@mikeycgto mikeycgto referenced this pull request in mikeycgto/message_verifier Mar 6, 2017

Closed

Add AES GCM Cipher Mode #9

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto Mar 15, 2017

Contributor

This PR should now be all set. I updated the configuring and security source guides to mention the change in encryption as of Rails version 5.1.

Pretty much all existing applications will want to continue to set the action_dispatch.encrypted_signed_cookie_salt and action_dispatch.authenticated_encrypted_cookie_salt configuration values to opt-in to upgrade behavior. This point should be made very clear the release notes.

The default values use to be 'encrypted cookie' and signed encrypted cookie' but are now no longer set in actionpack/lib/action_dispatch/railtie.rb.

Contributor

mikeycgto commented Mar 15, 2017

This PR should now be all set. I updated the configuring and security source guides to mention the change in encryption as of Rails version 5.1.

Pretty much all existing applications will want to continue to set the action_dispatch.encrypted_signed_cookie_salt and action_dispatch.authenticated_encrypted_cookie_salt configuration values to opt-in to upgrade behavior. This point should be made very clear the release notes.

The default values use to be 'encrypted cookie' and signed encrypted cookie' but are now no longer set in actionpack/lib/action_dispatch/railtie.rb.

@@ -110,7 +110,7 @@ def _decrypt(encrypted_message)
# Currently the OpenSSL bindings do not raise an error if auth_tag is
# truncated, which would allow an attacker to easily forge it. See
# https://github.com/ruby/openssl/issues/63
- raise InvalidMessage if aead_mode? && auth_tag.bytes.length != 16
+ raise InvalidMessage if aead_mode? && (auth_tag.blank? || auth_tag.bytes.length != 16)

This comment has been minimized.

@bdewater

bdewater Apr 3, 2017

Contributor

Nitpick, instead of blank? a nil? is enough because "".bytes == 0

@bdewater

bdewater Apr 3, 2017

Contributor

Nitpick, instead of blank? a nil? is enough because "".bytes == 0

This comment has been minimized.

@mikeycgto

mikeycgto Apr 3, 2017

Contributor

👍

@mikeycgto

mikeycgto Apr 3, 2017

Contributor

👍

guides/source/configuring.md
@@ -455,11 +455,11 @@ to `'http authentication'`.
* `config.action_dispatch.signed_cookie_salt` sets the signed cookies salt value.
Defaults to `'signed cookie'`.
-* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt
-value. Defaults to `'encrypted cookie'`.
+* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt value. This is value is deprecated in Rails 5.1 in favor of `config.action_dispatch.authenticated_encrypted_cookie_salt`.

This comment has been minimized.

@bdewater

bdewater Apr 3, 2017

Contributor

Master now targets 5.2.

@bdewater

bdewater Apr 3, 2017

Contributor

Master now targets 5.2.

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 14, 2017

Member

👋

Don't think we can yank the old cookie jars without deprecating secret_token. It's true that we require secret_key_base, so Rails 4 apps have been steadily upgrading their cookies as they've been encountering old cookies in production. But what if apps contain cookies.permanent.signed? Then they'd be decidedly not permanent 😅

We're also setting people up for upgrade pain if we enable the GCM cookie upgrade by default. I know Shopify had a continuous Rails 5 upgrade: they were running both 4.2 and 5.0 in production simultaneously to spot regressions. If this change had been in there, the 5.x cookies wouldn't be compatible with 4.2.

Instead, we should add a 5.2 upgrade file mimicking https://github.com/rails/rails/blob/46adb59b3474351e95ddc2357e67d169d6ffec51/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt with this lone config in it:

# Use AES 256 GCM authenticated encryption for encrypted cookies.
# Existing cookies will be converted on read then written with the new scheme.
# This upgrade option will be removed in 5.3.
Rails.application.config.use_authenticated_cookie_encryption = false

That flag then sets action_dispatch.authenticated_encrypted_cookie_salt to your default value when enabled. I'd prefer not to expose that as the config, since I don't think most apps need to tussle with it (we should be able to pick a sensible default there).

Member

kaspth commented May 14, 2017

👋

Don't think we can yank the old cookie jars without deprecating secret_token. It's true that we require secret_key_base, so Rails 4 apps have been steadily upgrading their cookies as they've been encountering old cookies in production. But what if apps contain cookies.permanent.signed? Then they'd be decidedly not permanent 😅

We're also setting people up for upgrade pain if we enable the GCM cookie upgrade by default. I know Shopify had a continuous Rails 5 upgrade: they were running both 4.2 and 5.0 in production simultaneously to spot regressions. If this change had been in there, the 5.x cookies wouldn't be compatible with 4.2.

Instead, we should add a 5.2 upgrade file mimicking https://github.com/rails/rails/blob/46adb59b3474351e95ddc2357e67d169d6ffec51/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt with this lone config in it:

# Use AES 256 GCM authenticated encryption for encrypted cookies.
# Existing cookies will be converted on read then written with the new scheme.
# This upgrade option will be removed in 5.3.
Rails.application.config.use_authenticated_cookie_encryption = false

That flag then sets action_dispatch.authenticated_encrypted_cookie_salt to your default value when enabled. I'd prefer not to expose that as the config, since I don't think most apps need to tussle with it (we should be able to pick a sensible default there).

+ request.secret_key_base.present? &&
+ request.encrypted_cookie_salt.present? &&
+ request.encrypted_signed_cookie_salt.present? &&
+ request.authenticated_encrypted_cookie_salt.present?

This comment has been minimized.

@kaspth

kaspth May 14, 2017

Member

Prefer:

request… &&
  request… &&
  request… &&
  request…?
@kaspth

kaspth May 14, 2017

Member

Prefer:

request… &&
  request… &&
  request… &&
  request…?
- secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
- sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ cipher = "aes-256-gcm"

This comment has been minimized.

@kaspth

kaspth May 14, 2017

Member

We should just make this the default for ActiveSupport::MessageEncryptor, so we wouldn't need this line.

I'll make that part of @assain's GSoC tasks 😉

@kaspth

kaspth May 14, 2017

Member

We should just make this the default for ActiveSupport::MessageEncryptor, so we wouldn't need this line.

I'll make that part of @assain's GSoC tasks 😉

This comment has been minimized.

@mikeycgto

mikeycgto May 14, 2017

Contributor

This sounds good. Agreed this should become the new default!

@mikeycgto

mikeycgto May 14, 2017

Contributor

This sounds good. Agreed this should become the new default!

@@ -115,7 +115,7 @@ def _decrypt(encrypted_message)
# Currently the OpenSSL bindings do not raise an error if auth_tag is
# truncated, which would allow an attacker to easily forge it. See
# https://github.com/ruby/openssl/issues/63
- raise InvalidMessage if aead_mode? && auth_tag.bytes.length != 16
+ raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16)

This comment has been minimized.

@kaspth

kaspth May 14, 2017

Member

Why do we need the nil? check? Should that be a separate fix + regression tests we backport to 5.1 and 5.0 as well?

@kaspth

kaspth May 14, 2017

Member

Why do we need the nil? check? Should that be a separate fix + regression tests we backport to 5.1 and 5.0 as well?

This comment has been minimized.

@mikeycgto

mikeycgto May 14, 2017

Contributor

This was something I stumbled upon during testing the upgrade cases. It is probably something that should be pulled out into a separate PR and backported to whenever AEAD was added as a feature of MessageEncryptor.

@mikeycgto

mikeycgto May 14, 2017

Contributor

This was something I stumbled upon during testing the upgrade cases. It is probably something that should be pulled out into a separate PR and backported to whenever AEAD was added as a feature of MessageEncryptor.

This comment has been minimized.

@mikeycgto

mikeycgto May 15, 2017

Contributor

I created PR rails/rails#29086 for this since we'll likely want to backport this fix to Rails 5.1 (which is when AEAD mode was introduced into MessageEncryptor.

@mikeycgto

mikeycgto May 15, 2017

Contributor

I created PR rails/rails#29086 for this since we'll likely want to backport this fix to Rails 5.1 (which is when AEAD mode was introduced into MessageEncryptor.

guides/source/configuring.md
-* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed
-encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed encrypted cookies salt value. This is value is deprecated in Rails 5.2 in favor of `config.action_dispatch.authenticated_encrypted_cookie_salt`.

This comment has been minimized.

@kaspth

kaspth May 14, 2017

Member

Just saying deprecated in the guides won't cut it. We need to use ActiveSupport::Deprecation.warn — but I'm not sure we should deprecate those just yet, so let's leave this mention out of this PR.

@kaspth

kaspth May 14, 2017

Member

Just saying deprecated in the guides won't cut it. We need to use ActiveSupport::Deprecation.warn — but I'm not sure we should deprecate those just yet, so let's leave this mention out of this PR.

This comment has been minimized.

@mikeycgto

mikeycgto May 14, 2017

Contributor

Sounds good. I'll remove this from the guide for now.

@mikeycgto

mikeycgto May 14, 2017

Contributor

Sounds good. I'll remove this from the guide for now.

guides/source/configuring.md
-encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed encrypted cookies salt value. This is value is deprecated in Rails 5.2 in favor of `config.action_dispatch.authenticated_encrypted_cookie_salt`.
+
+* `config.action_dispatch.authenticated_encrypted_cookie_salt` sets the authenticated encrypted cookie salt. Defaults to `'authenticated encrypted cookie'`.

This comment has been minimized.

@kaspth

kaspth May 14, 2017

Member

I'm really confused about this. Are apps expected to overwrite this? When would it make sense for apps to do so?

@kaspth

kaspth May 14, 2017

Member

I'm really confused about this. Are apps expected to overwrite this? When would it make sense for apps to do so?

This comment has been minimized.

@mikeycgto

mikeycgto May 15, 2017

Contributor

I don't think very many applications set these salts.

As long as the secret_key_base is sufficiently long and random it's fine to not use a random salt for PBKDF. In fact, since secret_key_base is from SecureRandom it would likely be OK to use directly as the cipher key. I'm not suggesting we do that though, because the key base is used for multiple features (like signed cookies).

Some quick Googling lead me to this post which mentions that they do set the salts (the post talks about accessing Rails' session in a Phoenix app). I'm on the fence if this new salt value should be configurable or not. If the value is changed for an existing application, legitimate cookies/sessions would be invalidated because the resulting cipher key would then be different and decryption would fail. Having a upgrade middleware to handle such a case would be overly complex and not something we should bother with.

@mikeycgto

mikeycgto May 15, 2017

Contributor

I don't think very many applications set these salts.

As long as the secret_key_base is sufficiently long and random it's fine to not use a random salt for PBKDF. In fact, since secret_key_base is from SecureRandom it would likely be OK to use directly as the cipher key. I'm not suggesting we do that though, because the key base is used for multiple features (like signed cookies).

Some quick Googling lead me to this post which mentions that they do set the salts (the post talks about accessing Rails' session in a Phoenix app). I'm on the fence if this new salt value should be configurable or not. If the value is changed for an existing application, legitimate cookies/sessions would be invalidated because the resulting cipher key would then be different and decryption would fail. Having a upgrade middleware to handle such a case would be overly complex and not something we should bother with.

This comment has been minimized.

@kaspth

kaspth May 15, 2017

Member

Agree the middleware is out of scope.

I'm fine with making it configurable. And then we'll need to mention it in the security guide, but stress that it's not something that most apps should use:

Rails provides a default salt for the authenticated encryption. In the rare case you want to change that use `config.action_dispatch.authenticated_encrypted_cookie_salt`.
@kaspth

kaspth May 15, 2017

Member

Agree the middleware is out of scope.

I'm fine with making it configurable. And then we'll need to mention it in the security guide, but stress that it's not something that most apps should use:

Rails provides a default salt for the authenticated encryption. In the rare case you want to change that use `config.action_dispatch.authenticated_encrypted_cookie_salt`.
guides/source/security.md
-Thus the session becomes a more secure place to store data. The encryption is
-done using a server-side secret key `secrets.secret_key_base` stored in
-`config/secrets.yml`.
+In Rails 4, encrypted cookies through AES in CBC mode with HMAC using SHA1 for verification was introduced. This prevents the user from accessing and tampering the content of the cookie. Thus the session becomes a more secure place to store data. The encryption is done using a server-side secret key `secrets.secret_key_base` stored in `config/secrets.yml`. Two salts are used when deriving keys for encryption and verification. These salts are set via the `config.action_dispatch.encrypted_cookie_salt` and `config.action_dispatch.encrypted_signed_cookie_salt` configuration values.

This comment has been minimized.

@kaspth

kaspth May 14, 2017

Member

I'd nix secret key in "using the server-side secret key secrets.secret_key_base".

@kaspth

kaspth May 14, 2017

Member

I'd nix secret key in "using the server-side secret key secrets.secret_key_base".

This comment has been minimized.

@kaspth

kaspth May 14, 2017

Member

secret_key_base could also be stored in config/secrets.yml.enc, so I'd drop the file reference.

@kaspth

kaspth May 14, 2017

Member

secret_key_base could also be stored in config/secrets.yml.enc, so I'd drop the file reference.

This comment has been minimized.

@mikeycgto

mikeycgto May 14, 2017

Contributor

Will do!

@mikeycgto

mikeycgto May 14, 2017

Contributor

Will do!

guides/source/security.md
-That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rails secret` instead_.
+In Rails 5.2, this encryption was changed to AES in GCM mode. This new scheme is faster and couples authentication and encryption into a single step. It also produces smaller ciphertexts and thus smaller cookies. The change also introduced a new salt which is set via the `config.action_dispatch.authenticated_encrypted_cookie_salt` configuration value. To have cookies encrypted with AES-CBC-HMAC be seamlessly upgraded to this new scheme, the `encrypted_cookie_salt` and `encrypted_signed_cookie_salt` configuration values should be set.

This comment has been minimized.

@kaspth

kaspth May 14, 2017

Member

I'd say:

Rails 5.2 uses AES in GCM mode for the encryption which couples authentication and encryption in one faster step and produces shorter ciphertexts.

Encrypted cookies are automatically upgraded if the `config.action_dispatch.use_authenticated_cookie_encryption` is enabled.
@kaspth

kaspth May 14, 2017

Member

I'd say:

Rails 5.2 uses AES in GCM mode for the encryption which couples authentication and encryption in one faster step and produces shorter ciphertexts.

Encrypted cookies are automatically upgraded if the `config.action_dispatch.use_authenticated_cookie_encryption` is enabled.
@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 14, 2017

Member

I dig the intention behind the secured proposal too 👍

But perhaps we should reap the rewards of the shorter ciphertexts and faster authentication + encryption by just having cookies spout out tokens that are encrypted then signed by default?

Strikes me as similar to HTML being auto-escaped in views.

For opt-out users could then use cookies.plain[:shareable_key] = '…' or similar.

Member

kaspth commented May 14, 2017

I dig the intention behind the secured proposal too 👍

But perhaps we should reap the rewards of the shorter ciphertexts and faster authentication + encryption by just having cookies spout out tokens that are encrypted then signed by default?

Strikes me as similar to HTML being auto-escaped in views.

For opt-out users could then use cookies.plain[:shareable_key] = '…' or similar.

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 14, 2017

Contributor

That sounds good on the upgrade path and it's configuration. It's an interesting point that some are running Rails 4.2 and Rails 5.0 in parallel. Making sure cookies are decryptable and accessible on both stacks is very important, to say the least! I can make the necessary changes and add a new_framework_defaults_5_2.rb file.

Contributor

mikeycgto commented May 14, 2017

That sounds good on the upgrade path and it's configuration. It's an interesting point that some are running Rails 4.2 and Rails 5.0 in parallel. Making sure cookies are decryptable and accessible on both stacks is very important, to say the least! I can make the necessary changes and add a new_framework_defaults_5_2.rb file.

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 14, 2017

Member

@mikeycgto Sounds good 👍

Well, I think Shopify is purely on 5.0 now, but they ran it in a dual setup for a while.

Also forgot to say: very well rounded and superbly done PR. Really appreciate how you're bringing your encryption expertise to bear here 👌

Member

kaspth commented May 14, 2017

@mikeycgto Sounds good 👍

Well, I think Shopify is purely on 5.0 now, but they ran it in a dual setup for a while.

Also forgot to say: very well rounded and superbly done PR. Really appreciate how you're bringing your encryption expertise to bear here 👌

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 14, 2017

Member

Another thing, let's just drop the # This upgrade option will be removed in 5.3. part, we shouldn't chase people to the future too hard, no matter how much they'd benefit from it.

Member

kaspth commented May 14, 2017

Another thing, let's just drop the # This upgrade option will be removed in 5.3. part, we shouldn't chase people to the future too hard, no matter how much they'd benefit from it.

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 14, 2017

Contributor

@kaspth should the use_authenticated_cookie_encryption configuration value be added to railties/lib/rails/application/configuration.rb and be initialized to false? Want to make sure I add this new configuration value to the right location (since it's not under the action_dispatch namespace).

Contributor

mikeycgto commented May 14, 2017

@kaspth should the use_authenticated_cookie_encryption configuration value be added to railties/lib/rails/application/configuration.rb and be initialized to false? Want to make sure I add this new configuration value to the right location (since it's not under the action_dispatch namespace).

mikeycgto added a commit to mikeycgto/rails that referenced this pull request May 15, 2017

Fix for AEAD auth_tag check in MessageEncryptor
When MessageEncryptor tries to +decrypt_and_verify+ ciphertexts
generated in a different mode (such CBC-HMAC), the +auth_tag+ may be
+nil+ and must explicitly check for it.

See the discussion here:
rails#28132 (comment)
@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 15, 2017

Member

@mikeycgto yeah, just set it to false after here

@read_encrypted_secrets = false

Let's nestle it under the config.action_dispatch namespace though so it's along authenticated_encrypted_cookie_salt.

Member

kaspth commented May 15, 2017

@mikeycgto yeah, just set it to false after here

@read_encrypted_secrets = false

Let's nestle it under the config.action_dispatch namespace though so it's along authenticated_encrypted_cookie_salt.

guides/source/configuring.md
@@ -455,11 +455,11 @@ to `'http authentication'`.
* `config.action_dispatch.signed_cookie_salt` sets the signed cookies salt value.
Defaults to `'signed cookie'`.
-* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt
-value. Defaults to `'encrypted cookie'`.
+* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt value. Defaults to `'signed cookie'`.

This comment has been minimized.

@kaspth

kaspth May 15, 2017

Member

Lets keep these lines wrapped at 80 chars.

@kaspth

kaspth May 15, 2017

Member

Lets keep these lines wrapped at 80 chars.

guides/source/security.md
-Thus the session becomes a more secure place to store data. The encryption is
-done using a server-side secret key `secrets.secret_key_base` stored in
-`config/secrets.yml`.
+In Rails 4, encrypted cookies through AES in CBC mode with HMAC using SHA1 for verification was introduced. This prevents the user from accessing and tampering the content of the cookie. Thus the session becomes a more secure place to store data. The encryption is performed using a server-side `secrets.secret_key_base`. Two salts are used when deriving keys for encryption and verification. These salts are set via the `config.action_dispatch.encrypted_cookie_salt` and `config.action_dispatch.encrypted_signed_cookie_salt` configuration values.

This comment has been minimized.

@kaspth

kaspth May 15, 2017

Member

Also needs to keep the 80 char limit.

@kaspth

kaspth May 15, 2017

Member

Also needs to keep the 80 char limit.

@@ -0,0 +1,3 @@
+# Use AES 256 GCM authenticated encryption for encrypted cookies.
+# Existing cookies will be converted on read then written with the new scheme.
+Rails.application.config.use_authenticated_cookie_encryption = false

This comment has been minimized.

@kaspth

kaspth May 15, 2017

Member

We need to copy over the intro from the _5_1.rb file and nestle the config to be under config.action_dispatch.

@kaspth

kaspth May 15, 2017

Member

We need to copy over the intro from the _5_1.rb file and nestle the config to be under config.action_dispatch.

+ @x = Custom.new
+ @enable_dependency_loading = false
+ @read_encrypted_secrets = false
+ @use_authenticated_cookie_encryption = false

This comment has been minimized.

@kaspth

kaspth May 15, 2017

Member

To nestle the config under action_dispatch we'll need to move the false assign to this file:

config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"

Your override in initializer further below in that file then needs to take the new namespace into account.

@kaspth

kaspth May 15, 2017

Member

To nestle the config under action_dispatch we'll need to move the false assign to this file:

config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"

Your override in initializer further below in that file then needs to take the new namespace into account.

actionpack/CHANGELOG.md
+
+ To have existing cookies be seamlessly upgraded to this new scheme
+ please continue to set the `encrypted_cookie_salt` and
+ `encrypted_signed_cookie_salt` values.

This comment has been minimized.

@kaspth

kaspth May 15, 2017

Member

Remember to rewrite this too 😉

@kaspth

kaspth May 15, 2017

Member

Remember to rewrite this too 😉

actionpack/CHANGELOG.md
+ To have existing cookies be seamlessly upgraded to this new scheme
+ please continue to set the `encrypted_cookie_salt` and
+ `encrypted_signed_cookie_salt` values.
+

This comment has been minimized.

@kaspth

kaspth May 15, 2017

Member

Don't forget to add your name too 😄

    *Michael Coyne*
@kaspth

kaspth May 15, 2017

Member

Don't forget to add your name too 😄

    *Michael Coyne*
@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 15, 2017

Contributor

@kaspth these changes sound good. Will get to them either later tonight or tomorrow morning sometime. Thanks for reviewing it again!

Contributor

mikeycgto commented May 15, 2017

@kaspth these changes sound good. Will get to them either later tonight or tomorrow morning sometime. Thanks for reviewing it again!

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 16, 2017

Contributor

Made some more changes and moved the use_authenticated_cookie_encryption configuration value to under the action_dispatch namespace. I added a small test to app_generator_test.rb as well to make sure the framework defaults file is generated.

One small question I have is if this config value is set to true in the new framework defaults initializer, will this initializer block here see the flag as true and thus set the salt as need? Maybe setting this salt should be done in an after_initialize block? Just wanted to ask about this and double check the configuration logic is sound.

Thanks again for all the reviews! Can't wait to see this feature merged in -- smaller 🍪 for all!!

Contributor

mikeycgto commented May 16, 2017

Made some more changes and moved the use_authenticated_cookie_encryption configuration value to under the action_dispatch namespace. I added a small test to app_generator_test.rb as well to make sure the framework defaults file is generated.

One small question I have is if this config value is set to true in the new framework defaults initializer, will this initializer block here see the flag as true and thus set the salt as need? Maybe setting this salt should be done in an after_initialize block? Just wanted to ask about this and double check the configuration logic is sound.

Thanks again for all the reviews! Can't wait to see this feature merged in -- smaller 🍪 for all!!

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 16, 2017

Contributor

Thought a bit more about this and I realized that because the two old salt values are still set by default, upgrade_legacy_hmac_aes_cbc_cookies? will always be true (assuming users have opted in to AEAD cookies in the first place).

Perhaps we should add a second configuration flag, such as config.action_dispatch.upgrade_hmac_cbc_cookie_encryption that will, for now, default to true. We can then change upgrade_legacy_hmac_aes_cbc_cookies? to just examine this configuration value as well as the use_authenticated_cookie_encryption value.

Ultimately, I think having direct configuration flags for both AEAD cookies as well as supporting the upgrade behavior is easier for developers to deal with than indirectly controlling this behavior via the salt config values. This way, new applications or applications that no longer need the upgrade behavior can easily opt-out of it. Thoughts?

Contributor

mikeycgto commented May 16, 2017

Thought a bit more about this and I realized that because the two old salt values are still set by default, upgrade_legacy_hmac_aes_cbc_cookies? will always be true (assuming users have opted in to AEAD cookies in the first place).

Perhaps we should add a second configuration flag, such as config.action_dispatch.upgrade_hmac_cbc_cookie_encryption that will, for now, default to true. We can then change upgrade_legacy_hmac_aes_cbc_cookies? to just examine this configuration value as well as the use_authenticated_cookie_encryption value.

Ultimately, I think having direct configuration flags for both AEAD cookies as well as supporting the upgrade behavior is easier for developers to deal with than indirectly controlling this behavior via the salt config values. This way, new applications or applications that no longer need the upgrade behavior can easily opt-out of it. Thoughts?

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 19, 2017

Contributor

Just rebased and my test_new_5_2_application_has_defaults in railties/test/generators/app_generator_test.rb is now failing. It seems this file isn't expected to be generated for a new 5.2 application (there is a test above it which indicate just that). However, since this feature is opt-in, we need this file to be generated so users can enable the new behavior if they'd like to. Any idea how we should solve this @kaspth?

Contributor

mikeycgto commented May 19, 2017

Just rebased and my test_new_5_2_application_has_defaults in railties/test/generators/app_generator_test.rb is now failing. It seems this file isn't expected to be generated for a new 5.2 application (there is a test above it which indicate just that). However, since this feature is opt-in, we need this file to be generated so users can enable the new behavior if they'd like to. Any idea how we should solve this @kaspth?

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 21, 2017

Contributor

I decided to add the upgrade_legacy_hmac_aes_cbc_cookies configuration flag as mention above so users can explicitly now control both opting-in to AES GCM cookies and whether or not upgrade behavior should be used. This should be easier to manage than fiddling with salt configuration values.

Not sure how to handle the failing test with regards to new_framework_defaults_5_1.rb.tt file. This file should be generated for new 5.2 applications so users can opt-in to AES GCM cookies. Perhaps these two configuration values should be moved to independent initializer for new applications? Any thoughts on this?

Contributor

mikeycgto commented May 21, 2017

I decided to add the upgrade_legacy_hmac_aes_cbc_cookies configuration flag as mention above so users can explicitly now control both opting-in to AES GCM cookies and whether or not upgrade behavior should be used. This should be easier to manage than fiddling with salt configuration values.

Not sure how to handle the failing test with regards to new_framework_defaults_5_1.rb.tt file. This file should be generated for new 5.2 applications so users can opt-in to AES GCM cookies. Perhaps these two configuration values should be moved to independent initializer for new applications? Any thoughts on this?

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 21, 2017

Member

However, since this feature is opt-in

It shouldn't be opt-in on a rails new 5.2 app. There it should be enabled by default, which then also solves the test failure. We need to add:

if respond_to?(:action_dispatch)
  action_dispatch.use_authenticated_cookie_encryption = true
end

right after here:

if respond_to?(:active_record)
active_record.cache_versioning = true
end

I don't understand the need for upgrade_hmac_cbc_cookie_encryption — that's what use_authenticated_cookie_encryption governs.

Member

kaspth commented May 21, 2017

However, since this feature is opt-in

It shouldn't be opt-in on a rails new 5.2 app. There it should be enabled by default, which then also solves the test failure. We need to add:

if respond_to?(:action_dispatch)
  action_dispatch.use_authenticated_cookie_encryption = true
end

right after here:

if respond_to?(:active_record)
active_record.cache_versioning = true
end

I don't understand the need for upgrade_hmac_cbc_cookie_encryption — that's what use_authenticated_cookie_encryption governs.

@kaspth

Some last defaults wrangling, but we're getting pretty close!

guides/source/configuring.md
@@ -456,10 +456,14 @@ to `'http authentication'`.
Defaults to `'signed cookie'`.
* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt
-value. Defaults to `'encrypted cookie'`.
+ value. Defaults to `'signed cookie'`.

This comment has been minimized.

@kaspth

kaspth May 21, 2017

Member

This seems off?

@kaspth

kaspth May 21, 2017

Member

This seems off?

This comment has been minimized.

@mikeycgto

mikeycgto May 21, 2017

Contributor

Will double check this.

@mikeycgto

mikeycgto May 21, 2017

Contributor

Will double check this.

guides/source/configuring.md
* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed
-encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+ encrypted cookies salt value. Defaults to `'encrypted cookie'`.

This comment has been minimized.

@kaspth

kaspth May 21, 2017

Member

Are we sure that's the default?

@kaspth

kaspth May 21, 2017

Member

Are we sure that's the default?

This comment has been minimized.

@mikeycgto

mikeycgto May 21, 2017

Contributor

Will double check this.

@mikeycgto

mikeycgto May 21, 2017

Contributor

Will double check this.

+# No longer upgrade CBC HMAC legacy encrypted cookies. This should be disabled
+# when this legacy form of encrption no longer needs to automatically upgraded
+# to GCM encryption.
+# Rails.application.config.action_dispatch.upgrade_legacy_hmac_aes_cbc_cookies = false

This comment has been minimized.

@kaspth

kaspth May 21, 2017

Member

I think this gets solved by Rails removing support for upgrading the cookies from CBC, so I'd rather not make this a config.

@kaspth

kaspth May 21, 2017

Member

I think this gets solved by Rails removing support for upgrading the cookies from CBC, so I'd rather not make this a config.

This comment has been minimized.

@mikeycgto

mikeycgto May 21, 2017

Contributor

See my latest comment for more discussion on this flag. I'm thinking to we just leave the upgrade behavior as is and have future version use "pure" GCM cookies...

@mikeycgto

mikeycgto May 21, 2017

Contributor

See my latest comment for more discussion on this flag. I'm thinking to we just leave the upgrade behavior as is and have future version use "pure" GCM cookies...

+
+# Use AES 256 GCM authenticated encryption for encrypted cookies.
+# Existing cookies will be converted on read then written with the new scheme.
+Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = false

This comment has been minimized.

@kaspth

kaspth May 21, 2017

Member

I was recently schooled on the new_framework_defaults_* file by @matthewd and how I've been doing them wrong.

We should follow the now added example above: use the future default, but start it commented out.

@kaspth

kaspth May 21, 2017

Member

I was recently schooled on the new_framework_defaults_* file by @matthewd and how I've been doing them wrong.

We should follow the now added example above: use the future default, but start it commented out.

This comment has been minimized.

@mikeycgto

mikeycgto May 21, 2017

Contributor

Understood. This file was a bit of mystery to me as well. Did this discussion occur in the PR from dhh for the new fragment caching feature? I'm definitely interested in fully understanding its uses!

@mikeycgto

mikeycgto May 21, 2017

Contributor

Understood. This file was a bit of mystery to me as well. Did this discussion occur in the PR from dhh for the new fragment caching feature? I'm definitely interested in fully understanding its uses!

This comment has been minimized.

@kaspth

kaspth May 21, 2017

Member

The intent is captured here: #28469 😊

@kaspth

kaspth May 21, 2017

Member

The intent is captured here: #28469 😊

+ run_generator
+
+ assert_file "config/initializers/new_framework_defaults_5_2.rb"
+ end

This comment has been minimized.

@kaspth

kaspth May 21, 2017

Member

Newer Rails app load_defaults from the version they were created with:

# Initialize configuration defaults for originally generated Rails version.
config.load_defaults <%= Rails::VERSION::STRING.to_f %>

So a brand new 5.2 app shouldn't have this file, it's only for 5.1 upgrading apps.

@kaspth

kaspth May 21, 2017

Member

Newer Rails app load_defaults from the version they were created with:

# Initialize configuration defaults for originally generated Rails version.
config.load_defaults <%= Rails::VERSION::STRING.to_f %>

So a brand new 5.2 app shouldn't have this file, it's only for 5.1 upgrading apps.

This comment has been minimized.

@mikeycgto

mikeycgto May 21, 2017

Contributor

Gotcha!

@mikeycgto

mikeycgto May 21, 2017

Contributor

Gotcha!

actionpack/CHANGELOG.md
+ Encrypted cookies may now use AES in GCM mode. To have existing cookies be
+ seamlessly upgraded to this new scheme set the
+ `action_dispatch.use_authenticated_cookie_encryption` configuration value to
+ `true`. This value can be set

This comment has been minimized.

@kaspth

kaspth May 21, 2017

Member

Let's mention the AES GCM benefits here (faster, shorter — copy from the security guide if you like) and that new_framework_defaults_5.2.rb shows how to upgrade.

@kaspth

kaspth May 21, 2017

Member

Let's mention the AES GCM benefits here (faster, shorter — copy from the security guide if you like) and that new_framework_defaults_5.2.rb shows how to upgrade.

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 21, 2017

Contributor

It shouldn't be opt-in on a rails new 5.2 app. There it should be enabled by default, which then also solves the test failure. We need to add:

if respond_to?(:action_dispatch)
  action_dispatch.use_authenticated_cookie_encryption = true
end

right after here:

if respond_to?(:active_record)
active_record.cache_versioning = true
end

I can make this change and remove the test I added.

I don't understand the need for upgrade_hmac_cbc_cookie_encryption — that's what use_authenticated_cookie_encryption governs.

Originally I had the upgrade_legacy_hmac_aes_cbc_cookies? method in the cookies middleware check if the salt values for CBC HMAC encryption were defined. If they were, the UpgradeLegacyHmacAesCbcCookieJar middleware is used and cookies are seamlessly upgraded. If they were not defined the EncryptedCookieJar middleware is used and no upgrading is attempted when decryption fails.

Due to these salt values still being defined the UpgradeLegacyHmacAesCbcCookieJar middleware will always be used even if upgrade behavior is not needed or desired. I thought adding an explicit flag would be easier to manage this behavior than clearing the salt values (thus having upgrade_legacy_hmac_aes_cbc_cookies? return false).

Perhaps this can be solved by removing the upgrade middleware altogether in future versions. I know cleaning up the cookies middleware in general is one of the overall goals for the GSoC project so perhaps we should just leave this be for now and have it get released as is in Rails 5.2?

Contributor

mikeycgto commented May 21, 2017

It shouldn't be opt-in on a rails new 5.2 app. There it should be enabled by default, which then also solves the test failure. We need to add:

if respond_to?(:action_dispatch)
  action_dispatch.use_authenticated_cookie_encryption = true
end

right after here:

if respond_to?(:active_record)
active_record.cache_versioning = true
end

I can make this change and remove the test I added.

I don't understand the need for upgrade_hmac_cbc_cookie_encryption — that's what use_authenticated_cookie_encryption governs.

Originally I had the upgrade_legacy_hmac_aes_cbc_cookies? method in the cookies middleware check if the salt values for CBC HMAC encryption were defined. If they were, the UpgradeLegacyHmacAesCbcCookieJar middleware is used and cookies are seamlessly upgraded. If they were not defined the EncryptedCookieJar middleware is used and no upgrading is attempted when decryption fails.

Due to these salt values still being defined the UpgradeLegacyHmacAesCbcCookieJar middleware will always be used even if upgrade behavior is not needed or desired. I thought adding an explicit flag would be easier to manage this behavior than clearing the salt values (thus having upgrade_legacy_hmac_aes_cbc_cookies? return false).

Perhaps this can be solved by removing the upgrade middleware altogether in future versions. I know cleaning up the cookies middleware in general is one of the overall goals for the GSoC project so perhaps we should just leave this be for now and have it get released as is in Rails 5.2?

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 21, 2017

Member

Perhaps this can be solved by removing the upgrade middleware altogether in future versions. I know cleaning up the cookies middleware in general is one of the overall goals for the GSoC project so perhaps we should just leave this be for now and have it get released as is in Rails 5.2?

My thoughts exactly 👍 (though the GSoC project might not necessarily clean up old cookies versions, but we'll see).

I think it's fine to check for the presence of the 3 salt values because use_authenticated_cookie_encryption guards the new salt being assigned the upgrade won't happen.

Member

kaspth commented May 21, 2017

Perhaps this can be solved by removing the upgrade middleware altogether in future versions. I know cleaning up the cookies middleware in general is one of the overall goals for the GSoC project so perhaps we should just leave this be for now and have it get released as is in Rails 5.2?

My thoughts exactly 👍 (though the GSoC project might not necessarily clean up old cookies versions, but we'll see).

I think it's fine to check for the presence of the 3 salt values because use_authenticated_cookie_encryption guards the new salt being assigned the upgrade won't happen.

+ def upgrade_legacy_hmac_aes_cbc_cookies?
+ request.secret_key_base.present? &&
+ request.use_authenticated_cookie_encryption? &&
+ request.use_authenticated_cookie_encryption?

This comment has been minimized.

@kaspth

kaspth May 21, 2017

Member

So this would be:

request.secret_key_base.present? &&
  request.authenticated_encrypted_cookie_salt.present? &&
  request.encrypted_signed_cookie_salt.present? &&
  request.encrypted_cookie_salt.present?

And we can kill the use_authenticated_cookie_encryption? and upgrade_legacy_hmac_aes_cbc_cookies? methods then.

authenticated_encrypted_cookie_salt was placed up further, since it's less likely to be assigned on upgrading apps and we'll avoid the presence check on the present in older releases salt values.

Related: I'm not sure why we check the presence of the secret_key_base here. Rails apps won't boot without it these days.

@kaspth

kaspth May 21, 2017

Member

So this would be:

request.secret_key_base.present? &&
  request.authenticated_encrypted_cookie_salt.present? &&
  request.encrypted_signed_cookie_salt.present? &&
  request.encrypted_cookie_salt.present?

And we can kill the use_authenticated_cookie_encryption? and upgrade_legacy_hmac_aes_cbc_cookies? methods then.

authenticated_encrypted_cookie_salt was placed up further, since it's less likely to be assigned on upgrading apps and we'll avoid the presence check on the present in older releases salt values.

Related: I'm not sure why we check the presence of the secret_key_base here. Rails apps won't boot without it these days.

This comment has been minimized.

@mikeycgto

mikeycgto May 21, 2017

Contributor

Checking secret_key_base seemed a bit unnecessary and I do think it's likely safe to drop at this point.

I'll also rework my commits soon and drop the upgrade_legacy_hmac_aes_cbc_cookies flag and revert the upgrade_legacy_hmac_aes_cbc_cookies? method to just check if the salt values are present?. I'll also update the above mention changes to the various guides. At that point, I think we'll be close to ready for this PR. Thanks again!!

@mikeycgto

mikeycgto May 21, 2017

Contributor

Checking secret_key_base seemed a bit unnecessary and I do think it's likely safe to drop at this point.

I'll also rework my commits soon and drop the upgrade_legacy_hmac_aes_cbc_cookies flag and revert the upgrade_legacy_hmac_aes_cbc_cookies? method to just check if the salt values are present?. I'll also update the above mention changes to the various guides. At that point, I think we'll be close to ready for this PR. Thanks again!!

AEAD encrypted cookies and sessions
This commit changes encrypted cookies from AES in CBC HMAC mode to
Authenticated Encryption using AES-GCM. It also provides a cookie jar
to transparently upgrade encrypted cookies to this new scheme. Some
other notable changes include:

- There is a new application configuration value:
  +use_authenticated_cookie_encryption+. When enabled, AEAD encrypted
  cookies will be used.

- +cookies.signed+ does not raise a +TypeError+ now if the name of an
  encrypted cookie is used. Encrypted cookies using the same key as
  signed cookies would be verified and serialization would then fail
  due the message still be encrypted.

@kaspth kaspth merged commit b88200f into rails:master May 28, 2017

2 checks passed

codeclimate no new or fixed issues
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 28, 2017

Member

@mikeycgto great work! 👏

Member

kaspth commented May 28, 2017

@mikeycgto great work! 👏

@mikeycgto

This comment has been minimized.

Show comment
Hide comment
@mikeycgto

mikeycgto May 28, 2017

Contributor

@kaspth thanks so much for all the help and the feedback! Excited to see this get merged and see Rails get more modern encryption!!

Contributor

mikeycgto commented May 28, 2017

@kaspth thanks so much for all the help and the feedback! Excited to see this get merged and see Rails get more modern encryption!!

@zzak zzak referenced this pull request in sinatra/sinatra May 28, 2017

Open

Encrypt cookies with AES-GCM #1300

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth May 29, 2017

Member

@mikeycgto no problem! Thanks for bringing Rails up to speed 💪

Member

kaspth commented May 29, 2017

@mikeycgto no problem! Thanks for bringing Rails up to speed 💪

mikeycgto referenced this pull request in rack/rack Jun 4, 2017

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