/** * @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} 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} 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} 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} 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 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} 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>} 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} 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} * @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} 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;