diff --git a/src/key.js b/src/key.js index 00d2ba03..4bc4b58a 100644 --- a/src/key.js +++ b/src/key.js @@ -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, selfCertificate: Array}} The primary user and the self signature + * @return {{user: Array, selfCertificate: Array}|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 diff --git a/src/keyring/keyring.js b/src/keyring/keyring.js index 2c8388a2..c991a763 100644 --- a/src/keyring/keyring.js +++ b/src/keyring/keyring.js @@ -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|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|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} all keys + */ +Keyring.prototype.getAllKeys = function () { + return this.publicKeys.keys.concat(this.privateKeys.keys); +}; + +/** + * Array of keys + * @param {Array} 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} 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} 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} 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} 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} 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} 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|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; }; diff --git a/src/keyring/localstore.js b/src/keyring/localstore.js index 4d169646..f9fa91c7 100644 --- a/src/keyring/localstore.js +++ b/src/keyring/localstore.js @@ -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} 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} 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} 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} 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)); +} diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 35965c47..5010351c 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -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); + } }; diff --git a/src/packet/signature.js b/src/packet/signature.js index 0f9f7f8b..607a0528 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -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(); diff --git a/src/packet/user_attribute.js b/src/packet/user_attribute.js index d14727d3..161738be 100644 --- a/src/packet/user_attribute.js +++ b/src/packet/user_attribute.js @@ -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]; + }); +}; diff --git a/test/general/key.js b/test/general/key.js index 151b847a..0f377d69 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -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', + '', + 'lQH+BFJhL04BBADclrUEDDsm0PSZbQ6pml9FpzTyXiyCyDN+rMOsy9J300Oc10kt', + '/nyBej9vZSRcaW5VpNNj0iA+c1/w2FPf84zNsTzvDmuMaNHFUzky4/vkYuZra//3', + '+Ri7CF8RawSYQ/4IRbC9zqdBlzniyfQOW7Dp/LYe8eibnDSrmkQem0G0jwARAQAB', + '/gMDAu7L//czBpE40p1ZqO8K3k7UejemjsQqc7kOqnlDYd1Z6/3NEA/UM30Siipr', + 'KjdIFY5+hp0hcs6EiiNq0PDfm/W2j+7HfrZ5kpeQVxDek4irezYZrl7JS2xezaLv', + 'k0Fv/6fxasnFtjOM6Qbstu67s5Gpl9y06ZxbP3VpT62+Xeibn/swWrfiJjuGEEhM', + 'bgnsMpHtzAz/L8y6KSzViG/05hBaqrvk3/GeEA6nE+o0+0a6r0LYLTemmq6FbaA1', + 'PHo+x7k7oFcBFUUeSzgx78GckuPwqr2mNfeF+IuSRnrlpZl3kcbHASPAOfEkyMXS', + 'sWGE7grCAjbyQyM3OEXTSyqnehvGS/1RdB6kDDxGwgE/QFbwNyEh6K4eaaAThW2j', + 'IEEI0WEnRkPi9fXyxhFsCLSI1XhqTaq7iDNqJTxE+AX2b9ZuZXAxI3Tc/7++vEyL', + '3p18N/MB2kt1Wb1azmXWL2EKlT1BZ5yDaJuBQ8BhphM3tCRUZXN0IE1jVGVzdGlu', + 'Z3RvbiA8dGVzdEBleGFtcGxlLmNvbT6IuQQTAQIAIwUCUmEvTgIbLwcLCQgHAwIB', + 'BhUIAgkKCwQWAgMBAh4BAheAAAoJEEpjYTpNbkCUMAwD+gIK08qpEZSVas9qW+Ok', + '32wzNkwxe6PQgZwcyBqMQYZUcKagC8+89pMQQ5sKUGvpIgat42Tf1KLGPcvG4cDA', + 'JZ6w2PYz9YHQqPh9LA+PAnV8m25TcGmKcKgvFUqQ3U53X/Y9sBP8HooRqfwwHcv9', + 'pMgQmojmNbI4VHydRqIBePawnQH+BFJhL04BBADpH8+0EVolpPiOrXTKoBKTiyrB', + 'UyxzodyJ8zmVJ3HMTEU/vidpQwzISwoc/ndDFMXQauq6xqBCD9m2BPQI3UdQzXnb', + 'LsAI52nWCIqOkzM5NAKWoKhyXK9Y4UH4v9LAYQgl/stIISvCgG4mJ8lzzEBWvRdf', + 'Qm2Ghb64/3V5NDdemwARAQAB/gMDAu7L//czBpE40iPcpLzL7GwBbWFhSWgSLy53', + 'Md99Kxw3cApWCok2E8R9/4VS0490xKZIa5y2I/K8thVhqk96Z8Kbt7MRMC1WLHgC', + 'qJvkeQCI6PrFM0PUIPLHAQtDJYKtaLXxYuexcAdKzZj3FHdtLNWCooK6n3vJlL1c', + 'WjZcHJ1PH7USlj1jup4XfxsbziuysRUSyXkjn92GZLm+64vCIiwhqAYoizF2NHHG', + 'hRTN4gQzxrxgkeVchl+ag7DkQUDANIIVI+A63JeLJgWJiH1fbYlwESByHW+zBFNt', + 'qStjfIOhjrfNIc3RvsggbDdWQLcbxmLZj4sB0ydPSgRKoaUdRHJY0S4vp9ouKOtl', + '2au/P1BP3bhD0fDXl91oeheYth+MSmsJFDg/vZJzCJhFaQ9dp+2EnjN5auNCNbaI', + 'beFJRHFf9cha8p3hh+AK54NRCT++B2MXYf+TPwqX88jYMBv8kk8vYUgo8128r1zQ', + 'EzjviQE9BBgBAgAJBQJSYS9OAhsuAKgJEEpjYTpNbkCUnSAEGQECAAYFAlJhL04A', + 'CgkQ4IT3RGwgLJe6ogQA2aaJEIBIXtgrs+8WSJ4k3DN4rRXcXaUZf667pjdD9nF2', + '3BzjFH6Z78JIGaxRHJdM7b05aE8YuzM8f3NIlT0F0OLq/TI2muYU9f/U2DQBuf+w', + 'KTB62+PELVgi9MsXC1Qv/u/o1LZtmmxTFFOD35xKsxZZI2OJj2pQpqObW27M8Nlc', + 'BQQAw2YA3fFc38qPK+PY4rZyTRdbvjyyX+1zeqIo8wn7QCQwXs+OGaH2fGoT35AI', + 'SXuqKcWqoEuO7OBSEFThCXBfUYMC01OrqKEswPm/V3zZkLu01q12UMwZach28QwK', + '/YZly4ioND2tdazj17u2rU2dwtiHPe1iMqGgVMoQirfLc+k=', + '=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'); + }); + }); diff --git a/test/general/keyring.js b/test/general/keyring.js index 6bf24fae..4e77dcaf 100644 --- a/test/general/keyring.js +++ b/test/general/keyring.js @@ -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); + }); + }); diff --git a/test/general/signature.js b/test/general/signature.js index df69d0e3..8c0b29a3 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -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; + }); + });