lib/fernet/token.rb in fernet-2.0.rc1 vs lib/fernet/token.rb in fernet-2.0.rc2

- old
+ new

@@ -1,38 +1,55 @@ # encoding UTF-8 require 'base64' require 'valcro' module Fernet + # Internal: encapsulates a fernet token structure and validation class Token include Valcro class InvalidToken < StandardError; end + # Internal: the default token version DEFAULT_VERSION = 0x80.freeze + # Internal: max allowed clock skew for calculating TTL MAX_CLOCK_SKEW = 60.freeze + # Internal: initializes a Token object + # + # token - the string representation of this token + # opts - a has containing + # secret: the secret, optionally base 64 encoded (required) + # enforce_ttl: whether to enforce TTL upon validation. Defaults to value + # set in Configuration.enforce_ttl + # ttl: number of seconds token is valid, defaults to Configuration.ttl def initialize(token, opts = {}) @token = token + @secret = Secret.new(opts.fetch(:secret)) @enforce_ttl = opts.fetch(:enforce_ttl) { Configuration.enforce_ttl } @ttl = opts[:ttl] || Configuration.ttl @now = opts[:now] end + # Internal: returns the token as a string def to_s @token end - def secret=(secret) - @secret = Secret.new(secret) - end - + # Internal: Validates this token and returns true if it's valid + # + # Returns a boolean set to true if it's valid, false otherwise def valid? validate super end + # Internal: returns the decrypted message in this token + # + # Raises InvalidToken if it cannot be decrypted or is invalid + # + # Returns a string containing the original message in plain text def message if valid? begin Encryption.decrypt(key: @secret.encryption_key, ciphertext: encrypted_message, @@ -43,26 +60,31 @@ else raise InvalidToken, error_messages end end - def self.generate(params) - unless params[:secret] + # Internal: generates a Fernet Token + # + # opts - a hash containing + # secret: a string containing the secret, optionally base64 encoded + # message: the message in plain text + def self.generate(opts) + unless opts[:secret] raise ArgumentError, 'Secret not provided' end - secret = Secret.new(params[:secret]) + secret = Secret.new(opts.fetch(:secret)) encrypted_message, iv = Encryption.encrypt(key: secret.encryption_key, - message: params[:message], - iv: params[:iv]) - issued_timestamp = (params[:now] || Time.now).to_i + message: opts[:message], + iv: opts[:iv]) + issued_timestamp = (opts[:now] || Time.now).to_i payload = [DEFAULT_VERSION].pack("C") + BitPacking.pack_int64_bigendian(issued_timestamp) + iv + encrypted_message mac = OpenSSL::HMAC.digest('sha256', secret.signing_key, payload) - new(Base64.urlsafe_encode64(payload + mac)) + new(Base64.urlsafe_encode64(payload + mac), secret: opts.fetch(:secret)) end private def decoded_token @decoded_token ||= Base64.urlsafe_decode64(@token) @@ -90,15 +112,14 @@ validate do if valid_base64? if unknown_token_version? errors.add :version, "is unknown" + elsif enforce_ttl? && !issued_recent_enough? + errors.add :issued_timestamp, "is too far in the past: token expired" else unless signatures_match? errors.add :signature, "does not match" - end - if enforce_ttl? && !issued_recent_enough? - errors.add :issued_timestamp, "is too far in the past: token expired" end if unacceptable_clock_slew? errors.add :issued_timestamp, "is too far in the future" end unless ciphertext_multiple_of_block_size?