import {EncodedKeyPackage, isEncodedPayload, KeyValue} from './types' import {Cryptor, InvalidKeyError} from './crypt/Cryptor' import {pkg} from './interface' import {Payload} from './Payload' /** * A collection of keys that are used to decode a base key. * * The base key is used to encode various values that can then be accessed * by all of the other keys in the package. */ export class KeyPackage { /** Create a new key package from an initial sharer's key. */ public static async fromInitialSharer(name: string, initialSharerKey: KeyValue): Promise { // Get the cryptor for the initial sharer const sharerCryptor = pkg.cryptor(initialSharerKey) // Encode a new global key using the initial sharer's key const envelope = new Payload(Cryptor.getNewKey()) const envelopeKey = await sharerCryptor.encode(envelope.encode()) // Return a new KeyPackage with the created envelope return new KeyPackage({ [name]: envelopeKey }) } /** Instantiate a key package from its serialized JSON value. */ public static fromJSON(value: { envelopes: EncodedKeyPackage }): KeyPackage { return new KeyPackage(value.envelopes) } constructor( /** The encoded user keys. */ private envelopes: EncodedKeyPackage, ) {} /** * Get a Cryptor instance for master key given some user's key, if that key is valid. * @param key */ async getCryptor(key: KeyValue): Promise { const envelopeCryptor = pkg.cryptor(key) for ( const encodedKeyName in this.envelopes ) { if ( !Object.hasOwnProperty.call(this.envelopes, encodedKeyName) ) { continue } const encodedKey = this.envelopes[encodedKeyName] // Attempt to decode one of the envelope keys using the input key const decodedKey = await envelopeCryptor.decode(encodedKey) if ( isEncodedPayload(decodedKey) ) { // We successfully decoded the envelope, which contains the global key return pkg.cryptor(Payload.from(decodedKey).value) } } throw new InvalidKeyError() } /** * Add a user's key to this package, granting them access to the master key. * @param sharerKey - a currently valid user's key * @param shareeName - the name of the sharee key * @param shareeKey - the key to add to the package */ async addCryptor(sharerKey: KeyValue, shareeName: string, shareeKey?: KeyValue): Promise { // Validate the sharer's key and get the cryptor const sharerCryptor = await this.getCryptor(sharerKey) // Create a new cryptor for the sharee const determinedShareeKey = shareeKey || Cryptor.getNewKey() const shareeCryptor = pkg.cryptor(determinedShareeKey) // Encode the global key using the sharee's key const envelope = new Payload(sharerCryptor.getKey()) this.envelopes[shareeName] = await shareeCryptor.encode(envelope.encode()) // Return the sharee's key that can be used to decode the global key return determinedShareeKey } /** * Remove the `shareeKey` from this key package. * @param sharerKey - a valid key in the package * @param shareeName - name of the key that will be removed */ async removeCryptor(sharerKey: KeyValue, shareeName: string): Promise { // Validate the sharer's key await this.getCryptor(sharerKey) const newCryptors = {} as EncodedKeyPackage for ( const encodedKeyName in this.envelopes ) { if ( !Object.hasOwnProperty.call(this.envelopes, encodedKeyName) ) { continue } if ( encodedKeyName !== shareeName ) { newCryptors[encodedKeyName] = this.envelopes[encodedKeyName] } } this.envelopes = newCryptors } /** Serialize the key package securely. */ toJSON() { return { envelopes: this.envelopes, } } }