fix: add type definitions for s2kNames

This commit is contained in:
Ayham Kteash 2024-01-10 17:36:57 +01:00
parent 64138a37c7
commit e3302d6b3b
No known key found for this signature in database
GPG Key ID: 830C24645F048DB3
5 changed files with 93 additions and 55 deletions

10
openpgp.d.ts vendored
View File

@ -733,6 +733,16 @@ interface VerifyMessageResult<T extends MaybeStream<Data> = MaybeStream<Data>> {
signatures: VerificationResult[]; signatures: VerificationResult[];
} }
export type S2KType = enums.s2k.argon2 | enums.s2k.gnu | enums.s2k.iterated | enums.s2k.salted | enums.s2k.simple
type S2KNames = 'argon2' | 'gnu' | 'iterated' | 'salted' | 'simple' | string
export interface S2K {
type: S2KNames
read(bytes: Uint8Array):number
write(): Uint8Array
produceKey(passphrase: string, keySize: number): Promise<Uint8Array>
}
/** /**
* Armor an OpenPGP binary packet block * Armor an OpenPGP binary packet block

View File

@ -2,7 +2,7 @@ import enums from '../../enums';
import util from '../../util'; import util from '../../util';
import crypto from '../../crypto'; import crypto from '../../crypto';
import type { default as loadArgonWasmModuleType } from 'argon2id'; import type { default as loadArgonWasmModuleType } from 'argon2id';
// import defaultConfig from '../../config';
import { Config } from '../../../openpgp'; import { Config } from '../../../openpgp';
const ARGON2_TYPE = 0x02; // id const ARGON2_TYPE = 0x02; // id
const ARGON2_VERSION = 0x13; const ARGON2_VERSION = 0x13;
@ -27,16 +27,15 @@ let argon2Promise: ReturnType<typeof loadArgonWasmModuleType>;
const ARGON2_WASM_MEMORY_THRESHOLD_RELOAD = 2 << 19; const ARGON2_WASM_MEMORY_THRESHOLD_RELOAD = 2 << 19;
class Argon2S2K { class Argon2S2K {
type: 'argon2'; type: 'argon2';
private salt: Uint8Array; private salt: Uint8Array;
private t: number; private t: number;
private p: number; private p: number;
private encodedM: number; private encodedM: number;
/** /**
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
*/ */
constructor(config:Config) { constructor(config: Config) {
const { passes, parallelism, memoryExponent } = config.s2kArgon2Params; const { passes, parallelism, memoryExponent } = config.s2kArgon2Params;
this.type = 'argon2'; this.type = 'argon2';
@ -55,10 +54,10 @@ class Argon2S2K {
} }
/** /**
* Parsing function for argon2 string-to-key specifier. * Parsing function for argon2 string-to-key specifier.
* @param {Uint8Array} bytes - Payload of argon2 string-to-key specifier * @param {Uint8Array} bytes - Payload of argon2 string-to-key specifier
* @returns {Integer} Actual length of the object. * @returns {Integer} Actual length of the object.
*/ */
read(bytes: Uint8Array) { read(bytes: Uint8Array) {
let i = 0; let i = 0;
@ -73,27 +72,27 @@ class Argon2S2K {
} }
/** /**
* Serializes s2k information * Serializes s2k information
* @returns {Uint8Array} Binary representation of s2k. * @returns {Uint8Array} Binary representation of s2k.
*/ */
write(): Uint8Array { write(): Uint8Array {
const arr = [ const arr = [
new Uint8Array([enums.write(enums.s2k, this.type)]), new Uint8Array([enums.write(enums.s2k, this.type)]),
this.salt, this.salt,
new Uint8Array([this.t, this.p, this.encodedM]) new Uint8Array([this.t, this.p, this.encodedM]),
]; ];
return util.concatUint8Array(arr); return util.concatUint8Array(arr);
} }
/** /**
* Produces a key using the specified passphrase and the defined * Produces a key using the specified passphrase and the defined
* hashAlgorithm * hashAlgorithm
* @param {String} passphrase - Passphrase containing user input * @param {String} passphrase - Passphrase containing user input
* @returns {Promise<Uint8Array>} Produced key with a length corresponding to `keySize` * @returns {Promise<Uint8Array>} Produced key with a length corresponding to `keySize`
* @throws {Argon2OutOfMemoryError|Errors} * @throws {Argon2OutOfMemoryError|Errors}
* @async * @async
*/ */
async produceKey(passphrase: string, keySize: number): Promise<Uint8Array> { async produceKey(passphrase: string, keySize: number): Promise<Uint8Array> {
const decodedM = 2 << (this.encodedM - 1); const decodedM = 2 << (this.encodedM - 1);
@ -101,7 +100,8 @@ class Argon2S2K {
// on first load, the argon2 lib is imported and the WASM module is initialized. // on first load, the argon2 lib is imported and the WASM module is initialized.
// the two steps need to be atomic to avoid race conditions causing multiple wasm modules // the two steps need to be atomic to avoid race conditions causing multiple wasm modules
// being loaded when `argon2Promise` is not initialized. // being loaded when `argon2Promise` is not initialized.
loadArgonWasmModule = loadArgonWasmModule || (await import('argon2id')).default; loadArgonWasmModule =
loadArgonWasmModule || (await import('argon2id')).default;
argon2Promise = argon2Promise || loadArgonWasmModule(); argon2Promise = argon2Promise || loadArgonWasmModule();
// important to keep local ref to argon2 in case the module is reloaded by another instance // important to keep local ref to argon2 in case the module is reloaded by another instance
@ -117,7 +117,7 @@ class Argon2S2K {
tagLength: keySize, tagLength: keySize,
memorySize: decodedM, memorySize: decodedM,
parallelism: this.p, parallelism: this.p,
passes: this.t passes: this.t,
}); });
// a lot of memory was used, reload to deallocate // a lot of memory was used, reload to deallocate
@ -128,13 +128,17 @@ class Argon2S2K {
} }
return hash; return hash;
} catch (e) { } catch (e) {
if (e instanceof Error && e.message && ( if (
e.message.includes('Unable to grow instance memory') || // Chrome e instanceof Error &&
e.message.includes('failed to grow memory') || // Firefox e.message &&
e.message.includes('WebAssembly.Memory.grow') || // Safari (e.message.includes('Unable to grow instance memory') || // Chrome
e.message.includes('Out of memory') // Safari iOS e.message.includes('failed to grow memory') || // Firefox
)) { e.message.includes('WebAssembly.Memory.grow') || // Safari
throw new Argon2OutOfMemoryError('Could not allocate required memory for Argon2'); e.message.includes('Out of memory')) // Safari iOS
) {
throw new Argon2OutOfMemoryError(
'Could not allocate required memory for Argon2'
);
} else { } else {
throw e; throw e;
} }

