Merge pull request #1598 from twiss/sig-notation-creation

Add support for creating Notation Data subpackets when signing or encrypting messages
This commit is contained in:
Daniel Huigens 2023-02-16 13:48:20 +01:00 committed by GitHub
commit 64ca5af879
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 99 additions and 37 deletions

7
openpgp.d.ts vendored
View File

@ -166,7 +166,7 @@ export class CleartextMessage {
* *
* @param privateKeys private keys with decrypted secret key data for signing * @param privateKeys private keys with decrypted secret key data for signing
*/ */
sign(privateKeys: PrivateKey[], signature?: Signature, signingKeyIDs?: KeyID[], date?: Date, userIDs?: UserID[], config?: Config): void; sign(privateKeys: PrivateKey[], signature?: Signature, signingKeyIDs?: KeyID[], date?: Date, userIDs?: UserID[], notations?: RawNotation[], config?: Config): void;
/** Verify signatures of cleartext signed message /** Verify signatures of cleartext signed message
* @param keys array of keys to verify signatures * @param keys array of keys to verify signatures
@ -285,7 +285,7 @@ export class Message<T extends MaybeStream<Data>> {
/** Sign the message (the literal data packet of the message) /** Sign the message (the literal data packet of the message)
@param signingKeys private keys with decrypted secret key data for signing @param signingKeys private keys with decrypted secret key data for signing
*/ */
public sign(signingKeys: PrivateKey[], signature?: Signature, signingKeyIDs?: KeyID[], date?: Date, userIDs?: UserID[], config?: Config): Promise<Message<T>>; public sign(signingKeys: PrivateKey[], signature?: Signature, signingKeyIDs?: KeyID[], date?: Date, userIDs?: UserID[], notations?: RawNotation[], config?: Config): Promise<Message<T>>;
/** Unwrap compressed message /** Unwrap compressed message
*/ */
@ -604,6 +604,8 @@ interface EncryptOptions {
signingUserIDs?: MaybeArray<UserID>; signingUserIDs?: MaybeArray<UserID>;
/** (optional) array of user IDs to encrypt for, e.g. { name:'Robert Receiver', email:'robert@openpgp.org' } */ /** (optional) array of user IDs to encrypt for, e.g. { name:'Robert Receiver', email:'robert@openpgp.org' } */
encryptionUserIDs?: MaybeArray<UserID>; encryptionUserIDs?: MaybeArray<UserID>;
/** (optional) array of notations to add to the signatures, e.g. { name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true } */
signatureNotations?: MaybeArray<RawNotation>;
config?: PartialConfig; config?: PartialConfig;
} }
@ -637,6 +639,7 @@ interface SignOptions {
signingKeyIDs?: MaybeArray<KeyID>; signingKeyIDs?: MaybeArray<KeyID>;
date?: Date; date?: Date;
signingUserIDs?: MaybeArray<UserID>; signingUserIDs?: MaybeArray<UserID>;
signatureNotations?: MaybeArray<RawNotation>;
config?: PartialConfig; config?: PartialConfig;
} }

View File

@ -64,14 +64,15 @@ export class CleartextMessage {
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to privateKeys[i] * @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to privateKeys[i]
* @param {Date} [date] - The creation time of the signature that should be created * @param {Date} [date] - The creation time of the signature that should be created
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] * @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true }]
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<CleartextMessage>} New cleartext message with signed content. * @returns {Promise<CleartextMessage>} New cleartext message with signed content.
* @async * @async
*/ */
async sign(privateKeys, signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], config = defaultConfig) { async sign(privateKeys, signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], config = defaultConfig) {
const literalDataPacket = new LiteralDataPacket(); const literalDataPacket = new LiteralDataPacket();
literalDataPacket.setText(this.text); literalDataPacket.setText(this.text);
const newSignature = new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, signingKeyIDs, date, userIDs, true, config)); const newSignature = new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, signingKeyIDs, date, userIDs, notations, true, config));
return new CleartextMessage(this.text, newSignature); return new CleartextMessage(this.text, newSignature);
} }

View File

