diff --git a/README.md b/README.md index b55bfd3..68b4c30 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ const keyOne = 'some key' const keyTwo = 'some other key' // Encode a new shared value using keyOne -const shared = await SharedValue.create(keyOne, value) +const shared = await SharedValue.create('key1', keyOne, value) // Allow keyTwo to access the shared value: -await shared.addKey(keyOne, keyTwo) +await shared.addKey(keyOne, 'key2', keyTwo) // Get the shared value: const decodedValue = await shared.get(keyTwo) // => 'value to be encoded' @@ -33,6 +33,9 @@ const decodedValue = await shared.get(keyTwo) // => 'value to be encoded' // Set the shared value: const encodedValue = await shared.set(keyTwo, 'override string') +// Remove "key1" from the shared value: +await shared.removeKey(keyTwo, "key1") + // Serialize the shared value securely: const serialized = shared.toJSON() ``` diff --git a/src/KeyPackage.ts b/src/KeyPackage.ts index 7aac29a..6fc8300 100644 --- a/src/KeyPackage.ts +++ b/src/KeyPackage.ts @@ -1,4 +1,4 @@ -import {EncodedValue, isEncodedPayload, KeyValue} from './types' +import {EncodedKeyPackage, isEncodedPayload, KeyValue} from './types' import {Cryptor, InvalidKeyError} from './crypt/Cryptor' import {pkg} from './interface' import {Payload} from './Payload' @@ -11,7 +11,7 @@ import {Payload} from './Payload' */ export class KeyPackage { /** Create a new key package from an initial sharer's key. */ - public static async fromInitialSharer(initialSharerKey: KeyValue): Promise { + public static async fromInitialSharer(name: string, initialSharerKey: KeyValue): Promise { // Get the cryptor for the initial sharer const sharerCryptor = pkg.cryptor(initialSharerKey) @@ -20,17 +20,17 @@ export class KeyPackage { const envelopeKey = await sharerCryptor.encode(envelope.encode()) // Return a new KeyPackage with the created envelope - return new KeyPackage([envelopeKey]) + return new KeyPackage({ [name]: envelopeKey }) } /** Instantiate a key package from its serialized JSON value. */ - public static fromJSON(value: { envelopes: EncodedValue[] }): KeyPackage { + public static fromJSON(value: { envelopes: EncodedKeyPackage }): KeyPackage { return new KeyPackage(value.envelopes) } constructor( /** The encoded user keys. */ - private readonly envelopes: EncodedValue[], + private envelopes: EncodedKeyPackage, ) {} /** @@ -40,7 +40,13 @@ export class KeyPackage { async getCryptor(key: KeyValue): Promise { const envelopeCryptor = pkg.cryptor(key) - for ( const encodedKey of this.envelopes ) { + 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) ) { @@ -55,9 +61,10 @@ export class KeyPackage { /** * 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, shareeKey?: KeyValue): Promise { + async addCryptor(sharerKey: KeyValue, shareeName: string, shareeKey?: KeyValue): Promise { // Validate the sharer's key and get the cryptor const sharerCryptor = await this.getCryptor(sharerKey) @@ -67,13 +74,35 @@ export class KeyPackage { // Encode the global key using the sharee's key const envelope = new Payload(sharerCryptor.getKey()) - const envelopeKey = await shareeCryptor.encode(envelope.encode()) - this.envelopes.push(envelopeKey) + 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 { diff --git a/src/SharedValue.ts b/src/SharedValue.ts index a6fd355..20ae28f 100644 --- a/src/SharedValue.ts +++ b/src/SharedValue.ts @@ -1,11 +1,11 @@ -import {EncodedValue, KeyValue} from './types' +import {EncodedKeyPackage, EncodedValue, KeyValue} from './types' import {KeyPackage} from './KeyPackage' import {Payload} from './Payload' /** Interface for a JSON-serializable shared value. */ export interface SerializedSharedValue { value: EncodedValue, - envelopes: EncodedValue[], + envelopes: EncodedKeyPackage, } /** @@ -17,11 +17,12 @@ export class SharedValue { /** * Create a new shared value. + * @param name - the name of the initial sharer's key * @param initialSharerKey - an initial key, used to give access to the encoded value * @param value - the value to be encoded and shared */ - public static async create(initialSharerKey: KeyValue, value: T): Promise> { - const keyPackage = await KeyPackage.fromInitialSharer(initialSharerKey) + public static async create(name: string, initialSharerKey: KeyValue, value: T): Promise> { + const keyPackage = await KeyPackage.fromInitialSharer(name, initialSharerKey) const cryptor = await keyPackage.getCryptor(initialSharerKey) const payload = new Payload(value) @@ -80,10 +81,20 @@ export class SharedValue { * Add a new key to the key package for this shared value. This allows * the `shareeKey` to access this shared value. * @param sharerKey - a currently valid key + * @param shareeName - the name of the new key * @param shareeKey - the new key to add */ - async addKey(sharerKey: KeyValue, shareeKey?: KeyValue): Promise { - return this.package.addCryptor(sharerKey, shareeKey) + async addKey(sharerKey: KeyValue, shareeName: string, shareeKey?: KeyValue): Promise { + return this.package.addCryptor(sharerKey, shareeName, shareeKey) + } + + /** + * Remove a given key from the key package. + * @param sharerKey + * @param shareeKey + */ + async removeKey(sharerKey: KeyValue, shareeName: string): Promise { + await this.package.removeCryptor(sharerKey, shareeName) } /** Serialize the shared value securely. */ diff --git a/src/types.ts b/src/types.ts index 36f8641..345eb10 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,9 @@ export type DecodedValue = string /** String that has been encrypted. */ export type EncodedValue = string +/** Collection of encoded keys, by name. */ +export type EncodedKeyPackage = { [name: string]: EncodedValue } + /** A key used for encryption. */ export type KeyValue = string