fork-openpgpjs/src/key/user.js
Daniel Huigens 809deee3a6 Add signatureNotations option to sign and encrypt
This allows adding Notation Data signature subpackets when signing or
encrypting a message.
2023-02-15 19:42:45 +01:00

275 lines
11 KiB
JavaScript

/**
* @module key/User
* @private
*/
import enums from '../enums';
import util from '../util';
import { PacketList } from '../packet';
import { mergeSignatures, isDataRevoked, createSignaturePacket } from './helper';
import defaultConfig from '../config';
/**
* Class that represents an user ID or attribute packet and the relevant signatures.
* @param {UserIDPacket|UserAttributePacket} userPacket - packet containing the user info
* @param {Key} mainKey - reference to main Key object containing the primary key and subkeys that the user is associated with
*/
class User {
constructor(userPacket, mainKey) {
this.userID = userPacket.constructor.tag === enums.packet.userID ? userPacket : null;
this.userAttribute = userPacket.constructor.tag === enums.packet.userAttribute ? userPacket : null;
this.selfCertifications = [];
this.otherCertifications = [];
this.revocationSignatures = [];
this.mainKey = mainKey;
}
/**
* Transforms structured user data to packetlist
* @returns {PacketList}
*/
toPacketList() {
const packetlist = new PacketList();
packetlist.push(this.userID || this.userAttribute);
packetlist.push(...this.revocationSignatures);
packetlist.push(...this.selfCertifications);
packetlist.push(...this.otherCertifications);
return packetlist;
}
/**
* Shallow clone
* @returns {User}
*/
clone() {
const user = new User(this.userID || this.userAttribute, this.mainKey);
user.selfCertifications = [...this.selfCertifications];
user.otherCertifications = [...this.otherCertifications];
user.revocationSignatures = [...this.revocationSignatures];
return user;
}
/**
* Generate third-party certifications over this user and its primary key
* @param {Array<PrivateKey>} signingKeys - Decrypted private keys for signing
* @param {Date} [date] - Date to use as creation date of the certificate, instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<User>} New user with new certifications.
* @async
*/
async certify(signingKeys, date, config) {
const primaryKey = this.mainKey.keyPacket;
const dataToSign = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
const user = new User(dataToSign.userID || dataToSign.userAttribute, this.mainKey);
user.otherCertifications = await Promise.all(signingKeys.map(async function(privateKey) {
if (!privateKey.isPrivate()) {
throw new Error('Need private key for signing');
}
if (privateKey.hasSameFingerprintAs(primaryKey)) {
throw new Error("The user's own key can only be used for self-certifications");
}
const signingKey = await privateKey.getSigningKey(undefined, date, undefined, config);
return createSignaturePacket(dataToSign, privateKey, signingKey.keyPacket, {
// Most OpenPGP implementations use generic certification (0x10)
signatureType: enums.signature.certGeneric,
keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData]
}, date, undefined, undefined, undefined, config);
}));
await user.update(this, date, config);
return user;
}
/**
* Checks if a given certificate of the user is revoked
* @param {SignaturePacket} certificate - The certificate to verify
* @param {PublicSubkeyPacket|
* SecretSubkeyPacket|
* PublicKeyPacket|
* SecretKeyPacket} [keyPacket] The key packet to verify the signature, instead of the primary key
* @param {Date} [date] - Use the given date for verification instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<Boolean>} True if the certificate is revoked.
* @async
*/
async isRevoked(certificate, keyPacket, date = new Date(), config = defaultConfig) {
const primaryKey = this.mainKey.keyPacket;
return isDataRevoked(primaryKey, enums.signature.certRevocation, {
key: primaryKey,
userID: this.userID,
userAttribute: this.userAttribute
}, this.revocationSignatures, certificate, keyPacket, date, config);
}
/**
* Verifies the user certificate.
* @param {SignaturePacket} certificate - A certificate of this user
* @param {Array<PublicKey>} verificationKeys - Array of keys to verify certificate signatures
* @param {Date} [date] - Use the given date instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<true|null>} true if the certificate could be verified, or null if the verification keys do not correspond to the certificate
* @throws if the user certificate is invalid.
* @async
*/
async verifyCertificate(certificate, verificationKeys, date = new Date(), config) {
const that = this;
const primaryKey = this.mainKey.keyPacket;
const dataToVerify = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
const { issuerKeyID } = certificate;
const issuerKeys = verificationKeys.filter(key => key.getKeys(issuerKeyID).length > 0);
if (issuerKeys.length === 0) {
return null;
}
await Promise.all(issuerKeys.map(async key => {
const signingKey = await key.getSigningKey(issuerKeyID, certificate.created, undefined, config);
if (certificate.revoked || await that.isRevoked(certificate, signingKey.keyPacket, date, config)) {
throw new Error('User certificate is revoked');
}
try {
await certificate.verify(signingKey.keyPacket, enums.signature.certGeneric, dataToVerify, date, undefined, config);
} catch (e) {
throw util.wrapError('User certificate is invalid', e);
}
}));
return true;
}
/**
* Verifies all user certificates
* @param {Array<PublicKey>} verificationKeys - Array of keys to verify certificate signatures
* @param {Date} [date] - Use the given date instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<Array<{
* keyID: module:type/keyid~KeyID,
* valid: Boolean | null
* }>>} List of signer's keyID and validity of signature.
* Signature validity is null if the verification keys do not correspond to the certificate.
* @async
*/
async verifyAllCertifications(verificationKeys, date = new Date(), config) {
const that = this;
const certifications = this.selfCertifications.concat(this.otherCertifications);
return Promise.all(certifications.map(async certification => ({
keyID: certification.issuerKeyID,
valid: await that.verifyCertificate(certification, verificationKeys, date, config).catch(() => false)
})));
}
/**
* Verify User. Checks for existence of self signatures, revocation signatures
* and validity of self signature.
* @param {Date} date - Use the given date instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<true>} Status of user.
* @throws {Error} if there are no valid self signatures.
* @async
*/
async verify(date = new Date(), config) {
if (!this.selfCertifications.length) {
throw new Error('No self-certifications found');
}
const that = this;
const primaryKey = this.mainKey.keyPacket;
const dataToVerify = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
// TODO replace when Promise.some or Promise.any are implemented
let exception;
for (let i = this.selfCertifications.length - 1; i >= 0; i--) {
try {
const selfCertification = this.selfCertifications[i];
if (selfCertification.revoked || await that.isRevoked(selfCertification, undefined, date, config)) {
throw new Error('Self-certification is revoked');
}
try {
await selfCertification.verify(primaryKey, enums.signature.certGeneric, dataToVerify, date, undefined, config);
} catch (e) {
throw util.wrapError('Self-certification is invalid', e);
}
return true;
} catch (e) {
exception = e;
}
}
throw exception;
}
/**
* Update user with new components from specified user
* @param {User} sourceUser - Source user to merge
* @param {Date} date - Date to verify the validity of signatures
* @param {Object} config - Full configuration
* @returns {Promise<undefined>}
* @async
*/
async update(sourceUser, date, config) {
const primaryKey = this.mainKey.keyPacket;
const dataToVerify = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
// self signatures
await mergeSignatures(sourceUser, this, 'selfCertifications', date, async function(srcSelfSig) {
try {
await srcSelfSig.verify(primaryKey, enums.signature.certGeneric, dataToVerify, date, false, config);
return true;
} catch (e) {
return false;
}
});
// other signatures
await mergeSignatures(sourceUser, this, 'otherCertifications', date);
// revocation signatures
await mergeSignatures(sourceUser, this, 'revocationSignatures', date, function(srcRevSig) {
return isDataRevoked(primaryKey, enums.signature.certRevocation, dataToVerify, [srcRevSig], undefined, undefined, date, config);
});
}
/**
* Revokes the user
* @param {SecretKeyPacket} primaryKey - decrypted private primary key for revocation
* @param {Object} reasonForRevocation - optional, object indicating the reason for revocation
* @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation
* @param {String} reasonForRevocation.string optional, string explaining the reason for revocation
* @param {Date} date - optional, override the creationtime of the revocation signature
* @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<User>} New user with revocation signature.
* @async
*/
async revoke(
primaryKey,
{
flag: reasonForRevocationFlag = enums.reasonForRevocation.noReason,
string: reasonForRevocationString = ''
} = {},
date = new Date(),
config = defaultConfig
) {
const dataToSign = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
const user = new User(dataToSign.userID || dataToSign.userAttribute, this.mainKey);
user.revocationSignatures.push(await createSignaturePacket(dataToSign, null, primaryKey, {
signatureType: enums.signature.certRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString
}, date, undefined, undefined, false, config));
await user.update(this);
return user;
}
}
export default User;