diff --git a/src/keyring/keyring.js b/src/keyring/keyring.js index 4c414300..fd923c9b 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,21 +37,105 @@ 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 hex number (lowercase) + * @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 hex number (lowercase) + * @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; +}; + +/** + * Searches the keyring for keys having the specified long key id (fingerprint) + * @param {String} longKeyId fingerprint in lowercase hex + * @param {Boolean} deep if true search also in subkeys + * @return {Array|null} keys found or null + */ +Keyring.prototype.getKeysForLongId = function (longKeyId, deep) { + var result = []; + result = result.concat(this.publicKeys.getForLongId(longKeyId, deep) || []); + result = result.concat(this.privateKeys.getForLongId(longKeyId, deep) || []); + return result.length ? result : null; +}; + +/** + * Removes keys having the specified long key id (fingerprint) from the keyring + * @param {String} longKeyId fingerprint in lowercase hex + * @return {Array|null} keys found or null + */ +Keyring.prototype.removeKeysForLongId = function (longKeyId) { + var result = []; + result = result.concat(this.publicKeys.removeForLongId(longKeyId) || []); + result = result.concat(this.privateKeys.removeForLongId(longKeyId) || []); + 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; }; /** @@ -82,82 +158,19 @@ 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 - */ -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; - } - } - 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 - */ -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); - } - 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 a key having the specified key id + * Searches the KeyArray for a key having the specified key id * @param {String} keyId provided as string of hex number (lowercase) * @param {Boolean} deep if true search also in subkeys * @return {module:key~Key|null} key found or null */ -Keyring.prototype.getKeyForId = function (keyId, deep) { +KeyArray.prototype.getForId = function (keyId, deep) { for (var i = 0; i < this.keys.length; i++) { if (this.keys[i].primaryKey.getKeyId().toHex() === keyId) { return this.keys[i]; } - if (deep) { + if (deep && this.keys[i].subKeys) { for (var j = 0; j < this.keys[i].subKeys.length; j++) { - if (this.keys[i].subKeys[j].getKeyId().toHex() === keyId) { + if (this.keys[i].subKeys[j].subKey.getKeyId().toHex() === keyId) { return this.keys[i]; } } @@ -172,14 +185,14 @@ Keyring.prototype.getKeyForId = function (keyId, deep) { * @param {Boolean} deep if true search also in subkeys * @return {module:key~Key|null} key found or null */ -Keyring.prototype.getKeyForLongId = function(longKeyId, deep) { +KeyArray.prototype.getForLongId = function(longKeyId, deep) { for (var i = 0; i < this.keys.length; i++) { if (this.keys[i].primaryKey.getFingerprint() === longKeyId) { return this.keys[i]; } - if (deep) { + if (deep && this.keys[i].subKeys) { for (var j = 0; j < this.keys[i].subKeys.length; j++) { - if (this.keys[i].subKeys[j].getFingerprint() === longKeyId) { + if (this.keys[i].subKeys[j].subKey.getFingerprint() === longKeyId) { return this.keys[i]; } } @@ -191,38 +204,57 @@ Keyring.prototype.getKeyForLongId = function(longKeyId, deep) { /** * 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 hex number (lowercase) + * @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; +KeyArray.prototype.removeForId = function (keyId) { + for (var i = 0; i < this.keys.length; i++) { + if (this.keys[i].primaryKey.getKeyId().toHex() === keyId) { + return this.keys.splice(i, 1)[0]; + } + } + return null; }; /** - * 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 + * Removes a key with the specified long key id (fingerprint) from the keyring + * @param {String} longKeyId fingerprint in lowercase hex + * @return {module:key~Key|null} The key object which has been removed or null */ -Keyring.prototype.exportPublicKey = function (index) { - return this.keys[index].toPublic().armor(); +KeyArray.prototype.removeForLongId = function (longKeyId) { + for (var i = 0; i < this.keys.length; i++) { + if (this.keys[i].primaryKey.getFingerprint() === longKeyId) { + 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)); +}