UNPKG

@planq-network/encrypted-backup

Version:

Libraries for implemented password encrypted account backups

199 lines (198 loc) 10.7 kB
/// <reference types="node" /> import { Result } from '@planq-network/base/lib/result'; import { CircuitBreakerServiceContext } from '@planq-network/identity/lib/odis/circuit-breaker'; import { ServiceContext as OdisServiceContext } from '@planq-network/identity/lib/odis/query'; import { SequentialDelayDomain } from '@planq-network/phone-number-privacy-common/lib/domains'; import { ComputationalHardeningConfig, EnvironmentIdentifier, HardeningConfig } from './config'; import { BackupError } from './errors'; /** * Backup structure encoding the information needed to implement the encrypted backup protocol. * * @remarks The structure below and its related functions implement the encrypted backup protocol * designed for wallet account backups. More information about the protocol can be found in the * official {@link https://docs.planq.network/celo-codebase/protocol/identity/encrypted-cloud-backup | * Planq documentation} */ export interface Backup { /** * AES-128-GCM encryption of the user's secret backup data. * * @remarks The backup key is derived from the user's password or PIN hardened with input from the * ODIS rate-limited hashing service and optionally a circuit breaker service. */ encryptedData: Buffer; /** * A randomly chosen 256-bit value. Ensures uniqueness of the password derived encryption key. * * @remarks The nonce value is appended to the password for local key derivation. It is also used * to derive an authentication key to include in the ODIS Domain for domain separation and to * ensure quota cannot be consumed by parties without access to the backup. */ nonce: Buffer; /** * ODIS Domain instance to be included in the query to ODIS for password hardening, * * @remarks Currently only SequentialDelayDomain is supported. Other ODIS domains intended for key * hardening may be supported in the future. */ odisDomain?: SequentialDelayDomain; /** * RSA-OAEP-256 encryption of a randomly chosen 128-bit value, the fuse key. * * @remarks The fuse key, if provided, is combined with the password in local key derivation. * Encryption is under the public key of the circuit breaker service. In order to get the fuseKey * the client will send this ciphertext to the circuit breaker service for decryption. */ encryptedFuseKey?: Buffer; /** * Options for local computational hardening of the encryption key through PBKDF or scrypt. * * @remarks Adding computational hardening provides a measure of security from password guessing * when the password has a moderate amount of entropy (e.g. a password generated under good * guidelines). If the user secret has very low entropy, such as with a 6-digit PIN, * computational hardening does not add significant security. */ computationalHardening?: ComputationalHardeningConfig; /** Version number for the backup feature. Used to facilitate backwards compatibility. */ version: string; /** * Data provided by the backup creator to identify the backup and its context * * @remarks Metadata is provided by, and only meaningful to, the SDK user. The intention is for * this metadata to be used for identifying the backup and providing any context needed in the * application * * @example * ```typescript * { * // Address of the primary account stored a backup of an account key. Used to display the * // balance and latest transaction information for a given backup. * accountAddress: string * // Unix timestamp used to indicate when the backup was created. * timestamp: number * } * ``` */ metadata?: { [key: string]: unknown; }; /** Information including the URL and public keys of the ODIS and circuit breaker services. */ environment?: { odis?: OdisServiceContext; circuitBreaker?: CircuitBreakerServiceContext; }; } export interface CreatePinEncryptedBackupArgs { data: Buffer; pin: string; environment?: EnvironmentIdentifier; metadata?: { [key: string]: unknown; }; } /** * Create a data backup, encrypting it with a hardened key derived from the given PIN. * * @remarks Using a 4 or 6 digit PIN for encryption requires an extremely restrictive rate limit for * attempts to guess the PIN. This is enforced by ODIS through the SequentialDelayDomain with * settings to allow the user (or an attacker) only a fixed number of attempts to guess their PIN. * * Because PINs have very little entropy, the total number of guesses is very restricted. * * On the first day, the client has 10 attempts. 5 within 10s. 5 more over roughly 45 minutes. * * On the second day, the client has 5 attempts over roughly 2 minutes. * * On the third day, the client has 3 attempts over roughly 40 seconds. * * On the fourth day, the client has 2 attempts over roughly 10 seconds. * * Overall, the client has 25 attempts over 4 days. All further attempts will be denied. * * It is strongly recommended that the calling application implement a PIN blocklist to prevent the * user from selecting a number of the most common PIN codes (e.g. blocking the top 25k PINs by * frequency of appearance in the HIBP Passwords dataset). An example implementation can be seen in * the Valora wallet. {@link * https://github.com/valora-inc/wallet/blob/3940661c40d08e4c5db952bd0abeaabb0030fc7a/packages/mobile/src/pincode/authentication.ts#L56-L108 * | PIN blocklist implementation} * * In order to handle the event of an ODIS service compromise, this configuration additionally * includes a circuit breaker service run by Valora. In the event of an ODIS compromise, the Valora * team will take their service offline, preventing backups using the circuit breaker from being * opened. This ensures that an attacker who has compromised ODIS cannot leverage their attack to * forcibly open backups created with this function. * * @param data The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. * @param pin PIN to use in deriving the encryption key. * @param hardening Configuration for how the password should be hardened in deriving the key. * @param metadata Arbitrary key-value data to include in the backup to identify it. */ export declare function createPinEncryptedBackup({ data, pin, environment, metadata, }: CreatePinEncryptedBackupArgs): Promise<Result<Backup, BackupError>>; export interface CreatePasswordEncryptedBackupArgs { data: Buffer; password: string; environment?: EnvironmentIdentifier; metadata?: { [key: string]: unknown; }; } /** * Create a data backup, encrypting it with a hardened key derived from the given password. * * @remarks Because passwords have moderate entropy, the total number of guesses is restricted. * * The user initially gets 5 attempts without delay. * * Then the user gets two attempts every 5 seconds for up to 20 attempts. * * Then the user gets two attempts every 30 seconds for up to 20 attempts. * * Then the user gets two attempts every 5 minutes for up to 20 attempts. * * Then the user gets two attempts every hour for up to 20 attempts. * * Then the user gets two attempts every day for up to 20 attempts. * * Following guidelines in NIST-800-63-3 it is strongly recommended that the caller apply a password * blocklist to the users choice of password. * * In order to handle the event of an ODIS service compromise, this configuration additionally * hardens the password input with a computational hardening function. In particular, scrypt is used * with IETF recommended parameters {@link * https://tools.ietf.org/id/draft-whited-kitten-password-storage-00.html#name-scrypt | IETF * recommended scrypt parameters } * * @param data The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. * @param password Password to use in deriving the encryption key. * @param hardening Configuration for how the password should be hardened in deriving the key. * @param metadata Arbitrary key-value data to include in the backup to identify it. */ export declare function createPasswordEncryptedBackup({ data, password, environment, metadata, }: CreatePasswordEncryptedBackupArgs): Promise<Result<Backup, BackupError>>; export interface CreateBackupArgs { data: Buffer; userSecret: Buffer | string; hardening: HardeningConfig; metadata?: { [key: string]: unknown; }; } /** * Create a data backup, encrypting it with a hardened key derived from the given password or PIN. * * @param data The secret data (e.g. BIP-39 mnemonic phrase) to be included in the encrypted backup. * @param userSecret Password, PIN, or other user secret to use in deriving the encryption key. * If a string is provided, it will be UTF-8 encoded into a Buffer before use. * @param hardening Configuration for how the password should be hardened in deriving the key. * @param metadata Arbitrary key-value data to include in the backup to identify it. * * @privateRemarks Most of this functions code is devoted to key generation starting with the input * password or PIN and ending up with a hardened encryption key. It is important that the order and * inputs to each step in the derivation be well considered and implemented correctly. One important * requirement is that no output included in the backup acts as a "commitment" to the password or PIN * value, except the final ciphertext. An example of an issue with this would be if a hash of the * password and nonce were included in the backup. If a commitment to the password or PIN is * included, an attacker can locally brute force that commitment to recover the password, then use * that knowledge to complete the derivation. */ export declare function createBackup({ data, userSecret, hardening, metadata, }: CreateBackupArgs): Promise<Result<Backup, BackupError>>; export interface OpenBackupArgs { backup: Backup; userSecret: Buffer | string; } /** * Open an encrypted backup file, using the provided password or PIN to derive the decryption key. * * @param backup Backup structure including the ciphertext and key derivation information. * @param userSecret Password, PIN, or other user secret to use in deriving the encryption key. * If a string is provided, it will be UTF-8 encoded into a Buffer before use. */ export declare function openBackup({ backup, userSecret, }: OpenBackupArgs): Promise<Result<Buffer, BackupError>>;