aboutsummaryrefslogtreecommitdiffstats
path: root/railties
diff options
context:
space:
mode:
authorMichael Coyne <mikeycgto@gmail.com>2017-02-23 13:54:17 -0500
committerMichael Coyne <mikeycgto@gmail.com>2017-05-22 08:50:36 +0000
commit5a3ba63d9abad86b7f6dd36a92cfaf722e52760b (patch)
tree523981cf2bfddf5941218a463a8b19544c28db65 /railties
parent7a2041335f2a5f86179e303fa84a4653f58e1620 (diff)
downloadrails-5a3ba63d9abad86b7f6dd36a92cfaf722e52760b.tar.gz
rails-5a3ba63d9abad86b7f6dd36a92cfaf722e52760b.tar.bz2
rails-5a3ba63d9abad86b7f6dd36a92cfaf722e52760b.zip
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.
Diffstat (limited to 'railties')
-rw-r--r--railties/lib/rails/application.rb1
-rw-r--r--railties/lib/rails/application/configuration.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt4
-rw-r--r--railties/test/application/middleware/session_test.rb93
4 files changed, 93 insertions, 9 deletions
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index f8a923141d..39ca2db8e1 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -260,6 +260,7 @@ module Rails
"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,
+ "action_dispatch.authenticated_encrypted_cookie_salt" => config.action_dispatch.authenticated_encrypted_cookie_salt,
"action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer,
"action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest
)
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 4dc9a431f6..4ffde6198a 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -88,6 +88,10 @@ module Rails
active_record.cache_versioning = true
end
+ if respond_to?(:action_dispatch)
+ action_dispatch.use_authenticated_cookie_encryption = true
+ end
+
else
raise "Unknown version #{target_version.to_s.inspect}"
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
index 52c08500d8..900baa607a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
@@ -9,3 +9,7 @@
# Make Active Record use stable #cache_key alongside new #cache_version method.
# This is needed for recyclable cache keys.
# Rails.application.config.active_record.cache_versioning = true
+
+# 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 = true
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 959a629ede..a14ea589ed 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -162,6 +162,11 @@ module ApplicationTests
end
RUBY
+ add_to_config <<-RUBY
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
+ RUBY
+
require "#{app_path}/config/environment"
get "/foo/write_session"
@@ -171,9 +176,9 @@ module ApplicationTests
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[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -209,6 +214,9 @@ module ApplicationTests
add_to_config <<-RUBY
secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
RUBY
require "#{app_path}/config/environment"
@@ -220,9 +228,9 @@ module ApplicationTests
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[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -264,6 +272,73 @@ module ApplicationTests
add_to_config <<-RUBY
secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get "/foo/write_raw_session"
+ get "/foo/read_session"
+ assert_equal "1", last_response.body
+
+ get "/foo/write_session"
+ get "/foo/read_session"
+ assert_equal "2", last_response.body
+
+ get "/foo/read_encrypted_cookie"
+ assert_equal "2", last_response.body
+
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
+
+ get "/foo/read_raw_cookie"
+ assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]
+ end
+
+ test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_raw_session
+ # AES-256-CBC with SHA1 HMAC
+ # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
+ cookies[:_myapp_session] = "TlgrdS85aUpDd1R2cDlPWlR6K0FJeGExckwySjZ2Z0pkR3d2QnRObGxZT25aalJWYWVvbFVLcHF4d0VQVDdSaFF2QjFPbG9MVjJzeWp3YjcyRUlKUUU2ZlR4bXlSNG9ZUkJPRUtld0E3dVU9LS0xNDZXbGpRZ3NjdW43N2haUEZJSUNRPT0=--3639b5ce54c09495cfeaae928cd5634e0c4b2e96"
+ head :ok
+ end
+
+ def write_session
+ session[:foo] = session[:foo] + 1
+ head :ok
+ end
+
+ def read_session
+ render plain: session[:foo]
+ end
+
+ def read_encrypted_cookie
+ render plain: cookies.encrypted[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render plain: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ # Use a static key
+ secrets.secret_key_base = "known key base"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
RUBY
require "#{app_path}/config/environment"
@@ -279,9 +354,9 @@ module ApplicationTests
get "/foo/read_encrypted_cookie"
assert_equal "2", 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[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]