import { Serializable , stringToBytes , AsBytes , AsSerializable } from "./encoding" ; import { Err , ErrorCode } from "./error" ; import { PBKDF2Params , AESKey , AESEncryptionParams , AESKeyParams , RSAEncryptionParams , RSAPrivateKey , RSAPublicKey } from "./crypto" ; import { getCryptoProvider as getProvider } from "./platform" ; /**
 * Base class for all **Container** implementations. In general, a **Container** is
 * an object for holding data encrypted using a symmetric cipher. Implementations
 * vary mostly in how the encryption key is generated. Sub classes must implement
 * the [[unlock]] method and may likely also want to augment [[lock]], [[validate]],
 * [[_fromRaw]] and [[_toRaw]].
 */ export abstract class BaseContainer extends Serializable { /** Parameters used for encryption of content data */ @ AsSerializable ( AESEncryptionParams ) encryptionParams : AESEncryptionParams = new AESEncryptionParams ( ) ; /** Encrypted data */ @ AsBytes ( ) encryptedData ? : Uint8Array ; /**
     * The key used for encryption. Sub classes must set this property in the [[unlock]] method.
     */ protected _key ? : AESKey ; /**
     * Encrypts the provided `data` and stores it in the container
     */ async setData ( data ) { if ( ! this . _key ) { throw new Err ( ErrorCode . ENCRYPTION_FAILED , "No encryption key provided!" ) ; } this . encryptionParams . iv = await getProvider ( ) . randomBytes ( 16 ) ; this . encryptionParams . additionalData = await getProvider ( ) . randomBytes ( 16 ) ; this . encryptedData = await getProvider ( ) . encrypt ( this . _key , data , this . encryptionParams ) ; } /**
     * Decrypts and extracts the plain text data from the container. This will
     * usually require unlocking the container first.
     */ async getData ( ) { if ( ! this . encryptedData || ! this . _key ) { throw new Err ( ErrorCode . DECRYPTION_FAILED ) ; } return await getProvider ( ) . decrypt ( this . _key , this . encryptedData , this . encryptionParams ) ; } /**
     * Unlocks the container, making it possible to extract the plain text
     * data via [[getData]]. The type of **secret** provided will differ based
     * on the encryption scheme used by implemenations.
     */ abstract unlock ( secret ) ; /**
     * Locks the container, removing the possibility to extract the plain text data
     * via [[getData]] until the container is unlocked again. Subclasses extending
     * this class must take care to delete any keys or other sensitive data
     * that may have been stored temporarily after unlocking the container.
     */ lock ( ) { delete this . _key ; } clone ( ) { const clone = super . clone ( ) ; clone . _key = this . _key ; return clone ; } } /**
 * Most basic **Container** implementation where the encryption key is
 * simply passed explicitly.
 */ export class SimpleContainer extends BaseContainer { async unlock ( key ) { this . _key = key ; } } /**
 * Password-based **Container** that uses the
 * [PBES2](https://tools.ietf.org/html/rfc2898#section-6.2) encryption scheme,
 * deriving the encryption key from a user-provided passphrase.
 */ export class PBES2Container extends BaseContainer { /** Parameters used for key derivation */ @ AsSerializable ( PBKDF2Params ) keyParams : PBKDF2Params = new PBKDF2Params ( ) ; /**
     * Unlocks the container using the given **password**
     */ async unlock ( password ) { if ( ! this . keyParams . salt . length ) { this . keyParams . salt = await getProvider ( ) . randomBytes ( 16 ) ; } this . _key = await getProvider ( ) . deriveKey ( stringToBytes ( password ) , this . keyParams ) ; } } /**
 * Represents an individual with access to a [[SharedContainer]]. Each accessor is mapped
 * to an entity owning a public/private key pair via their `id`.
 */ export class Accessor extends Serializable { /**
     * Identifier used to map an `Accessor` to the owner of the public key used to encrypt the shared key.
     */ id : string = "" ; /** Shared key encrypted with the public key of the entity associated with the `Accessor` object */ @ AsBytes ( ) encryptedKey : Uint8Array = new Uint8Array ( ) ; } /**
 * The `SharedContainer` is used to securely share data between a number of
 * accessors using a shared-key encryption scheme where the content data is
 * encrypted using a randomly generated shared key that is then encrypted with
 * each accessors public key and stored along the encrypted data. Accessors can
 * then retrieve the shared key by decrypting it using their private key and
 * use it to recover the original data.
 */ export class SharedContainer extends BaseContainer { /** Parameters used to wrap the shared encryption key */ @ AsSerializable ( RSAEncryptionParams ) keyParams : RSAEncryptionParams = new RSAEncryptionParams ( ) ; /** The ids and encrypted keys of all accessors */ @ AsSerializable ( Accessor ) accessors : Accessor [ ] = [ ] ; /**
     * Unlocks the container using the id and private key of a given accessor.
     * The id is used to look up the corresponding encrypted key while the
     * private key is used to decrypt it.
     */ async unlock ( { id , privateKey } ) { if ( this . _key ) { return ; } const accessor = this . accessors . find ( a => a . id === id ) ; if ( ! accessor || ! accessor . encryptedKey ) { throw new Err ( ErrorCode . MISSING_ACCESS , "You no longer have access to this vault." ) ; } this . _key = await getProvider ( ) . decrypt ( privateKey , accessor . encryptedKey , this . keyParams ) ; } /**
     * Updates the containers accessors, generating a new shared key and encrypting
     * it with the public keys of the provided **subjects**. Non-empty containers
     * need to be unlocked first.
     */ async updateAccessors ( subjects ) { let data = null ; if ( this . encryptedData ) { if ( ! this . _key ) { throw "Non-empty containers need to be unlocked before accessors can be updated!" ; } data = await this . getData ( ) ; } this . _key = await getProvider ( ) . generateKey ( new AESKeyParams ( ) ) ; if ( data ) { await this . setData ( data ) ; } this . accessors = await Promise . all ( subjects . map ( async ( { id , publicKey } ) => { const accessor = new Accessor ( ) ; accessor . id = id ; accessor . encryptedKey = await getProvider ( ) . encrypt ( publicKey , this . _key ! , this . keyParams ) ; return accessor ; } ) ) ; } }