mirror of
https://github.com/moparisthebest/mail
synced 2024-11-26 02:42:17 -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 = {
|
app.config = {
|
||||||
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
|
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
|
||||||
|
privkeyServerUrl: 'https://keychain-test.whiteout.io',
|
||||||
|
serverPrivateKeyId: 'EE342F0DDBB0F3BE',
|
||||||
symKeySize: 256,
|
symKeySize: 256,
|
||||||
symIvSize: 96,
|
symIvSize: 96,
|
||||||
asymKeySize: 2048,
|
asymKeySize: 2048,
|
||||||
|
@ -12,6 +12,7 @@ define(function(require) {
|
|||||||
OutboxBO = require('js/bo/outbox'),
|
OutboxBO = require('js/bo/outbox'),
|
||||||
mailreader = require('mailreader'),
|
mailreader = require('mailreader'),
|
||||||
ImapClient = require('imap-client'),
|
ImapClient = require('imap-client'),
|
||||||
|
Crypto = require('js/crypto/crypto'),
|
||||||
RestDAO = require('js/dao/rest-dao'),
|
RestDAO = require('js/dao/rest-dao'),
|
||||||
EmailDAO = require('js/dao/email-dao'),
|
EmailDAO = require('js/dao/email-dao'),
|
||||||
appConfig = require('js/app-config'),
|
appConfig = require('js/app-config'),
|
||||||
@ -20,6 +21,7 @@ define(function(require) {
|
|||||||
KeychainDAO = require('js/dao/keychain-dao'),
|
KeychainDAO = require('js/dao/keychain-dao'),
|
||||||
PublicKeyDAO = require('js/dao/publickey-dao'),
|
PublicKeyDAO = require('js/dao/publickey-dao'),
|
||||||
LawnchairDAO = require('js/dao/lawnchair-dao'),
|
LawnchairDAO = require('js/dao/lawnchair-dao'),
|
||||||
|
PrivateKeyDAO = require('js/dao/privatekey-dao'),
|
||||||
InvitationDAO = require('js/dao/invitation-dao'),
|
InvitationDAO = require('js/dao/invitation-dao'),
|
||||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||||
UpdateHandler = require('js/util/update/update-handler');
|
UpdateHandler = require('js/util/update/update-handler');
|
||||||
@ -55,7 +57,7 @@ define(function(require) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.buildModules = function(options) {
|
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
|
// start the mailreader's worker thread
|
||||||
mailreader.startWorker(config.workerPath + '/../lib/mailreader-parser-worker.js');
|
mailreader.startWorker(config.workerPath + '/../lib/mailreader-parser-worker.js');
|
||||||
@ -64,9 +66,12 @@ define(function(require) {
|
|||||||
restDao = new RestDAO();
|
restDao = new RestDAO();
|
||||||
lawnchairDao = new LawnchairDAO();
|
lawnchairDao = new LawnchairDAO();
|
||||||
pubkeyDao = new PublicKeyDAO(restDao);
|
pubkeyDao = new PublicKeyDAO(restDao);
|
||||||
|
privkeyDao = new PrivateKeyDAO(new RestDAO(config.privkeyServerUrl));
|
||||||
oauth = new OAuth(new RestDAO('https://www.googleapis.com'));
|
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) {
|
keychain.requestPermissionForKeyUpdate = function(params, callback) {
|
||||||
var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey;
|
var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey;
|
||||||
message = message.replace('{0}', params.userId);
|
message = message.replace('{0}', params.userId);
|
||||||
@ -85,7 +90,6 @@ define(function(require) {
|
|||||||
self._auth = new Auth(appConfigStore, oauth, new RestDAO('/ca'));
|
self._auth = new Auth(appConfigStore, oauth, new RestDAO('/ca'));
|
||||||
self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
|
self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
|
||||||
self._invitationDao = new InvitationDAO(restDao);
|
self._invitationDao = new InvitationDAO(restDao);
|
||||||
self._crypto = pgp = new PGP();
|
|
||||||
self._pgpbuilder = pgpbuilder = new PgpBuilder();
|
self._pgpbuilder = pgpbuilder = new PgpBuilder();
|
||||||
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader);
|
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader);
|
||||||
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
|
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
|
||||||
|
@ -8,12 +8,14 @@ requirejs([
|
|||||||
'js/controller/add-account',
|
'js/controller/add-account',
|
||||||
'js/controller/account',
|
'js/controller/account',
|
||||||
'js/controller/set-passphrase',
|
'js/controller/set-passphrase',
|
||||||
|
'js/controller/privatekey-upload',
|
||||||
'js/controller/contacts',
|
'js/controller/contacts',
|
||||||
'js/controller/about',
|
'js/controller/about',
|
||||||
'js/controller/login',
|
'js/controller/login',
|
||||||
'js/controller/login-initial',
|
'js/controller/login-initial',
|
||||||
'js/controller/login-new-device',
|
'js/controller/login-new-device',
|
||||||
'js/controller/login-existing',
|
'js/controller/login-existing',
|
||||||
|
'js/controller/login-privatekey-download',
|
||||||
'js/controller/mail-list',
|
'js/controller/mail-list',
|
||||||
'js/controller/read',
|
'js/controller/read',
|
||||||
'js/controller/write',
|
'js/controller/write',
|
||||||
@ -31,12 +33,14 @@ requirejs([
|
|||||||
AddAccountCtrl,
|
AddAccountCtrl,
|
||||||
AccountCtrl,
|
AccountCtrl,
|
||||||
SetPassphraseCtrl,
|
SetPassphraseCtrl,
|
||||||
|
PrivateKeyUploadCtrl,
|
||||||
ContactsCtrl,
|
ContactsCtrl,
|
||||||
AboutCtrl,
|
AboutCtrl,
|
||||||
LoginCtrl,
|
LoginCtrl,
|
||||||
LoginInitialCtrl,
|
LoginInitialCtrl,
|
||||||
LoginNewDeviceCtrl,
|
LoginNewDeviceCtrl,
|
||||||
LoginExistingCtrl,
|
LoginExistingCtrl,
|
||||||
|
LoginPrivateKeyDownloadCtrl,
|
||||||
MailListCtrl,
|
MailListCtrl,
|
||||||
ReadCtrl,
|
ReadCtrl,
|
||||||
WriteCtrl,
|
WriteCtrl,
|
||||||
@ -89,6 +93,10 @@ requirejs([
|
|||||||
templateUrl: 'tpl/login-new-device.html',
|
templateUrl: 'tpl/login-new-device.html',
|
||||||
controller: LoginNewDeviceCtrl
|
controller: LoginNewDeviceCtrl
|
||||||
});
|
});
|
||||||
|
$routeProvider.when('/login-privatekey-download', {
|
||||||
|
templateUrl: 'tpl/login-privatekey-download.html',
|
||||||
|
controller: LoginPrivateKeyDownloadCtrl
|
||||||
|
});
|
||||||
$routeProvider.when('/desktop', {
|
$routeProvider.when('/desktop', {
|
||||||
templateUrl: 'tpl/desktop.html',
|
templateUrl: 'tpl/desktop.html',
|
||||||
controller: NavigationCtrl
|
controller: NavigationCtrl
|
||||||
@ -113,6 +121,7 @@ requirejs([
|
|||||||
app.controller('MailListCtrl', MailListCtrl);
|
app.controller('MailListCtrl', MailListCtrl);
|
||||||
app.controller('AccountCtrl', AccountCtrl);
|
app.controller('AccountCtrl', AccountCtrl);
|
||||||
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
|
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
|
||||||
|
app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl);
|
||||||
app.controller('ContactsCtrl', ContactsCtrl);
|
app.controller('ContactsCtrl', ContactsCtrl);
|
||||||
app.controller('AboutCtrl', AboutCtrl);
|
app.controller('AboutCtrl', AboutCtrl);
|
||||||
app.controller('DialogCtrl', DialogCtrl);
|
app.controller('DialogCtrl', DialogCtrl);
|
||||||
|
@ -12,7 +12,7 @@ define(function(require) {
|
|||||||
var AccountCtrl = function($scope) {
|
var AccountCtrl = function($scope) {
|
||||||
userId = appController._emailDao._account.emailAddress;
|
userId = appController._emailDao._account.emailAddress;
|
||||||
keychain = appController._keychain;
|
keychain = appController._keychain;
|
||||||
pgp = appController._crypto;
|
pgp = appController._pgp;
|
||||||
|
|
||||||
$scope.state.account = {
|
$scope.state.account = {
|
||||||
toggle: function(to) {
|
toggle: function(to) {
|
||||||
|
@ -12,7 +12,7 @@ define(function(require) {
|
|||||||
|
|
||||||
var ContactsCtrl = function($scope) {
|
var ContactsCtrl = function($scope) {
|
||||||
keychain = appController._keychain,
|
keychain = appController._keychain,
|
||||||
pgp = appController._crypto;
|
pgp = appController._pgp;
|
||||||
|
|
||||||
$scope.state.contacts = {
|
$scope.state.contacts = {
|
||||||
toggle: function(to) {
|
toggle: function(to) {
|
||||||
|
@ -6,7 +6,7 @@ define(function(require) {
|
|||||||
|
|
||||||
var LoginExistingCtrl = function($scope, $location) {
|
var LoginExistingCtrl = function($scope, $location) {
|
||||||
var emailDao = appController._emailDao,
|
var emailDao = appController._emailDao,
|
||||||
pgp = appController._crypto;
|
pgp = appController._pgp;
|
||||||
|
|
||||||
$scope.incorrect = false;
|
$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') {
|
if (typeof availableKeys === 'undefined') {
|
||||||
// no public key available, start onboarding process
|
// no public key available, start onboarding process
|
||||||
goTo('/login-initial');
|
goTo('/login-initial');
|
||||||
} else if (!availableKeys.privateKey) {
|
|
||||||
// no private key, import key
|
} else if (availableKeys && !availableKeys.privateKey) {
|
||||||
goTo('/login-new-device');
|
// 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 {
|
} else {
|
||||||
// public and private key available, try empty passphrase
|
// public and private key available, try empty passphrase
|
||||||
appController._emailDao.unlock({
|
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'),
|
download = require('js/util/download'),
|
||||||
angular = require('angular'),
|
angular = require('angular'),
|
||||||
str = require('js/app-config').string,
|
str = require('js/app-config').string,
|
||||||
emailDao, invitationDao, outbox, crypto, keychain;
|
emailDao, invitationDao, outbox, pgp, keychain;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Controller
|
// Controller
|
||||||
@ -16,7 +16,7 @@ define(function(require) {
|
|||||||
emailDao = appController._emailDao;
|
emailDao = appController._emailDao;
|
||||||
invitationDao = appController._invitationDao;
|
invitationDao = appController._invitationDao;
|
||||||
outbox = appController._outboxBo;
|
outbox = appController._outboxBo;
|
||||||
crypto = appController._crypto;
|
pgp = appController._pgp;
|
||||||
keychain = appController._keychain;
|
keychain = appController._keychain;
|
||||||
|
|
||||||
// set default value so that the popover height is correct on init
|
// set default value so that the popover height is correct on init
|
||||||
@ -47,7 +47,7 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fpr = crypto.getFingerprint(pubkey.publicKey);
|
var fpr = pgp.getFingerprint(pubkey.publicKey);
|
||||||
var formatted = fpr.slice(32);
|
var formatted = fpr.slice(32);
|
||||||
|
|
||||||
$scope.keyId = 'PGP key: ' + formatted;
|
$scope.keyId = 'PGP key: ' + formatted;
|
||||||
|
@ -10,7 +10,7 @@ define(function(require) {
|
|||||||
|
|
||||||
var SetPassphraseCtrl = function($scope) {
|
var SetPassphraseCtrl = function($scope) {
|
||||||
keychain = appController._keychain;
|
keychain = appController._keychain;
|
||||||
pgp = appController._crypto;
|
pgp = appController._pgp;
|
||||||
|
|
||||||
$scope.state.setPassphrase = {
|
$scope.state.setPassphrase = {
|
||||||
toggle: function(to) {
|
toggle: function(to) {
|
||||||
|
@ -7,14 +7,14 @@ define(function(require) {
|
|||||||
aes = require('js/crypto/aes-gcm'),
|
aes = require('js/crypto/aes-gcm'),
|
||||||
util = require('js/crypto/util'),
|
util = require('js/crypto/util'),
|
||||||
str = require('js/app-config').string,
|
str = require('js/app-config').string,
|
||||||
crypto, emailDao, outbox, keychainDao;
|
pgp, emailDao, outbox, keychainDao;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Controller
|
// Controller
|
||||||
//
|
//
|
||||||
|
|
||||||
var WriteCtrl = function($scope, $filter) {
|
var WriteCtrl = function($scope, $filter) {
|
||||||
crypto = appController._crypto;
|
pgp = appController._pgp;
|
||||||
emailDao = appController._emailDao,
|
emailDao = appController._emailDao,
|
||||||
outbox = appController._outboxBo;
|
outbox = appController._outboxBo;
|
||||||
keychainDao = appController._keychain;
|
keychainDao = appController._keychain;
|
||||||
@ -218,7 +218,7 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fpr = crypto.getFingerprint(recipient.key.publicKey);
|
var fpr = pgp.getFingerprint(recipient.key.publicKey);
|
||||||
var formatted = fpr.slice(32);
|
var formatted = fpr.slice(32);
|
||||||
|
|
||||||
$scope.keyId = formatted;
|
$scope.keyId = formatted;
|
||||||
|
@ -7,7 +7,7 @@ define(['forge'], function(forge) {
|
|||||||
var self = {};
|
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} password The password in UTF8
|
||||||
* @param {String} salt The base64 encoded salt
|
* @param {String} salt The base64 encoded salt
|
||||||
* @param {String} keySize The key size in bits
|
* @param {String} keySize The key size in bits
|
||||||
|
@ -20,9 +20,7 @@ define(function(require) {
|
|||||||
var userId, passphrase;
|
var userId, passphrase;
|
||||||
|
|
||||||
if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) {
|
if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) {
|
||||||
callback({
|
callback(new Error('Crypto init failed. Not all options set!'));
|
||||||
errMsg: 'Crypto init failed. Not all options set!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,10 +36,7 @@ define(function(require) {
|
|||||||
|
|
||||||
function onGenerated(err, keys) {
|
function onGenerated(err, keys) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback({
|
callback(new Error('Keygeneration failed!'));
|
||||||
errMsg: 'Keygeneration failed!',
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,9 +141,7 @@ define(function(require) {
|
|||||||
|
|
||||||
// check options
|
// check options
|
||||||
if (!options.privateKeyArmored || !options.publicKeyArmored) {
|
if (!options.privateKeyArmored || !options.publicKeyArmored) {
|
||||||
callback({
|
callback(new Error('Importing keys failed. Not all options set!'));
|
||||||
errMsg: 'Importing keys failed. Not all options set!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,18 +156,14 @@ define(function(require) {
|
|||||||
this._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
|
this._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
resetKeys();
|
resetKeys();
|
||||||
callback({
|
callback(new Error('Importing keys failed. Parsing error!'));
|
||||||
errMsg: 'Importing keys failed. Parsing error!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// decrypt private key with passphrase
|
// decrypt private key with passphrase
|
||||||
if (!this._privateKey.decrypt(options.passphrase)) {
|
if (!this._privateKey.decrypt(options.passphrase)) {
|
||||||
resetKeys();
|
resetKeys();
|
||||||
callback({
|
callback(new Error('Incorrect passphrase!'));
|
||||||
errMsg: 'Incorrect passphrase!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,9 +172,7 @@ define(function(require) {
|
|||||||
privKeyId = this._privateKey.getKeyPacket().getKeyId().toHex();
|
privKeyId = this._privateKey.getKeyPacket().getKeyId().toHex();
|
||||||
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
|
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
|
||||||
resetKeys();
|
resetKeys();
|
||||||
callback({
|
callback(new Error('Key IDs dont match!'));
|
||||||
errMsg: 'Key IDs dont match!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,9 +184,7 @@ define(function(require) {
|
|||||||
*/
|
*/
|
||||||
PGP.prototype.exportKeys = function(callback) {
|
PGP.prototype.exportKeys = function(callback) {
|
||||||
if (!this._publicKey || !this._privateKey) {
|
if (!this._publicKey || !this._privateKey) {
|
||||||
callback({
|
callback(new Error('Could not export keys!'));
|
||||||
errMsg: 'Could not export keys!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,9 +205,7 @@ define(function(require) {
|
|||||||
newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined;
|
newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined;
|
||||||
|
|
||||||
if (!options.privateKeyArmored) {
|
if (!options.privateKeyArmored) {
|
||||||
callback({
|
callback(new Error('Private key must be specified to change passphrase!'));
|
||||||
errMsg: 'Private key must be specified to change passphrase!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,17 +219,13 @@ define(function(require) {
|
|||||||
try {
|
try {
|
||||||
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
|
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback({
|
callback(new Error('Importing key failed. Parsing error!'));
|
||||||
errMsg: 'Importing key failed. Parsing error!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// decrypt private key with passphrase
|
// decrypt private key with passphrase
|
||||||
if (!privKey.decrypt(options.oldPassphrase)) {
|
if (!privKey.decrypt(options.oldPassphrase)) {
|
||||||
callback({
|
callback(new Error('Old passphrase incorrect!'));
|
||||||
errMsg: 'Old passphrase incorrect!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,17 +237,13 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
newKeyArmored = privKey.armor();
|
newKeyArmored = privKey.armor();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback({
|
callback(new Error('Setting new passphrase failed!'));
|
||||||
errMsg: 'Setting new passphrase failed!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if new passphrase really works
|
// check if new passphrase really works
|
||||||
if (!privKey.decrypt(newPassphrase)) {
|
if (!privKey.decrypt(newPassphrase)) {
|
||||||
callback({
|
callback(new Error('Decrypting key with new passphrase failed!'));
|
||||||
errMsg: 'Decrypting key with new passphrase failed!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,9 +258,7 @@ define(function(require) {
|
|||||||
|
|
||||||
// check keys
|
// check keys
|
||||||
if (!this._privateKey || publicKeysArmored.length < 1) {
|
if (!this._privateKey || publicKeysArmored.length < 1) {
|
||||||
callback({
|
callback(new Error('Error encrypting. Keys must be set!'));
|
||||||
errMsg: 'Error encrypting. Keys must be set!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,10 +268,7 @@ define(function(require) {
|
|||||||
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
|
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
callback({
|
callback(new Error('Error encrypting plaintext!'));
|
||||||
errMsg: 'Error encrypting plaintext!',
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,9 +284,7 @@ define(function(require) {
|
|||||||
|
|
||||||
// check keys
|
// check keys
|
||||||
if (!this._privateKey || !publicKeyArmored) {
|
if (!this._privateKey || !publicKeyArmored) {
|
||||||
callback({
|
callback(new Error('Error decrypting. Keys must be set!'));
|
||||||
errMsg: 'Error decrypting. Keys must be set!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,10 +293,7 @@ define(function(require) {
|
|||||||
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
|
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
|
||||||
message = openpgp.message.readArmored(ciphertext);
|
message = openpgp.message.readArmored(ciphertext);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
callback({
|
callback(new Error('Error decrypting PGP message!'));
|
||||||
errMsg: 'Error decrypting PGP message!',
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,10 +302,7 @@ define(function(require) {
|
|||||||
|
|
||||||
function onDecrypted(err, decrypted) {
|
function onDecrypted(err, decrypted) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback({
|
callback(new Error('Error decrypting PGP message!'));
|
||||||
errMsg: 'Error decrypting PGP message!',
|
|
||||||
err: err
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,9 +314,7 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!signaturesValid) {
|
if (!signaturesValid) {
|
||||||
callback({
|
callback(new Error('Verifying PGP signature failed!'));
|
||||||
errMsg: 'Verifying PGP signature failed!'
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,14 +11,14 @@ define(function(require) {
|
|||||||
* PGP de-/encryption, receiving via IMAP, sending via SMTP, MIME parsing, local db persistence
|
* 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} 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} devicestorage Handles persistence to the local indexed db
|
||||||
* @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages
|
* @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages
|
||||||
* @param {Object} mailreader Parses MIME messages received from IMAP
|
* @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._keychain = keychain;
|
||||||
this._crypto = crypto;
|
this._pgp = pgp;
|
||||||
this._devicestorage = devicestorage;
|
this._devicestorage = devicestorage;
|
||||||
this._pgpbuilder = pgpbuilder;
|
this._pgpbuilder = pgpbuilder;
|
||||||
this._mailreader = mailreader;
|
this._mailreader = mailreader;
|
||||||
@ -105,7 +105,7 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// no keypair for is stored for the user... generate a new one
|
// no keypair for is stored for the user... generate a new one
|
||||||
self._crypto.generateKeys({
|
self._pgp.generateKeys({
|
||||||
emailAddress: self._account.emailAddress,
|
emailAddress: self._account.emailAddress,
|
||||||
keySize: self._account.asymKeySize,
|
keySize: self._account.asymKeySize,
|
||||||
passphrase: options.passphrase
|
passphrase: options.passphrase
|
||||||
@ -121,8 +121,8 @@ define(function(require) {
|
|||||||
function handleExistingKeypair(keypair) {
|
function handleExistingKeypair(keypair) {
|
||||||
var privKeyParams, pubKeyParams;
|
var privKeyParams, pubKeyParams;
|
||||||
try {
|
try {
|
||||||
privKeyParams = self._crypto.getKeyParams(keypair.privateKey.encryptedKey);
|
privKeyParams = self._pgp.getKeyParams(keypair.privateKey.encryptedKey);
|
||||||
pubKeyParams = self._crypto.getKeyParams(keypair.publicKey.publicKey);
|
pubKeyParams = self._pgp.getKeyParams(keypair.publicKey.publicKey);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback(new Error('Error reading key params!'));
|
callback(new Error('Error reading key params!'));
|
||||||
return;
|
return;
|
||||||
@ -148,7 +148,7 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// import existing key pair into crypto module
|
// import existing key pair into crypto module
|
||||||
self._crypto.importKeys({
|
self._pgp.importKeys({
|
||||||
passphrase: options.passphrase,
|
passphrase: options.passphrase,
|
||||||
privateKeyArmored: keypair.privateKey.encryptedKey,
|
privateKeyArmored: keypair.privateKey.encryptedKey,
|
||||||
publicKeyArmored: keypair.publicKey.publicKey
|
publicKeyArmored: keypair.publicKey.publicKey
|
||||||
@ -159,14 +159,14 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set decrypted privateKey to pgpMailer
|
// set decrypted privateKey to pgpMailer
|
||||||
self._pgpbuilder._privateKey = self._crypto._privateKey;
|
self._pgpbuilder._privateKey = self._pgp._privateKey;
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGenerated(generatedKeypair) {
|
function handleGenerated(generatedKeypair) {
|
||||||
// import the new key pair into crypto module
|
// import the new key pair into crypto module
|
||||||
self._crypto.importKeys({
|
self._pgp.importKeys({
|
||||||
passphrase: options.passphrase,
|
passphrase: options.passphrase,
|
||||||
privateKeyArmored: generatedKeypair.privateKeyArmored,
|
privateKeyArmored: generatedKeypair.privateKeyArmored,
|
||||||
publicKeyArmored: generatedKeypair.publicKeyArmored
|
publicKeyArmored: generatedKeypair.publicKeyArmored
|
||||||
@ -196,7 +196,7 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set decrypted privateKey to pgpMailer
|
// set decrypted privateKey to pgpMailer
|
||||||
self._pgpbuilder._privateKey = self._crypto._privateKey;
|
self._pgpbuilder._privateKey = self._pgp._privateKey;
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -816,7 +816,7 @@ define(function(require) {
|
|||||||
|
|
||||||
// get the receiver's public key to check the message signature
|
// get the receiver's public key to check the message signature
|
||||||
var encryptedNode = filterBodyParts(message.bodyParts, 'encrypted')[0];
|
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) {
|
if (err || !decrypted) {
|
||||||
showError(err.errMsg || err.message || 'An error occurred during the decryption.');
|
showError(err.errMsg || err.message || 'An error occurred during the decryption.');
|
||||||
return;
|
return;
|
||||||
|
@ -258,6 +258,11 @@ define(function(require) {
|
|||||||
* @param {Function} callback(error)
|
* @param {Function} callback(error)
|
||||||
*/
|
*/
|
||||||
KeychainDAO.prototype.setDeviceName = function(deviceName, callback) {
|
KeychainDAO.prototype.setDeviceName = function(deviceName, callback) {
|
||||||
|
if (!deviceName) {
|
||||||
|
callback(new Error('Please set a device name!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._localDbDao.persist(DB_DEVICENAME, deviceName, callback);
|
this._localDbDao.persist(DB_DEVICENAME, deviceName, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -323,7 +328,8 @@ define(function(require) {
|
|||||||
* @param {Function} callback(error)
|
* @param {Function} callback(error)
|
||||||
*/
|
*/
|
||||||
KeychainDAO.prototype.registerDevice = function(options, callback) {
|
KeychainDAO.prototype.registerDevice = function(options, callback) {
|
||||||
var self = this;
|
var self = this,
|
||||||
|
devName;
|
||||||
|
|
||||||
// check if deviceName is already persisted in storage
|
// check if deviceName is already persisted in storage
|
||||||
self.getDeviceName(function(err, deviceName) {
|
self.getDeviceName(function(err, deviceName) {
|
||||||
@ -336,6 +342,8 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function requestDeviceRegistration(deviceName) {
|
function requestDeviceRegistration(deviceName) {
|
||||||
|
devName = deviceName;
|
||||||
|
|
||||||
// request device registration session key
|
// request device registration session key
|
||||||
self._privateKeyDao.requestDeviceRegistration({
|
self._privateKeyDao.requestDeviceRegistration({
|
||||||
userId: options.userId,
|
userId: options.userId,
|
||||||
@ -357,15 +365,20 @@ define(function(require) {
|
|||||||
|
|
||||||
function decryptSessionKey(regSessionKey) {
|
function decryptSessionKey(regSessionKey) {
|
||||||
// TODO: fetch public key for service to verify response
|
// 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) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!serverPubkey || !serverPubkey.publicKey) {
|
||||||
|
callback(new Error('Server public key for device registration not found!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// decrypt the session key
|
// decrypt the session key
|
||||||
var ct = regSessionKey.encryptedRegSessionKey;
|
var ct = regSessionKey.encryptedRegSessionKey;
|
||||||
self._pgp.decrypt(ct, serverPubkey, function(err, decrypedSessionKey) {
|
self._pgp.decrypt(ct, serverPubkey.publicKey, function(err, decrypedSessionKey) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
@ -384,10 +397,10 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate deviceSecretIv
|
// generate iv
|
||||||
var deviceSecretIv = util.random(config.symIvSize);
|
var iv = util.random(config.symIvSize);
|
||||||
// encrypt deviceSecret
|
// encrypt deviceSecret
|
||||||
self._crypto.encrypt(deviceSecret, regSessionKey, deviceSecretIv, function(err, encryptedDeviceSecret) {
|
self._crypto.encrypt(deviceSecret, regSessionKey, iv, function(err, encryptedDeviceSecret) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
@ -396,9 +409,9 @@ define(function(require) {
|
|||||||
// upload encryptedDeviceSecret
|
// upload encryptedDeviceSecret
|
||||||
self._privateKeyDao.uploadDeviceSecret({
|
self._privateKeyDao.uploadDeviceSecret({
|
||||||
userId: options.userId,
|
userId: options.userId,
|
||||||
deviceName: options.deviceName,
|
deviceName: devName,
|
||||||
encryptedDeviceSecret: encryptedDeviceSecret,
|
encryptedDeviceSecret: encryptedDeviceSecret,
|
||||||
deviceSecretIv: deviceSecretIv
|
iv: iv
|
||||||
}, callback);
|
}, callback);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -420,7 +433,7 @@ define(function(require) {
|
|||||||
sessionId;
|
sessionId;
|
||||||
|
|
||||||
// request auth session key required for upload
|
// request auth session key required for upload
|
||||||
self._privateKeyDao.requestAuthSessionKeys({
|
self._privateKeyDao.requestAuthSessionKey({
|
||||||
userId: userId
|
userId: userId
|
||||||
}, function(err, authSessionKey) {
|
}, function(err, authSessionKey) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -441,12 +454,17 @@ define(function(require) {
|
|||||||
|
|
||||||
function decryptSessionKey(authSessionKey) {
|
function decryptSessionKey(authSessionKey) {
|
||||||
// TODO: fetch public key for service to verify response
|
// 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) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!serverPubkey || !serverPubkey.publicKey) {
|
||||||
|
callback(new Error('Server public key for authentication not found!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// decrypt the session key
|
// decrypt the session key
|
||||||
var ct1 = authSessionKey.encryptedAuthSessionKey;
|
var ct1 = authSessionKey.encryptedAuthSessionKey;
|
||||||
self._pgp.decrypt(ct1, serverPubkey.publicKey, function(err, decryptedSessionKey) {
|
self._pgp.decrypt(ct1, serverPubkey.publicKey, function(err, decryptedSessionKey) {
|
||||||
@ -456,7 +474,7 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decrypt the challenge
|
// decrypt the challenge
|
||||||
var ct2 = authSessionKey.encryptedAuthSessionKey;
|
var ct2 = authSessionKey.encryptedChallenge;
|
||||||
self._pgp.decrypt(ct2, serverPubkey.publicKey, function(err, decryptedChallenge) {
|
self._pgp.decrypt(ct2, serverPubkey.publicKey, function(err, decryptedChallenge) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(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
|
// respond to challenge by uploading the with the session key encrypted challenge
|
||||||
self._privateKeyDao.verifyAuthentication({
|
self._privateKeyDao.verifyAuthentication({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
encryptedChallenge: encryptedChallenge
|
encryptedChallenge: response.encryptedChallenge,
|
||||||
|
encryptedDeviceSecret: response.encryptedDeviceSecret,
|
||||||
|
iv: response.iv
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
@ -565,7 +585,7 @@ define(function(require) {
|
|||||||
var privkeyId = keypair.privateKey._id,
|
var privkeyId = keypair.privateKey._id,
|
||||||
pgpBlock = keypair.privateKey.encryptedKey;
|
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);
|
var iv = util.random(config.symIvSize);
|
||||||
self._crypto.encrypt(pgpBlock, encryptionKey, iv, function(err, ct) {
|
self._crypto.encrypt(pgpBlock, encryptionKey, iv, function(err, ct) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -575,6 +595,7 @@ define(function(require) {
|
|||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
_id: privkeyId,
|
_id: privkeyId,
|
||||||
|
userId: options.userId,
|
||||||
encryptedPrivateKey: ct,
|
encryptedPrivateKey: ct,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
iv: iv
|
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.
|
* 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)
|
* @param {Function} callback(error)
|
||||||
*/
|
*/
|
||||||
KeychainDAO.prototype.requestPrivateKeyDownload = function(userId, callback) {
|
KeychainDAO.prototype.requestPrivateKeyDownload = function(options, callback) {
|
||||||
this._privateKeyDao.requestDownload({
|
this._privateKeyDao.requestDownload(options, callback);
|
||||||
userId: userId
|
|
||||||
}, 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.
|
* 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.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.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.salt The salt required to derive the code derived key
|
||||||
* @param {String} options.iv The iv used to encrypt the private PGP 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) {
|
KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(options, callback) {
|
||||||
var self = this,
|
var self = this,
|
||||||
code = options.code,
|
code = options.code,
|
||||||
salt = options.salt,
|
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!'));
|
callback(new Error('Incomplete arguments!'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -670,21 +690,44 @@ define(function(require) {
|
|||||||
|
|
||||||
function decryptAndStore(derivedKey) {
|
function decryptAndStore(derivedKey) {
|
||||||
// decrypt the private key with the derived key
|
// decrypt the private key with the derived key
|
||||||
var pt = options.encryptedPrivkey,
|
var ct = options.encryptedPrivateKey,
|
||||||
iv = options.iv;
|
iv = options.iv;
|
||||||
|
|
||||||
self._crypto.decrypt(pt, derivedKey, iv, function(err, pgpBlock) {
|
self._crypto.decrypt(ct, derivedKey, iv, function(err, privateKeyArmored) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(new Error('Invalid keychain code!'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// store private key locally
|
// validate pgp key
|
||||||
self.saveLocalPrivateKey({
|
var keyParams;
|
||||||
_id: options.keyId,
|
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,
|
userId: options.userId,
|
||||||
encryptedKey: pgpBlock
|
encryptedKey: privateKeyArmored
|
||||||
}, callback);
|
};
|
||||||
|
|
||||||
|
// 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.
|
* Authenticate device registration by uploading the deviceSecret encrypted with the regSessionKeys.
|
||||||
* @param {String} options.userId The user's email address
|
* @param {String} options.userId The user's email address
|
||||||
* @param {String} options.deviceName The device's memorable name
|
* @param {String} options.deviceName The device's memorable name
|
||||||
* @param {Object} options.encryptedDeviceSecret {encryptedDeviceSecret:[base64 encoded]}
|
* @param {String} options.encryptedDeviceSecret The base64 encoded encrypted device secret
|
||||||
|
* @param {String} options.iv The iv used for encryption
|
||||||
* @param {Function} callback(error)
|
* @param {Function} callback(error)
|
||||||
*/
|
*/
|
||||||
PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
|
PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
|
||||||
var uri;
|
var uri;
|
||||||
|
|
||||||
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret) {
|
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) {
|
||||||
callback(new Error('Incomplete arguments!'));
|
callback(new Error('Incomplete arguments!'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
|
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.
|
* Request authSessionKeys required for upload the encrypted private PGP key.
|
||||||
* @param {String} options.userId The user's email address
|
* @param {String} options.userId The user's email address
|
||||||
* @param {Function} callback(error, authSessionKey)
|
* @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;
|
var uri;
|
||||||
|
|
||||||
if (!options.userId) {
|
if (!options.userId) {
|
||||||
@ -71,44 +72,50 @@ define(function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifiy authentication by uploading the challenge and deviceSecret encrypted with the authSessionKeys as a response.
|
* 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 {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.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)
|
* @param {Function} callback(error)
|
||||||
*/
|
*/
|
||||||
PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
|
PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
|
||||||
var uri;
|
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!'));
|
callback(new Error('Incomplete arguments!'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uri = '/auth/user/' + options.userId + '/session/' + options.sessionId;
|
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.
|
* 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)
|
* @param {Function} callback(error)
|
||||||
*/
|
*/
|
||||||
PrivateKeyDAO.prototype.upload = function(options, callback) {
|
PrivateKeyDAO.prototype.upload = function(options, callback) {
|
||||||
var uri,
|
var uri;
|
||||||
key = options.encryptedPrivateKey;
|
|
||||||
|
|
||||||
if (!options.userId || !key || !key._id) {
|
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) {
|
||||||
callback(new Error('Incomplete arguments!'));
|
callback(new Error('Incomplete arguments!'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uri = '/privatekey/user/' + options.userId + '/key/' + key._id;
|
uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId;
|
||||||
this._restDao.post(key, uri, callback);
|
this._restDao.post(options, uri, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request download for the encrypted private PGP key.
|
* Request download for the encrypted private PGP key.
|
||||||
* @param {[type]} options.userId The user's email address
|
* @param {String} options.userId The user's email address
|
||||||
* @param {Function} callback(error)
|
* @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) {
|
PrivateKeyDAO.prototype.requestDownload = function(options, callback) {
|
||||||
var uri;
|
var uri;
|
||||||
@ -121,7 +128,20 @@ define(function() {
|
|||||||
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId;
|
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId;
|
||||||
this._restDao.get({
|
this._restDao.get({
|
||||||
uri: uri
|
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/shared";
|
||||||
@import "views/add-account";
|
@import "views/add-account";
|
||||||
@import "views/account";
|
@import "views/account";
|
||||||
|
@import "views/privatekey-upload";
|
||||||
@import "views/set-passphrase";
|
@import "views/set-passphrase";
|
||||||
@import "views/contacts";
|
@import "views/contacts";
|
||||||
@import "views/about";
|
@import "views/about";
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
.view-account {
|
.view-account {
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $color-blue;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
margin: 50px auto 60px auto;
|
margin: 50px auto 60px auto;
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
|
|
||||||
|
@ -119,4 +119,15 @@
|
|||||||
margin-right: 10px;
|
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>
|
||||||
<tr>
|
<tr>
|
||||||
<td>PGP Key ID</td>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<td>PGP Fingerprint</td>
|
<td>PGP Fingerprint</td>
|
||||||
|
@ -31,6 +31,10 @@
|
|||||||
<div class="lightbox" ng-include="'tpl/set-passphrase.html'"></div>
|
<div class="lightbox" ng-include="'tpl/set-passphrase.html'"></div>
|
||||||
</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-overlay" ng-class="{'show': state.lightbox === 'contacts'}">
|
||||||
<div class="lightbox" ng-include="'tpl/contacts.html'"></div>
|
<div class="lightbox" ng-include="'tpl/contacts.html'"></div>
|
||||||
</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">
|
<ul class="nav-secondary">
|
||||||
<li><a href="#" ng-click="state.account.toggle(true); $event.preventDefault()">Account</a></li>
|
<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.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>
|
<li><a href="#" ng-click="state.about.toggle(true); $event.preventDefault()">About</a></li>
|
||||||
</ul>
|
</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,
|
var scope, accountCtrl,
|
||||||
dummyFingerprint, expectedFingerprint,
|
dummyFingerprint, expectedFingerprint,
|
||||||
dummyKeyId, expectedKeyId,
|
dummyKeyId, expectedKeyId,
|
||||||
emailAddress, keySize, cryptoMock, keychainMock;
|
emailAddress, keySize, pgpMock, keychainMock;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
|
||||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||||
|
|
||||||
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
|
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
|
||||||
expectedFingerprint = '3A2D 39B4 E140 4190 B8B9 49DE 7D7E 9903 6E71 2926';
|
expectedFingerprint = '3A2D 39B4 E140 4190 B8B9 49DE 7D7E 9903 6E71 2926';
|
||||||
dummyKeyId = '9FEB47936E712926';
|
dummyKeyId = '9FEB47936E712926';
|
||||||
expectedKeyId = '6E712926';
|
expectedKeyId = '6E712926';
|
||||||
cryptoMock.getFingerprint.returns(dummyFingerprint);
|
pgpMock.getFingerprint.returns(dummyFingerprint);
|
||||||
cryptoMock.getKeyId.returns(dummyKeyId);
|
pgpMock.getKeyId.returns(dummyKeyId);
|
||||||
emailAddress = 'fred@foo.com';
|
emailAddress = 'fred@foo.com';
|
||||||
keySize = 1234;
|
keySize = 1234;
|
||||||
appController._emailDao = {
|
appController._emailDao = {
|
||||||
@ -34,7 +34,7 @@ define(function(require) {
|
|||||||
asymKeySize: keySize
|
asymKeySize: keySize
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cryptoMock.getKeyParams.returns({
|
pgpMock.getKeyParams.returns({
|
||||||
_id: dummyKeyId,
|
_id: dummyKeyId,
|
||||||
fingerprint: dummyFingerprint,
|
fingerprint: dummyFingerprint,
|
||||||
userId: emailAddress,
|
userId: emailAddress,
|
||||||
|
@ -37,7 +37,7 @@ define(function(require) {
|
|||||||
expect(controller._userStorage).to.exist;
|
expect(controller._userStorage).to.exist;
|
||||||
expect(controller._invitationDao).to.exist;
|
expect(controller._invitationDao).to.exist;
|
||||||
expect(controller._keychain).to.exist;
|
expect(controller._keychain).to.exist;
|
||||||
expect(controller._crypto).to.exist;
|
expect(controller._pgp).to.exist;
|
||||||
expect(controller._pgpbuilder).to.exist;
|
expect(controller._pgpbuilder).to.exist;
|
||||||
expect(controller._emailDao).to.exist;
|
expect(controller._emailDao).to.exist;
|
||||||
expect(controller._outboxBo).to.exist;
|
expect(controller._outboxBo).to.exist;
|
||||||
|
@ -12,11 +12,11 @@ define(function(require) {
|
|||||||
describe('Contacts Controller unit test', function() {
|
describe('Contacts Controller unit test', function() {
|
||||||
var scope, contactsCtrl,
|
var scope, contactsCtrl,
|
||||||
origKeychain, keychainMock,
|
origKeychain, keychainMock,
|
||||||
origCrypto, cryptoMock;
|
origPgp, pgpMock;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
origCrypto = appController._crypto;
|
origPgp = appController._pgp;
|
||||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
|
||||||
origKeychain = appController._keychain;
|
origKeychain = appController._keychain;
|
||||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ define(function(require) {
|
|||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
// restore the module
|
// restore the module
|
||||||
appController._crypto = origCrypto;
|
appController._pgp = origPgp;
|
||||||
appController._keychain = origKeychain;
|
appController._keychain = origKeychain;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ define(function(require) {
|
|||||||
keychainMock.listLocalPublicKeys.yields(null, [{
|
keychainMock.listLocalPublicKeys.yields(null, [{
|
||||||
_id: '12345'
|
_id: '12345'
|
||||||
}]);
|
}]);
|
||||||
cryptoMock.getKeyParams.returns({
|
pgpMock.getKeyParams.returns({
|
||||||
fingerprint: 'asdf'
|
fingerprint: 'asdf'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ define(function(require) {
|
|||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||||
|
|
||||||
cryptoMock.getKeyParams.returns({
|
pgpMock.getKeyParams.returns({
|
||||||
_id: '12345',
|
_id: '12345',
|
||||||
userId: 'max@example.com',
|
userId: 'max@example.com',
|
||||||
userIds: []
|
userIds: []
|
||||||
@ -127,7 +127,7 @@ define(function(require) {
|
|||||||
it('should fail due to error in pgp.getKeyParams', function(done) {
|
it('should fail due to error in pgp.getKeyParams', function(done) {
|
||||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||||
|
|
||||||
cryptoMock.getKeyParams.throws(new Error('WAT'));
|
pgpMock.getKeyParams.throws(new Error('WAT'));
|
||||||
|
|
||||||
scope.onError = function(err) {
|
scope.onError = function(err) {
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
@ -140,7 +140,7 @@ define(function(require) {
|
|||||||
it('should fail due to error in keychain.saveLocalPublicKey', function(done) {
|
it('should fail due to error in keychain.saveLocalPublicKey', function(done) {
|
||||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||||
|
|
||||||
cryptoMock.getKeyParams.returns({
|
pgpMock.getKeyParams.returns({
|
||||||
_id: '12345',
|
_id: '12345',
|
||||||
userId: 'max@example.com'
|
userId: 'max@example.com'
|
||||||
});
|
});
|
||||||
|
@ -114,7 +114,7 @@ define(function(require) {
|
|||||||
// check configuration
|
// check configuration
|
||||||
//
|
//
|
||||||
expect(dao._keychain).to.equal(keychainStub);
|
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._devicestorage).to.equal(devicestorageStub);
|
||||||
expect(dao._mailreader).to.equal(mailreader);
|
expect(dao._mailreader).to.equal(mailreader);
|
||||||
expect(dao._pgpbuilder).to.equal(pgpBuilderStub);
|
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) {
|
it('should fail in decrypt', function(done) {
|
||||||
getDeviceNameStub.yields(null, 'iPhone');
|
getDeviceNameStub.yields(null, 'iPhone');
|
||||||
|
|
||||||
@ -753,7 +773,9 @@ define(function(require) {
|
|||||||
encryptedRegSessionKey: 'asdf'
|
encryptedRegSessionKey: 'asdf'
|
||||||
});
|
});
|
||||||
|
|
||||||
lookupPublicKeyStub.yields(null, 'pubkey');
|
lookupPublicKeyStub.yields(null, {
|
||||||
|
publicKey: 'pubkey'
|
||||||
|
});
|
||||||
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(42);
|
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(42);
|
||||||
|
|
||||||
keychainDao.registerDevice({
|
keychainDao.registerDevice({
|
||||||
@ -774,7 +796,9 @@ define(function(require) {
|
|||||||
encryptedRegSessionKey: 'asdf'
|
encryptedRegSessionKey: 'asdf'
|
||||||
});
|
});
|
||||||
|
|
||||||
lookupPublicKeyStub.yields(null, 'pubkey');
|
lookupPublicKeyStub.yields(null, {
|
||||||
|
publicKey: 'pubkey'
|
||||||
|
});
|
||||||
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
||||||
getDeviceSecretStub.yields(42);
|
getDeviceSecretStub.yields(42);
|
||||||
|
|
||||||
@ -796,7 +820,9 @@ define(function(require) {
|
|||||||
encryptedRegSessionKey: 'asdf'
|
encryptedRegSessionKey: 'asdf'
|
||||||
});
|
});
|
||||||
|
|
||||||
lookupPublicKeyStub.yields(null, 'pubkey');
|
lookupPublicKeyStub.yields(null, {
|
||||||
|
publicKey: 'pubkey'
|
||||||
|
});
|
||||||
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
||||||
getDeviceSecretStub.yields(null, 'secret');
|
getDeviceSecretStub.yields(null, 'secret');
|
||||||
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(42);
|
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(42);
|
||||||
@ -819,7 +845,9 @@ define(function(require) {
|
|||||||
encryptedRegSessionKey: 'asdf'
|
encryptedRegSessionKey: 'asdf'
|
||||||
});
|
});
|
||||||
|
|
||||||
lookupPublicKeyStub.yields(null, 'pubkey');
|
lookupPublicKeyStub.yields(null, {
|
||||||
|
publicKey: 'pubkey'
|
||||||
|
});
|
||||||
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
|
||||||
getDeviceSecretStub.yields(null, 'secret');
|
getDeviceSecretStub.yields(null, 'secret');
|
||||||
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(null, 'encryptedDeviceSecret');
|
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(null, 'encryptedDeviceSecret');
|
||||||
@ -847,8 +875,8 @@ define(function(require) {
|
|||||||
getDeviceSecretStub.restore();
|
getDeviceSecretStub.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail due to privkeyDao.requestAuthSessionKeys', function(done) {
|
it('should fail due to privkeyDao.requestAuthSessionKey', function(done) {
|
||||||
privkeyDaoStub.requestAuthSessionKeys.withArgs({
|
privkeyDaoStub.requestAuthSessionKey.withArgs({
|
||||||
userId: testUser
|
userId: testUser
|
||||||
}).yields(42);
|
}).yields(42);
|
||||||
|
|
||||||
@ -859,8 +887,8 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail due to privkeyDao.requestAuthSessionKeys response', function(done) {
|
it('should fail due to privkeyDao.requestAuthSessionKey response', function(done) {
|
||||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {});
|
privkeyDaoStub.requestAuthSessionKey.yields(null, {});
|
||||||
|
|
||||||
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
|
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
@ -870,7 +898,7 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should fail due to lookupPublicKey', function(done) {
|
it('should fail due to lookupPublicKey', function(done) {
|
||||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||||
encryptedChallenge: 'encryptedChallenge',
|
encryptedChallenge: 'encryptedChallenge',
|
||||||
sessionId: 'sessionId'
|
sessionId: 'sessionId'
|
||||||
@ -886,7 +914,7 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should fail due to pgp.decrypt', function(done) {
|
it('should fail due to pgp.decrypt', function(done) {
|
||||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||||
encryptedChallenge: 'encryptedChallenge',
|
encryptedChallenge: 'encryptedChallenge',
|
||||||
sessionId: 'sessionId'
|
sessionId: 'sessionId'
|
||||||
@ -906,7 +934,7 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should fail due to getDeviceSecret', function(done) {
|
it('should fail due to getDeviceSecret', function(done) {
|
||||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||||
encryptedChallenge: 'encryptedChallenge',
|
encryptedChallenge: 'encryptedChallenge',
|
||||||
sessionId: 'sessionId'
|
sessionId: 'sessionId'
|
||||||
@ -927,7 +955,7 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should fail due to crypto.encrypt', function(done) {
|
it('should fail due to crypto.encrypt', function(done) {
|
||||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||||
encryptedChallenge: 'encryptedChallenge',
|
encryptedChallenge: 'encryptedChallenge',
|
||||||
sessionId: 'sessionId'
|
sessionId: 'sessionId'
|
||||||
@ -949,7 +977,7 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should fail due to privkeyDao.verifyAuthentication', function(done) {
|
it('should fail due to privkeyDao.verifyAuthentication', function(done) {
|
||||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||||
encryptedChallenge: 'encryptedChallenge',
|
encryptedChallenge: 'encryptedChallenge',
|
||||||
sessionId: 'sessionId'
|
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) {
|
it('should work', function(done) {
|
||||||
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
|
privkeyDaoStub.requestAuthSessionKey.yields(null, {
|
||||||
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
|
||||||
encryptedChallenge: 'encryptedChallenge',
|
encryptedChallenge: 'encryptedChallenge',
|
||||||
sessionId: 'sessionId'
|
sessionId: 'sessionId'
|
||||||
});
|
});
|
||||||
|
|
||||||
lookupPublicKeyStub.yields(null, {
|
lookupPublicKeyStub.yields(null, {
|
||||||
publickKey: 'publicKey'
|
publicKey: 'publicKey'
|
||||||
});
|
});
|
||||||
|
|
||||||
pgpStub.decrypt.yields(null, 'decryptedStuff');
|
pgpStub.decrypt.yields(null, 'decryptedStuff');
|
||||||
@ -1152,10 +1201,12 @@ define(function(require) {
|
|||||||
|
|
||||||
describe('requestPrivateKeyDownload', function() {
|
describe('requestPrivateKeyDownload', function() {
|
||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
privkeyDaoStub.requestDownload.withArgs({
|
var options = {
|
||||||
userId: testUser
|
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() {
|
describe('decryptAndStorePrivateKeyLocally', function() {
|
||||||
var saveLocalPrivateKeyStub;
|
var saveLocalPrivateKeyStub, testData;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
testData = {
|
||||||
|
_id: 'keyId',
|
||||||
|
userId: testUser,
|
||||||
|
encryptedPrivateKey: 'encryptedPrivateKey',
|
||||||
|
code: 'code',
|
||||||
|
salt: 'salt',
|
||||||
|
iv: 'iv'
|
||||||
|
};
|
||||||
|
|
||||||
saveLocalPrivateKeyStub = sinon.stub(keychainDao, 'saveLocalPrivateKey');
|
saveLocalPrivateKeyStub = sinon.stub(keychainDao, 'saveLocalPrivateKey');
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
@ -1190,10 +1250,7 @@ define(function(require) {
|
|||||||
it('should fail due to crypto.deriveKey', function(done) {
|
it('should fail due to crypto.deriveKey', function(done) {
|
||||||
cryptoStub.deriveKey.yields(42);
|
cryptoStub.deriveKey.yields(42);
|
||||||
|
|
||||||
keychainDao.decryptAndStorePrivateKeyLocally({
|
keychainDao.decryptAndStorePrivateKeyLocally(testData, function(err) {
|
||||||
userId: testUser,
|
|
||||||
keyId: 'keyId'
|
|
||||||
}, function(err) {
|
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
||||||
done();
|
done();
|
||||||
@ -1204,10 +1261,7 @@ define(function(require) {
|
|||||||
cryptoStub.deriveKey.yields(null, 'derivedKey');
|
cryptoStub.deriveKey.yields(null, 'derivedKey');
|
||||||
cryptoStub.decrypt.yields(42);
|
cryptoStub.decrypt.yields(42);
|
||||||
|
|
||||||
keychainDao.decryptAndStorePrivateKeyLocally({
|
keychainDao.decryptAndStorePrivateKeyLocally(testData, function(err) {
|
||||||
userId: testUser,
|
|
||||||
keyId: 'keyId'
|
|
||||||
}, function(err) {
|
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
||||||
expect(cryptoStub.decrypt.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.deriveKey.yields(null, 'derivedKey');
|
||||||
cryptoStub.decrypt.yields(null, 'pgpBlock');
|
cryptoStub.decrypt.yields(null, 'privateKeyArmored');
|
||||||
saveLocalPrivateKeyStub.yields();
|
pgpStub.getKeyParams.throws(new Error());
|
||||||
|
|
||||||
keychainDao.decryptAndStorePrivateKeyLocally({
|
keychainDao.decryptAndStorePrivateKeyLocally(testData, function(err) {
|
||||||
userId: testUser,
|
expect(err).to.exist;
|
||||||
keyId: 'keyId'
|
|
||||||
}, function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
|
||||||
expect(cryptoStub.decrypt.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;
|
expect(saveLocalPrivateKeyStub.calledOnce).to.be.true;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
@ -7,10 +7,13 @@ define(function(require) {
|
|||||||
LoginCtrl = require('js/controller/login'),
|
LoginCtrl = require('js/controller/login'),
|
||||||
EmailDAO = require('js/dao/email-dao'),
|
EmailDAO = require('js/dao/email-dao'),
|
||||||
Auth = require('js/bo/auth'),
|
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() {
|
describe('Login Controller unit test', function() {
|
||||||
var scope, location, ctrl, origEmailDao, emailDaoMock,
|
var scope, location, ctrl,
|
||||||
|
origEmailDao, emailDaoMock,
|
||||||
|
origKeychain, keychainMock,
|
||||||
emailAddress = 'fred@foo.com',
|
emailAddress = 'fred@foo.com',
|
||||||
startAppStub,
|
startAppStub,
|
||||||
checkForUpdateStub,
|
checkForUpdateStub,
|
||||||
@ -21,14 +24,16 @@ define(function(require) {
|
|||||||
var hasChrome, hasIdentity;
|
var hasChrome, hasIdentity;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
hasChrome = !! window.chrome;
|
hasChrome = !!window.chrome;
|
||||||
hasIdentity = !! window.chrome.identity;
|
hasIdentity = !!window.chrome.identity;
|
||||||
window.chrome = window.chrome || {};
|
window.chrome = window.chrome || {};
|
||||||
window.chrome.identity = window.chrome.identity || {};
|
window.chrome.identity = window.chrome.identity || {};
|
||||||
|
|
||||||
// remember original module to restore later, then replace it
|
// remember original module to restore later, then replace it
|
||||||
origEmailDao = appController._emailDao;
|
origEmailDao = appController._emailDao;
|
||||||
|
origKeychain = appController._keychain;
|
||||||
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
|
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||||
|
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||||
appController._auth = authStub = sinon.createStubInstance(Auth);
|
appController._auth = authStub = sinon.createStubInstance(Auth);
|
||||||
|
|
||||||
startAppStub = sinon.stub(appController, 'start');
|
startAppStub = sinon.stub(appController, 'start');
|
||||||
@ -48,6 +53,7 @@ define(function(require) {
|
|||||||
|
|
||||||
// restore the app controller module
|
// restore the app controller module
|
||||||
appController._emailDao = origEmailDao;
|
appController._emailDao = origEmailDao;
|
||||||
|
appController._keychain = origKeychain;
|
||||||
appController.start.restore && appController.start.restore();
|
appController.start.restore && appController.start.restore();
|
||||||
appController.checkForUpdate.restore && appController.checkForUpdate.restore();
|
appController.checkForUpdate.restore && appController.checkForUpdate.restore();
|
||||||
appController.init.restore && appController.init.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) {
|
it('should forward to new device login', function(done) {
|
||||||
startAppStub.yields();
|
startAppStub.yields();
|
||||||
authStub.getEmailAddress.yields(null, emailAddress);
|
authStub.getEmailAddress.yields(null, emailAddress);
|
||||||
initStub.yields(null, {
|
initStub.yields(null, {
|
||||||
publicKey: 'b'
|
publicKey: 'b'
|
||||||
});
|
});
|
||||||
|
keychainMock.requestPrivateKeyDownload.yields();
|
||||||
|
|
||||||
angular.module('logintest', []);
|
angular.module('logintest', []);
|
||||||
mocks.module('logintest');
|
mocks.module('logintest');
|
||||||
@ -144,6 +180,7 @@ define(function(require) {
|
|||||||
expect(startAppStub.calledOnce).to.be.true;
|
expect(startAppStub.calledOnce).to.be.true;
|
||||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||||
expect(authStub.getEmailAddress.calledOnce).to.be.true;
|
expect(authStub.getEmailAddress.calledOnce).to.be.true;
|
||||||
|
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
scope = $rootScope.$new();
|
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-existing-ctrl-test',
|
||||||
'test/unit/login-initial-ctrl-test',
|
'test/unit/login-initial-ctrl-test',
|
||||||
'test/unit/login-new-device-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/login-ctrl-test',
|
||||||
'test/unit/read-ctrl-test',
|
'test/unit/read-ctrl-test',
|
||||||
'test/unit/navigation-ctrl-test',
|
'test/unit/navigation-ctrl-test',
|
||||||
|
@ -92,7 +92,7 @@ define(function(require) {
|
|||||||
publicKeyArmored: pubkey
|
publicKeyArmored: pubkey
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
expect(err.errMsg).to.equal('Incorrect passphrase!');
|
expect(err.message).to.equal('Incorrect passphrase!');
|
||||||
|
|
||||||
pgp.exportKeys(function(err, keys) {
|
pgp.exportKeys(function(err, keys) {
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
|
@ -57,7 +57,8 @@ define(function(require) {
|
|||||||
privkeyDao.uploadDeviceSecret({
|
privkeyDao.uploadDeviceSecret({
|
||||||
userId: emailAddress,
|
userId: emailAddress,
|
||||||
deviceName: deviceName,
|
deviceName: deviceName,
|
||||||
encryptedDeviceSecret: 'asdf'
|
encryptedDeviceSecret: 'asdf',
|
||||||
|
iv: 'iv'
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
done();
|
done();
|
||||||
@ -65,9 +66,9 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('requestAuthSessionKeys', function() {
|
describe('requestAuthSessionKey', function() {
|
||||||
it('should fail due to invalid args', function(done) {
|
it('should fail due to invalid args', function(done) {
|
||||||
privkeyDao.requestAuthSessionKeys({}, function(err) {
|
privkeyDao.requestAuthSessionKey({}, function(err) {
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -76,7 +77,7 @@ define(function(require) {
|
|||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields();
|
restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields();
|
||||||
|
|
||||||
privkeyDao.requestAuthSessionKeys({
|
privkeyDao.requestAuthSessionKey({
|
||||||
userId: emailAddress
|
userId: emailAddress
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
@ -96,13 +97,17 @@ define(function(require) {
|
|||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
var sessionId = '1';
|
var sessionId = '1';
|
||||||
|
|
||||||
restDaoStub.put.withArgs('asdf', '/auth/user/' + emailAddress + '/session/' + sessionId).yields();
|
var options = {
|
||||||
|
|
||||||
privkeyDao.verifyAuthentication({
|
|
||||||
userId: emailAddress,
|
userId: emailAddress,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
encryptedChallenge: 'asdf'
|
encryptedChallenge: 'asdf',
|
||||||
}, function(err) {
|
encryptedDeviceSecret: 'qwer',
|
||||||
|
iv: ' iv'
|
||||||
|
};
|
||||||
|
|
||||||
|
restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).yields();
|
||||||
|
|
||||||
|
privkeyDao.verifyAuthentication(options, function(err) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -118,16 +123,18 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
var key = {
|
var options = {
|
||||||
_id: '12345'
|
_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({
|
privkeyDao.upload(options, function(err) {
|
||||||
userId: emailAddress,
|
|
||||||
encryptedPrivateKey: key
|
|
||||||
}, function(err) {
|
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
done();
|
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;
|
emailAddress, keySize, cryptoMock, keychainMock;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
|
||||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||||
|
|
||||||
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
|
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
|
||||||
|
Loading…
Reference in New Issue
Block a user