| /** |
| * @license |
| * Copyright 2020 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| import {Aead} from '../aead/internal/aead'; |
| import {SecurityException} from '../exception/security_exception'; |
| import {Mac} from '../mac/internal/mac'; |
| |
| import * as aesCtr from './aes_ctr'; |
| import * as Bytes from './bytes'; |
| import * as hmac from './hmac'; |
| import {IndCpaCipher} from './ind_cpa_cipher'; |
| import * as Validators from './validators'; |
| |
| /** |
| * This primitive performs an encrypt-then-Mac operation on plaintext and |
| * additional authenticated data (aad). |
| * |
| * The Mac is computed over `aad || ciphertext || size of aad`, thus it |
| * doesn't violate https://en.wikipedia.org/wiki/Horton_Principle. |
| * |
| * This implementation is based on |
| * http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05. |
| * |
| * @final |
| */ |
| export class EncryptThenAuthenticate extends Aead { |
| /** |
| * @param ivSize the IV size in bytes |
| * @param tagSize the MAC tag size in bytes |
| * @throws {InvalidArgumentsException} |
| */ |
| constructor( |
| private readonly cipher: IndCpaCipher, private readonly ivSize: number, |
| private readonly mac: Mac, private readonly tagSize: number) { |
| super(); |
| } |
| |
| /** |
| * The plaintext is encrypted with an {@link IndCpaCipher}, then MAC |
| * is computed over `aad || ciphertext || t` where t is aad's length in bits |
| * represented as 64-bit bigendian unsigned integer. The final ciphertext |
| * format is `ind-cpa ciphertext || mac`. |
| * |
| * @override |
| */ |
| async encrypt(plaintext: Uint8Array, associatedData = new Uint8Array(0)): |
| Promise<Uint8Array> { |
| Validators.requireUint8Array(plaintext); |
| const payload = await this.cipher.encrypt(plaintext); |
| Validators.requireUint8Array(associatedData); |
| const aadLength = Bytes.fromNumber(associatedData.length * 8); |
| const mac = await this.mac.computeMac( |
| Bytes.concat(associatedData, payload, aadLength)); |
| if (this.tagSize != mac.length) { |
| throw new SecurityException( |
| 'invalid tag size, expected ' + this.tagSize + ' but got ' + |
| mac.length); |
| } |
| return Bytes.concat(payload, mac); |
| } |
| |
| /** |
| * @override |
| */ |
| async decrypt(ciphertext: Uint8Array, associatedData = new Uint8Array(0)): |
| Promise<Uint8Array> { |
| Validators.requireUint8Array(ciphertext); |
| if (ciphertext.length < this.ivSize + this.tagSize) { |
| throw new SecurityException('ciphertext too short'); |
| } |
| const payload = new Uint8Array( |
| ciphertext.subarray(0, ciphertext.length - this.tagSize)); |
| Validators.requireUint8Array(associatedData); |
| const aadLength = Bytes.fromNumber(associatedData.length * 8); |
| const input = Bytes.concat(associatedData, payload, aadLength); |
| const tag = new Uint8Array(ciphertext.subarray(payload.length)); |
| const isValidMac = await this.mac.verifyMac(tag, input); |
| if (!isValidMac) { |
| throw new SecurityException('invalid MAC'); |
| } |
| return this.cipher.decrypt(payload); |
| } |
| } |
| |
| /** |
| * @param ivSize the size of the IV |
| * @param hmacHashAlgo accepted names are SHA-1, SHA-256 and SHA-512 |
| * @param tagSize the size of the tag |
| * @throws {InvalidArgumentsException} |
| * @static |
| */ |
| export async function aesCtrHmacFromRawKeys( |
| aesKey: Uint8Array, ivSize: number, hmacHashAlgo: string, |
| hmacKey: Uint8Array, tagSize: number): Promise<EncryptThenAuthenticate> { |
| Validators.requireUint8Array(aesKey); |
| Validators.requireUint8Array(hmacKey); |
| const cipher = await aesCtr.fromRawKey(aesKey, ivSize); |
| const mac = await hmac.fromRawKey(hmacHashAlgo, hmacKey, tagSize); |
| return new EncryptThenAuthenticate(cipher, ivSize, mac, tagSize); |
| } |