2014-10-02 16:05:44 -04:00
|
|
|
'use strict';
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2014-11-18 12:44:00 -05:00
|
|
|
var ngModule = angular.module('woServices');
|
|
|
|
ngModule.service('keychain', Keychain);
|
|
|
|
module.exports = Keychain;
|
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
var DB_PUBLICKEY = 'publickey',
|
2015-02-27 14:04:44 -05:00
|
|
|
DB_PRIVATEKEY = 'privatekey';
|
2014-10-02 16:05:44 -04:00
|
|
|
|
2014-11-18 12:44:00 -05:00
|
|
|
/**
|
|
|
|
* A high-level Data-Access Api for handling Keypair synchronization
|
|
|
|
* between the cloud service and the device's local storage
|
|
|
|
*/
|
2014-11-25 11:46:33 -05:00
|
|
|
function Keychain(accountLawnchair, publicKey, privateKey, crypto, pgp, dialog, appConfig) {
|
|
|
|
this._lawnchairDAO = accountLawnchair;
|
2014-11-19 14:54:59 -05:00
|
|
|
this._publicKeyDao = publicKey;
|
|
|
|
this._privateKeyDao = privateKey;
|
2014-10-02 16:05:44 -04:00
|
|
|
this._crypto = crypto;
|
|
|
|
this._pgp = pgp;
|
2014-11-19 14:54:59 -05:00
|
|
|
this._dialog = dialog;
|
|
|
|
this._appConfig = appConfig;
|
2014-11-18 12:44:00 -05:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
|
|
|
|
//
|
|
|
|
// Public key functions
|
|
|
|
//
|
|
|
|
|
2014-11-19 14:54:59 -05:00
|
|
|
/**
|
|
|
|
* Display confirmation dialog to request a public key update
|
|
|
|
* @param {Object} params.newKey The user's updated public key object
|
|
|
|
* @param {String} params.userId The user's email address
|
|
|
|
*/
|
|
|
|
Keychain.prototype.requestPermissionForKeyUpdate = function(params, callback) {
|
|
|
|
var str = this._appConfig.string;
|
|
|
|
var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey;
|
|
|
|
message = message.replace('{0}', params.userId);
|
|
|
|
|
|
|
|
this._dialog.confirm({
|
|
|
|
title: str.updatePublicKeyTitle,
|
|
|
|
message: message,
|
|
|
|
positiveBtnStr: str.updatePublicKeyPosBtn,
|
|
|
|
negativeBtnStr: str.updatePublicKeyNegBtn,
|
|
|
|
showNegativeBtn: true,
|
|
|
|
callback: callback
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
|
|
|
* Verifies the public key of a user o nthe public key store
|
|
|
|
* @param {String} uuid The uuid to verify the key
|
|
|
|
*/
|
2014-12-11 14:43:51 -05:00
|
|
|
Keychain.prototype.verifyPublicKey = function(uuid) {
|
|
|
|
return this._publicKeyDao.verify(uuid);
|
2014-10-02 16:05:44 -04:00
|
|
|
};
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
|
|
|
* Checks for public key updates of a given user id
|
2014-11-06 08:28:14 -05:00
|
|
|
* @param {String} options.userId The user id (email address) for which to check the key
|
|
|
|
* @param {String} options.overridePermission (optional) Indicates if the update should happen automatically (true) or with the user being queried (false). Defaults to false
|
2014-10-02 16:05:44 -04:00
|
|
|
*/
|
2014-12-12 12:02:13 -05:00
|
|
|
Keychain.prototype.refreshKeyForUserId = function(options) {
|
2014-11-06 08:28:14 -05:00
|
|
|
var self = this,
|
|
|
|
userId = options.userId,
|
|
|
|
overridePermission = options.overridePermission;
|
2014-10-02 16:05:44 -04:00
|
|
|
|
|
|
|
// get the public key corresponding to the userId
|
2014-12-12 12:02:13 -05:00
|
|
|
return self.getReceiverPublicKey(userId).then(function(localKey) {
|
2014-10-02 16:05:44 -04:00
|
|
|
if (!localKey || !localKey._id) {
|
|
|
|
// there is no key available, no need to refresh
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// no need to refresh manually imported public keys
|
|
|
|
if (localKey.imported) {
|
2014-12-12 12:02:13 -05:00
|
|
|
return localKey;
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
|
|
|
// check if the key id still exists on the key server
|
2014-12-12 12:02:13 -05:00
|
|
|
return checkKeyExists(localKey);
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
// checks if the user's key has been revoked by looking up the key id
|
|
|
|
function checkKeyExists(localKey) {
|
2015-01-28 09:01:18 -05:00
|
|
|
return self._publicKeyDao.getByUserId(userId).then(function(cloudKey) {
|
2014-10-02 16:05:44 -04:00
|
|
|
if (cloudKey && cloudKey._id === localKey._id) {
|
|
|
|
// the key is present on the server, all is well
|
2014-12-12 12:02:13 -05:00
|
|
|
return localKey;
|
2014-06-18 04:02:33 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
// the key has changed, update the key
|
2015-01-28 09:01:18 -05:00
|
|
|
return updateKey(localKey, cloudKey);
|
2014-12-12 12:02:13 -05:00
|
|
|
|
|
|
|
}).catch(function(err) {
|
|
|
|
if (err && err.code === 42) {
|
|
|
|
// we're offline, we're done checking the key
|
|
|
|
return localKey;
|
|
|
|
}
|
|
|
|
throw err;
|
2014-05-23 04:52:34 -04:00
|
|
|
});
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
2014-05-23 04:52:34 -04:00
|
|
|
|
2015-01-28 09:01:18 -05:00
|
|
|
function updateKey(localKey, newKey) {
|
|
|
|
// the public key has changed, we need to ask for permission to update the key
|
|
|
|
if (overridePermission) {
|
|
|
|
// don't query the user, update the public key right away
|
|
|
|
return permissionGranted(localKey, newKey);
|
|
|
|
} else {
|
|
|
|
return requestPermission(localKey, newKey);
|
|
|
|
}
|
2014-12-12 12:02:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function requestPermission(localKey, newKey) {
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
// query the user if the public key should be updated
|
|
|
|
self.requestPermissionForKeyUpdate({
|
|
|
|
userId: userId,
|
|
|
|
newKey: newKey
|
|
|
|
}, function(granted) {
|
2014-10-02 16:05:44 -04:00
|
|
|
if (!granted) {
|
|
|
|
// permission was not given to update the key, so don't overwrite the old one!
|
2014-12-12 12:02:13 -05:00
|
|
|
resolve(localKey);
|
2014-05-23 04:52:34 -04:00
|
|
|
return;
|
|
|
|
}
|
2014-12-12 12:02:13 -05:00
|
|
|
// permission was granted by the user
|
|
|
|
permissionGranted(localKey, newKey).then(resolve).catch(reject);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2014-05-23 04:52:34 -04:00
|
|
|
|
2014-12-12 12:02:13 -05:00
|
|
|
function permissionGranted(localKey, newKey) {
|
|
|
|
// permission to update the key was given, so delete the old one and persist the new one
|
|
|
|
return self.removeLocalPublicKey(localKey._id).then(function() {
|
|
|
|
if (!newKey) {
|
|
|
|
// error or no new key to save
|
|
|
|
return;
|
2014-11-06 08:28:14 -05:00
|
|
|
}
|
2014-12-12 12:02:13 -05:00
|
|
|
// persist the new key and return it
|
|
|
|
return self.saveLocalPublicKey(newKey).then(function() {
|
|
|
|
return newKey;
|
|
|
|
});
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up a reveiver's public key by user id
|
|
|
|
* @param userId [String] the receiver's email address
|
|
|
|
*/
|
2014-12-12 12:02:13 -05:00
|
|
|
Keychain.prototype.getReceiverPublicKey = function(userId) {
|
2014-10-02 16:05:44 -04:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
// search local keyring for public key
|
2015-03-31 09:43:42 -04:00
|
|
|
return self._lawnchairDAO.list(DB_PUBLICKEY).then(function(allPubkeys) {
|
2015-01-28 09:01:18 -05:00
|
|
|
var userIds;
|
2014-10-02 16:05:44 -04:00
|
|
|
// query primary email address
|
|
|
|
var pubkey = _.findWhere(allPubkeys, {
|
|
|
|
userId: userId
|
|
|
|
});
|
2015-02-10 09:21:04 -05:00
|
|
|
// query mutliple userIds
|
2014-10-02 16:05:44 -04:00
|
|
|
if (!pubkey) {
|
|
|
|
for (var i = 0, match; i < allPubkeys.length; i++) {
|
2015-01-28 09:01:18 -05:00
|
|
|
userIds = self._pgp.getKeyParams(allPubkeys[i].publicKey).userIds;
|
|
|
|
match = _.findWhere(userIds, {
|
2014-10-02 16:05:44 -04:00
|
|
|
emailAddress: userId
|
|
|
|
});
|
|
|
|
if (match) {
|
|
|
|
pubkey = allPubkeys[i];
|
|
|
|
break;
|
2014-05-23 04:52:34 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
|
|
|
}
|
2014-12-12 12:02:13 -05:00
|
|
|
// that user's public key is already in local storage
|
2014-10-02 16:05:44 -04:00
|
|
|
if (pubkey && pubkey._id) {
|
2014-12-12 12:02:13 -05:00
|
|
|
return pubkey;
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
|
|
|
// no public key by that user id in storage
|
|
|
|
// find from cloud by email address
|
2014-12-12 12:02:13 -05:00
|
|
|
return self._publicKeyDao.getByUserId(userId).then(onKeyReceived).catch(onError);
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
2014-05-23 04:52:34 -04:00
|
|
|
|
2014-12-12 12:02:13 -05:00
|
|
|
function onKeyReceived(cloudPubkey) {
|
2014-10-02 16:05:44 -04:00
|
|
|
if (!cloudPubkey) {
|
|
|
|
// public key has been deleted without replacement
|
|
|
|
return;
|
|
|
|
}
|
2014-12-12 12:02:13 -05:00
|
|
|
// persist and return cloud key
|
|
|
|
return self.saveLocalPublicKey(cloudPubkey).then(function() {
|
|
|
|
return cloudPubkey;
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
}
|
2014-12-12 12:02:13 -05:00
|
|
|
|
|
|
|
function onError(err) {
|
|
|
|
if (err && err.code === 42) {
|
|
|
|
// offline
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
};
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
//
|
|
|
|
// Keypair functions
|
|
|
|
//
|
2014-06-13 06:33:30 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
|
|
|
* Gets the local user's key either from local storage
|
|
|
|
* or fetches it from the cloud. The private key is encrypted.
|
|
|
|
* If no key pair exists, null is returned.
|
|
|
|
* return [Object] The user's key pair {publicKey, privateKey}
|
|
|
|
*/
|
2014-12-12 12:02:13 -05:00
|
|
|
Keychain.prototype.getUserKeyPair = function(userId) {
|
2014-10-02 16:05:44 -04:00
|
|
|
var self = this;
|
2014-06-13 06:33:30 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// search for user's public key locally
|
2015-03-31 09:43:42 -04:00
|
|
|
return self._lawnchairDAO.list(DB_PUBLICKEY).then(function(allPubkeys) {
|
2014-10-02 16:05:44 -04:00
|
|
|
var pubkey = _.findWhere(allPubkeys, {
|
|
|
|
userId: userId
|
|
|
|
});
|
|
|
|
|
2015-02-10 09:21:04 -05:00
|
|
|
if (pubkey && pubkey._id && !pubkey.source) {
|
2014-10-02 16:05:44 -04:00
|
|
|
// that user's public key is already in local storage...
|
|
|
|
// sync keypair to the cloud
|
2014-12-12 12:02:13 -05:00
|
|
|
return syncKeypair(pubkey._id);
|
2014-06-06 12:36:23 -04:00
|
|
|
}
|
2014-10-02 16:05:44 -04:00
|
|
|
|
|
|
|
// no public key by that user id in storage
|
|
|
|
// find from cloud by email address
|
2014-12-12 12:02:13 -05:00
|
|
|
return self._publicKeyDao.getByUserId(userId).then(function(cloudPubkey) {
|
2015-02-10 09:21:04 -05:00
|
|
|
if (cloudPubkey && cloudPubkey._id && !cloudPubkey.source) {
|
2014-10-02 16:05:44 -04:00
|
|
|
// there is a public key for that user already in the cloud...
|
|
|
|
// sync keypair to local storage
|
2014-12-12 12:02:13 -05:00
|
|
|
return syncKeypair(cloudPubkey._id);
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2015-02-10 09:21:04 -05:00
|
|
|
// continue without keypair... generate or import new keypair
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
function syncKeypair(keypairId) {
|
2014-12-12 12:02:13 -05:00
|
|
|
var savedPubkey, savedPrivkey;
|
2014-10-02 16:05:44 -04:00
|
|
|
// persist key pair in local storage
|
2014-12-12 12:02:13 -05:00
|
|
|
return self.lookupPublicKey(keypairId).then(function(pub) {
|
|
|
|
savedPubkey = pub;
|
2015-02-10 09:21:04 -05:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// persist private key in local storage
|
2015-02-10 09:21:04 -05:00
|
|
|
return self.lookupPrivateKey(keypairId);
|
|
|
|
|
|
|
|
}).then(function(priv) {
|
|
|
|
savedPrivkey = priv;
|
2013-10-29 07:19:27 -04:00
|
|
|
|
2014-12-12 12:02:13 -05:00
|
|
|
}).then(function() {
|
|
|
|
var keys = {};
|
2013-10-29 07:19:27 -04:00
|
|
|
|
2014-12-12 12:02:13 -05:00
|
|
|
if (savedPubkey && savedPubkey.publicKey) {
|
|
|
|
keys.publicKey = savedPubkey;
|
|
|
|
}
|
|
|
|
if (savedPrivkey && savedPrivkey.encryptedKey) {
|
|
|
|
keys.privateKey = savedPrivkey;
|
|
|
|
}
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2014-12-12 12:02:13 -05:00
|
|
|
return keys;
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
2013-10-21 07:10:42 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
/**
|
|
|
|
* Checks to see if the user's key pair is stored both
|
|
|
|
* locally and in the cloud and persist arccordingly
|
|
|
|
* @param [Object] The user's key pair {publicKey, privateKey}
|
|
|
|
*/
|
2014-12-12 12:02:13 -05:00
|
|
|
Keychain.prototype.putUserKeyPair = function(keypair) {
|
2014-10-02 16:05:44 -04:00
|
|
|
var self = this;
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// validate input
|
|
|
|
if (!keypair || !keypair.publicKey || !keypair.privateKey || !keypair.publicKey.userId || keypair.publicKey.userId !== keypair.privateKey.userId) {
|
2014-12-12 12:02:13 -05:00
|
|
|
return new Promise(function() {
|
2015-02-20 11:55:11 -05:00
|
|
|
throw new Error('Cannot put user key pair: Incorrect input!');
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
}
|
2013-10-29 07:19:27 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// don't check the user's own public key for deletion in refreshKeyForUserId
|
|
|
|
keypair.publicKey.imported = true;
|
2013-10-21 07:10:42 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
// store public key locally
|
2014-12-12 12:02:13 -05:00
|
|
|
return self.saveLocalPublicKey(keypair.publicKey).then(function() {
|
2014-10-02 16:05:44 -04:00
|
|
|
// persist public key in cloud storage
|
2014-12-12 12:02:13 -05:00
|
|
|
return self._publicKeyDao.put(keypair.publicKey);
|
|
|
|
}).then(function() {
|
|
|
|
// store private key locally
|
|
|
|
return self.saveLocalPrivateKey(keypair.privateKey);
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
};
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2015-02-20 11:55:11 -05:00
|
|
|
/**
|
|
|
|
* Uploads the public key
|
|
|
|
* @param {Object} publicKey The user's public key
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
|
|
|
Keychain.prototype.uploadPublicKey = function(publicKey) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
// validate input
|
|
|
|
if (!publicKey || !publicKey.userId || !publicKey.publicKey) {
|
|
|
|
return new Promise(function() {
|
|
|
|
throw new Error('Cannot upload user key pair: Incorrect input!');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return self._publicKeyDao.put(publicKey);
|
|
|
|
};
|
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
//
|
|
|
|
// Helper functions
|
|
|
|
//
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2014-12-11 14:43:51 -05:00
|
|
|
Keychain.prototype.lookupPublicKey = function(id) {
|
2014-12-12 12:02:13 -05:00
|
|
|
var self = this,
|
|
|
|
cloudPubkey;
|
2013-08-16 15:21:24 -04:00
|
|
|
|
2014-10-02 16:05:44 -04:00
|
|
|
if (!id) {
|
2014-12-11 14:43:51 -05:00
|
|
|
return new Promise(function() {
|
|
|
|
throw new Error('ID must be set for public key query!');
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// lookup in local storage
|
2014-12-11 14:43:51 -05:00
|
|
|
return self._lawnchairDAO.read(DB_PUBLICKEY + '_' + id).then(function(pubkey) {
|
2014-10-02 16:05:44 -04:00
|
|
|
if (pubkey) {
|
2014-12-11 14:43:51 -05:00
|
|
|
return pubkey;
|
2014-10-02 16:05:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// fetch from cloud storage
|
2014-12-12 12:02:13 -05:00
|
|
|
return self._publicKeyDao.get(id).then(function(pub) {
|
|
|
|
cloudPubkey = pub;
|
2014-10-02 16:05:44 -04:00
|
|
|
// cache public key in cache
|
2014-12-12 12:02:13 -05:00
|
|
|
return self.saveLocalPublicKey(cloudPubkey);
|
|
|
|
}).then(function() {
|
|
|
|
return cloudPubkey;
|
2013-08-16 15:21:24 -04:00
|
|
|
});
|
2014-10-02 16:05:44 -04:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List all the locally stored public keys
|
|
|
|
*/
|
2014-12-11 14:43:51 -05:00
|
|
|
Keychain.prototype.listLocalPublicKeys = function() {
|
2014-10-02 16:05:44 -04:00
|
|
|
// search local keyring for public key
|
2015-03-31 09:43:42 -04:00
|
|
|
return this._lawnchairDAO.list(DB_PUBLICKEY);
|
2014-10-02 16:05:44 -04:00
|
|
|
};
|
|
|
|
|
2014-12-11 14:43:51 -05:00
|
|
|
Keychain.prototype.removeLocalPublicKey = function(id) {
|
|
|
|
return this._lawnchairDAO.remove(DB_PUBLICKEY + '_' + id);
|
2014-10-02 16:05:44 -04:00
|
|
|
};
|
|
|
|
|
2014-12-11 14:43:51 -05:00
|
|
|
Keychain.prototype.lookupPrivateKey = function(id) {
|
2014-10-02 16:05:44 -04:00
|
|
|
// lookup in local storage
|
2014-12-11 14:43:51 -05:00
|
|
|
return this._lawnchairDAO.read(DB_PRIVATEKEY + '_' + id);
|
2014-10-02 16:05:44 -04:00
|
|
|
};
|
|
|
|
|
2014-12-11 14:43:51 -05:00
|
|
|
Keychain.prototype.saveLocalPublicKey = function(pubkey) {
|
2014-10-02 16:05:44 -04:00
|
|
|
// persist public key (email, _id)
|
|
|
|
var pkLookupKey = DB_PUBLICKEY + '_' + pubkey._id;
|
2014-12-11 14:43:51 -05:00
|
|
|
return this._lawnchairDAO.persist(pkLookupKey, pubkey);
|
2014-10-02 16:05:44 -04:00
|
|
|
};
|
|
|
|
|
2014-12-11 14:43:51 -05:00
|
|
|
Keychain.prototype.saveLocalPrivateKey = function(privkey) {
|
2014-10-02 16:05:44 -04:00
|
|
|
// persist private key (email, _id)
|
|
|
|
var prkLookupKey = DB_PRIVATEKEY + '_' + privkey._id;
|
2014-12-11 14:43:51 -05:00
|
|
|
return this._lawnchairDAO.persist(prkLookupKey, privkey);
|
2014-11-18 12:44:00 -05:00
|
|
|
};
|