From ce704847389ea2b70fe5db1bdc181113ccd50f93 Mon Sep 17 00:00:00 2001 From: larabr Date: Mon, 19 Jul 2021 18:12:42 +0200 Subject: [PATCH] Replace `armor` option with `format` in `openpgp.encrypt`, `sign` and `encryptSessionKey` (#1354) Breaking changes: - a new `format` option has been added to `openpgp.encrypt`, `sign` and `encryptSessionKey` to select the format of the output message. `format` replaces the existing `armor` option, and accepts three values: * if `format: 'armor'` (default), an armored signed/encrypted message is returned (same as `armor: true`). * if `format: 'binary'`, a binary signed/encrypted message is returned (same as `armor: false`). * if `format: 'object'`, a Message or Signature object is returned (this was not supported before). This change is to uniform the output format selection across all top-level functions (following up to #1345). - All top-level functions now throw if unrecognised options are passed, to make library users aware that those options are not being applied. --- README.md | 4 +- openpgp.d.ts | 40 +++-- src/cleartext.js | 8 +- src/key/factory.js | 12 +- src/message.js | 8 +- src/openpgp.js | 107 +++++++----- src/signature.js | 4 +- test/general/config.js | 4 +- test/general/key.js | 24 +-- test/general/openpgp.js | 296 +++++++++++++++++++++++++++++++-- test/general/signature.js | 2 +- test/general/streaming.js | 14 +- test/security/subkey_trust.js | 11 +- test/typescript/definitions.ts | 29 ++-- 14 files changed, 443 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index a5b86c10..5b3227c2 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ Encryption will use the algorithm specified in config.preferredSymmetricAlgorith const encrypted = await openpgp.encrypt({ message, // input as Message object passwords: ['secret stuff'], // multiple passwords possible - armor: false // don't ASCII armor (for Uint8Array output) + format: 'binary' // don't ASCII armor (for Uint8Array output) }); console.log(encrypted); // Uint8Array @@ -358,7 +358,7 @@ Where the value can be any of: const encrypted = await openpgp.encrypt({ message, // input as Message object passwords: ['secret stuff'], // multiple passwords possible - armor: false // don't ASCII armor (for Uint8Array output) + format: 'binary' // don't ASCII armor (for Uint8Array output) }); console.log(encrypted); // raw encrypted packets as ReadableStream diff --git a/openpgp.d.ts b/openpgp.d.ts index be7f12e7..32d33493 100644 --- a/openpgp.d.ts +++ b/openpgp.d.ts @@ -172,11 +172,14 @@ export class CleartextMessage { /* ############## v5 MSG #################### */ export function generateSessionKey(options: { encryptionKeys: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig }): Promise; export function encryptSessionKey(options: SessionKey & { - encryptionKeys?: MaybeArray, passwords?: MaybeArray, armor?: true, wildcard?: boolean, encryptionKeyIDs?: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig + encryptionKeys?: MaybeArray, passwords?: MaybeArray, format?: 'armor', wildcard?: boolean, encryptionKeyIDs?: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig }) : Promise; export function encryptSessionKey(options: SessionKey & { - encryptionKeys?: MaybeArray, passwords?: MaybeArray, armor: false, wildcard?: boolean, encryptionKeyIDs?: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig + encryptionKeys?: MaybeArray, passwords?: MaybeArray, format: 'binary', wildcard?: boolean, encryptionKeyIDs?: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig }) : Promise; +export function encryptSessionKey(options: SessionKey & { + encryptionKeys?: MaybeArray, passwords?: MaybeArray, format: 'object', wildcard?: boolean, encryptionKeyIDs?: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig +}) : Promise>; export function decryptSessionKeys>(options: { message: Message, decryptionKeys?: MaybeArray, passwords?: MaybeArray, date?: Date, config?: PartialConfig }): Promise; export function readMessage>(options: { armoredMessage: T, config?: PartialConfig }): Promise>; @@ -185,28 +188,31 @@ export function readMessage>(options: { binary export function createMessage>(options: { text: T, filename?: string, date?: Date, type?: DataPacketType }): Promise>; export function createMessage>(options: { binary: T, filename?: string, date?: Date, type?: DataPacketType }): Promise>; -export function encrypt>(options: EncryptOptions & { message: Message, armor: false }): Promise< - T extends WebStream ? WebStream : - T extends NodeStream ? NodeStream : - Uint8Array ->; -export function encrypt>(options: EncryptOptions & { message: Message }): Promise< +export function encrypt>(options: EncryptOptions & { message: Message, format?: 'armor' }): Promise< T extends WebStream ? WebStream : T extends NodeStream ? NodeStream : string >; +export function encrypt>(options: EncryptOptions & { message: Message, format: 'binary' }): Promise< + T extends WebStream ? WebStream : + T extends NodeStream ? NodeStream : + Uint8Array +>; +export function encrypt>(options: EncryptOptions & { message: Message, format: 'object' }): Promise>; -export function sign>(options: SignOptions & { message: Message, armor: false }): Promise< - T extends WebStream ? WebStream : - T extends NodeStream ? NodeStream : - Uint8Array ->; -export function sign>(options: SignOptions & { message: Message }): Promise< +export function sign>(options: SignOptions & { message: Message, format?: 'armor' }): Promise< T extends WebStream ? WebStream : T extends NodeStream ? NodeStream : string >; -export function sign(options: SignOptions & { message: CleartextMessage }): Promise; +export function sign>(options: SignOptions & { message: Message, format: 'binary' }): Promise< + T extends WebStream ? WebStream : + T extends NodeStream ? NodeStream : + Uint8Array +>; +export function sign>(options: SignOptions & { message: Message, format: 'object' }): Promise>; +export function sign(options: SignOptions & { message: CleartextMessage, format?: 'armor' }): Promise; +export function sign(options: SignOptions & { message: CleartextMessage, format: 'object' }): Promise; export function decrypt>(options: DecryptOptions & { message: Message, format: 'binary' }): Promise>; signingKeys?: MaybeArray; - armor?: boolean; + format?: 'armor' | 'binary' | 'object'; dataType?: DataPacketType; detached?: boolean; signingKeyIDs?: MaybeArray; diff --git a/src/cleartext.js b/src/cleartext.js index 98ceb4c4..d0b91405 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -132,7 +132,7 @@ export class CleartextMessage { * @async * @static */ -export async function readCleartextMessage({ cleartextMessage, config }) { +export async function readCleartextMessage({ cleartextMessage, config, ...rest }) { config = { ...defaultConfig, ...config }; if (!cleartextMessage) { throw new Error('readCleartextMessage: must pass options object containing `cleartextMessage`'); @@ -140,6 +140,8 @@ export async function readCleartextMessage({ cleartextMessage, config }) { if (!util.isString(cleartextMessage)) { throw new Error('readCleartextMessage: options.cleartextMessage must be a string'); } + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + const input = await unarmor(cleartextMessage); if (input.type !== enums.armor.signed) { throw new Error('No cleartext signed message.'); @@ -203,12 +205,14 @@ function verifyHeaders(headers, packetlist) { * @static * @async */ -export async function createCleartextMessage({ text }) { +export async function createCleartextMessage({ text, ...rest }) { if (!text) { throw new Error('createCleartextMessage: must pass options object containing `text`'); } if (!util.isString(text)) { throw new Error('createCleartextMessage: options.text must be a string'); } + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + return new CleartextMessage(text); } diff --git a/src/key/factory.js b/src/key/factory.js index 1a738b2b..b30bbd14 100644 --- a/src/key/factory.js +++ b/src/key/factory.js @@ -275,7 +275,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf * @async * @static */ -export async function readKey({ armoredKey, binaryKey, config }) { +export async function readKey({ armoredKey, binaryKey, config, ...rest }) { config = { ...defaultConfig, ...config }; if (!armoredKey && !binaryKey) { throw new Error('readKey: must pass options object containing `armoredKey` or `binaryKey`'); @@ -286,6 +286,8 @@ export async function readKey({ armoredKey, binaryKey, config }) { if (binaryKey && !util.isUint8Array(binaryKey)) { throw new Error('readKey: options.binaryKey must be a Uint8Array'); } + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + let input; if (armoredKey) { const { type, data } = await unarmor(armoredKey, config); @@ -310,7 +312,7 @@ export async function readKey({ armoredKey, binaryKey, config }) { * @async * @static */ -export async function readPrivateKey({ armoredKey, binaryKey, config }) { +export async function readPrivateKey({ armoredKey, binaryKey, config, ...rest }) { config = { ...defaultConfig, ...config }; if (!armoredKey && !binaryKey) { throw new Error('readPrivateKey: must pass options object containing `armoredKey` or `binaryKey`'); @@ -321,6 +323,8 @@ export async function readPrivateKey({ armoredKey, binaryKey, config }) { if (binaryKey && !util.isUint8Array(binaryKey)) { throw new Error('readPrivateKey: options.binaryKey must be a Uint8Array'); } + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + let input; if (armoredKey) { const { type, data } = await unarmor(armoredKey, config); @@ -345,7 +349,7 @@ export async function readPrivateKey({ armoredKey, binaryKey, config }) { * @async * @static */ -export async function readKeys({ armoredKeys, binaryKeys, config }) { +export async function readKeys({ armoredKeys, binaryKeys, config, ...rest }) { config = { ...defaultConfig, ...config }; let input = armoredKeys || binaryKeys; if (!input) { @@ -357,6 +361,8 @@ export async function readKeys({ armoredKeys, binaryKeys, config }) { if (binaryKeys && !util.isUint8Array(binaryKeys)) { throw new Error('readKeys: options.binaryKeys must be a Uint8Array'); } + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + if (armoredKeys) { const { type, data } = await unarmor(armoredKeys, config); if (type !== enums.armor.publicKey && type !== enums.armor.privateKey) { diff --git a/src/message.js b/src/message.js index 825f62e3..628f8bf6 100644 --- a/src/message.js +++ b/src/message.js @@ -790,7 +790,7 @@ export async function createVerificationObjects(signatureList, literalDataList, * @async * @static */ -export async function readMessage({ armoredMessage, binaryMessage, config }) { +export async function readMessage({ armoredMessage, binaryMessage, config, ...rest }) { config = { ...defaultConfig, ...config }; let input = armoredMessage || binaryMessage; if (!input) { @@ -802,6 +802,8 @@ export async function readMessage({ armoredMessage, binaryMessage, config }) { if (binaryMessage && !util.isUint8Array(binaryMessage) && !util.isStream(binaryMessage)) { throw new Error('readMessage: options.binaryMessage must be a Uint8Array or stream'); } + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + const streamType = util.isStream(input); if (streamType) { await stream.loadStreamsPonyfill(); @@ -832,7 +834,7 @@ export async function readMessage({ armoredMessage, binaryMessage, config }) { * @async * @static */ -export async function createMessage({ text, binary, filename, date = new Date(), format = text !== undefined ? 'utf8' : 'binary' }) { +export async function createMessage({ text, binary, filename, date = new Date(), format = text !== undefined ? 'utf8' : 'binary', ...rest }) { let input = text !== undefined ? text : binary; if (input === undefined) { throw new Error('createMessage: must pass options object containing `text` or `binary`'); @@ -843,6 +845,8 @@ export async function createMessage({ text, binary, filename, date = new Date(), if (binary && !util.isUint8Array(binary) && !util.isStream(binary)) { throw new Error('createMessage: options.binary must be a Uint8Array or stream'); } + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + const streamType = util.isStream(input); if (streamType) { await stream.loadStreamsPonyfill(); diff --git a/src/openpgp.js b/src/openpgp.js index 3ef5ced0..160fc0c9 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -32,6 +32,7 @@ import util from './util'; /** * Generates a new OpenPGP key pair. Supports RSA and ECC keys. By default, primary and subkeys will be of same type. + * The generated primary key will have signing capabilities. By default, one subkey with encryption capabilities is also generated. * @param {Object} options * @param {Object|Array} options.userIDs - User IDs as objects: `{ name: 'Jo Doe', email: 'info@jo.com' }` * @param {'ecc'|'rsa'} [options.type='ecc'] - The primary key algorithm type: ECC (default) or RSA @@ -51,9 +52,11 @@ import util from './util'; * @async * @static */ -export async function generateKey({ userIDs = [], passphrase = '', type = 'ecc', rsaBits = 4096, curve = 'curve25519', keyExpirationTime = 0, date = new Date(), subkeys = [{}], format = 'armor', config }) { +export async function generateKey({ userIDs = [], passphrase = '', type = 'ecc', rsaBits = 4096, curve = 'curve25519', keyExpirationTime = 0, date = new Date(), subkeys = [{}], format = 'armor', config, ...rest }) { config = { ...defaultConfig, ...config }; userIDs = toArray(userIDs); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + if (userIDs.length === 0) { throw new Error('UserIDs are required for key generation'); } @@ -66,8 +69,8 @@ export async function generateKey({ userIDs = [], passphrase = '', type = 'ecc', const { key, revocationCertificate } = await generate(options, config); return { - privateKey: formatKey(key, format, config), - publicKey: formatKey(key.toPublic(), format, config), + privateKey: formatObject(key, format, config), + publicKey: formatObject(key.toPublic(), format, config), revocationCertificate }; } catch (err) { @@ -90,9 +93,11 @@ export async function generateKey({ userIDs = [], passphrase = '', type = 'ecc', * @async * @static */ -export async function reformatKey({ privateKey, userIDs = [], passphrase = '', keyExpirationTime = 0, date, format = 'armor', config }) { +export async function reformatKey({ privateKey, userIDs = [], passphrase = '', keyExpirationTime = 0, date, format = 'armor', config, ...rest }) { config = { ...defaultConfig, ...config }; userIDs = toArray(userIDs); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + if (userIDs.length === 0) { throw new Error('UserIDs are required for key reformat'); } @@ -102,8 +107,8 @@ export async function reformatKey({ privateKey, userIDs = [], passphrase = '', k const { key: reformattedKey, revocationCertificate } = await reformat(options, config); return { - privateKey: formatKey(reformattedKey, format, config), - publicKey: formatKey(reformattedKey.toPublic(), format, config), + privateKey: formatObject(reformattedKey, format, config), + publicKey: formatObject(reformattedKey.toPublic(), format, config), revocationCertificate }; } catch (err) { @@ -129,19 +134,21 @@ export async function reformatKey({ privateKey, userIDs = [], passphrase = '', k * @async * @static */ -export async function revokeKey({ key, revocationCertificate, reasonForRevocation, date = new Date(), format = 'armor', config }) { +export async function revokeKey({ key, revocationCertificate, reasonForRevocation, date = new Date(), format = 'armor', config, ...rest }) { config = { ...defaultConfig, ...config }; + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + try { const revokedKey = revocationCertificate ? await key.applyRevocationCertificate(revocationCertificate, date, config) : await key.revoke(reasonForRevocation, date, config); return revokedKey.isPrivate() ? { - privateKey: formatKey(revokedKey, format, config), - publicKey: formatKey(revokedKey.toPublic(), format, config) + privateKey: formatObject(revokedKey, format, config), + publicKey: formatObject(revokedKey.toPublic(), format, config) } : { privateKey: null, - publicKey: formatKey(revokedKey, format, config) + publicKey: formatObject(revokedKey, format, config) }; } catch (err) { throw util.wrapError('Error revoking key', err); @@ -158,8 +165,10 @@ export async function revokeKey({ key, revocationCertificate, reasonForRevocatio * @returns {Promise} The unlocked key object. * @async */ -export async function decryptKey({ privateKey, passphrase, config }) { +export async function decryptKey({ privateKey, passphrase, config, ...rest }) { config = { ...defaultConfig, ...config }; + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + if (!privateKey.isPrivate()) { throw new Error('Cannot decrypt a public key'); } @@ -190,8 +199,10 @@ export async function decryptKey({ privateKey, passphrase, config }) { * @returns {Promise} The locked key object. * @async */ -export async function encryptKey({ privateKey, passphrase, config }) { +export async function encryptKey({ privateKey, passphrase, config, ...rest }) { config = { ...defaultConfig, ...config }; + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + if (!privateKey.isPrivate()) { throw new Error('Cannot encrypt a public key'); } @@ -233,7 +244,7 @@ export async function encryptKey({ privateKey, passphrase, config }) { * @param {PrivateKey|PrivateKey[]} [options.signingKeys] - Private keys for signing. If omitted message will not be signed * @param {String|String[]} [options.passwords] - Array of passwords or a single password to encrypt the message * @param {Object} [options.sessionKey] - Session key in the form: `{ data:Uint8Array, algorithm:String }` - * @param {Boolean} [options.armor=true] - Whether the return values should be ascii armored (true, the default) or binary (false) + * @param {'armor'|'binary'|'object'} [options.format='armor'] - Format of the returned message * @param {Signature} [options.signature] - A detached signature to add to the encrypted message * @param {Boolean} [options.wildcard=false] - Use a key ID of 0 instead of the public key IDs * @param {KeyID|KeyID[]} [options.signingKeyIDs=latest-created valid signing (sub)keys] - Array of key IDs to use for signing. Each `signingKeyIDs[i]` corresponds to `signingKeys[i]` @@ -246,20 +257,22 @@ export async function encryptKey({ privateKey, passphrase, config }) { * @async * @static */ -export async function encrypt({ message, encryptionKeys, signingKeys, passwords, sessionKey, armor = true, signature = null, wildcard = false, signingKeyIDs = [], encryptionKeyIDs = [], date = new Date(), signingUserIDs = [], encryptionUserIDs = [], config, ...rest }) { +export async function encrypt({ message, encryptionKeys, signingKeys, passwords, sessionKey, format = 'armor', signature = null, wildcard = false, signingKeyIDs = [], encryptionKeyIDs = [], date = new Date(), signingUserIDs = [], encryptionUserIDs = [], config, ...rest }) { config = { ...defaultConfig, ...config }; - checkMessage(message); encryptionKeys = toArray(encryptionKeys); signingKeys = toArray(signingKeys); passwords = toArray(passwords); + checkMessage(message); checkOutputMessageFormat(format); + encryptionKeys = toArray(encryptionKeys); signingKeys = toArray(signingKeys); passwords = toArray(passwords); signingKeyIDs = toArray(signingKeyIDs); encryptionKeyIDs = toArray(encryptionKeyIDs); signingUserIDs = toArray(signingUserIDs); encryptionUserIDs = toArray(encryptionUserIDs); if (rest.detached) { throw new Error("The `detached` option has been removed from openpgp.encrypt, separately call openpgp.sign instead. Don't forget to remove the `privateKeys` option as well."); } if (rest.publicKeys) throw new Error('The `publicKeys` option has been removed from openpgp.encrypt, pass `encryptionKeys` instead'); if (rest.privateKeys) throw new Error('The `privateKeys` option has been removed from openpgp.encrypt, pass `signingKeys` instead'); + if (rest.armor !== undefined) throw new Error('The `armor` option has been removed from openpgp.encrypt, pass `format` instead.'); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); if (!signingKeys) { signingKeys = []; } - const streaming = message.fromStream; try { if (signingKeys.length || signature) { // sign the message only if signing keys or signature is specified @@ -270,6 +283,9 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords, config ); message = await message.encrypt(encryptionKeys, passwords, sessionKey, wildcard, encryptionKeyIDs, date, encryptionUserIDs, config); + if (format === 'object') return message; + // serialize data + const armor = format === 'armor'; const data = armor ? message.armor(config) : message.write(); return convertStream(data, streaming, armor ? 'utf8' : 'binary'); } catch (err) { @@ -313,6 +329,7 @@ export async function decrypt({ message, decryptionKeys, passwords, sessionKeys, checkMessage(message); verificationKeys = toArray(verificationKeys); decryptionKeys = toArray(decryptionKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys); if (rest.privateKeys) throw new Error('The `privateKeys` option has been removed from openpgp.decrypt, pass `decryptionKeys` instead'); if (rest.publicKeys) throw new Error('The `publicKeys` option has been removed from openpgp.decrypt, pass `verificationKeys` instead'); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); try { const decrypted = await message.decrypt(decryptionKeys, passwords, sessionKeys, date, config); @@ -359,24 +376,28 @@ export async function decrypt({ message, decryptionKeys, passwords, sessionKeys, * @param {Object} options * @param {CleartextMessage|Message} options.message - (cleartext) message to be signed * @param {PrivateKey|PrivateKey[]} options.signingKeys - Array of keys or single key with decrypted secret key data to sign cleartext - * @param {Boolean} [options.armor=true] - Whether the return values should be ascii armored (true, the default) or binary (false) + * @param {'armor'|'binary'|'object'} [options.format='armor'] - Format of the returned message * @param {Boolean} [options.detached=false] - If the return value should contain a detached signature * @param {KeyID|KeyID[]} [options.signingKeyIDs=latest-created valid signing (sub)keys] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i] * @param {Date} [options.date=current date] - Override the creation date of the signature * @param {Object|Object[]} [options.signingUserIDs=primary user IDs] - Array of user IDs to sign with, one per key in `signingKeys`, e.g. `[{ name: 'Steve Sender', email: 'steve@openpgp.org' }]` * @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config} - * @returns {Promise|MaybeStream>} Signed message (string if `armor` was true, the default; Uint8Array if `armor` was false). + * @returns {Promise>} Signed message (string if `armor` was true, the default; Uint8Array if `armor` was false). * @async * @static */ -export async function sign({ message, signingKeys, armor = true, detached = false, signingKeyIDs = [], date = new Date(), signingUserIDs = [], config, ...rest }) { +export async function sign({ message, signingKeys, format = 'armor', detached = false, signingKeyIDs = [], date = new Date(), signingUserIDs = [], config, ...rest }) { config = { ...defaultConfig, ...config }; - checkCleartextOrMessage(message); - if (rest.privateKeys) throw new Error('The `privateKeys` option has been removed from openpgp.sign, pass `signingKeys` instead'); - 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 detach-sign a cleartext message"); - + checkCleartextOrMessage(message); checkOutputMessageFormat(format); signingKeys = toArray(signingKeys); signingKeyIDs = toArray(signingKeyIDs); signingUserIDs = toArray(signingUserIDs); + + if (rest.privateKeys) throw new Error('The `privateKeys` option has been removed from openpgp.sign, pass `signingKeys` instead'); + if (rest.armor !== undefined) throw new Error('The `armor` option has been removed from openpgp.sign, pass `format` instead.'); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + + if (message instanceof CleartextMessage && format === 'binary') throw new Error('Cannot return signed cleartext message in binary format'); + if (message instanceof CleartextMessage && detached) throw new Error('Cannot detach-sign a cleartext message'); + if (!signingKeys || signingKeys.length === 0) { throw new Error('No signing keys provided'); } @@ -388,6 +409,9 @@ export async function sign({ message, signingKeys, armor = true, detached = fals } else { signature = await message.sign(signingKeys, undefined, signingKeyIDs, date, signingUserIDs, config); } + if (format === 'object') return signature; + + const armor = format === 'armor'; signature = armor ? signature.armor(config) : signature.write(); if (detached) { signature = stream.transformPair(message.packets.write(), async (readable, writable) => { @@ -431,13 +455,13 @@ export async function sign({ message, signingKeys, armor = true, detached = fals */ export async function verify({ message, verificationKeys, expectSigned = false, format = 'utf8', signature = null, date = new Date(), config, ...rest }) { config = { ...defaultConfig, ...config }; - checkCleartextOrMessage(message); + checkCleartextOrMessage(message); verificationKeys = toArray(verificationKeys); if (rest.publicKeys) throw new Error('The `publicKeys` option has been removed from openpgp.verify, pass `verificationKeys` instead'); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + if (message instanceof CleartextMessage && format === 'binary') throw new Error("Can't return cleartext message data as binary"); if (message instanceof CleartextMessage && signature) throw new Error("Can't verify detached cleartext signature"); - verificationKeys = toArray(verificationKeys); - try { const result = {}; if (signature) { @@ -487,6 +511,7 @@ export async function generateSessionKey({ encryptionKeys, date = new Date(), en config = { ...defaultConfig, ...config }; encryptionKeys = toArray(encryptionKeys); encryptionUserIDs = toArray(encryptionUserIDs); if (rest.publicKeys) throw new Error('The `publicKeys` option has been removed from openpgp.generateSessionKey, pass `encryptionKeys` instead'); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); try { const sessionKeys = await Message.generateSessionKey(encryptionKeys, date, encryptionUserIDs, config); @@ -505,7 +530,7 @@ export async function generateSessionKey({ encryptionKeys, date = new Date(), en * @param {String} [options.aeadAlgorithm] - AEAD algorithm, e.g. 'eax' or 'ocb' * @param {PublicKey|PublicKey[]} [options.encryptionKeys] - Array of public keys or single key, used to encrypt the key * @param {String|String[]} [options.passwords] - Passwords for the message - * @param {Boolean} [options.armor=true] - Whether the return values should be ascii armored (true, the default) or binary (false) + * @param {'armor'|'binary'} [options.format='armor'] - Format of the returned value * @param {Boolean} [options.wildcard=false] - Use a key ID of 0 instead of the public key IDs * @param {KeyID|KeyID[]} [options.encryptionKeyIDs=latest-created valid encryption (sub)keys] - Array of key IDs to use for encryption. Each encryptionKeyIDs[i] corresponds to encryptionKeys[i] * @param {Date} [options.date=current date] - Override the date @@ -515,14 +540,16 @@ export async function generateSessionKey({ encryptionKeys, date = new Date(), en * @async * @static */ -export async function encryptSessionKey({ data, algorithm, aeadAlgorithm, encryptionKeys, passwords, armor = true, wildcard = false, encryptionKeyIDs = [], date = new Date(), encryptionUserIDs = [], config, ...rest }) { +export async function encryptSessionKey({ data, algorithm, aeadAlgorithm, encryptionKeys, passwords, format = 'armor', wildcard = false, encryptionKeyIDs = [], date = new Date(), encryptionUserIDs = [], config, ...rest }) { config = { ...defaultConfig, ...config }; - checkBinary(data); checkString(algorithm, 'algorithm'); encryptionKeys = toArray(encryptionKeys); passwords = toArray(passwords); encryptionKeyIDs = toArray(encryptionKeyIDs); encryptionUserIDs = toArray(encryptionUserIDs); + checkBinary(data); checkString(algorithm, 'algorithm'); checkOutputMessageFormat(format); + encryptionKeys = toArray(encryptionKeys); passwords = toArray(passwords); encryptionKeyIDs = toArray(encryptionKeyIDs); encryptionUserIDs = toArray(encryptionUserIDs); if (rest.publicKeys) throw new Error('The `publicKeys` option has been removed from openpgp.encryptSessionKey, pass `encryptionKeys` instead'); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); try { const message = await Message.encryptSessionKey(data, algorithm, aeadAlgorithm, encryptionKeys, passwords, wildcard, encryptionKeyIDs, date, encryptionUserIDs, config); - return armor ? message.armor(config) : message.write(); + return formatObject(message, format, config); } catch (err) { throw util.wrapError('Error encrypting session key', err); } @@ -547,6 +574,7 @@ export async function decryptSessionKeys({ message, decryptionKeys, passwords, d config = { ...defaultConfig, ...config }; checkMessage(message); decryptionKeys = toArray(decryptionKeys); passwords = toArray(passwords); if (rest.privateKeys) throw new Error('The `privateKeys` option has been removed from openpgp.decryptSessionKeys, pass `decryptionKeys` instead'); + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); try { const sessionKeys = await message.decryptSessionKeys(decryptionKeys, passwords, date, config); @@ -588,6 +616,11 @@ function checkCleartextOrMessage(message) { throw new Error('Parameter [message] needs to be of type Message or CleartextMessage'); } } +function checkOutputMessageFormat(format) { + if (format !== 'armor' && format !== 'binary' && format !== 'object') { + throw new Error(`Unsupported format ${format}`); + } +} /** * Normalize parameter to an array if it is not undefined. @@ -652,20 +685,20 @@ function linkStreams(result, message) { } /** - * Convert the key object to the given format - * @param {Key} key + * Convert the object to the given format + * @param {Key|Message} object * @param {'armor'|'binary'|'object'} format * @param {Object} config - Full configuration * @returns {String|Uint8Array|Object} */ -function formatKey(key, format, config) { +function formatObject(object, format, config) { switch (format) { case 'object': - return key; + return object; case 'armor': - return key.armor(config); + return object.armor(config); case 'binary': - return key.write(); + return object.write(); default: throw new Error(`Unsupported format ${format}`); } diff --git a/src/signature.js b/src/signature.js index dd05363c..ae9e5623 100644 --- a/src/signature.js +++ b/src/signature.js @@ -71,7 +71,7 @@ export class Signature { * @async * @static */ -export async function readSignature({ armoredSignature, binarySignature, config }) { +export async function readSignature({ armoredSignature, binarySignature, config, ...rest }) { config = { ...defaultConfig, ...config }; let input = armoredSignature || binarySignature; if (!input) { @@ -83,6 +83,8 @@ export async function readSignature({ armoredSignature, binarySignature, config if (binarySignature && !util.isUint8Array(binarySignature)) { throw new Error('readSignature: options.binarySignature must be a Uint8Array'); } + const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`); + if (armoredSignature) { const { type, data } = await unarmor(input, config); if (type !== enums.armor.signature) { diff --git a/test/general/config.js b/test/general/config.js index ae66d181..71f6005f 100644 --- a/test/general/config.js +++ b/test/general/config.js @@ -182,11 +182,11 @@ vAFM3jjrAQDgJPXsv8PqCrLGDuMa/2r6SgzYd03aw/xt1WM6hgUvhQD+J54Z const userIDs = { name: 'Test User', email: 'text2@example.com' }; const { privateKey } = await openpgp.generateKey({ userIDs, format: 'object' }); - const encKey = await openpgp.encryptKey({ privateKey, userIDs, passphrase }); + const encKey = await openpgp.encryptKey({ privateKey, passphrase }); expect(encKey.keyPacket.s2k.c).to.equal(openpgp.config.s2kIterationCountByte); const config = { s2kIterationCountByte: 123 }; - const encKey2 = await openpgp.encryptKey({ privateKey, userIDs, passphrase, config }); + const encKey2 = await openpgp.encryptKey({ privateKey, passphrase, config }); expect(encKey2.keyPacket.s2k.c).to.equal(config.s2kIterationCountByte); } finally { openpgp.config.s2kIterationCountByte = s2kIterationCountByteVal; diff --git a/test/general/key.js b/test/general/key.js index b4cd1c07..1edca008 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2591,7 +2591,7 @@ function versionSpecificTests() { expect(newKey.users.length).to.equal(1); expect(newKey.users[0].userID.userID).to.equal('test '); expect(newKey.isDecrypted()).to.be.true; - return openpgp.sign({ message: await openpgp.createCleartextMessage({ text: 'hello' }), signingKeys: newKey, armor: true }).then(async function(signed) { + return openpgp.sign({ message: await openpgp.createCleartextMessage({ text: 'hello' }), signingKeys: newKey }).then(async function(signed) { return openpgp.verify( { message: await openpgp.readCleartextMessage({ cleartextMessage: signed }), verificationKeys: newKeyPublic } ).then(async function(verified) { @@ -2629,7 +2629,7 @@ function versionSpecificTests() { const opt2 = { privateKey, userIDs: userID2, format: 'object' }; return openpgp.reformatKey(opt2).then(async function({ privateKey: newKey, publicKey: newKeyPublic }) { const encrypted = await openpgp.encrypt({ - message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: newKey.toPublic(), signingKeys: newKey, armor: true, config: { minRSABits: 1024 } + message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: newKey.toPublic(), signingKeys: newKey, config: { minRSABits: 1024 } }); const decrypted = await openpgp.decrypt({ message: await openpgp.readMessage({ armoredMessage: encrypted }), decryptionKeys: newKey, verificationKeys: newKeyPublic, config: { minRSABits: 1024 } @@ -3442,10 +3442,10 @@ VYGdb3eNlV8CfoEC expect(sessionKey.algorithm).to.equal('aes128'); const config = { minRSABits: 1024 }; await openpgp.encrypt({ - message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: publicKey, signingKeys: privateKey, encryptionUserIDs: { name: 'Test User', email: 'b@c.com' }, armor: false, config + message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: publicKey, signingKeys: privateKey, encryptionUserIDs: { name: 'Test User', email: 'b@c.com' }, format: 'binary', config }); await expect(openpgp.encrypt({ - message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: publicKey, signingKeys: privateKey, encryptionUserIDs: { name: 'Test User', email: 'c@c.com' }, armor: false, config + message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: publicKey, signingKeys: privateKey, encryptionUserIDs: { name: 'Test User', email: 'c@c.com' }, format: 'binary', config })).to.be.rejectedWith('Could not find user that matches that user ID'); }); @@ -3456,7 +3456,7 @@ VYGdb3eNlV8CfoEC privateKey: await openpgp.readKey({ armoredKey: uidlessKey }), passphrase: 'correct horse battery staple' }); - await expect(openpgp.encrypt({ message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: publicKey, signingKeys: privateKey, armor: false })).to.be.rejectedWith('Could not find primary user'); + await expect(openpgp.encrypt({ message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: publicKey, signingKeys: privateKey, format: 'binary' })).to.be.rejectedWith('Could not find primary user'); }); it('Sign - specific user', async function() { @@ -3476,17 +3476,17 @@ VYGdb3eNlV8CfoEC privateKey.users[1].selfCertifications[0].preferredHashAlgorithms = [openpgp.enums.hash.sha512]; const config = { minRSABits: 1024 }; const signed = await openpgp.sign({ - message: await openpgp.createMessage({ text: 'hello' }), signingKeys: privateKey, signingUserIDs: { name: 'Test McTestington', email: 'test@example.com' }, armor: false, config + message: await openpgp.createMessage({ text: 'hello' }), signingKeys: privateKey, signingUserIDs: { name: 'Test McTestington', email: 'test@example.com' }, format: 'binary', config }); const signature = await openpgp.readMessage({ binaryMessage: signed }); expect(signature.packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512); const encrypted = await openpgp.encrypt({ - message: await openpgp.createMessage({ text: 'hello' }), passwords: 'test', signingKeys: privateKey, signingUserIDs: { name: 'Test McTestington', email: 'test@example.com' }, armor: false, config + message: await openpgp.createMessage({ text: 'hello' }), passwords: 'test', signingKeys: privateKey, signingUserIDs: { name: 'Test McTestington', email: 'test@example.com' }, format: 'binary', config }); const { signatures } = await openpgp.decrypt({ message: await openpgp.readMessage({ binaryMessage: encrypted }), passwords: 'test' }); expect((await signatures[0].signature).packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512); await expect(openpgp.encrypt({ - message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: publicKey, signingKeys: privateKey, signingUserIDs: { name: 'Not Test McTestington', email: 'test@example.com' }, armor: false, config + message: await openpgp.createMessage({ text: 'hello' }), encryptionKeys: publicKey, signingKeys: privateKey, signingUserIDs: { name: 'Not Test McTestington', email: 'test@example.com' }, format: 'binary', config })).to.be.rejectedWith('Could not find user that matches that user ID'); }); @@ -3768,7 +3768,7 @@ VYGdb3eNlV8CfoEC expect(subkey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); await subkey.verify(); expect(await newPrivateKey.getSigningKey()).to.be.equal(subkey); - const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to signed' }), signingKeys: newPrivateKey, armor:false }); + const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to signed' }), signingKeys: newPrivateKey, format: 'binary' }); const message = await openpgp.readMessage({ binaryMessage: signed }); const { signatures } = await openpgp.verify({ message, verificationKeys: [newPrivateKey.toPublic()] }); expect(signatures).to.exist; @@ -3790,7 +3790,7 @@ VYGdb3eNlV8CfoEC const publicKey = newPrivateKey.toPublic(); await subkey.verify(); expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subkey); - const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ text: vData }), encryptionKeys: publicKey, armor:false }); + const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ text: vData }), encryptionKeys: publicKey, format: 'binary' }); expect(encrypted).to.be.exist; const message = await openpgp.readMessage({ binaryMessage: encrypted }); const pkSessionKeys = message.packets.filterByTag(openpgp.enums.packet.publicKeyEncryptedSessionKey); @@ -3817,7 +3817,7 @@ VYGdb3eNlV8CfoEC expect(subkey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); await subkey.verify(); expect(await newPrivateKey.getSigningKey()).to.be.equal(subkey); - const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to signed' }), signingKeys: newPrivateKey, armor:false }); + const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to signed' }), signingKeys: newPrivateKey, format: 'binary' }); const message = await openpgp.readMessage({ binaryMessage: signed }); const { signatures } = await openpgp.verify({ message, verificationKeys: [newPrivateKey.toPublic()] }); expect(signatures).to.exist; @@ -3840,7 +3840,7 @@ VYGdb3eNlV8CfoEC const publicKey = newPrivateKey.toPublic(); const vData = 'the data to encrypted!'; expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subkey); - const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ text: vData }), encryptionKeys: publicKey, armor:false }); + const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ text: vData }), encryptionKeys: publicKey, format: 'binary' }); expect(encrypted).to.be.exist; const message = await openpgp.readMessage({ binaryMessage: encrypted }); const pkSessionKeys = message.packets.filterByTag(openpgp.enums.packet.publicKeyEncryptedSessionKey); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 23f708c9..69daabc1 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -1639,6 +1639,187 @@ aOU= message: await openpgp.createMessage({ text: plaintext }) })).to.be.rejectedWith(/No signing keys provided/); }); + + it('should output cleartext message of expected format', async function() { + const text = 'test'; + const message = await openpgp.createCleartextMessage({ text }); + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key }), + passphrase + }); + const config = { minRSABits: 1024 }; + + const cleartextMessage = await openpgp.sign({ message, signingKeys: privateKey, config, format: 'armor' }); + const parsedArmored = await openpgp.readCleartextMessage({ cleartextMessage }); + expect(parsedArmored.text).to.equal(text); + expect(parsedArmored.signature.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + + await expect(openpgp.sign({ message, signingKeys: privateKey, config, format: 'binary' })).to.be.rejectedWith(''); + + const objectMessage = await openpgp.sign({ message, signingKeys: privateKey, config, format: 'object' }); + expect(objectMessage.signature.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + const verified = await openpgp.verify({ message: objectMessage, verificationKeys: privateKey, expectSigned: true, config }); + expect(verified.data).to.equal(text); + }); + + it('should output message of expected format', async function() { + const text = 'test'; + const message = await openpgp.createMessage({ text }); + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key }), + passphrase + }); + const config = { minRSABits: 1024 }; + + const armoredMessage = await openpgp.sign({ message, signingKeys: privateKey, config, format: 'armor' }); + const parsedArmored = await openpgp.readMessage({ armoredMessage }); + expect(parsedArmored.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + + const binaryMessage = await openpgp.sign({ message, signingKeys: privateKey, config, format: 'binary' }); + const parsedBinary = await openpgp.readMessage({ binaryMessage }); + expect(parsedBinary.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + + const objectMessage = await openpgp.sign({ message, signingKeys: privateKey, config, format: 'object' }); + expect(objectMessage.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + const verified = await openpgp.verify({ message: objectMessage, verificationKeys: privateKey, expectSigned: true, config }); + expect(verified.data).to.equal(text); + }); + + it('should output message of expected format', async function() { + const text = 'test'; + const message = await openpgp.createMessage({ text }); + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key }), + passphrase + }); + const config = { minRSABits: 1024 }; + + const armoredMessage = await openpgp.sign({ message, signingKeys: privateKey, config, format: 'armor' }); + const parsedArmored = await openpgp.readMessage({ armoredMessage }); + expect(parsedArmored.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + + const binaryMessage = await openpgp.sign({ message, signingKeys: privateKey, config, format: 'binary' }); + const parsedBinary = await openpgp.readMessage({ binaryMessage }); + expect(parsedBinary.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + + const objectMessage = await openpgp.sign({ message, signingKeys: privateKey, config, format: 'object' }); + expect(objectMessage.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + const verified = await openpgp.verify({ message: objectMessage, verificationKeys: privateKey, expectSigned: true, config }); + expect(verified.data).to.equal(text); + }); + + it('should output message of expected format (with streaming)', async function() { + const text = 'test'; + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key }), + passphrase + }); + const config = { minRSABits: 1024 }; + + const armoredMessage = await openpgp.sign({ + message: await openpgp.createMessage({ text: stream.toStream(text) }), + signingKeys: privateKey, + format: 'armor', + config + }); + const parsedArmored = await openpgp.readMessage({ armoredMessage }); + expect(parsedArmored.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + + const binaryMessage = await openpgp.sign({ + message: await openpgp.createMessage({ text: stream.toStream(text) }), + signingKeys: privateKey, + format: 'binary', + config + }); + const parsedBinary = await openpgp.readMessage({ binaryMessage }); + expect(parsedBinary.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + + const objectMessage = await openpgp.sign({ + message: await openpgp.createMessage({ text: stream.toStream(text) }), + signingKeys: privateKey, + format: 'object', + config + }); + expect(objectMessage.packets.filterByTag(openpgp.enums.packet.onePassSignature)).to.have.length(1); + objectMessage.packets[1].data = await stream.readToEnd(objectMessage.packets[1].data); + objectMessage.packets[2].signedHashValue = await stream.readToEnd(objectMessage.packets[2].signedHashValue); + const { data: streamedData } = await openpgp.verify({ message: objectMessage, verificationKeys: privateKey, expectSigned: true, config }); + expect(await stream.readToEnd(streamedData)).to.equal(text); + expect(streamedData).to.equal(text); + }); + + it('should output message of expected format (detached)', async function() { + const text = 'test'; + const message = await openpgp.createMessage({ text }); + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key }), + passphrase + }); + const config = { minRSABits: 1024 }; + + const armoredSignature = await openpgp.sign({ message, signingKeys: privateKey, detached: true, config, format: 'armor' }); + const parsedArmored = await openpgp.readSignature({ armoredSignature }); + expect(parsedArmored.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + + const binarySignature = await openpgp.sign({ message, signingKeys: privateKey, detached: true, config, format: 'binary' }); + const parsedBinary = await openpgp.readSignature({ binarySignature }); + expect(parsedBinary.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + + const objectSignature = await openpgp.sign({ message, signingKeys: privateKey, detached: true, config, format: 'object' }); + expect(objectSignature.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + const verified = await openpgp.verify({ message, signature: objectSignature, verificationKeys: privateKey, expectSigned: true, config }); + expect(verified.data).to.equal(text); + }); + + it('should output message of expected format (detached, with streaming)', async function() { + const text = 'test'; + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key }), + passphrase + }); + const config = { minRSABits: 1024 }; + + const armoredSignature = await openpgp.sign({ + message: await openpgp.createMessage({ text: stream.toStream(text) }), + signingKeys: privateKey, + detached: true, + format: 'armor', + config + }); + const parsedArmored = await openpgp.readSignature({ armoredSignature: await stream.readToEnd(armoredSignature) }); + expect(parsedArmored.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + + const binarySignature = await openpgp.sign({ + message: await openpgp.createMessage({ text: stream.toStream(text) }), + signingKeys: privateKey, + detached: true, + format: 'binary', + config + }); + const parsedBinary = await openpgp.readSignature({ binarySignature: await stream.readToEnd(binarySignature) }); + expect(parsedBinary.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + + const streamedMessage = await openpgp.createMessage({ text: stream.toStream(text) }); + const objectSignature = await openpgp.sign({ + message: streamedMessage, + signingKeys: privateKey, + detached: true, + format: 'object', + config + }); + expect(objectSignature.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + + const armoredStreamedMessage = streamedMessage.armor(); // consume input message stream, to allow to read the signed hash + objectSignature.packets[0].signedHashValue = await stream.readToEnd(objectSignature.packets[0].signedHashValue); + const { data: streamedData } = await openpgp.verify({ + message: await openpgp.readMessage({ armoredMessage: armoredStreamedMessage }), + signature: objectSignature, + verificationKeys: privateKey, + expectSigned: true, + config + }); + expect(await stream.readToEnd(streamedData)).to.equal(text); + }); }); describe('encrypt - unit tests', function() { @@ -1648,6 +1829,87 @@ aOU= openpgp.encrypt({ message: await openpgp.createMessage({ text: 'test' }), encryptionKeys: expiredKey }) ).to.be.rejectedWith(/Primary key is expired/); }); + + it('should output message of expected format', async function() { + const passwords = 'password'; + const text = 'test'; + const message = await openpgp.createMessage({ text }); + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key }), + passphrase + }); + + const armoredMessage = await openpgp.encrypt({ message, passwords, format: 'armor' }); + const parsedArmored = await openpgp.readMessage({ armoredMessage }); + expect(parsedArmored.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + + const binaryMessage = await openpgp.encrypt({ message, passwords, format: 'binary' }); + const parsedBinary = await openpgp.readMessage({ binaryMessage }); + expect(parsedBinary.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + + const config = { minRSABits: 1024 }; + const objectMessage = await openpgp.encrypt({ message, passwords, signingKeys: privateKey, config, format: 'object' }); + expect(objectMessage.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + const decrypted = await openpgp.decrypt({ message: objectMessage, passwords, verificationKeys: privateKey, expectSigned: true, config }); + expect(decrypted.data).to.equal(text); + }); + + it('should output message of expected format (with streaming)', async function() { + const passwords = 'password'; + const text = 'test'; + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key }), + passphrase + }); + + const armoredMessage = await openpgp.encrypt({ + message: await openpgp.createMessage({ text: stream.toStream(text) }), + passwords, + format: 'armor' + }); + const parsedArmored = await openpgp.readMessage({ armoredMessage }); + expect(parsedArmored.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + + const binaryMessage = await openpgp.encrypt({ + message: await openpgp.createMessage({ text: stream.toStream(text) }), + passwords, + format: 'binary' + }); + const parsedBinary = await openpgp.readMessage({ binaryMessage }); + expect(parsedBinary.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + + const config = { minRSABits: 1024 }; + const objectMessage = await openpgp.encrypt({ + message: await openpgp.createMessage({ text: stream.toStream(text) }), + passwords, + signingKeys: privateKey, + format: 'object', + config + }); + expect(objectMessage.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + const { data: streamedData } = await openpgp.decrypt({ message: objectMessage, passwords, verificationKeys: privateKey, expectSigned: true, config }); + expect(await stream.readToEnd(streamedData)).to.equal(text); + }); + }); + + describe('encryptSessionKey - unit tests', function() { + it('should output message of expected format', async function() { + const passwords = 'password'; + const sessionKey = { data: new Uint8Array(16).fill(1), algorithm: 'aes128' }; + + const armoredMessage = await openpgp.encryptSessionKey({ ...sessionKey, passwords, format: 'armor' }); + const parsedArmored = await openpgp.readMessage({ armoredMessage }); + expect(parsedArmored.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + + const binaryMessage = await openpgp.encryptSessionKey({ ...sessionKey, passwords, format: 'binary' }); + const parsedBinary = await openpgp.readMessage({ binaryMessage }); + expect(parsedBinary.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + + const objectMessage = await openpgp.encryptSessionKey({ ...sessionKey, passwords, format: 'object' }); + expect(objectMessage.packets.filterByTag(openpgp.enums.packet.symEncryptedSessionKey)).to.have.length(1); + const [decryptedSessionKey] = await openpgp.decryptSessionKeys({ message: objectMessage, passwords }); + expect(decryptedSessionKey).to.deep.equal(sessionKey); + }); }); describe('encrypt, decrypt, sign, verify - integration tests', function() { @@ -1783,7 +2045,7 @@ aOU= data: sk, algorithm: 'aes128', encryptionKeys: publicKey, - armor: false + format: 'binary' }).then(async function(encrypted) { const message = await openpgp.readMessage({ binaryMessage: encrypted }); return openpgp.decryptSessionKeys({ @@ -1800,7 +2062,7 @@ aOU= data: sk, algorithm: 'aes128', passwords: password1, - armor: false + format: 'binary' }).then(async function(encrypted) { const message = await openpgp.readMessage({ binaryMessage: encrypted }); return openpgp.decryptSessionKeys({ @@ -1817,7 +2079,7 @@ aOU= data: sk, algorithm: 'aes128', encryptionKeys: publicKey, - armor: false + format: 'binary' }).then(async function(encrypted) { const message = await openpgp.readMessage({ binaryMessage: encrypted }); const invalidPrivateKey = await openpgp.readKey({ armoredKey: priv_key }); @@ -2552,7 +2814,7 @@ aOU= it('should fail to decrypt unarmored message with garbage data appended', async function() { const key = privateKey; - const message = await openpgp.encrypt({ message: await openpgp.createMessage({ text: 'test' }), encryptionKeys: key, signingKeys: key, armor: false }); + const message = await openpgp.encrypt({ message: await openpgp.createMessage({ text: 'test' }), encryptionKeys: key, signingKeys: key, format: 'binary' }); const encrypted = util.concat([message, new Uint8Array([11])]); await expect((async () => { await openpgp.decrypt({ message: await openpgp.readMessage({ binaryMessage: encrypted }), decryptionKeys: key, verificationKeys: key }); @@ -2705,7 +2967,7 @@ aOU= const encOpt = { message: await openpgp.createMessage({ text: plaintext }), passwords: password1, - armor: false + format: 'binary' }; const decOpt = { passwords: password1 @@ -2723,7 +2985,7 @@ aOU= const encOpt = { message: await openpgp.createMessage({ binary: new Uint8Array([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]) }), passwords: password1, - armor: false + format: 'binary' }; const decOpt = { passwords: password1, @@ -2950,7 +3212,7 @@ aOU= const signOpt = { message, signingKeys: privateKey, - armor: false + format: 'binary' }; const verifyOpt = { verificationKeys: publicKey @@ -2974,7 +3236,7 @@ aOU= message, signingKeys: privateKey, detached: true, - armor: false + format: 'binary' }; const verifyOpt = { message, @@ -3002,7 +3264,7 @@ aOU= signingKeys: privateKey_1337, detached: true, date: past, - armor: false + format: 'binary' }; const verifyOpt = { message, @@ -3040,7 +3302,7 @@ aOU= signingKeys: privateKey_2038_2045, detached: true, date: future, - armor: false + format: 'binary' }; const verifyOpt = { verificationKeys: publicKey_2038_2045, @@ -3066,7 +3328,7 @@ aOU= const signOpt = { message: await openpgp.createMessage({ binary: data }), signingKeys: privateKey, - armor: false + format: 'binary' }; const verifyOpt = { verificationKeys: publicKey, @@ -3105,7 +3367,7 @@ aOU= const signOpt = { message: await openpgp.createMessage({ binary: dataStream }), signingKeys: privateKey, - armor: false + format: 'binary' }; const verifyOpt = { verificationKeys: publicKey, @@ -3140,7 +3402,7 @@ aOU= message: await openpgp.createMessage({ text: plaintext, date: future }), encryptionKeys: publicKey_2038_2045, date: future, - armor: false + format: 'binary' }; return openpgp.encrypt(encryptOpt).then(async function (encrypted) { @@ -3161,7 +3423,7 @@ aOU= message: await openpgp.createMessage({ binary: data, date: past }), encryptionKeys: publicKey_2000_2008, date: past, - armor: false + format: 'binary' }; return openpgp.encrypt(encryptOpt).then(async function (encrypted) { @@ -3182,7 +3444,7 @@ aOU= encryptionKeys: publicKey_2000_2008, signingKeys: privateKey_2000_2008, date: past, - armor: false + format: 'binary' }; return openpgp.encrypt(encryptOpt).then(async function (encrypted) { @@ -3210,7 +3472,7 @@ aOU= encryptionKeys: publicKey_2038_2045, signingKeys: privateKey_2038_2045, date: future, - armor: false + format: 'binary' }; return openpgp.encrypt(encryptOpt).then(async function (encrypted) { @@ -3239,7 +3501,7 @@ aOU= encryptionKeys: publicKey_2038_2045, signingKeys: privateKey_2038_2045, date: future, - armor: false + format: 'binary' }; return openpgp.encrypt(encryptOpt).then(async function (encrypted) { diff --git a/test/general/signature.js b/test/general/signature.js index d6f1d012..185bd2c0 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -1462,7 +1462,7 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA }); const config = { minRSABits: 1024 }; - return openpgp.sign({ signingKeys: privKey, message: await openpgp.createMessage({ binary: plaintext }), armor: false, config }).then(async signed => { + return openpgp.sign({ signingKeys: privKey, message: await openpgp.createMessage({ binary: plaintext }), format: 'binary', config }).then(async signed => { const message = await openpgp.readMessage({ binaryMessage: signed }); return openpgp.verify({ verificationKeys: pubKey, message, format: 'binary', config }); diff --git a/test/general/streaming.js b/test/general/streaming.js index 91fdd37a..ed5243c8 100644 --- a/test/general/streaming.js +++ b/test/general/streaming.js @@ -257,7 +257,7 @@ function tests() { const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ binary: data }), passwords: ['test'], - armor: false + format: 'binary' }); expect(stream.isStream(encrypted)).to.equal(expectedType); @@ -285,7 +285,7 @@ function tests() { const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ binary: data }), passwords: ['test'], - armor: false + format: 'binary' }); expect(stream.isStream(encrypted)).to.equal(expectedType); @@ -316,7 +316,7 @@ function tests() { message: await openpgp.createMessage({ binary: data }), encryptionKeys: pubKey, signingKeys: privKey, - armor: false, + format: 'binary', config: { minRSABits: 1024 } }); expect(stream.isStream(encrypted)).to.equal(expectedType); @@ -352,7 +352,7 @@ function tests() { message: await openpgp.createMessage({ binary: data }), encryptionKeys: pub, signingKeys: priv, - armor: false + format: 'binary' }); expect(stream.isStream(encrypted)).to.equal(expectedType); @@ -387,7 +387,7 @@ function tests() { message: await openpgp.createMessage({ binary: data }), encryptionKeys: pub, signingKeys: priv, - armor: false + format: 'binary' }); expect(stream.isStream(encrypted)).to.equal(expectedType); @@ -814,7 +814,7 @@ function tests() { const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ binary: data }), passwords: ['test'], - armor: false + format: 'binary' }); expect(stream.isStream(encrypted)).to.equal(expectedType); @@ -1035,7 +1035,7 @@ module.exports = () => describe('Streaming', function() { const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ binary: data }), passwords: ['test'], - armor: false + format: 'binary' }); expect(stream.isStream(encrypted)).to.equal('node'); diff --git a/test/security/subkey_trust.js b/test/security/subkey_trust.js index 34c6335e..c652b782 100644 --- a/test/security/subkey_trust.js +++ b/test/security/subkey_trust.js @@ -21,14 +21,12 @@ async function generateTestData() { type: 'rsa', rsaBits: 2048, subkeys: [], - sign: false, format: 'object' }); const signed = await openpgp.sign({ message: await createCleartextMessage({ text: 'I am batman' }), - signingKeys: victimPrivKey, - armor: true + signingKeys: victimPrivKey }); return { victimPubKey: victimPrivKey.toPublic(), @@ -37,7 +35,7 @@ async function generateTestData() { }; } -async function testSubkeyTrust() { +module.exports = () => it('Does not trust subkeys without Primary Key Binding Signature', async function() { // attacker only has his own private key, // the victim's public key and a signed message const { victimPubKey, attackerPrivKey, signed } = await generateTestData(); @@ -68,8 +66,7 @@ async function testSubkeyTrust() { message: await readCleartextMessage({ cleartextMessage: signed }), verificationKeys: fakeKey }); + // expect the signature to have the expected keyID, but be invalid due to fake key binding signature in the subkey expect(verifyAttackerIsBatman.signatures[0].keyID.equals(victimPubKey.subkeys[0].getKeyID())).to.be.true; await expect(verifyAttackerIsBatman.signatures[0].verified).to.be.rejectedWith(/Could not find valid signing key packet/); -} - -module.exports = () => it('Does not trust subkeys without Primary Key Binding Signature', testSubkeyTrust); +}); diff --git a/test/typescript/definitions.ts b/test/typescript/definitions.ts index b4c22f31..10d169c4 100644 --- a/test/typescript/definitions.ts +++ b/test/typescript/definitions.ts @@ -12,13 +12,13 @@ import { readMessage, createMessage, Message, createCleartextMessage, encrypt, decrypt, sign, verify, config, enums, generateSessionKey, encryptSessionKey, decryptSessionKeys, - LiteralDataPacket, PacketList, CompressedDataPacket, PublicKeyPacket, PublicSubkeyPacket, SecretKeyPacket, SecretSubkeyPacket + LiteralDataPacket, PacketList, CompressedDataPacket, PublicKeyPacket, PublicSubkeyPacket, SecretKeyPacket, SecretSubkeyPacket, CleartextMessage } from '../..'; (async () => { // Generate keys - const keyOptions = { userIDs: [{ email: "user@corp.co" }], config: { v5Keys: true } }; + const keyOptions = { userIDs: [{ email: 'user@corp.co' }], config: { v5Keys: true } }; const { privateKey: privateKeyArmored, publicKey: publicKeyArmored } = await generateKey(keyOptions); const { privateKey: privateKeyBinary } = await generateKey({ ...keyOptions, format: 'binary' }); const { privateKey, publicKey, revocationCertificate } = await generateKey({ ...keyOptions, format: 'object' }); @@ -75,7 +75,7 @@ import { // Encrypt binary message (unarmored) const binary = new Uint8Array([1, 2]); const binaryMessage = await createMessage({ binary }); - const encryptedBinary: Uint8Array = await encrypt({ encryptionKeys: publicKeys, message: binaryMessage, armor: false }); + const encryptedBinary: Uint8Array = await encrypt({ encryptionKeys: publicKeys, message: binaryMessage, format: 'binary' }); expect(encryptedBinary).to.be.instanceOf(Uint8Array); // Decrypt text message (armored) @@ -91,14 +91,17 @@ import { expect(decryptedBinaryData).to.deep.equal(binary); // Encrypt message (inspect packets) - const encryptedMessage = await readMessage({ binaryMessage: encryptedBinary }); - expect(encryptedMessage).to.be.instanceOf(Message); + const encryptedBinaryObject: Message = await encrypt({ encryptionKeys: publicKeys, message: binaryMessage, format: 'object' }); + expect(encryptedBinaryObject).to.be.instanceOf(Message); + const encryptedTextObject: Message = await encrypt({ encryptionKeys: publicKeys, message: textMessage, format: 'object' }); + expect(encryptedTextObject).to.be.instanceOf(Message); // Session key functions + // Get session keys from encrypted message const sessionKeys = await decryptSessionKeys({ message: await readMessage({ binaryMessage: encryptedBinary }), decryptionKeys: privateKeys }); expect(sessionKeys).to.have.length(1); - // eslint-disable-next-line no-unused-vars const encryptedSessionKeys: string = await encryptSessionKey({ ...sessionKeys[0], passwords: 'pass', algorithm: 'aes128', aeadAlgorithm: 'eax' }); + expect(encryptedSessionKeys).to.include('-----BEGIN PGP MESSAGE-----'); const newSessionKey = await generateSessionKey({ encryptionKeys: privateKey.toPublic() }); expect(newSessionKey.data).to.exist; expect(newSessionKey.algorithm).to.exist; @@ -107,6 +110,8 @@ import { const cleartextMessage = await createCleartextMessage({ text: 'hello' }); const clearSignedArmor = await sign({ signingKeys: privateKeys, message: cleartextMessage }); expect(clearSignedArmor).to.include('-----BEGIN PGP SIGNED MESSAGE-----'); + const clearSignedObject: CleartextMessage = await sign({ signingKeys: privateKeys, message: cleartextMessage, format: 'object' }); + expect(clearSignedObject).to.be.instanceOf(CleartextMessage); // @ts-expect-error PublicKey not assignable to PrivateKey try { await sign({ signingKeys: publicKeys, message: cleartextMessage }); } catch (e) {} // @ts-expect-error Key not assignable to PrivateKey @@ -115,10 +120,14 @@ import { // Sign text message (armored) const textSignedArmor: string = await sign({ signingKeys: privateKeys, message: textMessage }); expect(textSignedArmor).to.include('-----BEGIN PGP MESSAGE-----'); - // Sign text message (unarmored) - const textSignedBinary: Uint8Array = await sign({ signingKeys: privateKeys, message: binaryMessage, armor: false }); + const textSignedBinary: Uint8Array = await sign({ signingKeys: privateKeys, message: binaryMessage, format: 'binary' }); expect(textSignedBinary).to.be.instanceOf(Uint8Array); + // Sign text and binary messages (inspect packages) + const binarySignedObject: Message = await sign({ signingKeys: privateKeys, message: binaryMessage, format: 'object' }); + expect(binarySignedObject).to.be.instanceOf(Message); + const textSignedObject: Message = await sign({ signingKeys: privateKeys, message: textMessage, format: 'object' }); + expect(textSignedObject).to.be.instanceOf(Message); // Verify signed text message (armored) const signedMessage = await readMessage({ armoredMessage: textSignedArmor }); @@ -168,7 +177,7 @@ import { // // Detached - sign binary message (unarmored) // const message = await createMessage({ text }); - // const signed = await sign({ privateKeys, message, detached: true, armor: false }); + // const signed = await sign({ privateKeys, message, detached: true, format: 'binary' }); // console.log(signed); // Uint8Array // // Streaming - encrypt text message on Node.js (armored) @@ -182,7 +191,7 @@ import { // // Streaming - encrypt binary message on Node.js (unarmored) // const data = fs.createReadStream(filename); // const message = await createMessage({ binary: data }); - // const encrypted = await encrypt({ publicKeys, message, armor: false }); + // const encrypted = await encrypt({ publicKeys, message, format: 'binary' }); // encrypted.pipe(targetStream); console.log('TypeScript definitions are correct');