mirror of
https://github.com/moparisthebest/mail
synced 2024-12-22 15:28:49 -05:00
commit
37b1862e9f
4
.gitignore
vendored
4
.gitignore
vendored
@ -8,7 +8,5 @@ dist/
|
|||||||
release/
|
release/
|
||||||
test/integration/src/
|
test/integration/src/
|
||||||
src/lib/*.js
|
src/lib/*.js
|
||||||
src/js/crypto/aes-cbc.js
|
src/js/crypto/aes-gcm.js
|
||||||
src/js/crypto/crypto-batch.js
|
|
||||||
src/js/crypto/rsa.js
|
|
||||||
src/js/crypto/util.js
|
src/js/crypto/util.js
|
||||||
|
17
Gruntfile.js
17
Gruntfile.js
@ -44,29 +44,17 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
jshint: {
|
jshint: {
|
||||||
all: ['Gruntfile.js', 'src/*.js', 'src/js/**/*.js', 'test/new-unit/*.js', 'test/unit/*.js', 'test/integration/*.js'],
|
all: ['Gruntfile.js', 'src/*.js', 'src/js/**/*.js', 'test/unit/*.js', 'test/integration/*.js'],
|
||||||
options: {
|
options: {
|
||||||
jshintrc: '.jshintrc'
|
jshintrc: '.jshintrc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
qunit: {
|
|
||||||
all: {
|
|
||||||
options: {
|
|
||||||
timeout: 20000,
|
|
||||||
urls: ['http://localhost:<%= connect.test.options.port %>/test/unit/index.html'
|
|
||||||
/*,
|
|
||||||
'http://localhost:<%= connect.test.options.port %>/test/integration/index.html'*/
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mocha: {
|
mocha: {
|
||||||
all: {
|
all: {
|
||||||
options: {
|
options: {
|
||||||
urls: [
|
urls: [
|
||||||
'http://localhost:<%= connect.test.options.port %>/test/new-unit/index.html',
|
'http://localhost:<%= connect.test.options.port %>/test/unit/index.html',
|
||||||
'http://localhost:<%= connect.test.options.port %>/test/integration/index.html'
|
'http://localhost:<%= connect.test.options.port %>/test/integration/index.html'
|
||||||
],
|
],
|
||||||
run: false,
|
run: false,
|
||||||
@ -255,7 +243,6 @@ module.exports = function(grunt) {
|
|||||||
// Load the plugin(s)
|
// Load the plugin(s)
|
||||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||||
grunt.loadNpmTasks('grunt-contrib-qunit');
|
|
||||||
grunt.loadNpmTasks('grunt-mocha');
|
grunt.loadNpmTasks('grunt-mocha');
|
||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||||
grunt.loadNpmTasks('grunt-csso');
|
grunt.loadNpmTasks('grunt-csso');
|
||||||
|
@ -32,7 +32,7 @@ We take the privacy of your data very seriously. Here are some of the technical
|
|||||||
You can download a prebuilt bundle under [releases](https://github.com/whiteout-io/mail-html5/releases) or build your own from source (requires [node.js](http://nodejs.org/download/), [grunt](http://gruntjs.com/getting-started#installing-the-cli) and [sass](http://sass-lang.com/install)):
|
You can download a prebuilt bundle under [releases](https://github.com/whiteout-io/mail-html5/releases) or build your own from source (requires [node.js](http://nodejs.org/download/), [grunt](http://gruntjs.com/getting-started#installing-the-cli) and [sass](http://sass-lang.com/install)):
|
||||||
|
|
||||||
npm install && npm test
|
npm install && npm test
|
||||||
|
|
||||||
This will download all dependencies, run the tests and build the Chrome Packaged App bundle **DEV.zip** which can be installed under [chrome://extensions](chrome://extensions) in developer mode.
|
This will download all dependencies, run the tests and build the Chrome Packaged App bundle **DEV.zip** which can be installed under [chrome://extensions](chrome://extensions) in developer mode.
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
@ -40,7 +40,7 @@ For development you can start a connect dev server:
|
|||||||
|
|
||||||
grunt dev
|
grunt dev
|
||||||
|
|
||||||
Then visit [http://localhost:8580/dist/chrome.html#/desktop](http://localhost:8580/dist/chrome.html#/desktop) for front-end code or [http://localhost:8580/test/new-unit/](http://localhost:8580/test/new-unit/) to test JavaScript changes. You can also start a watch task so you don't have rebuild everytime you make a change:
|
Then visit [http://localhost:8580/dist/chrome.html#/desktop](http://localhost:8580/dist/chrome.html#/desktop) for front-end code or [http://localhost:8580/test/unit/](http://localhost:8580/test/unit/) to test JavaScript changes. You can also start a watch task so you don't have rebuild everytime you make a change:
|
||||||
|
|
||||||
grunt watch
|
grunt watch
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"start": "grunt && grunt dev"
|
"start": "grunt && grunt dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/v0.1.1",
|
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/v0.2.0",
|
||||||
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/v0.3.3",
|
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/v0.3.3",
|
||||||
"mailreader": "https://github.com/whiteout-io/mailreader/tarball/v0.3.3",
|
"mailreader": "https://github.com/whiteout-io/mailreader/tarball/v0.3.3",
|
||||||
"pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/v0.3.4",
|
"pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/v0.3.4",
|
||||||
@ -27,7 +27,6 @@
|
|||||||
"sinon": "~1.7.3",
|
"sinon": "~1.7.3",
|
||||||
"grunt-contrib-connect": "~0.5.0",
|
"grunt-contrib-connect": "~0.5.0",
|
||||||
"grunt-contrib-jshint": "~0.6.4",
|
"grunt-contrib-jshint": "~0.6.4",
|
||||||
"grunt-contrib-qunit": "~0.2.2",
|
|
||||||
"grunt-mocha": "~0.4.1",
|
"grunt-mocha": "~0.4.1",
|
||||||
"grunt-contrib-clean": "~0.5.0",
|
"grunt-contrib-clean": "~0.5.0",
|
||||||
"grunt-csso": "~0.6.1",
|
"grunt-csso": "~0.6.1",
|
||||||
|
@ -25,8 +25,10 @@ define(function(require) {
|
|||||||
*/
|
*/
|
||||||
app.config = {
|
app.config = {
|
||||||
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
|
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
|
||||||
symKeySize: 128,
|
privkeyServerUrl: 'https://keychain-test.whiteout.io',
|
||||||
symIvSize: 128,
|
serverPrivateKeyId: 'EE342F0DDBB0F3BE',
|
||||||
|
symKeySize: 256,
|
||||||
|
symIvSize: 96,
|
||||||
asymKeySize: 2048,
|
asymKeySize: 2048,
|
||||||
workerPath: 'js',
|
workerPath: 'js',
|
||||||
gmail: {
|
gmail: {
|
||||||
|
@ -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,17 +8,19 @@ 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',
|
||||||
'js/controller/navigation',
|
'js/controller/navigation',
|
||||||
'cryptoLib/util',
|
'js/crypto/util',
|
||||||
'js/util/error',
|
'js/util/error',
|
||||||
'fastclick',
|
'fastclick',
|
||||||
'angularSanitize',
|
'angularSanitize',
|
||||||
@ -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);
|
||||||
|
@ -2,7 +2,7 @@ define(function(require) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('underscore'),
|
var _ = require('underscore'),
|
||||||
util = require('cryptoLib/util'),
|
util = require('js/crypto/util'),
|
||||||
config = require('js/app-config').config,
|
config = require('js/app-config').config,
|
||||||
outboxDb = 'email_OUTBOX';
|
outboxDb = 'email_OUTBOX';
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ define(function(require) {
|
|||||||
this._outboxBusy = false;
|
this._outboxBusy = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function activates the periodic checking of the local device storage for pending mails.
|
* This function activates the periodic checking of the local device storage for pending mails.
|
||||||
* @param {Function} callback(error, pendingMailsCount) Callback that informs you about the count of pending mails.
|
* @param {Function} callback(error, pendingMailsCount) Callback that informs you about the count of pending mails.
|
||||||
*/
|
*/
|
||||||
|
@ -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) {
|
||||||
|
@ -4,17 +4,17 @@ define(function(require) {
|
|||||||
var angular = require('angular'),
|
var angular = require('angular'),
|
||||||
_ = require('underscore'),
|
_ = require('underscore'),
|
||||||
appController = require('js/app-controller'),
|
appController = require('js/app-controller'),
|
||||||
aes = require('cryptoLib/aes-cbc'),
|
aes = require('js/crypto/aes-gcm'),
|
||||||
util = require('cryptoLib/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;
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// import web worker dependencies
|
|
||||||
importScripts('../../lib/require.js');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In the web worker thread context, 'this' and 'self' can be used as a global
|
|
||||||
* variable namespace similar to the 'window' object in the main thread
|
|
||||||
*/
|
|
||||||
self.onmessage = function(e) {
|
|
||||||
// fetch dependencies via require.js
|
|
||||||
require(['../../require-config'], function() {
|
|
||||||
require.config({
|
|
||||||
baseUrl: '../../lib'
|
|
||||||
});
|
|
||||||
|
|
||||||
require(['cryptoLib/crypto-batch'], function(batch) {
|
|
||||||
|
|
||||||
var output;
|
|
||||||
|
|
||||||
try {
|
|
||||||
output = doOperation(batch, e.data);
|
|
||||||
} catch (e) {
|
|
||||||
output = {
|
|
||||||
err: {
|
|
||||||
errMsg: (e.message) ? e.message : e
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass output back to main thread
|
|
||||||
self.postMessage(output);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function doOperation(batch, i) {
|
|
||||||
var output;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Asymmetric encryption
|
|
||||||
//
|
|
||||||
|
|
||||||
if (i.type === 'asymEncrypt' && i.receiverPubkeys && i.senderPrivkey && i.list) {
|
|
||||||
// start encryption
|
|
||||||
output = batch.encryptListForUser(i.list, i.receiverPubkeys, i.senderPrivkey);
|
|
||||||
|
|
||||||
} else if (i.type === 'asymDecrypt' && i.senderPubkeys && i.receiverPrivkey && i.list) {
|
|
||||||
// start decryption
|
|
||||||
output = batch.decryptListForUser(i.list, i.senderPubkeys, i.receiverPrivkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Symmetric encryption
|
|
||||||
//
|
|
||||||
else if (i.type === 'symEncrypt' && i.list) {
|
|
||||||
// start encryption
|
|
||||||
output = batch.authEncryptList(i.list);
|
|
||||||
|
|
||||||
} else if (i.type === 'symDecrypt' && i.list && i.keys) {
|
|
||||||
// start decryption
|
|
||||||
output = batch.authDecryptList(i.list, i.keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reencryption of asymmetric items to symmetric items
|
|
||||||
//
|
|
||||||
else if (i.type === 'reencrypt' && i.senderPubkeys && i.receiverPrivkey && i.list && i.symKey) {
|
|
||||||
// start validation and re-encryption
|
|
||||||
output = batch.reencryptListKeysForUser(i.list, i.senderPubkeys, i.receiverPrivkey, i.symKey);
|
|
||||||
|
|
||||||
} else if (i.type === 'decryptItems' && i.symKey && i.list) {
|
|
||||||
// start decryption
|
|
||||||
output = batch.decryptKeysAndList(i.list, i.symKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Error
|
|
||||||
//
|
|
||||||
else {
|
|
||||||
output = {
|
|
||||||
err: {
|
|
||||||
errMsg: 'Not all arguments for web worker crypto are defined!'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
}());
|
|
@ -5,120 +5,54 @@
|
|||||||
define(function(require) {
|
define(function(require) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var util = require('cryptoLib/util'),
|
var aes = require('js/crypto/aes-gcm'),
|
||||||
aes = require('cryptoLib/aes-cbc'),
|
|
||||||
rsa = require('cryptoLib/rsa'),
|
|
||||||
cryptoBatch = require('cryptoLib/crypto-batch'),
|
|
||||||
pbkdf2 = require('js/crypto/pbkdf2'),
|
pbkdf2 = require('js/crypto/pbkdf2'),
|
||||||
config = require('js/app-config').config;
|
config = require('js/app-config').config;
|
||||||
|
|
||||||
var passBasedKey,
|
var PBKDF2_WORKER = '/crypto/pbkdf2-worker.js';
|
||||||
BATCH_WORKER = '/crypto/crypto-batch-worker.js',
|
|
||||||
PBKDF2_WORKER = '/crypto/pbkdf2-worker.js';
|
|
||||||
|
|
||||||
var Crypto = function() {
|
var Crypto = function() {};
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the crypto modules by fetching the user's
|
* Encrypt plaintext using AES-GCM.
|
||||||
* encrypted secret key from storage and storing it in memory.
|
* @param {String} plaintext The input string in UTF-16
|
||||||
|
* @param {String} key The base64 encoded key
|
||||||
|
* @param {String} iv The base64 encoded IV
|
||||||
|
* @param {Function} callback(error, ciphertext)
|
||||||
|
* @return {String} The base64 encoded ciphertext
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.init = function(args, callback) {
|
Crypto.prototype.encrypt = function(plaintext, key, iv, callback) {
|
||||||
var self = this;
|
var ct;
|
||||||
|
|
||||||
// valdiate input
|
try {
|
||||||
if (!args.emailAddress || !args.keySize || !args.rsaKeySize || typeof args.password !== 'string' || !args.salt) {
|
ct = aes.encrypt(plaintext, key, iv);
|
||||||
callback({
|
} catch (err) {
|
||||||
errMsg: 'Crypto init failed. Not all args set!'
|
callback(err);
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emailAddress = args.emailAddress;
|
callback(null, ct);
|
||||||
self.keySize = args.keySize;
|
};
|
||||||
self.ivSize = args.keySize;
|
|
||||||
self.rsaKeySize = args.rsaKeySize;
|
|
||||||
|
|
||||||
// derive PBKDF2 from password in web worker thread
|
/**
|
||||||
self.deriveKey(args.password, args.salt, self.keySize, function(err, derivedKey) {
|
* Decrypt ciphertext suing AES-GCM
|
||||||
if (err) {
|
* @param {String} ciphertext The base64 encoded ciphertext
|
||||||
callback(err);
|
* @param {String} key The base64 encoded key
|
||||||
return;
|
* @param {String} iv The base64 encoded IV
|
||||||
}
|
* @param {Function} callback(error, plaintext)
|
||||||
|
* @return {String} The decrypted plaintext in UTF-16
|
||||||
|
*/
|
||||||
|
Crypto.prototype.decrypt = function(ciphertext, key, iv, callback) {
|
||||||
|
var pt;
|
||||||
|
|
||||||
// remember pbkdf2 for later use
|
try {
|
||||||
passBasedKey = derivedKey;
|
pt = aes.decrypt(ciphertext, key, iv);
|
||||||
|
} catch (err) {
|
||||||
// check if key exists
|
callback(err);
|
||||||
if (!args.storedKeypair) {
|
return;
|
||||||
// generate keys, encrypt and persist if none exists
|
|
||||||
generateKeypair(derivedKey);
|
|
||||||
} else {
|
|
||||||
// decrypt key
|
|
||||||
decryptKeypair(args.storedKeypair, derivedKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
function generateKeypair(derivedKey) {
|
|
||||||
// generate RSA keypair in web worker
|
|
||||||
rsa.generateKeypair(self.rsaKeySize, function(err, generatedKeypair) {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypt keypair
|
|
||||||
var iv = util.random(self.ivSize);
|
|
||||||
var encryptedPrivateKey = aes.encrypt(generatedKeypair.privkeyPem, derivedKey, iv);
|
|
||||||
|
|
||||||
// new encrypted keypair object
|
|
||||||
var newKeypair = {
|
|
||||||
publicKey: {
|
|
||||||
_id: generatedKeypair._id,
|
|
||||||
userId: self.emailAddress,
|
|
||||||
publicKey: generatedKeypair.pubkeyPem
|
|
||||||
},
|
|
||||||
privateKey: {
|
|
||||||
_id: generatedKeypair._id,
|
|
||||||
userId: self.emailAddress,
|
|
||||||
encryptedKey: encryptedPrivateKey,
|
|
||||||
iv: iv
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// return generated keypair for storage in keychain dao
|
|
||||||
callback(null, newKeypair);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function decryptKeypair(storedKeypair, derivedKey) {
|
callback(null, pt);
|
||||||
var decryptedPrivateKey;
|
|
||||||
|
|
||||||
// validate input
|
|
||||||
if (!storedKeypair || !storedKeypair.privateKey || !storedKeypair.privateKey.encryptedKey || !storedKeypair.privateKey.iv) {
|
|
||||||
callback({
|
|
||||||
errMsg: 'Incomplete arguments for private key decryption!'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to decrypt with derivedKey
|
|
||||||
try {
|
|
||||||
var prK = storedKeypair.privateKey;
|
|
||||||
decryptedPrivateKey = aes.decrypt(prK.encryptedKey, derivedKey, prK.iv);
|
|
||||||
} catch (ex) {
|
|
||||||
callback({
|
|
||||||
errMsg: 'Wrong password!'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// set rsa keys
|
|
||||||
rsa.init(storedKeypair.publicKey.publicKey, decryptedPrivateKey, storedKeypair.publicKey._id);
|
|
||||||
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,181 +73,6 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
|
||||||
// En/Decrypt a list of items with AES in a WebWorker thread
|
|
||||||
//
|
|
||||||
|
|
||||||
Crypto.prototype.symEncryptList = function(list, callback) {
|
|
||||||
var self = this,
|
|
||||||
key, envelope, envelopes = [];
|
|
||||||
|
|
||||||
// generate single secret key shared for all list items
|
|
||||||
key = util.random(self.keySize);
|
|
||||||
|
|
||||||
// package objects into batchable envelope format
|
|
||||||
list.forEach(function(i) {
|
|
||||||
envelope = {
|
|
||||||
id: i.id,
|
|
||||||
plaintext: i,
|
|
||||||
key: key,
|
|
||||||
iv: util.random(self.ivSize)
|
|
||||||
};
|
|
||||||
envelopes.push(envelope);
|
|
||||||
});
|
|
||||||
|
|
||||||
startWorker({
|
|
||||||
script: BATCH_WORKER,
|
|
||||||
args: {
|
|
||||||
type: 'symEncrypt',
|
|
||||||
list: envelopes
|
|
||||||
},
|
|
||||||
callback: function(err, encryptedList) {
|
|
||||||
// return generated secret key
|
|
||||||
callback(err, {
|
|
||||||
key: key,
|
|
||||||
list: encryptedList
|
|
||||||
});
|
|
||||||
},
|
|
||||||
noWorker: function() {
|
|
||||||
return cryptoBatch.authEncryptList(envelopes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Crypto.prototype.symDecryptList = function(list, keys, callback) {
|
|
||||||
startWorker({
|
|
||||||
script: BATCH_WORKER,
|
|
||||||
args: {
|
|
||||||
type: 'symDecrypt',
|
|
||||||
list: list,
|
|
||||||
keys: keys
|
|
||||||
},
|
|
||||||
callback: callback,
|
|
||||||
noWorker: function() {
|
|
||||||
return cryptoBatch.authDecryptList(list, keys);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// En/Decrypt something speficially using the user's secret key
|
|
||||||
//
|
|
||||||
|
|
||||||
Crypto.prototype.encryptListForUser = function(list, receiverPubkeys, callback) {
|
|
||||||
var self = this,
|
|
||||||
envelope, envelopes = [];
|
|
||||||
|
|
||||||
if (!receiverPubkeys || receiverPubkeys.length !== 1) {
|
|
||||||
callback({
|
|
||||||
errMsg: 'Encryption is currently implemented for only one receiver!'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var keypair = rsa.exportKeys();
|
|
||||||
var senderPrivkey = {
|
|
||||||
_id: keypair._id,
|
|
||||||
privateKey: keypair.privkeyPem
|
|
||||||
};
|
|
||||||
|
|
||||||
// package objects into batchable envelope format
|
|
||||||
list.forEach(function(i) {
|
|
||||||
envelope = {
|
|
||||||
id: i.id,
|
|
||||||
plaintext: i,
|
|
||||||
key: util.random(self.keySize),
|
|
||||||
iv: util.random(self.ivSize),
|
|
||||||
receiverPk: receiverPubkeys[0]._id
|
|
||||||
};
|
|
||||||
envelopes.push(envelope);
|
|
||||||
});
|
|
||||||
|
|
||||||
startWorker({
|
|
||||||
script: BATCH_WORKER,
|
|
||||||
args: {
|
|
||||||
type: 'asymEncrypt',
|
|
||||||
list: envelopes,
|
|
||||||
senderPrivkey: senderPrivkey,
|
|
||||||
receiverPubkeys: receiverPubkeys
|
|
||||||
},
|
|
||||||
callback: callback,
|
|
||||||
noWorker: function() {
|
|
||||||
return cryptoBatch.encryptListForUser(envelopes, receiverPubkeys, senderPrivkey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Crypto.prototype.decryptListForUser = function(list, senderPubkeys, callback) {
|
|
||||||
if (!senderPubkeys || senderPubkeys < 1) {
|
|
||||||
callback({
|
|
||||||
errMsg: 'Sender public keys must be set!'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var keypair = rsa.exportKeys();
|
|
||||||
var receiverPrivkey = {
|
|
||||||
_id: keypair._id,
|
|
||||||
privateKey: keypair.privkeyPem
|
|
||||||
};
|
|
||||||
|
|
||||||
startWorker({
|
|
||||||
script: BATCH_WORKER,
|
|
||||||
args: {
|
|
||||||
type: 'asymDecrypt',
|
|
||||||
list: list,
|
|
||||||
receiverPrivkey: receiverPrivkey,
|
|
||||||
senderPubkeys: senderPubkeys
|
|
||||||
},
|
|
||||||
callback: callback,
|
|
||||||
noWorker: function() {
|
|
||||||
return cryptoBatch.decryptListForUser(list, senderPubkeys, receiverPrivkey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Re-encrypt keys item and items seperately
|
|
||||||
//
|
|
||||||
|
|
||||||
Crypto.prototype.reencryptListKeysForUser = function(list, senderPubkeys, callback) {
|
|
||||||
var keypair = rsa.exportKeys();
|
|
||||||
var receiverPrivkey = {
|
|
||||||
_id: keypair._id,
|
|
||||||
privateKey: keypair.privkeyPem
|
|
||||||
};
|
|
||||||
|
|
||||||
startWorker({
|
|
||||||
script: BATCH_WORKER,
|
|
||||||
args: {
|
|
||||||
type: 'reencrypt',
|
|
||||||
list: list,
|
|
||||||
receiverPrivkey: receiverPrivkey,
|
|
||||||
senderPubkeys: senderPubkeys,
|
|
||||||
symKey: passBasedKey
|
|
||||||
},
|
|
||||||
callback: callback,
|
|
||||||
noWorker: function() {
|
|
||||||
return cryptoBatch.reencryptListKeysForUser(list, senderPubkeys, receiverPrivkey, passBasedKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Crypto.prototype.decryptKeysAndList = function(list, callback) {
|
|
||||||
startWorker({
|
|
||||||
script: BATCH_WORKER,
|
|
||||||
args: {
|
|
||||||
type: 'decryptItems',
|
|
||||||
list: list,
|
|
||||||
symKey: passBasedKey
|
|
||||||
},
|
|
||||||
callback: callback,
|
|
||||||
noWorker: function() {
|
|
||||||
return cryptoBatch.decryptKeysAndList(list, passBasedKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// helper functions
|
// helper functions
|
||||||
//
|
//
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
* A Wrapper for Forge's PBKDF2 function
|
* A Wrapper for Forge's PBKDF2 function
|
||||||
*/
|
*/
|
||||||
define(['node-forge'], function(forge) {
|
define(['forge'], function(forge) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
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 password [String] The password in UTF8
|
* @param {String} password The password in UTF8
|
||||||
* @param salt [String] The base64 encoded salt
|
* @param {String} salt The base64 encoded salt
|
||||||
* @param keySize [Number] The key size in bits
|
* @param {String} keySize The key size in bits
|
||||||
* @return [String] The base64 encoded key
|
* @return {String} The base64 encoded key
|
||||||
*/
|
*/
|
||||||
self.getKey = function(password, salt, keySize) {
|
self.getKey = function(password, salt, keySize) {
|
||||||
var key = forge.pkcs5.pbkdf2(password, forge.util.decode64(salt), 1000, keySize / 8);
|
var saltUtf8 = forge.util.decode64(salt);
|
||||||
var keyBase64 = forge.util.encode64(key);
|
var md = forge.md.sha256.create();
|
||||||
|
var key = forge.pkcs5.pbkdf2(password, saltUtf8, 10000, keySize / 8, md);
|
||||||
|
|
||||||
return keyBase64;
|
return forge.util.encode64(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
});
|
});
|
@ -20,23 +20,23 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate keypair (keytype 1=RSA)
|
// generate keypair
|
||||||
userId = 'Whiteout User <' + options.emailAddress + '>';
|
userId = 'Whiteout User <' + options.emailAddress + '>';
|
||||||
passphrase = (options.passphrase) ? options.passphrase : undefined;
|
passphrase = (options.passphrase) ? options.passphrase : undefined;
|
||||||
openpgp.generateKeyPair(1, options.keySize, userId, passphrase, onGenerated);
|
openpgp.generateKeyPair({
|
||||||
|
keyType: 1, // (keytype 1=RSA)
|
||||||
|
numBits: options.keySize,
|
||||||
|
userId: userId,
|
||||||
|
passphrase: passphrase
|
||||||
|
}, onGenerated);
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
define(function(require) {
|
define(function(require) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var util = require('cryptoLib/util'),
|
var util = require('js/crypto/util'),
|
||||||
_ = require('underscore'),
|
_ = require('underscore'),
|
||||||
config = require('js/app-config').config,
|
config = require('js/app-config').config,
|
||||||
str = require('js/app-config').string;
|
str = require('js/app-config').string;
|
||||||
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -818,7 +818,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;
|
||||||
|
@ -5,13 +5,27 @@
|
|||||||
define(function(require) {
|
define(function(require) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore'),
|
||||||
|
util = require('js/crypto/util'),
|
||||||
|
config = require('js/app-config').config;
|
||||||
|
|
||||||
var KeychainDAO = function(localDbDao, publicKeyDao) {
|
var DB_PUBLICKEY = 'publickey',
|
||||||
|
DB_PRIVATEKEY = 'privatekey',
|
||||||
|
DB_DEVICENAME = 'devicename',
|
||||||
|
DB_DEVICE_SECRET = 'devicesecret';
|
||||||
|
|
||||||
|
var KeychainDAO = function(localDbDao, publicKeyDao, privateKeyDao, crypto, pgp) {
|
||||||
this._localDbDao = localDbDao;
|
this._localDbDao = localDbDao;
|
||||||
this._publicKeyDao = publicKeyDao;
|
this._publicKeyDao = publicKeyDao;
|
||||||
|
this._privateKeyDao = privateKeyDao;
|
||||||
|
this._crypto = crypto;
|
||||||
|
this._pgp = pgp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Public key functions
|
||||||
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the public key of a user o nthe public key store
|
* Verifies the public key of a user o nthe public key store
|
||||||
* @param {String} uuid The uuid to verify the key
|
* @param {String} uuid The uuid to verify the key
|
||||||
@ -170,7 +184,7 @@ define(function(require) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// search local keyring for public key
|
// search local keyring for public key
|
||||||
self._localDbDao.list('publickey', 0, null, function(err, allPubkeys) {
|
self._localDbDao.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
@ -234,6 +248,492 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Device registration functions
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the device's memorable name e.g 'iPhone Work'
|
||||||
|
* @param {String} deviceName The device name
|
||||||
|
* @param {Function} callback(error)
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype.setDeviceName = function(deviceName, callback) {
|
||||||
|
if (!deviceName) {
|
||||||
|
callback(new Error('Please set a device name!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._localDbDao.persist(DB_DEVICENAME, deviceName, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the device' memorable name from local storage. Throws an error if not set
|
||||||
|
* @param {Function} callback(error, deviceName)
|
||||||
|
* @return {String} The device name
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype.getDeviceName = function(callback) {
|
||||||
|
// check if deviceName is already persisted in storage
|
||||||
|
this._localDbDao.read(DB_DEVICENAME, function(err, deviceName) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deviceName) {
|
||||||
|
callback(new Error('Device name not set!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, deviceName);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geneate a device specific key and secret to authenticate to the private key service.
|
||||||
|
* @param {Function} callback(error, deviceSecret:[base64 encoded string])
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype.getDeviceSecret = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// generate random deviceSecret or get from storage
|
||||||
|
self._localDbDao.read(DB_DEVICE_SECRET, function(err, storedDevSecret) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedDevSecret) {
|
||||||
|
// a device key is already available locally
|
||||||
|
callback(null, storedDevSecret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate random deviceSecret
|
||||||
|
var deviceSecret = util.random(config.symKeySize);
|
||||||
|
// persist deviceSecret to local storage (in plaintext)
|
||||||
|
self._localDbDao.persist(DB_DEVICE_SECRET, deviceSecret, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, deviceSecret);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the device on the private key server. This will give the device access to upload an encrypted private key.
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {Function} callback(error)
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype.registerDevice = function(options, callback) {
|
||||||
|
var self = this,
|
||||||
|
devName;
|
||||||
|
|
||||||
|
// check if deviceName is already persisted in storage
|
||||||
|
self.getDeviceName(function(err, deviceName) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestDeviceRegistration(deviceName);
|
||||||
|
});
|
||||||
|
|
||||||
|
function requestDeviceRegistration(deviceName) {
|
||||||
|
devName = deviceName;
|
||||||
|
|
||||||
|
// request device registration session key
|
||||||
|
self._privateKeyDao.requestDeviceRegistration({
|
||||||
|
userId: options.userId,
|
||||||
|
deviceName: deviceName
|
||||||
|
}, function(err, regSessionKey) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!regSessionKey.encryptedRegSessionKey) {
|
||||||
|
callback(new Error('Invalid format for session key!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptSessionKey(regSessionKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptSessionKey(regSessionKey) {
|
||||||
|
self.lookupPublicKey(config.serverPrivateKeyId, function(err, serverPubkey) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serverPubkey || !serverPubkey.publicKey) {
|
||||||
|
callback(new Error('Server public key for device registration not found!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt the session key
|
||||||
|
var ct = regSessionKey.encryptedRegSessionKey;
|
||||||
|
self._pgp.decrypt(ct, serverPubkey.publicKey, function(err, decrypedSessionKey) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadDeviceSecret(decrypedSessionKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadDeviceSecret(regSessionKey) {
|
||||||
|
// read device secret from local storage
|
||||||
|
self.getDeviceSecret(function(err, deviceSecret) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate iv
|
||||||
|
var iv = util.random(config.symIvSize);
|
||||||
|
// encrypt deviceSecret
|
||||||
|
self._crypto.encrypt(deviceSecret, regSessionKey, iv, function(err, encryptedDeviceSecret) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// upload encryptedDeviceSecret
|
||||||
|
self._privateKeyDao.uploadDeviceSecret({
|
||||||
|
userId: options.userId,
|
||||||
|
deviceName: devName,
|
||||||
|
encryptedDeviceSecret: encryptedDeviceSecret,
|
||||||
|
iv: iv
|
||||||
|
}, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Private key functions
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate to the private key server (required before private PGP key upload).
|
||||||
|
* @param {String} userId The user's email address
|
||||||
|
* @param {Function} callback(error, authSessionKey)
|
||||||
|
* @return {Object} {sessionId:String, sessionKey:[base64 encoded]}
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype._authenticateToPrivateKeyServer = function(userId, callback) {
|
||||||
|
var self = this,
|
||||||
|
sessionId;
|
||||||
|
|
||||||
|
// request auth session key required for upload
|
||||||
|
self._privateKeyDao.requestAuthSessionKey({
|
||||||
|
userId: userId
|
||||||
|
}, function(err, authSessionKey) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authSessionKey.encryptedAuthSessionKey || !authSessionKey.encryptedChallenge || !authSessionKey.sessionId) {
|
||||||
|
callback(new Error('Invalid format for session key!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember session id for verification
|
||||||
|
sessionId = authSessionKey.sessionId;
|
||||||
|
|
||||||
|
decryptSessionKey(authSessionKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
function decryptSessionKey(authSessionKey) {
|
||||||
|
self.lookupPublicKey(config.serverPrivateKeyId, function(err, serverPubkey) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serverPubkey || !serverPubkey.publicKey) {
|
||||||
|
callback(new Error('Server public key for authentication not found!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt the session key
|
||||||
|
var ct1 = authSessionKey.encryptedAuthSessionKey;
|
||||||
|
self._pgp.decrypt(ct1, serverPubkey.publicKey, function(err, decryptedSessionKey) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt the challenge
|
||||||
|
var ct2 = authSessionKey.encryptedChallenge;
|
||||||
|
self._pgp.decrypt(ct2, serverPubkey.publicKey, function(err, decryptedChallenge) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptChallenge(decryptedSessionKey, decryptedChallenge);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function encryptChallenge(sessionKey, challenge) {
|
||||||
|
// get device secret
|
||||||
|
self.getDeviceSecret(function(err, deviceSecret) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var iv = util.random(config.symIvSize);
|
||||||
|
// encrypt the challenge
|
||||||
|
self._crypto.encrypt(challenge, sessionKey, iv, function(err, encryptedChallenge) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt the device secret
|
||||||
|
self._crypto.encrypt(deviceSecret, sessionKey, iv, function(err, encryptedDeviceSecret) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
replyChallenge({
|
||||||
|
encryptedChallenge: encryptedChallenge,
|
||||||
|
encryptedDeviceSecret: encryptedDeviceSecret,
|
||||||
|
iv: iv
|
||||||
|
}, sessionKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replyChallenge(response, sessionKey) {
|
||||||
|
// respond to challenge by uploading the with the session key encrypted challenge
|
||||||
|
self._privateKeyDao.verifyAuthentication({
|
||||||
|
userId: userId,
|
||||||
|
sessionId: sessionId,
|
||||||
|
encryptedChallenge: response.encryptedChallenge,
|
||||||
|
encryptedDeviceSecret: response.encryptedDeviceSecret,
|
||||||
|
iv: response.iv
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, {
|
||||||
|
sessionId: sessionId,
|
||||||
|
sessionKey: sessionKey
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt and upload the private PGP key to the server.
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {String} options.code The randomly generated or self selected code used to derive the key for the encryption of the private PGP key
|
||||||
|
* @param {Function} callback(error)
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype.uploadPrivateKey = function(options, callback) {
|
||||||
|
var self = this,
|
||||||
|
keySize = config.symKeySize,
|
||||||
|
salt;
|
||||||
|
|
||||||
|
if (!options.userId || !options.code) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deriveKey(options.code);
|
||||||
|
|
||||||
|
function deriveKey(code) {
|
||||||
|
// generate random salt
|
||||||
|
salt = util.random(keySize);
|
||||||
|
// derive key from the code using PBKDF2
|
||||||
|
self._crypto.deriveKey(code, salt, keySize, function(err, key) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptPrivateKey(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function encryptPrivateKey(encryptionKey) {
|
||||||
|
// get private key from local storage
|
||||||
|
self.getUserKeyPair(options.userId, function(err, keypair) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var privkeyId = keypair.privateKey._id,
|
||||||
|
pgpBlock = keypair.privateKey.encryptedKey;
|
||||||
|
|
||||||
|
// encrypt the private key with the derived key
|
||||||
|
var iv = util.random(config.symIvSize);
|
||||||
|
self._crypto.encrypt(pgpBlock, encryptionKey, iv, function(err, ct) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = {
|
||||||
|
_id: privkeyId,
|
||||||
|
userId: options.userId,
|
||||||
|
encryptedPrivateKey: ct,
|
||||||
|
salt: salt,
|
||||||
|
iv: iv
|
||||||
|
};
|
||||||
|
|
||||||
|
uploadPrivateKey(payload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadPrivateKey(payload) {
|
||||||
|
// authenticate to server for upload
|
||||||
|
self._authenticateToPrivateKeyServer(options.userId, function(err, authSessionKey) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt encryptedPrivateKey again using authSessionKey
|
||||||
|
var pt = payload.encryptedPrivateKey,
|
||||||
|
iv = payload.iv,
|
||||||
|
key = authSessionKey.sessionKey;
|
||||||
|
self._crypto.encrypt(pt, key, iv, function(err, ct) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace the encryptedPrivateKey with the double wrapped ciphertext
|
||||||
|
payload.encryptedPrivateKey = ct;
|
||||||
|
// set sessionId
|
||||||
|
payload.sessionId = authSessionKey.sessionId;
|
||||||
|
|
||||||
|
// upload the encrypted priavet key
|
||||||
|
self._privateKeyDao.upload(payload, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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} options.userId The user's email address
|
||||||
|
* @param {String} options.keyId The private PGP key id
|
||||||
|
* @param {Function} callback(error)
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype.requestPrivateKeyDownload = function(options, callback) {
|
||||||
|
this._privateKeyDao.requestDownload(options, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the encrypted private PGP key from the server using the recovery token.
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {String} options.keyId The user's email address
|
||||||
|
* @param {String} options.recoveryToken The recovery token acquired via email/sms from the key server
|
||||||
|
* @param {Function} callback(error, encryptedPrivateKey)
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype.downloadPrivateKey = function(options, callback) {
|
||||||
|
this._privateKeyDao.download(options, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.code The randomly generated or self selected code used to derive the key for the decryption of the private PGP key
|
||||||
|
* @param {String} options.encryptedPrivateKey The encrypted private PGP key
|
||||||
|
* @param {String} options.salt The salt required to derive the code derived key
|
||||||
|
* @param {String} options.iv The iv used to encrypt the private PGP key
|
||||||
|
* @param {Function} callback(error, keyObject)
|
||||||
|
*/
|
||||||
|
KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(options, callback) {
|
||||||
|
var self = this,
|
||||||
|
code = options.code,
|
||||||
|
salt = options.salt,
|
||||||
|
keySize = config.symKeySize;
|
||||||
|
|
||||||
|
if (!options._id || !options.userId || !options.code || !options.salt || !options.encryptedPrivateKey || !options.iv) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// derive key from the code and the salt using PBKDF2
|
||||||
|
self._crypto.deriveKey(code, salt, keySize, function(err, key) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptAndStore(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
function decryptAndStore(derivedKey) {
|
||||||
|
// decrypt the private key with the derived key
|
||||||
|
var ct = options.encryptedPrivateKey,
|
||||||
|
iv = options.iv;
|
||||||
|
|
||||||
|
self._crypto.decrypt(ct, derivedKey, iv, function(err, privateKeyArmored) {
|
||||||
|
if (err) {
|
||||||
|
callback(new Error('Invalid keychain code!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate pgp key
|
||||||
|
var keyParams;
|
||||||
|
try {
|
||||||
|
keyParams = self._pgp.getKeyParams(privateKeyArmored);
|
||||||
|
} catch (e) {
|
||||||
|
callback(new Error('Error parsing private PGP key!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyParams._id !== options._id || keyParams.userId !== options.userId) {
|
||||||
|
callback(new Error('Private key parameters don\'t match with public key\'s!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyObject = {
|
||||||
|
_id: options._id,
|
||||||
|
userId: options.userId,
|
||||||
|
encryptedKey: privateKeyArmored
|
||||||
|
};
|
||||||
|
|
||||||
|
// store private key locally
|
||||||
|
self.saveLocalPrivateKey(keyObject, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, keyObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Keypair functions
|
||||||
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the local user's key either from local storage
|
* Gets the local user's key either from local storage
|
||||||
* or fetches it from the cloud. The private key is encrypted.
|
* or fetches it from the cloud. The private key is encrypted.
|
||||||
@ -244,7 +744,7 @@ define(function(require) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// search for user's public key locally
|
// search for user's public key locally
|
||||||
self._localDbDao.list('publickey', 0, null, function(err, allPubkeys) {
|
self._localDbDao.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
@ -364,7 +864,7 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lookup in local storage
|
// lookup in local storage
|
||||||
self._localDbDao.read('publickey_' + id, function(err, pubkey) {
|
self._localDbDao.read(DB_PUBLICKEY + '_' + id, function(err, pubkey) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
@ -400,29 +900,29 @@ define(function(require) {
|
|||||||
*/
|
*/
|
||||||
KeychainDAO.prototype.listLocalPublicKeys = function(callback) {
|
KeychainDAO.prototype.listLocalPublicKeys = function(callback) {
|
||||||
// search local keyring for public key
|
// search local keyring for public key
|
||||||
this._localDbDao.list('publickey', 0, null, callback);
|
this._localDbDao.list(DB_PUBLICKEY, 0, null, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
KeychainDAO.prototype.removeLocalPublicKey = function(id, callback) {
|
KeychainDAO.prototype.removeLocalPublicKey = function(id, callback) {
|
||||||
this._localDbDao.remove('publickey_' + id, callback);
|
this._localDbDao.remove(DB_PUBLICKEY + '_' + id, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
KeychainDAO.prototype.lookupPrivateKey = function(id, callback) {
|
KeychainDAO.prototype.lookupPrivateKey = function(id, callback) {
|
||||||
// lookup in local storage
|
// lookup in local storage
|
||||||
this._localDbDao.read('privatekey_' + id, callback);
|
this._localDbDao.read(DB_PRIVATEKEY + '_' + id, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
KeychainDAO.prototype.saveLocalPublicKey = function(pubkey, callback) {
|
KeychainDAO.prototype.saveLocalPublicKey = function(pubkey, callback) {
|
||||||
// persist public key (email, _id)
|
// persist public key (email, _id)
|
||||||
var pkLookupKey = 'publickey_' + pubkey._id;
|
var pkLookupKey = DB_PUBLICKEY + '_' + pubkey._id;
|
||||||
this._localDbDao.persist(pkLookupKey, pubkey, callback);
|
this._localDbDao.persist(pkLookupKey, pubkey, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
KeychainDAO.prototype.saveLocalPrivateKey = function(privkey, callback) {
|
KeychainDAO.prototype.saveLocalPrivateKey = function(privkey, callback) {
|
||||||
// persist private key (email, _id)
|
// persist private key (email, _id)
|
||||||
var prkLookupKey = 'privatekey_' + privkey._id;
|
var prkLookupKey = DB_PRIVATEKEY + '_' + privkey._id;
|
||||||
this._localDbDao.persist(prkLookupKey, privkey, callback);
|
this._localDbDao.persist(prkLookupKey, privkey, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
return KeychainDAO;
|
return KeychainDAO;
|
||||||
});
|
});
|
||||||
|
170
src/js/dao/privatekey-dao.js
Normal file
170
src/js/dao/privatekey-dao.js
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
define(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PrivateKeyDAO = function(restDao) {
|
||||||
|
this._restDao = restDao;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Device registration functions
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request registration of a new device by fetching registration session key.
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {String} options.deviceName The device's memorable name
|
||||||
|
* @param {Function} callback(error, regSessionKey)
|
||||||
|
* @return {Object} {encryptedRegSessionKey:[base64]}
|
||||||
|
*/
|
||||||
|
PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback) {
|
||||||
|
var uri;
|
||||||
|
|
||||||
|
if (!options.userId || !options.deviceName) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
|
||||||
|
this._restDao.post(undefined, uri, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate device registration by uploading the deviceSecret encrypted with the regSessionKeys.
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {String} options.deviceName The device's memorable name
|
||||||
|
* @param {String} options.encryptedDeviceSecret The base64 encoded encrypted device secret
|
||||||
|
* @param {String} options.iv The iv used for encryption
|
||||||
|
* @param {Function} callback(error)
|
||||||
|
*/
|
||||||
|
PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
|
||||||
|
var uri;
|
||||||
|
|
||||||
|
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
|
||||||
|
this._restDao.put(options, uri, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Private key functions
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request authSessionKeys required for upload the encrypted private PGP key.
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {Function} callback(error, authSessionKey)
|
||||||
|
* @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]}
|
||||||
|
*/
|
||||||
|
PrivateKeyDAO.prototype.requestAuthSessionKey = function(options, callback) {
|
||||||
|
var uri;
|
||||||
|
|
||||||
|
if (!options.userId) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/auth/user/' + options.userId;
|
||||||
|
this._restDao.post(undefined, uri, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.encryptedChallenge The server's base64 encoded challenge encrypted using the authSessionKey
|
||||||
|
* @param {String} options.encryptedDeviceSecret The server's base64 encoded deviceSecret encrypted using the authSessionKey
|
||||||
|
* @param {String} options.iv The iv used for encryption
|
||||||
|
* @param {Function} callback(error)
|
||||||
|
*/
|
||||||
|
PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
|
||||||
|
var uri;
|
||||||
|
|
||||||
|
if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/auth/user/' + options.userId + '/session/' + options.sessionId;
|
||||||
|
this._restDao.put(options, uri, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload the encrypted private PGP key.
|
||||||
|
* @param {String} options._id The hex encoded capital 16 char key id
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {String} options.encryptedPrivateKey The base64 encoded encrypted private PGP key
|
||||||
|
* @param {String} options.sessionId The session id
|
||||||
|
* @param {Function} callback(error)
|
||||||
|
*/
|
||||||
|
PrivateKeyDAO.prototype.upload = function(options, callback) {
|
||||||
|
var uri;
|
||||||
|
|
||||||
|
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId;
|
||||||
|
this._restDao.post(options, uri, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request download for the encrypted private PGP key.
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {String} options.keyId The private PGP key id
|
||||||
|
* @param {Function} callback(error, found)
|
||||||
|
* @return {Boolean} weather the key was found on the server or not.
|
||||||
|
*/
|
||||||
|
PrivateKeyDAO.prototype.requestDownload = function(options, callback) {
|
||||||
|
var uri;
|
||||||
|
|
||||||
|
if (!options.userId || !options.keyId) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId;
|
||||||
|
this._restDao.get({
|
||||||
|
uri: uri
|
||||||
|
}, 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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the download request for the private PGP key using the recovery token sent via email. This downloads the actual encrypted private key.
|
||||||
|
* @param {String} options.userId The user's email address
|
||||||
|
* @param {String} options.keyId The private key id
|
||||||
|
* @param {String} options.recoveryToken The token proving the user own the email account
|
||||||
|
* @param {Function} callback(error, encryptedPrivateKey)
|
||||||
|
* @return {Object} {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], encryptedUserId: [base64 encoded]}
|
||||||
|
*/
|
||||||
|
PrivateKeyDAO.prototype.download = function(options, callback) {
|
||||||
|
var uri;
|
||||||
|
|
||||||
|
if (!options.userId || !options.keyId || !options.recoveryToken) {
|
||||||
|
callback(new Error('Incomplete arguments!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId + '/recovery/' + options.recoveryToken;
|
||||||
|
this._restDao.get({
|
||||||
|
uri: uri
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
return PrivateKeyDAO;
|
||||||
|
});
|
@ -17,7 +17,48 @@ define(function(require) {
|
|||||||
* @param {String} options.type (optional) The type of data that you're expecting back from the server: json, xml, text. Default: json.
|
* @param {String} options.type (optional) The type of data that you're expecting back from the server: json, xml, text. Default: json.
|
||||||
*/
|
*/
|
||||||
RestDAO.prototype.get = function(options, callback) {
|
RestDAO.prototype.get = function(options, callback) {
|
||||||
var xhr, acceptHeader;
|
options.method = 'GET';
|
||||||
|
this._processRequest(options, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST (create) request
|
||||||
|
*/
|
||||||
|
RestDAO.prototype.post = function(item, uri, callback) {
|
||||||
|
this._processRequest({
|
||||||
|
method: 'POST',
|
||||||
|
payload: item,
|
||||||
|
uri: uri
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT (update) request
|
||||||
|
*/
|
||||||
|
RestDAO.prototype.put = function(item, uri, callback) {
|
||||||
|
this._processRequest({
|
||||||
|
method: 'PUT',
|
||||||
|
payload: item,
|
||||||
|
uri: uri
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE (remove) request
|
||||||
|
*/
|
||||||
|
RestDAO.prototype.remove = function(uri, callback) {
|
||||||
|
this._processRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
uri: uri
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// helper functions
|
||||||
|
//
|
||||||
|
|
||||||
|
RestDAO.prototype._processRequest = function(options, callback) {
|
||||||
|
var xhr, format;
|
||||||
|
|
||||||
if (typeof options.uri === 'undefined') {
|
if (typeof options.uri === 'undefined') {
|
||||||
callback({
|
callback({
|
||||||
@ -30,11 +71,11 @@ define(function(require) {
|
|||||||
options.type = options.type || 'json';
|
options.type = options.type || 'json';
|
||||||
|
|
||||||
if (options.type === 'json') {
|
if (options.type === 'json') {
|
||||||
acceptHeader = 'application/json';
|
format = 'application/json';
|
||||||
} else if (options.type === 'xml') {
|
} else if (options.type === 'xml') {
|
||||||
acceptHeader = 'application/xml';
|
format = 'application/xml';
|
||||||
} else if (options.type === 'text') {
|
} else if (options.type === 'text') {
|
||||||
acceptHeader = 'text/plain';
|
format = 'text/plain';
|
||||||
} else {
|
} else {
|
||||||
callback({
|
callback({
|
||||||
code: 400,
|
code: 400,
|
||||||
@ -44,14 +85,16 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
xhr = new XMLHttpRequest();
|
xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', this._baseUri + options.uri);
|
xhr.open(options.method, this._baseUri + options.uri);
|
||||||
xhr.setRequestHeader('Accept', acceptHeader);
|
xhr.setRequestHeader('Accept', format);
|
||||||
|
xhr.setRequestHeader('Content-Type', format);
|
||||||
|
|
||||||
xhr.onload = function() {
|
xhr.onload = function() {
|
||||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
var res;
|
||||||
var res;
|
|
||||||
|
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201 || xhr.status === 304)) {
|
||||||
if (options.type === 'json') {
|
if (options.type === 'json') {
|
||||||
res = JSON.parse(xhr.responseText);
|
res = xhr.responseText ? JSON.parse(xhr.responseText) : xhr.responseText;
|
||||||
} else {
|
} else {
|
||||||
res = xhr.responseText;
|
res = xhr.responseText;
|
||||||
}
|
}
|
||||||
@ -69,72 +112,11 @@ define(function(require) {
|
|||||||
xhr.onerror = function() {
|
xhr.onerror = function() {
|
||||||
callback({
|
callback({
|
||||||
code: 42,
|
code: 42,
|
||||||
errMsg: 'Error calling GET on ' + options.uri
|
errMsg: 'Error calling ' + options.method + ' on ' + options.uri
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send();
|
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined);
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PUT (create/update) request
|
|
||||||
*/
|
|
||||||
RestDAO.prototype.put = function(item, uri, callback) {
|
|
||||||
var xhr;
|
|
||||||
|
|
||||||
xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('PUT', this._baseUri + uri);
|
|
||||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201 || xhr.status === 304)) {
|
|
||||||
callback(null, xhr.responseText, xhr.status);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback({
|
|
||||||
code: xhr.status,
|
|
||||||
errMsg: xhr.statusText
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function() {
|
|
||||||
callback({
|
|
||||||
errMsg: 'Error calling PUT on ' + uri
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send(JSON.stringify(item));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE (remove) request
|
|
||||||
*/
|
|
||||||
RestDAO.prototype.remove = function(uri, callback) {
|
|
||||||
var xhr;
|
|
||||||
|
|
||||||
xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('DELETE', this._baseUri + uri);
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
||||||
callback(null, xhr.responseText, xhr.status);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback({
|
|
||||||
code: xhr.status,
|
|
||||||
errMsg: xhr.statusText
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function() {
|
|
||||||
callback({
|
|
||||||
errMsg: 'Error calling DELETE on ' + uri
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return RestDAO;
|
return RestDAO;
|
||||||
|
2
src/lib/forge/forge.min.js
vendored
2
src/lib/forge/forge.min.js
vendored
File diff suppressed because one or more lines are too long
12
src/lib/openpgp/openpgp.min.js
vendored
12
src/lib/openpgp/openpgp.min.js
vendored
File diff suppressed because one or more lines are too long
2
src/lib/openpgp/openpgp.worker.min.js
vendored
2
src/lib/openpgp/openpgp.worker.min.js
vendored
@ -1 +1 @@
|
|||||||
/*! OpenPGPjs.org this is LGPL licensed code, see LICENSE/our website for more information.- v0.5.1 - 2014-04-03 */!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(){function a(a){window.openpgp.crypto.random.randomBuffer.size<d&&postMessage({event:"request-seed"}),postMessage(a)}function b(a){var b=window.openpgp.packet.List.fromStructuredClone(a);return new window.openpgp.key.Key(b)}function c(a){var b=window.openpgp.packet.List.fromStructuredClone(a);return new window.openpgp.message.Message(b)}window={},importScripts("openpgp.min.js");var d=4e4,e=6e4;window.openpgp.crypto.random.randomBuffer.init(e),onmessage=function(d){var e=null,f=null,g=d.data,h=!1;switch(g.event){case"seed-random":g.buf instanceof Uint8Array||(g.buf=new Uint8Array(g.buf)),window.openpgp.crypto.random.randomBuffer.set(g.buf);break;case"encrypt-message":try{g.keys=g.keys.map(b),e=window.openpgp.encryptMessage(g.keys,g.text)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"sign-and-encrypt-message":try{g.publicKeys=g.publicKeys.map(b),g.privateKey=b(g.privateKey),e=window.openpgp.signAndEncryptMessage(g.publicKeys,g.privateKey,g.text)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"decrypt-message":try{g.privateKey=b(g.privateKey),g.message=c(g.message.packets),e=window.openpgp.decryptMessage(g.privateKey,g.message)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"decrypt-and-verify-message":try{g.privateKey=b(g.privateKey),g.publicKeys=g.publicKeys.map(b),g.message=c(g.message.packets),e=window.openpgp.decryptAndVerifyMessage(g.privateKey,g.publicKeys,g.message)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"sign-clear-message":try{g.privateKeys=g.privateKeys.map(b),e=window.openpgp.signClearMessage(g.privateKeys,g.text)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"verify-clear-signed-message":try{g.publicKeys=g.publicKeys.map(b);var j=window.openpgp.packet.List.fromStructuredClone(g.message.packets);g.message=new window.openpgp.cleartext.CleartextMessage(g.message.text,j),e=window.openpgp.verifyClearSignedMessage(g.publicKeys,g.message)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"generate-key-pair":try{e=window.openpgp.generateKeyPair(g.keyType,g.numBits,g.userId,g.passphrase),e.key=e.key.toPacketlist()}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"decrypt-key":try{g.privateKey=b(g.privateKey),h=g.privateKey.decrypt(g.password),h?e=g.privateKey.toPacketlist():f="Wrong password"}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"decrypt-key-packet":try{g.privateKey=b(g.privateKey),g.keyIds=g.keyIds.map(window.openpgp.Keyid.fromClone),h=g.privateKey.decryptKeyPacket(g.keyIds,g.password),h?e=g.privateKey.toPacketlist():f="Wrong password"}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;default:throw new Error("Unknown Worker Event.")}}},{}]},{},[1]);
|
/*! OpenPGPjs.org this is LGPL licensed code, see LICENSE/our website for more information.- v0.6.0 - 2014-05-09 */!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(){function a(a){window.openpgp.crypto.random.randomBuffer.size<d&&postMessage({event:"request-seed"}),postMessage(a)}function b(a){var b=window.openpgp.packet.List.fromStructuredClone(a);return new window.openpgp.key.Key(b)}function c(a){var b=window.openpgp.packet.List.fromStructuredClone(a);return new window.openpgp.message.Message(b)}window={},importScripts("openpgp.min.js");var d=4e4,e=6e4;window.openpgp.crypto.random.randomBuffer.init(e),onmessage=function(d){var e=null,f=null,g=d.data,h=!1;switch(g.event){case"seed-random":g.buf instanceof Uint8Array||(g.buf=new Uint8Array(g.buf)),window.openpgp.crypto.random.randomBuffer.set(g.buf);break;case"encrypt-message":try{g.keys=g.keys.map(b),e=window.openpgp.encryptMessage(g.keys,g.text)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"sign-and-encrypt-message":try{g.publicKeys=g.publicKeys.map(b),g.privateKey=b(g.privateKey),e=window.openpgp.signAndEncryptMessage(g.publicKeys,g.privateKey,g.text)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"decrypt-message":try{g.privateKey=b(g.privateKey),g.message=c(g.message.packets),e=window.openpgp.decryptMessage(g.privateKey,g.message)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"decrypt-and-verify-message":try{g.privateKey=b(g.privateKey),g.publicKeys=g.publicKeys.map(b),g.message=c(g.message.packets),e=window.openpgp.decryptAndVerifyMessage(g.privateKey,g.publicKeys,g.message)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"sign-clear-message":try{g.privateKeys=g.privateKeys.map(b),e=window.openpgp.signClearMessage(g.privateKeys,g.text)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"verify-clear-signed-message":try{g.publicKeys=g.publicKeys.map(b);var j=window.openpgp.packet.List.fromStructuredClone(g.message.packets);g.message=new window.openpgp.cleartext.CleartextMessage(g.message.text,j),e=window.openpgp.verifyClearSignedMessage(g.publicKeys,g.message)}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"generate-key-pair":try{e=window.openpgp.generateKeyPair(g.options),e.key=e.key.toPacketlist()}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"decrypt-key":try{g.privateKey=b(g.privateKey),h=g.privateKey.decrypt(g.password),h?e=g.privateKey.toPacketlist():f="Wrong password"}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;case"decrypt-key-packet":try{g.privateKey=b(g.privateKey),g.keyIds=g.keyIds.map(window.openpgp.Keyid.fromClone),h=g.privateKey.decryptKeyPacket(g.keyIds,g.password),h?e=g.privateKey.toPacketlist():f="Wrong password"}catch(i){f=i.message}a({event:"method-return",data:e,err:f});break;default:throw new Error("Unknown Worker Event.")}}},{}]},{},[1]);
|
@ -7,7 +7,6 @@
|
|||||||
paths: {
|
paths: {
|
||||||
js: '../js',
|
js: '../js',
|
||||||
test: '../../test',
|
test: '../../test',
|
||||||
cryptoLib: '../js/crypto',
|
|
||||||
underscore: 'underscore/underscore-min',
|
underscore: 'underscore/underscore-min',
|
||||||
lawnchair: 'lawnchair/lawnchair-git',
|
lawnchair: 'lawnchair/lawnchair-git',
|
||||||
lawnchairSQL: 'lawnchair/lawnchair-adapter-webkit-sqlite-git',
|
lawnchairSQL: 'lawnchair/lawnchair-adapter-webkit-sqlite-git',
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -66,7 +66,6 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 $nav-padding;
|
padding: 0 $nav-padding;
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
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>
|
||||||
|
30
src/tpl/login-privatekey-download.html
Normal file
30
src/tpl/login-privatekey-download.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<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>
|
@ -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 -->
|
@ -1,105 +0,0 @@
|
|||||||
define(function(require) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var LawnchairDAO = require('js/dao/lawnchair-dao'),
|
|
||||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
|
||||||
expect = chai.expect;
|
|
||||||
|
|
||||||
var testUser = 'test@example.com';
|
|
||||||
|
|
||||||
describe('Device Storage DAO unit tests', function() {
|
|
||||||
|
|
||||||
var storageDao, lawnchairDaoStub;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
|
|
||||||
storageDao = new DeviceStorageDAO(lawnchairDaoStub);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {});
|
|
||||||
|
|
||||||
describe('init', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
lawnchairDaoStub.init.yields();
|
|
||||||
|
|
||||||
storageDao.init(testUser, function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.init.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('store list', function() {
|
|
||||||
it('should fail', function(done) {
|
|
||||||
var list = [{}];
|
|
||||||
|
|
||||||
storageDao.storeList(list, '', function(err) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with empty list', function(done) {
|
|
||||||
var list = [];
|
|
||||||
|
|
||||||
storageDao.storeList(list, 'email', function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work', function(done) {
|
|
||||||
lawnchairDaoStub.batch.yields();
|
|
||||||
|
|
||||||
var list = [{
|
|
||||||
foo: 'bar'
|
|
||||||
}];
|
|
||||||
|
|
||||||
storageDao.storeList(list, 'email', function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.batch.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('remove list', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
lawnchairDaoStub.removeList.yields();
|
|
||||||
|
|
||||||
storageDao.removeList('email', function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.removeList.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('list items', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields();
|
|
||||||
|
|
||||||
storageDao.listItems('email', 0, null, function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('clear', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
lawnchairDaoStub.clear.yields();
|
|
||||||
|
|
||||||
storageDao.clear(function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.clear.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html style="overflow-y: auto">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>JavaScript Unit Tests</title>
|
|
||||||
<link rel="stylesheet" href="../lib/mocha.css" />
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="mocha"></div>
|
|
||||||
|
|
||||||
<script src="../lib/chai.js"></script>
|
|
||||||
<script src="../lib/sinon.js"></script>
|
|
||||||
<script src="../lib/mocha.js"></script>
|
|
||||||
|
|
||||||
<script data-main="main.js" src="../../src/lib/require.js"></script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,625 +0,0 @@
|
|||||||
define(function(require) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var LawnchairDAO = require('js/dao/lawnchair-dao'),
|
|
||||||
PublicKeyDAO = require('js/dao/publickey-dao'),
|
|
||||||
KeychainDAO = require('js/dao/keychain-dao'),
|
|
||||||
expect = chai.expect;
|
|
||||||
|
|
||||||
var testUser = 'test@example.com';
|
|
||||||
|
|
||||||
describe('Keychain DAO unit tests', function() {
|
|
||||||
|
|
||||||
var keychainDao, lawnchairDaoStub, pubkeyDaoStub;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
|
|
||||||
pubkeyDaoStub = sinon.createStubInstance(PublicKeyDAO);
|
|
||||||
keychainDao = new KeychainDAO(lawnchairDaoStub, pubkeyDaoStub);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {});
|
|
||||||
|
|
||||||
describe('verify public key', function() {
|
|
||||||
it('should verify public key', function(done) {
|
|
||||||
var uuid = 'asdfasdfasdfasdf';
|
|
||||||
pubkeyDaoStub.verify.yields();
|
|
||||||
|
|
||||||
keychainDao.verifyPublicKey(uuid, function() {
|
|
||||||
expect(pubkeyDaoStub.verify.calledWith(uuid)).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('listLocalPublicKeys', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
lawnchairDaoStub.list.withArgs('publickey', 0, null).yields();
|
|
||||||
|
|
||||||
keychainDao.listLocalPublicKeys(function() {
|
|
||||||
expect(lawnchairDaoStub.list.callCount).to.equal(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('removeLocalPublicKey', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
var id = 'asdf';
|
|
||||||
|
|
||||||
lawnchairDaoStub.remove.withArgs('publickey_' + id).yields();
|
|
||||||
|
|
||||||
keychainDao.removeLocalPublicKey(id, function() {
|
|
||||||
expect(lawnchairDaoStub.remove.callCount).to.equal(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('refreshKeyForUserId', function() {
|
|
||||||
var getPubKeyStub,
|
|
||||||
oldKey = {
|
|
||||||
_id: 123
|
|
||||||
},
|
|
||||||
newKey = {
|
|
||||||
_id: 456
|
|
||||||
},
|
|
||||||
importedKey = {
|
|
||||||
_id: 789,
|
|
||||||
imported: true
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
getPubKeyStub = sinon.stub(keychainDao, 'getReceiverPublicKey');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
keychainDao.getReceiverPublicKey.restore();
|
|
||||||
delete keychainDao.requestPermissionForKeyUpdate;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not find a key', function(done) {
|
|
||||||
getPubKeyStub.yields();
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not update the key when up to date', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields(null, oldKey);
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.to.equal(oldKey);
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update key', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields();
|
|
||||||
pubkeyDaoStub.getByUserId.withArgs(testUser).yields(null, newKey);
|
|
||||||
keychainDao.requestPermissionForKeyUpdate = function(opts, cb) {
|
|
||||||
expect(opts.userId).to.equal(testUser);
|
|
||||||
expect(opts.newKey).to.equal(newKey);
|
|
||||||
cb(true);
|
|
||||||
};
|
|
||||||
lawnchairDaoStub.remove.withArgs('publickey_' + oldKey._id).yields();
|
|
||||||
lawnchairDaoStub.persist.withArgs('publickey_' + newKey._id, newKey).yields();
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.equal(newKey);
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.remove.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.persist.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove key', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields();
|
|
||||||
pubkeyDaoStub.getByUserId.withArgs(testUser).yields();
|
|
||||||
keychainDao.requestPermissionForKeyUpdate = function(opts, cb) {
|
|
||||||
expect(opts.userId).to.equal(testUser);
|
|
||||||
expect(opts.newKey).to.not.exist;
|
|
||||||
cb(true);
|
|
||||||
};
|
|
||||||
lawnchairDaoStub.remove.withArgs('publickey_' + oldKey._id).yields();
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.remove.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.persist.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should go offline while fetching new key', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields();
|
|
||||||
pubkeyDaoStub.getByUserId.withArgs(testUser).yields({
|
|
||||||
code: 42
|
|
||||||
});
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.to.equal(oldKey);
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.remove.called).to.be.false;
|
|
||||||
expect(lawnchairDaoStub.persist.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not remove old key on user rejection', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields();
|
|
||||||
pubkeyDaoStub.getByUserId.withArgs(testUser).yields(null, newKey);
|
|
||||||
keychainDao.requestPermissionForKeyUpdate = function(opts, cb) {
|
|
||||||
expect(opts.userId).to.equal(testUser);
|
|
||||||
expect(opts.newKey).to.exist;
|
|
||||||
cb(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.equal(oldKey);
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.remove.called).to.be.false;
|
|
||||||
expect(lawnchairDaoStub.persist.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not remove manually imported key', function(done) {
|
|
||||||
getPubKeyStub.yields(null, importedKey);
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.equal(importedKey);
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update not the key when offline', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields({
|
|
||||||
code: 42
|
|
||||||
});
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.to.equal(oldKey);
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.called).to.be.false;
|
|
||||||
expect(lawnchairDaoStub.remove.called).to.be.false;
|
|
||||||
expect(lawnchairDaoStub.persist.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should error while persisting new key', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields();
|
|
||||||
pubkeyDaoStub.getByUserId.withArgs(testUser).yields(null, newKey);
|
|
||||||
keychainDao.requestPermissionForKeyUpdate = function(opts, cb) {
|
|
||||||
expect(opts.userId).to.equal(testUser);
|
|
||||||
expect(opts.newKey).to.equal(newKey);
|
|
||||||
cb(true);
|
|
||||||
};
|
|
||||||
lawnchairDaoStub.remove.withArgs('publickey_' + oldKey._id).yields();
|
|
||||||
lawnchairDaoStub.persist.yields({});
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.remove.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.persist.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should error while deleting old key', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields();
|
|
||||||
pubkeyDaoStub.getByUserId.withArgs(testUser).yields();
|
|
||||||
keychainDao.requestPermissionForKeyUpdate = function(opts, cb) {
|
|
||||||
expect(opts.userId).to.equal(testUser);
|
|
||||||
cb(true);
|
|
||||||
};
|
|
||||||
lawnchairDaoStub.remove.yields({});
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.remove.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.persist.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should error while persisting new key', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields();
|
|
||||||
pubkeyDaoStub.getByUserId.withArgs(testUser).yields(null, newKey);
|
|
||||||
keychainDao.requestPermissionForKeyUpdate = function(opts, cb) {
|
|
||||||
expect(opts.userId).to.equal(testUser);
|
|
||||||
expect(opts.newKey).to.equal(newKey);
|
|
||||||
cb(true);
|
|
||||||
};
|
|
||||||
lawnchairDaoStub.remove.withArgs('publickey_' + oldKey._id).yields();
|
|
||||||
lawnchairDaoStub.persist.yields({});
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
|
|
||||||
expect(getPubKeyStub.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.remove.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.persist.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should error when get failed', function(done) {
|
|
||||||
getPubKeyStub.yields(null, oldKey);
|
|
||||||
pubkeyDaoStub.get.withArgs(oldKey._id).yields({});
|
|
||||||
|
|
||||||
keychainDao.refreshKeyForUserId(testUser, function(err, key) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('lookup public key', function() {
|
|
||||||
it('should fail', function(done) {
|
|
||||||
keychainDao.lookupPublicKey(undefined, function(err, key) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail', function(done) {
|
|
||||||
lawnchairDaoStub.read.yields(42);
|
|
||||||
|
|
||||||
keychainDao.lookupPublicKey('12345', function(err, key) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.read.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work from local storage', function(done) {
|
|
||||||
lawnchairDaoStub.read.yields(null, {
|
|
||||||
_id: '12345',
|
|
||||||
publicKey: 'asdf'
|
|
||||||
});
|
|
||||||
|
|
||||||
keychainDao.lookupPublicKey('12345', function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.exist;
|
|
||||||
expect(lawnchairDaoStub.read.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work from cloud', function(done) {
|
|
||||||
lawnchairDaoStub.read.yields();
|
|
||||||
pubkeyDaoStub.get.yields(null, {
|
|
||||||
_id: '12345',
|
|
||||||
publicKey: 'asdf'
|
|
||||||
});
|
|
||||||
lawnchairDaoStub.persist.yields();
|
|
||||||
|
|
||||||
keychainDao.lookupPublicKey('12345', function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.exist;
|
|
||||||
expect(key._id).to.equal('12345');
|
|
||||||
expect(lawnchairDaoStub.read.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.get.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.persist.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get public keys by id', function() {
|
|
||||||
it('should fail', function(done) {
|
|
||||||
keychainDao.getPublicKeys([], function(err, keys) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(keys.length).to.equal(0);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail', function(done) {
|
|
||||||
lawnchairDaoStub.read.yields(42);
|
|
||||||
|
|
||||||
var ids = [{
|
|
||||||
_id: '12345'
|
|
||||||
}];
|
|
||||||
keychainDao.getPublicKeys(ids, function(err, keys) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(keys).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.read.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work from local storage', function(done) {
|
|
||||||
lawnchairDaoStub.read.yields(null, {
|
|
||||||
_id: '12345',
|
|
||||||
publicKey: 'asdf'
|
|
||||||
});
|
|
||||||
|
|
||||||
var ids = [{
|
|
||||||
_id: '12345'
|
|
||||||
}];
|
|
||||||
keychainDao.getPublicKeys(ids, function(err, keys) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(keys.length).to.equal(1);
|
|
||||||
expect(keys[0]._id).to.equal('12345');
|
|
||||||
expect(lawnchairDaoStub.read.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get receiver public key', function() {
|
|
||||||
it('should fail due to error in lawnchair list', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields(42);
|
|
||||||
|
|
||||||
keychainDao.getReceiverPublicKey(testUser, function(err, key) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work from lawnchair list', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields(null, [{
|
|
||||||
_id: '12345',
|
|
||||||
userId: testUser,
|
|
||||||
publicKey: 'asdf'
|
|
||||||
}]);
|
|
||||||
|
|
||||||
keychainDao.getReceiverPublicKey(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.exist;
|
|
||||||
expect(key._id).to.equal('12345');
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work for keys with secondary userIds', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields(null, [{
|
|
||||||
_id: '12345',
|
|
||||||
userId: 'not testUser',
|
|
||||||
userIds: [{
|
|
||||||
emailAddress: testUser
|
|
||||||
}],
|
|
||||||
publicKey: 'asdf'
|
|
||||||
}]);
|
|
||||||
|
|
||||||
keychainDao.getReceiverPublicKey(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.exist;
|
|
||||||
expect(key._id).to.equal('12345');
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail due to error in pubkey dao', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields(null, []);
|
|
||||||
pubkeyDaoStub.getByUserId.yields({});
|
|
||||||
|
|
||||||
keychainDao.getReceiverPublicKey(testUser, function(err, key) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work from pubkey dao with empty result', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields(null, []);
|
|
||||||
pubkeyDaoStub.getByUserId.yields();
|
|
||||||
|
|
||||||
keychainDao.getReceiverPublicKey(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work from pubkey dao', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields(null, []);
|
|
||||||
pubkeyDaoStub.getByUserId.yields(null, {
|
|
||||||
_id: '12345',
|
|
||||||
publicKey: 'asdf'
|
|
||||||
});
|
|
||||||
lawnchairDaoStub.persist.yields();
|
|
||||||
|
|
||||||
keychainDao.getReceiverPublicKey(testUser, function(err, key) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(key).to.exist;
|
|
||||||
expect(key._id).to.equal('12345');
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.persist.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get user key pair', function() {
|
|
||||||
it('should work if local keys are already present', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields(null, [{
|
|
||||||
_id: '12345',
|
|
||||||
userId: testUser,
|
|
||||||
publicKey: 'asdf'
|
|
||||||
}]);
|
|
||||||
lawnchairDaoStub.read.yields(null, {
|
|
||||||
_id: '12345',
|
|
||||||
publicKey: 'asdf',
|
|
||||||
encryptedKey: 'qwer'
|
|
||||||
});
|
|
||||||
|
|
||||||
keychainDao.getUserKeyPair(testUser, function(err, keys) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(keys).to.exist;
|
|
||||||
expect(keys.publicKey).to.exist;
|
|
||||||
expect(keys.privateKey).to.exist;
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.read.calledTwice).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work if local keys are not already present', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields();
|
|
||||||
pubkeyDaoStub.getByUserId.yields();
|
|
||||||
|
|
||||||
keychainDao.getUserKeyPair(testUser, function(err, keys) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(keys).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work if local keys are not already present', function(done) {
|
|
||||||
lawnchairDaoStub.list.yields();
|
|
||||||
pubkeyDaoStub.getByUserId.yields(null, {
|
|
||||||
_id: '12345',
|
|
||||||
publicKey: 'asdf'
|
|
||||||
});
|
|
||||||
lawnchairDaoStub.read.yields(null, {
|
|
||||||
_id: '12345',
|
|
||||||
publicKey: 'asdf',
|
|
||||||
encryptedKey: 'qwer'
|
|
||||||
});
|
|
||||||
|
|
||||||
keychainDao.getUserKeyPair(testUser, function(err, keys) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(keys).to.exist;
|
|
||||||
expect(keys.publicKey).to.exist;
|
|
||||||
expect(keys.privateKey).to.exist;
|
|
||||||
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
|
||||||
expect(lawnchairDaoStub.read.calledTwice).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('put user keypair', function() {
|
|
||||||
it('should fail', function(done) {
|
|
||||||
var keypair = {
|
|
||||||
publicKey: {
|
|
||||||
_id: '12345',
|
|
||||||
userId: testUser,
|
|
||||||
publicKey: 'asdf'
|
|
||||||
},
|
|
||||||
privateKey: {
|
|
||||||
_id: '12345',
|
|
||||||
encryptedKey: 'qwer'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
keychainDao.putUserKeyPair(keypair, function(err) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work', function(done) {
|
|
||||||
var keypair = {
|
|
||||||
publicKey: {
|
|
||||||
_id: '12345',
|
|
||||||
userId: testUser,
|
|
||||||
publicKey: 'asdf'
|
|
||||||
},
|
|
||||||
privateKey: {
|
|
||||||
_id: '12345',
|
|
||||||
userId: testUser,
|
|
||||||
encryptedKey: 'qwer'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
lawnchairDaoStub.persist.yields();
|
|
||||||
pubkeyDaoStub.put.yields();
|
|
||||||
|
|
||||||
keychainDao.putUserKeyPair(keypair, function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(lawnchairDaoStub.persist.calledTwice).to.be.true;
|
|
||||||
expect(pubkeyDaoStub.put.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,62 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
require(['../../src/require-config'], function() {
|
|
||||||
require.config({
|
|
||||||
baseUrl: '../../src/lib',
|
|
||||||
paths: {
|
|
||||||
angularMocks: '../../test/lib/angular-mocks'
|
|
||||||
},
|
|
||||||
shim: {
|
|
||||||
angularMocks: {
|
|
||||||
exports: 'angular.mock',
|
|
||||||
deps: ['angular']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Start the main app logic.
|
|
||||||
require(['js/app-config'], function(app) {
|
|
||||||
app.config.workerPath = '../../src/js';
|
|
||||||
|
|
||||||
startTests();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function startTests() {
|
|
||||||
mocha.setup('bdd');
|
|
||||||
|
|
||||||
require(
|
|
||||||
[
|
|
||||||
'test/new-unit/oauth-test',
|
|
||||||
'test/new-unit/auth-test',
|
|
||||||
'test/new-unit/email-dao-test',
|
|
||||||
'test/new-unit/app-controller-test',
|
|
||||||
'test/new-unit/pgp-test',
|
|
||||||
'test/new-unit/rest-dao-test',
|
|
||||||
'test/new-unit/publickey-dao-test',
|
|
||||||
'test/new-unit/lawnchair-dao-test',
|
|
||||||
'test/new-unit/keychain-dao-test',
|
|
||||||
'test/new-unit/devicestorage-dao-test',
|
|
||||||
'test/new-unit/dialog-ctrl-test',
|
|
||||||
'test/new-unit/add-account-ctrl-test',
|
|
||||||
'test/new-unit/account-ctrl-test',
|
|
||||||
'test/new-unit/set-passphrase-ctrl-test',
|
|
||||||
'test/new-unit/contacts-ctrl-test',
|
|
||||||
'test/new-unit/login-existing-ctrl-test',
|
|
||||||
'test/new-unit/login-initial-ctrl-test',
|
|
||||||
'test/new-unit/login-new-device-ctrl-test',
|
|
||||||
'test/new-unit/login-ctrl-test',
|
|
||||||
'test/new-unit/read-ctrl-test',
|
|
||||||
'test/new-unit/navigation-ctrl-test',
|
|
||||||
'test/new-unit/mail-list-ctrl-test',
|
|
||||||
'test/new-unit/write-ctrl-test',
|
|
||||||
'test/new-unit/outbox-bo-test',
|
|
||||||
'test/new-unit/invitation-dao-test',
|
|
||||||
'test/new-unit/update-handler-test'
|
|
||||||
], function() {
|
|
||||||
//Tests loaded, run tests
|
|
||||||
mocha.run();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
@ -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,
|
@ -1,24 +0,0 @@
|
|||||||
define(['cryptoLib/aes-cbc', 'cryptoLib/util', 'test/test-data'], function(aes, util, testData) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module("AES Crypto");
|
|
||||||
|
|
||||||
var aesTest = {
|
|
||||||
keySize: 128,
|
|
||||||
testMessage: testData.generateBigString(1000)
|
|
||||||
};
|
|
||||||
|
|
||||||
test("CBC mode", 4, function() {
|
|
||||||
var plaintext = aesTest.testMessage;
|
|
||||||
var key = util.random(aesTest.keySize);
|
|
||||||
var iv = util.random(aesTest.keySize);
|
|
||||||
ok(key, 'Key: ' + key);
|
|
||||||
equal(util.base642Str(key).length * 8, aesTest.keySize, 'Keysize ' + aesTest.keySize);
|
|
||||||
|
|
||||||
var ciphertext = aes.encrypt(plaintext, key, iv);
|
|
||||||
ok(ciphertext, 'Ciphertext lenght: ' + ciphertext.length);
|
|
||||||
|
|
||||||
var decrypted = aes.decrypt(ciphertext, key, iv);
|
|
||||||
equal(decrypted, plaintext, 'Decryption correct' + decrypted);
|
|
||||||
});
|
|
||||||
});
|
|
@ -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'
|
||||||
});
|
});
|
@ -1,121 +1,58 @@
|
|||||||
define(['js/crypto/crypto', 'cryptoLib/util', 'test/test-data'], function(Crypto, util, testData) {
|
define(function(require) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module("Crypto Api");
|
var Crypto = require('js/crypto/crypto'),
|
||||||
|
util = require('js/crypto/util'),
|
||||||
|
config = require('js/app-config').config,
|
||||||
|
expect = chai.expect;
|
||||||
|
|
||||||
var cryptoTest = {
|
describe('Crypto unit tests', function() {
|
||||||
user: 'crypto_test@example.com',
|
this.timeout(20000);
|
||||||
password: 'Password',
|
|
||||||
keySize: 128,
|
|
||||||
ivSize: 128,
|
|
||||||
rsaKeySize: 1024,
|
|
||||||
salt: util.random(128)
|
|
||||||
};
|
|
||||||
|
|
||||||
var crypto;
|
var crypto,
|
||||||
|
password = 'password',
|
||||||
|
keySize = config.symKeySize,
|
||||||
|
ivSize = config.symIvSize;
|
||||||
|
|
||||||
asyncTest("Init without keypair", 4, function() {
|
beforeEach(function() {
|
||||||
crypto = new Crypto();
|
crypto = new Crypto();
|
||||||
// init dependencies
|
});
|
||||||
ok(crypto, 'Crypto');
|
|
||||||
|
|
||||||
// test without passing keys
|
afterEach(function() {});
|
||||||
crypto.init({
|
|
||||||
emailAddress: cryptoTest.user,
|
|
||||||
password: cryptoTest.password,
|
|
||||||
salt: cryptoTest.salt,
|
|
||||||
keySize: cryptoTest.keySize,
|
|
||||||
rsaKeySize: cryptoTest.rsaKeySize
|
|
||||||
}, function(err, generatedKeypair) {
|
|
||||||
ok(!err && generatedKeypair, 'Init crypto without keypair input');
|
|
||||||
var pk = generatedKeypair.publicKey;
|
|
||||||
ok(pk._id && pk.userId, 'Key ID: ' + pk._id);
|
|
||||||
ok(pk.publicKey.indexOf('-----BEGIN PUBLIC KEY-----') === 0, pk.publicKey);
|
|
||||||
cryptoTest.generatedKeypair = generatedKeypair;
|
|
||||||
|
|
||||||
start();
|
describe('AES encrypt/decrypt', function() {
|
||||||
});
|
it('should work', function(done) {
|
||||||
});
|
var plaintext = 'Hello, World!';
|
||||||
|
var key = util.random(keySize);
|
||||||
|
var iv = util.random(ivSize);
|
||||||
|
|
||||||
asyncTest("Init with keypair", 1, function() {
|
crypto.encrypt(plaintext, key, iv, function(err, ciphertext) {
|
||||||
// test with passing keypair
|
expect(err).to.not.exist;
|
||||||
crypto.init({
|
expect(ciphertext).to.exist;
|
||||||
emailAddress: cryptoTest.user,
|
|
||||||
password: cryptoTest.password,
|
|
||||||
salt: cryptoTest.salt,
|
|
||||||
keySize: cryptoTest.keySize,
|
|
||||||
rsaKeySize: cryptoTest.rsaKeySize,
|
|
||||||
storedKeypair: cryptoTest.generatedKeypair
|
|
||||||
}, function(err, generatedKeypair) {
|
|
||||||
ok(!err && !generatedKeypair, 'Init crypto with keypair input');
|
|
||||||
|
|
||||||
start();
|
crypto.decrypt(ciphertext, key, iv, function(err, decrypted) {
|
||||||
});
|
expect(err).to.not.exist;
|
||||||
});
|
expect(decrypted).to.equal(plaintext);
|
||||||
|
|
||||||
asyncTest("PBKDF2 (Async/Worker)", 2, function() {
|
done();
|
||||||
crypto.deriveKey(cryptoTest.password, cryptoTest.salt, cryptoTest.keySize, function(err, key) {
|
});
|
||||||
ok(!err);
|
});
|
||||||
equal(util.base642Str(key).length * 8, cryptoTest.keySize, 'Keysize ' + cryptoTest.keySize);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
start();
|
describe("PBKDF2 (Async/Worker)", function() {
|
||||||
});
|
it('should work', function(done) {
|
||||||
});
|
var salt = util.random(keySize);
|
||||||
|
|
||||||
asyncTest("AES/HMAC encrypt batch (Async/Worker)", 2, function() {
|
crypto.deriveKey(password, salt, keySize, function(err, key) {
|
||||||
// generate test data
|
expect(err).to.not.exist;
|
||||||
cryptoTest.symlist = testData.getEmailCollection(10);
|
expect(util.base642Str(key).length * 8).to.equal(keySize);
|
||||||
|
|
||||||
crypto.symEncryptList(cryptoTest.symlist, function(err, result) {
|
done();
|
||||||
ok(!err && result.key && result.list && result.list[0].hmac, 'Encrypt list for user');
|
});
|
||||||
equal(result.list.length, cryptoTest.symlist.length, 'Length of list');
|
});
|
||||||
cryptoTest.symEncryptedList = result.list;
|
|
||||||
cryptoTest.symKey = result.key;
|
|
||||||
|
|
||||||
start();
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
asyncTest("AES/HMAC decrypt batch (Async/Worker)", 3, function() {
|
|
||||||
var keys = [];
|
|
||||||
for (var i = 0; i < cryptoTest.symEncryptedList.length; i++) {
|
|
||||||
keys.push(cryptoTest.symKey);
|
|
||||||
}
|
|
||||||
crypto.symDecryptList(cryptoTest.symEncryptedList, keys, function(err, decryptedList) {
|
|
||||||
ok(!err && decryptedList, 'Decrypt list');
|
|
||||||
equal(decryptedList.length, cryptoTest.symlist.length, 'Length of list');
|
|
||||||
deepEqual(decryptedList, cryptoTest.symlist, 'Decrypted list is correct');
|
|
||||||
|
|
||||||
start();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
asyncTest("AES/RSA encrypt batch for User (Async/Worker)", 2, function() {
|
|
||||||
// generate test data
|
|
||||||
cryptoTest.list = testData.getEmailCollection(10);
|
|
||||||
|
|
||||||
var receiverPubkeys = [cryptoTest.generatedKeypair.publicKey];
|
|
||||||
|
|
||||||
crypto.encryptListForUser(cryptoTest.list, receiverPubkeys, function(err, encryptedList) {
|
|
||||||
ok(!err && encryptedList, 'Encrypt list for user');
|
|
||||||
equal(encryptedList.length, cryptoTest.list.length, 'Length of list');
|
|
||||||
cryptoTest.encryptedList = encryptedList;
|
|
||||||
|
|
||||||
start();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
asyncTest("AES/RSA decrypt batch for User (Async/Worker)", 3, function() {
|
|
||||||
|
|
||||||
var senderPubkeys = [cryptoTest.generatedKeypair.publicKey];
|
|
||||||
|
|
||||||
crypto.decryptListForUser(cryptoTest.encryptedList, senderPubkeys, function(err, decryptedList) {
|
|
||||||
ok(!err && decryptedList, 'Decrypt list');
|
|
||||||
equal(decryptedList.length, cryptoTest.list.length, 'Length of list');
|
|
||||||
deepEqual(decryptedList, cryptoTest.list, 'Decrypted list is correct');
|
|
||||||
|
|
||||||
start();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
@ -1,106 +1,105 @@
|
|||||||
define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/devicestorage-dao', 'test/test-data', 'js/dao/lawnchair-dao'], function(_, util, Crypto, DeviceStorageDAO, testData, LawnchairDAO) {
|
define(function(require) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module("DeviceStorage");
|
var LawnchairDAO = require('js/dao/lawnchair-dao'),
|
||||||
|
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||||
|
expect = chai.expect;
|
||||||
|
|
||||||
var devicestorageTest = {
|
var testUser = 'test@example.com';
|
||||||
user: 'devicestorage_test@example.com',
|
|
||||||
password: 'Password',
|
|
||||||
keySize: 128,
|
|
||||||
ivSize: 128,
|
|
||||||
rsaKeySize: 1024
|
|
||||||
};
|
|
||||||
|
|
||||||
var crypto, storage;
|
describe('Device Storage DAO unit tests', function() {
|
||||||
|
|
||||||
asyncTest("Init", 3, function() {
|
var storageDao, lawnchairDaoStub;
|
||||||
// init dependencies
|
|
||||||
storage = new DeviceStorageDAO(new LawnchairDAO());
|
|
||||||
storage.init(devicestorageTest.user, function() {
|
|
||||||
ok(storage, 'DeviceStorageDAO');
|
|
||||||
|
|
||||||
// generate test data
|
beforeEach(function() {
|
||||||
devicestorageTest.list = testData.getEmailCollection(100);
|
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
|
||||||
|
storageDao = new DeviceStorageDAO(lawnchairDaoStub);
|
||||||
|
});
|
||||||
|
|
||||||
// init crypto
|
afterEach(function() {});
|
||||||
crypto = new Crypto();
|
|
||||||
crypto.init({
|
|
||||||
emailAddress: devicestorageTest.user,
|
|
||||||
password: devicestorageTest.password,
|
|
||||||
salt: util.random(devicestorageTest.keySize),
|
|
||||||
keySize: devicestorageTest.keySize,
|
|
||||||
rsaKeySize: devicestorageTest.rsaKeySize
|
|
||||||
}, function(err, generatedKeypair) {
|
|
||||||
ok(!err && generatedKeypair, 'Init crypto');
|
|
||||||
devicestorageTest.generatedKeypair = generatedKeypair;
|
|
||||||
|
|
||||||
// clear db before tests
|
describe('init', function() {
|
||||||
storage.clear(function(err) {
|
it('should work', function(done) {
|
||||||
ok(!err, 'DB cleared. Error status: ' + err);
|
lawnchairDaoStub.init.yields();
|
||||||
|
|
||||||
start();
|
storageDao.init(testUser, function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
expect(lawnchairDaoStub.init.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
asyncTest("Encrypt list for user", 2, function() {
|
describe('store list', function() {
|
||||||
var receiverPubkeys = [devicestorageTest.generatedKeypair.publicKey];
|
it('should fail', function(done) {
|
||||||
|
var list = [{}];
|
||||||
|
|
||||||
crypto.encryptListForUser(devicestorageTest.list, receiverPubkeys, function(err, encryptedList) {
|
storageDao.storeList(list, '', function(err) {
|
||||||
ok(!err);
|
expect(err).to.exist;
|
||||||
equal(encryptedList.length, devicestorageTest.list.length, 'Encrypt list');
|
done();
|
||||||
|
});
|
||||||
encryptedList.forEach(function(i) {
|
|
||||||
i.sentDate = _.findWhere(devicestorageTest.list, {
|
|
||||||
id: i.id
|
|
||||||
}).sentDate;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
devicestorageTest.encryptedList = encryptedList;
|
it('should work with empty list', function(done) {
|
||||||
start();
|
var list = [];
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
asyncTest("Store encrypted list", 1, function() {
|
storageDao.storeList(list, 'email', function(err) {
|
||||||
storage.storeList(devicestorageTest.encryptedList, 'email_inbox', function() {
|
expect(err).to.not.exist;
|
||||||
ok(true, 'Store encrypted list');
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
start();
|
it('should work', function(done) {
|
||||||
});
|
lawnchairDaoStub.batch.yields();
|
||||||
});
|
|
||||||
|
|
||||||
asyncTest("List items", 4, function() {
|
var list = [{
|
||||||
var senderPubkeys = [devicestorageTest.generatedKeypair.publicKey];
|
foo: 'bar'
|
||||||
|
}];
|
||||||
|
|
||||||
var offset = 2,
|
storageDao.storeList(list, 'email', function(err) {
|
||||||
num = 6;
|
expect(err).to.not.exist;
|
||||||
|
expect(lawnchairDaoStub.batch.calledOnce).to.be.true;
|
||||||
// list encrypted items from storage
|
done();
|
||||||
storage.listItems('email_inbox', offset, num, function(err, encryptedList) {
|
});
|
||||||
ok(!err);
|
|
||||||
|
|
||||||
// decrypt list
|
|
||||||
crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
|
|
||||||
ok(!err);
|
|
||||||
equal(decryptedList.length, num, 'Found ' + decryptedList.length + ' items in store (and decrypted)');
|
|
||||||
|
|
||||||
var origSet = devicestorageTest.list.splice(92, num);
|
|
||||||
deepEqual(decryptedList, origSet, 'Messages decrypted correctly');
|
|
||||||
|
|
||||||
start();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
asyncTest("Delete List items", 1, function() {
|
describe('remove list', function() {
|
||||||
// list encrypted items from storage
|
it('should work', function(done) {
|
||||||
storage.removeList('email_inbox', function(err) {
|
lawnchairDaoStub.removeList.yields();
|
||||||
ok(!err);
|
|
||||||
|
|
||||||
start();
|
storageDao.removeList('email', function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
expect(lawnchairDaoStub.removeList.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('list items', function() {
|
||||||
|
it('should work', function(done) {
|
||||||
|
lawnchairDaoStub.list.yields();
|
||||||
|
|
||||||
|
storageDao.listItems('email', 0, null, function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('clear', function() {
|
||||||
|
it('should work', function(done) {
|
||||||
|
lawnchairDaoStub.clear.yields();
|
||||||
|
|
||||||
|
storageDao.clear(function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
expect(lawnchairDaoStub.clear.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
File diff suppressed because it is too large
Load Diff
@ -1,42 +0,0 @@
|
|||||||
define(['node-forge', 'cryptoLib/util', 'test/test-data'], function(forge, util, testData) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module("Forge Crypto");
|
|
||||||
|
|
||||||
var forgeRsaTest = {
|
|
||||||
keySize: 1024,
|
|
||||||
testMessage: '06a9214036b8a15b512e03d534120006'
|
|
||||||
};
|
|
||||||
|
|
||||||
var forgeAesTest = {
|
|
||||||
keySize: 128,
|
|
||||||
testMessage: testData.generateBigString(1000)
|
|
||||||
};
|
|
||||||
|
|
||||||
test("SHA-1 Hash", 1, function() {
|
|
||||||
var sha1 = forge.md.sha1.create();
|
|
||||||
sha1.update(forgeAesTest.testMessage);
|
|
||||||
var digest = sha1.digest().toHex();
|
|
||||||
ok(digest, digest);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("SHA-256 Hash", 1, function() {
|
|
||||||
forgeRsaTest.md = forge.md.sha256.create();
|
|
||||||
forgeRsaTest.md.update(forgeAesTest.testMessage);
|
|
||||||
var digest = forgeRsaTest.md.digest().toHex();
|
|
||||||
ok(digest, digest);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("HMAC SHA-256", 1, function() {
|
|
||||||
var key = util.base642Str(util.random(forgeAesTest.keySize));
|
|
||||||
var iv = util.base642Str(util.random(forgeAesTest.keySize));
|
|
||||||
|
|
||||||
var hmac = forge.hmac.create();
|
|
||||||
hmac.start('sha256', key);
|
|
||||||
hmac.update(iv);
|
|
||||||
hmac.update(forgeAesTest.testMessage);
|
|
||||||
var digest = hmac.digest().toHex();
|
|
||||||
|
|
||||||
ok(digest, digest);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,17 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html style="overflow-y: auto">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>JavaScript Unit Tests</title>
|
<title>JavaScript Unit Tests</title>
|
||||||
<link rel="stylesheet" href="../qunit-1.11.0.css">
|
<link rel="stylesheet" href="../lib/mocha.css" />
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="qunit"></div>
|
<div id="mocha"></div>
|
||||||
<div id="qunit-fixture"></div>
|
|
||||||
|
|
||||||
<script src="../qunit-1.11.0.js"></script>
|
<script src="../lib/chai.js"></script>
|
||||||
<script>QUnit.config.autostart = false;</script>
|
<script src="../lib/sinon.js"></script>
|
||||||
|
<script src="../lib/mocha.js"></script>
|
||||||
|
|
||||||
<script data-main="main.js" src="../../src/lib/require.js"></script>
|
<script data-main="main.js" src="../../src/lib/require.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,16 +1,22 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require(['../../src/require-config'], function() {
|
require(['../../src/require-config'], function() {
|
||||||
|
|
||||||
require.config({
|
require.config({
|
||||||
baseUrl: '../../src/lib'
|
baseUrl: '../../src/lib',
|
||||||
|
paths: {
|
||||||
|
angularMocks: '../../test/lib/angular-mocks'
|
||||||
|
},
|
||||||
|
shim: {
|
||||||
|
angularMocks: {
|
||||||
|
exports: 'angular.mock',
|
||||||
|
deps: ['angular']
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start the main app logic.
|
|
||||||
require(['js/app-config', 'cordova'], function(app) {
|
|
||||||
// clear session storage of failed tests, so async order is correct after fail & refresh
|
|
||||||
window.sessionStorage.clear();
|
|
||||||
|
|
||||||
|
// Start the main app logic.
|
||||||
|
require(['js/app-config'], function(app) {
|
||||||
app.config.workerPath = '../../src/js';
|
app.config.workerPath = '../../src/js';
|
||||||
|
|
||||||
startTests();
|
startTests();
|
||||||
@ -18,17 +24,43 @@ require(['../../src/require-config'], function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function startTests() {
|
function startTests() {
|
||||||
|
mocha.setup('bdd');
|
||||||
|
|
||||||
require(
|
require(
|
||||||
[
|
[
|
||||||
'test/unit/forge-test',
|
'test/unit/oauth-test',
|
||||||
'test/unit/aes-test',
|
'test/unit/auth-test',
|
||||||
'test/unit/rsa-test',
|
'test/unit/email-dao-test',
|
||||||
'test/unit/keychain-dao-test',
|
'test/unit/app-controller-test',
|
||||||
|
'test/unit/pgp-test',
|
||||||
'test/unit/crypto-test',
|
'test/unit/crypto-test',
|
||||||
'test/unit/devicestorage-dao-test'
|
'test/unit/rest-dao-test',
|
||||||
|
'test/unit/publickey-dao-test',
|
||||||
|
'test/unit/privatekey-dao-test',
|
||||||
|
'test/unit/lawnchair-dao-test',
|
||||||
|
'test/unit/keychain-dao-test',
|
||||||
|
'test/unit/devicestorage-dao-test',
|
||||||
|
'test/unit/dialog-ctrl-test',
|
||||||
|
'test/unit/add-account-ctrl-test',
|
||||||
|
'test/unit/account-ctrl-test',
|
||||||
|
'test/unit/set-passphrase-ctrl-test',
|
||||||
|
'test/unit/contacts-ctrl-test',
|
||||||
|
'test/unit/login-existing-ctrl-test',
|
||||||
|
'test/unit/login-initial-ctrl-test',
|
||||||
|
'test/unit/login-new-device-ctrl-test',
|
||||||
|
'test/unit/login-privatekey-download-ctrl-test',
|
||||||
|
'test/unit/privatekey-upload-ctrl-test',
|
||||||
|
'test/unit/login-ctrl-test',
|
||||||
|
'test/unit/read-ctrl-test',
|
||||||
|
'test/unit/navigation-ctrl-test',
|
||||||
|
'test/unit/mail-list-ctrl-test',
|
||||||
|
'test/unit/write-ctrl-test',
|
||||||
|
'test/unit/outbox-bo-test',
|
||||||
|
'test/unit/invitation-dao-test',
|
||||||
|
'test/unit/update-handler-test'
|
||||||
], function() {
|
], function() {
|
||||||
//Tests loaded, run tests
|
//Tests loaded, run tests
|
||||||
QUnit.start();
|
mocha.run();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ define(function(require) {
|
|||||||
keySize = 512,
|
keySize = 512,
|
||||||
keyId = 'F6F60E9B42CDFF4C',
|
keyId = 'F6F60E9B42CDFF4C',
|
||||||
pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' +
|
pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' +
|
||||||
'Version: OpenPGP.js v0.5.1\r\n' +
|
'Version: OpenPGP.js v0.6.0\r\n' +
|
||||||
'Comment: http://openpgpjs.org\r\n' +
|
'Comment: http://openpgpjs.org\r\n' +
|
||||||
'\r\n' +
|
'\r\n' +
|
||||||
'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\r\n' +
|
'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\r\n' +
|
||||||
@ -24,7 +24,7 @@ define(function(require) {
|
|||||||
'=6XMW\r\n' +
|
'=6XMW\r\n' +
|
||||||
'-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n',
|
'-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n',
|
||||||
privkey = '-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n' +
|
privkey = '-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n' +
|
||||||
'Version: OpenPGP.js v0.5.1\r\n' +
|
'Version: OpenPGP.js v0.6.0\r\n' +
|
||||||
'Comment: http://openpgpjs.org\r\n' +
|
'Comment: http://openpgpjs.org\r\n' +
|
||||||
'\r\n' +
|
'\r\n' +
|
||||||
'xcBeBFJYTLwBAf9jGbQlDgGL8ixYw6dzgTBp9xL/BcI88j2yBdCVMPi+8tl0\r\n' +
|
'xcBeBFJYTLwBAf9jGbQlDgGL8ixYw6dzgTBp9xL/BcI88j2yBdCVMPi+8tl0\r\n' +
|
||||||
@ -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;
|
201
test/unit/privatekey-dao-test.js
Normal file
201
test/unit/privatekey-dao-test.js
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
define(function(require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var RestDAO = require('js/dao/rest-dao'),
|
||||||
|
PrivateKeyDAO = require('js/dao/privatekey-dao'),
|
||||||
|
expect = chai.expect;
|
||||||
|
|
||||||
|
describe('Private Key DAO unit tests', function() {
|
||||||
|
|
||||||
|
var privkeyDao, restDaoStub,
|
||||||
|
emailAddress = 'test@example.com',
|
||||||
|
deviceName = 'iPhone Work';
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
restDaoStub = sinon.createStubInstance(RestDAO);
|
||||||
|
privkeyDao = new PrivateKeyDAO(restDaoStub);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {});
|
||||||
|
|
||||||
|
describe('requestDeviceRegistration', function() {
|
||||||
|
it('should fail due to invalid args', function(done) {
|
||||||
|
privkeyDao.requestDeviceRegistration({}, function(err, sessionKey) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
expect(sessionKey).to.not.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
|
restDaoStub.post.yields(null, {
|
||||||
|
encryptedRegSessionKey: 'asdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
privkeyDao.requestDeviceRegistration({
|
||||||
|
userId: emailAddress,
|
||||||
|
deviceName: deviceName
|
||||||
|
}, function(err, sessionKey) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
expect(sessionKey).to.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('uploadDeviceSecret', function() {
|
||||||
|
it('should fail due to invalid args', function(done) {
|
||||||
|
privkeyDao.uploadDeviceSecret({}, function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
|
restDaoStub.put.yields();
|
||||||
|
|
||||||
|
privkeyDao.uploadDeviceSecret({
|
||||||
|
userId: emailAddress,
|
||||||
|
deviceName: deviceName,
|
||||||
|
encryptedDeviceSecret: 'asdf',
|
||||||
|
iv: 'iv'
|
||||||
|
}, function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestAuthSessionKey', function() {
|
||||||
|
it('should fail due to invalid args', function(done) {
|
||||||
|
privkeyDao.requestAuthSessionKey({}, function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
|
restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields();
|
||||||
|
|
||||||
|
privkeyDao.requestAuthSessionKey({
|
||||||
|
userId: emailAddress
|
||||||
|
}, function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('verifyAuthentication', function() {
|
||||||
|
it('should fail due to invalid args', function(done) {
|
||||||
|
privkeyDao.verifyAuthentication({}, function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
|
var sessionId = '1';
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
userId: emailAddress,
|
||||||
|
sessionId: sessionId,
|
||||||
|
encryptedChallenge: 'asdf',
|
||||||
|
encryptedDeviceSecret: 'qwer',
|
||||||
|
iv: ' iv'
|
||||||
|
};
|
||||||
|
|
||||||
|
restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).yields();
|
||||||
|
|
||||||
|
privkeyDao.verifyAuthentication(options, function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('upload', function() {
|
||||||
|
it('should fail due to invalid args', function(done) {
|
||||||
|
privkeyDao.upload({}, function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
|
var options = {
|
||||||
|
_id: '12345',
|
||||||
|
userId: emailAddress,
|
||||||
|
encryptedPrivateKey: 'asdf',
|
||||||
|
sessionId: '1',
|
||||||
|
salt: 'salt',
|
||||||
|
iv: 'iv'
|
||||||
|
};
|
||||||
|
|
||||||
|
restDaoStub.post.withArgs(options, '/privatekey/user/' + emailAddress + '/session/' + options.sessionId).yields();
|
||||||
|
|
||||||
|
privkeyDao.upload(options, function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestDownload', function() {
|
||||||
|
it('should fail due to invalid args', function(done) {
|
||||||
|
privkeyDao.requestDownload({}, function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
|
var key = {
|
||||||
|
_id: '12345'
|
||||||
|
};
|
||||||
|
|
||||||
|
restDaoStub.get.withArgs({
|
||||||
|
uri: '/privatekey/user/' + emailAddress + '/key/' + key._id
|
||||||
|
}).yields();
|
||||||
|
|
||||||
|
privkeyDao.requestDownload({
|
||||||
|
userId: emailAddress,
|
||||||
|
keyId: key._id
|
||||||
|
}, function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('download', function() {
|
||||||
|
it('should fail due to invalid args', function(done) {
|
||||||
|
privkeyDao.download({}, function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
|
var key = {
|
||||||
|
_id: '12345'
|
||||||
|
};
|
||||||
|
|
||||||
|
restDaoStub.get.withArgs({
|
||||||
|
uri: '/privatekey/user/' + emailAddress + '/key/' + key._id + '/recovery/token'
|
||||||
|
}).yields();
|
||||||
|
|
||||||
|
privkeyDao.download({
|
||||||
|
userId: emailAddress,
|
||||||
|
keyId: key._id,
|
||||||
|
recoveryToken: 'token'
|
||||||
|
}, function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
260
test/unit/privatekey-upload-ctrl-test.js
Normal file
260
test/unit/privatekey-upload-ctrl-test.js
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
define(function(require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var expect = chai.expect,
|
||||||
|
angular = require('angular'),
|
||||||
|
mocks = require('angularMocks'),
|
||||||
|
PrivateKeyUploadCtrl = require('js/controller/privatekey-upload'),
|
||||||
|
appController = require('js/app-controller'),
|
||||||
|
KeychainDAO = require('js/dao/keychain-dao'),
|
||||||
|
PGP = require('js/crypto/pgp');
|
||||||
|
|
||||||
|
describe('Private Key Upload Controller unit test', function() {
|
||||||
|
var scope, location, ctrl,
|
||||||
|
origEmailDao, emailDaoMock,
|
||||||
|
origKeychain, keychainMock,
|
||||||
|
pgpStub,
|
||||||
|
emailAddress = 'fred@foo.com';
|
||||||
|
|
||||||
|
beforeEach(function(done) {
|
||||||
|
// remember original module to restore later, then replace it
|
||||||
|
origEmailDao = appController._emailDao;
|
||||||
|
appController._emailDao = emailDaoMock = {
|
||||||
|
_account: {
|
||||||
|
emailAddress: emailAddress
|
||||||
|
}
|
||||||
|
};
|
||||||
|
origKeychain = appController._keychain;
|
||||||
|
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||||
|
keychainMock._pgp = pgpStub = sinon.createStubInstance(PGP);
|
||||||
|
|
||||||
|
angular.module('login-privatekey-download-test', []);
|
||||||
|
mocks.module('login-privatekey-download-test');
|
||||||
|
mocks.inject(function($controller, $rootScope) {
|
||||||
|
scope = $rootScope.$new();
|
||||||
|
scope.state = {};
|
||||||
|
ctrl = $controller(PrivateKeyUploadCtrl, {
|
||||||
|
$location: location,
|
||||||
|
$scope: scope
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
// restore the app controller module
|
||||||
|
appController._keychain = origKeychain;
|
||||||
|
appController._emailDao = origEmailDao;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('checkServerForKey', function() {
|
||||||
|
var keyParams = {
|
||||||
|
userId: emailAddress,
|
||||||
|
_id: 'keyId'
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should fail', function(done) {
|
||||||
|
pgpStub.getKeyParams.returns(keyParams);
|
||||||
|
keychainMock.requestPrivateKeyDownload.yields(42);
|
||||||
|
|
||||||
|
scope.onError = function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.checkServerForKey();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', function(done) {
|
||||||
|
pgpStub.getKeyParams.returns(keyParams);
|
||||||
|
keychainMock.requestPrivateKeyDownload.yields(null, true);
|
||||||
|
|
||||||
|
scope.checkServerForKey(function(privateKeySynced) {
|
||||||
|
expect(privateKeySynced).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined', function(done) {
|
||||||
|
pgpStub.getKeyParams.returns(keyParams);
|
||||||
|
keychainMock.requestPrivateKeyDownload.yields(null, false);
|
||||||
|
|
||||||
|
scope.checkServerForKey(function(privateKeySynced) {
|
||||||
|
expect(privateKeySynced).to.be.undefined;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('displayUploadUi', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
var generateCodeStub = sinon.stub(scope, 'generateCode');
|
||||||
|
generateCodeStub.returns('asdf');
|
||||||
|
|
||||||
|
scope.displayUploadUi();
|
||||||
|
expect(scope.step).to.equal(1);
|
||||||
|
expect(scope.code).to.equal('asdf');
|
||||||
|
|
||||||
|
generateCodeStub.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateCode', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
expect(scope.generateCode().length).to.equal(24);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('verifyCode', function() {
|
||||||
|
it('should fail for wrong code', function() {
|
||||||
|
scope.code0 = 'b';
|
||||||
|
scope.code1 = 'b';
|
||||||
|
scope.code2 = 'b';
|
||||||
|
scope.code3 = 'b';
|
||||||
|
scope.code4 = 'b';
|
||||||
|
scope.code5 = 'b';
|
||||||
|
scope.code = 'aaaaaa';
|
||||||
|
|
||||||
|
scope.onError = function() {};
|
||||||
|
expect(scope.verifyCode()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function() {
|
||||||
|
scope.code0 = 'a';
|
||||||
|
scope.code1 = 'a';
|
||||||
|
scope.code2 = 'a';
|
||||||
|
scope.code3 = 'a';
|
||||||
|
scope.code4 = 'a';
|
||||||
|
scope.code5 = 'a';
|
||||||
|
scope.code = 'aaaaaa';
|
||||||
|
|
||||||
|
scope.onError = function() {};
|
||||||
|
expect(scope.verifyCode()).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setDeviceName', function() {
|
||||||
|
it('should work', function(done) {
|
||||||
|
keychainMock.setDeviceName.yields();
|
||||||
|
scope.setDeviceName(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('encryptAndUploadKey', function() {
|
||||||
|
it('should fail due to keychain.registerDevice', function(done) {
|
||||||
|
keychainMock.registerDevice.yields(42);
|
||||||
|
|
||||||
|
scope.onError = function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
expect(keychainMock.registerDevice.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.encryptAndUploadKey();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
|
keychainMock.registerDevice.yields();
|
||||||
|
keychainMock.uploadPrivateKey.yields();
|
||||||
|
|
||||||
|
scope.encryptAndUploadKey(function(err) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
expect(keychainMock.registerDevice.calledOnce).to.be.true;
|
||||||
|
expect(keychainMock.uploadPrivateKey.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('goBack', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
scope.step = 2;
|
||||||
|
scope.goBack();
|
||||||
|
expect(scope.step).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not work for < 2', function() {
|
||||||
|
scope.step = 1;
|
||||||
|
scope.goBack();
|
||||||
|
expect(scope.step).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('goForward', function() {
|
||||||
|
var verifyCodeStub, setDeviceNameStub, encryptAndUploadKeyStub;
|
||||||
|
beforeEach(function() {
|
||||||
|
verifyCodeStub = sinon.stub(scope, 'verifyCode');
|
||||||
|
setDeviceNameStub = sinon.stub(scope, 'setDeviceName');
|
||||||
|
encryptAndUploadKeyStub = sinon.stub(scope, 'encryptAndUploadKey');
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
verifyCodeStub.restore();
|
||||||
|
setDeviceNameStub.restore();
|
||||||
|
encryptAndUploadKeyStub.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for < 2', function() {
|
||||||
|
scope.step = 1;
|
||||||
|
scope.goForward();
|
||||||
|
expect(scope.step).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for 2', function() {
|
||||||
|
verifyCodeStub.returns(true);
|
||||||
|
scope.step = 2;
|
||||||
|
scope.goForward();
|
||||||
|
expect(scope.step).to.equal(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not work for 2 when code invalid', function() {
|
||||||
|
verifyCodeStub.returns(false);
|
||||||
|
scope.step = 2;
|
||||||
|
scope.goForward();
|
||||||
|
expect(scope.step).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail for 3 due to error in setDeviceName', function(done) {
|
||||||
|
scope.step = 3;
|
||||||
|
setDeviceNameStub.yields(42);
|
||||||
|
|
||||||
|
scope.onError = function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
expect(scope.step).to.equal(3);
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.goForward();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail for 3 due to error in encryptAndUploadKey', function(done) {
|
||||||
|
scope.step = 3;
|
||||||
|
setDeviceNameStub.yields();
|
||||||
|
encryptAndUploadKeyStub.yields(42);
|
||||||
|
|
||||||
|
scope.onError = function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
expect(scope.step).to.equal(4);
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.goForward();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for 3', function(done) {
|
||||||
|
scope.step = 3;
|
||||||
|
setDeviceNameStub.yields();
|
||||||
|
encryptAndUploadKeyStub.yields();
|
||||||
|
|
||||||
|
scope.onError = function(err) {
|
||||||
|
expect(err.title).to.equal('Success');
|
||||||
|
expect(scope.step).to.equal(4);
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.goForward();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -145,6 +145,31 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('post', function() {
|
||||||
|
it('should fail', function() {
|
||||||
|
restDao.post('/asdf', {}, function(err) {
|
||||||
|
expect(err).to.exist;
|
||||||
|
expect(err.code).to.equal(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(requests.length).to.equal(1);
|
||||||
|
requests[0].respond(500, {
|
||||||
|
"Content-Type": "text/plain"
|
||||||
|
}, 'Internal error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', function() {
|
||||||
|
restDao.post('/asdf', {}, function(err, res, status) {
|
||||||
|
expect(err).to.not.exist;
|
||||||
|
expect(res).to.equal('');
|
||||||
|
expect(status).to.equal(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(requests.length).to.equal(1);
|
||||||
|
requests[0].respond(201);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('put', function() {
|
describe('put', function() {
|
||||||
it('should fail', function() {
|
it('should fail', function() {
|
||||||
restDao.put('/asdf', {}, function(err) {
|
restDao.put('/asdf', {}, function(err) {
|
@ -1,53 +0,0 @@
|
|||||||
define(['cryptoLib/rsa'], function(rsa) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module("RSA Crypto");
|
|
||||||
|
|
||||||
var rsaTest = {
|
|
||||||
keySize: 1024,
|
|
||||||
testMessage: '06a9214036b8a15b512e03d534120006'
|
|
||||||
};
|
|
||||||
|
|
||||||
asyncTest("Generate keypair", 1, function() {
|
|
||||||
rsa.generateKeypair(rsaTest.keySize, function(err) {
|
|
||||||
ok(!err);
|
|
||||||
|
|
||||||
start();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Export keys", 2, function() {
|
|
||||||
rsaTest.keypair = rsa.exportKeys();
|
|
||||||
|
|
||||||
ok(rsaTest.keypair.pubkeyPem.indexOf('-----BEGIN PUBLIC KEY-----') === 0, rsaTest.keypair.pubkeyPem);
|
|
||||||
ok(rsaTest.keypair.privkeyPem.indexOf('-----BEGIN RSA PRIVATE KEY-----') === 0, rsaTest.keypair.privkeyPem);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Init", 2, function() {
|
|
||||||
rsa.init(rsaTest.keypair.pubkeyPem, rsaTest.keypair.privkeyPem);
|
|
||||||
var exported = rsa.exportKeys();
|
|
||||||
|
|
||||||
ok(exported.pubkeyPem.indexOf('-----BEGIN PUBLIC KEY-----') === 0);
|
|
||||||
ok(exported.privkeyPem.indexOf('-----BEGIN RSA PRIVATE KEY-----') === 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Encrypt", 1, function() {
|
|
||||||
rsaTest.ct = rsa.encrypt(rsaTest.testMessage);
|
|
||||||
ok(rsaTest.ct);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Decrypt", 1, function() {
|
|
||||||
var pt = rsa.decrypt(rsaTest.ct);
|
|
||||||
equal(pt, rsaTest.testMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Sign", 1, function() {
|
|
||||||
rsaTest.sig = rsa.sign([btoa('iv'), btoa(rsaTest.testMessage)]);
|
|
||||||
ok(rsaTest.sig);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Verify", 1, function() {
|
|
||||||
var res = rsa.verify([btoa('iv'), btoa(rsaTest.testMessage)], rsaTest.sig);
|
|
||||||
ok(res);
|
|
||||||
});
|
|
||||||
});
|
|
@ -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