# encoding: utf-8 module PagesCore # = Digest Verifier # # ==== Usage # # verifier = PagesCore::DigestVerifier.new("super secret!") # # digest = verifier.generate("foo") # # digest.verify("foo", digest) # # => true # digest.verify("bar", digest) # # => raises PagesCore::Errors::InvalidSignature # # Credit where credit is due: adapted and simplified from # +ActiveSupport::MessageVerifier+, since we don't need to handle # arbitrary data structures and ship the serialized data to the client. class DigestVerifier class InvalidSignatureError < StandardError; end def initialize(secret, options = {}) @secret = secret @digest = options[:digest] || "SHA1" end # Generates a digest for a string. def generate(data) generate_digest(data) end # Verifies that digest is valid for data. # Raises a +PagesCore::DigestVerifier::InvalidSignatureError+ error if not. def verify(data, digest) unless valid_digest?(data, digest) raise PagesCore::DigestVerifier::InvalidSignatureError end true end private def secure_compare(a, b) return false unless a.bytesize == b.bytesize l = a.unpack "C#{a.bytesize}" res = 0 b.each_byte { |byte| res |= byte ^ l.shift } res.zero? end def generate_digest(data) require "openssl" unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest( OpenSSL::Digest.const_get(@digest).new, @secret, data ) end def valid_digest?(data, digest) data.present? && digest.present? && secure_compare(digest, generate_digest(data)) end end end