-
Notifications
You must be signed in to change notification settings - Fork 21.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AEAD encrypted cookies and sessions #28132
Conversation
Could you explain briefly too why migrating to AEAD is good? |
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 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 |
Thanks for the explanation! |
@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. |
Oh, nice! You even mentioned it in the description 🤦♂️ |
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 |
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. |
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! |
Notes on Legacy SupportThought more about this and want to clarify somethings as far as legacy support goes. Currently there are two legacy cookie jar classes This PR adds a I think it's worth considering at this time if 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 Thus, my main question is, should Possible Major API ChangesI'd also go as far as to suggest some major API changes to the cookie middleware. Specifically, we could deprecate the
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! |
54c40b5
to
3b1e5f4
Compare
3b1e5f4
to
be8b9e8
Compare
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. |
c5eaeaf
to
8170de3
Compare
guides/source/security.md
Outdated
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
Outdated
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember to rewrite this too 😉
actionpack/CHANGELOG.md
Outdated
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. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to add your name too 😄
*Michael Coyne*
@kaspth these changes sound good. Will get to them either later tonight or tomorrow morning sometime. Thanks for reviewing it again! |
f5fc1ba
to
362d263
Compare
Made some more changes and moved the One small question I have is if this config value is set to Thanks again for all the reviews! Can't wait to see this feature merged in -- smaller 🍪 for all!! |
362d263
to
d6bfe8d
Compare
Thought a bit more about this and I realized that because the two old salt values are still set by default, Perhaps we should add a second configuration flag, such as 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? |
d6bfe8d
to
3faa392
Compare
Just rebased and my |
0c37842
to
834db45
Compare
I decided to add the Not sure how to handle the failing test with regards to |
It shouldn't be opt-in on a if respond_to?(:action_dispatch)
action_dispatch.use_authenticated_cookie_encryption = true
end right after here: rails/railties/lib/rails/application/configuration.rb Lines 87 to 89 in 75fa8dd
I don't understand the need for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some last defaults wrangling, but we're getting pretty close!
guides/source/configuring.md
Outdated
@@ -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'`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems off?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will double check this.
guides/source/configuring.md
Outdated
|
||
* `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'`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure that's the default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this gets solved by Rails removing support for upgrading the cookies from CBC, so I'd rather not make this a config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intent is captured here: #28469 😊
run_generator | ||
|
||
assert_file "config/initializers/new_framework_defaults_5_2.rb" | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newer Rails app load_defaults
from the version they were created with:
rails/railties/lib/rails/generators/rails/app/templates/config/application.rb
Lines 25 to 26 in e8b4a92
# 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha!
actionpack/CHANGELOG.md
Outdated
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
I can make this change and remove the test I added.
Originally I had the Due to these salt values still being defined the 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 |
def upgrade_legacy_hmac_aes_cbc_cookies? | ||
request.secret_key_base.present? && | ||
request.use_authenticated_cookie_encryption? && | ||
request.use_authenticated_cookie_encryption? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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!!
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.
834db45
to
5a3ba63
Compare
@mikeycgto great work! 👏 |
@kaspth thanks so much for all the help and the feedback! Excited to see this get merged and see Rails get more modern encryption!! |
@mikeycgto no problem! Thanks for bringing Rails up to speed 💪 |
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.