Skip to content

Commit

Permalink
Replace strings with integer algorithm identifiers in packet classes (#…
Browse files Browse the repository at this point in the history
…1410)

In several packet classes, we used to store string identifiers for public-key,
aead, cipher or hash algorithms. To make the code consistent and to avoid
having to convert to/from string values, we now always store integer values
instead, e.g. `enums.symmetric.aes128` is used instead of `'aes128'`.

This is not expected to be a breaking change for most library users. Note that
the type of `Key.getAlgorithmInfo()` and of the session key objects returned
and accepted by top-level functions remain unchanged.

Affected classes (type changes for some properties and method's arguments):
- `PublicKeyPacket`, `PublicSubkeyPacket`, `SecretKeyPacket`,
`SecretSubkeyPacket`
- `SymEncryptedIntegrityProtectedDataPacket`, `AEADEncryptedDataPacket`,
`SymmetricallyEncryptedDataPacket`
- `LiteralDataPacket`, `CompressedDataPacket`
- `PublicKeyEncryptedSessionKey`, `SymEncryptedSessionKeyPacket`
- `SignaturePacket`

Other potentially breaking changes:
- Removed property `AEADEncryptedDataPacket.aeadAlgo`, since it was redudant
given `.aeadAlgorithm`.
- Renamed `AEADEncryptedDataPacket.cipherAlgo` -> `.cipherAlgorithm`
  • Loading branch information
larabr committed Nov 22, 2021
1 parent 03fa68d commit 6da1c53
Show file tree
Hide file tree
Showing 34 changed files with 407 additions and 314 deletions.
25 changes: 15 additions & 10 deletions openpgp.d.ts
Expand Up @@ -185,8 +185,8 @@ export function decryptSessionKeys<T extends MaybeStream<Data>>(options: { messa
export function readMessage<T extends MaybeStream<string>>(options: { armoredMessage: T, config?: PartialConfig }): Promise<Message<T>>;
export function readMessage<T extends MaybeStream<Uint8Array>>(options: { binaryMessage: T, config?: PartialConfig }): Promise<Message<T>>;

export function createMessage<T extends MaybeStream<string>>(options: { text: T, filename?: string, date?: Date, type?: DataPacketType }): Promise<Message<T>>;
export function createMessage<T extends MaybeStream<Uint8Array>>(options: { binary: T, filename?: string, date?: Date, type?: DataPacketType }): Promise<Message<T>>;
export function createMessage<T extends MaybeStream<string>>(options: { text: T, filename?: string, date?: Date, format?: enums.literalFormatNames }): Promise<Message<T>>;
export function createMessage<T extends MaybeStream<Uint8Array>>(options: { binary: T, filename?: string, date?: Date, format?: enums.literalFormatNames }): Promise<Message<T>>;

export function encrypt<T extends MaybeStream<Data>>(options: EncryptOptions & { message: Message<T>, format?: 'armored' }): Promise<
T extends WebStream<infer X> ? WebStream<string> :
Expand Down 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 All @@ -438,8 +438,8 @@ export class LiteralDataPacket extends BasePacket {
static readonly tag: enums.packet.literalData;
private getText(clone?: boolean): MaybeStream<string>;
private getBytes(clone?: boolean): MaybeStream<Uint8Array>;
private setText(text: MaybeStream<string>, format?: DataPacketType);
private setBytes(bytes: MaybeStream<Uint8Array>, format?: DataPacketType);
private setText(text: MaybeStream<string>, format?: enums.literal);
private setBytes(bytes: MaybeStream<Uint8Array>, format: enums.literal);
private setFilename(filename: string);
private getFilename(): string;
private writeHeader(): Uint8Array;
Expand Down Expand Up @@ -534,8 +534,6 @@ export type AnyPacket = BasePacket;
export type AnySecretKeyPacket = SecretKeyPacket | SecretSubkeyPacket;
export type AnyKeyPacket = BasePublicKeyPacket;

type DataPacketType = 'utf8' | 'binary' | 'text' | 'mime';

type AllowedPackets = Map<enums.packet, object>; // mapping to Packet classes (i.e. typeof LiteralDataPacket etc.)
export class PacketList<T extends AnyPacket> extends Array<T> {
static fromBinary(bytes: MaybeStream<Uint8Array>, allowedPackets: AllowedPackets, config?: Config): PacketList<AnyPacket>; // the packet types depend on`allowedPackets`
Expand Down Expand Up @@ -630,7 +628,6 @@ interface SignOptions {
message: CleartextMessage | Message<MaybeStream<Data>>;
signingKeys?: MaybeArray<PrivateKey>;
format?: 'armored' | 'binary' | 'object';
dataType?: DataPacketType;
detached?: boolean;
signingKeyIDs?: MaybeArray<KeyID>;
date?: Date;
Expand Down Expand Up @@ -876,4 +873,12 @@ export namespace enums {
ocb = 2,
experimentalGCM = 100 // Private algorithm
}

export type literalFormatNames = 'utf8' | 'binary' | 'text' | 'mime'
enum literal {
binary = 98,
text = 116,
utf8 = 117,
mime = 109
}
}
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];
}
36 changes: 15 additions & 21 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 @@ -110,26 +111,19 @@ export default {
*/
digest: function(algo, data) {
switch (algo) {
case 1:
// - MD5 [HAC]
case enums.hash.md5:
return this.md5(data);
case 2:
// - SHA-1 [FIPS180]
case enums.hash.sha1:
return this.sha1(data);
case 3:
// - RIPE-MD/160 [HAC]
case enums.hash.ripemd:
return this.ripemd(data);
case 8:
// - SHA256 [FIPS180]
case enums.hash.sha256:
return this.sha256(data);
case 9:
// - SHA384 [FIPS180]
case enums.hash.sha384:
return this.sha384(data);
case 10:
// - SHA512 [FIPS180]
case enums.hash.sha512:
return this.sha512(data);
case 11:
// - SHA224 [FIPS180]
case enums.hash.sha224:
return this.sha224(data);
default:
throw new Error('Invalid hash function.');
Expand All @@ -143,18 +137,18 @@ export default {
*/
getHashByteLength: function(algo) {
switch (algo) {
case 1: // - MD5 [HAC]
case enums.hash.md5:
return 16;
case 2: // - SHA-1 [FIPS180]
case 3: // - RIPE-MD/160 [HAC]
case enums.hash.sha1:
case enums.hash.ripemd:
return 20;
case 8: // - SHA256 [FIPS180]
case enums.hash.sha256:
return 32;
case 9: // - SHA384 [FIPS180]
case enums.hash.sha384:
return 48;
case 10: // - SHA512 [FIPS180]
case enums.hash.sha512:
return 64;
case 11: // - SHA224 [FIPS180]
case enums.hash.sha224:
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
7 changes: 5 additions & 2 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,11 +37,13 @@ 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') {
if (cipher !== enums.symmetric.aes128 &&
cipher !== enums.symmetric.aes192 &&
cipher !== enums.symmetric.aes256) {
throw new Error('GCM mode supports only AES cipher');
}

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

0 comments on commit 6da1c53

Please sign in to comment.