# 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