Slightly simplifies key.js; adds key.verifyKeyPackets which should be run before getEncryption/SigningKeyPacket

This commit is contained in:
Mahrud Sayrafi 2018-03-04 07:00:44 -08:00 committed by Sanjana Rajan
parent 354b961b67
commit ec22dabac3
9 changed files with 373 additions and 367 deletions

View File

@ -53,6 +53,8 @@ export default {
* @property {Boolean} password_collision_check * @property {Boolean} password_collision_check
*/ */
password_collision_check: false, password_collision_check: false,
/** @property {Boolean} revocations_expire If true, expired revocation signatures are ignored */
revocations_expire: false,
/** @property {Boolean} use_native Use native Node.js crypto/zlib and WebCrypto APIs when available */ /** @property {Boolean} use_native Use native Node.js crypto/zlib and WebCrypto APIs when available */
use_native: true, use_native: true,

View File

@ -234,13 +234,6 @@ const ECDSASignature = nodeCrypto ?
); );
}) : undefined; }) : undefined;
const ECParameters = nodeCrypto ?
asn1.define('ECParameters', function() {
this.choice({
namedCurve: this.objid()
});
}) : undefined;
const ECPrivateKey = nodeCrypto ? const ECPrivateKey = nodeCrypto ?
asn1.define('ECPrivateKey', function() { asn1.define('ECPrivateKey', function() {
this.seq().obj( this.seq().obj(

View File

@ -45,12 +45,12 @@ export function Key(packetlist) {
} }
// same data as in packetlist but in structured form // same data as in packetlist but in structured form
this.primaryKey = null; this.primaryKey = null;
this.revocationSignature = null; this.revocationSignatures = [];
this.directSignatures = null; this.directSignatures = [];
this.users = null; this.users = [];
this.subKeys = null; this.subKeys = [];
this.packetlist2structure(packetlist); this.packetlist2structure(packetlist);
if (!this.primaryKey || !this.users) { if (!this.primaryKey || !this.users.length) {
throw new Error('Invalid key: need at least key and user ID packet'); throw new Error('Invalid key: need at least key and user ID packet');
} }
} }
@ -73,17 +73,11 @@ Key.prototype.packetlist2structure = function(packetlist) {
case enums.packet.userid: case enums.packet.userid:
case enums.packet.userAttribute: case enums.packet.userAttribute:
user = new User(packetlist[i]); user = new User(packetlist[i]);
if (!this.users) {
this.users = [];
}
this.users.push(user); this.users.push(user);
break; break;
case enums.packet.publicSubkey: case enums.packet.publicSubkey:
case enums.packet.secretSubkey: case enums.packet.secretSubkey:
user = null; user = null;
if (!this.subKeys) {
this.subKeys = [];
}
subKey = new SubKey(packetlist[i]); subKey = new SubKey(packetlist[i]);
this.subKeys.push(subKey); this.subKeys.push(subKey);
break; break;
@ -98,34 +92,19 @@ Key.prototype.packetlist2structure = function(packetlist) {
continue; continue;
} }
if (packetlist[i].issuerKeyId.equals(primaryKeyId)) { if (packetlist[i].issuerKeyId.equals(primaryKeyId)) {
if (!user.selfCertifications) {
user.selfCertifications = [];
}
user.selfCertifications.push(packetlist[i]); user.selfCertifications.push(packetlist[i]);
} else { } else {
if (!user.otherCertifications) {
user.otherCertifications = [];
}
user.otherCertifications.push(packetlist[i]); user.otherCertifications.push(packetlist[i]);
} }
break; break;
case enums.signature.cert_revocation: case enums.signature.cert_revocation:
if (user) { if (user) {
if (!user.revocationCertifications) { user.revocationSignatures.push(packetlist[i]);
user.revocationCertifications = [];
}
user.revocationCertifications.push(packetlist[i]);
} else { } else {
if (!this.directSignatures) {
this.directSignatures = [];
}
this.directSignatures.push(packetlist[i]); this.directSignatures.push(packetlist[i]);
} }
break; break;
case enums.signature.key: case enums.signature.key:
if (!this.directSignatures) {
this.directSignatures = [];
}
this.directSignatures.push(packetlist[i]); this.directSignatures.push(packetlist[i]);
break; break;
case enums.signature.subkey_binding: case enums.signature.subkey_binding:
@ -136,14 +115,14 @@ Key.prototype.packetlist2structure = function(packetlist) {
subKey.bindingSignatures.push(packetlist[i]); subKey.bindingSignatures.push(packetlist[i]);
break; break;
case enums.signature.key_revocation: case enums.signature.key_revocation:
this.revocationSignature = packetlist[i]; this.revocationSignatures.push(packetlist[i]);
break; break;
case enums.signature.subkey_revocation: case enums.signature.subkey_revocation:
if (!subKey) { if (!subKey) {
util.print_debug('Dropping subkey revocation signature without preceding subkey packet'); util.print_debug('Dropping subkey revocation signature without preceding subkey packet');
continue; continue;
} }
subKey.revocationSignature = packetlist[i]; subKey.revocationSignatures.push(packetlist[i]);
break; break;
} }
break; break;
@ -158,40 +137,42 @@ Key.prototype.packetlist2structure = function(packetlist) {
Key.prototype.toPacketlist = function() { Key.prototype.toPacketlist = function() {
const packetlist = new packet.List(); const packetlist = new packet.List();
packetlist.push(this.primaryKey); packetlist.push(this.primaryKey);
packetlist.push(this.revocationSignature); packetlist.concat(this.revocationSignatures);
packetlist.concat(this.directSignatures); packetlist.concat(this.directSignatures);
let i; this.users.map(user => packetlist.concat(user.toPacketlist()));
for (i = 0; i < this.users.length; i++) { this.subKeys.map(subKey => packetlist.concat(subKey.toPacketlist()));
packetlist.concat(this.users[i].toPacketlist());
}
if (this.subKeys) {
for (i = 0; i < this.subKeys.length; i++) {
packetlist.concat(this.subKeys[i].toPacketlist());
}
}
return packetlist; return packetlist;
}; };
/** /**
* Returns all the private and public subkey packets * Returns packetlist containing all public or private subkey packets matching keyId;
* @returns {Array<(module:packet/public_subkey|module:packet/secret_subkey)>} * If keyId is not present, returns all subkey packets.
* @param {type/keyid} keyId
* @returns {module:packet/packetlist}
*/ */
Key.prototype.getSubkeyPackets = function() { Key.prototype.getSubkeyPackets = function(keyId=null) {
const subKeys = []; const packets = new packet.List();
if (this.subKeys) { this.subKeys.forEach(subKey => {
for (let i = 0; i < this.subKeys.length; i++) { if (!keyId || subKey.subKey.getKeyId().equals(keyId)) {
subKeys.push(this.subKeys[i].subKey); packets.push(subKey.subKey);
} }
} });
return subKeys; return packets;
}; };
/** /**
* Returns all the private and public key and subkey packets * Returns a packetlist containing all public or private key packets matching keyId.
* @returns {Array<(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key)>} * If keyId is not present, returns all key packets starting with the primary key.
* @param {type/keyid} keyId
* @returns {module:packet/packetlist}
*/ */
Key.prototype.getAllKeyPackets = function() { Key.prototype.getKeyPackets = function(keyId=null) {
return [this.primaryKey].concat(this.getSubkeyPackets()); const packets = new packet.List();
if (!keyId || this.primaryKey.getKeyId().equals(keyId)) {
packets.push(this.primaryKey);
}
packets.concat(this.getSubkeyPackets(keyId));
return packets;
}; };
/** /**
@ -199,32 +180,7 @@ Key.prototype.getAllKeyPackets = function() {
* @returns {Array<module:type/keyid>} * @returns {Array<module:type/keyid>}
*/ */
Key.prototype.getKeyIds = function() { Key.prototype.getKeyIds = function() {
const keyIds = []; return this.getKeyPackets().map(keyPacket => keyPacket.getKeyId());
const keys = this.getAllKeyPackets();
for (let i = 0; i < keys.length; i++) {
keyIds.push(keys[i].getKeyId());
}
return keyIds;
};
/**
* Returns array containing first key packet for given key ID or all key packets in the case of a wildcard ID
* @param {type/keyid} keyId
* @returns {(module:packet/public_subkey|module:packet/public_key|
* module:packet/secret_subkey|module:packet/secret_key|null)}
*/
Key.prototype.getKeyPackets = function(packetKeyId) {
const keys = this.getAllKeyPackets();
if (packetKeyId.isWildcard()) {
return keys;
}
for (let i = 0; i < keys.length; i++) {
const keyId = keys[i].getKeyId();
if (keyId.equals(packetKeyId)) {
return [keys[i]];
}
}
return [];
}; };
/** /**
@ -297,11 +253,25 @@ Key.prototype.armor = function() {
return armor.encode(type, this.toPacketlist().write()); return armor.encode(type, this.toPacketlist().write());
}; };
function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) {
const normDate = util.normalizeDate(date);
return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) &&
keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) &&
keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) &&
(!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) &&
signature.verified && !signature.revoked && !signature.isExpired(normDate) &&
(normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity)));
}
/** /**
* Returns first key packet or key packet by given keyId that is available for signing or signature verification * Returns first key packet or key packet by given keyId that is available for signing and verification
*
* NOTE: call verifyKeyPackets before calling this function.
* @param {module:type/keyid} keyId, optional * @param {module:type/keyid} keyId, optional
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date use the given date for verification instead of the current time
* @returns {(module:packet/secret_subkey|module:packet/secret_key|null)} key packet or null if no signing key has been found * @returns {(module:packet/secret_subkey|
module:packet/secret_key|null)} key packet or null if no signing key has been found
*/ */
Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) { Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) {
const primaryUser = this.getPrimaryUser(date); const primaryUser = this.getPrimaryUser(date);
@ -309,7 +279,6 @@ Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) {
isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) {
return this.primaryKey; return this.primaryKey;
} }
if (this.subKeys) {
for (let i = 0; i < this.subKeys.length; i++) { for (let i = 0; i < this.subKeys.length; i++) {
if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) {
for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) {
@ -319,7 +288,7 @@ Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) {
} }
} }
} }
} // TODO throw descriptive error
return null; return null;
}; };
@ -332,31 +301,24 @@ function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) {
(!signature.keyFlags || (!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 ||
(signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) && (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) &&
(!signature.isExpired(normDate) && signature.verified && !signature.revoked && !signature.isExpired(normDate) &&
(normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity)))); (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity)));
}
function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) {
const normDate = util.normalizeDate(date);
return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) &&
keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) &&
keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) &&
(!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) &&
(!signature.isExpired(normDate) &&
(normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity))));
} }
/** /**
* Returns first key packet or key packet by given keyId that is available for encryption or decryption * Returns first key packet or key packet by given keyId that is available for encryption or decryption
*
* NOTE: call verifyKeyPackets before calling this function.
* @param {module:type/keyid} keyId, optional * @param {module:type/keyid} keyId, optional
* @param {Date} date optional * @param {Date} date, optional
* @returns {(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key|null)} key packet or null if no encryption key has been found * @returns {(module:packet/public_subkey|
* module:packet/secret_subkey|
* module:packet/secret_key|
* module:packet/public_key|null)} key packet or null if no encryption key has been found
*/ */
Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) {
// V4: by convention subkeys are preferred for encryption service // V4: by convention subkeys are preferred for encryption service
// V3: keys MUST NOT have subkeys // V3: keys MUST NOT have subkeys
if (this.subKeys) {
for (let i = 0; i < this.subKeys.length; i++) { for (let i = 0; i < this.subKeys.length; i++) {
if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) {
for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) {
@ -366,88 +328,103 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) {
} }
} }
} }
}
// if no valid subkey for encryption, evaluate primary key // if no valid subkey for encryption, evaluate primary key
const primaryUser = this.getPrimaryUser(date); const primaryUser = this.getPrimaryUser(date);
if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) &&
isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) {
return this.primaryKey; return this.primaryKey;
} }
// TODO throw descriptive error
return null; return null;
}; };
/** /**
* Encrypts all secret key and subkey packets * Encrypts all secret key and subkey packets matching keyId
* @param {module:type/keyid} keyId
* @param {String} passphrase * @param {String} passphrase
* @returns {Promise<Boolean>} * @returns {Promise<Array<module:packet/secret_key|module:packet/secret_subkey>>}
*/ */
Key.prototype.encrypt = async function(passphrase) { Key.prototype.encrypt = async function(passphrase, keyId=null) {
if (!this.isPrivate()) { if (!this.isPrivate()) {
throw new Error("Nothing to encrypt in a public key"); throw new Error("Nothing to encrypt in a public key");
} }
const keys = this.getAllKeyPackets(); return Promise.all(this.getKeyPackets(keyId).map(async function(keyPacket) {
await Promise.all(keys.map(async function(packet) { await keyPacket.encrypt(passphrase);
await packet.encrypt(passphrase); await keyPacket.clearPrivateParams();
await packet.clearPrivateParams(); return keyPacket;
return packet;
})); }));
return true;
}; };
/** /**
* Decrypts all secret key and subkey packets * Decrypts all secret key and subkey packets matching keyId
* @param {String} passphrase * @param {String} passphrase
* @returns {Promise<Boolean>} true if all key and subkey packets decrypted successfully * @param {module:type/keyid} keyId
* @returns {Promise<Boolean>} true if all matching key and subkey packets decrypted successfully
*/ */
Key.prototype.decrypt = async function(passphrase) { Key.prototype.decrypt = async function(passphrase, keyId=null) {
if (!this.isPrivate()) { if (!this.isPrivate()) {
throw new Error("Nothing to decrypt in a public key"); throw new Error("Nothing to decrypt in a public key");
} }
const keys = this.getAllKeyPackets(); const results = await Promise.all(this.getKeyPackets(keyId).map(async function(keyPacket) {
await Promise.all(keys.map(packet => packet.decrypt(passphrase))); return keyPacket.decrypt(passphrase);
return true; }));
return results.every(result => result === true);
}; };
/** /**
* Decrypts specific key packets by key ID * Checks if a signature on a key is revoked
* @param {Array<module:type/keyid>} keyIds * @param {module:packet/secret_key|
* @param {String} passphrase * @param {module:packet/signature} signature The signature to verify
* @returns {Boolean} true if all key packets decrypted successfully * @param {module:packet/public_subkey|
* module:packet/secret_subkey|
* module:packet/public_key|
* module:packet/secret_key} key, optional The key to verify the signature
* @param {Date} date Use the given date instead of the current time
* @returns {Promise<Boolean>} True if the certificate is revoked
*/ */
Key.prototype.decryptKeyPacket = function(keyIds, passphrase) { Key.prototype.isRevoked = async function(signature, key, date=new Date()) {
if (this.isPrivate()) { return isDataRevoked(
const keys = this.getAllKeyPackets(); this.primaryKey, { key: this.primaryKey }, this.revocationSignatures, signature, key, date
for (let i = 0; i < keys.length; i++) { );
const keyId = keys[i].getKeyId(); };
for (let j = 0; j < keyIds.length; j++) {
if (keyId.equals(keyIds[j])) { /**
const success = keys[i].decrypt(passphrase); * Returns a packetlist containing all verified public or private key packets matching keyId.
if (!success) { * If keyId is not present, returns all verified key packets starting with the primary key.
return false; * Verification is in the context of given date.
* @param {type/keyid} keyId
* @param {Date} date Use the given date instead of the current time
* @returns {Promise<module:packet/packetlist>}
*/
Key.prototype.verifyKeyPackets = async function(keyId=null, date=new Date()) {
const packets = new packet.List();
const { primaryKey } = this;
if (await this.verifyPrimaryKey(date)) {
if (!keyId || primaryKey.getKeyId().equals(keyId)) {
packets.push(primaryKey);
} }
} }
await Promise.all(this.subKeys.map(async subKey => {
if (!keyId || subKey.subKey.getKeyId().equals(keyId)) {
if (await subKey.verify(primaryKey, date)) {
packets.push(subKey.subKey);
} }
} }
} else { }));
throw new Error("Nothing to decrypt in a public key"); return packets;
}
return true;
}; };
/** /**
* Verify primary key. Checks for revocation signatures, expiration time * Verify primary key. Checks for revocation signatures, expiration time
* and valid self signature * and valid self signature
* @param {Date} date (optional) use the given date for verification instead of the current time * @param {Date} date (optional) use the given date for verification instead of the current time
* @returns {Promise{module:enums.keyStatus}} The status of the primary key * @returns {Promise<module:enums.keyStatus>} The status of the primary key
*/ */
Key.prototype.verifyPrimaryKey = async function(date=new Date()) { Key.prototype.verifyPrimaryKey = async function(date=new Date()) {
// TODO clarify OpenPGP's behavior given an expired revocation signature // check for key revocation signatures
// check revocation signature if (await this.isRevoked(null, null, date)) {
if (this.revocationSignature && !this.revocationSignature.isExpired() &&
(this.revocationSignature.verified ||
await this.revocationSignature.verify(this.primaryKey, { key: this.primaryKey }))) {
return enums.keyStatus.revoked; return enums.keyStatus.revoked;
} }
const creationTime = this.primaryKey.created.getTime(); const creationTime = this.primaryKey.created.getTime();
@ -461,7 +438,7 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) {
} }
// check for at least one self signature. Self signature of user ID not mandatory // check for at least one self signature. Self signature of user ID not mandatory
// See {@link https://tools.ietf.org/html/rfc4880#section-11.1} // See {@link https://tools.ietf.org/html/rfc4880#section-11.1}
if (!this.users.some(user => user.userId && user.selfCertifications)) { if (!this.users.some(user => user.userId && user.selfCertifications.length)) {
return enums.keyStatus.no_self_cert; return enums.keyStatus.no_self_cert;
} }
// check for valid self signature // check for valid self signature
@ -472,7 +449,8 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) {
} }
// check V4 expiration time // check V4 expiration time
if (date !== null && this.primaryKey.version === 4) { if (date !== null && this.primaryKey.version === 4) {
const expirationTime = primaryUser.selfCertificate.keyNeverExpires === false ? creationTime + primaryUser.selfCertificate.keyExpirationTime*1000 : Infinity; const expirationTime = primaryUser.selfCertificate.keyNeverExpires === false ?
creationTime + primaryUser.selfCertificate.keyExpirationTime*1000 : Infinity;
if (!(creationTime <= currentTime && currentTime < expirationTime)) { if (!(creationTime <= currentTime && currentTime < expirationTime)) {
return enums.keyStatus.expired; return enums.keyStatus.expired;
} }
@ -516,6 +494,8 @@ function getExpirationTime(keyPacket, selfCertificate, defaultValue=null) {
* - otherwise, returns the user with the latest self signature * - otherwise, returns the user with the latest self signature
* *
* NOTE: call verifyPrimaryUser before calling this function. * NOTE: call verifyPrimaryUser before calling this function.
* This is because getPrimaryUser isn't async, so it cannot validate and instead
* relies on already validated certificates.
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date use the given date for verification instead of the current time
* @returns {{user: Array<module:packet/User>, selfCertificate: Array<module:packet/signature>}|null} The primary user and the self signature * @returns {{user: Array<module:packet/User>, selfCertificate: Array<module:packet/signature>}|null} The primary user and the self signature
*/ */
@ -523,7 +503,7 @@ Key.prototype.getPrimaryUser = function(date=new Date()) {
let primaryUsers = []; let primaryUsers = [];
for (let i = 0; i < this.users.length; i++) { for (let i = 0; i < this.users.length; i++) {
// here we only check the primary user ID, ignoring the primary user attribute // here we only check the primary user ID, ignoring the primary user attribute
if (!this.users[i].userId || !this.users[i].selfCertifications) { if (!this.users[i].userId || !this.users[i].selfCertifications.length) {
continue; continue;
} }
for (let j = 0; j < this.users[i].selfCertifications.length; j++) { for (let j = 0; j < this.users[i].selfCertifications.length; j++) {
@ -548,7 +528,7 @@ Key.prototype.getPrimaryUser = function(date=new Date()) {
/** /**
* Update key with new components from specified key with same key ID: * Update key with new components from specified key with same key ID:
* users, subkeys, certificates are merged into the destination key, * users, subkeys, certificates are merged into the destination key,
* duplicates are ignored. * duplicates and expired signatures are ignored.
* If the specified key is a private key and the destination key is public, * If the specified key is a private key and the destination key is public,
* the destination key is transformed to a private key. * the destination key is transformed to a private key.
* @param {module:key~Key} key source key to merge * @param {module:key~Key} key source key to merge
@ -563,8 +543,8 @@ Key.prototype.update = async function(key) {
} }
if (this.isPublic() && key.isPrivate()) { if (this.isPublic() && key.isPrivate()) {
// check for equal subkey packets // check for equal subkey packets
const equal = ((this.subKeys && this.subKeys.length) === (key.subKeys && key.subKeys.length)) && const equal = (this.subKeys.length === key.subKeys.length) &&
(!this.subKeys || this.subKeys.every(function(destSubKey) { (this.subKeys.every(function(destSubKey) {
return key.subKeys.some(function(srcSubKey) { return key.subKeys.some(function(srcSubKey) {
return destSubKey.subKey.getFingerprint() === srcSubKey.subKey.getFingerprint(); return destSubKey.subKey.getFingerprint() === srcSubKey.subKey.getFingerprint();
}); });
@ -574,13 +554,10 @@ Key.prototype.update = async function(key) {
} }
this.primaryKey = key.primaryKey; this.primaryKey = key.primaryKey;
} }
// TODO clarify OpenPGP's behavior given an expired revocation signature // revocation signatures
// revocation signature await mergeSignatures(key, this, 'revocationSignatures', function(srcRevSig) {
if (!this.revocationSignature && key.revocationSignature && !key.revocationSignature.isExpired() && return isDataRevoked(that.primaryKey, that, [srcRevSig], null, key.primaryKey);
(key.revocationSignature.verified || });
await key.revocationSignature.verify(key.primaryKey, { key: key.primaryKey }))) {
this.revocationSignature = key.revocationSignature;
}
// direct signatures // direct signatures
await mergeSignatures(key, this, 'directSignatures'); await mergeSignatures(key, this, 'directSignatures');
// TODO replace when Promise.some or Promise.any are implemented // TODO replace when Promise.some or Promise.any are implemented
@ -600,7 +577,6 @@ Key.prototype.update = async function(key) {
})); }));
// TODO replace when Promise.some or Promise.any are implemented // TODO replace when Promise.some or Promise.any are implemented
// subkeys // subkeys
if (key.subKeys) {
await Promise.all(key.subKeys.map(async function(srcSubKey) { await Promise.all(key.subKeys.map(async function(srcSubKey) {
let found = false; let found = false;
await Promise.all(that.subKeys.map(async function(dstSubKey) { await Promise.all(that.subKeys.map(async function(dstSubKey) {
@ -613,7 +589,6 @@ Key.prototype.update = async function(key) {
that.subKeys.push(srcSubKey); that.subKeys.push(srcSubKey);
} }
})); }));
}
}; };
/** /**
@ -627,7 +602,7 @@ Key.prototype.update = async function(key) {
async function mergeSignatures(source, dest, attr, checkFn) { async function mergeSignatures(source, dest, attr, checkFn) {
source = source[attr]; source = source[attr];
if (source) { if (source) {
if (!dest[attr]) { if (!dest[attr].length) {
dest[attr] = source; dest[attr] = source;
} else { } else {
await Promise.all(source.map(async function(sourceSig) { await Promise.all(source.map(async function(sourceSig) {
@ -683,7 +658,8 @@ Key.prototype.signAllUsers = async function(privateKeys) {
* - if no arguments are given, verifies the self certificates; * - if no arguments are given, verifies the self certificates;
* - otherwise, verifies all certificates signed with given keys. * - otherwise, verifies all certificates signed with given keys.
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures * @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
* @returns {Promise<Array<({keyid: module:type/keyid, valid: Boolean})>>} list of signer's keyid and validity of signature * @returns {Promise<Array<{keyid: module:type/keyid,
* valid: Boolean}>>} List of signer's keyid and validity of signature
*/ */
Key.prototype.verifyPrimaryUser = async function(keys) { Key.prototype.verifyPrimaryUser = async function(keys) {
const { primaryKey } = this; const { primaryKey } = this;
@ -692,7 +668,7 @@ Key.prototype.verifyPrimaryUser = async function(keys) {
let lastPrimaryUserID = null; let lastPrimaryUserID = null;
await Promise.all(this.users.map(async function(user) { await Promise.all(this.users.map(async function(user) {
// here we verify both the primary user ID or the primary user attribute // here we verify both the primary user ID or the primary user attribute
if (!(user.userId || user.userAttribute) || !user.selfCertifications) { if (!(user.userId || user.userAttribute) || !user.selfCertifications.length) {
return; return;
} }
const dataToVerify = { userid: user.userId || user.userAttribute, key: primaryKey }; const dataToVerify = { userid: user.userId || user.userAttribute, key: primaryKey };
@ -733,7 +709,9 @@ Key.prototype.verifyPrimaryUser = async function(keys) {
* - if no arguments are given, verifies the self certificates; * - if no arguments are given, verifies the self certificates;
* - otherwise, verifies all certificates signed with given keys. * - otherwise, verifies all certificates signed with given keys.
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures * @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
* @returns {Promise<Array<({userid: String, keyid: module:type/keyid, valid: Boolean})>>} list of userid, signer's keyid and validity of signature * @returns {Promise<Array<{userid: String,
* keyid: module:type/keyid,
* valid: Boolean}>>} list of userid, signer's keyid and validity of signature
*/ */
Key.prototype.verifyAllUsers = async function(keys) { Key.prototype.verifyAllUsers = async function(keys) {
const results = []; const results = [];
@ -762,9 +740,9 @@ function User(userPacket) {
} }
this.userId = userPacket.tag === enums.packet.userid ? userPacket : null; this.userId = userPacket.tag === enums.packet.userid ? userPacket : null;
this.userAttribute = userPacket.tag === enums.packet.userAttribute ? userPacket : null; this.userAttribute = userPacket.tag === enums.packet.userAttribute ? userPacket : null;
this.selfCertifications = null; this.selfCertifications = [];
this.otherCertifications = null; this.otherCertifications = [];
this.revocationCertifications = null; this.revocationSignatures = [];
} }
/** /**
@ -774,41 +752,18 @@ function User(userPacket) {
User.prototype.toPacketlist = function() { User.prototype.toPacketlist = function() {
const packetlist = new packet.List(); const packetlist = new packet.List();
packetlist.push(this.userId || this.userAttribute); packetlist.push(this.userId || this.userAttribute);
packetlist.concat(this.revocationCertifications); packetlist.concat(this.revocationSignatures);
packetlist.concat(this.selfCertifications); packetlist.concat(this.selfCertifications);
packetlist.concat(this.otherCertifications); packetlist.concat(this.otherCertifications);
return packetlist; return packetlist;
}; };
/**
* Checks if a self certificate of the user is revoked
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
* @param {module:packet/signature} certificate The certificate to verify
* @param {module:packet/public_subkey|module:packet/public_key|
* module:packet/secret_subkey|module:packet/secret_key} key, optional The key to verify the signature
* @returns {Promise<Boolean>} True if the certificate is revoked
*/
User.prototype.isRevoked = async function(primaryKey, certificate, key) {
certificate.revoked = null;
if (this.revocationCertifications) {
const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey };
// TODO clarify OpenPGP's behavior given an expired revocation signature
const results = await Promise.all(this.revocationCertifications.map(async function(revCert) {
return revCert.issuerKeyId.equals(certificate.issuerKeyId) &&
!revCert.isExpired() &&
(revCert.verified || revCert.verify(key || primaryKey, dataToVerify));
}));
certificate.revoked = results.some(result => result === true);
return certificate.revoked;
}
return false;
};
/** /**
* Signs user * Signs user
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|
* @param {Array<module:key~Key>} privateKeys decrypted private keys for signing * module:packet/public_key} primaryKey The primary key packet
* @returns {Promise<module:key~Key>} new user with new certificate signatures * @param {Array<module:key~Key>} privateKeys Decrypted private keys for signing
* @returns {Promise<module:key~Key>} New user with new certificate signatures
*/ */
User.prototype.sign = async function(primaryKey, privateKeys) { User.prototype.sign = async function(primaryKey, privateKeys) {
const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey }; const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey };
@ -820,7 +775,7 @@ User.prototype.sign = async function(primaryKey, privateKeys) {
if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) { if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) {
throw new Error('Not implemented for self signing'); throw new Error('Not implemented for self signing');
} }
await privateKey.verifyPrimaryUser(); await privateKey.verifyKeyPackets();
const signingKeyPacket = privateKey.getSigningKeyPacket(); const signingKeyPacket = privateKey.getSigningKeyPacket();
if (!signingKeyPacket) { if (!signingKeyPacket) {
throw new Error(`Could not find valid signing key packet in key ${ throw new Error(`Could not find valid signing key packet in key ${
@ -843,12 +798,34 @@ User.prototype.sign = async function(primaryKey, privateKeys) {
return user; return user;
}; };
/**
* Checks if a given certificate of the user is revoked
* @param {module:packet/secret_key|
* module:packet/public_key} primaryKey The primary key packet
* @param {module:packet/signature} certificate The certificate to verify
* @param {module:packet/public_subkey|
* module:packet/secret_subkey|
* module:packet/public_key|
* module:packet/secret_key} key, optional The key to verify the signature
* @param {Date} date Use the given date instead of the current time
* @returns {Promise<Boolean>} True if the certificate is revoked
*/
User.prototype.isRevoked = async function(primaryKey, certificate, key, date=new Date()) {
return isDataRevoked(
primaryKey, {
key: primaryKey,
userid: this.userId || this.userAttribute
}, this.revocationSignatures, certificate, key, date
);
};
/** /**
* Verifies the user certificate * Verifies the user certificate
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|
module:packet/public_key} primaryKey The primary key packet
* @param {module:packet/signature} certificate A certificate of this user * @param {module:packet/signature} certificate A certificate of this user
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures * @param {Array<module:key~Key>} keys Array of keys to verify certificate signatures
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date Use the given date instead of the current time
* @returns {Promise<module:enums.keyStatus>} status of the certificate * @returns {Promise<module:enums.keyStatus>} status of the certificate
*/ */
User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date=new Date()) { User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date=new Date()) {
@ -857,7 +834,7 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys,
const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey };
const results = await Promise.all(keys.map(async function(key) { const results = await Promise.all(keys.map(async function(key) {
if (!key.getKeyIds().some(id => id.equals(keyid))) { return; } if (!key.getKeyIds().some(id => id.equals(keyid))) { return; }
await key.verifyPrimaryUser(); await key.verifyKeyPackets();
const keyPacket = key.getSigningKeyPacket(keyid, date); const keyPacket = key.getSigningKeyPacket(keyid, date);
if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) { if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) {
return enums.keyStatus.revoked; return enums.keyStatus.revoked;
@ -875,13 +852,15 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys,
/** /**
* Verifies all user certificates * Verifies all user certificates
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures * module:packet/public_key} primaryKey The primary key packet
* @returns {Promise<Array<({keyid: module:type/keyid, valid: Boolean})>>} list of signer's keyid and validity of signature * @param {Array<module:key~Key>} keys Array of keys to verify certificate signatures
* @returns {Promise<Array<{keyid: module:type/keyid,
* valid: Boolean}>>} List of signer's keyid and validity of signature
*/ */
User.prototype.verifyAllCertifications = async function(primaryKey, keys) { User.prototype.verifyAllCertifications = async function(primaryKey, keys) {
const that = this; const that = this;
const certifications = this.selfCertifications.concat(this.otherCertifications || []); const certifications = this.selfCertifications.concat(this.otherCertifications);
return Promise.all(certifications.map(async function(certification) { return Promise.all(certifications.map(async function(certification) {
const status = await that.verifyCertificate(primaryKey, certification, keys); const status = await that.verifyCertificate(primaryKey, certification, keys);
return { return {
@ -894,17 +873,19 @@ User.prototype.verifyAllCertifications = async function(primaryKey, keys) {
/** /**
* Verify User. Checks for existence of self signatures, revocation signatures * Verify User. Checks for existence of self signatures, revocation signatures
* and validity of self signature * and validity of self signature
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|
* @returns {Promise<module:enums.keyStatus>} status of user * module:packet/public_key} primaryKey The primary key packet
* @returns {Promise<module:enums.keyStatus>} Status of user
*/ */
User.prototype.verify = async function(primaryKey) { User.prototype.verify = async function(primaryKey) {
if (!this.selfCertifications) { if (!this.selfCertifications.length) {
return enums.keyStatus.no_self_cert; return enums.keyStatus.no_self_cert;
} }
const that = this; const that = this;
const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey };
// TODO replace when Promise.some or Promise.any are implemented // TODO replace when Promise.some or Promise.any are implemented
const results = [enums.keyStatus.invalid].concat(await Promise.all(this.selfCertifications.map(async function(selfCertification) { const results = [enums.keyStatus.invalid].concat(
await Promise.all(this.selfCertifications.map(async function(selfCertification) {
if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification)) { if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification)) {
return enums.keyStatus.revoked; return enums.keyStatus.revoked;
} }
@ -923,7 +904,7 @@ User.prototype.verify = async function(primaryKey) {
/** /**
* Update user with new components from specified user * Update user with new components from specified user
* @param {module:key~User} user source user to merge * @param {module:key~User} user source user to merge
* @param {Promise<module:packet/signature>} primaryKey primary key used for validation * @param {module:packet/signature} primaryKey primary key used for validation
*/ */
User.prototype.update = async function(user, primaryKey) { User.prototype.update = async function(user, primaryKey) {
const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey };
@ -934,7 +915,9 @@ User.prototype.update = async function(user, primaryKey) {
// other signatures // other signatures
await mergeSignatures(user, this, 'otherCertifications'); await mergeSignatures(user, this, 'otherCertifications');
// revocation signatures // revocation signatures
await mergeSignatures(user, this, 'revocationCertifications'); await mergeSignatures(user, this, 'revocationSignatures', function(srcRevSig) {
return isDataRevoked(primaryKey, dataToVerify, [srcRevSig]);
});
}; };
/** /**
@ -947,7 +930,7 @@ function SubKey(subKeyPacket) {
} }
this.subKey = subKeyPacket; this.subKey = subKeyPacket;
this.bindingSignatures = []; this.bindingSignatures = [];
this.revocationSignature = null; this.revocationSignatures = [];
} }
/** /**
@ -957,65 +940,43 @@ function SubKey(subKeyPacket) {
SubKey.prototype.toPacketlist = function() { SubKey.prototype.toPacketlist = function() {
const packetlist = new packet.List(); const packetlist = new packet.List();
packetlist.push(this.subKey); packetlist.push(this.subKey);
packetlist.push(this.revocationSignature); packetlist.concat(this.revocationSignatures);
for (let i = 0; i < this.bindingSignatures.length; i++) { packetlist.concat(this.bindingSignatures);
packetlist.push(this.bindingSignatures[i]);
}
return packetlist; return packetlist;
}; };
/** /**
* Returns true if the subkey can be used for encryption * Checks if a binding signature of a subkey is revoked
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|
* @param {Date} date use the given date for verification instead of the current time * module:packet/public_key} primaryKey The primary key packet
* @returns {Promise<Boolean>} * @param {module:packet/signature} signature The binding signature to verify
* @param {module:packet/public_subkey|
* module:packet/secret_subkey|
* module:packet/public_key|
* module:packet/secret_key} key, optional The key to verify the signature
* @param {Date} date Use the given date instead of the current time
* @returns {Promise<Boolean>} True if the binding signature is revoked
*/ */
SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date()) { SubKey.prototype.isRevoked = async function(primaryKey, signature, key, date=new Date()) {
if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { return isDataRevoked(
return false; primaryKey, {
} key: primaryKey,
for (let i = 0; i < this.bindingSignatures.length; i++) { bind: this.subKey
if (isValidEncryptionKeyPacket(this.subKey, this.bindingSignatures[i], date)) { }, this.revocationSignatures, signature, key, date
return true; );
}
}
return false;
};
/**
* Returns true if the subkey can be used for signing of data
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
* @param {Date} date use the given date for verification instead of the current time
* @returns {Promise<Boolean>}
*/
SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date()) {
if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) {
return false;
}
for (let i = 0; i < this.bindingSignatures.length; i++) {
if (isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i], date)) {
return true;
}
}
return false;
}; };
/** /**
* Verify subkey. Checks for revocation signatures, expiration time * Verify subkey. Checks for revocation signatures, expiration time
* and valid binding signature * and valid binding signature
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|
* @param {Date} date use the given date for verification instead of the current time * module:packet/public_key} primaryKey The primary key packet
* @param {Date} date Use the given date instead of the current time
* @returns {Promise<module:enums.keyStatus>} The status of the subkey * @returns {Promise<module:enums.keyStatus>} The status of the subkey
*/ */
SubKey.prototype.verify = async function(primaryKey, date=new Date()) { SubKey.prototype.verify = async function(primaryKey, date=new Date()) {
const that = this; const that = this;
// TODO clarify OpenPGP's behavior given an expired revocation signature const dataToVerify = { key: primaryKey, bind: this.subKey };
// check subkey revocation signature
if (this.revocationSignature && !this.revocationSignature.isExpired() &&
(this.revocationSignature.verified ||
await this.revocationSignature.verify(primaryKey, { key: primaryKey, bind: this.subKey }))) {
return enums.keyStatus.revoked;
}
const creationTime = this.subKey.created.getTime(); const creationTime = this.subKey.created.getTime();
const currentTime = util.normalizeDate(date); const currentTime = util.normalizeDate(date);
// check V3 expiration time // check V3 expiration time
@ -1027,25 +988,31 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) {
} }
// check subkey binding signatures (at least one valid binding sig needed) // check subkey binding signatures (at least one valid binding sig needed)
// TODO replace when Promise.some or Promise.any are implemented // TODO replace when Promise.some or Promise.any are implemented
const results = [enums.keyStatus.invalid].concat(await Promise.all(this.bindingSignatures.map(async function(bindingSignature) { const results = [enums.keyStatus.invalid].concat(
await Promise.all(this.bindingSignatures.map(async function(bindingSignature) {
// check binding signature is verified
if (!(bindingSignature.verified || await bindingSignature.verify(primaryKey, dataToVerify))) {
return enums.keyStatus.invalid;
}
// check binding signature is not revoked
if (bindingSignature.revoked || await that.isRevoked(primaryKey, bindingSignature, null, date)) {
return enums.keyStatus.revoked;
}
// check binding signature is not expired // check binding signature is not expired
if (bindingSignature.isExpired(date)) { if (bindingSignature.isExpired(date)) {
return enums.keyStatus.expired; // last expired binding signature return enums.keyStatus.expired;
}
// check binding signature can verify
if (!(bindingSignature.verified ||
await bindingSignature.verify(primaryKey, { key: primaryKey, bind: that.subKey }))) {
return enums.keyStatus.invalid; // last invalid binding signature
} }
// check V4 expiration time // check V4 expiration time
if (that.subKey.version === 4 && currentTime !== null) { if (that.subKey.version === 4 && currentTime !== null) {
const expirationTime = bindingSignature.keyNeverExpires === false ? (creationTime + bindingSignature.keyExpirationTime*1000) : Infinity; const expirationTime = bindingSignature.keyNeverExpires === false ?
(creationTime + bindingSignature.keyExpirationTime*1000) : Infinity;
if (!(creationTime <= currentTime && currentTime < expirationTime)) { if (!(creationTime <= currentTime && currentTime < expirationTime)) {
return enums.keyStatus.expired; // last V4 expired binding signature return enums.keyStatus.expired; // last V4 expired binding signature
} }
} }
return enums.keyStatus.valid; // found a binding signature that passed all checks return enums.keyStatus.valid; // found a binding signature that passed all checks
}))); }))
);
return results.some(status => status === enums.keyStatus.valid) ? return results.some(status => status === enums.keyStatus.valid) ?
enums.keyStatus.valid : results.pop(); enums.keyStatus.valid : results.pop();
}; };
@ -1071,7 +1038,7 @@ SubKey.prototype.getExpirationTime = function() {
/** /**
* Update subkey with new components from specified subkey * Update subkey with new components from specified subkey
* @param {module:key~SubKey} subKey source subkey to merge * @param {module:key~SubKey} subKey source subkey to merge
* @param {Promise<module:packet/signature>} primaryKey primary key used for validation * @param {module:packet/signature} primaryKey primary key used for validation
*/ */
SubKey.prototype.update = async function(subKey, primaryKey) { SubKey.prototype.update = async function(subKey, primaryKey) {
if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) {
@ -1087,27 +1054,24 @@ SubKey.prototype.update = async function(subKey, primaryKey) {
} }
// update missing binding signatures // update missing binding signatures
const that = this; const that = this;
await Promise.all(subKey.bindingSignatures.map(async function(newBindingSignature) { const dataToVerify = { key: primaryKey, bind: that.subKey };
if (newBindingSignature.verified || await mergeSignatures(subKey, this, 'bindingSignatures', async function(srcBindSig) {
await newBindingSignature.verify(primaryKey, { key: primaryKey, bind: that.subKey })) { if (!(srcBindSig.verified || await srcBindSig.verify(primaryKey, dataToVerify))) {
return false;
}
for (let i = 0; i < that.bindingSignatures.length; i++) { for (let i = 0; i < that.bindingSignatures.length; i++) {
if (that.bindingSignatures[i].issuerKeyId.equals(newBindingSignature.issuerKeyId)) { if (that.bindingSignatures[i].issuerKeyId.equals(srcBindSig.issuerKeyId)) {
that.bindingSignatures[i] = newBindingSignature; // TODO check which one is more recent
return; that.bindingSignatures[i] = srcBindSig;
return false;
} }
} }
that.bindingSignatures.push(newBindingSignature); return true;
} });
})); // revocation signatures
// TODO clarify OpenPGP's behavior given an expired revocation signature await mergeSignatures(subKey, this, 'revocationSignatures', function(srcRevSig) {
// revocation signature return isDataRevoked(primaryKey, dataToVerify, [srcRevSig]);
if (!this.revocationSignature && });
subKey.revocationSignature &&
!subKey.revocationSignature.isExpired() &&
(subKey.revocationSignature.verified ||
await subKey.revocationSignature.verify(primaryKey, { key: primaryKey, bind: this.subKey }))) {
this.revocationSignature = subKey.revocationSignature;
}
}; };
/** /**
@ -1146,7 +1110,8 @@ export function read(data) {
/** /**
* Reads an OpenPGP armored text and returns one or multiple key objects * Reads an OpenPGP armored text and returns one or multiple key objects
* @param {String} armoredText text to be parsed * @param {String} armoredText text to be parsed
* @returns {{keys: Array<module:key~Key>, err: (Array<Error>|null)}} result object with key and error arrays * @returns {{keys: Array<module:key~Key>,
err: (Array<Error>|null)}} result object with key and error arrays
* @static * @static
*/ */
export function readArmored(armoredText) { export function readArmored(armoredText) {
@ -1255,16 +1220,13 @@ export function generate(options) {
export async function reformat(options) { export async function reformat(options) {
let secretKeyPacket; let secretKeyPacket;
let secretSubkeyPacket; let secretSubkeyPacket;
options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign;
if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated
if (options.keyType !== enums.publicKey.rsa_encrypt_sign) {
throw new Error('Only RSA Encrypt or Sign supported'); throw new Error('Only RSA Encrypt or Sign supported');
} }
try { if (!options.privateKey.decrypt()) {
await options.privateKey.decrypt();
}
catch(err) {
throw new Error('Key not decrypted'); throw new Error('Key not decrypted');
} }
@ -1295,7 +1257,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) {
if (options.passphrase) { if (options.passphrase) {
await secretKeyPacket.encrypt(options.passphrase); await secretKeyPacket.encrypt(options.passphrase);
if (secretSubkeyPacket) { if (secretSubkeyPacket) {
secretSubkeyPacket.encrypt(options.passphrase); await secretSubkeyPacket.encrypt(options.passphrase);
} }
} }
@ -1380,6 +1342,43 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) {
return new Key(packetlist); return new Key(packetlist);
} }
/**
* Checks if a given certificate or binding signature is revoked
* @param {module:packet/secret_key|
* module:packet/public_key} primaryKey The primary key packet
* @param {Object} dataToVerify The data to check
* @param {Array<module:packet/signature>} revocations The revocation signatures to check
* @param {module:packet/signature} signature The certificate or signature to check
* @param {module:packet/public_subkey|
* module:packet/secret_subkey|
* module:packet/public_key|
* module:packet/secret_key} key, optional The key packet to check the signature
* @param {Date} date Use the given date instead of the current time
* @returns {Promise<Boolean>} True if the signature revokes the data
*/
async function isDataRevoked(primaryKey, dataToVerify, revocations, signature, key, date=new Date()) {
key = key || primaryKey;
const normDate = util.normalizeDate(date);
const revocationKeyIds = [];
await Promise.all(revocations.map(async function(revocationSignature) {
if (!(config.revocations_expire && revocationSignature.isExpired(normDate)) &&
(revocationSignature.verified || await revocationSignature.verify(key, dataToVerify))) {
// TODO get an identifier of the revoked object instead
revocationKeyIds.push(revocationSignature.issuerKeyId);
return true;
}
return false;
}));
// TODO further verify that this is the signature that should be revoked
// In particular, if signature.issuerKeyId is a wildcard, any revocation signature will revoke it
if (signature) {
signature.revoked = revocationKeyIds.some(keyId => keyId.equals(signature.issuerKeyId)) ? true :
signature.revoked;
return signature.revoked;
}
return revocationKeyIds.length > 0;
}
/** /**
* Returns the preferred signature hash algorithm of a key * Returns the preferred signature hash algorithm of a key
* @param {object} key * @param {object} key
@ -1388,7 +1387,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) {
export function getPreferredHashAlgo(key) { export function getPreferredHashAlgo(key) {
let hash_algo = config.prefer_hash_algorithm; let hash_algo = config.prefer_hash_algorithm;
let pref_algo = hash_algo; let pref_algo = hash_algo;
if (Key.prototype.isPrototypeOf(key)) { if (key instanceof Key) {
const primaryUser = key.getPrimaryUser(); const primaryUser = key.getPrimaryUser();
if (primaryUser && primaryUser.selfCertificate.preferredHashAlgorithms) { if (primaryUser && primaryUser.selfCertificate.preferredHashAlgorithms) {
[pref_algo] = primaryUser.selfCertificate.preferredHashAlgorithms; [pref_algo] = primaryUser.selfCertificate.preferredHashAlgorithms;

View File

@ -148,24 +148,23 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) {
if (!symESKeyPacketlist) { if (!symESKeyPacketlist) {
throw new Error('No symmetrically encrypted session key packet found.'); throw new Error('No symmetrically encrypted session key packet found.');
} }
await Promise.all(symESKeyPacketlist.map(async function(packet) { await Promise.all(symESKeyPacketlist.map(async function(keyPacket) {
await Promise.all(passwords.map(async function(password) { await Promise.all(passwords.map(async function(password) {
try { try {
await packet.decrypt(password); await keyPacket.decrypt(password);
keyPackets.push(packet); keyPackets.push(keyPacket);
} catch (err) {} } catch (err) {}
})); }));
})); }));
} else if (privateKeys) { } else if (privateKeys) {
const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
if (!pkESKeyPacketlist) { if (!pkESKeyPacketlist) {
throw new Error('No public key encrypted session key packet found.'); throw new Error('No public key encrypted session key packet found.');
} }
await Promise.all(pkESKeyPacketlist.map(async function(packet) { await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) {
const privateKeyPackets = privateKeys.reduce(function(acc, privateKey) { const privateKeyPackets = privateKeys.reduce(function(acc, privateKey) {
return acc.concat(privateKey.getKeyPackets(packet.publicKeyId)); return acc.concat(privateKey.getKeyPackets(keyPacket.publicKeyId));
}, []); }, new packet.List());
await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) {
if (!privateKeyPacket) { if (!privateKeyPacket) {
return; return;
@ -174,8 +173,8 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) {
throw new Error('Private key is not decrypted.'); throw new Error('Private key is not decrypted.');
} }
try { try {
await packet.decrypt(privateKeyPacket); await keyPacket.decrypt(privateKeyPacket);
keyPackets.push(packet); keyPackets.push(keyPacket);
} catch (err) {} } catch (err) {}
})); }));
})); }));
@ -302,7 +301,7 @@ export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwor
if (publicKeys) { if (publicKeys) {
const results = await Promise.all(publicKeys.map(async function(key) { const results = await Promise.all(publicKeys.map(async function(key) {
await key.verifyPrimaryUser(); await key.verifyKeyPackets(undefined, date);
const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date); const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date);
if (!encryptionKeyPacket) { if (!encryptionKeyPacket) {
throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex());
@ -318,7 +317,6 @@ export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwor
})); }));
packetlist.concat(results); packetlist.concat(results);
} }
if (passwords) { if (passwords) {
const testDecrypt = async function(keyPacket, password) { const testDecrypt = async function(keyPacket, password) {
try { try {
@ -396,7 +394,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new
if (privateKey.isPublic()) { if (privateKey.isPublic()) {
throw new Error('Need private key for signing'); throw new Error('Need private key for signing');
} }
await privateKey.verifyPrimaryUser(); await privateKey.verifyKeyPackets(undefined, date);
const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date); const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date);
if (!signingKeyPacket) { if (!signingKeyPacket) {
throw new Error('Could not find valid key packet for signing in key ' + throw new Error('Could not find valid key packet for signing in key ' +
@ -475,10 +473,11 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
if (privateKey.isPublic()) { if (privateKey.isPublic()) {
throw new Error('Need private key for signing'); throw new Error('Need private key for signing');
} }
await privateKey.verifyPrimaryUser(); await privateKey.verifyKeyPackets(undefined, date);
const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date); const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date);
if (!signingKeyPacket) { if (!signingKeyPacket) {
throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); throw new Error('Could not find valid key packet for signing in key ' +
privateKey.primaryKey.getKeyId().toHex());
} }
if (!signingKeyPacket.isDecrypted) { if (!signingKeyPacket.isDecrypted) {
throw new Error('Private key is not decrypted.'); throw new Error('Private key is not decrypted.');
@ -545,15 +544,14 @@ export async function createVerificationObjects(signatureList, literalDataList,
return Promise.all(signatureList.map(async function(signature) { return Promise.all(signatureList.map(async function(signature) {
let keyPacket = null; let keyPacket = null;
await Promise.all(keys.map(async function(key) { await Promise.all(keys.map(async function(key) {
await key.verifyPrimaryUser();
// Look for the unique key packet that matches issuerKeyId of signature // Look for the unique key packet that matches issuerKeyId of signature
await key.verifyKeyPackets(signature.issuerKeyId, date);
const result = key.getSigningKeyPacket(signature.issuerKeyId, date); const result = key.getSigningKeyPacket(signature.issuerKeyId, date);
if (result) { if (result) {
keyPacket = result; keyPacket = result;
} }
})); }));
// Look for the unique key packet that matches issuerKeyId of signature
const verifiedSig = { const verifiedSig = {
keyid: signature.issuerKeyId, keyid: signature.issuerKeyId,
valid: keyPacket ? await signature.verify(keyPacket, literalDataList[0]) : null valid: keyPacket ? await signature.verify(keyPacket, literalDataList[0]) : null

View File

@ -177,6 +177,19 @@ Packetlist.prototype.some = async function (callback) {
return false; return false;
}; };
/**
* Executes the callback function once for each element,
* returns true if all callbacks returns a truthy value
*/
Packetlist.prototype.every = function (callback) {
for (let i = 0; i < this.length; i++) {
if (!callback(this[i], i, this)) {
return false;
}
}
return true;
};
/** /**
* Traverses packet tree and returns first matching packet * Traverses packet tree and returns first matching packet
* @param {module:enums.packet} type The packet type * @param {module:enums.packet} type The packet type
@ -240,6 +253,7 @@ Packetlist.prototype.concat = function (packetlist) {
this.push(packetlist[i]); this.push(packetlist[i]);
} }
} }
return this;
}; };
/** /**

View File

@ -86,6 +86,7 @@ export default function Signature(date=new Date()) {
this.embeddedSignature = null; this.embeddedSignature = null;
this.verified = null; this.verified = null;
this.revoked = null;
} }
/** /**

View File

@ -53,7 +53,7 @@ Keyid.prototype.toHex = function() {
}; };
Keyid.prototype.equals = function(keyid) { Keyid.prototype.equals = function(keyid) {
return this.bytes === keyid.bytes; return keyid.isWildcard() || this.bytes === keyid.bytes;
}; };
Keyid.prototype.isNull = function() { Keyid.prototype.isNull = function() {

View File

@ -1278,10 +1278,11 @@ describe('Key', function() {
}); });
}); });
it('Find a valid subkey binding signature among many invalid ones', function(done) { it('Find a valid subkey binding signature among many invalid ones', function() {
const k = openpgp.key.readArmored(valid_binding_sig_among_many_expired_sigs_pub).keys[0]; const k = openpgp.key.readArmored(valid_binding_sig_among_many_expired_sigs_pub).keys[0];
return k.verifyKeyPackets().then(() => {
expect(k.getEncryptionKeyPacket()).to.not.be.null; expect(k.getEncryptionKeyPacket()).to.not.be.null;
done(); })
}); });
}); });

View File

@ -448,8 +448,7 @@ describe("Signature", function() {
const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0];
const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0];
const keyids = esMsg.getEncryptionKeyIds(); esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId));
privKey.decryptKeyPacket(keyids, 'hello world');
return openpgp.decrypt({ privateKeys: privKey, publicKeys:[pubKey], message:esMsg }).then(function(decrypted) { return openpgp.decrypt({ privateKeys: privKey, publicKeys:[pubKey], message:esMsg }).then(function(decrypted) {
expect(decrypted.data).to.exist; expect(decrypted.data).to.exist;
@ -483,8 +482,7 @@ describe("Signature", function() {
const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0];
const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0];
const keyids = esMsg.getEncryptionKeyIds(); esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId));
privKey.decryptKeyPacket(keyids, 'hello world');
return openpgp.decrypt({ privateKeys: privKey, publicKeys:[pubKey], message:esMsg }).then(function(decrypted) { return openpgp.decrypt({ privateKeys: privKey, publicKeys:[pubKey], message:esMsg }).then(function(decrypted) {
expect(decrypted.data).to.exist; expect(decrypted.data).to.exist;