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:
parent
da7f0426ec
commit
bffaa888ac
4 changed files with 30 additions and 5 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue