mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-10-14 00:59:29 +00:00
590 lines
22 KiB
JavaScript
590 lines
22 KiB
JavaScript
// 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
|
|
|
|
import PublicKeyPacket from './public_key';
|
|
import { newS2KFromConfig, newS2KFromType } from '../type/s2k';
|
|
import crypto from '../crypto';
|
|
import enums from '../enums';
|
|
import util from '../util';
|
|
import defaultConfig from '../config';
|
|
import { UnsupportedError, writeTag } from './packet';
|
|
import computeHKDF from '../crypto/hkdf';
|
|
|
|
/**
|
|
* A Secret-Key packet contains all the information that is found in a
|
|
* Public-Key packet, including the public-key material, but also
|
|
* includes the secret-key material after all the public-key fields.
|
|
* @extends PublicKeyPacket
|
|
*/
|
|
class SecretKeyPacket extends PublicKeyPacket {
|
|
static get tag() {
|
|
return enums.packet.secretKey;
|
|
}
|
|
|
|
/**
|
|
* @param {Date} [date] - Creation date
|
|
* @param {Object} [config] - Full configuration, defaults to openpgp.config
|
|
*/
|
|
constructor(date = new Date(), config = defaultConfig) {
|
|
super(date, config);
|
|
/**
|
|
* Secret-key data
|
|
*/
|
|
this.keyMaterial = null;
|
|
/**
|
|
* Indicates whether secret-key data is encrypted. `this.isEncrypted === false` means data is available in decrypted form.
|
|
*/
|
|
this.isEncrypted = null;
|
|
/**
|
|
* S2K usage
|
|
* @type {enums.symmetric}
|
|
*/
|
|
this.s2kUsage = 0;
|
|
/**
|
|
* S2K object
|
|
* @type {type/s2k}
|
|
*/
|
|
this.s2k = null;
|
|
/**
|
|
* Symmetric algorithm to encrypt the key with
|
|
* @type {enums.symmetric}
|
|
*/
|
|
this.symmetric = null;
|
|
/**
|
|
* AEAD algorithm to encrypt the key with (if AEAD protection is enabled)
|
|
* @type {enums.aead}
|
|
*/
|
|
this.aead = null;
|
|
/**
|
|
* Whether the key is encrypted using the legacy AEAD format proposal from RFC4880bis
|
|
* (i.e. it was encrypted with the flag `config.aeadProtect` in OpenPGP.js v5 or older).
|
|
* This value is only relevant to know how to decrypt the key:
|
|
* if AEAD is enabled, a v4 key is always re-encrypted using the standard AEAD mechanism.
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
this.isLegacyAEAD = null;
|
|
/**
|
|
* Decrypted private parameters, referenced by name
|
|
* @type {Object}
|
|
*/
|
|
this.privateParams = null;
|
|
/**
|
|
* `true` for keys whose integrity is already confirmed, based on
|
|
* the AEAD encryption mechanism
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
this.usedModernAEAD = null;
|
|
}
|
|
|
|
// 5.5.3. Secret-Key Packet Formats
|
|
|
|
/**
|
|
* Internal parser for private keys as specified in
|
|
* {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.5.3|RFC4880bis-04 section 5.5.3}
|
|
* @param {Uint8Array} bytes - Input string to read the packet from
|
|
* @async
|
|
*/
|
|
async read(bytes, config = defaultConfig) {
|
|
// - A Public-Key or Public-Subkey packet, as described above.
|
|
let i = await this.readPublicKey(bytes, config);
|
|
const startOfSecretKeyData = i;
|
|
|
|
// - One octet indicating string-to-key usage conventions. Zero
|
|
// indicates that the secret-key data is not encrypted. 255 or 254
|
|
// indicates that a string-to-key specifier is being given. Any
|
|
// other value is a symmetric-key encryption algorithm identifier.
|
|
this.s2kUsage = bytes[i++];
|
|
|
|
// - Only for a version 5 packet, a one-octet scalar octet count of
|
|
// the next 4 optional fields.
|
|
if (this.version === 5) {
|
|
i++;
|
|
}
|
|
|
|
// - Only for a version 6 packet where the secret key material is
|
|
// encrypted (that is, where the previous octet is not zero), a one-
|
|
// octet scalar octet count of the cumulative length of all the
|
|
// following optional string-to-key parameter fields.
|
|
if (this.version === 6 && this.s2kUsage) {
|
|
i++;
|
|
}
|
|
|
|
try {
|
|
// - [Optional] If string-to-key usage octet was 255, 254, or 253, a
|
|
// one-octet symmetric encryption algorithm.
|
|
if (this.s2kUsage === 255 || this.s2kUsage === 254 || this.s2kUsage === 253) {
|
|
this.symmetric = bytes[i++];
|
|
|
|
// - [Optional] If string-to-key usage octet was 253, a one-octet
|
|
// AEAD algorithm.
|
|
if (this.s2kUsage === 253) {
|
|
this.aead = bytes[i++];
|
|
}
|
|
|
|
// - [Optional] Only for a version 6 packet, and if string-to-key usage
|
|
// octet was 255, 254, or 253, an one-octet count of the following field.
|
|
if (this.version === 6) {
|
|
i++;
|
|
}
|
|
|
|
// - [Optional] If string-to-key usage octet was 255, 254, or 253, a
|
|
// string-to-key specifier. The length of the string-to-key
|
|
// specifier is implied by its type, as described above.
|
|
const s2kType = bytes[i++];
|
|
this.s2k = newS2KFromType(s2kType);
|
|
i += this.s2k.read(bytes.subarray(i, bytes.length));
|
|
|
|
if (this.s2k.type === 'gnu-dummy') {
|
|
return;
|
|
}
|
|
} else if (this.s2kUsage) {
|
|
this.symmetric = this.s2kUsage;
|
|
}
|
|
|
|
|
|
if (this.s2kUsage) {
|
|
// OpenPGP.js up to v5 used to support encrypting v4 keys using AEAD as specified by draft RFC4880bis (https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#section-5.5.3-3.5).
|
|
// This legacy format is incompatible, but fundamentally indistinguishable, from that of the crypto-refresh for v4 keys (v5 keys are always in legacy format).
|
|
// While parsing the key may succeed (if IV and AES block sizes match), key decryption will always
|
|
// fail if the key was parsed according to the wrong format, since the keys are processed differently.
|
|
// Thus, for v4 keys, we rely on the caller to instruct us to process the key as legacy, via config flag.
|
|
this.isLegacyAEAD = this.s2kUsage === 253 && (
|
|
this.version === 5 || (this.version === 4 && config.parseAEADEncryptedV4KeysAsLegacy));
|
|
// - crypto-refresh: If string-to-key usage octet was 255, 254 [..], an initialization vector (IV)
|
|
// of the same length as the cipher's block size.
|
|
// - RFC4880bis (v5 keys, regardless of AEAD): an Initial Vector (IV) of the same length as the
|
|
// cipher's block size. If string-to-key usage octet was 253 the IV is used as the nonce for the AEAD algorithm.
|
|
// If the AEAD algorithm requires a shorter nonce, the high-order bits of the IV are used and the remaining bits MUST be zero
|
|
if (this.s2kUsage !== 253 || this.isLegacyAEAD) {
|
|
this.iv = bytes.subarray(
|
|
i,
|
|
i + crypto.getCipherParams(this.symmetric).blockSize
|
|
);
|
|
this.usedModernAEAD = false;
|
|
} else {
|
|
// crypto-refresh: If string-to-key usage octet was 253 (that is, the secret data is AEAD-encrypted),
|
|
// an initialization vector (IV) of size specified by the AEAD algorithm (see Section 5.13.2), which
|
|
// is used as the nonce for the AEAD algorithm.
|
|
this.iv = bytes.subarray(
|
|
i,
|
|
i + crypto.getAEADMode(this.aead).ivLength
|
|
);
|
|
// the non-legacy AEAD encryption mechanism also authenticates public key params; no need for manual validation.
|
|
this.usedModernAEAD = true;
|
|
}
|
|
|
|
i += this.iv.length;
|
|
}
|
|
} catch (e) {
|
|
// if the s2k is unsupported, we still want to support encrypting and verifying with the given key
|
|
if (!this.s2kUsage) throw e; // always throw for decrypted keys
|
|
this.unparseableKeyMaterial = bytes.subarray(startOfSecretKeyData);
|
|
this.isEncrypted = true;
|
|
}
|
|
|
|
// - Only for a version 5 packet, a four-octet scalar octet count for
|
|
// the following key material.
|
|
if (this.version === 5) {
|
|
i += 4;
|
|
}
|
|
|
|
// - Plain or encrypted multiprecision integers comprising the secret
|
|
// key data. These algorithm-specific fields are as described
|
|
// below.
|
|
this.keyMaterial = bytes.subarray(i);
|
|
this.isEncrypted = !!this.s2kUsage;
|
|
|
|
if (!this.isEncrypted) {
|
|
let cleartext;
|
|
if (this.version === 6) {
|
|
cleartext = this.keyMaterial;
|
|
} else {
|
|
cleartext = this.keyMaterial.subarray(0, -2);
|
|
if (!util.equalsUint8Array(util.writeChecksum(cleartext), this.keyMaterial.subarray(-2))) {
|
|
throw new Error('Key checksum mismatch');
|
|
}
|
|
}
|
|
try {
|
|
const { read, privateParams } = crypto.parsePrivateKeyParams(this.algorithm, cleartext, this.publicParams);
|
|
if (read < cleartext.length) {
|
|
throw new Error('Error reading MPIs');
|
|
}
|
|
this.privateParams = privateParams;
|
|
} catch (err) {
|
|
if (err instanceof UnsupportedError) throw err;
|
|
// avoid throwing potentially sensitive errors
|
|
throw new Error('Error reading MPIs');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an OpenPGP key packet for the given key.
|
|
* @returns {Uint8Array} A string of bytes containing the secret key OpenPGP packet.
|
|
*/
|
|
write() {
|
|
const serializedPublicKey = this.writePublicKey();
|
|
if (this.unparseableKeyMaterial) {
|
|
return util.concatUint8Array([
|
|
serializedPublicKey,
|
|
this.unparseableKeyMaterial
|
|
]);
|
|
}
|
|
|
|
const arr = [serializedPublicKey];
|
|
arr.push(new Uint8Array([this.s2kUsage]));
|
|
|
|
const optionalFieldsArr = [];
|
|
// - [Optional] If string-to-key usage octet was 255, 254, or 253, a
|
|
// one- octet symmetric encryption algorithm.
|
|
if (this.s2kUsage === 255 || this.s2kUsage === 254 || this.s2kUsage === 253) {
|
|
optionalFieldsArr.push(this.symmetric);
|
|
|
|
// - [Optional] If string-to-key usage octet was 253, a one-octet
|
|
// AEAD algorithm.
|
|
if (this.s2kUsage === 253) {
|
|
optionalFieldsArr.push(this.aead);
|
|
}
|
|
|
|
const s2k = this.s2k.write();
|
|
|
|
// - [Optional] Only for a version 6 packet, and if string-to-key usage
|
|
// octet was 255, 254, or 253, an one-octet count of the following field.
|
|
if (this.version === 6) {
|
|
optionalFieldsArr.push(s2k.length);
|
|
}
|
|
|
|
// - [Optional] If string-to-key usage octet was 255, 254, or 253, a
|
|
// string-to-key specifier. The length of the string-to-key
|
|
// specifier is implied by its type, as described above.
|
|
optionalFieldsArr.push(...s2k);
|
|
}
|
|
|
|
// - [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') {
|
|
optionalFieldsArr.push(...this.iv);
|
|
}
|
|
|
|
if (this.version === 5 || (this.version === 6 && this.s2kUsage)) {
|
|
arr.push(new Uint8Array([optionalFieldsArr.length]));
|
|
}
|
|
arr.push(new Uint8Array(optionalFieldsArr));
|
|
|
|
if (!this.isDummy()) {
|
|
if (!this.s2kUsage) {
|
|
this.keyMaterial = crypto.serializeParams(this.algorithm, this.privateParams);
|
|
}
|
|
|
|
if (this.version === 5) {
|
|
arr.push(util.writeNumber(this.keyMaterial.length, 4));
|
|
}
|
|
arr.push(this.keyMaterial);
|
|
|
|
if (!this.s2kUsage && this.version !== 6) {
|
|
arr.push(util.writeChecksum(this.keyMaterial));
|
|
}
|
|
}
|
|
|
|
return util.concatUint8Array(arr);
|
|
}
|
|
|
|
/**
|
|
* Check whether secret-key data is available in decrypted form.
|
|
* Returns false for gnu-dummy keys and null for public keys.
|
|
* @returns {Boolean|null}
|
|
*/
|
|
isDecrypted() {
|
|
return this.isEncrypted === false;
|
|
}
|
|
|
|
/**
|
|
* Check whether the key includes secret key material.
|
|
* Some secret keys do not include it, and can thus only be used
|
|
* for public-key operations (encryption and verification).
|
|
* Such keys are:
|
|
* - GNU-dummy keys, where the secret material has been stripped away
|
|
* - encrypted keys with unsupported S2K or cipher
|
|
*/
|
|
isMissingSecretKeyMaterial() {
|
|
return this.unparseableKeyMaterial !== undefined || this.isDummy();
|
|
}
|
|
|
|
/**
|
|
* Check whether this is a gnu-dummy key
|
|
* @returns {Boolean}
|
|
*/
|
|
isDummy() {
|
|
return !!(this.s2k && this.s2k.type === 'gnu-dummy');
|
|
}
|
|
|
|
/**
|
|
* Remove private key material, converting the key to a dummy one.
|
|
* The resulting key cannot be used for signing/decrypting but can still verify signatures.
|
|
* @param {Object} [config] - Full configuration, defaults to openpgp.config
|
|
*/
|
|
makeDummy(config = defaultConfig) {
|
|
if (this.isDummy()) {
|
|
return;
|
|
}
|
|
if (this.isDecrypted()) {
|
|
this.clearPrivateParams();
|
|
}
|
|
delete this.unparseableKeyMaterial;
|
|
this.isEncrypted = null;
|
|
this.keyMaterial = null;
|
|
this.s2k = newS2KFromType(enums.s2k.gnu, config);
|
|
this.s2k.algorithm = 0;
|
|
this.s2k.c = 0;
|
|
this.s2k.type = 'gnu-dummy';
|
|
this.s2kUsage = 254;
|
|
this.symmetric = enums.symmetric.aes256;
|
|
this.isLegacyAEAD = null;
|
|
this.usedModernAEAD = null;
|
|
}
|
|
|
|
/**
|
|
* Encrypt the payload. By default, we use aes256 and iterated, salted string
|
|
* to key specifier. If the key is in a decrypted state (isEncrypted === false)
|
|
* and the passphrase is empty or undefined, the key will be set as not encrypted.
|
|
* This can be used to remove passphrase protection after calling decrypt().
|
|
* @param {String} passphrase
|
|
* @param {Object} [config] - Full configuration, defaults to openpgp.config
|
|
* @throws {Error} if encryption was not successful
|
|
* @async
|
|
*/
|
|
async encrypt(passphrase, config = defaultConfig) {
|
|
if (this.isDummy()) {
|
|
return;
|
|
}
|
|
|
|
if (!this.isDecrypted()) {
|
|
throw new Error('Key packet is already encrypted');
|
|
}
|
|
|
|
if (!passphrase) {
|
|
throw new Error('A non-empty passphrase is required for key encryption.');
|
|
}
|
|
|
|
this.s2k = newS2KFromConfig(config);
|
|
this.s2k.generateSalt();
|
|
const cleartext = crypto.serializeParams(this.algorithm, this.privateParams);
|
|
this.symmetric = enums.symmetric.aes256;
|
|
|
|
const { blockSize } = crypto.getCipherParams(this.symmetric);
|
|
|
|
if (config.aeadProtect) {
|
|
this.s2kUsage = 253;
|
|
this.aead = config.preferredAEADAlgorithm;
|
|
const mode = crypto.getAEADMode(this.aead);
|
|
this.isLegacyAEAD = this.version === 5; // v4 is always re-encrypted with standard format instead.
|
|
this.usedModernAEAD = !this.isLegacyAEAD; // legacy AEAD does not guarantee integrity of public key material
|
|
|
|
const serializedPacketTag = writeTag(this.constructor.tag);
|
|
const key = await produceEncryptionKey(this.version, this.s2k, passphrase, this.symmetric, this.aead, serializedPacketTag, this.isLegacyAEAD);
|
|
|
|
const modeInstance = await mode(this.symmetric, key);
|
|
this.iv = this.isLegacyAEAD ? crypto.random.getRandomBytes(blockSize) : crypto.random.getRandomBytes(mode.ivLength);
|
|
const associateData = this.isLegacyAEAD ?
|
|
new Uint8Array() :
|
|
util.concatUint8Array([serializedPacketTag, this.writePublicKey()]);
|
|
|
|
this.keyMaterial = await modeInstance.encrypt(cleartext, this.iv.subarray(0, mode.ivLength), associateData);
|
|
} else {
|
|
this.s2kUsage = 254;
|
|
this.usedModernAEAD = false;
|
|
const key = await produceEncryptionKey(this.version, this.s2k, passphrase, this.symmetric);
|
|
this.iv = crypto.random.getRandomBytes(blockSize);
|
|
this.keyMaterial = await crypto.mode.cfb.encrypt(this.symmetric, key, util.concatUint8Array([
|
|
cleartext,
|
|
await crypto.hash.sha1(cleartext, config)
|
|
]), this.iv, config);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrypts the private key params which are needed to use the key.
|
|
* Successful decryption does not imply key integrity, call validate() to confirm that.
|
|
* {@link SecretKeyPacket.isDecrypted} should be false, as
|
|
* otherwise calls to this function will throw an error.
|
|
* @param {String} passphrase - The passphrase for this private key as string
|
|
* @throws {Error} if the key is already decrypted, or if decryption was not successful
|
|
* @async
|
|
*/
|
|
async decrypt(passphrase) {
|
|
if (this.isDummy()) {
|
|
return false;
|
|
}
|
|
|
|
if (this.unparseableKeyMaterial) {
|
|
throw new Error('Key packet cannot be decrypted: unsupported S2K or cipher algo');
|
|
}
|
|
|
|
if (this.isDecrypted()) {
|
|
throw new Error('Key packet is already decrypted.');
|
|
}
|
|
|
|
let key;
|
|
const serializedPacketTag = writeTag(this.constructor.tag); // relevant for AEAD only
|
|
if (this.s2kUsage === 254 || this.s2kUsage === 253) {
|
|
key = await produceEncryptionKey(
|
|
this.version, this.s2k, passphrase, this.symmetric, this.aead, serializedPacketTag, this.isLegacyAEAD);
|
|
} else if (this.s2kUsage === 255) {
|
|
throw new Error('Encrypted private key is authenticated using an insecure two-byte hash');
|
|
} else {
|
|
throw new Error('Private key is encrypted using an insecure S2K function: unsalted MD5');
|
|
}
|
|
|
|
let cleartext;
|
|
if (this.s2kUsage === 253) {
|
|
const mode = crypto.getAEADMode(this.aead, true);
|
|
const modeInstance = await mode(this.symmetric, key);
|
|
try {
|
|
const associateData = this.isLegacyAEAD ?
|
|
new Uint8Array() :
|
|
util.concatUint8Array([serializedPacketTag, this.writePublicKey()]);
|
|
cleartext = await modeInstance.decrypt(this.keyMaterial, this.iv.subarray(0, mode.ivLength), associateData);
|
|
} catch (err) {
|
|
if (err.message === 'Authentication tag mismatch') {
|
|
throw new Error('Incorrect key passphrase: ' + err.message);
|
|
}
|
|
throw err;
|
|
}
|
|
} else {
|
|
const cleartextWithHash = await crypto.mode.cfb.decrypt(this.symmetric, key, this.keyMaterial, this.iv);
|
|
|
|
cleartext = cleartextWithHash.subarray(0, -20);
|
|
const hash = await crypto.hash.sha1(cleartext);
|
|
|
|
if (!util.equalsUint8Array(hash, cleartextWithHash.subarray(-20))) {
|
|
throw new Error('Incorrect key passphrase');
|
|
}
|
|
}
|
|
|
|
try {
|
|
const { privateParams } = crypto.parsePrivateKeyParams(this.algorithm, cleartext, this.publicParams);
|
|
this.privateParams = privateParams;
|
|
} catch (err) {
|
|
throw new Error('Error reading MPIs');
|
|
}
|
|
this.isEncrypted = false;
|
|
this.keyMaterial = null;
|
|
this.s2kUsage = 0;
|
|
this.aead = null;
|
|
this.symmetric = null;
|
|
this.isLegacyAEAD = null;
|
|
}
|
|
|
|
/**
|
|
* Checks that the key parameters are consistent
|
|
* @throws {Error} if validation was not successful
|
|
* @async
|
|
*/
|
|
async validate() {
|
|
if (this.isDummy()) {
|
|
return;
|
|
}
|
|
|
|
if (!this.isDecrypted()) {
|
|
throw new Error('Key is not decrypted');
|
|
}
|
|
|
|
if (this.usedModernAEAD) {
|
|
// key integrity confirmed by successful AEAD decryption
|
|
return;
|
|
}
|
|
|
|
let validParams;
|
|
try {
|
|
// this can throw if some parameters are undefined
|
|
validParams = await crypto.validateParams(this.algorithm, this.publicParams, this.privateParams);
|
|
} catch (_) {
|
|
validParams = false;
|
|
}
|
|
if (!validParams) {
|
|
throw new Error('Key is invalid');
|
|
}
|
|
}
|
|
|
|
async generate(bits, curve) {
|
|
// The deprecated OIDs for Ed25519Legacy and Curve25519Legacy are used in legacy version 4 keys and signatures.
|
|
// Implementations MUST NOT accept or generate v6 key material using the deprecated OIDs.
|
|
if (this.version === 6 && (
|
|
(this.algorithm === enums.publicKey.ecdh && curve === enums.curve.curve25519Legacy) ||
|
|
this.algorithm === enums.publicKey.eddsaLegacy
|
|
)) {
|
|
throw new Error(`Cannot generate v6 keys of type 'ecc' with curve ${curve}. Generate a key of type 'curve25519' instead`);
|
|
}
|
|
const { privateParams, publicParams } = await crypto.generateParams(this.algorithm, bits, curve);
|
|
this.privateParams = privateParams;
|
|
this.publicParams = publicParams;
|
|
this.isEncrypted = false;
|
|
}
|
|
|
|
/**
|
|
* Clear private key parameters
|
|
*/
|
|
clearPrivateParams() {
|
|
if (this.isMissingSecretKeyMaterial()) {
|
|
return;
|
|
}
|
|
|
|
Object.keys(this.privateParams).forEach(name => {
|
|
const param = this.privateParams[name];
|
|
param.fill(0);
|
|
delete this.privateParams[name];
|
|
});
|
|
this.privateParams = null;
|
|
this.isEncrypted = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derive encryption key
|
|
* @param {Number} keyVersion - key derivation differs for v5 keys
|
|
* @param {module:type/s2k} s2k
|
|
* @param {String} passphrase
|
|
* @param {module:enums.symmetric} cipherAlgo
|
|
* @param {module:enums.aead} [aeadMode] - for AEAD-encrypted keys only (excluding v5)
|
|
* @param {Uint8Array} [serializedPacketTag] - for AEAD-encrypted keys only (excluding v5)
|
|
* @param {Boolean} [isLegacyAEAD] - for AEAD-encrypted keys from RFC4880bis (v4 and v5 only)
|
|
* @returns encryption key
|
|
*/
|
|
async function produceEncryptionKey(keyVersion, s2k, passphrase, cipherAlgo, aeadMode, serializedPacketTag, isLegacyAEAD) {
|
|
if (s2k.type === 'argon2' && !aeadMode) {
|
|
throw new Error('Using Argon2 S2K without AEAD is not allowed');
|
|
}
|
|
if (s2k.type === 'simple' && keyVersion === 6) {
|
|
throw new Error('Using Simple S2K with version 6 keys is not allowed');
|
|
}
|
|
const { keySize } = crypto.getCipherParams(cipherAlgo);
|
|
const derivedKey = await s2k.produceKey(passphrase, keySize);
|
|
if (!aeadMode || keyVersion === 5 || isLegacyAEAD) {
|
|
return derivedKey;
|
|
}
|
|
const info = util.concatUint8Array([
|
|
serializedPacketTag,
|
|
new Uint8Array([keyVersion, cipherAlgo, aeadMode])
|
|
]);
|
|
return computeHKDF(enums.hash.sha256, derivedKey, new Uint8Array(), info, keySize);
|
|
}
|
|
|
|
export default SecretKeyPacket;
|