Merge pull request #179 from toberndo/keyring_enh

Refactoring keyring, Key update method, Fixes
This commit is contained in:
Tankred Hase 2014-02-27 11:04:25 +01:00
commit 7cba2be2fc
9 changed files with 723 additions and 173 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

@ -27,14 +27,6 @@ var enums = require('../enums.js'),
keyModule = require('../key.js'),
util = require('../util.js');
/**
* Callback to check if a key matches the input
* @callback module:keyring/keyring.checkCallback
* @param {String} input input to search for
* @param {module:key~Key} key The key to be checked.
* @return {Boolean} True if the input matches the specified key
*/
module.exports = Keyring;
/**
@ -45,25 +37,87 @@ module.exports = Keyring;
*/
function Keyring(storeHandler) {
this.storeHandler = storeHandler || new (require('./localstore.js'))();
this.keys = this.storeHandler.load();
};
this.publicKeys = new KeyArray(this.storeHandler.loadPublic());
this.privateKeys = new KeyArray(this.storeHandler.loadPrivate());
}
/**
* Calls the storeHandler to save the keys
*/
Keyring.prototype.store = function () {
this.storeHandler.store(this.keys);
this.storeHandler.storePublic(this.publicKeys.keys);
this.storeHandler.storePrivate(this.privateKeys.keys);
};
/**
* Clear the keyring - erase all the keys
*/
Keyring.prototype.clear = function() {
this.keys = [];
this.publicKeys.keys = [];
this.privateKeys.keys = [];
};
/**
* Searches the keyring for keys having the specified key id
* @param {String} keyId provided as string of lowercase hex number
* withouth 0x prefix (can be 16-character key ID or fingerprint)
* @param {Boolean} deep if true search also in subkeys
* @return {Array<module:key~Key>|null} keys found or null
*/
Keyring.prototype.getKeysForId = function (keyId, deep) {
var result = [];
result = result.concat(this.publicKeys.getForId(keyId, deep) || []);
result = result.concat(this.privateKeys.getForId(keyId, deep) || []);
return result.length ? result : null;
};
/**
* Removes keys having the specified key id from the keyring
* @param {String} keyId provided as string of lowercase hex number
* withouth 0x prefix (can be 16-character key ID or fingerprint)
* @return {Array<module:key~Key>|null} keys found or null
*/
Keyring.prototype.removeKeysForId = function (keyId) {
var result = [];
result = result.concat(this.publicKeys.removeForId(keyId) || []);
result = result.concat(this.privateKeys.removeForId(keyId) || []);
return result.length ? result : null;
};
/**
* Get all public and private keys
* @return {Array<module:key~Key>} all keys
*/
Keyring.prototype.getAllKeys = function () {
return this.publicKeys.keys.concat(this.privateKeys.keys);
};
/**
* Array of keys
* @param {Array<module:key~Key>} keys The keys to store in this array
*/
function KeyArray(keys) {
this.keys = keys;
}
/**
* Searches all keys in the KeyArray matching the address or address part of the user ids
* @param {String} email email address to search for
* @return {Array<module:key~Key>} The public keys associated with provided email address.
*/
KeyArray.prototype.getForAddress = function(email) {
var results = [];
for (var i = 0; i < this.keys.length; i++) {
if (emailCheck(email, this.keys[i])) {
results.push(this.keys[i]);
}
}
return results;
};
/**
* Checks a key to see if it matches the specified email address
* @private
* @param {String} email email address to search for
* @param {module:key~Key} key The key to be checked.
* @return {Boolean} True if the email address is defined in the specified key
@ -83,111 +137,84 @@ function emailCheck(email, key) {
/**
* Checks a key to see if it matches the specified keyid
* @param {String} id hex string keyid to search for
* @param {module:key~Key} key the key to be checked.
* @return {Boolean} true if the email address is defined in the specified key
* @inner
* @private
* @param {String} keyId provided as string of lowercase hex number
* withouth 0x prefix (can be 16-character key ID or fingerprint)
* @param {module:packet/secret_key|public_key|public_subkey|secret_subkey} keypacket The keypacket to be checked
* @return {Boolean} True if keypacket has the specified keyid
*/
function idCheck(id, key) {
var keyids = key.getKeyIds();
for (var i = 0; i < keyids.length; i++) {
if (util.hexstrdump(keyids[i].write()) == id) {
return true;
}
function keyIdCheck(keyId, keypacket) {
if (keyId.length === 16) {
return keyId === keypacket.getKeyId().toHex();
} else {
return keyId === keypacket.getFingerprint();
}
return false;
}
/**
* searches all public keys in the keyring matching the address or address part of the user ids
* @param {Array<module:key~Key>} keys array of keys to search
* @param {module:keyring/keyring.checkCallback} identityFunction callback function which checks for a match
* @param {String} identityInput input to check against
* @param {module:enums.packet} keyType packet types of keys to check
* @return {Array<module:key~Key>} array of keys which match
* Searches the KeyArray for a key having the specified key id
* @param {String} keyId provided as string of lowercase hex number
* withouth 0x prefix (can be 16-character key ID or fingerprint)
* @param {Boolean} deep if true search also in subkeys
* @return {module:key~Key|null} key found or null
*/
function checkForIdentityAndKeyTypeMatch(keys, identityFunction, identityInput, keyType) {
var results = [];
for (var p = 0; p < keys.length; p++) {
var key = keys[p];
switch (keyType) {
case enums.packet.publicKey:
if (key.isPublic() && identityFunction(identityInput, key)) {
results.push(key);
KeyArray.prototype.getForId = function (keyId, deep) {
for (var i = 0; i < this.keys.length; i++) {
if (keyIdCheck(keyId, this.keys[i].primaryKey)) {
return this.keys[i];
}
if (deep && this.keys[i].subKeys) {
for (var j = 0; j < this.keys[i].subKeys.length; j++) {
if (keyIdCheck(keyId, this.keys[i].subKeys[j].subKey)) {
return this.keys[i];
}
break;
case enums.packet.secretKey:
if (key.isPrivate() && identityFunction(identityInput, key)) {
results.push(key);
}
break;
}
}
}
return results;
}
/**
* searches all public keys in the keyring matching the address or address part of the user ids
* @param {String} email email address to search for
* @return {Array<module:key~Key>} The public keys associated with provided email address.
*/
Keyring.prototype.getPublicKeyForAddress = function (email) {
return checkForIdentityAndKeyTypeMatch(this.keys, emailCheck, email, enums.packet.publicKey);
};
/**
* Searches the keyring for a private key containing the specified email address
* @param {String} email email address to search for
* @return {Array<module:key~Key>} private keys found
*/
Keyring.prototype.getPrivateKeyForAddress = function (email) {
return checkForIdentityAndKeyTypeMatch(this.keys, emailCheck, email, enums.packet.secretKey);
};
/**
* Searches the keyring for public keys having the specified key id
* @param {String} keyId provided as string of hex number (lowercase)
* @return {Array<module:key~Key>} public keys found
*/
Keyring.prototype.getKeysForKeyId = function (keyId) {
return checkForIdentityAndKeyTypeMatch(this.keys, idCheck, keyId, enums.packet.publicKey);
return null;
};
/**
* Imports a key from an ascii armored message
* @param {String} armored message to read the keys/key from
* @return {Array<Error>|null} array of error objects or null
*/
Keyring.prototype.importKey = function (armored) {
this.keys = this.keys.concat(keyModule.readArmored(armored).keys);
return true;
KeyArray.prototype.importKey = function (armored) {
var imported = keyModule.readArmored(armored);
var that = this;
imported.keys.forEach(function(key) {
// check if key already in key array
var keyidHex = key.primaryKey.getKeyId().toHex();
var keyFound = that.getForId(keyidHex);
if (keyFound) {
keyFound.update(key);
} else {
that.push(key);
}
});
return imported.err ? imported.err : null;
};
/**
* returns the armored message representation of the key at key ring index
* @param {Integer} index the index of the key within the array
* @return {String} armored message representing the key object
* Add key to KeyArray
* @param {module:key~Key} key The key that will be added to the keyring
* @return {Number} The new length of the KeyArray
*/
Keyring.prototype.exportKey = function (index) {
return this.keys[index].armor();
KeyArray.prototype.push = function (key) {
return this.keys.push(key);
};
/**
* Removes a public key from the public key keyring at the specified index
* @param {Integer} index the index of the public key within the publicKeys array
* @return {module:key~Key} The public key object which has been removed
* Removes a key with the specified keyid from the keyring
* @param {String} keyId provided as string of lowercase hex number
* withouth 0x prefix (can be 16-character key ID or fingerprint)
* @return {module:key~Key|null} The key object which has been removed or null
*/
Keyring.prototype.removeKey = function (index) {
var removed = this.keys.splice(index, 1);
return removed;
};
/**
* returns the armored message representation of the public key portion of the key at key ring index
* @param {Integer} index the index of the key within the array
* @return {String} armored message representing the public key object
*/
Keyring.prototype.exportPublicKey = function (index) {
return this.keys[index].toPublic().armor();
KeyArray.prototype.removeForId = function (keyId) {
for (var i = 0; i < this.keys.length; i++) {
if (keyIdCheck(keyId, this.keys[i].primaryKey)) {
return this.keys.splice(i, 1)[0];
}
}
return null;
};

View File

@ -17,56 +17,83 @@
/**
* The class that deals with storage of the keyring. Currently the only option is to use HTML5 local storage.
* @requires openpgp
* @requires config
* @module keyring/localstore
* @param {String} item itemname in localstore
* @param {String} prefix prefix for itemnames in localstore
*/
module.exports = LocalStore;
var openpgp = require('../');
var config = require('../config'),
keyModule = require('../key.js');
function LocalStore(item) {
function LocalStore(prefix) {
prefix = prefix || 'openpgp-';
this.publicKeysItem = prefix + this.publicKeysItem;
this.privateKeysItem = prefix + this.privateKeysItem;
if (typeof window != 'undefined' && window.localStorage) {
this.storage = window.localStorage;
} else {
this.storage = new (require('node-localstorage').LocalStorage)(openpgp.config.node_store);
}
if(typeof item == 'string') {
this.item = item;
this.storage = new (require('node-localstorage').LocalStorage)(config.node_store);
}
}
/*
* Declare the localstore itemname
* Declare the localstore itemnames
*/
LocalStore.prototype.item = 'armoredKeys';
LocalStore.prototype.publicKeysItem = 'public-keys';
LocalStore.prototype.privateKeysItem = 'private-keys';
/**
* Load the keyring from HTML5 local storage and initializes this instance.
* Load the public keys from HTML5 local storage.
* @return {Array<module:key~Key>} array of keys retrieved from localstore
*/
LocalStore.prototype.load = function () {
var armoredKeys = JSON.parse(this.storage.getItem(this.item));
LocalStore.prototype.loadPublic = function () {
return loadKeys(this.storage, this.publicKeysItem);
};
/**
* Load the private keys from HTML5 local storage.
* @return {Array<module:key~Key>} array of keys retrieved from localstore
*/
LocalStore.prototype.loadPrivate = function () {
return loadKeys(this.storage, this.privateKeysItem);
};
function loadKeys(storage, itemname) {
var armoredKeys = JSON.parse(storage.getItem(itemname));
var keys = [];
if (armoredKeys !== null && armoredKeys.length !== 0) {
var key;
for (var i = 0; i < armoredKeys.length; i++) {
key = openpgp.key.readArmored(armoredKeys[i]).keys[0];
key = keyModule.readArmored(armoredKeys[i]).keys[0];
keys.push(key);
}
}
return keys;
}
/**
* Saves the current state of the public keys to HTML5 local storage.
* The key array gets stringified using JSON
* @param {Array<module:key~Key>} keys array of keys to save in localstore
*/
LocalStore.prototype.storePublic = function (keys) {
storeKeys(this.storage, this.publicKeysItem, keys);
};
/**
* Saves the current state of the keyring to HTML5 local storage.
* The privateKeys array and publicKeys array gets Stringified using JSON
* Saves the current state of the private keys to HTML5 local storage.
* The key array gets stringified using JSON
* @param {Array<module:key~Key>} keys array of keys to save in localstore
*/
LocalStore.prototype.store = function (keys) {
LocalStore.prototype.storePrivate = function (keys) {
storeKeys(this.storage, this.privateKeysItem, keys);
};
function storeKeys(storage, itemname, keys) {
var armoredKeys = [];
for (var i = 0; i < keys.length; i++) {
armoredKeys.push(keys[i].armor());
}
this.storage.setItem(this.item, JSON.stringify(armoredKeys));
};
storage.setItem(itemname, JSON.stringify(armoredKeys));
}

View File

@ -55,6 +55,16 @@ function PublicKey() {
this.algorithm = 'rsa_sign';
// time in days (V3 only)
this.expirationTimeV3 = 0;
/**
* Fingerprint in lowercase hex
* @type {String}
*/
this.fingerprint = null;
/**
* Keyid
* @type {module:type/keyid}
*/
this.keyid = null;
}
/**
@ -158,31 +168,39 @@ PublicKey.prototype.writeOld = function () {
* @return {String} A 8 byte key id
*/
PublicKey.prototype.getKeyId = function () {
var keyid = new type_keyid();
if (this.version == 4) {
keyid.read(this.getFingerprint().substr(12, 8));
} else if (this.version == 3) {
keyid.read(this.mpi[0].write().substr(-8));
if (this.keyid) {
return this.keyid;
}
return keyid;
this.keyid = new type_keyid();
if (this.version == 4) {
this.keyid.read(util.hex2bin(this.getFingerprint()).substr(12, 8));
} else if (this.version == 3) {
this.keyid.read(this.mpi[0].write().substr(-8));
}
return this.keyid;
};
/**
* Calculates the fingerprint of the key
* @return {String} A string containing the fingerprint
* @return {String} A string containing the fingerprint in lowercase hex
*/
PublicKey.prototype.getFingerprint = function () {
if (this.fingerprint) {
return this.fingerprint;
}
var toHash = '';
if (this.version == 4) {
toHash = this.writeOld();
return crypto.hash.sha1(toHash);
this.fingerprint = crypto.hash.sha1(toHash);
} else if (this.version == 3) {
var mpicount = crypto.getPublicMpiCount(this.algorithm);
for (var i = 0; i < mpicount; i++) {
toHash += this.mpi[i].toBytes();
}
return crypto.hash.md5(toHash);
this.fingerprint = crypto.hash.md5(toHash);
}
this.fingerprint = util.hexstrdump(this.fingerprint);
return this.fingerprint;
};
/**
@ -200,4 +218,7 @@ PublicKey.prototype.postCloneTypeFix = function() {
for (var i = 0; i < this.mpi.length; i++) {
this.mpi[i] = type_mpi.fromClone(this.mpi[i]);
}
if (this.keyid) {
this.keyid = type_keyid.fromClone(this.keyid);
}
};

View File

@ -51,6 +51,7 @@ function Signature() {
this.publicKeyAlgorithm = null;
this.signatureData = null;
this.unhashedSubpackets = null;
this.signedHashValue = null;
this.created = new Date();
@ -166,9 +167,11 @@ Signature.prototype.read = function (bytes) {
// hash algorithm, the hashed subpacket length, and the hashed
// subpacket body.
this.signatureData = bytes.substr(0, i);
var sigDataLength = i;
// unhashed subpackets
i += subpackets.call(this, bytes.substr(i), false);
this.unhashedSubpackets = bytes.substr(sigDataLength, i - sigDataLength);
break;
default:
@ -184,7 +187,8 @@ Signature.prototype.read = function (bytes) {
Signature.prototype.write = function () {
return this.signatureData +
util.writeNumber(0, 2) + // Number of unsigned subpackets.
// unhashed subpackets or two octets for zero
(this.unhashedSubpackets ? this.unhashedSubpackets : util.writeNumber(0, 2)) +
this.signedHashValue +
this.signature;
};
@ -559,7 +563,7 @@ Signature.prototype.toSign = function (type, data) {
case t.key:
if (data.key === undefined)
throw new Error('Key packet is required for this sigtature.');
throw new Error('Key packet is required for this signature.');
return data.key.writeOld();

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];
});
};

View File

@ -221,6 +221,48 @@ describe('Key', function() {
'=e8xo',
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
var priv_key_rsa =
['-----BEGIN PGP PRIVATE KEY BLOCK-----',
'Version: GnuPG v2.0.19 (GNU/Linux)',
'Type: RSA/RSA 1024',
'Pwd: hello world',
'',
'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',
'=lw5e',
'-----END PGP PRIVATE KEY BLOCK-----'].join('\n');
it('Parsing armored text with two keys', function(done) {
var pubKeys = openpgp.key.readArmored(twoKeys);
expect(pubKeys).to.exist;
@ -250,9 +292,9 @@ describe('Key', function() {
expect(pubKeyV3).to.exist;
expect(pubKeyV4.getKeyPacket().getKeyId().toHex()).to.equal('4a63613a4d6e4094');
expect(openpgp.util.hexstrdump(pubKeyV4.getKeyPacket().getFingerprint())).to.equal('f470e50dcb1ad5f1e64e08644a63613a4d6e4094');
expect(pubKeyV4.getKeyPacket().getFingerprint()).to.equal('f470e50dcb1ad5f1e64e08644a63613a4d6e4094');
expect(pubKeyV3.getKeyPacket().getKeyId().toHex()).to.equal('e5b7a014a237ba9d');
expect(openpgp.util.hexstrdump(pubKeyV3.getKeyPacket().getFingerprint())).to.equal('a44fcee620436a443bc4913640ab3e49');
expect(pubKeyV3.getKeyPacket().getFingerprint()).to.equal('a44fcee620436a443bc4913640ab3e49');
done();
});
@ -322,5 +364,95 @@ describe('Key', function() {
expect(pubKey.subKeys[0].getExpirationTime().toISOString()).to.be.equal('2018-11-26T10:58:29.000Z');
});
it('update() - throw error if fingerprints not equal', function() {
var keys = openpgp.key.readArmored(twoKeys).keys;
expect(keys[0].update.bind(keys[0], keys[1])).to.throw('Key update method: fingerprints of keys not equal');
});
it('update() - merge revocation signature', function() {
var source = openpgp.key.readArmored(pub_revoked).keys[0];
var dest = openpgp.key.readArmored(pub_revoked).keys[0];
expect(source.revocationSignature).to.exist;
dest.revocationSignature = null;
dest.update(source);
expect(dest.revocationSignature).to.exist.and.be.an.instanceof(openpgp.packet.Signature);
});
it('update() - merge user', function() {
var source = openpgp.key.readArmored(pub_sig_test).keys[0];
var dest = openpgp.key.readArmored(pub_sig_test).keys[0];
expect(source.users[1]).to.exist;
dest.users.pop();
dest.update(source);
expect(dest.users[1]).to.exist;
expect(dest.users[1].userId).to.equal(source.users[1].userId);
});
it('update() - merge user - other and revocation certification', function() {
var source = openpgp.key.readArmored(pub_sig_test).keys[0];
var dest = openpgp.key.readArmored(pub_sig_test).keys[0];
expect(source.users[1].otherCertifications).to.exist;
expect(source.users[1].revocationCertifications).to.exist;
dest.users[1].otherCertifications = null;
dest.users[1].revocationCertifications.pop();
dest.update(source);
expect(dest.users[1].otherCertifications).to.exist.and.to.have.length(1);
expect(dest.users[1].otherCertifications[0].signature).to.equal(source.users[1].otherCertifications[0].signature);
expect(dest.users[1].revocationCertifications).to.exist.and.to.have.length(2);
expect(dest.users[1].revocationCertifications[1].signature).to.equal(source.users[1].revocationCertifications[1].signature);
});
it('update() - merge subkey', function() {
var source = openpgp.key.readArmored(pub_sig_test).keys[0];
var dest = openpgp.key.readArmored(pub_sig_test).keys[0];
expect(source.subKeys[1]).to.exist;
dest.subKeys.pop();
dest.update(source);
expect(dest.subKeys[1]).to.exist;
expect(dest.subKeys[1].subKey.getKeyId().toHex()).to.equal(source.subKeys[1].subKey.getKeyId().toHex());
});
it('update() - merge subkey - revocation signature', function() {
var source = openpgp.key.readArmored(pub_sig_test).keys[0];
var dest = openpgp.key.readArmored(pub_sig_test).keys[0];
expect(source.subKeys[0].revocationSignature).to.exist;
dest.subKeys[0].revocationSignature = null;
dest.update(source);
expect(dest.subKeys[0].revocationSignature).to.exist;
expect(dest.subKeys[0].revocationSignature.signature).to.equal(dest.subKeys[0].revocationSignature.signature);
});
it('update() - merge private key into public key', function() {
var source = openpgp.key.readArmored(priv_key_rsa).keys[0];
var dest = openpgp.key.readArmored(twoKeys).keys[0];
expect(dest.isPublic()).to.be.true;
dest.update(source);
expect(dest.isPrivate()).to.be.true;
expect(source.verifyPrimaryKey()).to.equal(dest.verifyPrimaryKey());
expect(source.users[0].verify(source.primaryKey)).to.equal(dest.users[0].verify(dest.primaryKey));
expect(source.subKeys[0].verify(source.primaryKey)).to.equal(dest.subKeys[0].verify(dest.primaryKey));
});
it('update() - merge private key into public key - no subkeys', function() {
var source = openpgp.key.readArmored(priv_key_rsa).keys[0];
var dest = openpgp.key.readArmored(twoKeys).keys[0];
source.subKeys = null;
dest.subKeys = null;
expect(dest.isPublic()).to.be.true;
dest.update(source);
expect(dest.isPrivate()).to.be.true;
expect(source.verifyPrimaryKey()).to.equal(dest.verifyPrimaryKey());
expect(source.users[0].verify(source.primaryKey)).to.equal(dest.users[0].verify(dest.primaryKey));
});
it('update() - merge private key into public key - mismatch throws error', function() {
var source = openpgp.key.readArmored(priv_key_rsa).keys[0];
var dest = openpgp.key.readArmored(twoKeys).keys[0];
source.subKeys = null;
expect(dest.subKeys).to.exist;
expect(dest.isPublic()).to.be.true;
expect(dest.update.bind(dest, source)).to.throw('Cannot update public key with private key if subkey mismatch');
});
});

View File

@ -10,7 +10,8 @@ describe("Keyring", function() {
var user = 'whiteout.test@t-online.de',
passphrase = 'asdf',
keySize = 512,
keyId = 'F6F60E9B42CDFF4C',
keyId = 'f6f60e9b42cdff4c',
keyFingerP = '5856cef789c3a307e8a1b976f6f60e9b42cdff4c',
pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
'Version: OpenPGP.js v.1.20131011\n' +
'Comment: http://openpgpjs.org\n' +
@ -37,65 +38,243 @@ describe("Keyring", function() {
'Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXqIiN602mWrkd8jcEzLsW5\n' +
'IUNzVPLhrFIuKyBDTpLnC07Loce1\n' +
'=ULta\n' +
'-----END PGP PRIVATE KEY BLOCK-----';
'-----END PGP PRIVATE KEY BLOCK-----',
keyId2 = 'ba993fc2aee18a3a',
keyFingerP2 = '560b7a7f3f9ab516b233b299ba993fc2aee18a3a',
subkeyId2 = 'f47c5210a8cc2740',
subkeyFingerP2 = '2a20c371141e000833848d85f47c5210a8cc2740',
pubkey2 =
['-----BEGIN PGP PUBLIC KEY BLOCK-----',
'Version: GnuPG v2.0.22 (GNU/Linux)',
'',
'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',
'og2umGfGng==',
'=v3+L',
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
it('Import key pair', function(done) {
it('Import key pair', function() {
// clear any keys already in the keychain
keyring.clear();
keyring.importKey(privkey);
keyring.importKey(pubkey);
done();
keyring.store();
keyring.publicKeys.importKey(pubkey);
keyring.publicKeys.importKey(pubkey2);
keyring.privateKeys.importKey(privkey);
});
it('getPublicKeyForAddress() - unknown address', function(done) {
var key = keyring.getPublicKeyForAddress('nobody@example.com');
expect(key).to.be.empty;
done();
it('getKeysForId() - unknown id', function() {
var keys = keyring.getKeysForId('01234567890123456');
expect(keys).to.be.null;
});
it('getPublicKeyForAddress() - valid address', function(done) {
var key = keyring.getPublicKeyForAddress(user);
expect(key).to.exist.and.have.length(1);
done();
it('getKeysForId() - valid id', function() {
var keys = keyring.getKeysForId(keyId);
// we get public and private key
expect(keys).to.exist.and.have.length(2);
expect(keys[0].primaryKey.getKeyId().toHex()).equals(keyId);
});
it('getPrivateKeyForAddress() - unknown address', function(done) {
var key = keyring.getPrivateKeyForAddress('nobody@example.com');
expect(key).to.be.empty;
done();
it('publicKeys.getForId() - unknown id', function() {
var key = keyring.publicKeys.getForId('01234567890123456');
expect(key).to.be.null;
});
it('getPrivateKeyForAddress() - valid address', function(done) {
var key = keyring.getPrivateKeyForAddress(user);
expect(key).to.exist.and.have.length(1);
done();
it('publicKeys.getForId() - valid id', function() {
var key = keyring.publicKeys.getForId(keyId);
expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key);
expect(key.primaryKey.getKeyId().toHex()).equals(keyId);
});
it('getKeysForKeyId() - unknown id', function(done) {
var keys = keyring.getKeysForKeyId('000102030405060708');
it('privateKeys.getForId() - unknown id', function() {
var key = keyring.privateKeys.getForId('01234567890123456');
expect(key).to.be.null;
});
it('privateKeys.getForId() - valid id', function() {
var key = keyring.privateKeys.getForId(keyId);
expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key);
expect(key.primaryKey.getKeyId().toHex()).equals(keyId);
});
it('publicKeys.getForId() - subkey id', function() {
var key = keyring.publicKeys.getForId(subkeyId2);
expect(key).to.be.null;
});
it('publicKeys.getForId() - deep, including subkeys - subkey id', function() {
var key = keyring.publicKeys.getForId(subkeyId2, true);
expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key);
expect(key.primaryKey.getKeyId().toHex()).equals(keyId2);
});
it('getKeysForId() - unknown fingerprint', function() {
var keys = keyring.getKeysForId('71130e8383bef9526e062600d5e9f93acbbc7275');
expect(keys).to.be.null;
});
it('getKeysForId() - valid fingerprint', function() {
var keys = keyring.getKeysForId(keyFingerP2);
expect(keys).to.exist.and.have.length(1);
expect(keys[0].primaryKey.getKeyId().toHex()).equals(keyId2);
});
it('publicKeys.getForId() - unknown fingerprint', function() {
var key = keyring.publicKeys.getForId('71130e8383bef9526e062600d5e9f93acbbc7275');
expect(key).to.be.null;
});
it('publicKeys.getForId() - valid fingerprint', function() {
var key = keyring.publicKeys.getForId(keyFingerP2);
expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key);
expect(key.primaryKey.getKeyId().toHex()).equals(keyId2);
});
it('publicKeys.getForId() - subkey fingerprint', function() {
var key = keyring.publicKeys.getForId(subkeyFingerP2);
expect(key).to.be.null;
});
it('publicKeys.getForId() - deep, including subkeys - subkey fingerprint', function() {
var key = keyring.publicKeys.getForId(subkeyFingerP2, true);
expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key);
expect(key.primaryKey.getKeyId().toHex()).equals(keyId2);
});
it('publicKeys.getForAddress() - unknown address', function() {
var keys = keyring.publicKeys.getForAddress('nobody@example.com');
expect(keys).to.be.empty;
done();
});
it('getKeysForKeyId() - valid id', function(done) {
var keys = keyring.getKeysForKeyId(keyId.toLowerCase());
it('publicKeys.getForAddress() - valid address', function() {
var keys = keyring.publicKeys.getForAddress(user);
expect(keys).to.exist.and.have.length(1);
done();
});
it('store keys in localstorage', function(done){
it('privateKeys.getForAddress() - unknown address', function() {
var key = keyring.privateKeys.getForAddress('nobody@example.com');
expect(key).to.be.empty;
});
it('privateKeys.getForAddress() - valid address', function() {
var key = keyring.privateKeys.getForAddress(user);
expect(key).to.exist.and.have.length(1);
});
it('store keys in localstorage', function(){
keyring.store();
done();
});
it('after loading from localstorage: getKeysForKeyId() - valid id', function(done) {
it('after loading from localstorage: getKeysForKeyId() - valid id', function() {
var keyring = new openpgp.Keyring(),
keys = keyring.getKeysForKeyId(keyId.toLowerCase());
expect(keys).to.exist.and.have.length(1);
done();
keys = keyring.getKeysForId(keyId);
// we expect public and private key
expect(keys).to.exist.and.have.length(2);
});
it('publicKeys.removeForId() - unknown id', function() {
var key = keyring.publicKeys.removeForId('01234567890123456');
expect(key).to.be.null;
});
it('publicKeys.removeForId() - valid id', function() {
var key = keyring.publicKeys.removeForId(keyId);
expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key);
expect(key.primaryKey.getKeyId().toHex()).equals(keyId);
expect(keyring.publicKeys.keys).to.exist.and.have.length(1);
});
it('publicKeys.removeForId() - unknown fingerprint', function() {
var key = keyring.publicKeys.removeForId('71130e8383bef9526e062600d5e9f93acbbc7275');
expect(key).to.be.null;
expect(keyring.publicKeys.keys).to.exist.and.have.length(1);
});
it('publicKeys.removeForId() - valid fingerprint', function() {
var key = keyring.publicKeys.removeForId(keyFingerP2);
expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key);
expect(key.primaryKey.getKeyId().toHex()).equals(keyId2);
expect(keyring.publicKeys.keys).to.be.empty;
});
it('customize localstorage itemname', function() {
var localstore1 = new openpgp.Keyring.localstore('my-custom-name');
var localstore2 = new openpgp.Keyring.localstore('my-custom-name');
var localstore1 = new openpgp.Keyring.localstore('my-custom-prefix-');
var localstore2 = new openpgp.Keyring.localstore('my-custom-prefix-');
var localstore3 = new openpgp.Keyring.localstore();
localstore3.store([]);
localstore3.storePublic([]);
var key = openpgp.key.readArmored(pubkey).keys[0];
localstore1.store([key]);
expect(localstore2.load()[0].primaryKey.getKeyId().equals(key.primaryKey.getKeyId())).to.be.true;
expect(localstore3.load()).to.have.length(0);
localstore1.storePublic([key]);
expect(localstore2.loadPublic()[0].primaryKey.getKeyId().equals(key.primaryKey.getKeyId())).to.be.true;
expect(localstore3.loadPublic()).to.have.length(0);
});
it('removeKeysForId() - unknown id', function() {
keyring.publicKeys.importKey(pubkey);
keyring.publicKeys.importKey(pubkey2);
keyring.privateKeys.importKey(privkey);
expect(keyring.publicKeys.keys).to.have.length(2);
expect(keyring.privateKeys.keys).to.have.length(1);
var keys = keyring.removeKeysForId('01234567890123456');
expect(keys).to.be.null;
expect(keyring.publicKeys.keys).to.have.length(2);
expect(keyring.privateKeys.keys).to.have.length(1);
});
it('removeKeysForId() - valid id', function() {
var keys = keyring.removeKeysForId(keyId);
expect(keys).to.have.length(2);
expect(keyring.publicKeys.keys).to.have.length(1);
expect(keyring.privateKeys.keys).to.have.length(0);
});
it('removeKeysForId() - unknown fingerprint', function() {
keyring.publicKeys.importKey(pubkey);
keyring.publicKeys.importKey(pubkey2);
keyring.privateKeys.importKey(privkey);
expect(keyring.publicKeys.keys).to.have.length(2);
expect(keyring.privateKeys.keys).to.have.length(1);
var keys = keyring.removeKeysForId('71130e8383bef9526e062600d5e9f93acbbc7275');
expect(keys).to.be.null;
expect(keyring.publicKeys.keys).to.have.length(2);
expect(keyring.privateKeys.keys).to.have.length(1);
});
it('removeKeysForId() - valid fingerprint', function() {
var keys = keyring.removeKeysForId(keyFingerP);
expect(keys).to.have.length(2);
expect(keyring.publicKeys.keys).to.have.length(1);
expect(keyring.privateKeys.keys).to.have.length(0);
});
});

View File

@ -571,4 +571,12 @@ describe("Signature", function() {
expect(verified).to.be.true;
done();
});
it('Write unhashed subpackets', function() {
var pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0];
expect(pubKey.users[0].selfCertifications).to.exist;
pubKey = openpgp.key.readArmored(pubKey.armor()).keys[0]
expect(pubKey.users[0].selfCertifications).to.exist;
});
});