mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-10-14 00:59:29 +00:00
RFC9580 says that:
Argon2 is only used with AEAD (S2K usage octet 253). An
implementation MUST NOT create and MUST reject as malformed any
secret key packet where the S2K usage octet is not AEAD (253) and
the S2K specifier type is Argon2.
Therefore, we disallow reading and writing Argon2 keys without AEAD.
And:
[The Simple and Salted S2K methods] are used only for reading in
backwards compatibility mode.
Since v6 keys don't need backwards compatibility, we also disallow
reading Simple S2K there. We still allow reading Salted S2K since the
spec says it may be used "when [the password] is high entropy".
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);
|
|
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;
|