From 29be3f5d8386fc9a8a67844fa9b7d6860574e715 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?=
 <lukasz.strzalkowski@gmail.com>
Date: Tue, 12 Aug 2014 21:57:51 +0200
Subject: Add config option for cookies digest

You can now configure custom digest for cookies in the same way as `serializer`:

  config.action_dispatch.cookies_digest = 'SHA256'
---
 actionpack/CHANGELOG.md                            |  5 +++
 .../lib/action_dispatch/middleware/cookies.rb      | 12 ++++--
 actionpack/test/dispatch/cookies_test.rb           | 50 ++++++++++++++++++++++
 3 files changed, 64 insertions(+), 3 deletions(-)

(limited to 'actionpack')

diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 44b8fa843d..253b647aca 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,8 @@
+*   Add `config.action_dispatch.cookies_digest` option for setting custom
+    digest. The default remains the same - 'SHA1'.
+
+    *Łukasz Strzałkowski*
+
 *   Extract source code for the entire exception stack trace for
     better debugging and diagnosis.
 
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index ac9e5effe2..0f088c5539 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -90,6 +90,7 @@ module ActionDispatch
     SECRET_TOKEN = "action_dispatch.secret_token".freeze
     SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
     COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
+    COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
 
     # Cookies can typically store 4096 bytes.
     MAX_COOKIE_SIZE = 4096
@@ -212,7 +213,8 @@ module ActionDispatch
           secret_token: env[SECRET_TOKEN],
           secret_key_base: env[SECRET_KEY_BASE],
           upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
-          serializer: env[COOKIES_SERIALIZER]
+          serializer: env[COOKIES_SERIALIZER],
+          digest: env[COOKIES_DIGEST]
         }
       end
 
@@ -441,6 +443,10 @@ module ActionDispatch
             serializer
           end
         end
+
+        def digest
+          @options[:digest] || 'SHA1'
+        end
     end
 
     class SignedCookieJar #:nodoc:
@@ -451,7 +457,7 @@ module ActionDispatch
         @parent_jar = parent_jar
         @options = options
         secret = key_generator.generate_key(@options[:signed_cookie_salt])
-        @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
+        @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: NullSerializer)
       end
 
       def [](name)
@@ -508,7 +514,7 @@ module ActionDispatch
         @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, serializer: NullSerializer)
+        @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: NullSerializer)
       end
 
       def [](name)
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 0f145666d1..b40c21c067 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -369,6 +369,35 @@ class CookiesTest < ActionController::TestCase
     assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name]
   end
 
+  def test_signed_cookie_using_default_digest
+    get :set_signed_cookie
+    cookies = @controller.send :cookies
+    assert_not_equal 45, cookies[:user_id]
+    assert_equal 45, cookies.signed[:user_id]
+
+    key_generator = @request.env["action_dispatch.key_generator"]
+    signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+    secret = key_generator.generate_key(signed_cookie_salt)
+
+    verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: 'SHA1')
+    assert_equal verifier.generate(45), cookies[:user_id]
+  end
+
+  def test_signed_cookie_using_custom_digest
+    @request.env["action_dispatch.cookies_digest"] = 'SHA256'
+    get :set_signed_cookie
+    cookies = @controller.send :cookies
+    assert_not_equal 45, cookies[:user_id]
+    assert_equal 45, cookies.signed[:user_id]
+
+    key_generator = @request.env["action_dispatch.key_generator"]
+    signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+    secret = key_generator.generate_key(signed_cookie_salt)
+
+    verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: 'SHA256')
+    assert_equal verifier.generate(45), cookies[:user_id]
+  end
+
   def test_signed_cookie_using_default_serializer
     get :set_signed_cookie
     cookies = @controller.send :cookies
@@ -481,6 +510,27 @@ class CookiesTest < ActionController::TestCase
     assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
   end
 
+  def test_encrypted_cookie_using_custom_digest
+    @request.env["action_dispatch.cookies_digest"] = 'SHA256'
+    get :set_encrypted_cookie
+    cookies = @controller.send :cookies
+    assert_not_equal 'bar', cookies[:foo]
+    assert_equal 'bar', cookies.encrypted[:foo]
+
+    sign_secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+
+    sha1_verifier   = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActionDispatch::Cookies::NullSerializer, digest: 'SHA1')
+    sha256_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActionDispatch::Cookies::NullSerializer, digest: 'SHA256')
+
+    assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do
+      sha1_verifier.verify(cookies[:foo])
+    end
+
+    assert_nothing_raised do
+      sha256_verifier.verify(cookies[:foo])
+    end
+  end
+
   def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
     @request.env["action_dispatch.cookies_serializer"] = :hybrid
 
-- 
cgit v1.2.3