Add key update method. Fix getPrimaryUser: evaluation of most significant self signature.

This commit is contained in:
Thomas Oberndörfer 2014-02-23 20:46:52 +01:00
parent 753b1fc637
commit d6e4e3c028
2 changed files with 157 additions and 5 deletions

View File

@ -474,13 +474,13 @@ function getExpirationTime(keyPacket, selfCertificate) {
return new Date(keyPacket.created.getTime() + selfCertificate.keyExpirationTime*1000);
}
return null;
};
}
/**
* Returns primary user and most significant (latest valid) self signature
* - if multiple users are marked as primary users returns the one with the latest self signature
* - if no primary user is found returns the user with the latest self signature
* @return {{user: Array<module:packet/User>, selfCertificate: Array<module:packet/signature>}} The primary user and the self signature
* @return {{user: Array<module:packet/User>, selfCertificate: Array<module:packet/signature>}|null} The primary user and the self signature
*/
Key.prototype.getPrimaryUser = function() {
var user = null;
@ -493,9 +493,9 @@ Key.prototype.getPrimaryUser = function() {
if (!selfCert) {
continue;
}
if (!user ||
!userSelfCert.isPrimaryUserID && selfCert.isPrimaryUserID ||
userSelfCert.created < selfCert.created) {
if (!user ||
(!userSelfCert.isPrimaryUserID || selfCert.isPrimaryUserID) &&
userSelfCert.created > selfCert.created) {
user = this.users[i];
userSelfCert = selfCert;
}
@ -503,6 +503,102 @@ Key.prototype.getPrimaryUser = function() {
return user ? {user: user, selfCertificate: userSelfCert} : null;
};
/**
* Update key with new components from specified key with same key ID:
* users, subkeys, certificates are merged into the destination key,
* duplicates are ignored.
* If the specified key is a private key and the destination key is public,
* the destination key is tranformed to a private key.
* @param {module:key~Key} key source key to merge
*/
Key.prototype.update = function(key) {
var that = this;
if (key.verifyPrimaryKey() === enums.keyStatus.invalid) {
return;
}
if (this.primaryKey.getFingerprint() !== key.primaryKey.getFingerprint()) {
throw new Error('Key update method: fingerprints of keys not equal');
}
if (this.isPublic() && key.isPrivate()) {
// check for equal subkey packets
var equal = ((this.subKeys && this.subKeys.length) === (key.subKeys && key.subKeys.length)) &&
(!this.subKeys || this.subKeys.every(function(destSubKey) {
return key.subKeys.some(function(srcSubKey) {
return destSubKey.subKey.getFingerprint() === srcSubKey.subKey.getFingerprint();
});
}));
if (!equal) {
throw new Error('Cannot update public key with private key if subkey mismatch');
}
this.primaryKey = key.primaryKey;
}
// revocation signature
if (!this.revocationSignature && key.revocationSignature && !key.revocationSignature.isExpired() &&
(key.revocationSignature.verified ||
key.revocationSignature.verify(key.primaryKey, {key: key.primaryKey}))) {
this.revocationSignature = key.revocationSignature;
}
// direct signatures
mergeSignatures(key, this, 'directSignatures');
// users
key.users.forEach(function(srcUser) {
var found = false;
for (var i = 0; i < that.users.length; i++) {
if (srcUser.userId && (srcUser.userId.userid === that.users[i].userId.userid) ||
srcUser.userAttribute && (srcUser.userAttribute.equals(that.users[i].userAttribute))) {
that.users[i].update(srcUser, that.primaryKey);
found = true;
break;
}
}
if (!found) {
that.users.push(srcUser);
}
});
// subkeys
if (key.subKeys) {
key.subKeys.forEach(function(srcSubKey) {
var found = false;
for (var i = 0; i < that.subKeys.length; i++) {
if (srcSubKey.subKey.getFingerprint() === that.subKeys[i].subKey.getFingerprint()) {
that.subKeys[i].update(srcSubKey, that.primaryKey);
found = true;
break;
}
}
if (!found) {
that.subKeys.push(srcSubKey);
}
});
}
};
/**
* Merges signatures from source[attr] to dest[attr]
* @private
* @param {Object} source
* @param {Object} dest
* @param {String} attr
* @param {Function} checkFn optional, signature only merged if true
*/
function mergeSignatures(source, dest, attr, checkFn) {
source = source[attr];
if (source) {
if (!dest[attr]) {
dest[attr] = source;
} else {
source.forEach(function(sourceSig) {
if (!sourceSig.isExpired() && (!checkFn || checkFn(sourceSig)) &&
!dest[attr].some(function(destSig) {
return destSig.signature === sourceSig.signature;
})) {
dest[attr].push(sourceSig);
}
});
}
}
}
// TODO
Key.prototype.revoke = function() {
@ -616,6 +712,24 @@ User.prototype.verify = function(primaryKey) {
return status;
};
/**
* Update user with new components from specified user
* @param {module:key~User} user source user to merge
* @param {module:packet/signature} primaryKey primary key used for validation
*/
User.prototype.update = function(user, primaryKey) {
var that = this;
// self signatures
mergeSignatures(user, this, 'selfCertifications', function(srcSelfSig) {
return srcSelfSig.verified ||
srcSelfSig.verify(primaryKey, {userid: that.userId || that.userAttribute, key: primaryKey});
});
// other signatures
mergeSignatures(user, this, 'otherCertifications');
// revocation signatures
mergeSignatures(user, this, 'revocationCertifications');
};
/**
* @class
* @classdesc Class that represents a subkey packet and the relevant signatures.
@ -706,6 +820,30 @@ SubKey.prototype.getExpirationTime = function() {
return getExpirationTime(this.subKey, this.bindingSignature);
};
/**
* Update subkey with new components from specified subkey
* @param {module:key~SubKey} subKey source subkey to merge
* @param {module:packet/signature} primaryKey primary key used for validation
*/
SubKey.prototype.update = function(subKey, primaryKey) {
if (this.verify(primaryKey) === enums.keyStatus.invalid) {
return;
}
if (this.subKey.getFingerprint() !== subKey.subKey.getFingerprint()) {
throw new Error('SubKey update method: fingerprints of subkeys not equal');
}
if (this.subKey.tag === enums.packet.publicSubkey &&
subKey.subKey.tag === enums.packet.secretSubkey) {
this.subKey = subKey.subKey;
}
// revocation signature
if (!this.revocationSignature && subKey.revocationSignature && !subKey.revocationSignature.isExpired() &&
(subKey.revocationSignature.verified ||
subKey.revocationSignature.verify(primaryKey, {key: primaryKey, bind: this.subKey}))) {
this.revocationSignature = subKey.revocationSignature;
}
};
/**
* Reads an OpenPGP armored text and returns one or multiple key objects
* @param {String} armoredText text to be parsed

View File

@ -64,3 +64,17 @@ UserAttribute.prototype.read = function(bytes) {
i += len.len;
}
};
/**
* Compare for equality
* @param {module:user_attribute~UserAttribute} usrAttr
* @return {Boolean} true if equal
*/
UserAttribute.prototype.equals = function(usrAttr) {
if (!usrAttr || !(usrAttr instanceof UserAttribute)) {
return false;
}
return this.attributes.every(function(attr, index) {
return attr === usrAttr.attributes[index];
});
};