Add config parameter to top-level functions (#1241)

Refactor functions to take the configuration as a parameter.

This allows setting a config option for a single function call, whereas
setting `openpgp.config` could lead to concurrency-related issues when
multiple async function calls are made at the same time.

`openpgp.config` is used as default for unset config values in top-level
functions.
`openpgp.config` is used as default config object in low-level functions
(i.e., when calling a low-level function, it may be required to pass
`{ ...openpgp.config, modifiedConfig: modifiedValue }`).

Also,

- remove `config.rsaBlinding`: blinding is now always applied to RSA decryption
- remove `config.debug`: debugging mode can be enabled by setting
  `process.env.NODE_ENV = 'development'`
- remove `config.useNative`: native crypto is always used when available
This commit is contained in:
larabr
2021-02-26 20:04:54 +01:00
committed by GitHub
parent 15ee659c9c
commit 7f37a8aaca
51 changed files with 1361 additions and 1038 deletions

View File

@@ -43,7 +43,7 @@ import { createReadableStreamWrapper } from '@mattiasbuelens/web-streams-adapter
import { Message } from './message';
import { CleartextMessage } from './cleartext';
import { generate, reformat } from './key';
import config from './config/config';
import defaultConfig from './config';
import './polyfills';
import util from './util';
@@ -74,27 +74,29 @@ if (globalThis.ReadableStream) {
* @param {Number} keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires
* @param {Array<Object>} subkeys (optional) Options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
* sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<Object>} The generated key object in the form:
* { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String }
* @async
* @static
*/
export function generateKey({ userIds = [], passphrase = "", type = "ecc", rsaBits = 4096, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}] }) {
export function generateKey({ userIds = [], passphrase = "", type = "ecc", rsaBits = 4096, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}], config }) {
config = { ...defaultConfig, ...config };
userIds = toArray(userIds);
const options = { userIds, passphrase, type, rsaBits, curve, keyExpirationTime, date, subkeys };
if (type === "rsa" && rsaBits < config.minRsaBits) {
throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${rsaBits}`);
}
return generate(options).then(async key => {
const revocationCertificate = await key.getRevocationCertificate(date);
return generate(options, config).then(async key => {
const revocationCertificate = await key.getRevocationCertificate(date, config);
key.revocationSignatures = [];
return {
key: key,
privateKeyArmored: key.armor(),
publicKeyArmored: key.toPublic().armor(),
privateKeyArmored: key.armor(config),
publicKeyArmored: key.toPublic().armor(config),
revocationCertificate: revocationCertificate
};
@@ -107,24 +109,26 @@ export function generateKey({ userIds = [], passphrase = "", type = "ecc", rsaBi
* @param {Object|Array<Object>} userIds User IDs as objects: { name:'Jo Doe', email:'info@jo.com' }
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
* @param {Number} keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<Object>} The generated key object in the form:
* { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String }
* @async
* @static
*/
export function reformatKey({ privateKey, userIds = [], passphrase = "", keyExpirationTime = 0, date }) {
export function reformatKey({ privateKey, userIds = [], passphrase = "", keyExpirationTime = 0, date, config }) {
config = { ...defaultConfig, ...config };
userIds = toArray(userIds);
const options = { privateKey, userIds, passphrase, keyExpirationTime, date };
return reformat(options).then(async key => {
const revocationCertificate = await key.getRevocationCertificate(date);
return reformat(options, config).then(async key => {
const revocationCertificate = await key.getRevocationCertificate(date, config);
key.revocationSignatures = [];
return {
key: key,
privateKeyArmored: key.armor(),
publicKeyArmored: key.toPublic().armor(),
privateKeyArmored: key.armor(config),
publicKeyArmored: key.toPublic().armor(config),
revocationCertificate: revocationCertificate
};
@@ -139,33 +143,34 @@ export function reformatKey({ privateKey, userIds = [], passphrase = "", keyExpi
* @param {Object} reasonForRevocation (optional) object indicating the reason for revocation
* @param {module:enums.reasonForRevocation} reasonForRevocation.flag (optional) flag indicating the reason for revocation
* @param {String} reasonForRevocation.string (optional) string explaining the reason for revocation
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<Object>} The revoked key object in the form:
* { privateKey:Key, privateKeyArmored:String, publicKey:Key, publicKeyArmored:String }
* (if private key is passed) or { publicKey:Key, publicKeyArmored:String } (otherwise)
* @async
* @static
*/
export function revokeKey({
key, revocationCertificate, reasonForRevocation
} = {}) {
export function revokeKey({ key, revocationCertificate, reasonForRevocation, config }) {
config = { ...defaultConfig, ...config };
return Promise.resolve().then(() => {
if (revocationCertificate) {
return key.applyRevocationCertificate(revocationCertificate);
return key.applyRevocationCertificate(revocationCertificate, config);
} else {
return key.revoke(reasonForRevocation);
return key.revoke(reasonForRevocation, undefined, config);
}
}).then(async key => {
if (key.isPrivate()) {
const publicKey = key.toPublic();
return {
privateKey: key,
privateKeyArmored: key.armor(),
privateKeyArmored: key.armor(config),
publicKey: publicKey,
publicKeyArmored: publicKey.armor()
publicKeyArmored: publicKey.armor(config)
};
}
return {
publicKey: key,
publicKeyArmored: key.armor()
publicKeyArmored: key.armor(config)
};
}).catch(onError.bind(null, 'Error revoking key'));
}
@@ -175,10 +180,12 @@ export function revokeKey({
* This method does not change the original key.
* @param {Key} privateKey the private key to decrypt
* @param {String|Array<String>} passphrase the user's passphrase(s)
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<Key>} the unlocked key object
* @async
*/
export async function decryptKey({ privateKey, passphrase }) {
export async function decryptKey({ privateKey, passphrase, config }) {
config = { ...defaultConfig, ...config };
const key = await privateKey.clone();
// shallow clone is enough since the encrypted material is not changed in place by decryption
key.getKeys().forEach(k => {
@@ -188,7 +195,7 @@ export async function decryptKey({ privateKey, passphrase }) {
);
});
try {
await key.decrypt(passphrase);
await key.decrypt(passphrase, undefined, config);
return key;
} catch (err) {
key.clearPrivateParams();
@@ -201,10 +208,12 @@ export async function decryptKey({ privateKey, passphrase }) {
* This method does not change the original key.
* @param {Key} privateKey the private key to encrypt
* @param {String|Array<String>} passphrase if multiple passphrases, they should be in the same order as the packets each should encrypt
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<Key>} the locked key object
* @async
*/
export async function encryptKey({ privateKey, passphrase }) {
export async function encryptKey({ privateKey, passphrase, config }) {
config = { ...defaultConfig, ...config };
const key = await privateKey.clone();
key.getKeys().forEach(k => {
// shallow clone the key packets
@@ -221,7 +230,7 @@ export async function encryptKey({ privateKey, passphrase }) {
k.keyPacket.privateParams = privateParams;
});
try {
await key.encrypt(passphrase);
await key.encrypt(passphrase, undefined, config);
return key;
} catch (err) {
key.clearPrivateParams();
@@ -245,7 +254,6 @@ export async function encryptKey({ privateKey, passphrase }) {
* @param {Key|Array<Key>} privateKeys (optional) private keys for signing. If omitted message will not be signed
* @param {String|Array<String>} passwords (optional) array of passwords or a single password to encrypt the message
* @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String }
* @param {module:enums.compression} compression (optional) which compression algorithm to compress the message with, defaults to what is specified in config
* @param {Boolean} armor (optional) whether the return values should be ascii armored (true, the default) or binary (false)
* @param {'web'|'ponyfill'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any.
* @param {Signature} signature (optional) a detached signature to add to the encrypted message
@@ -255,11 +263,13 @@ export async function encryptKey({ privateKey, passphrase }) {
* @param {Date} date (optional) override the creation date of the message signature
* @param {Array<Object>} fromUserIds (optional) array of user IDs to sign with, one per key in `privateKeys`, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array<Object>} toUserIds (optional) array of user IDs to encrypt for, one per key in `publicKeys`, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }]
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<String|ReadableStream<String>|NodeStream<String>|Uint8Array|ReadableStream<Uint8Array>|NodeStream<Uint8Array>>} (String if `armor` was true, the default; Uint8Array if `armor` was false)
* @async
* @static
*/
export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKey, compression = config.compression, armor = true, streaming = message && message.fromStream, detached = false, signature = null, wildcard = false, signingKeyIds = [], encryptionKeyIds = [], date = new Date(), fromUserIds = [], toUserIds = [] }) {
export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKey, armor = true, streaming = message && message.fromStream, detached = false, signature = null, wildcard = false, signingKeyIds = [], encryptionKeyIds = [], date = new Date(), fromUserIds = [], toUserIds = [], config }) {
config = { ...defaultConfig, ...config };
checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); fromUserIds = toArray(fromUserIds); toUserIds = toArray(toUserIds);
if (detached) {
throw new Error("detached option has been removed from openpgp.encrypt. Separately call openpgp.sign instead. Don't forget to remove privateKeys option as well.");
@@ -270,11 +280,11 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
privateKeys = [];
}
if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified
message = await message.sign(privateKeys, signature, signingKeyIds, date, fromUserIds, message.fromStream);
message = await message.sign(privateKeys, signature, signingKeyIds, date, fromUserIds, message.fromStream, config);
}
message = message.compress(compression);
message = await message.encrypt(publicKeys, passwords, sessionKey, wildcard, encryptionKeyIds, date, toUserIds, streaming);
const data = armor ? message.armor() : message.write();
message = message.compress(config);
message = await message.encrypt(publicKeys, passwords, sessionKey, wildcard, encryptionKeyIds, date, toUserIds, streaming, config);
const data = armor ? message.armor(config) : message.write();
return convertStream(data, streaming, armor ? 'utf8' : 'binary');
}).catch(onError.bind(null, 'Error encrypting message'));
}
@@ -291,6 +301,7 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
* @param {'web'|'ponyfill'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any.
* @param {Signature} signature (optional) detached signature for verification
* @param {Date} date (optional) use the given date for verification instead of the current time
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<Object>} Object containing decrypted and verified message in the form:
*
* {
@@ -308,16 +319,17 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
* @async
* @static
*/
export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format = 'utf8', streaming = message && message.fromStream, signature = null, date = new Date() }) {
export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format = 'utf8', streaming = message && message.fromStream, signature = null, date = new Date(), config }) {
config = { ...defaultConfig, ...config };
checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys);
return message.decrypt(privateKeys, passwords, sessionKeys, streaming).then(async function(decrypted) {
return message.decrypt(privateKeys, passwords, sessionKeys, streaming, config).then(async function(decrypted) {
if (!publicKeys) {
publicKeys = [];
}
const result = {};
result.signatures = signature ? await decrypted.verifyDetached(signature, publicKeys, date, streaming) : await decrypted.verify(publicKeys, date, streaming);
result.signatures = signature ? await decrypted.verifyDetached(signature, publicKeys, date, streaming, config) : await decrypted.verify(publicKeys, date, streaming, config);
result.data = format === 'binary' ? decrypted.getLiteralData() : decrypted.getText();
result.filename = decrypted.getFilename();
linkStreams(result, message);
@@ -345,11 +357,13 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
* @param {Array<module:type/keyid>} signingKeyIds (optional) array of key IDs to use for signing. Each signingKeyIds[i] corresponds to privateKeys[i]
* @param {Date} date (optional) override the creation date of the signature
* @param {Array<Object>} fromUserIds (optional) array of user IDs to sign with, one per key in `privateKeys`, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<String|ReadableStream<String>|NodeStream<String>|Uint8Array|ReadableStream<Uint8Array>|NodeStream<Uint8Array>>} (String if `armor` was true, the default; Uint8Array if `armor` was false)
* @async
* @static
*/
export function sign({ message, privateKeys, armor = true, streaming = message && message.fromStream, detached = false, signingKeyIds = [], date = new Date(), fromUserIds = [] }) {
export function sign({ message, privateKeys, armor = true, streaming = message && message.fromStream, detached = false, signingKeyIds = [], date = new Date(), fromUserIds = [], config }) {
config = { ...defaultConfig, ...config };
checkCleartextOrMessage(message);
if (message instanceof CleartextMessage && !armor) throw new Error("Can't sign non-armored cleartext message");
if (message instanceof CleartextMessage && detached) throw new Error("Can't sign detached cleartext message");
@@ -357,12 +371,14 @@ export function sign({ message, privateKeys, armor = true, streaming = message &
return Promise.resolve().then(async function() {
let signature;
if (detached) {
signature = await message.signDetached(privateKeys, undefined, signingKeyIds, date, fromUserIds, message.fromStream);
if (message instanceof CleartextMessage) {
signature = await message.sign(privateKeys, undefined, signingKeyIds, date, fromUserIds, config);
} else if (detached) {
signature = await message.signDetached(privateKeys, undefined, signingKeyIds, date, fromUserIds, message.fromStream, config);
} else {
signature = await message.sign(privateKeys, undefined, signingKeyIds, date, fromUserIds, message.fromStream);
signature = await message.sign(privateKeys, undefined, signingKeyIds, date, fromUserIds, message.fromStream, config);
}
signature = armor ? signature.armor() : signature.write();
signature = armor ? signature.armor(config) : signature.write();
if (detached) {
signature = stream.transformPair(message.packets.write(), async (readable, writable) => {
await Promise.all([
@@ -383,6 +399,7 @@ export function sign({ message, privateKeys, armor = true, streaming = message &
* @param {'web'|'ponyfill'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any.
* @param {Signature} signature (optional) detached signature for verification
* @param {Date} date (optional) use the given date for verification instead of the current time
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<Object>} Object containing verified message in the form:
*
* {
@@ -399,14 +416,19 @@ export function sign({ message, privateKeys, armor = true, streaming = message &
* @async
* @static
*/
export function verify({ message, publicKeys, format = 'utf8', streaming = message && message.fromStream, signature = null, date = new Date() }) {
export function verify({ message, publicKeys, format = 'utf8', streaming = message && message.fromStream, signature = null, date = new Date(), config }) {
config = { ...defaultConfig, ...config };
checkCleartextOrMessage(message);
if (message instanceof CleartextMessage && format === 'binary') throw new Error("Can't return cleartext message data as binary");
publicKeys = toArray(publicKeys);
return Promise.resolve().then(async function() {
const result = {};
result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date, streaming) : await message.verify(publicKeys, date, streaming);
if (message instanceof CleartextMessage) {
result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date, config) : await message.verify(publicKeys, date, config);
} else {
result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date, streaming, config) : await message.verify(publicKeys, date, streaming, config);
}
result.data = format === 'binary' ? message.getLiteralData() : message.getText();
if (streaming) linkStreams(result, message);
result.data = await convertStream(result.data, streaming, format);
@@ -427,16 +449,18 @@ export function verify({ message, publicKeys, format = 'utf8', streaming = messa
* @param {Key|Array<Key>} publicKeys array of public keys or single key used to select algorithm preferences for
* @param {Date} date (optional) date to select algorithm preferences at
* @param {Array} toUserIds (optional) user IDs to select algorithm preferences for
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<{ data: Uint8Array, algorithm: String }>} object with session key data and algorithm
* @async
* @static
*/
export function generateSessionKey({ publicKeys, date = new Date(), toUserIds = [] }) {
export function generateSessionKey({ publicKeys, date = new Date(), toUserIds = [], config }) {
config = { ...defaultConfig, ...config };
publicKeys = toArray(publicKeys); toUserIds = toArray(toUserIds);
return Promise.resolve().then(async function() {
return Message.generateSessionKey(publicKeys, date, toUserIds);
return Message.generateSessionKey(publicKeys, date, toUserIds, config);
}).catch(onError.bind(null, 'Error generating session key'));
}
@@ -454,17 +478,19 @@ export function generateSessionKey({ publicKeys, date = new Date(), toUserIds =
* @param {Array<module:type/keyid>} encryptionKeyIds (optional) array of key IDs to use for encryption. Each encryptionKeyIds[i] corresponds to publicKeys[i]
* @param {Date} date (optional) override the date
* @param {Array} toUserIds (optional) array of user IDs to encrypt for, one per key in `publicKeys`, e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }]
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<String|Uint8Array>} (String if `armor` was true, the default; Uint8Array if `armor` was false)
* @async
* @static
*/
export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, passwords, armor = true, wildcard = false, encryptionKeyIds = [], date = new Date(), toUserIds = [] }) {
export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, passwords, armor = true, wildcard = false, encryptionKeyIds = [], date = new Date(), toUserIds = [], config }) {
config = { ...defaultConfig, ...config };
checkBinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords); toUserIds = toArray(toUserIds);
return Promise.resolve().then(async function() {
const message = await Message.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, encryptionKeyIds, date, toUserIds);
return armor ? message.armor() : message.write();
const message = await Message.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, encryptionKeyIds, date, toUserIds, config);
return armor ? message.armor(config) : message.write();
}).catch(onError.bind(null, 'Error encrypting session key'));
}
@@ -472,21 +498,23 @@ export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys,
/**
* Decrypt symmetric session keys with a private key or password. Either a private key or
* a password must be specified.
* @param {Message} message a message object containing the encrypted session key packets
* @param {Message} message a message object containing the encrypted session key packets
* @param {Key|Array<Key>} privateKeys (optional) private keys with decrypted secret key data
* @param {String|Array<String>} passwords (optional) passwords to decrypt the session key
* @returns {Promise<Object|undefined>} Array of decrypted session key, algorithm pairs in form:
* { data:Uint8Array, algorithm:String }
* or 'undefined' if no key packets found
* @param {Object} config (optional) custom configuration settings to overwrite those in openpgp.config
* @returns {Promise<Object|undefined>} Array of decrypted session key, algorithm pairs in form:
* { data:Uint8Array, algorithm:String }
* or 'undefined' if no key packets found
* @async
* @static
*/
export function decryptSessionKeys({ message, privateKeys, passwords }) {
export function decryptSessionKeys({ message, privateKeys, passwords, config }) {
config = { ...defaultConfig, ...config };
checkMessage(message); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
return Promise.resolve().then(async function() {
return message.decryptSessionKeys(privateKeys, passwords);
return message.decryptSessionKeys(privateKeys, passwords, config);
}).catch(onError.bind(null, 'Error decrypting session keys'));
}