larabr a6283e64cc Drop internal cipher/aes module
The module was barely used, and its presence confusing, since
WebCrypto or asmcrypto are often directly used and usable instead.
Also, use AES_CBC instead of AES_ECB for single-block encryption,
so that we can drop support for the latter in the asmcrypto lib.
2024-02-27 14:43:24 +01:00

276 lines
9.2 KiB
JavaScript

// Modified by ProtonTech AG
// Modified by Recurity Labs GmbH
// modified version of https://www.hanewin.net/encrypt/PGdecode.js:
/* OpenPGP encryption using RSA/AES
* Copyright 2005-2006 Herbert Hanewinkel, www.haneWIN.de
* version 2.0, check www.haneWIN.de for the latest version
* This software is provided as-is, without express or implied warranty.
* Permission to use, copy, modify, distribute or sell this software, with or
* without fee, for any purpose and by any individual or organization, is hereby
* granted, provided that the above copyright notice and this paragraph appear
* in all copies. Distribution as a part of an application or binary must
* include the above copyright notice in the documentation and/or other
* materials provided with the application or distribution.
*/
/**
* @module crypto/mode/cfb
*/
import { AES_CFB } from '@openpgp/asmcrypto.js/aes/cfb.js';
import * as stream from '@openpgp/web-stream-tools';
import util from '../../util';
import enums from '../../enums';
import { getLegacyCipher, getCipherParams } from '../cipher';
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
const knownAlgos = nodeCrypto ? nodeCrypto.getCiphers() : [];
const nodeAlgos = {
idea: knownAlgos.includes('idea-cfb') ? 'idea-cfb' : undefined, /* Unused, not implemented */
tripledes: knownAlgos.includes('des-ede3-cfb') ? 'des-ede3-cfb' : undefined,
cast5: knownAlgos.includes('cast5-cfb') ? 'cast5-cfb' : undefined,
blowfish: knownAlgos.includes('bf-cfb') ? 'bf-cfb' : undefined,
aes128: knownAlgos.includes('aes-128-cfb') ? 'aes-128-cfb' : undefined,
aes192: knownAlgos.includes('aes-192-cfb') ? 'aes-192-cfb' : undefined,
aes256: knownAlgos.includes('aes-256-cfb') ? 'aes-256-cfb' : undefined
/* twofish is not implemented in OpenSSL */
};
/**
* CFB encryption
* @param {enums.symmetric} algo - block cipher algorithm
* @param {Uint8Array} key
* @param {MaybeStream<Uint8Array>} plaintext
* @param {Uint8Array} iv
* @param {Object} config - full configuration, defaults to openpgp.config
* @returns MaybeStream<Uint8Array>
*/
export async function encrypt(algo, key, plaintext, iv, config) {
const algoName = enums.read(enums.symmetric, algo);
if (util.getNodeCrypto() && nodeAlgos[algoName]) { // Node crypto library.
return nodeEncrypt(algo, key, plaintext, iv);
}
if (util.isAES(algo)) {
return aesEncrypt(algo, key, plaintext, iv, config);
}
const LegacyCipher = await getLegacyCipher(algo);
const cipherfn = new LegacyCipher(key);
const block_size = cipherfn.blockSize;
const blockc = iv.slice();
let pt = new Uint8Array();
const process = chunk => {
if (chunk) {
pt = util.concatUint8Array([pt, chunk]);
}
const ciphertext = new Uint8Array(pt.length);
let i;
let j = 0;
while (chunk ? pt.length >= block_size : pt.length) {
const encblock = cipherfn.encrypt(blockc);
for (i = 0; i < block_size; i++) {
blockc[i] = pt[i] ^ encblock[i];
ciphertext[j++] = blockc[i];
}
pt = pt.subarray(block_size);
}
return ciphertext.subarray(0, j);
};
return stream.transform(plaintext, process, process);
}
/**
* CFB decryption
* @param {enums.symmetric} algo - block cipher algorithm
* @param {Uint8Array} key
* @param {MaybeStream<Uint8Array>} ciphertext
* @param {Uint8Array} iv
* @returns MaybeStream<Uint8Array>
*/
export async function decrypt(algo, key, ciphertext, iv) {
const algoName = enums.read(enums.symmetric, algo);
if (nodeCrypto && nodeAlgos[algoName]) { // Node crypto library.
return nodeDecrypt(algo, key, ciphertext, iv);
}
if (util.isAES(algo)) {
return aesDecrypt(algo, key, ciphertext, iv);
}
const LegacyCipher = await getLegacyCipher(algo);
const cipherfn = new LegacyCipher(key);
const block_size = cipherfn.blockSize;
let blockp = iv;
let ct = new Uint8Array();
const process = chunk => {
if (chunk) {
ct = util.concatUint8Array([ct, chunk]);
}
const plaintext = new Uint8Array(ct.length);
let i;
let j = 0;
while (chunk ? ct.length >= block_size : ct.length) {
const decblock = cipherfn.encrypt(blockp);
blockp = ct.subarray(0, block_size);
for (i = 0; i < block_size; i++) {
plaintext[j++] = blockp[i] ^ decblock[i];
}
ct = ct.subarray(block_size);
}
return plaintext.subarray(0, j);
};
return stream.transform(ciphertext, process, process);
}
class WebCryptoEncryptor {
constructor(algo, key, iv) {
const { blockSize } = getCipherParams(algo);
this.key = key;
this.prevBlock = iv;
this.nextBlock = new Uint8Array(blockSize);
this.i = 0; // pointer inside next block
this.blockSize = blockSize;
this.zeroBlock = new Uint8Array(this.blockSize);
}
static async isSupported(algo) {
const { keySize } = getCipherParams(algo);
return webCrypto.importKey('raw', new Uint8Array(keySize), 'aes-cbc', false, ['encrypt'])
.then(() => true, () => false);
}
async _runCBC(plaintext, nonZeroIV) {
const mode = 'AES-CBC';
this.keyRef = this.keyRef || await webCrypto.importKey('raw', this.key, mode, false, ['encrypt']);
const ciphertext = await webCrypto.encrypt(
{ name: mode, iv: nonZeroIV || this.zeroBlock },
this.keyRef,
plaintext
);
return new Uint8Array(ciphertext).subarray(0, plaintext.length);
}
async encryptChunk(value) {
const missing = this.nextBlock.length - this.i;
const added = value.subarray(0, missing);
this.nextBlock.set(added, this.i);
if ((this.i + value.length) >= (2 * this.blockSize)) {
const leftover = (value.length - missing) % this.blockSize;
const plaintext = util.concatUint8Array([
this.nextBlock,
value.subarray(missing, value.length - leftover)
]);
const toEncrypt = util.concatUint8Array([
this.prevBlock,
plaintext.subarray(0, plaintext.length - this.blockSize) // stop one block "early", since we only need to xor the plaintext and pass it over as prevBlock
]);
const encryptedBlocks = await this._runCBC(toEncrypt);
xorMut(encryptedBlocks, plaintext);
this.prevBlock = encryptedBlocks.subarray(-this.blockSize).slice();
// take care of leftover data
if (leftover > 0) this.nextBlock.set(value.subarray(-leftover).slice());
this.i = leftover;
return encryptedBlocks;
}
this.i += added.length;
let encryptedBlock = new Uint8Array();
if (this.i === this.nextBlock.length) { // block ready to be encrypted
const curBlock = this.nextBlock;
encryptedBlock = await this._runCBC(this.prevBlock);
xorMut(encryptedBlock, curBlock);
this.prevBlock = encryptedBlock.slice();
this.i = 0;
const remaining = value.subarray(added.length);
this.nextBlock.set(remaining, this.i);
this.i += remaining.length;
}
return encryptedBlock;
}
async finish() {
let result;
if (this.i === 0) { // nothing more to encrypt
result = new Uint8Array();
} else {
this.nextBlock = this.nextBlock.subarray(0, this.i);
const curBlock = this.nextBlock;
const encryptedBlock = await this._runCBC(this.prevBlock);
xorMut(encryptedBlock, curBlock);
result = encryptedBlock.subarray(0, curBlock.length);
}
this.clearSensitiveData();
return result;
}
clearSensitiveData() {
this.nextBlock.fill(0);
this.prevBlock.fill(0);
this.keyRef = null;
this.key = null;
}
async encrypt(plaintext) {
// plaintext is internally padded to block length before encryption
const encryptedWithPadding = await this._runCBC(
util.concatUint8Array([new Uint8Array(this.blockSize), plaintext]),
this.iv
);
// drop encrypted padding
const ct = encryptedWithPadding.subarray(0, plaintext.length);
xorMut(ct, plaintext);
this.clearSensitiveData();
return ct;
}
}
async function aesEncrypt(algo, key, pt, iv) {
if (webCrypto && await WebCryptoEncryptor.isSupported(algo)) { // Chromium does not implement AES with 192-bit keys
const cfb = new WebCryptoEncryptor(algo, key, iv);
return util.isStream(pt) ? stream.transform(pt, value => cfb.encryptChunk(value), () => cfb.finish()) : cfb.encrypt(pt);
} else {
const cfb = new AES_CFB(key, iv);
return stream.transform(pt, value => cfb.aes.AES_Encrypt_process(value), () => cfb.aes.AES_Encrypt_finish());
}
}
function aesDecrypt(algo, key, ct, iv) {
if (util.isStream(ct)) {
const cfb = new AES_CFB(key, iv);
return stream.transform(ct, value => cfb.aes.AES_Decrypt_process(value), () => cfb.aes.AES_Decrypt_finish());
}
return AES_CFB.decrypt(ct, key, iv);
}
function xorMut(a, b) {
const aLength = Math.min(a.length, b.length);
for (let i = 0; i < aLength; i++) {
a[i] = a[i] ^ b[i];
}
}
function nodeEncrypt(algo, key, pt, iv) {
const algoName = enums.read(enums.symmetric, algo);
const cipherObj = new nodeCrypto.createCipheriv(nodeAlgos[algoName], key, iv);
return stream.transform(pt, value => new Uint8Array(cipherObj.update(value)));
}
function nodeDecrypt(algo, key, ct, iv) {
const algoName = enums.read(enums.symmetric, algo);
const decipherObj = new nodeCrypto.createDecipheriv(nodeAlgos[algoName], key, iv);
return stream.transform(ct, value => new Uint8Array(decipherObj.update(value)));
}