@planq-network/encrypted-backup
Version:
Libraries for implemented password encrypted account backups
199 lines (198 loc) • 10.7 kB
TypeScript
/// <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>>;