Skip to content

Commit

Permalink
Merge pull request #1598 from twiss/sig-notation-creation
Browse files Browse the repository at this point in the history
Add support for creating Notation Data subpackets when signing or encrypting messages
  • Loading branch information
twiss committed Feb 16, 2023
2 parents 2e4e053 + 70778bc commit 64ca5af
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 37 deletions.
7 changes: 5 additions & 2 deletions openpgp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class CleartextMessage {
*
* @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
* @param keys array of keys to verify signatures
Expand Down Expand Up @@ -285,7 +285,7 @@ export class Message<T extends MaybeStream<Data>> {
/** Sign the message (the literal data packet of the message)
@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
*/
Expand Down Expand Up @@ -604,6 +604,8 @@ interface EncryptOptions {
signingUserIDs?: MaybeArray<UserID>;
/** (optional) array of user IDs to encrypt for, e.g. { name:'Robert Receiver', email:'robert@openpgp.org' } */
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;
}

Expand Down Expand Up @@ -637,6 +639,7 @@ interface SignOptions {
signingKeyIDs?: MaybeArray<KeyID>;
date?: Date;
signingUserIDs?: MaybeArray<UserID>;
signatureNotations?: MaybeArray<RawNotation>;
config?: PartialConfig;
}

Expand Down
5 changes: 3 additions & 2 deletions src/cleartext.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {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} [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
* @returns {Promise<CleartextMessage>} New cleartext message with signed content.
* @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();
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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/key/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
signatureType: enums.signature.keyRevocation,
reasonForRevocationFlag: enums.reasonForRevocation.noReason,
reasonForRevocationString: ''
}, options.date, undefined, undefined, config));
}, options.date, undefined, undefined, undefined, config));

if (options.passphrase) {
secretKeyPacket.clearPrivateParams();
Expand Down
6 changes: 4 additions & 2 deletions src/key/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export async function createBindingSignature(subkey, primaryKey, options, config
subkeySignaturePacket.keyFlags = [enums.keyFlags.signData];
subkeySignaturePacket.embeddedSignature = await createSignaturePacket(dataToSign, null, subkey, {
signatureType: enums.signature.keyBinding
}, options.date, undefined, undefined, config);
}, options.date, undefined, undefined, undefined, config);
} else {
subkeySignaturePacket.keyFlags = [enums.keyFlags.encryptCommunication | enums.keyFlags.encryptStorage];
}
Expand Down Expand Up @@ -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 {Date} [date] - Override the creationtime of the signature
* @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} config - full configuration
* @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()) {
throw new Error('Cannot sign with a gnu-dummy key.');
}
Expand All @@ -207,6 +208,7 @@ export async function createSignaturePacket(dataToSign, privateKey, signingKeyPa
Object.assign(signaturePacket, signatureProperties);
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userID, config);
signaturePacket.rawNotations = notations;
await signaturePacket.sign(signingKeyPacket, dataToSign, date, detached);
return signaturePacket;
}
Expand Down
2 changes: 1 addition & 1 deletion src/key/private_key.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class PrivateKey extends PublicKey {
signatureType: enums.signature.keyRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString
}, date, undefined, undefined, config));
}, date, undefined, undefined, undefined, config));
return key;
}

Expand Down
2 changes: 1 addition & 1 deletion src/key/subkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ class Subkey {
signatureType: enums.signature.subkeyRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString
}, date, undefined, false, config));
}, date, undefined, undefined, false, config));
await subkey.update(this);
return subkey;
}
Expand Down
4 changes: 2 additions & 2 deletions src/key/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class User {
// Most OpenPGP implementations use generic certification (0x10)
signatureType: enums.signature.certGeneric,
keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData]
}, date, undefined, undefined, config);
}, date, undefined, undefined, undefined, config);
}));
await user.update(this, date, config);
return user;
Expand Down Expand Up @@ -265,7 +265,7 @@ class User {
signatureType: enums.signature.certRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString
}, date, undefined, false, config));
}, date, undefined, undefined, false, config));
await user.update(this);
return user;
}
Expand Down
15 changes: 9 additions & 6 deletions src/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {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} [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
* @returns {Promise<Message>} New message with signed content.
* @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 literalDataPacket = this.packets.findPacket(enums.packet.literalData);
Expand Down Expand Up @@ -530,7 +531,7 @@ export class Message {
});

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);
}
Expand Down Expand Up @@ -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 {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} [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
* @returns {Promise<Signature>} New detached signature of message content.
* @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);
if (!literalDataPacket) {
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));
}

/**
Expand Down Expand Up @@ -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 {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} [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 {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<PacketList>} List of signature packets.
* @async
* @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();

// If data packet was created from Uint8Array, use binary, otherwise use text
Expand All @@ -724,7 +727,7 @@ export async function createSignaturePackets(literalDataPacket, signingKeys, sig
throw new Error('Need private key for signing');
}
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 => {
packetlist.push(...signatureList);
});
Expand Down
16 changes: 9 additions & 7 deletions src/openpgp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {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.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}
* @returns {Promise<MaybeStream<String>|MaybeStream<Uint8Array>>} Encrypted message (string if `armor` was true, the default; Uint8Array if `armor` was false).
* @async
* @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);
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);
signingKeyIDs = toArray(signingKeyIDs); encryptionKeyIDs = toArray(encryptionKeyIDs); signingUserIDs = toArray(signingUserIDs); encryptionUserIDs = toArray(encryptionUserIDs); signatureNotations = toArray(signatureNotations);
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.");
}
Expand All @@ -280,7 +281,7 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords,
const streaming = message.fromStream;
try {
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(
await getPreferredAlgo('compression', encryptionKeys, date, encryptionUserIDs, config),
Expand Down Expand Up @@ -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 {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.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}
* @returns {Promise<MaybeStream<String|Uint8Array>>} Signed message (string if `armor` was true, the default; Uint8Array if `armor` was false).
* @async
* @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);
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.armor !== undefined) throw new Error('The `armor` option has been removed from openpgp.sign, pass `format` instead.');
Expand All @@ -411,9 +413,9 @@ export async function sign({ message, signingKeys, format = 'armored', detached
try {
let signature;
if (detached) {
signature = await message.signDetached(signingKeys, undefined, signingKeyIDs, date, signingUserIDs, config);
signature = await message.signDetached(signingKeys, undefined, signingKeyIDs, date, signingUserIDs, signatureNotations, config);
} 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;

Expand Down

0 comments on commit 64ca5af

Please sign in to comment.