class Darrrr::AccountProvider

Constants

FIELDS
PRIVATE_FIELDS

Only applicable when acting as a recovery provider

REQUIRED_FIELDS

These are the fields required by the spec

URL_FIELDS

Public Instance Methods

dangerous_unverified_recovery_token(countersigned_token) click to toggle source

Parses a countersigned_token and returns the nested recovery token WITHOUT verifying any signatures. This should only be used if no user context can be identified or if we're extracting issuer information.

# File lib/darrrr/account_provider.rb, line 69
def dangerous_unverified_recovery_token(countersigned_token)
  parsed_countersigned_token = RecoveryToken.parse(Base64.strict_decode64(countersigned_token))
  RecoveryToken.parse(parsed_countersigned_token.data)
end
encryptor_key() click to toggle source
# File lib/darrrr/account_provider.rb, line 74
def encryptor_key
  :darrrr_account_provider_encryptor
end
generate_recovery_token(data:, audience:, context: nil, options: 0x00) click to toggle source

Generates a binary token with an encrypted arbitrary data payload.

data: value to encrypt in the token provider: the recovery provider/audience of the token context: arbitrary data passed on to underlying crypto operations options: the value to set for the options byte

returns a [RecoveryToken, b64 encoded sealed_token] tuple

# File lib/darrrr/account_provider.rb, line 59
def generate_recovery_token(data:, audience:, context: nil, options: 0x00)
  token = RecoveryToken.build(issuer: self, audience: audience, type: RECOVERY_TOKEN_TYPE, options: options)
  token.data = self.encryptor.encrypt(data, self, context)

  [token, seal(token, context)]
end
to_h() click to toggle source

Used to serve content at /.well-known/delegated-account-recovery/configuration

# File lib/darrrr/account_provider.rb, line 40
def to_h
  {
    "issuer" => self.issuer,
    "tokensign-pubkeys-secp256r1" => self.unseal_keys.dup,
    "save-token-return" => self.save_token_return,
    "recover-account-return" => self.recover_account_return,
    "privacy-policy" => self.privacy_policy,
    "icon-152px" => self.icon_152px
  }
end
unseal_keys(context = nil) click to toggle source

The CryptoHelper defines an `unseal` method that requires us to define a `unseal_keys` method that will return the set of keys that are valid when verifying the signature on a sealed key.

returns the value of `tokensign_pubkeys_secp256r1` or executes a proc passing `self` as the first argument.

# File lib/darrrr/account_provider.rb, line 31
def unseal_keys(context = nil)
  if @tokensign_pubkeys_secp256r1.respond_to?(:call)
    @tokensign_pubkeys_secp256r1.call(context)
  else
    @tokensign_pubkeys_secp256r1
  end
end
validate_countersigned_recovery_token!(countersigned_token, context = {}) click to toggle source

Validates the countersigned recovery token by verifying the signature of the countersigned token, parsing out the origin recovery token, verifying the signature on the recovery token, and finally decrypting the data in the origin recovery token.

countersigned_token: our original recovery token wrapped in recovery token instance that is signed by the recovery provider. context: arbitrary data to be passed to Provider#unseal.

returns a verified recovery token or raises an error if the token fails validation.

# File lib/darrrr/account_provider.rb, line 89
def validate_countersigned_recovery_token!(countersigned_token, context = {})
  # 5. Validate the the issuer field is present in the token,
  # and that it matches the audience field in the original countersigned token.
  begin
    recovery_provider = RecoveryToken.recovery_provider_issuer(Base64.strict_decode64(countersigned_token))
  rescue RecoveryTokenSerializationError => e
    raise CountersignedTokenError.new("Countersigned token is invalid: " + e.message, :countersigned_token_parse_error)
  rescue UnknownProviderError => e
    raise CountersignedTokenError.new(e.message, :recovery_token_invalid_issuer)
  end

  # 1. Parse the countersigned-token.
  # 2. Validate that the version field is 0.
  # 7. Retrieve the current Recovery Provider configuration as described in Section 2.
  # 8. Validate that the counter-signed token signature validates with a current element of the countersign-pubkeys-secp256r1 array.
  begin
    parsed_countersigned_token = recovery_provider.unseal(Base64.strict_decode64(countersigned_token), context)
  rescue TokenFormatError => e
    raise CountersignedTokenError.new(e.message, :countersigned_invalid_token_version)
  rescue CryptoError
    raise CountersignedTokenError.new("Countersigned token has an invalid signature", :countersigned_invalid_signature)
  end

  # 3. De-serialize the original recovery token from the data field.
  # 4. Validate the signature on the original recovery token.
  begin
    recovery_token = self.unseal(parsed_countersigned_token.data, context)
  rescue RecoveryTokenSerializationError => e
    raise CountersignedTokenError.new("Nested recovery token is invalid: " + e.message, :recovery_token_token_parse_error)
  rescue TokenFormatError => e
    raise CountersignedTokenError.new("Nested recovery token format error: #{e.message}", :recovery_token_invalid_token_type)
  rescue CryptoError
    raise CountersignedTokenError.new("Nested recovery token has an invalid signature", :recovery_token_invalid_signature)
  end

  # 5. Validate the the issuer field is present in the countersigned-token,
  # and that it matches the audience field in the original token.

  countersigned_token_issuer = parsed_countersigned_token.issuer
  if countersigned_token_issuer.blank? || countersigned_token_issuer != recovery_token.audience || recovery_provider.origin != countersigned_token_issuer
    raise CountersignedTokenError.new("Validate the the issuer field is present in the countersigned-token, and that it matches the audience field in the original token", :recovery_token_invalid_issuer)
  end

  # 6. Validate the token binding for the countersigned token, if present.
  # (the token binding for the inner token is not relevant)
  # TODO not required, to be implemented later

  # 9. Decrypt the data field from the original recovery token and parse its information, if present.
  # no decryption here is attempted. Attempts to call `decode` will just fail.

  # 10. Apply any additional processing which provider-specific data in the opaque data portion may indicate is necessary.
  begin
    if DateTime.parse(parsed_countersigned_token.issued_time).utc < (Time.now - CLOCK_SKEW).utc
      raise CountersignedTokenError.new("Countersigned recovery token issued at time is too far in the past", :stale_token)
    end
  rescue ArgumentError
    raise CountersignedTokenError.new("Invalid countersigned token issued time", :invalid_issued_time)
  end

  recovery_token
end