File: active_support/message_verifier.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: ActiveSupport#1
  class: MessageVerifier#18
inherits from
  Object ( Builtin-Module )
has properties
method: initialize / 2 #21
method: verify / 1 #26
method: generate / 1 #37
method: secure_compare (1/2) / 2 #45
method: secure_compare (2/E) / 2 #61
method: generate_digest / 1 #74
  class: InvalidSignature#19
inherits from
  StandardError ( Builtin-Module )

Code

   1  module ActiveSupport
   2    # MessageVerifier makes it easy to generate and verify messages which are signed
   3    # to prevent tampering.
   4    # 
   5    # This is useful for cases like remember-me tokens and auto-unsubscribe links where the
   6    # session store isn't suitable or available.
   7    #
   8    # Remember Me:
   9    #   cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
  10    # 
  11    # In the authentication filter:
  12    #
  13    #   id, time = @verifier.verify(cookies[:remember_me])
  14    #   if time < Time.now
  15    #     self.current_user = User.find(id)
  16    #   end
  17    # 
  18    class MessageVerifier
  19      class InvalidSignature < StandardError; end
  20      
  21      def initialize(secret, digest = 'SHA1')
  22        @secret = secret
  23        @digest = digest
  24      end
  25      
  26      def verify(signed_message)
  27        raise InvalidSignature if signed_message.blank?
  28 
  29        data, digest = signed_message.split("--")
  30        if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
  31          Marshal.load(ActiveSupport::Base64.decode64(data))
  32        else
  33          raise InvalidSignature
  34        end
  35      end
  36      
  37      def generate(value)
  38        data = ActiveSupport::Base64.encode64s(Marshal.dump(value))
  39        "#{data}--#{generate_digest(data)}"
  40      end
  41      
  42      private
  43        if "foo".respond_to?(:force_encoding)
  44          # constant-time comparison algorithm to prevent timing attacks
  45          def secure_compare(a, b)
  46            a = a.dup.force_encoding(Encoding::BINARY)
  47            b = b.dup.force_encoding(Encoding::BINARY)
  48 
  49            if a.length == b.length
  50              result = 0
  51              for i in 0..(a.length - 1)
  52                result |= a[i].ord ^ b[i].ord
  53              end
  54              result == 0
  55            else
  56              false
  57            end
  58          end
  59        else
  60          # For <= 1.8.6
  61          def secure_compare(a, b)
  62            if a.length == b.length
  63              result = 0
  64              for i in 0..(a.length - 1)
  65                result |= a[i] ^ b[i]
  66              end
  67              result == 0
  68            else
  69              false
  70            end
  71          end
  72        end
  73 
  74        def generate_digest(data)
  75          require 'openssl' unless defined?(OpenSSL)
  76          OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), @secret, data)
  77        end
  78    end
  79  end