blob: 191e5157c23d34301f4fe74e0f43c13b6abd015a [file] [log] [blame]
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {SecurityException} from '../exception/security_exception';
import {CryptoFormat} from '../internal/crypto_format';
import * as PrimitiveSet from '../internal/primitive_set';
import {PrimitiveWrapper} from '../internal/primitive_wrapper';
import {PbKeyStatusType} from '../internal/proto';
import * as Registry from '../internal/registry';
import {Constructor} from '../internal/util';
import {Aead} from './internal/aead';
/**
* @final
*/
class WrappedAead extends Aead {
// The constructor should be @private, but it is not supported by Closure
// (see https://github.com/google/closure-compiler/issues/2761).
constructor(private readonly aeadSet: PrimitiveSet.PrimitiveSet<Aead>) {
super();
}
static newAead(aeadSet: PrimitiveSet.PrimitiveSet<Aead>): Aead {
if (!aeadSet) {
throw new SecurityException('Primitive set has to be non-null.');
}
if (!aeadSet.getPrimary()) {
throw new SecurityException('Primary has to be non-null.');
}
return new WrappedAead(aeadSet);
}
/**
*/
async encrypt(plaintext: Uint8Array, opt_associatedData?: Uint8Array|null):
Promise<Uint8Array> {
if (!plaintext) {
throw new SecurityException('Plaintext has to be non-null.');
}
const primary = this.aeadSet.getPrimary()
if (!primary) {
throw new SecurityException('Primary has to be non-null.');
}
const primitive = primary.getPrimitive();
const encryptedText =
await primitive.encrypt(plaintext, opt_associatedData);
const keyId = primary.getIdentifier();
const ciphertext = new Uint8Array(keyId.length + encryptedText.length);
ciphertext.set(keyId, 0);
ciphertext.set(encryptedText, keyId.length);
return ciphertext;
}
/**
*/
async decrypt(ciphertext: Uint8Array, opt_associatedData?: Uint8Array|null):
Promise<Uint8Array> {
if (!ciphertext) {
throw new SecurityException('Ciphertext has to be non-null.');
}
if (ciphertext.length > CryptoFormat.NON_RAW_PREFIX_SIZE) {
const keyId = ciphertext.subarray(0, CryptoFormat.NON_RAW_PREFIX_SIZE);
const entries = await this.aeadSet.getPrimitives(keyId);
const rawCiphertext = ciphertext.subarray(
CryptoFormat.NON_RAW_PREFIX_SIZE, ciphertext.length);
let decryptedText: Uint8Array|undefined;
try {
decryptedText = await this.tryDecryption(
entries, rawCiphertext, opt_associatedData);
} catch (e) {
}
if (decryptedText) {
return decryptedText;
}
}
const entries = await this.aeadSet.getRawPrimitives();
const decryptedText =
await this.tryDecryption(entries, ciphertext, opt_associatedData);
return decryptedText;
}
/**
* Tries to decrypt the ciphertext using each entry in entriesArray and
* returns the ciphertext decrypted by first primitive which succeed. It
* throws an exception if no entry succeeds.
*
*
*/
private async tryDecryption(
entriesArray: Array<PrimitiveSet.Entry<Aead>>, ciphertext: Uint8Array,
opt_associatedData?: Uint8Array|null): Promise<Uint8Array> {
const entriesArrayLength = entriesArray.length;
for (let i = 0; i < entriesArrayLength; i++) {
if (entriesArray[i].getKeyStatus() != PbKeyStatusType.ENABLED) {
continue;
}
const primitive = entriesArray[i].getPrimitive();
let decryptionResult;
try {
decryptionResult =
await primitive.decrypt(ciphertext, opt_associatedData);
} catch (e) {
continue;
}
return decryptionResult;
}
throw new SecurityException('Decryption failed for the given ciphertext.');
}
}
export class AeadWrapper implements PrimitiveWrapper<Aead> {
/**
*/
wrap(primitiveSet: PrimitiveSet.PrimitiveSet<Aead>): Aead {
return WrappedAead.newAead(primitiveSet);
}
/**
*/
getPrimitiveType(): Constructor<Aead> {
return Aead;
}
static register() {
Registry.registerPrimitiveWrapper(new AeadWrapper());
}
}