mirror of
https://github.com/moparisthebest/mail
synced 2024-11-25 18:32:20 -05:00
Implement client side key sync protocol and ui
This commit is contained in:
parent
c890cbe71d
commit
b5fda88b8a
@ -25,6 +25,8 @@ define(function(require) {
|
||||
*/
|
||||
app.config = {
|
||||
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
|
||||
privkeyServerUrl: 'https://keychain-test.whiteout.io',
|
||||
serverPrivateKeyId: 'EE342F0DDBB0F3BE',
|
||||
symKeySize: 256,
|
||||
symIvSize: 96,
|
||||
asymKeySize: 2048,
|
||||
|
@ -12,6 +12,7 @@ define(function(require) {
|
||||
OutboxBO = require('js/bo/outbox'),
|
||||
mailreader = require('mailreader'),
|
||||
ImapClient = require('imap-client'),
|
||||
Crypto = require('js/crypto/crypto'),
|
||||
RestDAO = require('js/dao/rest-dao'),
|
||||
EmailDAO = require('js/dao/email-dao'),
|
||||
appConfig = require('js/app-config'),
|
||||
@ -20,6 +21,7 @@ define(function(require) {
|
||||
KeychainDAO = require('js/dao/keychain-dao'),
|
||||
PublicKeyDAO = require('js/dao/publickey-dao'),
|
||||
LawnchairDAO = require('js/dao/lawnchair-dao'),
|
||||
PrivateKeyDAO = require('js/dao/privatekey-dao'),
|
||||
InvitationDAO = require('js/dao/invitation-dao'),
|
||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||
UpdateHandler = require('js/util/update/update-handler');
|
||||
@ -55,7 +57,7 @@ define(function(require) {
|
||||
};
|
||||
|
||||
self.buildModules = function(options) {
|
||||
var lawnchairDao, restDao, pubkeyDao, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore;
|
||||
var lawnchairDao, restDao, pubkeyDao, privkeyDao, crypto, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore;
|
||||
|
||||
// start the mailreader's worker thread
|
||||
mailreader.startWorker(config.workerPath + '/../lib/mailreader-parser-worker.js');
|
||||
@ -64,9 +66,12 @@ define(function(require) {
|
||||
restDao = new RestDAO();
|
||||
lawnchairDao = new LawnchairDAO();
|
||||
pubkeyDao = new PublicKeyDAO(restDao);
|
||||
privkeyDao = new PrivateKeyDAO(new RestDAO(config.privkeyServerUrl));
|
||||
oauth = new OAuth(new RestDAO('https://www.googleapis.com'));
|
||||
|
||||
self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
|
||||
crypto = new Crypto();
|
||||
self._pgp = pgp = new PGP();
|
||||
self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao, privkeyDao, crypto, pgp);
|
||||
keychain.requestPermissionForKeyUpdate = function(params, callback) {
|
||||
var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey;
|
||||
message = message.replace('{0}', params.userId);
|
||||
@ -85,7 +90,6 @@ define(function(require) {
|
||||
self._auth = new Auth(appConfigStore, oauth, new RestDAO('/ca'));
|
||||
self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
|
||||
self._invitationDao = new InvitationDAO(restDao);
|
||||
self._crypto = pgp = new PGP();
|
||||
self._pgpbuilder = pgpbuilder = new PgpBuilder();
|
||||
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader);
|
||||
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
|
||||
|
@ -8,12 +8,14 @@ requirejs([
|
||||
'js/controller/add-account',
|
||||
'js/controller/account',
|
||||
'js/controller/set-passphrase',
|
||||
'js/controller/privatekey-upload',
|
||||
'js/controller/contacts',
|
||||
'js/controller/about',
|
||||
'js/controller/login',
|
||||
'js/controller/login-initial',
|
||||
'js/controller/login-new-device',
|
||||
'js/controller/login-existing',
|
||||
'js/controller/login-privatekey-download',
|
||||
'js/controller/mail-list',
|
||||
'js/controller/read',
|
||||
'js/controller/write',
|
||||
@ -31,12 +33,14 @@ requirejs([
|
||||
AddAccountCtrl,
|
||||
AccountCtrl,
|
||||
SetPassphraseCtrl,
|
||||
PrivateKeyUploadCtrl,
|
||||
ContactsCtrl,
|
||||
AboutCtrl,
|
||||
LoginCtrl,
|
||||
LoginInitialCtrl,
|
||||
LoginNewDeviceCtrl,
|
||||
LoginExistingCtrl,
|
||||
LoginPrivateKeyDownloadCtrl,
|
||||
MailListCtrl,
|
||||
ReadCtrl,
|
||||
WriteCtrl,
|
||||
@ -89,6 +93,10 @@ requirejs([
|
||||
templateUrl: 'tpl/login-new-device.html',
|
||||
controller: LoginNewDeviceCtrl
|
||||
});
|
||||
$routeProvider.when('/login-privatekey-download', {
|
||||
templateUrl: 'tpl/login-privatekey-download.html',
|
||||
controller: LoginPrivateKeyDownloadCtrl
|
||||
});
|
||||
$routeProvider.when('/desktop', {
|
||||
templateUrl: 'tpl/desktop.html',
|
||||
controller: NavigationCtrl
|
||||
@ -113,6 +121,7 @@ requirejs([
|
||||
app.controller('MailListCtrl', MailListCtrl);
|
||||
app.controller('AccountCtrl', AccountCtrl);
|
||||
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
|
||||
app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl);
|
||||
app.controller('ContactsCtrl', ContactsCtrl);
|
||||
app.controller('AboutCtrl', AboutCtrl);
|
||||
app.controller('DialogCtrl', DialogCtrl);
|
||||
|
@ -12,7 +12,7 @@ define(function(require) {
|
||||
var AccountCtrl = function($scope) {
|
||||
userId = appController._emailDao._account.emailAddress;
|
||||
keychain = appController._keychain;
|
||||
pgp = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
|
||||
$scope.state.account = {
|
||||
toggle: function(to) {
|
||||
|
@ -12,7 +12,7 @@ define(function(require) {
|
||||
|
||||
var ContactsCtrl = function($scope) {
|
||||
keychain = appController._keychain,
|
||||
pgp = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
|
||||
$scope.state.contacts = {
|
||||
toggle: function(to) {
|
||||
|
@ -6,7 +6,7 @@ define(function(require) {
|
||||
|
||||
var LoginExistingCtrl = function($scope, $location) {
|
||||
var emailDao = appController._emailDao,
|
||||
pgp = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
|
||||
$scope.incorrect = false;
|
||||
|
||||
|
103
src/js/controller/login-privatekey-download.js
Normal file
103
src/js/controller/login-privatekey-download.js
Normal file
@ -0,0 +1,103 @@
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var appController = require('js/app-controller');
|
||||
|
||||
var LoginPrivateKeyDownloadCtrl = function($scope, $location) {
|
||||
var keychain = appController._keychain,
|
||||
emailDao = appController._emailDao,
|
||||
userId = emailDao._account.emailAddress;
|
||||
|
||||
$scope.step = 1;
|
||||
|
||||
$scope.verifyRecoveryToken = function(callback) {
|
||||
if (!$scope.recoveryToken) {
|
||||
$scope.onError(new Error('Please set the recovery token!'));
|
||||
return;
|
||||
}
|
||||
|
||||
keychain.getUserKeyPair(userId, function(err, keypair) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// remember for storage later
|
||||
$scope.cachedKeypair = keypair;
|
||||
|
||||
keychain.downloadPrivateKey({
|
||||
userId: userId,
|
||||
keyId: keypair.publicKey._id,
|
||||
recoveryToken: $scope.recoveryToken
|
||||
}, function(err, encryptedPrivateKey) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.encryptedPrivateKey = encryptedPrivateKey;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.decryptAndStorePrivateKeyLocally = function() {
|
||||
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
|
||||
|
||||
if (!inputCode) {
|
||||
$scope.onError(new Error('Please enter the keychain code!'));
|
||||
return;
|
||||
}
|
||||
|
||||
var options = $scope.encryptedPrivateKey;
|
||||
options.code = inputCode.toUpperCase();
|
||||
|
||||
keychain.decryptAndStorePrivateKeyLocally(options, function(err, privateKey) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// add private key to cached keypair object
|
||||
$scope.cachedKeypair.privateKey = privateKey;
|
||||
|
||||
// try empty passphrase
|
||||
emailDao.unlock({
|
||||
keypair: $scope.cachedKeypair,
|
||||
passphrase: undefined
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
// go to passphrase login screen
|
||||
$scope.goTo('/login-existing');
|
||||
return;
|
||||
}
|
||||
|
||||
// passphrase is corrent ... go to main app
|
||||
$scope.goTo('/desktop');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.goForward = function() {
|
||||
if ($scope.step === 1) {
|
||||
$scope.verifyRecoveryToken(function() {
|
||||
$scope.step++;
|
||||
$scope.$apply();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.step === 2) {
|
||||
$scope.decryptAndStorePrivateKeyLocally();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.goTo = function(location) {
|
||||
$location.path(location);
|
||||
$scope.$apply();
|
||||
};
|
||||
};
|
||||
|
||||
return LoginPrivateKeyDownloadCtrl;
|
||||
});
|
@ -52,9 +52,28 @@ define(function(require) {
|
||||
if (typeof availableKeys === 'undefined') {
|
||||
// no public key available, start onboarding process
|
||||
goTo('/login-initial');
|
||||
} else if (!availableKeys.privateKey) {
|
||||
// no private key, import key
|
||||
goTo('/login-new-device');
|
||||
|
||||
} else if (availableKeys && !availableKeys.privateKey) {
|
||||
// check if private key is synced
|
||||
appController._keychain.requestPrivateKeyDownload({
|
||||
userId: availableKeys.publicKey.userId,
|
||||
keyId: availableKeys.publicKey._id,
|
||||
}, function(err, privateKeySynced) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (privateKeySynced) {
|
||||
// private key is synced, proceed to download
|
||||
goTo('/login-privatekey-download');
|
||||
return;
|
||||
}
|
||||
|
||||
// no private key, import key file
|
||||
goTo('/login-new-device');
|
||||
});
|
||||
|
||||
} else {
|
||||
// public and private key available, try empty passphrase
|
||||
appController._emailDao.unlock({
|
||||
|
170
src/js/controller/privatekey-upload.js
Normal file
170
src/js/controller/privatekey-upload.js
Normal file
@ -0,0 +1,170 @@
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var appController = require('js/app-controller'),
|
||||
keychain, pgp;
|
||||
|
||||
var PrivateKeyUploadCtrl = function($scope) {
|
||||
keychain = appController._keychain;
|
||||
pgp = keychain._pgp;
|
||||
|
||||
$scope.state.privateKeyUpload = {
|
||||
toggle: function(to) {
|
||||
// open lightbox
|
||||
$scope.state.lightbox = (to) ? 'privatekey-upload' : undefined;
|
||||
if (!to) {
|
||||
return;
|
||||
}
|
||||
|
||||
// show syncing status
|
||||
$scope.step = 4;
|
||||
// check if key is already synced
|
||||
$scope.checkServerForKey(function(privateKeySynced) {
|
||||
if (privateKeySynced) {
|
||||
// close lightbox
|
||||
$scope.state.lightbox = undefined;
|
||||
// show message
|
||||
$scope.onError({
|
||||
title: 'Info',
|
||||
message: 'Your PGP key has already been synced.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// show sync ui if key is not synced
|
||||
$scope.displayUploadUi();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.checkServerForKey = function(callback) {
|
||||
var keyParams = pgp.getKeyParams();
|
||||
keychain.requestPrivateKeyDownload({
|
||||
userId: keyParams.userId,
|
||||
keyId: keyParams._id,
|
||||
}, function(err, privateKeySynced) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (privateKeySynced) {
|
||||
callback(privateKeySynced);
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.displayUploadUi = function() {
|
||||
// go to step 1
|
||||
$scope.step = 1;
|
||||
// generate new code for the user
|
||||
$scope.code = $scope.generateCode();
|
||||
$scope.displayedCode = $scope.code.slice(0, 4) + '-' + $scope.code.slice(4, 8) + '-' + $scope.code.slice(8, 12) + '-' + $scope.code.slice(12, 16) + '-' + $scope.code.slice(16, 20) + '-' + $scope.code.slice(20, 24);
|
||||
};
|
||||
|
||||
$scope.generateCode = function() {
|
||||
function randomString(length, chars) {
|
||||
var result = '';
|
||||
var randomValues = new Uint8Array(length); // get random length number of bytes
|
||||
window.crypto.getRandomValues(randomValues);
|
||||
for (var i = 0; i < length; i++) {
|
||||
result += chars[Math.round(randomValues[i] / 255 * (chars.length - 1))];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return randomString(24, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ');
|
||||
};
|
||||
|
||||
$scope.verifyCode = function() {
|
||||
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
|
||||
|
||||
if (inputCode.toUpperCase() !== $scope.code) {
|
||||
var err = new Error('The code does not match. Please go back and check the generated code.');
|
||||
err.sync = true;
|
||||
$scope.onError(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.setDeviceName = function(callback) {
|
||||
keychain.setDeviceName($scope.deviceName, callback);
|
||||
};
|
||||
|
||||
$scope.encryptAndUploadKey = function(callback) {
|
||||
var userId = appController._emailDao._account.emailAddress;
|
||||
var code = $scope.code;
|
||||
|
||||
// register device to keychain service
|
||||
keychain.registerDevice({
|
||||
userId: userId
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// encrypt private PGP key using code and upload
|
||||
keychain.uploadPrivateKey({
|
||||
userId: userId,
|
||||
code: code
|
||||
}, callback);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.goBack = function() {
|
||||
if ($scope.step > 1) {
|
||||
$scope.step--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.goForward = function() {
|
||||
if ($scope.step < 2) {
|
||||
$scope.step++;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.step === 2 && $scope.verifyCode()) {
|
||||
$scope.step++;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.step === 3) {
|
||||
// set device name to local storage
|
||||
$scope.setDeviceName(function(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// show spinner
|
||||
$scope.step++;
|
||||
$scope.$apply();
|
||||
|
||||
// init key sync
|
||||
$scope.encryptAndUploadKey(function(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// close sync dialog
|
||||
$scope.state.privateKeyUpload.toggle(false);
|
||||
// show success message
|
||||
$scope.onError({
|
||||
title: 'Success',
|
||||
message: 'Whiteout Keychain setup successful!'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
return PrivateKeyUploadCtrl;
|
||||
});
|
@ -5,7 +5,7 @@ define(function(require) {
|
||||
download = require('js/util/download'),
|
||||
angular = require('angular'),
|
||||
str = require('js/app-config').string,
|
||||
emailDao, invitationDao, outbox, crypto, keychain;
|
||||
emailDao, invitationDao, outbox, pgp, keychain;
|
||||
|
||||
//
|
||||
// Controller
|
||||
@ -16,7 +16,7 @@ define(function(require) {
|
||||
emailDao = appController._emailDao;
|
||||
invitationDao = appController._invitationDao;
|
||||
outbox = appController._outboxBo;
|
||||
crypto = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
keychain = appController._keychain;
|
||||
|
||||
// set default value so that the popover height is correct on init
|
||||
@ -47,7 +47,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fpr = crypto.getFingerprint(pubkey.publicKey);
|
||||
var fpr = pgp.getFingerprint(pubkey.publicKey);
|
||||
var formatted = fpr.slice(32);
|
||||
|
||||
$scope.keyId = 'PGP key: ' + formatted;
|
||||
|
@ -10,7 +10,7 @@ define(function(require) {
|
||||
|
||||
var SetPassphraseCtrl = function($scope) {
|
||||
keychain = appController._keychain;
|
||||
pgp = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
|
||||
$scope.state.setPassphrase = {
|
||||
toggle: function(to) {
|
||||
|
@ -7,14 +7,14 @@ define(function(require) {
|
||||
aes = require('js/crypto/aes-gcm'),
|
||||
util = require('js/crypto/util'),
|
||||
str = require('js/app-config').string,
|
||||
crypto, emailDao, outbox, keychainDao;
|
||||
pgp, emailDao, outbox, keychainDao;
|
||||
|
||||
//
|
||||
// Controller
|
||||
//
|
||||
|
||||
var WriteCtrl = function($scope, $filter) {
|
||||
crypto = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
emailDao = appController._emailDao,
|
||||
outbox = appController._outboxBo;
|
||||
keychainDao = appController._keychain;
|
||||
@ -218,7 +218,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fpr = crypto.getFingerprint(recipient.key.publicKey);
|
||||
var fpr = pgp.getFingerprint(recipient.key.publicKey);
|
||||
var formatted = fpr.slice(32);
|
||||
|
||||
$scope.keyId = formatted;
|
||||
|
@ -7,7 +7,7 @@ define(['forge'], function(forge) {
|
||||
var self = {};
|
||||
|
||||
/**
|
||||
* PBKDF2-HMAC-SHA1 key derivation with a random salt and 1000 iterations
|
||||
* PBKDF2-HMAC-SHA256 key derivation with a random salt and 10000 iterations
|
||||
* @param {String} password The password in UTF8
|
||||
* @param {String} salt The base64 encoded salt
|
||||
* @param {String} keySize The key size in bits
|
||||
|
@ -20,9 +20,7 @@ define(function(require) {
|
||||
var userId, passphrase;
|
||||
|
||||
if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) {
|
||||
callback({
|
||||
errMsg: 'Crypto init failed. Not all options set!'
|
||||
});
|
||||
callback(new Error('Crypto init failed. Not all options set!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -38,10 +36,7 @@ define(function(require) {
|
||||
|
||||
function onGenerated(err, keys) {
|
||||
if (err) {
|
||||
callback({
|
||||
errMsg: 'Keygeneration failed!',
|
||||
err: err
|
||||
});
|
||||
callback(new Error('Keygeneration failed!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -146,9 +141,7 @@ define(function(require) {
|
||||
|
||||
// check options
|
||||
if (!options.privateKeyArmored || !options.publicKeyArmored) {
|
||||
callback({
|
||||
errMsg: 'Importing keys failed. Not all options set!'
|
||||
});
|
||||
callback(new Error('Importing keys failed. Not all options set!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -163,18 +156,14 @@ define(function(require) {
|
||||
this._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
|
||||
} catch (e) {
|
||||
resetKeys();
|
||||
callback({
|
||||
errMsg: 'Importing keys failed. Parsing error!'
|
||||
});
|
||||
callback(new Error('Importing keys failed. Parsing error!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// decrypt private key with passphrase
|
||||
if (!this._privateKey.decrypt(options.passphrase)) {
|
||||
resetKeys();
|
||||
callback({
|
||||
errMsg: 'Incorrect passphrase!'
|
||||
});
|
||||
callback(new Error('Incorrect passphrase!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -183,9 +172,7 @@ define(function(require) {
|
||||
privKeyId = this._privateKey.getKeyPacket().getKeyId().toHex();
|
||||
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
|
||||
resetKeys();
|
||||
callback({
|
||||
errMsg: 'Key IDs dont match!'
|
||||
});
|
||||
callback(new Error('Key IDs dont match!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -197,9 +184,7 @@ define(function(require) {
|
||||
*/
|
||||
PGP.prototype.exportKeys = function(callback) {
|
||||
if (!this._publicKey || !this._privateKey) {
|
||||
callback({
|
||||
errMsg: 'Could not export keys!'
|
||||
});
|
||||
callback(new Error('Could not export keys!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -220,9 +205,7 @@ define(function(require) {
|
||||
newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined;
|
||||
|
||||
if (!options.privateKeyArmored) {
|
||||
callback({
|
||||
errMsg: 'Private key must be specified to change passphrase!'
|
||||
});
|
||||
callback(new Error('Private key must be specified to change passphrase!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -236,17 +219,13 @@ define(function(require) {
|
||||
try {
|
||||
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
|
||||
} catch (e) {
|
||||
callback({
|
||||
errMsg: 'Importing key failed. Parsing error!'
|
||||
});
|
||||
callback(new Error('Importing key failed. Parsing error!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// decrypt private key with passphrase
|
||||
if (!privKey.decrypt(options.oldPassphrase)) {
|
||||
callback({
|
||||
errMsg: 'Old passphrase incorrect!'
|
||||
});
|
||||
callback(new Error('Old passphrase incorrect!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -258,17 +237,13 @@ define(function(require) {
|
||||
}
|
||||
newKeyArmored = privKey.armor();
|
||||
} catch (e) {
|
||||
callback({
|
||||
errMsg: 'Setting new passphrase failed!'
|
||||
});
|
||||
callback(new Error('Setting new passphrase failed!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// check if new passphrase really works
|
||||
if (!privKey.decrypt(newPassphrase)) {
|
||||
callback({
|
||||
errMsg: 'Decrypting key with new passphrase failed!'
|
||||
});
|
||||
callback(new Error('Decrypting key with new passphrase failed!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -283,9 +258,7 @@ define(function(require) {
|
||||
|
||||
// check keys
|
||||
if (!this._privateKey || publicKeysArmored.length < 1) {
|
||||
callback({
|
||||
errMsg: 'Error encrypting. Keys must be set!'
|
||||
});
|
||||
callback(new Error('Error encrypting. Keys must be set!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -295,10 +268,7 @@ define(function(require) {
|
||||
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
|
||||
});
|
||||
} catch (err) {
|
||||
callback({
|
||||
errMsg: 'Error encrypting plaintext!',
|
||||
err: err
|
||||
});
|
||||
callback(new Error('Error encrypting plaintext!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -314,9 +284,7 @@ define(function(require) {
|
||||
|
||||
// check keys
|
||||
if (!this._privateKey || !publicKeyArmored) {
|
||||
callback({
|
||||
errMsg: 'Error decrypting. Keys must be set!'
|
||||
});
|
||||
callback(new Error('Error decrypting. Keys must be set!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -325,10 +293,7 @@ define(function(require) {
|
||||
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
|
||||
message = openpgp.message.readArmored(ciphertext);
|
||||
} catch (err) {
|
||||
callback({
|
||||
errMsg: 'Error decrypting PGP message!',
|
||||
err: err
|
||||
});
|
||||
callback(new Error('Error decrypting PGP message!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -337,10 +302,7 @@ define(function(require) {
|
||||
|
||||
function onDecrypted(err, decrypted) {
|
||||
if (err) {
|
||||
callback({
|
||||
errMsg: 'Error decrypting PGP message!',
|
||||
err: err
|
||||
});
|
||||
callback(new Error('Error decrypting PGP message!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -352,9 +314,7 @@ define(function(require) {
|
||||
}
|
||||
});
|
||||
if (!signaturesValid) {
|
||||
callback({
|
||||
errMsg: 'Verifying PGP signature failed!'
|
||||
});
|
||||
callback(new Error('Verifying PGP signature failed!'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,14 @@ define(function(require) {
|
||||
* PGP de-/encryption, receiving via IMAP, sending via SMTP, MIME parsing, local db persistence
|
||||
*
|
||||
* @param {Object} keychain The keychain DAO handles keys transparently
|
||||
* @param {Object} crypto Orchestrates decryption
|
||||
* @param {Object} pgp Orchestrates decryption
|
||||
* @param {Object} devicestorage Handles persistence to the local indexed db
|
||||
* @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages
|
||||
* @param {Object} mailreader Parses MIME messages received from IMAP
|
||||
*/
|
||||
var EmailDAO = function(keychain, crypto, devicestorage, pgpbuilder, mailreader) {
|
||||
var EmailDAO = function(keychain, pgp, devicestorage, pgpbuilder, mailreader) {
|
||||
this._keychain = keychain;
|
||||
this._crypto = crypto;
|
||||
this._pgp = pgp;
|
||||
this._devicestorage = devicestorage;
|
||||
this._pgpbuilder = pgpbuilder;
|
||||
this._mailreader = mailreader;
|
||||
@ -105,7 +105,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// no keypair for is stored for the user... generate a new one
|
||||
self._crypto.generateKeys({
|
||||
self._pgp.generateKeys({
|
||||
emailAddress: self._account.emailAddress,
|
||||
keySize: self._account.asymKeySize,
|
||||
passphrase: options.passphrase
|
||||
@ -121,8 +121,8 @@ define(function(require) {
|
||||
function handleExistingKeypair(keypair) {
|
||||
var privKeyParams, pubKeyParams;
|
||||
try {
|
||||
privKeyParams = self._crypto.getKeyParams(keypair.privateKey.encryptedKey);
|
||||
pubKeyParams = self._crypto.getKeyParams(keypair.publicKey.publicKey);
|
||||
privKeyParams = self._pgp.getKeyParams(keypair.privateKey.encryptedKey);
|
||||
pubKeyParams = self._pgp.getKeyParams(keypair.publicKey.publicKey);
|
||||
} catch (e) {
|
||||
callback(new Error('Error reading key params!'));
|
||||
return;
|
||||
@ -148,7 +148,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// import existing key pair into crypto module
|
||||
self._crypto.importKeys({
|
||||
self._pgp.importKeys({
|
||||
passphrase: options.passphrase,
|
||||
privateKeyArmored: keypair.privateKey.encryptedKey,
|
||||
publicKeyArmored: keypair.publicKey.publicKey
|
||||
@ -159,14 +159,14 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// set decrypted privateKey to pgpMailer
|
||||
self._pgpbuilder._privateKey = self._crypto._privateKey;
|
||||
self._pgpbuilder._privateKey = self._pgp._privateKey;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function handleGenerated(generatedKeypair) {
|
||||
// import the new key pair into crypto module
|
||||
self._crypto.importKeys({
|
||||
self._pgp.importKeys({
|
||||
passphrase: options.passphrase,
|
||||
privateKeyArmored: generatedKeypair.privateKeyArmored,
|
||||
publicKeyArmored: generatedKeypair.publicKeyArmored
|
||||
@ -196,7 +196,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// set decrypted privateKey to pgpMailer
|
||||
self._pgpbuilder._privateKey = self._crypto._privateKey;
|
||||
self._pgpbuilder._privateKey = self._pgp._privateKey;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
@ -816,7 +816,7 @@ define(function(require) {
|
||||
|
||||
// get the receiver's public key to check the message signature
|
||||
var encryptedNode = filterBodyParts(message.bodyParts, 'encrypted')[0];
|
||||
self._crypto.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted) {
|
||||
self._pgp.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted) {
|
||||
if (err || !decrypted) {
|
||||
showError(err.errMsg || err.message || 'An error occurred during the decryption.');
|
||||
return;
|
||||
|
@ -258,6 +258,11 @@ define(function(require) {
|
||||
* @param {Function} callback(error)
|
||||
*/
|
||||
KeychainDAO.prototype.setDeviceName = function(deviceName, callback) {
|
||||
if (!deviceName) {
|
||||
callback(new Error('Please set a device name!'));
|
||||
return;
|
||||
}
|
||||
|
||||
this._localDbDao.persist(DB_DEVICENAME, deviceName, callback);
|
||||
};
|
||||
|
||||
@ -323,7 +328,8 @@ define(function(require) {
|
||||
* @param {Function} callback(error)
|
||||
*/
|
||||
KeychainDAO.prototype.registerDevice = function(options, callback) {
|
||||
var self = this;
|
||||
var self = this,
|
||||
devName;
|
||||
|
||||
// check if deviceName is already persisted in storage
|
||||
self.getDeviceName(function(err, deviceName) {
|
||||
@ -336,6 +342,8 @@ define(function(require) {
|
||||
});
|
||||
|
||||
function requestDeviceRegistration(deviceName) {
|
||||
devName = deviceName;
|
||||
|
||||
// request device registration session key
|
||||
self._privateKeyDao.requestDeviceRegistration({
|
||||
userId: options.userId,
|
||||
@ -357,15 +365,20 @@ define(function(require) {
|
||||
|
||||
function decryptSessionKey(regSessionKey) {
|
||||
// TODO: fetch public key for service to verify response
|
||||
self.lookupPublicKey('WELL_KNOWN_SERVER_KEY_ID', function(err, serverPubkey) {
|
||||
self.lookupPublicKey(config.serverPrivateKeyId, function(err, serverPubkey) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!serverPubkey || !serverPubkey.publicKey) {
|
||||
callback(new Error('Server public key for device registration not found!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// decrypt the session key
|
||||
var ct = regSessionKey.encryptedRegSessionKey;
|
||||
self._pgp.decrypt(ct, serverPubkey, function(err, decrypedSessionKey) {
|
||||
self._pgp.decrypt(ct, serverPubkey.publicKey, function(err, decrypedSessionKey) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
@ -384,10 +397,10 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
// generate deviceSecretIv
|
||||
var deviceSecretIv = util.random(config.symIvSize);
|
||||
// generate iv
|
||||
var iv = util.random(config.symIvSize);
|
||||
// encrypt deviceSecret
|
||||
self._crypto.encrypt(deviceSecret, regSessionKey, deviceSecretIv, function(err, encryptedDeviceSecret) {
|
||||
self._crypto.encrypt(deviceSecret, regSessionKey, iv, function(err, encryptedDeviceSecret) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
@ -396,9 +409,9 @@ define(function(require) {
|
||||
// upload encryptedDeviceSecret
|
||||
self._privateKeyDao.uploadDeviceSecret({
|
||||
userId: options.userId,
|
||||
deviceName: options.deviceName,
|
||||
deviceName: devName,
|
||||
encryptedDeviceSecret: encryptedDeviceSecret,
|
||||
deviceSecretIv: deviceSecretIv
|
||||
iv: iv
|
||||
}, callback);
|
||||
});
|
||||
});
|
||||
@ -420,7 +433,7 @@ define(function(require) {
|
||||
sessionId;
|
||||
|
||||
// request auth session key required for upload
|
||||
self._privateKeyDao.requestAuthSessionKeys({
|
||||
self._privateKeyDao.requestAuthSessionKey({
|
||||
userId: userId
|
||||
}, function(err, authSessionKey) {
|
||||
if (err) {
|
||||
@ -441,12 +454,17 @@ define(function(require) {
|
||||
|
||||
function decryptSessionKey(authSessionKey) {
|
||||
// TODO: fetch public key for service to verify response
|
||||
self.lookupPublicKey('WELL_KNOWN_SERVER_KEY_ID', function(err, serverPubkey) {
|
||||
self.lookupPublicKey(config.serverPrivateKeyId, function(err, serverPubkey) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!serverPubkey || !serverPubkey.publicKey) {
|
||||
callback(new Error('Server public key for authentication not found!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// decrypt the session key
|
||||
var ct1 = authSessionKey.encryptedAuthSessionKey;
|
||||
self._pgp.decrypt(ct1, serverPubkey.publicKey, function(err, decryptedSessionKey) {
|
||||
@ -456,7 +474,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// decrypt the challenge
|
||||
var ct2 = authSessionKey.encryptedAuthSessionKey;
|
||||
var ct2 = authSessionKey.encryptedChallenge;
|
||||
self._pgp.decrypt(ct2, serverPubkey.publicKey, function(err, decryptedChallenge) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
@ -502,12 +520,14 @@ define(function(require) {
|
||||
});
|
||||
}
|
||||
|
||||
function replyChallenge(encryptedChallenge, sessionKey) {
|
||||
function replyChallenge(response, sessionKey) {
|
||||
// respond to challenge by uploading the with the session key encrypted challenge
|
||||
self._privateKeyDao.verifyAuthentication({
|
||||
userId: userId,
|
||||
sessionId: sessionId,
|
||||
encryptedChallenge: encryptedChallenge
|
||||
encryptedChallenge: response.encryptedChallenge,
|
||||
encryptedDeviceSecret: response.encryptedDeviceSecret,
|
||||
iv: response.iv
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
@ -565,7 +585,7 @@ define(function(require) {
|
||||
var privkeyId = keypair.privateKey._id,
|
||||
pgpBlock = keypair.privateKey.encryptedKey;
|
||||
|
||||
// encrypt the private key with the derived key (AES-GCM authenticated encryption)
|
||||
// encrypt the private key with the derived key
|
||||
var iv = util.random(config.symIvSize);
|
||||
self._crypto.encrypt(pgpBlock, encryptionKey, iv, function(err, ct) {
|
||||
if (err) {
|
||||
@ -575,6 +595,7 @@ define(function(require) {
|
||||
|
||||
var payload = {
|
||||
_id: privkeyId,
|
||||
userId: options.userId,
|
||||
encryptedPrivateKey: ct,
|
||||
salt: salt,
|
||||
iv: iv
|
||||
@ -617,13 +638,12 @@ define(function(require) {
|
||||
|
||||
/**
|
||||
* Request downloading the user's encrypted private key. This will initiate the server to send the recovery token via email/sms to the user.
|
||||
* @param {String} userId The user's email address
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {String} options.keyId The private PGP key id
|
||||
* @param {Function} callback(error)
|
||||
*/
|
||||
KeychainDAO.prototype.requestPrivateKeyDownload = function(userId, callback) {
|
||||
this._privateKeyDao.requestDownload({
|
||||
userId: userId
|
||||
}, callback);
|
||||
KeychainDAO.prototype.requestPrivateKeyDownload = function(options, callback) {
|
||||
this._privateKeyDao.requestDownload(options, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -639,21 +659,21 @@ define(function(require) {
|
||||
|
||||
/**
|
||||
* This is called after the encrypted private key has successfully been downloaded and it's ready to be decrypted and stored in localstorage.
|
||||
* @param {String} options._id The private PGP key id
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {String} options.keyId The user's email address
|
||||
* @param {String} options.code The randomly generated or self selected code used to derive the key for the decryption of the private PGP key
|
||||
* @param {String} options.encryptedPrivkey The encrypted private PGP key
|
||||
* @param {String} options.encryptedPrivateKey The encrypted private PGP key
|
||||
* @param {String} options.salt The salt required to derive the code derived key
|
||||
* @param {String} options.iv The iv used to encrypt the private PGP key
|
||||
* @param {Function} callback(error)
|
||||
* @param {Function} callback(error, keyObject)
|
||||
*/
|
||||
KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(options, callback) {
|
||||
var self = this,
|
||||
code = options.code,
|
||||
salt = options.salt,
|
||||
keySize = config.keySize;
|
||||
keySize = config.symKeySize;
|
||||
|
||||
if (!options.keyId || !options.userId) {
|
||||
if (!options._id || !options.userId || !options.code || !options.salt || !options.encryptedPrivateKey || !options.iv) {
|
||||
callback(new Error('Incomplete arguments!'));
|
||||
return;
|
||||
}
|
||||
@ -670,21 +690,44 @@ define(function(require) {
|
||||
|
||||
function decryptAndStore(derivedKey) {
|
||||
// decrypt the private key with the derived key
|
||||
var pt = options.encryptedPrivkey,
|
||||
var ct = options.encryptedPrivateKey,
|
||||
iv = options.iv;
|
||||
|
||||
self._crypto.decrypt(pt, derivedKey, iv, function(err, pgpBlock) {
|
||||
self._crypto.decrypt(ct, derivedKey, iv, function(err, privateKeyArmored) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
callback(new Error('Invalid keychain code!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// store private key locally
|
||||
self.saveLocalPrivateKey({
|
||||
_id: options.keyId,
|
||||
// validate pgp key
|
||||
var keyParams;
|
||||
try {
|
||||
keyParams = self._pgp.getKeyParams(privateKeyArmored);
|
||||
} catch (e) {
|
||||
callback(new Error('Error parsing private PGP key!'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyParams._id !== options._id || keyParams.userId !== options.userId) {
|
||||
callback(new Error('Private key parameters don\'t match with public key\'s!'));
|
||||
return;
|
||||
}
|
||||
|
||||
var keyObject = {
|
||||
_id: options._id,
|
||||
userId: options.userId,
|
||||
encryptedKey: pgpBlock
|
||||
}, callback);
|
||||
encryptedKey: privateKeyArmored
|
||||
};
|
||||
|
||||
// store private key locally
|
||||
self.saveLocalPrivateKey(keyObject, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, keyObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -30,21 +30,22 @@ define(function() {
|
||||
|
||||
/**
|
||||
* Authenticate device registration by uploading the deviceSecret encrypted with the regSessionKeys.
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {String} options.deviceName The device's memorable name
|
||||
* @param {Object} options.encryptedDeviceSecret {encryptedDeviceSecret:[base64 encoded]}
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {String} options.deviceName The device's memorable name
|
||||
* @param {String} options.encryptedDeviceSecret The base64 encoded encrypted device secret
|
||||
* @param {String} options.iv The iv used for encryption
|
||||
* @param {Function} callback(error)
|
||||
*/
|
||||
PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
|
||||
var uri;
|
||||
|
||||
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret) {
|
||||
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) {
|
||||
callback(new Error('Incomplete arguments!'));
|
||||
return;
|
||||
}
|
||||
|
||||
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
|
||||
this._restDao.put(options.encryptedDeviceSecret, uri, callback);
|
||||
this._restDao.put(options, uri, callback);
|
||||
};
|
||||
|
||||
//
|
||||
@ -55,9 +56,9 @@ define(function() {
|
||||
* Request authSessionKeys required for upload the encrypted private PGP key.
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {Function} callback(error, authSessionKey)
|
||||
* @return {Object} {sessionId, encryptedAuthSessionKeys:[base64 encoded], encryptedChallenge:[base64 encoded]}
|
||||
* @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]}
|
||||
*/
|
||||
PrivateKeyDAO.prototype.requestAuthSessionKeys = function(options, callback) {
|
||||
PrivateKeyDAO.prototype.requestAuthSessionKey = function(options, callback) {
|
||||
var uri;
|
||||
|
||||
if (!options.userId) {
|
||||
@ -71,44 +72,50 @@ define(function() {
|
||||
|
||||
/**
|
||||
* Verifiy authentication by uploading the challenge and deviceSecret encrypted with the authSessionKeys as a response.
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {Object} options.encryptedChallenge The server's challenge encrypted using the authSessionKey {encryptedChallenge:[base64 encoded], encryptedDeviceSecret:[base64 encoded], iv}
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {String} options.encryptedChallenge The server's base64 encoded challenge encrypted using the authSessionKey
|
||||
* @param {String} options.encryptedDeviceSecret The server's base64 encoded deviceSecret encrypted using the authSessionKey
|
||||
* @param {String} options.iv The iv used for encryption
|
||||
* @param {Function} callback(error)
|
||||
*/
|
||||
PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
|
||||
var uri;
|
||||
|
||||
if (!options.userId || !options.sessionId || !options.encryptedChallenge) {
|
||||
if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) {
|
||||
callback(new Error('Incomplete arguments!'));
|
||||
return;
|
||||
}
|
||||
|
||||
uri = '/auth/user/' + options.userId + '/session/' + options.sessionId;
|
||||
this._restDao.put(options.encryptedChallenge, uri, callback);
|
||||
this._restDao.put(options, uri, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload the encrypted private PGP key.
|
||||
* @param {String} options.encryptedPrivateKey {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], sessionId: [base64 encoded]}
|
||||
* @param {String} options._id The hex encoded capital 16 char key id
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {String} options.encryptedPrivateKey The base64 encoded encrypted private PGP key
|
||||
* @param {String} options.sessionId The session id
|
||||
* @param {Function} callback(error)
|
||||
*/
|
||||
PrivateKeyDAO.prototype.upload = function(options, callback) {
|
||||
var uri,
|
||||
key = options.encryptedPrivateKey;
|
||||
var uri;
|
||||
|
||||
if (!options.userId || !key || !key._id) {
|
||||
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) {
|
||||
callback(new Error('Incomplete arguments!'));
|
||||
return;
|
||||
}
|
||||
|
||||
uri = '/privatekey/user/' + options.userId + '/key/' + key._id;
|
||||
this._restDao.post(key, uri, callback);
|
||||
uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId;
|
||||
this._restDao.post(options, uri, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Request download for the encrypted private PGP key.
|
||||
* @param {[type]} options.userId The user's email address
|
||||
* @param {Function} callback(error)
|
||||
* @param {String} options.userId The user's email address
|
||||
* @param {String} options.keyId The private PGP key id
|
||||
* @param {Function} callback(error, found)
|
||||
* @return {Boolean} weather the key was found on the server or not.
|
||||
*/
|
||||
PrivateKeyDAO.prototype.requestDownload = function(options, callback) {
|
||||
var uri;
|
||||
@ -121,7 +128,20 @@ define(function() {
|
||||
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId;
|
||||
this._restDao.get({
|
||||
uri: uri
|
||||
}, callback);
|
||||
}, function(err) {
|
||||
// 404: there is no encrypted private key on the server
|
||||
if (err && err.code !== 200) {
|
||||
callback(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, true);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -32,6 +32,7 @@
|
||||
@import "views/shared";
|
||||
@import "views/add-account";
|
||||
@import "views/account";
|
||||
@import "views/privatekey-upload";
|
||||
@import "views/set-passphrase";
|
||||
@import "views/contacts";
|
||||
@import "views/about";
|
||||
|
@ -1,8 +1,12 @@
|
||||
.view-account {
|
||||
|
||||
a {
|
||||
color: $color-blue;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 50px auto 60px auto;
|
||||
|
||||
|
||||
td {
|
||||
padding-top: 15px;
|
||||
|
||||
|
@ -119,4 +119,15 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-login-privatekey-download {
|
||||
.content {
|
||||
max-width: 500px;
|
||||
|
||||
input.code {
|
||||
margin-right: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
17
src/sass/views/_privatekey-upload.scss
Normal file
17
src/sass/views/_privatekey-upload.scss
Normal file
@ -0,0 +1,17 @@
|
||||
.view-privatekey-upload {
|
||||
|
||||
a {
|
||||
color: $color-blue;
|
||||
}
|
||||
|
||||
.step {
|
||||
margin: 20px 0;
|
||||
|
||||
.working {
|
||||
text-align: center;
|
||||
font-size: 30px;
|
||||
margin: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PGP Key ID</td>
|
||||
<td>{{keyId}}</td>
|
||||
<td>{{keyId}} (<a href="https://whiteout.io/revocation.html" title="Click here to reset your account." target="_blank">Revoke key</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PGP Fingerprint</td>
|
||||
|
@ -31,6 +31,10 @@
|
||||
<div class="lightbox" ng-include="'tpl/set-passphrase.html'"></div>
|
||||
</div>
|
||||
|
||||
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'privatekey-upload'}">
|
||||
<div class="lightbox" ng-include="'tpl/privatekey-upload.html'"></div>
|
||||
</div>
|
||||
|
||||
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'contacts'}">
|
||||
<div class="lightbox" ng-include="'tpl/contacts.html'"></div>
|
||||
</div>
|
||||
|
41
src/tpl/login-privatekey-download.html
Normal file
41
src/tpl/login-privatekey-download.html
Normal file
@ -0,0 +1,41 @@
|
||||
<div class="view-login view-login-privatekey-download">
|
||||
<div class="logo">
|
||||
<img src="img/whiteout_logo.svg" alt="whiteout.io">
|
||||
</div><!--/logo-->
|
||||
|
||||
<div class="content">
|
||||
|
||||
<div class="step" ng-show="step === 1">
|
||||
<p><b>Key sync.</b> We have sent you an email containing a recovery token. Please copy and paste the identifier below.</p>
|
||||
|
||||
<input type="text" class="input-text" size="42" ng-model="recoveryToken" placeholder="Recovery token" focus-me="step === 1">
|
||||
</div>
|
||||
|
||||
<div class="step" ng-show="step === 2">
|
||||
<p><b>Key sync.</b> Please enter the keychain code you wrote down during sync setup.</p>
|
||||
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code0" focus-me="step === 2"> -
|
||||
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code1"> -
|
||||
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code2"> -
|
||||
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code3"> -
|
||||
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code4"> -
|
||||
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code5">
|
||||
<!--<a href="https://whiteout.io/revocation.html" title="Click here to reset your account." target="_blank">Lost your keychain code?</a>-->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="btn" ng-click="goForward()">Continue</button>
|
||||
</div>
|
||||
|
||||
</div><!--/content-->
|
||||
</div>
|
||||
|
||||
<!-- popovers -->
|
||||
<div id="passphrase-info" class="popover right" ng-controller="PopoverCtrl">
|
||||
<div class="arrow"></div>
|
||||
<div class="popover-title"><b>What is this?</b></div>
|
||||
<div class="popover-content">
|
||||
<p>A passphrase is like a password that protects your PGP key.</p>
|
||||
<p>There is no way to access your messages without your passphrase.</p>
|
||||
<p>If you have forgotten your passphrase, please request an account reset by sending an email to <b>support@whiteout.io</b>. You will not be able to read previous messages after a reset.</p>
|
||||
</div>
|
||||
</div><!--/.popover-->
|
@ -17,6 +17,7 @@
|
||||
<ul class="nav-secondary">
|
||||
<li><a href="#" ng-click="state.account.toggle(true); $event.preventDefault()">Account</a></li>
|
||||
<li><a href="#" ng-click="state.contacts.toggle(true); $event.preventDefault()">Contacts</a></li>
|
||||
<li><a href="#" ng-click="state.privateKeyUpload.toggle(true); $event.preventDefault()">Key sync (experimental)</a></li>
|
||||
<li><a href="#" ng-click="state.about.toggle(true); $event.preventDefault()">About</a></li>
|
||||
</ul>
|
||||
|
||||
|
46
src/tpl/privatekey-upload.html
Normal file
46
src/tpl/privatekey-upload.html
Normal file
@ -0,0 +1,46 @@
|
||||
<div class="lightbox-body" ng-controller="PrivateKeyUploadCtrl">
|
||||
<header>
|
||||
<h2>Setup Key Sync</h2>
|
||||
<button class="close" ng-click="state.privateKeyUpload.toggle(false)" data-action="lightbox-close"></button>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<div class="dialog view-privatekey-upload">
|
||||
|
||||
<div class="step" ng-show="step === 1">
|
||||
<p>Your keychain code can be used to securely backup and sync your PGP key between devices. This feature is experimental and not recommended for production use. <a href="" target="_blank">Learn more</a>.</p>
|
||||
|
||||
<h2>{{displayedCode}}</h2>
|
||||
|
||||
<p>Please write down your keychain code and keep it in a safe place. Whiteout Networks cannot recover a lost code.</p>
|
||||
</div>
|
||||
|
||||
<div class="step" ng-show="step === 2">
|
||||
<p>Please confirm the keychain code you have written down.</p>
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code0" focus-me="step === 2"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code1"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code2"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code3"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code4"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code5">
|
||||
</div>
|
||||
|
||||
<div class="step" ng-show="step === 3">
|
||||
<p>Please enter a memorable name for this device e.g. "MacBook Work".</p>
|
||||
<input type="text" class="input-text" ng-model="deviceName" placeholder="Device name" focus-me="step === 3">
|
||||
</div>
|
||||
|
||||
<div class="step" ng-show="step === 4">
|
||||
<div class="working">
|
||||
<span class="spinner"></span>
|
||||
</div><!--/.working-->
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button ng-show="step > 1 && step < 4" class="btn btn-alt" ng-click="goBack()">Go back</button>
|
||||
<button ng-show="step < 4" class="btn" ng-click="goForward()">Continue</button>
|
||||
</div>
|
||||
|
||||
</div><!-- /.view-privatekey-upload -->
|
||||
</div><!-- /.content -->
|
||||
</div><!-- /.lightbox-body -->
|
@ -14,18 +14,18 @@ define(function(require) {
|
||||
var scope, accountCtrl,
|
||||
dummyFingerprint, expectedFingerprint,
|
||||
dummyKeyId, expectedKeyId,
|
||||
emailAddress, keySize, cryptoMock, keychainMock;
|
||||
emailAddress, keySize, pgpMock, keychainMock;
|
||||
|
||||
beforeEach(function() {
|
||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
||||
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
|
||||
expectedFingerprint = '3A2D 39B4 E140 4190 B8B9 49DE 7D7E 9903 6E71 2926';
|
||||
dummyKeyId = '9FEB47936E712926';
|
||||
expectedKeyId = '6E712926';
|
||||
cryptoMock.getFingerprint.returns(dummyFingerprint);
|
||||
cryptoMock.getKeyId.returns(dummyKeyId);
|
||||
pgpMock.getFingerprint.returns(dummyFingerprint);
|
||||
pgpMock.getKeyId.returns(dummyKeyId);
|
||||
emailAddress = 'fred@foo.com';
|
||||
keySize = 1234;
|
||||
appController._emailDao = {
|
||||
@ -34,7 +34,7 @@ define(function(require) {
|
||||
asymKeySize: keySize
|
||||
}
|
||||
};
|
||||
cryptoMock.getKeyParams.returns({
|
||||
pgpMock.getKeyParams.returns({
|
||||
_id: dummyKeyId,
|
||||
fingerprint: dummyFingerprint,
|
||||
userId: emailAddress,
|
||||
|
@ -37,7 +37,7 @@ define(function(require) {
|
||||
expect(controller._userStorage).to.exist;
|
||||
expect(controller._invitationDao).to.exist;
|
||||
expect(controller._keychain).to.exist;
|
||||
expect(controller._crypto).to.exist;
|
||||
expect(controller._pgp).to.exist;
|
||||
expect(controller._pgpbuilder).to.exist;
|
||||
expect(controller._emailDao).to.exist;
|
||||
expect(controller._outboxBo).to.exist;
|
||||
|
@ -12,11 +12,11 @@ define(function(require) {
|
||||
describe('Contacts Controller unit test', function() {
|
||||
var scope, contactsCtrl,
|
||||
origKeychain, keychainMock,
|
||||
origCrypto, cryptoMock;
|
||||
origPgp, pgpMock;
|
||||
|
||||
beforeEach(function() {
|
||||
origCrypto = appController._crypto;
|
||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
||||
origPgp = appController._pgp;
|
||||
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
|
||||
origKeychain = appController._keychain;
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
@ -33,7 +33,7 @@ define(function(require) {
|
||||
|
||||
afterEach(function() {
|
||||
// restore the module
|
||||
appController._crypto = origCrypto;
|
||||
appController._pgp = origPgp;
|
||||
appController._keychain = origKeychain;
|
||||
});
|
||||
|
||||
@ -60,7 +60,7 @@ define(function(require) {
|
||||
keychainMock.listLocalPublicKeys.yields(null, [{
|
||||
_id: '12345'
|
||||
}]);
|
||||
cryptoMock.getKeyParams.returns({
|
||||
pgpMock.getKeyParams.returns({
|
||||
fingerprint: 'asdf'
|
||||
});
|
||||
|
||||
@ -92,7 +92,7 @@ define(function(require) {
|
||||
it('should work', function(done) {
|
||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||
|
||||
cryptoMock.getKeyParams.returns({
|
||||
pgpMock.getKeyParams.returns({
|
||||
_id: '12345',
|
||||
userId: 'max@example.com',
|
||||
userIds: []
|
||||
@ -127,7 +127,7 @@ define(function(require) {
|
||||
it('should fail due to error in pgp.getKeyParams', function(done) {
|
||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||
|
||||
cryptoMock.getKeyParams.throws(new Error('WAT'));
|
||||
pgpMock.getKeyParams.throws(new Error('WAT'));
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
@ -140,7 +140,7 @@ define(function(require) {
|
||||
it('should fail due to error in keychain.saveLocalPublicKey', function(done) {
|
||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||
|
||||
cryptoMock.getKeyParams.returns({
|
||||
pgpMock.getKeyParams.returns({
|
||||
_id: '12345',
|
||||
userId: 'max@example.com'
|
||||
});
|
||||
|
@ -114,7 +114,7 @@ define(function(require) {
|
||||
// check configuration
|
||||
//
|
||||
expect(dao._keychain).to.equal(keychainStub);
|
||||
expect(dao._crypto).to.equal(pgpStub);
|
||||
expect(dao._pgp).to.equal(pgpStub);
|
||||
expect(dao._devicestorage).to.equal(devicestorageStub);
|
||||
expect(dao._mailreader).to.equal(mailreader);
|
||||
expect(dao._pgpbuilder).to.equal(pgpBuilderStub);
|
||||
|
@ -743,6 +743,26 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when server public key not found', function(done) {
|
||||
getDeviceNameStub.yields(null, 'iPhone');
|
||||
|
||||
privkeyDaoStub.requestDeviceRegistration.withArgs({
|
||||
userId: testUser,
|
||||
deviceName: 'iPhone'
|
||||
}).yields(null, {
|
||||
encryptedRegSessionKey: 'asdf'
|
||||
});
|
||||
|
||||
lookupPublicKeyStub.yields();
|
||||
|
||||
keychainDao.registerDevice({
|
||||
userId: testUser
|
||||
}, function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail in decrypt', function(done) {
|
||||
getDeviceNameStub.yields(null, 'iPhone');
|
||||
|
||||
@ -753,7 +773,9 @@ define(function(require) {
|
||||
encryptedRegSessionKey: 'asdf'
|
||||
});
|
||||
|
||||
lookupPublicKeyStub.yields(null, 'pubkey');
|
||||
lookupPublicKeyStub.yields(null, {
|
||||
publicKey: 'pubkey'
|
||||
});
|
||||
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(42);
|
||||
|
||||
keychainDao.registerDevice({
|
||||
@ -774,7 +796,9 @@ define(function(require) {
|
||||
encryptedRegSessionKey: 'asdf'
|
||||
});
|
||||
|
||||
lookupPublicKeyStub.yields(null, 'pubkey');
|
||||
lookupPublicKeyStub.yields(null, {
|
||||
publicKey: 'pubkey'
|
||||
});
|
||||
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
||||
getDeviceSecretStub.yields(42);
|
||||
|
||||
@ -796,7 +820,9 @@ define(function(require) {
|
||||
encryptedRegSessionKey: 'asdf'
|
||||
});
|
||||
|
||||
lookupPublicKeyStub.yields(null, 'pubkey');
|
||||
lookupPublicKeyStub.yields(null, {
|
||||
publicKey: 'pubkey'
|
||||
});
|
||||
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
||||
getDeviceSecretStub.yields(null, 'secret');
|
||||
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(42);
|
||||
@ -819,7 +845,9 @@ define(function(require) {
|
||||
encryptedRegSessionKey: 'asdf'
|
||||
});
|
||||
|
||||
lookupPublicKeyStub.yields(null, 'pubkey');
|
||||
lookupPublicKeyStub.yields(null, {
|
||||
publicKey: 'pubkey'
|
||||
});
|
||||
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
||||
getDeviceSecretStub.yields(null, 'secret');
|
||||
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(null, 'encryptedDeviceSecret');
|
||||
@ -847,8 +875,8 @@ define(function(require) {
|
||||
getDeviceSecretStub.restore();
|
||||
});
|
||||
|
||||
it('should fail due to privkeyDao.requestAuthSessionKeys', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKeys.withArgs({
|
||||
it('should fail due to privkeyDao.requestAuthSessionKey', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKey.withArgs({
|
||||
userId: testUser
|
||||
}).yields(42);
|
||||
|
||||
@ -859,8 +887,8 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to privkeyDao.requestAuthSessionKeys response', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {});
|
||||
it('should fail due to privkeyDao.requestAuthSessionKey response', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKey.yields(null, {});
|
||||
|
||||
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
|
||||
expect(err).to.exist;
|
||||
@ -870,7 +898,7 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should fail due to lookupPublicKey', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
||||
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||
encryptedChallenge: 'encryptedChallenge',
|
||||
sessionId: 'sessionId'
|
||||
@ -886,7 +914,7 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should fail due to pgp.decrypt', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
||||
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||
encryptedChallenge: 'encryptedChallenge',
|
||||
sessionId: 'sessionId'
|
||||
@ -906,7 +934,7 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should fail due to getDeviceSecret', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
||||
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||
encryptedChallenge: 'encryptedChallenge',
|
||||
sessionId: 'sessionId'
|
||||
@ -927,7 +955,7 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should fail due to crypto.encrypt', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
||||
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||
encryptedChallenge: 'encryptedChallenge',
|
||||
sessionId: 'sessionId'
|
||||
@ -949,7 +977,7 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should fail due to privkeyDao.verifyAuthentication', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
||||
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||
encryptedChallenge: 'encryptedChallenge',
|
||||
sessionId: 'sessionId'
|
||||
@ -971,15 +999,36 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to server public key nto found', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||
encryptedChallenge: 'encryptedChallenge',
|
||||
sessionId: 'sessionId'
|
||||
});
|
||||
|
||||
lookupPublicKeyStub.yields();
|
||||
|
||||
pgpStub.decrypt.yields(null, 'decryptedStuff');
|
||||
getDeviceSecretStub.yields(null, 'deviceSecret');
|
||||
cryptoStub.encrypt.yields(null, 'encryptedStuff');
|
||||
privkeyDaoStub.verifyAuthentication.yields();
|
||||
|
||||
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
|
||||
expect(err).to.exist;
|
||||
expect(authSessionKey).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
||||
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||
encryptedChallenge: 'encryptedChallenge',
|
||||
sessionId: 'sessionId'
|
||||
});
|
||||
|
||||
lookupPublicKeyStub.yields(null, {
|
||||
publickKey: 'publicKey'
|
||||
publicKey: 'publicKey'
|
||||
});
|
||||
|
||||
pgpStub.decrypt.yields(null, 'decryptedStuff');
|
||||
@ -1152,10 +1201,12 @@ define(function(require) {
|
||||
|
||||
describe('requestPrivateKeyDownload', function() {
|
||||
it('should work', function(done) {
|
||||
privkeyDaoStub.requestDownload.withArgs({
|
||||
var options = {
|
||||
userId: testUser
|
||||
}).yields();
|
||||
keychainDao.requestPrivateKeyDownload(testUser, done);
|
||||
};
|
||||
|
||||
privkeyDaoStub.requestDownload.withArgs(options).yields();
|
||||
keychainDao.requestPrivateKeyDownload(options, done);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1171,9 +1222,18 @@ define(function(require) {
|
||||
});
|
||||
|
||||
describe('decryptAndStorePrivateKeyLocally', function() {
|
||||
var saveLocalPrivateKeyStub;
|
||||
var saveLocalPrivateKeyStub, testData;
|
||||
|
||||
beforeEach(function() {
|
||||
testData = {
|
||||
_id: 'keyId',
|
||||
userId: testUser,
|
||||
encryptedPrivateKey: 'encryptedPrivateKey',
|
||||
code: 'code',
|
||||
salt: 'salt',
|
||||
iv: 'iv'
|
||||
};
|
||||
|
||||
saveLocalPrivateKeyStub = sinon.stub(keychainDao, 'saveLocalPrivateKey');
|
||||
});
|
||||
afterEach(function() {
|
||||
@ -1190,10 +1250,7 @@ define(function(require) {
|
||||
it('should fail due to crypto.deriveKey', function(done) {
|
||||
cryptoStub.deriveKey.yields(42);
|
||||
|
||||
keychainDao.decryptAndStorePrivateKeyLocally({
|
||||
userId: testUser,
|
||||
keyId: 'keyId'
|
||||
}, function(err) {
|
||||
keychainDao.decryptAndStorePrivateKeyLocally(testData, function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
||||
done();
|
||||
@ -1204,10 +1261,7 @@ define(function(require) {
|
||||
cryptoStub.deriveKey.yields(null, 'derivedKey');
|
||||
cryptoStub.decrypt.yields(42);
|
||||
|
||||
keychainDao.decryptAndStorePrivateKeyLocally({
|
||||
userId: testUser,
|
||||
keyId: 'keyId'
|
||||
}, function(err) {
|
||||
keychainDao.decryptAndStorePrivateKeyLocally(testData, function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
||||
expect(cryptoStub.decrypt.calledOnce).to.be.true;
|
||||
@ -1215,18 +1269,52 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
it('should fail due to pgp.getKeyParams', function(done) {
|
||||
cryptoStub.deriveKey.yields(null, 'derivedKey');
|
||||
cryptoStub.decrypt.yields(null, 'pgpBlock');
|
||||
saveLocalPrivateKeyStub.yields();
|
||||
cryptoStub.decrypt.yields(null, 'privateKeyArmored');
|
||||
pgpStub.getKeyParams.throws(new Error());
|
||||
|
||||
keychainDao.decryptAndStorePrivateKeyLocally({
|
||||
userId: testUser,
|
||||
keyId: 'keyId'
|
||||
}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
keychainDao.decryptAndStorePrivateKeyLocally(testData, function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
||||
expect(cryptoStub.decrypt.calledOnce).to.be.true;
|
||||
expect(pgpStub.getKeyParams.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to saveLocalPrivateKey', function(done) {
|
||||
cryptoStub.deriveKey.yields(null, 'derivedKey');
|
||||
cryptoStub.decrypt.yields(null, 'privateKeyArmored');
|
||||
pgpStub.getKeyParams.returns(testData);
|
||||
saveLocalPrivateKeyStub.yields(42);
|
||||
|
||||
keychainDao.decryptAndStorePrivateKeyLocally(testData, function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
||||
expect(cryptoStub.decrypt.calledOnce).to.be.true;
|
||||
expect(pgpStub.getKeyParams.calledOnce).to.be.true;
|
||||
expect(saveLocalPrivateKeyStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
cryptoStub.deriveKey.yields(null, 'derivedKey');
|
||||
cryptoStub.decrypt.yields(null, 'privateKeyArmored');
|
||||
pgpStub.getKeyParams.returns(testData);
|
||||
saveLocalPrivateKeyStub.yields();
|
||||
|
||||
keychainDao.decryptAndStorePrivateKeyLocally(testData, function(err, keyObject) {
|
||||
expect(err).to.not.exist;
|
||||
expect(keyObject).to.deep.equal({
|
||||
_id: 'keyId',
|
||||
userId: testUser,
|
||||
encryptedKey: 'privateKeyArmored'
|
||||
});
|
||||
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
||||
expect(cryptoStub.decrypt.calledOnce).to.be.true;
|
||||
expect(pgpStub.getKeyParams.calledOnce).to.be.true;
|
||||
expect(saveLocalPrivateKeyStub.calledOnce).to.be.true;
|
||||
|
||||
done();
|
||||
|
@ -7,10 +7,13 @@ define(function(require) {
|
||||
LoginCtrl = require('js/controller/login'),
|
||||
EmailDAO = require('js/dao/email-dao'),
|
||||
Auth = require('js/bo/auth'),
|
||||
appController = require('js/app-controller');
|
||||
appController = require('js/app-controller'),
|
||||
KeychainDAO = require('js/dao/keychain-dao');
|
||||
|
||||
describe('Login Controller unit test', function() {
|
||||
var scope, location, ctrl, origEmailDao, emailDaoMock,
|
||||
var scope, location, ctrl,
|
||||
origEmailDao, emailDaoMock,
|
||||
origKeychain, keychainMock,
|
||||
emailAddress = 'fred@foo.com',
|
||||
startAppStub,
|
||||
checkForUpdateStub,
|
||||
@ -21,14 +24,16 @@ define(function(require) {
|
||||
var hasChrome, hasIdentity;
|
||||
|
||||
beforeEach(function() {
|
||||
hasChrome = !! window.chrome;
|
||||
hasIdentity = !! window.chrome.identity;
|
||||
hasChrome = !!window.chrome;
|
||||
hasIdentity = !!window.chrome.identity;
|
||||
window.chrome = window.chrome || {};
|
||||
window.chrome.identity = window.chrome.identity || {};
|
||||
|
||||
// remember original module to restore later, then replace it
|
||||
origEmailDao = appController._emailDao;
|
||||
origKeychain = appController._keychain;
|
||||
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
appController._auth = authStub = sinon.createStubInstance(Auth);
|
||||
|
||||
startAppStub = sinon.stub(appController, 'start');
|
||||
@ -48,6 +53,7 @@ define(function(require) {
|
||||
|
||||
// restore the app controller module
|
||||
appController._emailDao = origEmailDao;
|
||||
appController._keychain = origKeychain;
|
||||
appController.start.restore && appController.start.restore();
|
||||
appController.checkForUpdate.restore && appController.checkForUpdate.restore();
|
||||
appController.init.restore && appController.init.restore();
|
||||
@ -128,12 +134,42 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should forward to privatekey download login', function(done) {
|
||||
startAppStub.yields();
|
||||
authStub.getEmailAddress.yields(null, emailAddress);
|
||||
initStub.yields(null, {
|
||||
publicKey: 'b'
|
||||
});
|
||||
keychainMock.requestPrivateKeyDownload.yields(null, {});
|
||||
|
||||
angular.module('logintest', []);
|
||||
mocks.module('logintest');
|
||||
mocks.inject(function($controller, $rootScope, $location) {
|
||||
location = $location;
|
||||
sinon.stub(location, 'path', function(path) {
|
||||
expect(path).to.equal('/login-privatekey-download');
|
||||
expect(startAppStub.calledOnce).to.be.true;
|
||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||
expect(authStub.getEmailAddress.calledOnce).to.be.true;
|
||||
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
ctrl = $controller(LoginCtrl, {
|
||||
$location: location,
|
||||
$scope: scope
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should forward to new device login', function(done) {
|
||||
startAppStub.yields();
|
||||
authStub.getEmailAddress.yields(null, emailAddress);
|
||||
initStub.yields(null, {
|
||||
publicKey: 'b'
|
||||
});
|
||||
keychainMock.requestPrivateKeyDownload.yields();
|
||||
|
||||
angular.module('logintest', []);
|
||||
mocks.module('logintest');
|
||||
@ -144,6 +180,7 @@ define(function(require) {
|
||||
expect(startAppStub.calledOnce).to.be.true;
|
||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||
expect(authStub.getEmailAddress.calledOnce).to.be.true;
|
||||
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
|
235
test/unit/login-privatekey-download-ctrl-test.js
Normal file
235
test/unit/login-privatekey-download-ctrl-test.js
Normal file
@ -0,0 +1,235 @@
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var expect = chai.expect,
|
||||
angular = require('angular'),
|
||||
mocks = require('angularMocks'),
|
||||
LoginPrivateKeyDownloadCtrl = require('js/controller/login-privatekey-download'),
|
||||
EmailDAO = require('js/dao/email-dao'),
|
||||
appController = require('js/app-controller'),
|
||||
KeychainDAO = require('js/dao/keychain-dao');
|
||||
|
||||
describe('Login Private Key Download Controller unit test', function() {
|
||||
var scope, location, ctrl,
|
||||
origEmailDao, emailDaoMock,
|
||||
origKeychain, keychainMock,
|
||||
emailAddress = 'fred@foo.com';
|
||||
|
||||
beforeEach(function(done) {
|
||||
// remember original module to restore later, then replace it
|
||||
origEmailDao = appController._emailDao;
|
||||
origKeychain = appController._keychain;
|
||||
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
emailDaoMock._account = {
|
||||
emailAddress: emailAddress
|
||||
};
|
||||
|
||||
angular.module('login-privatekey-download-test', []);
|
||||
mocks.module('login-privatekey-download-test');
|
||||
mocks.inject(function($controller, $rootScope) {
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
ctrl = $controller(LoginPrivateKeyDownloadCtrl, {
|
||||
$location: location,
|
||||
$scope: scope
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
// restore the app controller module
|
||||
appController._emailDao = origEmailDao;
|
||||
appController._keychain = origKeychain;
|
||||
});
|
||||
|
||||
describe('initialization', function() {
|
||||
it('should work', function() {
|
||||
expect(scope.step).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyRecoveryToken', function() {
|
||||
var testKeypair = {
|
||||
publicKey: {
|
||||
_id: 'id'
|
||||
}
|
||||
};
|
||||
|
||||
it('should fail for empty recovery token', function(done) {
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.recoveryToken = undefined;
|
||||
scope.verifyRecoveryToken();
|
||||
});
|
||||
|
||||
it('should fail in keychain.getUserKeyPair', function(done) {
|
||||
keychainMock.getUserKeyPair.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.recoveryToken = 'token';
|
||||
scope.verifyRecoveryToken();
|
||||
});
|
||||
|
||||
it('should fail in keychain.downloadPrivateKey', function(done) {
|
||||
keychainMock.getUserKeyPair.yields(null, testKeypair);
|
||||
keychainMock.downloadPrivateKey.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(keychainMock.downloadPrivateKey.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.recoveryToken = 'token';
|
||||
scope.verifyRecoveryToken();
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
keychainMock.getUserKeyPair.yields(null, testKeypair);
|
||||
keychainMock.downloadPrivateKey.yields(null, 'encryptedPrivateKey');
|
||||
|
||||
scope.recoveryToken = 'token';
|
||||
scope.verifyRecoveryToken(function() {
|
||||
expect(scope.encryptedPrivateKey).to.equal('encryptedPrivateKey');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('decryptAndStorePrivateKeyLocally', function() {
|
||||
beforeEach(function() {
|
||||
scope.code0 = '0';
|
||||
scope.code1 = '1';
|
||||
scope.code2 = '2';
|
||||
scope.code3 = '3';
|
||||
scope.code4 = '4';
|
||||
scope.code5 = '5';
|
||||
|
||||
scope.encryptedPrivateKey = {
|
||||
encryptedPrivateKey: 'encryptedPrivateKey'
|
||||
};
|
||||
scope.cachedKeypair = {
|
||||
publicKey: {
|
||||
_id: 'keyId'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it('should fail on empty code', function(done) {
|
||||
scope.code0 = '';
|
||||
scope.code1 = '';
|
||||
scope.code2 = '';
|
||||
scope.code3 = '';
|
||||
scope.code4 = '';
|
||||
scope.code5 = '';
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
|
||||
it('should fail on decryptAndStorePrivateKeyLocally', function(done) {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
|
||||
it('should goto /login-existing on emailDao.unlock fail', function(done) {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
|
||||
encryptedKey: 'keyArmored'
|
||||
});
|
||||
emailDaoMock.unlock.yields(42);
|
||||
|
||||
scope.goTo = function(location) {
|
||||
expect(location).to.equal('/login-existing');
|
||||
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
|
||||
it('should goto /desktop on emailDao.unlock success', function(done) {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
|
||||
encryptedKey: 'keyArmored'
|
||||
});
|
||||
emailDaoMock.unlock.yields();
|
||||
|
||||
scope.goTo = function(location) {
|
||||
expect(location).to.equal('/desktop');
|
||||
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
});
|
||||
|
||||
describe('goForward', function() {
|
||||
it('should work in step 1', function() {
|
||||
var verifyRecoveryTokenStub = sinon.stub(scope, 'verifyRecoveryToken');
|
||||
verifyRecoveryTokenStub.yields();
|
||||
scope.step = 1;
|
||||
|
||||
scope.goForward();
|
||||
|
||||
expect(verifyRecoveryTokenStub.calledOnce).to.be.true;
|
||||
expect(scope.step).to.equal(2);
|
||||
verifyRecoveryTokenStub.restore();
|
||||
});
|
||||
it('should work in step 2', function() {
|
||||
var decryptAndStorePrivateKeyLocallyStub = sinon.stub(scope, 'decryptAndStorePrivateKeyLocally');
|
||||
decryptAndStorePrivateKeyLocallyStub.returns();
|
||||
scope.step = 2;
|
||||
|
||||
scope.goForward();
|
||||
|
||||
expect(decryptAndStorePrivateKeyLocallyStub.calledOnce).to.be.true;
|
||||
decryptAndStorePrivateKeyLocallyStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('goTo', function() {
|
||||
it('should work', function(done) {
|
||||
mocks.inject(function($controller, $rootScope, $location) {
|
||||
location = $location;
|
||||
sinon.stub(location, 'path', function(path) {
|
||||
expect(path).to.equal('/desktop');
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
ctrl = $controller(LoginPrivateKeyDownloadCtrl, {
|
||||
$location: location,
|
||||
$scope: scope
|
||||
});
|
||||
});
|
||||
|
||||
scope.goTo('/desktop');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -48,6 +48,8 @@ function startTests() {
|
||||
'test/unit/login-existing-ctrl-test',
|
||||
'test/unit/login-initial-ctrl-test',
|
||||
'test/unit/login-new-device-ctrl-test',
|
||||
'test/unit/login-privatekey-download-ctrl-test',
|
||||
'test/unit/privatekey-upload-ctrl-test',
|
||||
'test/unit/login-ctrl-test',
|
||||
'test/unit/read-ctrl-test',
|
||||
'test/unit/navigation-ctrl-test',
|
||||
|
@ -92,7 +92,7 @@ define(function(require) {
|
||||
publicKeyArmored: pubkey
|
||||
}, function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(err.errMsg).to.equal('Incorrect passphrase!');
|
||||
expect(err.message).to.equal('Incorrect passphrase!');
|
||||
|
||||
pgp.exportKeys(function(err, keys) {
|
||||
expect(err).to.exist;
|
||||
|
@ -57,7 +57,8 @@ define(function(require) {
|
||||
privkeyDao.uploadDeviceSecret({
|
||||
userId: emailAddress,
|
||||
deviceName: deviceName,
|
||||
encryptedDeviceSecret: 'asdf'
|
||||
encryptedDeviceSecret: 'asdf',
|
||||
iv: 'iv'
|
||||
}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
done();
|
||||
@ -65,9 +66,9 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestAuthSessionKeys', function() {
|
||||
describe('requestAuthSessionKey', function() {
|
||||
it('should fail due to invalid args', function(done) {
|
||||
privkeyDao.requestAuthSessionKeys({}, function(err) {
|
||||
privkeyDao.requestAuthSessionKey({}, function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
});
|
||||
@ -76,7 +77,7 @@ define(function(require) {
|
||||
it('should work', function(done) {
|
||||
restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields();
|
||||
|
||||
privkeyDao.requestAuthSessionKeys({
|
||||
privkeyDao.requestAuthSessionKey({
|
||||
userId: emailAddress
|
||||
}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
@ -96,13 +97,17 @@ define(function(require) {
|
||||
it('should work', function(done) {
|
||||
var sessionId = '1';
|
||||
|
||||
restDaoStub.put.withArgs('asdf', '/auth/user/' + emailAddress + '/session/' + sessionId).yields();
|
||||
|
||||
privkeyDao.verifyAuthentication({
|
||||
var options = {
|
||||
userId: emailAddress,
|
||||
sessionId: sessionId,
|
||||
encryptedChallenge: 'asdf'
|
||||
}, function(err) {
|
||||
encryptedChallenge: 'asdf',
|
||||
encryptedDeviceSecret: 'qwer',
|
||||
iv: ' iv'
|
||||
};
|
||||
|
||||
restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).yields();
|
||||
|
||||
privkeyDao.verifyAuthentication(options, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
done();
|
||||
});
|
||||
@ -118,16 +123,18 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
var key = {
|
||||
_id: '12345'
|
||||
var options = {
|
||||
_id: '12345',
|
||||
userId: emailAddress,
|
||||
encryptedPrivateKey: 'asdf',
|
||||
sessionId: '1',
|
||||
salt: 'salt',
|
||||
iv: 'iv'
|
||||
};
|
||||
|
||||
restDaoStub.post.withArgs(key, '/privatekey/user/' + emailAddress + '/key/' + key._id).yields();
|
||||
restDaoStub.post.withArgs(options, '/privatekey/user/' + emailAddress + '/session/' + options.sessionId).yields();
|
||||
|
||||
privkeyDao.upload({
|
||||
userId: emailAddress,
|
||||
encryptedPrivateKey: key
|
||||
}, function(err) {
|
||||
privkeyDao.upload(options, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
done();
|
||||
});
|
||||
|
260
test/unit/privatekey-upload-ctrl-test.js
Normal file
260
test/unit/privatekey-upload-ctrl-test.js
Normal file
@ -0,0 +1,260 @@
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var expect = chai.expect,
|
||||
angular = require('angular'),
|
||||
mocks = require('angularMocks'),
|
||||
PrivateKeyUploadCtrl = require('js/controller/privatekey-upload'),
|
||||
appController = require('js/app-controller'),
|
||||
KeychainDAO = require('js/dao/keychain-dao'),
|
||||
PGP = require('js/crypto/pgp');
|
||||
|
||||
describe('Private Key Upload Controller unit test', function() {
|
||||
var scope, location, ctrl,
|
||||
origEmailDao, emailDaoMock,
|
||||
origKeychain, keychainMock,
|
||||
pgpStub,
|
||||
emailAddress = 'fred@foo.com';
|
||||
|
||||
beforeEach(function(done) {
|
||||
// remember original module to restore later, then replace it
|
||||
origEmailDao = appController._emailDao;
|
||||
appController._emailDao = emailDaoMock = {
|
||||
_account: {
|
||||
emailAddress: emailAddress
|
||||
}
|
||||
};
|
||||
origKeychain = appController._keychain;
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
keychainMock._pgp = pgpStub = sinon.createStubInstance(PGP);
|
||||
|
||||
angular.module('login-privatekey-download-test', []);
|
||||
mocks.module('login-privatekey-download-test');
|
||||
mocks.inject(function($controller, $rootScope) {
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
ctrl = $controller(PrivateKeyUploadCtrl, {
|
||||
$location: location,
|
||||
$scope: scope
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
// restore the app controller module
|
||||
appController._keychain = origKeychain;
|
||||
appController._emailDao = origEmailDao;
|
||||
});
|
||||
|
||||
describe('checkServerForKey', function() {
|
||||
var keyParams = {
|
||||
userId: emailAddress,
|
||||
_id: 'keyId'
|
||||
};
|
||||
|
||||
it('should fail', function(done) {
|
||||
pgpStub.getKeyParams.returns(keyParams);
|
||||
keychainMock.requestPrivateKeyDownload.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.checkServerForKey();
|
||||
});
|
||||
|
||||
it('should return true', function(done) {
|
||||
pgpStub.getKeyParams.returns(keyParams);
|
||||
keychainMock.requestPrivateKeyDownload.yields(null, true);
|
||||
|
||||
scope.checkServerForKey(function(privateKeySynced) {
|
||||
expect(privateKeySynced).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined', function(done) {
|
||||
pgpStub.getKeyParams.returns(keyParams);
|
||||
keychainMock.requestPrivateKeyDownload.yields(null, false);
|
||||
|
||||
scope.checkServerForKey(function(privateKeySynced) {
|
||||
expect(privateKeySynced).to.be.undefined;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('displayUploadUi', function() {
|
||||
it('should work', function() {
|
||||
var generateCodeStub = sinon.stub(scope, 'generateCode');
|
||||
generateCodeStub.returns('asdf');
|
||||
|
||||
scope.displayUploadUi();
|
||||
expect(scope.step).to.equal(1);
|
||||
expect(scope.code).to.equal('asdf');
|
||||
|
||||
generateCodeStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateCode', function() {
|
||||
it('should work', function() {
|
||||
expect(scope.generateCode().length).to.equal(24);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyCode', function() {
|
||||
it('should fail for wrong code', function() {
|
||||
scope.code0 = 'b';
|
||||
scope.code1 = 'b';
|
||||
scope.code2 = 'b';
|
||||
scope.code3 = 'b';
|
||||
scope.code4 = 'b';
|
||||
scope.code5 = 'b';
|
||||
scope.code = 'aaaaaa';
|
||||
|
||||
scope.onError = function() {};
|
||||
expect(scope.verifyCode()).to.be.false;
|
||||
});
|
||||
|
||||
it('should work', function() {
|
||||
scope.code0 = 'a';
|
||||
scope.code1 = 'a';
|
||||
scope.code2 = 'a';
|
||||
scope.code3 = 'a';
|
||||
scope.code4 = 'a';
|
||||
scope.code5 = 'a';
|
||||
scope.code = 'aaaaaa';
|
||||
|
||||
scope.onError = function() {};
|
||||
expect(scope.verifyCode()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDeviceName', function() {
|
||||
it('should work', function(done) {
|
||||
keychainMock.setDeviceName.yields();
|
||||
scope.setDeviceName(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('encryptAndUploadKey', function() {
|
||||
it('should fail due to keychain.registerDevice', function(done) {
|
||||
keychainMock.registerDevice.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.registerDevice.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.encryptAndUploadKey();
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
keychainMock.registerDevice.yields();
|
||||
keychainMock.uploadPrivateKey.yields();
|
||||
|
||||
scope.encryptAndUploadKey(function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(keychainMock.registerDevice.calledOnce).to.be.true;
|
||||
expect(keychainMock.uploadPrivateKey.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('goBack', function() {
|
||||
it('should work', function() {
|
||||
scope.step = 2;
|
||||
scope.goBack();
|
||||
expect(scope.step).to.equal(1);
|
||||
});
|
||||
|
||||
it('should not work for < 2', function() {
|
||||
scope.step = 1;
|
||||
scope.goBack();
|
||||
expect(scope.step).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('goForward', function() {
|
||||
var verifyCodeStub, setDeviceNameStub, encryptAndUploadKeyStub;
|
||||
beforeEach(function() {
|
||||
verifyCodeStub = sinon.stub(scope, 'verifyCode');
|
||||
setDeviceNameStub = sinon.stub(scope, 'setDeviceName');
|
||||
encryptAndUploadKeyStub = sinon.stub(scope, 'encryptAndUploadKey');
|
||||
});
|
||||
afterEach(function() {
|
||||
verifyCodeStub.restore();
|
||||
setDeviceNameStub.restore();
|
||||
encryptAndUploadKeyStub.restore();
|
||||
});
|
||||
|
||||
it('should work for < 2', function() {
|
||||
scope.step = 1;
|
||||
scope.goForward();
|
||||
expect(scope.step).to.equal(2);
|
||||
});
|
||||
|
||||
it('should work for 2', function() {
|
||||
verifyCodeStub.returns(true);
|
||||
scope.step = 2;
|
||||
scope.goForward();
|
||||
expect(scope.step).to.equal(3);
|
||||
});
|
||||
|
||||
it('should not work for 2 when code invalid', function() {
|
||||
verifyCodeStub.returns(false);
|
||||
scope.step = 2;
|
||||
scope.goForward();
|
||||
expect(scope.step).to.equal(2);
|
||||
});
|
||||
|
||||
it('should fail for 3 due to error in setDeviceName', function(done) {
|
||||
scope.step = 3;
|
||||
setDeviceNameStub.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(scope.step).to.equal(3);
|
||||
done();
|
||||
};
|
||||
|
||||
scope.goForward();
|
||||
});
|
||||
|
||||
it('should fail for 3 due to error in encryptAndUploadKey', function(done) {
|
||||
scope.step = 3;
|
||||
setDeviceNameStub.yields();
|
||||
encryptAndUploadKeyStub.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(scope.step).to.equal(4);
|
||||
done();
|
||||
};
|
||||
|
||||
scope.goForward();
|
||||
});
|
||||
|
||||
it('should work for 3', function(done) {
|
||||
scope.step = 3;
|
||||
setDeviceNameStub.yields();
|
||||
encryptAndUploadKeyStub.yields();
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err.title).to.equal('Success');
|
||||
expect(scope.step).to.equal(4);
|
||||
done();
|
||||
};
|
||||
|
||||
scope.goForward();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -16,7 +16,7 @@ define(function(require) {
|
||||
emailAddress, keySize, cryptoMock, keychainMock;
|
||||
|
||||
beforeEach(function() {
|
||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
||||
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
|
||||
|
Loading…
Reference in New Issue
Block a user