@ -249,7 +249,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
signatureType: enums.signature.keyRevocation, signatureType: enums.signature.keyRevocation,
reasonForRevocationFlag: enums.reasonForRevocation.noReason, reasonForRevocationFlag: enums.reasonForRevocation.noReason,
reasonForRevocationString: '' reasonForRevocationString: ''
}, options.date, undefined, undefined, config)); }, options.date, undefined, undefined, undefined, config));
if (options.passphrase) { if (options.passphrase) {
secretKeyPacket.clearPrivateParams(); secretKeyPacket.clearPrivateParams();

View File

@ -96,7 +96,7 @@ export async function createBindingSignature(subkey, primaryKey, options, config
subkeySignaturePacket.keyFlags = [enums.keyFlags.signData]; subkeySignaturePacket.keyFlags = [enums.keyFlags.signData];
subkeySignaturePacket.embeddedSignature = await createSignaturePacket(dataToSign, null, subkey, { subkeySignaturePacket.embeddedSignature = await createSignaturePacket(dataToSign, null, subkey, {
signatureType: enums.signature.keyBinding signatureType: enums.signature.keyBinding
}, options.date, undefined, undefined, config); }, options.date, undefined, undefined, undefined, config);
} else { } else {
subkeySignaturePacket.keyFlags = [enums.keyFlags.encryptCommunication | enums.keyFlags.encryptStorage]; subkeySignaturePacket.keyFlags = [enums.keyFlags.encryptCommunication | enums.keyFlags.encryptStorage];
} }
@ -192,11 +192,12 @@ export async function getPreferredAlgo(type, keys = [], date = new Date(), userI
* @param {Object} [signatureProperties] - Properties to write on the signature packet before signing * @param {Object} [signatureProperties] - Properties to write on the signature packet before signing
* @param {Date} [date] - Override the creationtime of the signature * @param {Date} [date] - Override the creationtime of the signature
* @param {Object} [userID] - User ID * @param {Object} [userID] - User ID
* @param {Array} [notations] - Notation Data to add to the signature, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true }]
* @param {Object} [detached] - Whether to create a detached signature packet * @param {Object} [detached] - Whether to create a detached signature packet
* @param {Object} config - full configuration * @param {Object} config - full configuration
* @returns {Promise<SignaturePacket>} Signature packet. * @returns {Promise<SignaturePacket>} Signature packet.
*/ */
export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userID, detached = false, config) { export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userID, notations = [], detached = false, config) {
if (signingKeyPacket.isDummy()) { if (signingKeyPacket.isDummy()) {
throw new Error('Cannot sign with a gnu-dummy key.'); throw new Error('Cannot sign with a gnu-dummy key.');
} }
@ -207,6 +208,7 @@ export async function createSignaturePacket(dataToSign, privateKey, signingKeyPa
Object.assign(signaturePacket, signatureProperties); Object.assign(signaturePacket, signatureProperties);
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userID, config); signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userID, config);
signaturePacket.rawNotations = notations;
await signaturePacket.sign(signingKeyPacket, dataToSign, date, detached); await signaturePacket.sign(signingKeyPacket, dataToSign, date, detached);
return signaturePacket; return signaturePacket;
} }

View File

@ -191,7 +191,7 @@ class PrivateKey extends PublicKey {
signatureType: enums.signature.keyRevocation, signatureType: enums.signature.keyRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString reasonForRevocationString
}, date, undefined, undefined, config)); }, date, undefined, undefined, undefined, config));
return key; return key;
} }

View File

@ -190,7 +190,7 @@ class Subkey {
signatureType: enums.signature.subkeyRevocation, signatureType: enums.signature.subkeyRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString reasonForRevocationString
}, date, undefined, false, config)); }, date, undefined, undefined, false, config));
await subkey.update(this); await subkey.update(this);
return subkey; return subkey;
} }

View File

