| /** |
| * @license |
| * Copyright 2020 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| import {Aead} from '../aead'; |
| import {AeadKeyTemplates} from '../aead/aead_key_templates'; |
| import {SecurityException} from '../exception/security_exception'; |
| import {HybridDecrypt, HybridEncrypt} from '../hybrid'; |
| import * as HybridConfig from '../hybrid/hybrid_config'; |
| import {HybridKeyTemplates} from '../hybrid/hybrid_key_templates'; |
| import {Mac} from '../mac'; |
| import * as Bytes from '../subtle/bytes'; |
| import * as Random from '../subtle/random'; |
| import {assertExists, assertMessageEquals, createKeyset} from '../testing/internal/test_utils'; |
| |
| import {BinaryKeysetReader} from './binary_keyset_reader'; |
| import {BinaryKeysetWriter} from './binary_keyset_writer'; |
| import {CleartextKeysetHandle} from './cleartext_keyset_handle'; |
| import {KeyFactory, KeyManager} from './key_manager'; |
| import {generateNew, KeysetHandle, read, readNoSecret} from './keyset_handle'; |
| import {PbKeyData, PbKeyMaterialType, PbKeyset, PbKeysetKey, PbKeyStatusType, PbMessage, PbOutputPrefixType} from './proto'; |
| import * as Registry from './registry'; |
| import {Constructor} from './util'; |
| |
| describe('KeysetHandle', () => { |
| beforeEach(() => { |
| // Use a generous promise timeout for running continuously. |
| jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 1000; // 1000s |
| |
| HybridConfig.register(); |
| }); |
| |
| afterEach(() => { |
| Registry.reset(); |
| |
| // Reset the promise timeout to default value. |
| jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; // 1s |
| }); |
| |
| describe('constructor', () => { |
| it('throws with empty list of keys', async () => { |
| const keyset = new PbKeyset().setKeyList([]); |
| expect(() => new KeysetHandle(keyset)) |
| .toThrowError( |
| SecurityException, |
| 'Keyset should be non null and must contain at least one key.'); |
| }); |
| |
| it('does not throw for valid keyset protos', async () => { |
| const keyset = createKeyset(); |
| expect(() => new KeysetHandle(keyset)).not.toThrow(); |
| }); |
| }); |
| |
| describe('getKeyset', () => { |
| it('returns the underlying keyset proto', async () => { |
| const keyset = createKeyset(); |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| const result = keysetHandle.getKeyset(); |
| expect(result).toEqual(keyset); |
| }); |
| }); |
| |
| describe('read', () => { |
| it('is not yet implemented', async () => { |
| const keyTemplate = AeadKeyTemplates.aes128CtrHmacSha256(); |
| const keysetHandle = await generateNew(keyTemplate); |
| const serializedKeyset = |
| CleartextKeysetHandle.serializeToBinary(keysetHandle); |
| const keysetReader = new BinaryKeysetReader(serializedKeyset); |
| const aead = await keysetHandle.getPrimitive<Aead>(Aead); |
| |
| await expectAsync(read(keysetReader, aead)) |
| .toBeRejectedWithError( |
| SecurityException, 'KeysetHandle -- read: Not implemented yet.'); |
| }); |
| }); |
| |
| describe('generateNew', () => { |
| it('generates new keyset handles given a key template', async () => { |
| const keyTemplate = AeadKeyTemplates.aes128CtrHmacSha256(); |
| const keysetHandle = await generateNew(keyTemplate); |
| const keyset = keysetHandle.getKeyset(); |
| expect(1).toBe(keyset.getKeyList().length); |
| |
| const key = keyset.getKeyList()[0]; |
| expect(keyset.getPrimaryKeyId()).toBe(key.getKeyId()); |
| expect(keyTemplate.getOutputPrefixType()).toBe(key.getOutputPrefixType()); |
| expect(PbKeyStatusType.ENABLED).toBe(key.getStatus()); |
| |
| const keyData = assertExists(key.getKeyData()); |
| expect(keyTemplate.getTypeUrl()).toBe(keyData.getTypeUrl()); |
| |
| const aead = await keysetHandle.getPrimitive(Aead); |
| const plaintext = Random.randBytes(20); |
| const ciphertext = await aead.encrypt(plaintext); |
| expect(await aead.decrypt(ciphertext)).toEqual(plaintext); |
| }); |
| }); |
| |
| describe('write', () => { |
| it('is not yet implemented', async () => { |
| const keyset = createKeysetAndInitializeRegistry(Aead); |
| const keysetHandle = new KeysetHandle(keyset); |
| const keysetWriter = new BinaryKeysetWriter(); |
| const aead = await keysetHandle.getPrimitive<Aead>(Aead); |
| |
| await expectAsync(keysetHandle.write(keysetWriter, aead)) |
| .toBeRejectedWithError( |
| SecurityException, 'KeysetHandle -- write: Not implemented yet.'); |
| }); |
| }); |
| |
| describe('getPrimitive', () => { |
| it('aead', async () => { |
| const keyset = createKeysetAndInitializeRegistry(Aead); |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| const aead = await keysetHandle.getPrimitive<Aead>(Aead); |
| |
| // Test the aead primitive returned by getPrimitive method. |
| const plaintext = new Uint8Array([1, 2, 3, 4, 5, 6]); |
| const ciphertext = await aead.encrypt(plaintext); |
| const decryptedText = await aead.decrypt(ciphertext); |
| |
| expect(decryptedText).toEqual(plaintext); |
| }); |
| |
| it('hybrid encrypt', async () => { |
| const keyset = createKeysetAndInitializeRegistry(HybridEncrypt); |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| // Test the HybridEncrypt primitive returned by getPrimitive method. |
| const hybridEncrypt = |
| await keysetHandle.getPrimitive<HybridEncrypt>(HybridEncrypt); |
| const plaintext = Random.randBytes(10); |
| const ciphertext = await hybridEncrypt.encrypt(plaintext); |
| |
| // DummyHybridEncrypt just appends a ciphertext suffix to the plaintext. |
| // Since the primary key id is 1, the ciphertext prefix should also be 1. |
| expect(ciphertext) |
| .toEqual(Bytes.concat( |
| new Uint8Array([ |
| 0, 0, 0, 0, 1 |
| ]) /* prefix which is 1-byte version + 4-byte primary key id*/, |
| plaintext, |
| new Uint8Array([1]) /* suffix which is 1-byte primary key id */)); |
| }); |
| |
| it('hybrid decrypt', async () => { |
| const decryptKeysetHandle = |
| new KeysetHandle(createKeysetAndInitializeRegistry(HybridDecrypt)); |
| const hybridDecrypt = |
| await decryptKeysetHandle.getPrimitive<HybridDecrypt>(HybridDecrypt); |
| |
| const encryptKeysetHandle = |
| new KeysetHandle(createKeysetAndInitializeRegistry(HybridEncrypt)); |
| const hybridEncrypt = |
| await encryptKeysetHandle.getPrimitive<HybridEncrypt>(HybridEncrypt); |
| |
| const plaintext = Random.randBytes(10); |
| const ciphertext = await hybridEncrypt.encrypt(plaintext); |
| const decrypted = await hybridDecrypt.decrypt(ciphertext); |
| |
| expect(decrypted).toEqual(plaintext); |
| }); |
| |
| it('aead, custom key manager', async () => { |
| const keyset = new PbKeyset(); |
| |
| // Add a new key with a new key type associated to custom key manager |
| // to the keyset. |
| const keyTypeUrl = 'new_custom_aead_key_type'; |
| const keyId = 0xFFFFFFFF; |
| const key = |
| createKey({keyId, outputPrefix: PbOutputPrefixType.TINK, keyTypeUrl}); |
| keyset.addKey(key); |
| keyset.setPrimaryKeyId(keyId); |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| // Create a custom key manager. |
| const customKeyManager = new DummyKeyManager( |
| keyTypeUrl, new DummyAead(Random.randBytes(10)), Aead); |
| |
| // Encrypt with the primitive returned by customKeyManager. |
| const aead = |
| await keysetHandle.getPrimitive<Aead>(Aead, customKeyManager); |
| const plaintext = Random.randBytes(20); |
| const ciphertext = await aead.encrypt(plaintext); |
| |
| // Register another key manager with the custom key type. |
| const managerInRegistry = new DummyKeyManager( |
| keyTypeUrl, new DummyAead(Random.randBytes(10)), Aead); |
| Registry.registerKeyManager(managerInRegistry); |
| |
| // Check that the primitive returned by getPrimitive cannot decrypt the |
| // ciphertext. This is because managerInRegistry is different from |
| // customKeyManager. |
| const aeadFromRegistry = await keysetHandle.getPrimitive<Aead>(Aead); |
| |
| await expectAsync(aeadFromRegistry.decrypt(ciphertext)) |
| .toBeRejectedWithError( |
| SecurityException, 'Decryption failed for the given ciphertext.'); |
| |
| // Check that the primitive returned by getPrimitive with customKeyManager |
| // decrypts correctly. |
| const aeadFromCustomKeyManager = |
| await keysetHandle.getPrimitive<Aead>(Aead, customKeyManager); |
| const decryptedText = await aeadFromCustomKeyManager.decrypt(ciphertext); |
| expect(decryptedText).toEqual(plaintext); |
| }); |
| |
| it('hybrid encrypt, custom key manager', async () => { |
| const keyset = new PbKeyset(); |
| |
| // Add a new key with a new key type associated to custom key manager |
| // to the keyset. |
| const keyTypeUrl = 'new_custom_hybrid_encrypt_key_type'; |
| const keyId = 0xFFFFFFFF; |
| const key = |
| createKey({keyId, outputPrefix: PbOutputPrefixType.TINK, keyTypeUrl}); |
| keyset.addKey(key); |
| keyset.setPrimaryKeyId(keyId); |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| // Create a custom key manager. |
| const customKeyManager = new DummyKeyManager( |
| keyTypeUrl, new DummyHybridEncrypt(Random.randBytes(10)), |
| HybridEncrypt); |
| |
| // Encrypt with the primitive returned by customKeyManager. |
| const customHybridEncrypt = |
| await keysetHandle.getPrimitive<HybridEncrypt>( |
| HybridEncrypt, customKeyManager); |
| const plaintext = Random.randBytes(20); |
| const ciphertext = await customHybridEncrypt.encrypt(plaintext); |
| |
| // Register another key manager with the custom key type. |
| const managerInRegistry = new DummyKeyManager( |
| keyTypeUrl, new DummyHybridEncrypt(Random.randBytes(10)), |
| HybridEncrypt); |
| Registry.registerKeyManager(managerInRegistry); |
| |
| // Check that the primitive returned by getPrimitive is not the same as |
| // customHybridEncrypt. This is because managerInRegistry is different |
| // from customKeyManager. |
| const hybridFromRegistry = |
| await keysetHandle.getPrimitive<HybridEncrypt>(HybridEncrypt); |
| const ciphertext2 = await hybridFromRegistry.encrypt(plaintext); |
| expect(ciphertext2).not.toEqual(ciphertext); |
| |
| // Check that the primitive returned by getPrimitive with customKeyManager |
| // is the same as customHybridEncrypt. |
| const hybridEncryptFromCustomKeyManager = |
| await keysetHandle.getPrimitive<HybridEncrypt>( |
| HybridEncrypt, customKeyManager); |
| const ciphertext3 = |
| await hybridEncryptFromCustomKeyManager.encrypt(plaintext); |
| expect(ciphertext3).toEqual(ciphertext); |
| }); |
| |
| it('hybrid decrypt, custom key manager', async () => { |
| // Both private and public keys have the same key id. |
| const keyId = 0xFFFFFFFF; |
| |
| // Create a public keyset. |
| |
| const publicKeyset = new PbKeyset(); |
| // Add a new key with a new key type associated to custom key manager |
| // to the keyset. |
| const publicKeyTypeUrl = 'new_custom_hybrid_encrypt_key_type'; |
| const publicKey = createKey({ |
| keyId, |
| outputPrefix: PbOutputPrefixType.TINK, |
| keyTypeUrl: publicKeyTypeUrl |
| }); |
| publicKeyset.addKey(publicKey); |
| publicKeyset.setPrimaryKeyId(keyId); |
| const publicKeysetHandle = new KeysetHandle(publicKeyset); |
| |
| // Create a corresponding private keyset. |
| |
| const privateKeyset = new PbKeyset(); |
| // Add a new key with a new key type associated to custom key manager |
| // to the keyset. |
| const privateKeyTypeUrl = 'new_custom_hybrid_decrypt_key_type'; |
| const privateKey = createKey({ |
| keyId, |
| outputPrefix: PbOutputPrefixType.TINK, |
| keyTypeUrl: privateKeyTypeUrl |
| }); |
| privateKeyset.addKey(privateKey); |
| privateKeyset.setPrimaryKeyId(keyId); |
| const privateKeysetHandle = new KeysetHandle(privateKeyset); |
| |
| // DummyHybridEncrypt (and DummyHybridDecrypt) just appends (and removes) |
| // a suffix to the plaintext. Create a random suffix that allows to |
| // determine which HybridDecrypt object is valid. |
| const ciphertextSuffix = Random.randBytes(10); |
| |
| // Register a public key manager that uses the legit ciphertext suffix. |
| const publicKeyManagerInRegistry = new DummyKeyManager( |
| publicKeyTypeUrl, new DummyHybridEncrypt(ciphertextSuffix), |
| HybridEncrypt); |
| Registry.registerKeyManager(publicKeyManagerInRegistry); |
| |
| // Encrypt with the primitive returned by getPrimitive. |
| const hybridEncrypt = |
| await publicKeysetHandle.getPrimitive<HybridEncrypt>(HybridEncrypt); |
| const plaintext = Random.randBytes(20); |
| const ciphertext = await hybridEncrypt.encrypt(plaintext); |
| |
| // Register a private key manager that uses a random ciphertext suffix. |
| const keyManagerWithRandomSuffix = new DummyKeyManager( |
| privateKeyTypeUrl, new DummyHybridDecrypt(Random.randBytes(10)), |
| HybridDecrypt); |
| Registry.registerKeyManager(keyManagerWithRandomSuffix); |
| |
| // Check that the primitive returned by getPrimitive cannot decrypt. This |
| // is because the ciphertext suffix is different. |
| const hybridDecryptFromRegistry = |
| await privateKeysetHandle.getPrimitive<HybridDecrypt>(HybridDecrypt); |
| await expectAsync(hybridDecryptFromRegistry.decrypt(ciphertext)) |
| .toBeRejectedWithError( |
| SecurityException, 'Decryption failed for the given ciphertext.'); |
| |
| // Create a custom private key manager with the correct ciphertext suffix. |
| const customHybridDecryptKeyManager = new DummyKeyManager( |
| privateKeyTypeUrl, new DummyHybridDecrypt(ciphertextSuffix), |
| HybridDecrypt); |
| |
| // Check that the primitive returned by getPrimitive with |
| // customHybridDecryptKeyManager can decrypt. |
| const customHybridDecrypt = |
| await privateKeysetHandle.getPrimitive<HybridDecrypt>( |
| HybridDecrypt, customHybridDecryptKeyManager); |
| const decrypted = await customHybridDecrypt.decrypt(ciphertext); |
| expect(decrypted).toEqual(plaintext); |
| }); |
| |
| it('keyset contains key corresponding to different primitive', async () => { |
| const keyset = createKeysetAndInitializeRegistry(Aead); |
| |
| // Add new key with new key type url to the keyset and register a key |
| // manager providing Mac primitives with this key. |
| const macKeyTypeUrl = 'mac_key_type_1'; |
| const macKeyId = 0xFFFFFFFF; |
| const macKey = createKey({ |
| keyId: macKeyId, |
| outputPrefix: PbOutputPrefixType.TINK, |
| keyTypeUrl: macKeyTypeUrl |
| }); |
| keyset.addKey(macKey); |
| const primitive = new DummyMac(new Uint8Array([0xFF])); |
| Registry.registerKeyManager( |
| new DummyKeyManager(macKeyTypeUrl, primitive, Mac)); |
| |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| await expectAsync(keysetHandle.getPrimitive<Aead>(Aead)) |
| .toBeRejectedWithError( |
| SecurityException, |
| 'Requested primitive type which is not supported by ' + |
| 'this key manager.'); |
| }); |
| }); |
| |
| describe('getPrimitiveSet', () => { |
| it('primary key is the enabled key with given id', async () => { |
| const keyId = 1; |
| const primaryUrl = 'key_type_url_for_primary_key'; |
| const disabledUrl = 'key_type_url_for_disabled_key'; |
| |
| const keyset = new PbKeyset(); |
| keyset.addKey(createKey({ |
| keyId, |
| outputPrefix: PbOutputPrefixType.TINK, |
| keyTypeUrl: disabledUrl, |
| enabled: false |
| })); |
| keyset.addKey(createKey({ |
| keyId, |
| outputPrefix: PbOutputPrefixType.LEGACY, |
| keyTypeUrl: disabledUrl, |
| enabled: false |
| })); |
| keyset.addKey(createKey({ |
| keyId, |
| outputPrefix: PbOutputPrefixType.RAW, |
| keyTypeUrl: disabledUrl, |
| enabled: false |
| })); |
| keyset.addKey(createKey({ |
| keyId, |
| outputPrefix: PbOutputPrefixType.TINK, |
| keyTypeUrl: primaryUrl, |
| enabled: true |
| })); |
| keyset.setPrimaryKeyId(keyId); |
| |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| const primitive = new DummyAead(new Uint8Array(Random.randBytes(10))); |
| Registry.registerKeyManager( |
| new DummyKeyManager(primaryUrl, primitive, Aead)); |
| Registry.registerKeyManager(new DummyKeyManager( |
| disabledUrl, new DummyAead(new Uint8Array(Random.randBytes(10))), |
| Aead)); |
| |
| const primitiveSet = await keysetHandle.getPrimitiveSet(Aead); |
| const primary = assertExists(primitiveSet.getPrimary()); |
| expect(primary.getPrimitive()).toBe(primitive); |
| }); |
| |
| it('disabled keys should be ignored', async () => { |
| const enabledRawKeysCount = 10; |
| const enabledUrl = 'enabled_key_type_url'; |
| const disabledUrl = 'disabled_key_type_url'; |
| |
| // Create keyset with both enabled and disabled RAW keys. |
| const keyset = new PbKeyset(); |
| // Add RAW keys with different ids from [1, ENABLED_RAW_KEYS_COUNT]. |
| for (let i = 0; i < enabledRawKeysCount; i++) { |
| keyset.addKey(createKey({ |
| keyId: 1 + i, |
| outputPrefix: PbOutputPrefixType.RAW, |
| keyTypeUrl: enabledUrl, |
| enabled: true |
| })); |
| keyset.addKey(createKey({ |
| keyId: 1 + i, |
| outputPrefix: PbOutputPrefixType.RAW, |
| keyTypeUrl: disabledUrl, |
| enabled: false |
| })); |
| } |
| keyset.setPrimaryKeyId(1); |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| // Register KeyManager (the key manager for enabled keys should be |
| // enough). |
| const primitive = new DummyAead(new Uint8Array(Random.randBytes(10))); |
| Registry.registerKeyManager( |
| new DummyKeyManager(enabledUrl, primitive, Aead)); |
| |
| // Get primitives and get all raw primitives. |
| const primitiveSet = await keysetHandle.getPrimitiveSet(Aead); |
| const rawPrimitives = primitiveSet.getRawPrimitives(); |
| |
| // Should return all enabled RAW primitives and nothing else (disabled |
| // primitives should not be added into primitive set). |
| expect(rawPrimitives.length).toBe(enabledRawKeysCount); |
| |
| // Test that it returns the correct RAW primitives by using getPrimitive. |
| for (let i = 0; i < enabledRawKeysCount; ++i) { |
| expect(rawPrimitives[i].getPrimitive()).toBe(primitive); |
| } |
| }); |
| |
| it('with custom key manager', async () => { |
| // Create keyset handle. |
| const keyTypeUrl = 'some_key_type_url'; |
| const keyId = 1; |
| const key = |
| createKey({keyId, outputPrefix: PbOutputPrefixType.TINK, keyTypeUrl}); |
| |
| const keyset = new PbKeyset(); |
| keyset.addKey(key); |
| keyset.setPrimaryKeyId(keyId); |
| |
| const keysetHandle = new KeysetHandle(keyset); |
| |
| // Register key manager for the given keyType. |
| const primitive = new DummyAead(new Uint8Array(Random.randBytes(10))); |
| Registry.registerKeyManager( |
| new DummyKeyManager(keyTypeUrl, primitive, Aead)); |
| |
| // Use getPrimitives with custom key manager for the keyType. |
| const customPrimitive = |
| new DummyAead(new Uint8Array(Random.randBytes(10))); |
| const customKeyManager = |
| new DummyKeyManager(keyTypeUrl, customPrimitive, Aead); |
| const primitiveSet = |
| await keysetHandle.getPrimitiveSet(Aead, customKeyManager); |
| |
| // Primary should be the entry corresponding to the keyTypeUrl and thus |
| // getPrimitive should return customPrimitive. |
| const primary = assertExists(primitiveSet.getPrimary()); |
| expect(primary.getPrimitive()).toBe(customPrimitive); |
| }); |
| }); |
| |
| describe('readNoSecret', () => { |
| it('throws for keysets containing secret key material', () => { |
| const secretKeyMaterialTypes = [ |
| PbKeyMaterialType.SYMMETRIC, PbKeyMaterialType.ASYMMETRIC_PRIVATE, |
| PbKeyMaterialType.UNKNOWN_KEYMATERIAL |
| ]; |
| for (const secretKeyMaterialType of secretKeyMaterialTypes) { |
| // Create a public keyset. |
| const keyset = new PbKeyset(); |
| for (let i = 0; i < 3; i++) { |
| const key = createKey({ |
| keyId: i + 1, |
| outputPrefix: PbOutputPrefixType.TINK, |
| keyTypeUrl: 'someType', |
| enabled: (i % 4) < 2, |
| keyMaterialType: PbKeyMaterialType.ASYMMETRIC_PUBLIC, |
| }); |
| keyset.addKey(key); |
| } |
| keyset.setPrimaryKeyId(1); |
| const key = createKey({ |
| keyId: 0xFFFFFFFF, |
| outputPrefix: PbOutputPrefixType.RAW, |
| keyTypeUrl: 'someType', |
| enabled: true, |
| keyMaterialType: secretKeyMaterialType, |
| }); |
| keyset.addKey(key); |
| const reader = |
| BinaryKeysetReader.withUint8Array(keyset.serializeBinary()); |
| expect(() => readNoSecret(reader)) |
| .toThrowError( |
| SecurityException, 'Keyset contains secret key material.'); |
| } |
| }); |
| |
| it('returns non-secret keysets', () => { |
| // Create a public keyset. |
| const keyset = new PbKeyset(); |
| for (let i = 0; i < 3; i++) { |
| const key = createKey({ |
| keyId: i + 1, |
| outputPrefix: PbOutputPrefixType.TINK, |
| keyTypeUrl: 'someType', |
| enabled: (i % 4) < 2, |
| keyMaterialType: PbKeyMaterialType.ASYMMETRIC_PUBLIC, |
| }); |
| keyset.addKey(key); |
| } |
| keyset.setPrimaryKeyId(1); |
| |
| const reader = |
| BinaryKeysetReader.withUint8Array(keyset.serializeBinary()); |
| const keysetHandle = readNoSecret(reader); |
| |
| assertMessageEquals(keysetHandle.getKeyset(), keyset); |
| }); |
| }); |
| |
| describe('getPublicKeysetHandle', () => { |
| it('can get a public keyset from a private keyset', async () => { |
| const privateHandle = await generateNew( |
| HybridKeyTemplates.eciesP256HkdfHmacSha256Aes128Gcm()); |
| expect(() => privateHandle.getPublicKeysetHandle()) |
| .not.toThrowError( |
| SecurityException, 'The keyset contains a non-private key'); |
| }); |
| |
| it('can not get a public keyset from another public keyset', async () => { |
| const privateHandle = await generateNew( |
| HybridKeyTemplates.eciesP256HkdfHmacSha256Aes128Gcm()); |
| const publicHandle = privateHandle.getPublicKeysetHandle(); |
| expect(() => publicHandle.getPublicKeysetHandle()) |
| .toThrowError( |
| SecurityException, 'The keyset contains a non-private key'); |
| }); |
| }); |
| |
| describe('writeNoSecret', () => { |
| it('throws if the keyset contains secret keys', async () => { |
| const privateHandle = await generateNew( |
| HybridKeyTemplates.eciesP256HkdfHmacSha256Aes128Gcm()); |
| expect(() => privateHandle.writeNoSecret(new BinaryKeysetWriter())) |
| .toThrowError(SecurityException); |
| }); |
| |
| it('writes bytes if the keyset contains no secret keys', async () => { |
| const privateHandle = await generateNew( |
| HybridKeyTemplates.eciesP256HkdfHmacSha256Aes128Gcm()); |
| const publicHandle = privateHandle.getPublicKeysetHandle(); |
| expect(() => publicHandle.writeNoSecret(new BinaryKeysetWriter())) |
| .not.toThrow(); |
| }); |
| |
| it('can import the keyset using readNoSecret', async () => { |
| const privateHandle = await generateNew( |
| HybridKeyTemplates.eciesP256HkdfHmacSha256Aes128Gcm()); |
| const publicHandle = privateHandle.getPublicKeysetHandle(); |
| const keysetBytes = publicHandle.writeNoSecret(new BinaryKeysetWriter()); |
| |
| const importedHandle = readNoSecret(new BinaryKeysetReader(keysetBytes)); |
| assertMessageEquals(publicHandle.getKeyset(), importedHandle.getKeyset()); |
| }); |
| }); |
| }); |
| |
| /** Function for creating keys for testing purposes. */ |
| function createKey({ |
| keyId, |
| outputPrefix, |
| keyTypeUrl, |
| enabled = true, |
| keyMaterialType = PbKeyMaterialType.SYMMETRIC |
| }: { |
| keyId: number, |
| outputPrefix: PbOutputPrefixType, |
| keyTypeUrl: string, |
| enabled?: boolean, |
| keyMaterialType?: PbKeyMaterialType, |
| }): PbKeysetKey { |
| return new PbKeysetKey() |
| .setStatus(enabled ? PbKeyStatusType.ENABLED : PbKeyStatusType.DISABLED) |
| .setOutputPrefixType(outputPrefix) |
| .setKeyId(keyId) |
| .setKeyData(new PbKeyData() |
| .setTypeUrl(keyTypeUrl) |
| .setValue(new Uint8Array([1])) |
| .setKeyMaterialType(keyMaterialType)); |
| } |
| |
| /** |
| * Function for creating keysets for testing purposes. |
| * Primary has id 1. |
| * |
| * The function also register DummyKeyManager providing primitives for each |
| * keyType added to the Keyset. |
| */ |
| function createKeysetAndInitializeRegistry( |
| primitiveType: Constructor<unknown>, numberOfKeys = 15): PbKeyset { |
| const numberOfKeyTypes = 5; |
| const keyTypePrefix = 'key_type_'; |
| |
| for (let i = 0; i < numberOfKeyTypes; i++) { |
| const typeUrl = keyTypePrefix + i.toString(); |
| let primitive; |
| switch (primitiveType) { |
| case HybridDecrypt: |
| primitive = new DummyHybridDecrypt(new Uint8Array([i])); |
| break; |
| case HybridEncrypt: |
| primitive = new DummyHybridEncrypt(new Uint8Array([i])); |
| break; |
| default: |
| primitive = new DummyAead(new Uint8Array([i])); |
| break; |
| } |
| Registry.registerKeyManager( |
| new DummyKeyManager(typeUrl, primitive, primitiveType)); |
| } |
| |
| const keyset = new PbKeyset(); |
| |
| for (let i = 1; i < numberOfKeys; i++) { |
| const keyTypeUrl = keyTypePrefix + (i % numberOfKeyTypes).toString(); |
| let outputPrefix: PbOutputPrefixType; |
| switch (i % 3) { |
| case 0: |
| outputPrefix = PbOutputPrefixType.TINK; |
| break; |
| case 1: |
| outputPrefix = PbOutputPrefixType.LEGACY; |
| break; |
| default: |
| outputPrefix = PbOutputPrefixType.RAW; |
| } |
| // There are no primitives added to PrimitiveSet for disabled keys, thus |
| // they are quite rarely added into the Keyset. |
| keyset.addKey( |
| createKey({keyId: i, outputPrefix, keyTypeUrl, enabled: i % 7 < 6})); |
| } |
| |
| keyset.setPrimaryKeyId(1); |
| return keyset; |
| } |
| |
| class DummyAead extends Aead { |
| constructor(private readonly ciphertextSuffix: Uint8Array) { |
| super(); |
| } |
| |
| // Encrypt method just append the primitive identifier to plaintext. |
| async encrypt(plaintext: Uint8Array, associatedData?: Uint8Array) { |
| const result = |
| new Uint8Array(plaintext.length + this.ciphertextSuffix.length); |
| result.set(plaintext, 0); |
| result.set(this.ciphertextSuffix, plaintext.length); |
| return result; |
| } |
| |
| // Decrypt method throws an exception whenever ciphertext does not end with |
| // ciphertext suffix, otherwise it returns the first part (without |
| // ciphertext suffix). |
| async decrypt(ciphertext: Uint8Array, associatedData?: Uint8Array) { |
| const plaintext = ciphertext.subarray( |
| 0, ciphertext.length - this.ciphertextSuffix.length); |
| const ciphertextSuffix = ciphertext.subarray( |
| ciphertext.length - this.ciphertextSuffix.length, ciphertext.length); |
| |
| if ([...ciphertextSuffix].toString() !== |
| [...this.ciphertextSuffix].toString()) { |
| throw new SecurityException('Ciphertext decryption failed.'); |
| } |
| |
| return plaintext; |
| } |
| } |
| |
| class DummyMac extends Mac { |
| constructor(private readonly tag: Uint8Array) { |
| super(); |
| } |
| |
| /** |
| * Just appends the tag to the data. |
| */ |
| async computeMac(data: Uint8Array) { |
| return this.tag; |
| } |
| |
| /** |
| * Returns whether data ends with tag. |
| */ |
| async verifyMac(tag: Uint8Array, data: Uint8Array) { |
| return [...tag].toString() === [...this.tag].toString(); |
| } |
| } |
| |
| class DummyHybridEncrypt extends HybridEncrypt { |
| constructor(private readonly ciphertextSuffix: Uint8Array) { |
| super(); |
| } |
| // Async is used here just because real primitives returns Promise. |
| async encrypt(plaintext: Uint8Array, associatedData?: Uint8Array) { |
| return Bytes.concat(plaintext, this.ciphertextSuffix); |
| } |
| } |
| |
| class DummyHybridDecrypt extends HybridDecrypt { |
| constructor(private readonly ciphertextSuffix: Uint8Array) { |
| super(); |
| } |
| |
| async decrypt(ciphertext: Uint8Array, associatedData?: Uint8Array) { |
| const cipherLen = ciphertext.length; |
| const suffixLen = this.ciphertextSuffix.length; |
| const plaintext = ciphertext.subarray(0, cipherLen - suffixLen); |
| const suffix = ciphertext.subarray(cipherLen - suffixLen, cipherLen); |
| if (!Bytes.isEqual(this.ciphertextSuffix, suffix)) { |
| throw new SecurityException('Ciphertext decryption failed.'); |
| } |
| return plaintext; |
| } |
| } |
| |
| class DummyKeyManager<T> implements KeyManager<T> { |
| constructor( |
| private readonly keyType: string, private readonly primitive: T, |
| private readonly primitiveType: Constructor<T>) {} |
| |
| async getPrimitive(primitiveType: Constructor<T>, key: PbKeyData|PbMessage) { |
| if (primitiveType !== this.getPrimitiveType()) { |
| throw new SecurityException( |
| 'Requested primitive type which is not ' + |
| 'supported by this key manager.'); |
| } |
| return this.primitive; |
| } |
| |
| doesSupport(keyType: string) { |
| return keyType === this.getKeyType(); |
| } |
| |
| getKeyType() { |
| return this.keyType; |
| } |
| |
| getPrimitiveType() { |
| return this.primitiveType; |
| } |
| |
| getVersion(): number { |
| throw new SecurityException('Not implemented, function is not needed.'); |
| } |
| |
| getKeyFactory(): KeyFactory { |
| throw new SecurityException('Not implemented, function is not needed.'); |
| } |
| } |