159 lines
5.5 KiB
JavaScript
159 lines
5.5 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
|
|
* @private
|
|
*/
|
|
|
|
import { AES_CFB } from '@openpgp/asmcrypto.js/dist_es8/aes/cfb';
|
|
|
|
import stream from '@openpgp/web-stream-tools';
|
|
import * as cipher from '../cipher';
|
|
import util from '../../util';
|
|
|
|
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 */
|
|
};
|
|
|
|
export async function encrypt(algo, key, plaintext, iv, config) {
|
|
if (util.getNodeCrypto() && nodeAlgos[algo]) { // Node crypto library.
|
|
return nodeEncrypt(algo, key, plaintext, iv);
|
|
}
|
|
if (algo.substr(0, 3) === 'aes') {
|
|
return aesEncrypt(algo, key, plaintext, iv, config);
|
|
}
|
|
|
|
const cipherfn = new cipher[algo](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);
|
|
}
|
|
|
|
export async function decrypt(algo, key, ciphertext, iv) {
|
|
if (util.getNodeCrypto() && nodeAlgos[algo]) { // Node crypto library.
|
|
return nodeDecrypt(algo, key, ciphertext, iv);
|
|
}
|
|
if (algo.substr(0, 3) === 'aes') {
|
|
return aesDecrypt(algo, key, ciphertext, iv);
|
|
}
|
|
|
|
const cipherfn = new cipher[algo](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;
|
|
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);
|
|
}
|
|
|
|
function aesEncrypt(algo, key, pt, iv, config) {
|
|
if (
|
|
util.getWebCrypto() &&
|
|
key.length !== 24 && // Chrome doesn't support 192 bit keys, see https://www.chromium.org/blink/webcrypto#TOC-AES-support
|
|
!util.isStream(pt) &&
|
|
pt.length >= 3000 * config.minBytesForWebCrypto // Default to a 3MB minimum. Chrome is pretty slow for small messages, see: https://bugs.chromium.org/p/chromium/issues/detail?id=701188#c2
|
|
) { // Web Crypto
|
|
return webEncrypt(algo, key, pt, iv);
|
|
}
|
|
// asm.js fallback
|
|
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) {
|
|
for (let i = 0; i < a.length; i++) {
|
|
a[i] = a[i] ^ b[i];
|
|
}
|
|
}
|
|
|
|
async function webEncrypt(algo, key, pt, iv) {
|
|
const ALGO = 'AES-CBC';
|
|
const _key = await webCrypto.importKey('raw', key, { name: ALGO }, false, ['encrypt']);
|
|
const { blockSize } = cipher[algo];
|
|
const cbc_pt = util.concatUint8Array([new Uint8Array(blockSize), pt]);
|
|
const ct = new Uint8Array(await webCrypto.encrypt({ name: ALGO, iv }, _key, cbc_pt)).subarray(0, pt.length);
|
|
xorMut(ct, pt);
|
|
return ct;
|
|
}
|
|
|
|
function nodeEncrypt(algo, key, pt, iv) {
|
|
const cipherObj = new nodeCrypto.createCipheriv(nodeAlgos[algo], key, iv);
|
|
return stream.transform(pt, value => new Uint8Array(cipherObj.update(value)));
|
|
}
|
|
|
|
function nodeDecrypt(algo, key, ct, iv) {
|
|
const decipherObj = new nodeCrypto.createDecipheriv(nodeAlgos[algo], key, iv);
|
|
return stream.transform(ct, value => new Uint8Array(decipherObj.update(value)));
|
|
}
|