@ -77,7 +77,7 @@ class User {
// Most OpenPGP implementations use generic certification (0x10) // Most OpenPGP implementations use generic certification (0x10)
signatureType: enums.signature.certGeneric, signatureType: enums.signature.certGeneric,
keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData] keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData]
}, date, undefined, undefined, config); }, date, undefined, undefined, undefined, config);
})); }));
await user.update(this, date, config); await user.update(this, date, config);
return user; return user;
@ -265,7 +265,7 @@ class User {
signatureType: enums.signature.certRevocation, signatureType: enums.signature.certRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString reasonForRevocationString
}, date, undefined, false, config)); }, date, undefined, undefined, false, config));
await user.update(this); await user.update(this);
return user; return user;
} }

View File

@ -476,11 +476,12 @@ export class Message {
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i] * @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i]
* @param {Date} [date] - Override the creation time of the signature * @param {Date} [date] - Override the creation time of the signature
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] * @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true }]
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Message>} New message with signed content. * @returns {Promise<Message>} New message with signed content.
* @async * @async
*/ */
async sign(signingKeys = [], signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], config = defaultConfig) { async sign(signingKeys = [], signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], config = defaultConfig) {
const packetlist = new PacketList(); const packetlist = new PacketList();
const literalDataPacket = this.packets.findPacket(enums.packet.literalData); const literalDataPacket = this.packets.findPacket(enums.packet.literalData);
@ -530,7 +531,7 @@ export class Message {
}); });
packetlist.push(literalDataPacket); packetlist.push(literalDataPacket);
packetlist.push(...(await createSignaturePackets(literalDataPacket, signingKeys, signature, signingKeyIDs, date, userIDs, false, config))); packetlist.push(...(await createSignaturePackets(literalDataPacket, signingKeys, signature, signingKeyIDs, date, userIDs, notations, false, config)));
return new Message(packetlist); return new Message(packetlist);
} }
@ -563,16 +564,17 @@ export class Message {
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i] * @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i]
* @param {Date} [date] - Override the creation time of the signature * @param {Date} [date] - Override the creation time of the signature
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] * @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true }]
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Signature>} New detached signature of message content. * @returns {Promise<Signature>} New detached signature of message content.
* @async * @async
*/ */
async signDetached(signingKeys = [], signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], config = defaultConfig) { async signDetached(signingKeys = [], signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], config = defaultConfig) {
const literalDataPacket = this.packets.findPacket(enums.packet.literalData); const literalDataPacket = this.packets.findPacket(enums.packet.literalData);
if (!literalDataPacket) { if (!literalDataPacket) {
throw new Error('No literal data packet to sign.'); throw new Error('No literal data packet to sign.');
} }
return new Signature(await createSignaturePackets(literalDataPacket, signingKeys, signature, signingKeyIDs, date, userIDs, true, config)); return new Signature(await createSignaturePackets(literalDataPacket, signingKeys, signature, signingKeyIDs, date, userIDs, notations, true, config));
} }
/** /**
@ -705,13 +707,14 @@ export class Message {
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i] * @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i]
* @param {Date} [date] - Override the creationtime of the signature * @param {Date} [date] - Override the creationtime of the signature
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] * @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true }]
* @param {Boolean} [detached] - Whether to create detached signature packets * @param {Boolean} [detached] - Whether to create detached signature packets
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<PacketList>} List of signature packets. * @returns {Promise<PacketList>} List of signature packets.
* @async * @async
* @private * @private
*/ */
export async function createSignaturePackets(literalDataPacket, signingKeys, signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], detached = false, config = defaultConfig) { export async function createSignaturePackets(literalDataPacket, signingKeys, signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], detached = false, config = defaultConfig) {
const packetlist = new PacketList(); const packetlist = new PacketList();
// If data packet was created from Uint8Array, use binary, otherwise use text // If data packet was created from Uint8Array, use binary, otherwise use text
@ -724,7 +727,7 @@ export async function createSignaturePackets(literalDataPacket, signingKeys, sig
throw new Error('Need private key for signing'); throw new Error('Need private key for signing');
} }
const signingKey = await primaryKey.getSigningKey(signingKeyIDs[i], date, userID, config); const signingKey = await primaryKey.getSigningKey(signingKeyIDs[i], date, userID, config);
return createSignaturePacket(literalDataPacket, primaryKey, signingKey.keyPacket, { signatureType }, date, userID, detached, config); return createSignaturePacket(literalDataPacket, primaryKey, signingKey.keyPacket, { signatureType }, date, userID, notations, detached, config);
})).then(signatureList => { })).then(signatureList => {
packetlist.push(...signatureList); packetlist.push(...signatureList);
}); });

