Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace strings with integer algorithm identifiers in packet classes #1410

Merged
merged 13 commits into from Nov 22, 2021
Merged
6 changes: 3 additions & 3 deletions openpgp.d.ts
Expand Up @@ -359,7 +359,7 @@ declare abstract class BasePacket {
* - A Subkey Packet cannot always be used when a Primary Key Packet is expected (and vice versa).
*/
declare abstract class BasePublicKeyPacket extends BasePacket {
public algorithm: enums.publicKeyNames;
public algorithm: enums.publicKey;
public created: Date;
public version: number;
public getAlgorithmInfo(): AlgorithmInfo;
Expand Down Expand Up @@ -417,8 +417,8 @@ export class SymEncryptedIntegrityProtectedDataPacket extends BasePacket {

export class AEADEncryptedDataPacket extends BasePacket {
static readonly tag: enums.packet.aeadEncryptedData;
private decrypt(sessionKeyAlgorithm: string, sessionKey: Uint8Array, config?: Config): void;
private encrypt(sessionKeyAlgorithm: string, sessionKey: Uint8Array, config?: Config): void;
private decrypt(sessionKeyAlgorithm: enums.symmetric, sessionKey: Uint8Array, config?: Config): void;
private encrypt(sessionKeyAlgorithm: enums.symmetric, sessionKey: Uint8Array, config?: Config): void;
private crypt(fn: Function, sessionKey: Uint8Array, data: MaybeStream<Uint8Array>): MaybeStream<Uint8Array>
}

Expand Down
5 changes: 4 additions & 1 deletion src/crypto/cipher/aes.js
@@ -1,6 +1,9 @@
import { AES_ECB } from '@openpgp/asmcrypto.js/dist_es8/aes/ecb';

// TODO use webCrypto or nodeCrypto when possible.
/**
* Javascript AES implementation.
* This is used as fallback if the native Crypto APIs are not available.
*/
function aes(length) {
const C = function(key) {
const aesECB = new AES_ECB(key);
Expand Down
29 changes: 27 additions & 2 deletions src/crypto/crypto.js
Expand Up @@ -26,6 +26,7 @@

import publicKey from './public_key';
import * as cipher from './cipher';
import mode from './mode';
import { getRandomBytes } from './random';
import ECDHSymkey from '../type/ecdh_symkey';
import KDFParams from '../type/kdf_params';
Expand Down Expand Up @@ -348,7 +349,8 @@ export async function validateParams(algo, publicParams, privateParams) {
* @async
*/
export async function getPrefixRandom(algo) {
const prefixrandom = await getRandomBytes(cipher[algo].blockSize);
const { blockSize } = getCipher(algo);
const prefixrandom = await getRandomBytes(blockSize);
const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]);
return util.concat([prefixrandom, repeat]);
}
Expand All @@ -361,5 +363,28 @@ export async function getPrefixRandom(algo) {
* @async
*/
export function generateSessionKey(algo) {
return getRandomBytes(cipher[algo].keySize);
const { keySize } = getCipher(algo);
return getRandomBytes(keySize);
}

/**
* Get implementation of the given AEAD mode
* @param {enums.aead} algo
* @returns {Object}
* @throws {Error} on invalid algo
*/
export function getAEADMode(algo) {
const algoName = enums.read(enums.aead, algo);
return mode[algoName];
}

/**
* Get implementation of the given cipher
* @param {enums.symmetric} algo
* @returns {Object}
* @throws {Error} on invalid algo
*/
export function getCipher(algo) {
const algoName = enums.read(enums.symmetric, algo);
return cipher[algoName];
}
15 changes: 8 additions & 7 deletions src/crypto/hash/index.js
Expand Up @@ -16,6 +16,7 @@ import * as stream from '@openpgp/web-stream-tools';
import md5 from './md5';
import util from '../../util';
import defaultConfig from '../../config';
import enums from '../../enums';

const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
Expand Down Expand Up @@ -143,18 +144,18 @@ export default {
*/
getHashByteLength: function(algo) {
switch (algo) {
case 1: // - MD5 [HAC]
case enums.hash.md5: // - MD5 [HAC]
larabr marked this conversation as resolved.
Show resolved Hide resolved
return 16;
case 2: // - SHA-1 [FIPS180]
case 3: // - RIPE-MD/160 [HAC]
case enums.hash.sha1: // - SHA-1 [FIPS180]
case enums.hash.ripemd: // - RIPE-MD/160 [HAC]
return 20;
case 8: // - SHA256 [FIPS180]
case enums.hash.sha256: // - SHA256 [FIPS180]
return 32;
case 9: // - SHA384 [FIPS180]
case enums.hash.sha384: // - SHA384 [FIPS180]
return 48;
case 10: // - SHA512 [FIPS180]
case enums.hash.sha512: // - SHA512 [FIPS180]
return 64;
case 11: // - SHA224 [FIPS180]
case enums.hash.sha224: // - SHA224 [FIPS180]
return 28;
default:
throw new Error('Invalid hash algorithm.');
Expand Down
40 changes: 31 additions & 9 deletions src/crypto/mode/cfb.js
Expand Up @@ -27,6 +27,7 @@ import { AES_CFB } from '@openpgp/asmcrypto.js/dist_es8/aes/cfb';
import * as stream from '@openpgp/web-stream-tools';
import * as cipher from '../cipher';
import util from '../../util';
import enums from '../../enums';

const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
Expand All @@ -43,15 +44,25 @@ const nodeAlgos = {
/* 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) {
if (util.getNodeCrypto() && nodeAlgos[algo]) { // Node crypto library.
const algoName = enums.read(enums.symmetric, algo);
if (util.getNodeCrypto() && nodeAlgos[algoName]) { // Node crypto library.
return nodeEncrypt(algo, key, plaintext, iv);
}
if (algo.substr(0, 3) === 'aes') {
if (algoName.substr(0, 3) === 'aes') {
return aesEncrypt(algo, key, plaintext, iv, config);
}

const cipherfn = new cipher[algo](key);
const cipherfn = new cipher[algoName](key);
const block_size = cipherfn.blockSize;

const blockc = iv.slice();
Expand All @@ -76,15 +87,24 @@ export async function encrypt(algo, key, plaintext, iv, config) {
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) {
if (util.getNodeCrypto() && nodeAlgos[algo]) { // Node crypto library.
const algoName = enums.read(enums.symmetric, algo);
if (util.getNodeCrypto() && nodeAlgos[algoName]) { // Node crypto library.
return nodeDecrypt(algo, key, ciphertext, iv);
}
if (algo.substr(0, 3) === 'aes') {
if (algoName.substr(0, 3) === 'aes') {
return aesDecrypt(algo, key, ciphertext, iv);
}

const cipherfn = new cipher[algo](key);
const cipherfn = new cipher[algoName](key);
const block_size = cipherfn.blockSize;

let blockp = iv;
Expand Down Expand Up @@ -140,19 +160,21 @@ function xorMut(a, b) {
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 { blockSize } = crypto.getCipher(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);
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 decipherObj = new nodeCrypto.createDecipheriv(nodeAlgos[algo], key, 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)));
}
7 changes: 5 additions & 2 deletions src/crypto/mode/eax.js
Expand Up @@ -25,6 +25,7 @@
import { AES_CTR } from '@openpgp/asmcrypto.js/dist_es8/aes/ctr';
import CMAC from '../cmac';
import util from '../../util';
import enums from '../../enums';

const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
Expand Down Expand Up @@ -74,11 +75,13 @@ async function CTR(key) {

/**
* Class to en/decrypt using EAX mode.
* @param {String} cipher - The symmetric cipher algorithm to use e.g. 'aes128'
* @param {enums.symmetric} cipher - The symmetric cipher algorithm to use
* @param {Uint8Array} key - The encryption key
*/
async function EAX(cipher, key) {
if (cipher.substr(0, 3) !== 'aes') {
if (cipher !== enums.symmetric.aes128 &&
cipher !== enums.symmetric.aes192 &&
cipher !== enums.symmetric.aes256) {
throw new Error('EAX mode supports only AES cipher');
}

Expand Down
9 changes: 6 additions & 3 deletions src/crypto/mode/gcm.js
Expand Up @@ -24,6 +24,7 @@

import { AES_GCM } from '@openpgp/asmcrypto.js/dist_es8/aes/gcm';
import util from '../../util';
import enums from '../../enums';

const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
Expand All @@ -36,12 +37,14 @@ const ALGO = 'AES-GCM';

/**
* Class to en/decrypt using GCM mode.
* @param {String} cipher - The symmetric cipher algorithm to use e.g. 'aes128'
* @param {enums.symmetric} cipher - The symmetric cipher algorithm to use
* @param {Uint8Array} key - The encryption key
*/
async function GCM(cipher, key) {
if (cipher.substr(0, 3) !== 'aes') {
throw new Error('GCM mode supports only AES cipher');
if (cipher !== enums.symmetric.aes128 &&
cipher !== enums.symmetric.aes192 &&
cipher !== enums.symmetric.aes256) {
throw new Error('EAX mode supports only AES cipher');
larabr marked this conversation as resolved.
Show resolved Hide resolved
}

if (util.getWebCrypto() && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support
Expand Down
7 changes: 4 additions & 3 deletions src/crypto/mode/ocb.js
Expand Up @@ -23,7 +23,7 @@

import * as ciphers from '../cipher';
import util from '../../util';

import enums from '../../enums';

const blockLength = 16;
const ivLength = 15;
Expand Down Expand Up @@ -59,7 +59,7 @@ const one = new Uint8Array([1]);

/**
* Class to en/decrypt using OCB mode.
* @param {String} cipher - The symmetric cipher algorithm to use e.g. 'aes128'
* @param {enums.symmetric} cipher - The symmetric cipher algorithm to use
* @param {Uint8Array} key - The encryption key
*/
async function OCB(cipher, key) {
Expand All @@ -72,7 +72,8 @@ async function OCB(cipher, key) {
constructKeyVariables(cipher, key);

function constructKeyVariables(cipher, key) {
const aes = new ciphers[cipher](key);
const cipherName = enums.read(enums.symmetric, cipher);
const aes = new ciphers[cipherName](key);
encipher = aes.encrypt.bind(aes);
decipher = aes.decrypt.bind(aes);

Expand Down
5 changes: 5 additions & 0 deletions src/crypto/public_key/elliptic/curves.js
Expand Up @@ -220,6 +220,11 @@ async function generate(curve) {
};
}

/**
* Get preferred hash algo to use with the given curve
* @param {module:type/oid} oid - curve oid
* @returns {enums.hash} hash algorithm
*/
function getPreferredHashAlgo(oid) {
return curves[enums.write(enums.curve, oid.toHex())].hash;
}
Expand Down
10 changes: 5 additions & 5 deletions src/crypto/public_key/elliptic/ecdh.js
Expand Up @@ -24,14 +24,14 @@
import nacl from '@openpgp/tweetnacl/nacl-fast-light.js';
import { Curve, jwkToRawPublic, rawPublicToJWK, privateToJWK, validateStandardParams } from './curves';
import * as aesKW from '../../aes_kw';
import * as cipher from '../../cipher';
import { getRandomBytes } from '../../random';
import hash from '../../hash';
import enums from '../../../enums';
import util from '../../../util';
import { b64ToUint8Array } from '../../../encoding/base64';
import * as pkcs5 from '../../pkcs5';
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
import { getCipher } from '../../crypto';

const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
Expand Down Expand Up @@ -132,8 +132,8 @@ export async function encrypt(oid, kdfParams, data, Q, fingerprint) {
const curve = new Curve(oid);
const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q);
const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint);
const cipherAlgo = enums.read(enums.symmetric, kdfParams.cipher);
const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipherAlgo].keySize, param);
const { keySize } = getCipher(kdfParams.cipher);
const Z = await kdf(kdfParams.hash, sharedKey, keySize, param);
const wrappedKey = aesKW.wrap(Z, m);
return { publicKey, wrappedKey };
}
Expand Down Expand Up @@ -192,12 +192,12 @@ export async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) {
const curve = new Curve(oid);
const { sharedKey } = await genPrivateEphemeralKey(curve, V, Q, d);
const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint);
const cipherAlgo = enums.read(enums.symmetric, kdfParams.cipher);
const { keySize } = getCipher(kdfParams.cipher);
let err;
for (let i = 0; i < 3; i++) {
try {
// Work around old go crypto bug and old OpenPGP.js bug, respectively.
const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipherAlgo].keySize, param, i === 1, i === 2);
const Z = await kdf(kdfParams.hash, sharedKey, keySize, param, i === 1, i === 2);
return pkcs5.decode(aesKW.unwrap(Z, C));
} catch (e) {
err = e;
Expand Down
17 changes: 14 additions & 3 deletions src/enums.js
Expand Up @@ -448,7 +448,13 @@ export default {
v5Keys: 4
},

/** Asserts validity and converts from string/integer to integer. */
/**
* Asserts validity of given value and converts from string/integer to integer.
* @param {Object} type target enum type
* @param {String|Integer} e value to check and/or convert
* @returns {Integer} enum value if it exists
* @throws {Error} if the value is invalid
*/
write: function(type, e) {
if (typeof e === 'number') {
e = this.read(type, e);
Expand All @@ -461,7 +467,13 @@ export default {
throw new Error('Invalid enum value.');
},

/** Converts from an integer to string. */
/**
* Converts enum integer value to the corresponding string, if it exists.
* @param {Object} type target enum type
* @param {Integer} e value to convert
* @returns {String} name of enum value if it exists
* @throws {Error} if the value is invalid
*/
read: function(type, e) {
if (!type[byValue]) {
type[byValue] = [];
Expand All @@ -476,5 +488,4 @@ export default {

throw new Error('Invalid enum value.');
}

};