/** * @module key/Subkey * @private */ import enums from '../enums'; import * as helper from './helper'; import { PacketList } from '../packet'; import defaultConfig from '../config'; /** * Class that represents a subkey packet and the relevant signatures. * @borrows PublicSubkeyPacket#getKeyID as Subkey#getKeyID * @borrows PublicSubkeyPacket#getFingerprint as Subkey#getFingerprint * @borrows PublicSubkeyPacket#hasSameFingerprintAs as Subkey#hasSameFingerprintAs * @borrows PublicSubkeyPacket#getAlgorithmInfo as Subkey#getAlgorithmInfo * @borrows PublicSubkeyPacket#getCreationTime as Subkey#getCreationTime * @borrows PublicSubkeyPacket#isDecrypted as Subkey#isDecrypted */ class Subkey { /** * @param {SecretSubkeyPacket|PublicSubkeyPacket} subkeyPacket - subkey packet to hold in the Subkey * @param {Key} mainKey - reference to main Key object, containing the primary key packet corresponding to the subkey */ constructor(subkeyPacket, mainKey) { this.keyPacket = subkeyPacket; this.bindingSignatures = []; this.revocationSignatures = []; this.mainKey = mainKey; } /** * Transforms structured subkey data to packetlist * @returns {PacketList} */ toPacketList() { const packetlist = new PacketList(); packetlist.push(this.keyPacket); packetlist.push(...this.revocationSignatures); packetlist.push(...this.bindingSignatures); return packetlist; } /** * Shallow clone * @return {Subkey} */ clone() { const subkey = new Subkey(this.keyPacket, this.mainKey); subkey.bindingSignatures = [...this.bindingSignatures]; subkey.revocationSignatures = [...this.revocationSignatures]; return subkey; } /** * Checks if a binding signature of a subkey is revoked * @param {SignaturePacket} signature - The binding signature to verify * @param {PublicSubkeyPacket| * SecretSubkeyPacket| * PublicKeyPacket| * SecretKeyPacket} key, optional The key to verify the signature * @param {Date} [date] - Use the given date for verification instead of the current time * @param {Object} [config] - Full configuration, defaults to openpgp.config * @returns {Promise} True if the binding signature is revoked. * @async */ async isRevoked(signature, key, date = new Date(), config = defaultConfig) { const primaryKey = this.mainKey.keyPacket; return helper.isDataRevoked( primaryKey, enums.signature.subkeyRevocation, { key: primaryKey, bind: this.keyPacket }, this.revocationSignatures, signature, key, date, config ); } /** * Verify subkey. Checks for revocation signatures, expiration time * and valid binding signature. * @param {Date} date - Use the given date instead of the current time * @param {Object} [config] - Full configuration, defaults to openpgp.config * @returns {Promise} * @throws {Error} if the subkey is invalid. * @async */ async verify(date = new Date(), config = defaultConfig) { const primaryKey = this.mainKey.keyPacket; const dataToVerify = { key: primaryKey, bind: this.keyPacket }; // check subkey binding signatures const bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config); // check binding signature is not revoked if (bindingSignature.revoked || await this.isRevoked(bindingSignature, null, date, config)) { throw new Error('Subkey is revoked'); } // check for expiration time if (helper.isDataExpired(this.keyPacket, bindingSignature, date)) { throw new Error('Subkey is expired'); } return bindingSignature; } /** * Returns the expiration time of the subkey or Infinity if key does not expire. * Returns null if the subkey is invalid. * @param {Date} date - Use the given date instead of the current time * @param {Object} [config] - Full configuration, defaults to openpgp.config * @returns {Promise} * @async */ async getExpirationTime(date = new Date(), config = defaultConfig) { const primaryKey = this.mainKey.keyPacket; const dataToVerify = { key: primaryKey, bind: this.keyPacket }; let bindingSignature; try { bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config); } catch (e) { return null; } const keyExpiry = helper.getKeyExpirationTime(this.keyPacket, bindingSignature); const sigExpiry = bindingSignature.getExpirationTime(); return keyExpiry < sigExpiry ? keyExpiry : sigExpiry; } /** * Update subkey with new components from specified subkey * @param {Subkey} subkey - Source subkey to merge * @param {Date} [date] - Date to verify validity of signatures * @param {Object} [config] - Full configuration, defaults to openpgp.config * @throws {Error} if update failed * @async */ async update(subkey, date = new Date(), config = defaultConfig) { const primaryKey = this.mainKey.keyPacket; if (!this.hasSameFingerprintAs(subkey)) { throw new Error('Subkey update method: fingerprints of subkeys not equal'); } // key packet if (this.keyPacket.constructor.tag === enums.packet.publicSubkey && subkey.keyPacket.constructor.tag === enums.packet.secretSubkey) { this.keyPacket = subkey.keyPacket; } // update missing binding signatures const that = this; const dataToVerify = { key: primaryKey, bind: that.keyPacket }; await helper.mergeSignatures(subkey, this, 'bindingSignatures', date, async function(srcBindSig) { for (let i = 0; i < that.bindingSignatures.length; i++) { if (that.bindingSignatures[i].issuerKeyID.equals(srcBindSig.issuerKeyID)) { if (srcBindSig.created > that.bindingSignatures[i].created) { that.bindingSignatures[i] = srcBindSig; } return false; } } try { await srcBindSig.verify(primaryKey, enums.signature.subkeyBinding, dataToVerify, date, undefined, config); return true; } catch (e) { return false; } }); // revocation signatures await helper.mergeSignatures(subkey, this, 'revocationSignatures', date, function(srcRevSig) { return helper.isDataRevoked(primaryKey, enums.signature.subkeyRevocation, dataToVerify, [srcRevSig], undefined, undefined, date, config); }); } /** * Revokes the subkey * @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 subkey with revocation signature. * @async */ async revoke( primaryKey, { flag: reasonForRevocationFlag = enums.reasonForRevocation.noReason, string: reasonForRevocationString = '' } = {}, date = new Date(), config = defaultConfig ) { const dataToSign = { key: primaryKey, bind: this.keyPacket }; const subkey = new Subkey(this.keyPacket, this.mainKey); subkey.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, primaryKey, { signatureType: enums.signature.subkeyRevocation, reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), reasonForRevocationString }, date, undefined, false, config)); await subkey.update(this); return subkey; } hasSameFingerprintAs(other) { return this.keyPacket.hasSameFingerprintAs(other.keyPacket || other); } } ['getKeyID', 'getFingerprint', 'getAlgorithmInfo', 'getCreationTime', 'isDecrypted'].forEach(name => { Subkey.prototype[name] = function() { return this.keyPacket[name](); }; }); export default Subkey;