From bffaa888ac4a1ee60a9f93650b9184a9402eff09 Mon Sep 17 00:00:00 2001 From: Willem van Bergen Date: Thu, 15 Sep 2011 08:28:53 -0400 Subject: Custom serializers and deserializers in MessageVerifier and MessageEncryptor. By default, these classes use Marshal for serializing and deserializing messages. Unfortunately, the Marshal format is closely associated with Ruby internals and even changes between different interpreters. This makes the resulting message very hard to impossible to unserialize messages generated by these classes in other environments like node.js. This patch solves this by allowing you to set your own custom serializer and deserializer lambda functions. By default, it still uses Marshal to be backwards compatible. --- activesupport/lib/active_support/message_encryptor.rb | 8 ++++++-- activesupport/lib/active_support/message_verifier.rb | 8 ++++++-- activesupport/test/message_encryptor_test.rb | 10 +++++++++- activesupport/test/message_verifier_test.rb | 9 +++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 4f7cd12d48..05d6790075 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -13,9 +13,13 @@ module ActiveSupport class InvalidMessage < StandardError; end OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError + attr_accessor :serializer, :deserializer + def initialize(secret, cipher = 'aes-256-cbc') @secret = secret @cipher = cipher + @serializer = lambda { |value| Marshal.dump(value) } + @deserializer = lambda { |value| Marshal.load(value) } end def encrypt(value) @@ -27,7 +31,7 @@ module ActiveSupport cipher.key = @secret cipher.iv = iv - encrypted_data = cipher.update(Marshal.dump(value)) + encrypted_data = cipher.update(serializer.call(value)) encrypted_data << cipher.final [encrypted_data, iv].map {|v| ActiveSupport::Base64.encode64s(v)}.join("--") @@ -44,7 +48,7 @@ module ActiveSupport decrypted_data = cipher.update(encrypted_data) decrypted_data << cipher.final - Marshal.load(decrypted_data) + deserializer.call(decrypted_data) rescue OpenSSLCipherError, TypeError raise InvalidMessage end diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 8f3946325a..e38e242cfe 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -21,9 +21,13 @@ module ActiveSupport class MessageVerifier class InvalidSignature < StandardError; end + attr_accessor :serializer, :deserializer + def initialize(secret, digest = 'SHA1') @secret = secret @digest = digest + @serializer = lambda { |value| Marshal.dump(value) } + @deserializer = lambda { |value| Marshal.load(value) } end def verify(signed_message) @@ -31,14 +35,14 @@ module ActiveSupport data, digest = signed_message.split("--") if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) - Marshal.load(ActiveSupport::Base64.decode64(data)) + deserializer.call(ActiveSupport::Base64.decode64(data)) else raise InvalidSignature end end def generate(value) - data = ActiveSupport::Base64.encode64s(Marshal.dump(value)) + data = ActiveSupport::Base64.encode64s(serializer.call(value)) "#{data}--#{generate_digest(data)}" end diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index e45d5ecd59..bd11b76e60 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -8,6 +8,7 @@ rescue LoadError, NameError else require 'active_support/time' +require 'active_support/json' class MessageEncryptorTest < Test::Unit::TestCase def setup @@ -38,7 +39,14 @@ class MessageEncryptorTest < Test::Unit::TestCase message = @encryptor.encrypt_and_sign(@data) assert_equal @data, @encryptor.decrypt_and_verify(message) end - + + def test_alternative_serialization_method + @encryptor.serializer = lambda { |value| ActiveSupport::JSON.encode(value) } + @encryptor.deserializer = lambda { |value| ActiveSupport::JSON.decode(value) } + + message = @encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.local(2010) }) + assert_equal @encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00-05:00" } + end private def assert_not_decrypted(value) diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index 4821311244..83f34ebc33 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -8,6 +8,7 @@ rescue LoadError, NameError else require 'active_support/time' +require 'active_support/json' class MessageVerifierTest < Test::Unit::TestCase def setup @@ -31,6 +32,14 @@ class MessageVerifierTest < Test::Unit::TestCase assert_not_verified("#{data}--#{hash.reverse}") assert_not_verified("purejunk") end + + def test_alternative_serialization_method + @verifier.serializer = lambda { |value| ActiveSupport::JSON.encode(value) } + @verifier.deserializer = lambda { |value| ActiveSupport::JSON.decode(value) } + + message = @verifier.generate({ :foo => 123, 'bar' => Time.local(2010) }) + assert_equal @verifier.verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00-05:00" } + end def assert_not_verified(message) assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do -- cgit v1.2.3 From a8aaef676217f53f2812cd56f71a6b00c5d22162 Mon Sep 17 00:00:00 2001 From: Willem van Bergen Date: Thu, 15 Sep 2011 09:50:39 -0400 Subject: Fixed tests so that they will also run properly in other timezones. --- activesupport/test/message_encryptor_test.rb | 4 ++-- activesupport/test/message_verifier_test.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index bd11b76e60..95e64fe6d1 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -44,8 +44,8 @@ class MessageEncryptorTest < Test::Unit::TestCase @encryptor.serializer = lambda { |value| ActiveSupport::JSON.encode(value) } @encryptor.deserializer = lambda { |value| ActiveSupport::JSON.decode(value) } - message = @encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.local(2010) }) - assert_equal @encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00-05:00" } + message = @encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) }) + assert_equal @encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } end private diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index 83f34ebc33..224e38a52a 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -37,8 +37,8 @@ class MessageVerifierTest < Test::Unit::TestCase @verifier.serializer = lambda { |value| ActiveSupport::JSON.encode(value) } @verifier.deserializer = lambda { |value| ActiveSupport::JSON.decode(value) } - message = @verifier.generate({ :foo => 123, 'bar' => Time.local(2010) }) - assert_equal @verifier.verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00-05:00" } + message = @verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) }) + assert_equal @verifier.verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } end def assert_not_verified(message) -- cgit v1.2.3