UNPKG

pooja-docucomb-tink-crypto

Version:

A multi-language, cross-platform library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.

304 lines (264 loc) 12.3 kB
/** * @license * Copyright 2020 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {SecurityException} from '../exception/security_exception'; import * as PrimitiveSet from '../internal/primitive_set'; import {PbKeysetKey, PbKeyStatusType, PbOutputPrefixType} from '../internal/proto'; import * as Bytes from '../subtle/bytes'; import * as Random from '../subtle/random'; import {HybridDecryptWrapper} from './hybrid_decrypt_wrapper'; import {HybridEncryptWrapper} from './hybrid_encrypt_wrapper'; import {HybridDecrypt} from './internal/hybrid_decrypt'; import {HybridEncrypt} from './internal/hybrid_encrypt'; describe('hybrid decrypt wrapper test', function() { it('decrypt, invalid ciphertext', async function() { const primitiveSets = createDummyPrimitiveSets(); const decryptPrimitiveSet = primitiveSets['decryptPrimitiveSet']; const hybridDecrypt = new HybridDecryptWrapper().wrap(decryptPrimitiveSet); // Ciphertext which cannot be decrypted by any primitive in the primitive // set. const ciphertext = new Uint8Array([9, 8, 7, 6, 5, 4, 3]); try { await hybridDecrypt.decrypt(ciphertext); fail('Should throw an exception'); } catch (e: any) { expect(e.toString()).toBe(ExceptionText.cannotBeDecrypted()); } }); it('decrypt, should work', async function() { const primitiveSets = createDummyPrimitiveSets(); const plaintext = Random.randBytes(10); // As keys are just dummy keys which do not contain key data, the same key // is used for both encrypt and decrypt. const key = createDummyKeysetKey( /** keyId = */ 0xFFFFFFFF, PbOutputPrefixType.TINK, /** enabled = */ true); const ciphertextSuffix = new Uint8Array([0, 0, 0, 0xFF]); // Get the ciphertext. const encryptPrimitiveSet = primitiveSets['encryptPrimitiveSet']; const encryptPrimitive = new DummyHybridEncrypt(ciphertextSuffix); const entry = encryptPrimitiveSet.addPrimitive(encryptPrimitive, key); // Has to be set to primary as then it is used in encryption. encryptPrimitiveSet.setPrimary(entry); const hybridEncrypt = new HybridEncryptWrapper().wrap(encryptPrimitiveSet); const ciphertext = await hybridEncrypt.encrypt(plaintext); // Create a primitive set containing the primitive which can be used for // encryption. Add also few more primitives with the same key as the // primitive set should decrypt the ciphertext whenever there is at least // one primitive which does not fail to decrypt the ciphertext. const decryptPrimitiveSet = primitiveSets['decryptPrimitiveSet']; const decryptPrimitive = new DummyHybridDecrypt(ciphertextSuffix); decryptPrimitiveSet.addPrimitive( new DummyHybridDecrypt(Random.randBytes(5)), key); decryptPrimitiveSet.addPrimitive(decryptPrimitive, key); decryptPrimitiveSet.addPrimitive( new DummyHybridDecrypt(Random.randBytes(5)), key); // Decrypt the ciphertext. const hybridDecrypt = new HybridDecryptWrapper().wrap(decryptPrimitiveSet); const decryptedCiphertext = await hybridDecrypt.decrypt(ciphertext); // Test that the result is the original plaintext. expect(decryptedCiphertext).toEqual(plaintext); }); it('decrypt, ciphertext encrypted by raw primitive', async function() { const primitiveSets = createDummyPrimitiveSets(); const plaintext = Random.randBytes(10); // As keys are just dummy keys which do not contain key data, the same key // is used for both encrypt and decrypt. const key = createDummyKeysetKey( /** keyId = */ 0xFFFFFFFF, PbOutputPrefixType.RAW, /** enabled = */ true); const ciphertextSuffix = new Uint8Array([0, 0, 0, 0xFF]); // Get the ciphertext. const encryptPrimitive = new DummyHybridEncrypt(ciphertextSuffix); const ciphertext = await encryptPrimitive.encrypt(plaintext); // Decrypt the ciphertext. const decryptPrimitiveSet = primitiveSets['decryptPrimitiveSet']; const decryptPrimitive = new DummyHybridDecrypt(ciphertextSuffix); decryptPrimitiveSet.addPrimitive(decryptPrimitive, key); const hybridDecrypt = new HybridDecryptWrapper().wrap(decryptPrimitiveSet); const decryptedCiphertext = await hybridDecrypt.decrypt(ciphertext); // Test that the result is the original plaintext. expect(decryptedCiphertext).toEqual(plaintext); }); it('decrypt, with context info', async function() { const primitiveSets = createDummyPrimitiveSets(); const plaintext = Random.randBytes(10); const contextInfo = Random.randBytes(10); // As keys are just dummy keys which do not contain key data, the same key // is used for both encrypt and decrypt. const key = createDummyKeysetKey( /** keyId = */ 0xFFFFFFFF, PbOutputPrefixType.RAW, /** enabled = */ true); const ciphertextSuffix = new Uint8Array([0, 0, 0, 0xFF]); // Get the ciphertext. const encryptPrimitive = new DummyHybridEncrypt(ciphertextSuffix); const ciphertext = await encryptPrimitive.encrypt(plaintext, contextInfo); // Get primitive for decryption. const decryptPrimitiveSet = primitiveSets['decryptPrimitiveSet']; const decryptPrimitive = new DummyHybridDecrypt(ciphertextSuffix); decryptPrimitiveSet.addPrimitive(decryptPrimitive, key); const hybridDecrypt = new HybridDecryptWrapper().wrap(decryptPrimitiveSet); // Check that contextInfo was passed correctly (decryption without // contextInfo argument should not work, but with contextInfo it should work // properly). try { await hybridDecrypt.decrypt(ciphertext); fail('An exception should be thrown.'); } catch (e: any) { expect(e.toString()).toBe(ExceptionText.cannotBeDecrypted()); } const decryptedCiphertext = await hybridDecrypt.decrypt(ciphertext, contextInfo); // Test that the result is the original plaintext. expect(decryptedCiphertext).toEqual(plaintext); }); it('decrypt, with disabled primitive', async function() { const primitiveSets = createDummyPrimitiveSets(); const plaintext = Random.randBytes(10); const key = createDummyKeysetKey( /** keyId = */ 0xFFFFFFFF, PbOutputPrefixType.RAW, /** enabled = */ false); const ciphertextSuffix = new Uint8Array([0, 0, 0, 0xFF]); const encryptPrimitive = new DummyHybridEncrypt(ciphertextSuffix); const ciphertext = await encryptPrimitive.encrypt(plaintext); const decryptPrimitiveSet = primitiveSets['decryptPrimitiveSet']; const decryptPrimitive = new DummyHybridDecrypt(ciphertextSuffix); decryptPrimitiveSet.addPrimitive(decryptPrimitive, key); const hybridDecrypt = new HybridDecryptWrapper().wrap(decryptPrimitiveSet); try { await hybridDecrypt.decrypt(ciphertext); fail('An exception should be thrown.'); } catch (e: any) { expect(e.toString()).toBe(ExceptionText.cannotBeDecrypted()); } }); it('decrypt, with empty ciphertext', async function() { const primitiveSets = createDummyPrimitiveSets(); const decryptPrimitiveSet = primitiveSets['decryptPrimitiveSet']; const hybridDecrypt = new HybridDecryptWrapper().wrap(decryptPrimitiveSet); try { await hybridDecrypt.decrypt(new Uint8Array(0)); fail('An exception should be thrown.'); } catch (e: any) { expect(e.toString()).toBe(ExceptionText.cannotBeDecrypted()); } }); }); /** @final */ class ExceptionText { static nullPrimitiveSet(): string { return 'SecurityException: Primitive set has to be non-null.'; } static cannotBeDecrypted(): string { return 'SecurityException: Decryption failed for the given ciphertext.'; } static nullCiphertext(): string { return 'SecurityException: Ciphertext has to be non-null.'; } } /** Function for creating keys for testing purposes. */ function createDummyKeysetKey( keyId: number, outputPrefix: PbOutputPrefixType, enabled: boolean): PbKeysetKey { const key = new PbKeysetKey(); if (enabled) { key.setStatus(PbKeyStatusType.ENABLED); } else { key.setStatus(PbKeyStatusType.DISABLED); } key.setOutputPrefixType(outputPrefix); key.setKeyId(keyId); return key; } /** * Creates a primitive sets for HybridEncrypt and HybridDecrypt with * 'numberOfPrimitives' primitives. The keys corresponding to the primitives * have ids from the set [1, ..., numberOfPrimitives] and the primitive * corresponding to key with id 'numberOfPrimitives' is set to be primary * whenever opt_withPrimary is set to true (where true is the default value). */ function createDummyPrimitiveSets(opt_withPrimary: boolean = true): { encryptPrimitiveSet: PrimitiveSet.PrimitiveSet<DummyHybridEncrypt>, decryptPrimitiveSet: PrimitiveSet.PrimitiveSet<DummyHybridDecrypt> } { const numberOfPrimitives = 5; const encryptPrimitiveSet = new PrimitiveSet.PrimitiveSet<DummyHybridEncrypt>(DummyHybridEncrypt); const decryptPrimitiveSet = new PrimitiveSet.PrimitiveSet<DummyHybridDecrypt>(DummyHybridDecrypt); for (let i = 1; i < numberOfPrimitives; i++) { let outputPrefix: PbOutputPrefixType; switch (i % 3) { case 0: outputPrefix = PbOutputPrefixType.TINK; break; case 1: outputPrefix = PbOutputPrefixType.LEGACY; break; default: outputPrefix = PbOutputPrefixType.RAW; } const key = createDummyKeysetKey(i, outputPrefix, /* enabled = */ i % 4 < 2); const ciphertextSuffix = new Uint8Array([0, 0, i]); const hybridEncrypt = new DummyHybridEncrypt(ciphertextSuffix); encryptPrimitiveSet.addPrimitive(hybridEncrypt, key); const hybridDecrypt = new DummyHybridDecrypt(ciphertextSuffix); decryptPrimitiveSet.addPrimitive(hybridDecrypt, key); } const key = createDummyKeysetKey( numberOfPrimitives, PbOutputPrefixType.TINK, /* enabled = */ true); const ciphertextSuffix = new Uint8Array([0, 0, numberOfPrimitives]); const hybridEncrypt = new DummyHybridEncrypt(ciphertextSuffix); const encryptEntry = encryptPrimitiveSet.addPrimitive(hybridEncrypt, key); const hybridDecrypt = new DummyHybridDecrypt(ciphertextSuffix); const decryptEntry = decryptPrimitiveSet.addPrimitive(hybridDecrypt, key); if (opt_withPrimary) { encryptPrimitiveSet.setPrimary(encryptEntry); decryptPrimitiveSet.setPrimary(decryptEntry); } return { 'encryptPrimitiveSet': encryptPrimitiveSet, 'decryptPrimitiveSet': decryptPrimitiveSet }; } /** @final */ class DummyHybridEncrypt extends HybridEncrypt { constructor(private readonly ciphertextSuffix: Uint8Array) { super(); } async encrypt(plaintext: Uint8Array, opt_contextInfo?: Uint8Array) { const ciphertext = Bytes.concat(plaintext, this.ciphertextSuffix); if (opt_contextInfo) { return Bytes.concat(ciphertext, opt_contextInfo); } return ciphertext; } } /** @final */ class DummyHybridDecrypt extends HybridDecrypt { constructor(private readonly ciphertextSuffix: Uint8Array) { super(); } async decrypt(ciphertext: Uint8Array, opt_contextInfo?: Uint8Array) { if (opt_contextInfo) { const infoLength = opt_contextInfo.length; const contextInfo = ciphertext.slice(ciphertext.length - infoLength, ciphertext.length); if ([...contextInfo].toString() !== [...opt_contextInfo].toString()) { throw new SecurityException('Context info does not match.'); } ciphertext = ciphertext.slice(0, ciphertext.length - infoLength); } const plaintext = ciphertext.slice(0, ciphertext.length - this.ciphertextSuffix.length); const suffix = ciphertext.slice( ciphertext.length - this.ciphertextSuffix.length, ciphertext.length); if ([...suffix].toString() === [...this.ciphertextSuffix].toString()) { return plaintext; } throw new SecurityException(ExceptionText.cannotBeDecrypted()); } }