1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

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.
This commit is contained in:
Willem van Bergen 2011-09-15 08:28:53 -04:00
parent da7f0426ec
commit bffaa888ac
4 changed files with 30 additions and 5 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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