View File

@ -256,16 +256,17 @@ export async function encryptKey({ privateKey, passphrase, config, ...rest }) {
* @param {Date} [options.date=current date] - Override the creation date of the message signature * @param {Date} [options.date=current date] - Override the creation date of the message 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|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|Object[]} [options.encryptionUserIDs=primary user IDs] - Array of user IDs to encrypt for, one per key in `encryptionKeys`, e.g. `[{ name: 'Robert Receiver', email: 'robert@openpgp.org' }]` * @param {Object|Object[]} [options.encryptionUserIDs=primary user IDs] - Array of user IDs to encrypt for, one per key in `encryptionKeys`, e.g. `[{ name: 'Robert Receiver', email: 'robert@openpgp.org' }]`
* @param {Object|Object[]} [options.signatureNotations=[]] - Array of notations to add to the signatures, e.g. `[{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true }]`
* @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config} * @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config}
* @returns {Promise<MaybeStream<String>|MaybeStream<Uint8Array>>} Encrypted message (string if `armor` was true, the default; Uint8Array if `armor` was false). * @returns {Promise<MaybeStream<String>|MaybeStream<Uint8Array>>} Encrypted message (string if `armor` was true, the default; Uint8Array if `armor` was false).
* @async * @async
* @static * @static
*/ */
export async function encrypt({ message, encryptionKeys, signingKeys, passwords, sessionKey, format = 'armored', signature = null, wildcard = false, signingKeyIDs = [], encryptionKeyIDs = [], date = new Date(), signingUserIDs = [], encryptionUserIDs = [], config, ...rest }) { export async function encrypt({ message, encryptionKeys, signingKeys, passwords, sessionKey, format = 'armored', signature = null, wildcard = false, signingKeyIDs = [], encryptionKeyIDs = [], date = new Date(), signingUserIDs = [], encryptionUserIDs = [], signatureNotations = [], config, ...rest }) {
config = { ...defaultConfig, ...config }; checkConfig(config); config = { ...defaultConfig, ...config }; checkConfig(config);
checkMessage(message); checkOutputMessageFormat(format); checkMessage(message); checkOutputMessageFormat(format);
encryptionKeys = toArray(encryptionKeys); signingKeys = toArray(signingKeys); passwords = toArray(passwords); encryptionKeys = toArray(encryptionKeys); signingKeys = toArray(signingKeys); passwords = toArray(passwords);
signingKeyIDs = toArray(signingKeyIDs); encryptionKeyIDs = toArray(encryptionKeyIDs); signingUserIDs = toArray(signingUserIDs); encryptionUserIDs = toArray(encryptionUserIDs); signingKeyIDs = toArray(signingKeyIDs); encryptionKeyIDs = toArray(encryptionKeyIDs); signingUserIDs = toArray(signingUserIDs); encryptionUserIDs = toArray(encryptionUserIDs); signatureNotations = toArray(signatureNotations);
if (rest.detached) { 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."); 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.");
} }
@ -280,7 +281,7 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords,
const streaming = message.fromStream; const streaming = message.fromStream;
try { try {
if (signingKeys.length || signature) { // sign the message only if signing keys or signature is specified if (signingKeys.length || signature) { // sign the message only if signing keys or signature is specified
message = await message.sign(signingKeys, signature, signingKeyIDs, date, signingUserIDs, config); message = await message.sign(signingKeys, signature, signingKeyIDs, date, signingUserIDs, signatureNotations, config);
} }
message = message.compress( message = message.compress(
await getPreferredAlgo('compression', encryptionKeys, date, encryptionUserIDs, config), await getPreferredAlgo('compression', encryptionKeys, date, encryptionUserIDs, config),
@ -387,15 +388,16 @@ export async function decrypt({ message, decryptionKeys, passwords, sessionKeys,
* @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 {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 {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|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|Object[]} [options.signatureNotations=[]] - Array of notations to add to the signatures, e.g. `[{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true }]`
* @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config} * @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config}
* @returns {Promise<MaybeStream<String|Uint8Array>>} Signed message (string if `armor` was true, the default; Uint8Array if `armor` was false). * @returns {Promise<MaybeStream<String|Uint8Array>>} Signed message (string if `armor` was true, the default; Uint8Array if `armor` was false).
* @async * @async
* @static * @static
*/ */
export async function sign({ message, signingKeys, format = 'armored', detached = false, signingKeyIDs = [], date = new Date(), signingUserIDs = [], config, ...rest }) { export async function sign({ message, signingKeys, format = 'armored', detached = false, signingKeyIDs = [], date = new Date(), signingUserIDs = [], signatureNotations = [], config, ...rest }) {
config = { ...defaultConfig, ...config }; checkConfig(config); config = { ...defaultConfig, ...config }; checkConfig(config);
checkCleartextOrMessage(message); checkOutputMessageFormat(format); checkCleartextOrMessage(message); checkOutputMessageFormat(format);
signingKeys = toArray(signingKeys); signingKeyIDs = toArray(signingKeyIDs); signingUserIDs = toArray(signingUserIDs); signingKeys = toArray(signingKeys); signingKeyIDs = toArray(signingKeyIDs); signingUserIDs = toArray(signingUserIDs); signatureNotations = toArray(signatureNotations);
if (rest.privateKeys) throw new Error('The `privateKeys` option has been removed from openpgp.sign, pass `signingKeys` instead'); 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.'); if (rest.armor !== undefined) throw new Error('The `armor` option has been removed from openpgp.sign, pass `format` instead.');
@ -411,9 +413,9 @@ export async function sign({ message, signingKeys, format = 'armored', detached
try { try {
let signature; let signature;
if (detached) { if (detached) {
signature = await message.signDetached(signingKeys, undefined, signingKeyIDs, date, signingUserIDs, config); signature = await message.signDetached(signingKeys, undefined, signingKeyIDs, date, signingUserIDs, signatureNotations, config);
} else { } else {
signature = await message.sign(signingKeys, undefined, signingKeyIDs, date, signingUserIDs, config); signature = await message.sign(signingKeys, undefined, signingKeyIDs, date, signingUserIDs, signatureNotations, config);
} }
if (format === 'object') return signature; if (format === 'object') return signature;

View File

@ -260,13 +260,14 @@ class SignaturePacket {
// MUST NOT be included in the signature. // MUST NOT be included in the signature.
arr.push(writeSubPacket(sub.issuer, this.issuerKeyID.write())); arr.push(writeSubPacket(sub.issuer, this.issuerKeyID.write()));
} }
this.rawNotations.forEach(([{ name, value, humanReadable }]) => { this.rawNotations.forEach(({ name, value, humanReadable }) => {
bytes = [new Uint8Array([humanReadable ? 0x80 : 0, 0, 0, 0])]; bytes = [new Uint8Array([humanReadable ? 0x80 : 0, 0, 0, 0])];
const encodedName = util.encodeUTF8(name);
// 2 octets of name length // 2 octets of name length
bytes.push(util.writeNumber(name.length, 2)); bytes.push(util.writeNumber(encodedName.length, 2));
// 2 octets of value length // 2 octets of value length
bytes.push(util.writeNumber(value.length, 2)); bytes.push(util.writeNumber(value.length, 2));
bytes.push(util.stringToUint8Array(name)); bytes.push(encodedName);
bytes.push(value); bytes.push(value);
bytes = util.concat(bytes); bytes = util.concat(bytes);
arr.push(writeSubPacket(sub.notationData, bytes)); arr.push(writeSubPacket(sub.notationData, bytes));
@ -284,20 +285,20 @@ class SignaturePacket {
arr.push(writeSubPacket(sub.keyServerPreferences, bytes)); arr.push(writeSubPacket(sub.keyServerPreferences, bytes));
} }
if (this.preferredKeyServer !== null) { if (this.preferredKeyServer !== null) {
arr.push(writeSubPacket(sub.preferredKeyServer, util.stringToUint8Array(this.preferredKeyServer))); arr.push(writeSubPacket(sub.preferredKeyServer, util.encodeUTF8(this.preferredKeyServer)));
} }
if (this.isPrimaryUserID !== null) { if (this.isPrimaryUserID !== null) {
arr.push(writeSubPacket(sub.primaryUserID, new Uint8Array([this.isPrimaryUserID ? 1 : 0]))); arr.push(writeSubPacket(sub.primaryUserID, new Uint8Array([this.isPrimaryUserID ? 1 : 0])));
} }
if (this.policyURI !== null) { if (this.policyURI !== null) {
arr.push(writeSubPacket(sub.policyURI, util.stringToUint8Array(this.policyURI))); arr.push(writeSubPacket(sub.policyURI, util.encodeUTF8(this.policyURI)));
} }
if (this.keyFlags !== null) { if (this.keyFlags !== null) {
bytes = util.stringToUint8Array(util.uint8ArrayToString(this.keyFlags)); bytes = util.stringToUint8Array(util.uint8ArrayToString(this.keyFlags));
arr.push(writeSubPacket(sub.keyFlags, bytes)); arr.push(writeSubPacket(sub.keyFlags, bytes));
} }
if (this.signersUserID !== null) { if (this.signersUserID !== null) {
arr.push(writeSubPacket(sub.signersUserID, util.stringToUint8Array(this.signersUserID))); arr.push(writeSubPacket(sub.signersUserID, util.encodeUTF8(this.signersUserID)));
} }
if (this.reasonForRevocationFlag !== null) { if (this.reasonForRevocationFlag !== null) {
bytes = util.stringToUint8Array(String.fromCharCode(this.reasonForRevocationFlag) + this.reasonForRevocationString); bytes = util.stringToUint8Array(String.fromCharCode(this.reasonForRevocationFlag) + this.reasonForRevocationString);
@ -354,7 +355,7 @@ class SignaturePacket {
let mypos = 0; let mypos = 0;
// The leftmost bit denotes a "critical" packet // The leftmost bit denotes a "critical" packet
const critical = bytes[mypos] & 0x80; const critical = !!(bytes[mypos] & 0x80);
const type = bytes[mypos] & 0x7F; const type = bytes[mypos] & 0x7F;
if (!hashed) { if (!hashed) {
@ -437,13 +438,13 @@ class SignaturePacket {
const n = util.readNumber(bytes.subarray(mypos, mypos + 2)); const n = util.readNumber(bytes.subarray(mypos, mypos + 2));
mypos += 2; mypos += 2;
const name = util.uint8ArrayToString(bytes.subarray(mypos, mypos + m)); const name = util.decodeUTF8(bytes.subarray(mypos, mypos + m));
const value = bytes.subarray(mypos + m, mypos + m + n); const value = bytes.subarray(mypos + m, mypos + m + n);
this.rawNotations.push({ name, humanReadable, value, critical }); this.rawNotations.push({ name, humanReadable, value, critical });
if (humanReadable) { if (humanReadable) {
this.notations[name] = util.uint8ArrayToString(value); this.notations[name] = util.decodeUTF8(value);
} }
break; break;
} }
@ -461,7 +462,7 @@ class SignaturePacket {
break; break;
case enums.signatureSubpacket.preferredKeyServer: case enums.signatureSubpacket.preferredKeyServer:
// Preferred Key Server // Preferred Key Server
this.preferredKeyServer = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length)); this.preferredKeyServer = util.decodeUTF8(bytes.subarray(mypos, bytes.length));
break; break;
case enums.signatureSubpacket.primaryUserID: case enums.signatureSubpacket.primaryUserID:
// Primary User ID // Primary User ID
@ -469,7 +470,7 @@ class SignaturePacket {
break; break;
case enums.signatureSubpacket.policyURI: case enums.signatureSubpacket.policyURI:
// Policy URI // Policy URI
this.policyURI = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length)); this.policyURI = util.decodeUTF8(bytes.subarray(mypos, bytes.length));
break; break;
case enums.signatureSubpacket.keyFlags: case enums.signatureSubpacket.keyFlags:
// Key Flags // Key Flags
@ -477,12 +478,12 @@ class SignaturePacket {
break; break;
case enums.signatureSubpacket.signersUserID: case enums.signatureSubpacket.signersUserID:
// Signer's User ID // Signer's User ID
this.signersUserID = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length)); this.signersUserID = util.decodeUTF8(bytes.subarray(mypos, bytes.length));
break; break;
case enums.signatureSubpacket.reasonForRevocation: case enums.signatureSubpacket.reasonForRevocation:
// Reason for Revocation // Reason for Revocation
this.reasonForRevocationFlag = bytes[mypos++]; this.reasonForRevocationFlag = bytes[mypos++];
this.reasonForRevocationString = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length)); this.reasonForRevocationString = util.decodeUTF8(bytes.subarray(mypos, bytes.length));
break; break;
case enums.signatureSubpacket.features: case enums.signatureSubpacket.features:
// Features // Features

View File

@ -1138,6 +1138,48 @@ eSvSZutLuKKbidSYMLhWROPlwKc2GU2ws6PrLZAyCAel/lU=
expect(await sig.verified).to.be.true; expect(await sig.verified).to.be.true;
}); });
it('Can create notations', async function() {
const privKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key_arm2 }),
passphrase: 'hello world'
});
const config = { minRSABits: 1024 };
const message_with_notation = await openpgp.encrypt({
message: await openpgp.createMessage({ text: 'test' }),
encryptionKeys: privKey,
signingKeys: privKey,
signatureNotations: [
{
name: 'test@example.com',
value: new TextEncoder().encode('test'),
humanReadable: true
},
{
name: 'séparation-de-domaine@proton.ch',
value: new Uint8Array([0, 1, 2, 3]),
humanReadable: false
}
],
config
});
const { signatures: [sig] } = await openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: message_with_notation }),
decryptionKeys: privKey,
verificationKeys: privKey
});
const { packets: [{ rawNotations: notations }] } = await sig.signature;
expect(notations).to.have.length(2);
expect(notations[0].name).to.equal('test@example.com');
expect(notations[0].value).to.deep.equal(new Uint8Array([116, 101, 115, 116]));
expect(notations[0].humanReadable).to.be.true;
expect(notations[0].critical).to.be.false;
expect(notations[1].name).to.equal('séparation-de-domaine@proton.ch');
expect(notations[1].value).to.deep.equal(new Uint8Array([0, 1, 2, 3]));
expect(notations[1].humanReadable).to.be.false;
expect(notations[1].critical).to.be.false;
});
it('Verify cleartext signed message with two signatures with openpgp.verify', async function() { it('Verify cleartext signed message with two signatures with openpgp.verify', async function() {
const cleartextMessage = const cleartextMessage =
['-----BEGIN PGP SIGNED MESSAGE-----', ['-----BEGIN PGP SIGNED MESSAGE-----',

View File

@ -134,6 +134,14 @@ import {
const textSignedObject: Message<string> = await sign({ signingKeys: privateKeys, message: textMessage, format: 'object' }); const textSignedObject: Message<string> = await sign({ signingKeys: privateKeys, message: textMessage, format: 'object' });
expect(textSignedObject).to.be.instanceOf(Message); expect(textSignedObject).to.be.instanceOf(Message);
// Sign text message (armored)
const textSignedWithNotations: string = await sign({ signingKeys: privateKeys, message: textMessage, signatureNotations: [{
name: 'test@example.org',
value: new TextEncoder().encode('test'),
humanReadable: true
}] });
expect(textSignedWithNotations).to.include('-----BEGIN PGP MESSAGE-----');
// Verify signed text message (armored) // Verify signed text message (armored)
const signedMessage = await readMessage({ armoredMessage: textSignedArmor }); const signedMessage = await readMessage({ armoredMessage: textSignedArmor });
const verifiedText = await verify({ verificationKeys: publicKeys, message: signedMessage }); const verifiedText = await verify({ verificationKeys: publicKeys, message: signedMessage });