View File

@ -27,7 +27,12 @@
* @module type/s2k * @module type/s2k
*/ */
import type { Config, enums as enumsType } from '../../../openpgp'; import type {
Config,
S2KNames,
S2KType,
enums as enumsType,
} from '../../../openpgp';
import enums from '../../enums'; import enums from '../../enums';
import crypto from '../../crypto'; import crypto from '../../crypto';
@ -36,16 +41,16 @@ import util from '../../util';
class GenericS2K { class GenericS2K {
private algorithm: number; private algorithm: number;
type: string; type: S2KNames;
private c: number; private c: number;
private salt: Uint8Array | null; private salt: Uint8Array | null;
/** /**
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
*/ */
constructor(s2kType: enumsType.s2k.simple | enumsType.s2k.salted | enumsType.s2k.iterated, config:Config ) { constructor(s2kType: S2KType, config: Config) {
/** /**
* Hash function identifier, or 0 for gnu-dummy keys * Hash function identifier
* @type {module:enums.hash | 0} * @type {module:enums.hash}
*/ */
this.algorithm = enums.hash.sha256; this.algorithm = enums.hash.sha256;
/** /**
@ -81,7 +86,7 @@ class GenericS2K {
* @param {Uint8Array} bytes - Payload of string-to-key specifier * @param {Uint8Array} bytes - Payload of string-to-key specifier
* @returns {Integer} Actual length of the object. * @returns {Integer} Actual length of the object.
*/ */
read(bytes: Uint8Array): Number { read(bytes: Uint8Array): number {
let i = 0; let i = 0;
this.algorithm = bytes[i++]; this.algorithm = bytes[i++];
@ -103,10 +108,8 @@ class GenericS2K {
break; break;
default: default:
throw new UnsupportedError('Unknown s2k type.'); // unreachable throw new UnsupportedError('Unknown s2k type.'); // unreachable
} }
return i; return i;
} }
@ -115,17 +118,19 @@ class GenericS2K {
* @returns {Uint8Array} Binary representation of s2k. * @returns {Uint8Array} Binary representation of s2k.
*/ */
write(): Uint8Array { write(): Uint8Array {
const arr = [new Uint8Array([enums.write(enums.s2k, this.type), this.algorithm])]; const arr = [
new Uint8Array([enums.write(enums.s2k, this.type), this.algorithm]),
];
switch (this.type) { switch (this.type) {
case 'simple': case 'simple':
break; break;
case 'salted': case 'salted':
if (!this.salt) { throw Error('Salt was not set') } if (!this.salt) throw Error('Salt was not set');
arr.push(this.salt); arr.push(this.salt);
break; break;
case 'iterated': case 'iterated':
this.salt &&arr.push(this.salt); this.salt && arr.push(this.salt);
arr.push(new Uint8Array([this.c])); arr.push(new Uint8Array([this.c]));
break; break;
default: default:
@ -154,10 +159,17 @@ class GenericS2K {
let toHash; let toHash;
switch (this.type) { switch (this.type) {
case 'simple': case 'simple':
toHash = util.concatUint8Array([new Uint8Array(prefixlen), encodedPassphrase]); toHash = util.concatUint8Array([
new Uint8Array(prefixlen),
encodedPassphrase,
]);
break; break;
case 'salted': case 'salted':
toHash = util.concatUint8Array([new Uint8Array(prefixlen), this.salt, encodedPassphrase]); toHash = util.concatUint8Array([
new Uint8Array(prefixlen),
this.salt,
encodedPassphrase,
]);
break; break;
case 'iterated': { case 'iterated': {
const data = util.concatUint8Array([this.salt, encodedPassphrase]); const data = util.concatUint8Array([this.salt, encodedPassphrase]);
@ -165,7 +177,11 @@ class GenericS2K {
const count = Math.max(this.getCount(), datalen); const count = Math.max(this.getCount(), datalen);
toHash = new Uint8Array(prefixlen + count); toHash = new Uint8Array(prefixlen + count);
toHash.set(data, prefixlen); toHash.set(data, prefixlen);
for (let pos = prefixlen + datalen; pos < count; pos += datalen, datalen *= 2) { for (
let pos = prefixlen + datalen;
pos < count;
pos += datalen, datalen *= 2
) {
toHash.copyWithin(pos, prefixlen, pos); toHash.copyWithin(pos, prefixlen, pos);
} }
break; break;

View File

@ -15,21 +15,22 @@
// License along with this library; if not, write to the Free Software // License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import { S2KNames } from '../../../openpgp';
import enums from '../../enums'; import enums from '../../enums';
import { UnsupportedError } from '../../packet/packet'; import { UnsupportedError } from '../../packet/packet';
import util from '../../util'; import util from '../../util';
class GnuS2K { class GnuS2K {
type: 'gnu' = 'gnu'; type: S2KNames = 'gnu';
gnuType?: 'gnu-dummy' = undefined; gnuType?: 'gnu-dummy' = undefined;
algorithm: number = enums.hash.sha256 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}). * 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 * @param {Uint8Array} bytes - Payload of string-to-key specifier
* @returns {Integer} Actual length of the object. * @returns {Integer} Actual length of the object.
*/ */
read(bytes: Uint8Array): Number { read(bytes: Uint8Array): number {
let i = 0; let i = 0;
this.algorithm = bytes[i++]; this.algorithm = bytes[i++];
if (util.uint8ArrayToString(bytes.subarray(i, i + 3)) === 'GNU') { if (util.uint8ArrayToString(bytes.subarray(i, i + 3)) === 'GNU') {

View File

@ -5,8 +5,12 @@ import { UnsupportedError } from '../../packet/packet';
import GnuS2K from './gnu'; import GnuS2K from './gnu';
import defaultConfig from '../../config'; import defaultConfig from '../../config';
import { Config } from '../../../openpgp'; import { Config, S2K, S2KType } from '../../../openpgp';
const allowedS2KTypesForEncryption = new Set([enums.s2k.argon2, enums.s2k.iterated]);
const allowedS2KTypesForEncryption = new Set([
enums.s2k.argon2,
enums.s2k.iterated,
]);
/** /**
* Instantiate a new S2K instance of the given type * Instantiate a new S2K instance of the given type
@ -15,7 +19,10 @@ const allowedS2KTypesForEncryption = new Set([enums.s2k.argon2, enums.s2k.iterat
* @returns {Object} New s2k object * @returns {Object} New s2k object
* @throws {Error} for unknown or unsupported types * @throws {Error} for unknown or unsupported types
*/ */
export function newS2KFromType (type: number, config:Config = defaultConfig ): Argon2S2K | GenericS2K | GnuS2K { export function newS2KFromType(
type: S2KType,
config: Config = defaultConfig
): S2K {
switch (type) { switch (type) {
case enums.s2k.gnu: case enums.s2k.gnu:
return new GnuS2K(); return new GnuS2K();
@ -36,7 +43,7 @@ export function newS2KFromType (type: number, config:Config = defaultConfig ): A
* @returns {Object} New s2k object * @returns {Object} New s2k object
* @throws {Error} for unknown or unsupported types * @throws {Error} for unknown or unsupported types
*/ */
export function newS2KFromConfig(config:Config = defaultConfig) { export function newS2KFromConfig(config: Config = defaultConfig) {
const { s2kType } = config; const { s2kType } = config;
if (!allowedS2KTypesForEncryption.has(s2kType)) { if (!allowedS2KTypesForEncryption.has(s2kType)) {