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.
282 lines (252 loc) • 11.1 kB
text/typescript
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import 'jasmine';
import {InvalidArgumentsException} from '../../../exception/invalid_arguments_exception';
import {SecurityException} from '../../../exception/security_exception';
import * as bytes from '../../../subtle/bytes';
import {randBytes} from '../../../subtle/random';
import {AesGcmHpkeAead} from './aes_gcm_hpke_aead';
import {HPKE_BORINGSSL_TEST_VECTORS} from './hpke_boringssl_test_vectors';
import * as hpkeUtil from './hpke_util';
import {parseTestVectors} from './testing/hpke_test_utils';
interface TestVector {
aeadId: Uint8Array;
key: Uint8Array;
encryptions: Array<{
nonce: Uint8Array,
plaintext: Uint8Array,
ciphertext: Uint8Array,
associatedData: Uint8Array,
}>;
}
/**
* Test vectors as described in @see https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A
*/
const TEST_VECTORS: TestVector[] = [
{
aeadId: hpkeUtil.AES_128_GCM_AEAD_ID,
key: bytes.fromHex('868c066ef58aae6dc589b6cfdd18f97e'),
encryptions: [
{
nonce: bytes.fromHex('4e0bc5018beba4bf004cca59'),
plaintext: bytes.fromHex(
'4265617574792069732074727574682c20747275746820626561757479'),
ciphertext: bytes.fromHex(
'5ad590bb8baa577f8619db35a36311226a896e7342a6d836d8b7bcd2f20b6c7f9076ac232e3ab2523f39513434'),
associatedData: bytes.fromHex('436f756e742d30'),
},
{
nonce: bytes.fromHex('4e0bc5018beba4bf004cca58'),
plaintext: bytes.fromHex(
'4265617574792069732074727574682c20747275746820626561757479'),
ciphertext: bytes.fromHex(
'fa6f037b47fc21826b610172ca9637e82d6e5801eb31cbd3748271affd4ecb06646e0329cbdf3c3cd655b28e82'),
associatedData: bytes.fromHex('436f756e742d31'),
},
{
nonce: bytes.fromHex('4e0bc5018beba4bf004cca5b'),
plaintext: bytes.fromHex(
'4265617574792069732074727574682c20747275746820626561757479'),
ciphertext: bytes.fromHex(
'895cabfac50ce6c6eb02ffe6c048bf53b7f7be9a91fc559402cbc5b8dcaeb52b2ccc93e466c28fb55fed7a7fec'),
associatedData: bytes.fromHex('436f756e742d32'),
},
]
},
{
aeadId: hpkeUtil.AES_256_GCM_AEAD_ID,
key: bytes.fromHex(
'751e346ce8f0ddb2305c8a2a85c70d5cf559c53093656be636b9406d4d7d1b70'),
encryptions: [
{
nonce: bytes.fromHex('55ff7a7d739c69f44b25447b'),
plaintext: bytes.fromHex(
'4265617574792069732074727574682c20747275746820626561757479'),
ciphertext: bytes.fromHex(
'170f8beddfe949b75ef9c387e201baf4132fa7374593dfafa90768788b7b2b200aafcc6d80ea4c795a7c5b841a'),
associatedData: bytes.fromHex('436f756e742d30'),
},
{
nonce: bytes.fromHex('55ff7a7d739c69f44b25447a'),
plaintext: bytes.fromHex(
'4265617574792069732074727574682c20747275746820626561757479'),
ciphertext: bytes.fromHex(
'd9ee248e220ca24ac00bbbe7e221a832e4f7fa64c4fbab3945b6f3af0c5ecd5e16815b328be4954a05fd352256'),
associatedData: bytes.fromHex('436f756e742d31'),
},
{
nonce: bytes.fromHex('55ff7a7d739c69f44b254479'),
plaintext: bytes.fromHex(
'4265617574792069732074727574682c20747275746820626561757479'),
ciphertext: bytes.fromHex(
'142cf1e02d1f58d9285f2af7dcfa44f7c3f2d15c73d460c48c6e0e506a3144bae35284e7e221105b61d24e1c7a'),
associatedData: bytes.fromHex('436f756e742d32'),
},
]
},
...parseTestVectors(HPKE_BORINGSSL_TEST_VECTORS)
];
describe('AES-GCM HPKE AEAD', () => {
for (const testInfo of TEST_VECTORS) {
let aeadAlgorithm: string;
let incorrectKeyLength: 16|32;
if (bytes.isEqual(testInfo.aeadId, hpkeUtil.AES_128_GCM_AEAD_ID)) {
aeadAlgorithm = 'AES-128-GCM';
incorrectKeyLength = 32;
} else if (bytes.isEqual(testInfo.aeadId, hpkeUtil.AES_256_GCM_AEAD_ID)) {
aeadAlgorithm = 'AES-256-GCM';
incorrectKeyLength = 16;
} else {
throw new InvalidArgumentsException(
`unsupported AEAD id : ${testInfo.aeadId}`);
}
describe('seal and open', () => {
it(`should work for ${aeadAlgorithm}`, async () => {
const aead = new AesGcmHpkeAead(testInfo.key.length as 16 | 32);
for (const encryption of testInfo.encryptions) {
const ciphertext: Uint8Array = await aead.seal({
key: testInfo.key,
nonce: encryption.nonce,
plaintext: encryption.plaintext,
associatedData: encryption.associatedData,
});
const plaintext: Uint8Array = await aead.open({
key: testInfo.key,
nonce: encryption.nonce,
ciphertext: encryption.ciphertext,
associatedData: encryption.associatedData,
});
expect(ciphertext).toEqual(encryption.ciphertext);
expect(plaintext).toEqual(encryption.plaintext);
}
});
});
describe('seal', () => {
it(`should fail with the wrong key length for ${aeadAlgorithm}`,
async () => {
const aead = new AesGcmHpkeAead(incorrectKeyLength);
await expectAsync(aead.seal({
key: testInfo.key,
nonce: testInfo.encryptions[0].nonce,
plaintext: testInfo.encryptions[0].plaintext,
associatedData: testInfo.encryptions[0].associatedData,
})).toBeRejectedWithError(SecurityException);
});
it(`should generate different ciphertexts for different nonces using ${
aeadAlgorithm}`,
async () => {
const aead = new AesGcmHpkeAead(testInfo.key.length as 16 | 32);
const encryption = testInfo.encryptions[0];
const ciphertexts = new Set<string>();
for (let i = 0; i < 10; i++) {
const nonce: Uint8Array = randBytes(encryption.nonce.length);
const ciphertext: Uint8Array = await aead.seal({
key: testInfo.key,
nonce,
plaintext: encryption.plaintext,
associatedData: encryption.associatedData,
});
const ciphertextString: string = bytes.toHex(ciphertext);
expect(ciphertexts).not.toContain(ciphertextString);
ciphertexts.add(ciphertextString);
}
});
it(`should generate different ciphertexts for different plaintexts using ${
aeadAlgorithm}`,
async () => {
const aead = new AesGcmHpkeAead(testInfo.key.length as 16 | 32);
const encryption = testInfo.encryptions[0];
const ciphertexts = new Set<string>();
for (let i = 0; i < 10; i++) {
const plaintext: Uint8Array =
randBytes(encryption.plaintext.length);
const ciphertext: Uint8Array = await aead.seal({
key: testInfo.key,
nonce: encryption.nonce,
plaintext,
associatedData: encryption.associatedData,
});
const ciphertextString: string = bytes.toHex(ciphertext);
expect(ciphertexts).not.toContain(ciphertextString);
ciphertexts.add(ciphertextString);
}
});
it(`should generate different ciphertexts for different associated data using ${
aeadAlgorithm}`,
async () => {
const aead = new AesGcmHpkeAead(testInfo.key.length as 16 | 32);
const encryption = testInfo.encryptions[0];
const ciphertexts = new Set<string>();
for (let i = 0; i < 10; i++) {
const associatedData: Uint8Array =
randBytes(encryption.associatedData.length);
const ciphertext: Uint8Array = await aead.seal({
key: testInfo.key,
nonce: encryption.nonce,
plaintext: encryption.plaintext,
associatedData,
});
const ciphertextString: string = bytes.toHex(ciphertext);
expect(ciphertexts).not.toContain(ciphertextString);
ciphertexts.add(ciphertextString);
}
});
});
describe('open', () => {
it(`should fail with the wrong key length for ${aeadAlgorithm}`,
async () => {
const aead = new AesGcmHpkeAead(incorrectKeyLength);
await expectAsync(aead.open({
key: testInfo.key,
nonce: testInfo.encryptions[0].nonce,
ciphertext: testInfo.encryptions[0].ciphertext,
associatedData: testInfo.encryptions[0].associatedData,
})).toBeRejectedWithError(SecurityException);
});
for (const encryption of testInfo.encryptions) {
it(`should fail with an invalid key for ${aeadAlgorithm}`, async () => {
const aead = new AesGcmHpkeAead(testInfo.key.length as 16 | 32);
await expectAsync(aead.open({
key: randBytes(testInfo.key.length),
nonce: encryption.nonce,
ciphertext: encryption.ciphertext,
associatedData: encryption.associatedData,
})).toBeRejectedWithError(SecurityException);
});
it(`should fail with an invalid nonce for ${aeadAlgorithm}`,
async () => {
const aead = new AesGcmHpkeAead(testInfo.key.length as 16 | 32);
await expectAsync(aead.open({
key: testInfo.key,
nonce: randBytes(encryption.nonce.length),
ciphertext: encryption.ciphertext,
associatedData: encryption.associatedData,
})).toBeRejectedWithError(SecurityException);
});
it(`should fail with an invalid ciphertext for ${aeadAlgorithm}`,
async () => {
const aead = new AesGcmHpkeAead(testInfo.key.length as 16 | 32);
await expectAsync(aead.open({
key: testInfo.key,
nonce: encryption.nonce,
ciphertext: randBytes(encryption.ciphertext.length),
associatedData: encryption.associatedData,
})).toBeRejectedWithError(SecurityException);
});
it(`should fail with invalid associated data for ${aeadAlgorithm}`,
async () => {
const aead = new AesGcmHpkeAead(testInfo.key.length as 16 | 32);
await expectAsync(aead.open({
key: testInfo.key,
nonce: encryption.nonce,
ciphertext: encryption.ciphertext,
associatedData: randBytes(encryption.associatedData.length),
})).toBeRejectedWithError(SecurityException);
});
}
});
}
});