// OpenPGP.js - An OpenPGP implementation in javascript // Copyright (C) 2018 ProtonTech AG // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 3.0 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA /** * @fileoverview This module implements AES-EAX en/decryption on top of * native AES-CTR using either the WebCrypto API or Node.js' crypto API. * @requires asmcrypto.js * @requires crypto/cmac * @requires util * @module crypto/eax */ import { AES_CTR } from 'asmcrypto.js/src/aes/ctr/exports'; import CMAC from './cmac'; import util from '../util'; const webCrypto = util.getWebCryptoAll(); const nodeCrypto = util.getNodeCrypto(); const Buffer = util.getNodeBuffer(); const blockLength = 16; const ivLength = blockLength; const tagLength = blockLength; const zero = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); const one = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); const two = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]); class OMAC extends CMAC { mac(t, message) { return super.mac(concat(t, message)); } } class CTR { constructor(key) { if (util.getWebCryptoAll() && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support this.key = webCrypto.importKey('raw', key, { name: 'AES-CTR', length: key.length * 8 }, false, ['encrypt']); this.ctr = this.webCtr; } else if (util.getNodeCrypto()) { // Node crypto library this.key = new Buffer(key); this.ctr = this.nodeCtr; } else { // asm.js fallback this.key = key; } } webCtr(pt, iv) { return this.key .then(keyObj => webCrypto.encrypt({ name: 'AES-CTR', counter: iv, length: blockLength * 8 }, keyObj, pt)) .then(ct => new Uint8Array(ct)); } nodeCtr(pt, iv) { pt = new Buffer(pt); iv = new Buffer(iv); const en = new nodeCrypto.createCipheriv('aes-' + (this.key.length * 8) + '-ctr', this.key, iv); const ct = Buffer.concat([en.update(pt), en.final()]); return Promise.resolve(new Uint8Array(ct)); } ctr(pt, iv) { return Promise.resolve(AES_CTR.encrypt(pt, this.key, iv)); } } class EAX { /** * Class to en/decrypt using EAX mode. * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' * @param {Uint8Array} key The encryption key */ constructor(cipher, key) { if (cipher.substr(0, 3) !== 'aes') { throw new Error('EAX mode supports only AES cipher'); } const omac = new OMAC(key); this.omac = omac.mac.bind(omac); const ctr = new CTR(key); this.ctr = ctr.ctr.bind(ctr); } /** * Encrypt plaintext input. * @param {Uint8Array} plaintext The cleartext input to be encrypted * @param {Uint8Array} nonce The nonce (16 bytes) * @param {Uint8Array} adata Associated data to sign * @returns {Promise} The ciphertext output */ async encrypt(plaintext, nonce, adata) { const _nonce = this.omac(zero, nonce); const _adata = this.omac(one, adata); const ciphered = await this.ctr(plaintext, _nonce); const _ciphered = this.omac(two, ciphered); const tag = xor3(_nonce, _ciphered, _adata); // Assumes that omac(*).length === tagLength. return concat(ciphered, tag); } /** * Decrypt ciphertext input. * @param {Uint8Array} ciphertext The ciphertext input to be decrypted * @param {Uint8Array} nonce The nonce (16 bytes) * @param {Uint8Array} adata Associated data to verify * @returns {Promise} The plaintext output */ async decrypt(ciphertext, nonce, adata) { if (ciphertext.length < tagLength) throw new Error('Invalid EAX ciphertext'); const ciphered = ciphertext.subarray(0, ciphertext.length - tagLength); const tag = ciphertext.subarray(ciphertext.length - tagLength); const _nonce = this.omac(zero, nonce); const _adata = this.omac(one, adata); const _ciphered = this.omac(two, ciphered); const _tag = xor3(_nonce, _ciphered, _adata); // Assumes that omac(*).length === tagLength. if (!util.equalsUint8Array(tag, _tag)) throw new Error('Authentication tag mismatch in EAX ciphertext'); const plaintext = await this.ctr(ciphered, _nonce); return plaintext; } } /** * Get EAX nonce as defined by {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.16.1|RFC4880bis-04, section 5.16.1}. * @param {Uint8Array} iv The initialization vector (16 bytes) * @param {Uint8Array} chunkIndex The chunk index (8 bytes) */ EAX.getNonce = function(iv, chunkIndex) { const nonce = iv.slice(); for (let i = 0; i < chunkIndex.length; i++) { nonce[8 + i] ^= chunkIndex[i]; } return nonce; }; EAX.blockLength = blockLength; EAX.ivLength = ivLength; export default EAX; ////////////////////////// // // // Helper functions // // // ////////////////////////// function xor3(a, b, c) { return a.map((n, i) => n ^ b[i] ^ c[i]); } function concat(...arrays) { return util.concatUint8Array(arrays); }