Keyring: separate storage for public and private keys. ImportKey method supports update.

This commit is contained in:
Thomas Oberndörfer 2014-02-23 20:48:38 +01:00
parent d6e4e3c028
commit efc384e71c
2 changed files with 181 additions and 122 deletions

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,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<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 hex number (lowercase)
* @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;
};
/**
* 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<module:key~Key>|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<module:key~Key>|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<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;
};
/**
@ -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<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
*/
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<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 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<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 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;
};

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