From 2b49d9dd7c9a5ac5674193f35985094c1c4bf5ff Mon Sep 17 00:00:00 2001 From: Ayham Kteash Date: Mon, 11 Dec 2023 16:27:19 +0100 Subject: [PATCH 1/4] refactor: gnu s2k type to its own class --- src/type/s2k/generic.ts | 23 +------- src/type/s2k/gnu.ts | 120 ++++++++++++++++++++++++++++++++++++++++ src/type/s2k/index.ts | 6 +- 3 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 src/type/s2k/gnu.ts diff --git a/src/type/s2k/generic.ts b/src/type/s2k/generic.ts index 7c3dc4f7..df6bc8ea 100644 --- a/src/type/s2k/generic.ts +++ b/src/type/s2k/generic.ts @@ -101,22 +101,8 @@ class GenericS2K { this.c = bytes[i++]; break; - case 'gnu': - if (util.uint8ArrayToString(bytes.subarray(i, i + 3)) === 'GNU') { - i += 3; // GNU - const gnuExtType = 1000 + bytes[i++]; - if (gnuExtType === 1001) { - this.type = 'gnu-dummy'; - // GnuPG extension mode 1001 -- don't write secret key at all - } else { - throw new UnsupportedError('Unknown s2k gnu protection mode.'); - } - } else { - throw new UnsupportedError('Unknown s2k type.'); - } - break; - default: + throw new UnsupportedError('Unknown s2k type.'); // unreachable } @@ -128,9 +114,6 @@ class GenericS2K { * @returns {Uint8Array} Binary representation of s2k. */ write(): Uint8Array { - if (this.type === 'gnu-dummy') { - return new Uint8Array([101, 0, ...util.stringToUint8Array('GNU'), 1]); - } const arr = [new Uint8Array([enums.write(enums.s2k, this.type), this.algorithm])]; switch (this.type) { @@ -143,8 +126,6 @@ class GenericS2K { this.salt &&arr.push(this.salt); arr.push(new Uint8Array([this.c])); break; - case 'gnu': - throw new Error('GNU s2k type not supported.'); default: throw new Error('Unknown s2k type.'); } @@ -187,8 +168,6 @@ class GenericS2K { } break; } - case 'gnu': - throw new Error('GNU s2k type not supported.'); default: throw new Error('Unknown s2k type.'); } diff --git a/src/type/s2k/gnu.ts b/src/type/s2k/gnu.ts new file mode 100644 index 00000000..99d3dc11 --- /dev/null +++ b/src/type/s2k/gnu.ts @@ -0,0 +1,120 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// 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 + +/** + * Implementation of the String-to-key specifier + * + * {@link https://tools.ietf.org/html/rfc4880#section-3.7|RFC4880 3.7}: + * String-to-key (S2K) specifiers are used to convert passphrase strings + * into symmetric-key encryption/decryption keys. They are used in two + * places, currently: to encrypt the secret part of private keys in the + * private keyring, and to convert passphrases to encryption keys for + * symmetrically encrypted messages. + * @module type/s2k + */ + +import defaultConfig from '../../config'; +import crypto from '../../crypto'; +import enums from '../../enums'; +import { UnsupportedError } from '../../packet/packet'; +import util from '../../util'; + +class GnuS2k { + algorithm: number; + type: string; + c: number; + /** + * @param {Object} [config] - Full configuration, defaults to openpgp.config + */ + constructor(config = defaultConfig) { + /** + * Hash function identifier, or 0 for gnu-dummy keys + * @type {module:enums.hash | 0} + */ + this.algorithm = enums.hash.sha256; + /** + * enums.s2k identifier or 'gnu-dummy' + * @type {String} + */ + this.type = 'gnu'; + /** + * @type {Integer} + */ + this.c = config.s2kIterationCountByte; + } + + /** + * Parsing function for a string-to-key specifier ({@link https://tools.ietf.org/html/rfc4880#section-3.7|RFC 4880 3.7}). + * @param {Uint8Array} bytes - Payload of string-to-key specifier + * @returns {Integer} Actual length of the object. + */ + read(bytes: Uint8Array): Number { + let i = 0; + this.algorithm = bytes[i++]; + if (util.uint8ArrayToString(bytes.subarray(i, i + 3)) === 'GNU') { + i += 3; // GNU + const gnuExtType = 1000 + bytes[i++]; + if (gnuExtType === 1001) { + this.type = 'gnu-dummy'; + // GnuPG extension mode 1001 -- don't write secret key at all + } else { + throw new UnsupportedError('Unknown s2k gnu protection mode.'); + } + } else { + throw new UnsupportedError('Unknown s2k type.'); + } + + return i; + } + + /** + * Serializes s2k information + * @returns {Uint8Array} Binary representation of s2k. + */ + write(): Uint8Array { + if (this.type === 'gnu-dummy') { + return new Uint8Array([101, 0, ...util.stringToUint8Array('GNU'), 1]); + } else { + throw new Error('GNU s2k type not supported.'); + } + } + + /** + * Produces a key using the specified passphrase and the defined + * hashAlgorithm + * @param {String} passphrase - Passphrase containing user input + * @returns {Promise} Produced key with a length corresponding to. + * hashAlgorithm hash length + * @async + */ + async produceKey(passphrase: string, numBytes: number): Promise { + const arr: number[] = []; + let rlength = 0; + + while (rlength < numBytes) { + if (this.type !== 'gnu') { + throw new Error('Unknown s2k type.'); + } else { + throw new Error('GNU s2k type not supported.'); + } + } + + return util.concatUint8Array(arr).subarray(0, numBytes); + } +} + +export default GnuS2k; diff --git a/src/type/s2k/index.ts b/src/type/s2k/index.ts index a54c5e1f..9b5d23e4 100644 --- a/src/type/s2k/index.ts +++ b/src/type/s2k/index.ts @@ -3,6 +3,7 @@ import Argon2S2K, { Argon2OutOfMemoryError } from './argon2'; import GenericS2K from './generic'; import enums from '../../enums'; import { UnsupportedError } from '../../packet/packet'; +import GnuS2K from './gnu'; const allowedS2KTypesForEncryption = new Set([enums.s2k.argon2, enums.s2k.iterated]); @@ -13,12 +14,13 @@ const allowedS2KTypesForEncryption = new Set([enums.s2k.argon2, enums.s2k.iterat * @returns {Object} New s2k object * @throws {Error} for unknown or unsupported types */ -export function newS2KFromType (type: number, config = defaultConfig): Argon2S2K | GenericS2K { +export function newS2KFromType (type: number, config = defaultConfig): Argon2S2K | GenericS2K | GnuS2K { switch (type) { + case enums.s2k.gnu: + return new GnuS2K(config); case enums.s2k.argon2: return new Argon2S2K(config); case enums.s2k.iterated: - case enums.s2k.gnu: case enums.s2k.salted: case enums.s2k.simple: return new GenericS2K(type, config); From df654e7b5e0090ba6e0f254b04eec97ec5afc375 Mon Sep 17 00:00:00 2001 From: Ayham Kteash Date: Tue, 12 Dec 2023 13:46:24 +0100 Subject: [PATCH 2/4] fix: remove unused import --- src/type/s2k/gnu.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/type/s2k/gnu.ts b/src/type/s2k/gnu.ts index 99d3dc11..cdbe3154 100644 --- a/src/type/s2k/gnu.ts +++ b/src/type/s2k/gnu.ts @@ -28,7 +28,6 @@ */ import defaultConfig from '../../config'; -import crypto from '../../crypto'; import enums from '../../enums'; import { UnsupportedError } from '../../packet/packet'; import util from '../../util'; From 2983d225cc08c4b5cc535eea9481dcdefa643c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Kr=C3=BCger?= Date: Mon, 8 Jan 2024 18:31:52 +0100 Subject: [PATCH 3/4] refactor: s2k uses gnuType for dummy keys --- src/packet/secret_key.js | 9 +++--- src/type/s2k/gnu.ts | 59 ++++++---------------------------------- src/type/s2k/index.ts | 2 +- 3 files changed, 14 insertions(+), 56 deletions(-) diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index d260d12d..8cdd7db6 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -134,7 +134,7 @@ class SecretKeyPacket extends PublicKeyPacket { this.s2k = newS2KFromType(s2kType); i += this.s2k.read(bytes.subarray(i, bytes.length)); - if (this.s2k.type === 'gnu-dummy') { + if (this.s2k.gnuType === 'gnu-dummy') { return; } } else if (this.s2kUsage) { @@ -253,7 +253,7 @@ class SecretKeyPacket extends PublicKeyPacket { // - [Optional] If secret data is encrypted (string-to-key usage octet // not zero), an Initial Vector (IV) of the same length as the // cipher's block size. - if (this.s2kUsage && this.s2k.type !== 'gnu-dummy') { + if (this.s2kUsage && this.s2k.gnuType !== 'gnu-dummy') { optionalFieldsArr.push(...this.iv); } @@ -306,7 +306,7 @@ class SecretKeyPacket extends PublicKeyPacket { * @returns {Boolean} */ isDummy() { - return !!(this.s2k && this.s2k.type === 'gnu-dummy'); + return !!(this.s2k && this.s2k.gnuType === 'gnu-dummy'); } /** @@ -327,7 +327,8 @@ class SecretKeyPacket extends PublicKeyPacket { this.s2k = newS2KFromType(enums.s2k.gnu, config); this.s2k.algorithm = 0; this.s2k.c = 0; - this.s2k.type = 'gnu-dummy'; + this.s2k.type = 'gnu'; + this.s2k.gnuType = 'gnu-dummy'; this.s2kUsage = 254; this.symmetric = enums.symmetric.aes256; } diff --git a/src/type/s2k/gnu.ts b/src/type/s2k/gnu.ts index cdbe3154..2fbceb10 100644 --- a/src/type/s2k/gnu.ts +++ b/src/type/s2k/gnu.ts @@ -15,46 +15,14 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -/** - * Implementation of the String-to-key specifier - * - * {@link https://tools.ietf.org/html/rfc4880#section-3.7|RFC4880 3.7}: - * String-to-key (S2K) specifiers are used to convert passphrase strings - * into symmetric-key encryption/decryption keys. They are used in two - * places, currently: to encrypt the secret part of private keys in the - * private keyring, and to convert passphrases to encryption keys for - * symmetrically encrypted messages. - * @module type/s2k - */ - -import defaultConfig from '../../config'; import enums from '../../enums'; import { UnsupportedError } from '../../packet/packet'; import util from '../../util'; -class GnuS2k { - algorithm: number; - type: string; - c: number; - /** - * @param {Object} [config] - Full configuration, defaults to openpgp.config - */ - constructor(config = defaultConfig) { - /** - * Hash function identifier, or 0 for gnu-dummy keys - * @type {module:enums.hash | 0} - */ - this.algorithm = enums.hash.sha256; - /** - * enums.s2k identifier or 'gnu-dummy' - * @type {String} - */ - this.type = 'gnu'; - /** - * @type {Integer} - */ - this.c = config.s2kIterationCountByte; - } +class GnuS2K { + type: 'gnu' = 'gnu'; + gnuType?: 'gnu-dummy' = undefined; + algorithm: number = enums.hash.sha256 /** * Parsing function for a string-to-key specifier ({@link https://tools.ietf.org/html/rfc4880#section-3.7|RFC 4880 3.7}). @@ -68,7 +36,7 @@ class GnuS2k { i += 3; // GNU const gnuExtType = 1000 + bytes[i++]; if (gnuExtType === 1001) { - this.type = 'gnu-dummy'; + this.gnuType = 'gnu-dummy'; // GnuPG extension mode 1001 -- don't write secret key at all } else { throw new UnsupportedError('Unknown s2k gnu protection mode.'); @@ -85,7 +53,7 @@ class GnuS2k { * @returns {Uint8Array} Binary representation of s2k. */ write(): Uint8Array { - if (this.type === 'gnu-dummy') { + if (this.gnuType === 'gnu-dummy') { return new Uint8Array([101, 0, ...util.stringToUint8Array('GNU'), 1]); } else { throw new Error('GNU s2k type not supported.'); @@ -101,19 +69,8 @@ class GnuS2k { * @async */ async produceKey(passphrase: string, numBytes: number): Promise { - const arr: number[] = []; - let rlength = 0; - - while (rlength < numBytes) { - if (this.type !== 'gnu') { - throw new Error('Unknown s2k type.'); - } else { - throw new Error('GNU s2k type not supported.'); - } - } - - return util.concatUint8Array(arr).subarray(0, numBytes); + throw new Error('Gnu S2K does not support producing keys'); } } -export default GnuS2k; +export default GnuS2K; diff --git a/src/type/s2k/index.ts b/src/type/s2k/index.ts index 9b5d23e4..2948cfbb 100644 --- a/src/type/s2k/index.ts +++ b/src/type/s2k/index.ts @@ -17,7 +17,7 @@ const allowedS2KTypesForEncryption = new Set([enums.s2k.argon2, enums.s2k.iterat export function newS2KFromType (type: number, config = defaultConfig): Argon2S2K | GenericS2K | GnuS2K { switch (type) { case enums.s2k.gnu: - return new GnuS2K(config); + return new GnuS2K(); case enums.s2k.argon2: return new Argon2S2K(config); case enums.s2k.iterated: From 888f149f12644614e307e95f607669b50dfc8c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Kr=C3=BCger?= Date: Tue, 9 Jan 2024 12:54:32 +0100 Subject: [PATCH 4/4] refactor: add Config type instead of defaultConfig --- src/type/s2k/argon2.ts | 18 ++++++++++-------- src/type/s2k/generic.ts | 7 ++++--- src/type/s2k/index.ts | 6 +++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/type/s2k/argon2.ts b/src/type/s2k/argon2.ts index 5636d608..f8421b57 100644 --- a/src/type/s2k/argon2.ts +++ b/src/type/s2k/argon2.ts @@ -2,6 +2,8 @@ import defaultConfig from '../../config'; import enums from '../../enums'; import util from '../../util'; import crypto from '../../crypto'; +import type { default as loadArgonWasmModuleType } from 'argon2id'; +import { Config } from '../../../openpgp'; const ARGON2_TYPE = 0x02; // id const ARGON2_VERSION = 0x13; @@ -20,21 +22,21 @@ export class Argon2OutOfMemoryError extends Error { } // cache argon wasm module -let loadArgonWasmModule: Function; -let argon2Promise: Promise; +let loadArgonWasmModule: typeof loadArgonWasmModuleType; +let argon2Promise: ReturnType; // reload wasm module above this treshold, to deallocated used memory const ARGON2_WASM_MEMORY_THRESHOLD_RELOAD = 2 << 19; class Argon2S2K { - type: string; - salt: Uint8Array | null; - t: number; - p: number; - encodedM: number; + type: 'argon2'; + private salt: Uint8Array | null; + private t: number; + private p: number; + private encodedM: number; /** * @param {Object} [config] - Full configuration, defaults to openpgp.config */ - constructor(config = defaultConfig) { + constructor(config: Config) { const { passes, parallelism, memoryExponent } = config.s2kArgon2Params; diff --git a/src/type/s2k/generic.ts b/src/type/s2k/generic.ts index df6bc8ea..36b9b41b 100644 --- a/src/type/s2k/generic.ts +++ b/src/type/s2k/generic.ts @@ -27,9 +27,10 @@ * @module type/s2k */ -import defaultConfig from '../../config'; -import crypto from '../../crypto'; +import type { Config, enums as enumsType } from '../../../openpgp'; import enums from '../../enums'; + +import crypto from '../../crypto'; import { UnsupportedError } from '../../packet/packet'; import util from '../../util'; @@ -41,7 +42,7 @@ class GenericS2K { /** * @param {Object} [config] - Full configuration, defaults to openpgp.config */ - constructor(s2kType: number, config = defaultConfig) { + constructor(s2kType: enumsType.s2k.simple | enumsType.s2k.salted | enumsType.s2k.iterated, config: Config) { /** * Hash function identifier, or 0 for gnu-dummy keys * @type {module:enums.hash | 0} diff --git a/src/type/s2k/index.ts b/src/type/s2k/index.ts index 2948cfbb..7fd8059a 100644 --- a/src/type/s2k/index.ts +++ b/src/type/s2k/index.ts @@ -1,9 +1,9 @@ -import defaultConfig from '../../config'; import Argon2S2K, { Argon2OutOfMemoryError } from './argon2'; import GenericS2K from './generic'; import enums from '../../enums'; import { UnsupportedError } from '../../packet/packet'; import GnuS2K from './gnu'; +import { Config } from '../../../openpgp'; const allowedS2KTypesForEncryption = new Set([enums.s2k.argon2, enums.s2k.iterated]); @@ -14,7 +14,7 @@ const allowedS2KTypesForEncryption = new Set([enums.s2k.argon2, enums.s2k.iterat * @returns {Object} New s2k object * @throws {Error} for unknown or unsupported types */ -export function newS2KFromType (type: number, config = defaultConfig): Argon2S2K | GenericS2K | GnuS2K { +export function newS2KFromType (type: number, config: Config): Argon2S2K | GenericS2K | GnuS2K { switch (type) { case enums.s2k.gnu: return new GnuS2K(); @@ -35,7 +35,7 @@ export function newS2KFromType (type: number, config = defaultConfig): Argon2S2K * @returns {Object} New s2k object * @throws {Error} for unknown or unsupported types */ -export function newS2KFromConfig(config = defaultConfig) { +export function newS2KFromConfig(config: Config) { const { s2kType } = config; if (!allowedS2KTypesForEncryption.has(s2kType)) {