Skip to content

Commit

Permalink
Custom serializers and deserializers in MessageVerifier and MessageEn…
Browse files Browse the repository at this point in the history
…cryptor.

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.
  • Loading branch information
wvanbergen committed Sep 15, 2011
1 parent da7f042 commit bffaa88
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 5 deletions.
8 changes: 6 additions & 2 deletions activesupport/lib/active_support/message_encryptor.rb
Expand Up @@ -13,9 +13,13 @@ class MessageEncryptor
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)
Expand All @@ -27,7 +31,7 @@ def encrypt(value)
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("--")
Expand All @@ -44,7 +48,7 @@ def decrypt(encrypted_message)
decrypted_data = cipher.update(encrypted_data)
decrypted_data << cipher.final

Marshal.load(decrypted_data)
deserializer.call(decrypted_data)
rescue OpenSSLCipherError, TypeError
raise InvalidMessage
end
Expand Down
8 changes: 6 additions & 2 deletions activesupport/lib/active_support/message_verifier.rb
Expand Up @@ -21,24 +21,28 @@ 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)
raise InvalidSignature if signed_message.blank?

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

Expand Down
10 changes: 9 additions & 1 deletion activesupport/test/message_encryptor_test.rb
Expand Up @@ -8,6 +8,7 @@
else

require 'active_support/time'
require 'active_support/json'

class MessageEncryptorTest < Test::Unit::TestCase
def setup
Expand Down Expand Up @@ -38,7 +39,14 @@ def test_signed_round_tripping
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)
Expand Down
9 changes: 9 additions & 0 deletions activesupport/test/message_verifier_test.rb
Expand Up @@ -8,6 +8,7 @@
else

require 'active_support/time'
require 'active_support/json'

class MessageVerifierTest < Test::Unit::TestCase
def setup
Expand All @@ -31,6 +32,14 @@ def test_tampered_data_raises
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
Expand Down

0 comments on commit bffaa88

Please sign in to comment.