mirror of
https://github.com/moparisthebest/mail
synced 2024-12-22 07:18:49 -05:00
commit
37b1862e9f
4
.gitignore
vendored
4
.gitignore
vendored
@ -8,7 +8,5 @@ dist/
|
||||
release/
|
||||
test/integration/src/
|
||||
src/lib/*.js
|
||||
src/js/crypto/aes-cbc.js
|
||||
src/js/crypto/crypto-batch.js
|
||||
src/js/crypto/rsa.js
|
||||
src/js/crypto/aes-gcm.js
|
||||
src/js/crypto/util.js
|
||||
|
17
Gruntfile.js
17
Gruntfile.js
@ -44,29 +44,17 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
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: {
|
||||
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: {
|
||||
all: {
|
||||
options: {
|
||||
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'
|
||||
],
|
||||
run: false,
|
||||
@ -255,7 +243,6 @@ module.exports = function(grunt) {
|
||||
// Load the plugin(s)
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-qunit');
|
||||
grunt.loadNpmTasks('grunt-mocha');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
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)):
|
||||
|
||||
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.
|
||||
|
||||
### Development
|
||||
@ -40,7 +40,7 @@ For development you can start a connect dev server:
|
||||
|
||||
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
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
"start": "grunt && grunt dev"
|
||||
},
|
||||
"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",
|
||||
"mailreader": "https://github.com/whiteout-io/mailreader/tarball/v0.3.3",
|
||||
"pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/v0.3.4",
|
||||
@ -27,7 +27,6 @@
|
||||
"sinon": "~1.7.3",
|
||||
"grunt-contrib-connect": "~0.5.0",
|
||||
"grunt-contrib-jshint": "~0.6.4",
|
||||
"grunt-contrib-qunit": "~0.2.2",
|
||||
"grunt-mocha": "~0.4.1",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-csso": "~0.6.1",
|
||||
|
@ -25,8 +25,10 @@ define(function(require) {
|
||||
*/
|
||||
app.config = {
|
||||
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
|
||||
symKeySize: 128,
|
||||
symIvSize: 128,
|
||||
privkeyServerUrl: 'https://keychain-test.whiteout.io',
|
||||
serverPrivateKeyId: 'EE342F0DDBB0F3BE',
|
||||
symKeySize: 256,
|
||||
symIvSize: 96,
|
||||
asymKeySize: 2048,
|
||||
workerPath: 'js',
|
||||
gmail: {
|
||||
|
@ -12,6 +12,7 @@ define(function(require) {
|
||||
OutboxBO = require('js/bo/outbox'),
|
||||
mailreader = require('mailreader'),
|
||||
ImapClient = require('imap-client'),
|
||||
Crypto = require('js/crypto/crypto'),
|
||||
RestDAO = require('js/dao/rest-dao'),
|
||||
EmailDAO = require('js/dao/email-dao'),
|
||||
appConfig = require('js/app-config'),
|
||||
@ -20,6 +21,7 @@ define(function(require) {
|
||||
KeychainDAO = require('js/dao/keychain-dao'),
|
||||
PublicKeyDAO = require('js/dao/publickey-dao'),
|
||||
LawnchairDAO = require('js/dao/lawnchair-dao'),
|
||||
PrivateKeyDAO = require('js/dao/privatekey-dao'),
|
||||
InvitationDAO = require('js/dao/invitation-dao'),
|
||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||
UpdateHandler = require('js/util/update/update-handler');
|
||||
@ -55,7 +57,7 @@ define(function(require) {
|
||||
};
|
||||
|
||||
self.buildModules = function(options) {
|
||||
var lawnchairDao, restDao, pubkeyDao, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore;
|
||||
var lawnchairDao, restDao, pubkeyDao, privkeyDao, crypto, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore;
|
||||
|
||||
// start the mailreader's worker thread
|
||||
mailreader.startWorker(config.workerPath + '/../lib/mailreader-parser-worker.js');
|
||||
@ -64,9 +66,12 @@ define(function(require) {
|
||||
restDao = new RestDAO();
|
||||
lawnchairDao = new LawnchairDAO();
|
||||
pubkeyDao = new PublicKeyDAO(restDao);
|
||||
privkeyDao = new PrivateKeyDAO(new RestDAO(config.privkeyServerUrl));
|
||||
oauth = new OAuth(new RestDAO('https://www.googleapis.com'));
|
||||
|
||||
self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
|
||||
crypto = new Crypto();
|
||||
self._pgp = pgp = new PGP();
|
||||
self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao, privkeyDao, crypto, pgp);
|
||||
keychain.requestPermissionForKeyUpdate = function(params, callback) {
|
||||
var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey;
|
||||
message = message.replace('{0}', params.userId);
|
||||
@ -85,7 +90,6 @@ define(function(require) {
|
||||
self._auth = new Auth(appConfigStore, oauth, new RestDAO('/ca'));
|
||||
self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
|
||||
self._invitationDao = new InvitationDAO(restDao);
|
||||
self._crypto = pgp = new PGP();
|
||||
self._pgpbuilder = pgpbuilder = new PgpBuilder();
|
||||
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader);
|
||||
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
|
||||
|
@ -8,17 +8,19 @@ requirejs([
|
||||
'js/controller/add-account',
|
||||
'js/controller/account',
|
||||
'js/controller/set-passphrase',
|
||||
'js/controller/privatekey-upload',
|
||||
'js/controller/contacts',
|
||||
'js/controller/about',
|
||||
'js/controller/login',
|
||||
'js/controller/login-initial',
|
||||
'js/controller/login-new-device',
|
||||
'js/controller/login-existing',
|
||||
'js/controller/login-privatekey-download',
|
||||
'js/controller/mail-list',
|
||||
'js/controller/read',
|
||||
'js/controller/write',
|
||||
'js/controller/navigation',
|
||||
'cryptoLib/util',
|
||||
'js/crypto/util',
|
||||
'js/util/error',
|
||||
'fastclick',
|
||||
'angularSanitize',
|
||||
@ -31,12 +33,14 @@ requirejs([
|
||||
AddAccountCtrl,
|
||||
AccountCtrl,
|
||||
SetPassphraseCtrl,
|
||||
PrivateKeyUploadCtrl,
|
||||
ContactsCtrl,
|
||||
AboutCtrl,
|
||||
LoginCtrl,
|
||||
LoginInitialCtrl,
|
||||
LoginNewDeviceCtrl,
|
||||
LoginExistingCtrl,
|
||||
LoginPrivateKeyDownloadCtrl,
|
||||
MailListCtrl,
|
||||
ReadCtrl,
|
||||
WriteCtrl,
|
||||
@ -89,6 +93,10 @@ requirejs([
|
||||
templateUrl: 'tpl/login-new-device.html',
|
||||
controller: LoginNewDeviceCtrl
|
||||
});
|
||||
$routeProvider.when('/login-privatekey-download', {
|
||||
templateUrl: 'tpl/login-privatekey-download.html',
|
||||
controller: LoginPrivateKeyDownloadCtrl
|
||||
});
|
||||
$routeProvider.when('/desktop', {
|
||||
templateUrl: 'tpl/desktop.html',
|
||||
controller: NavigationCtrl
|
||||
@ -113,6 +121,7 @@ requirejs([
|
||||
app.controller('MailListCtrl', MailListCtrl);
|
||||
app.controller('AccountCtrl', AccountCtrl);
|
||||
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
|
||||
app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl);
|
||||
app.controller('ContactsCtrl', ContactsCtrl);
|
||||
app.controller('AboutCtrl', AboutCtrl);
|
||||
app.controller('DialogCtrl', DialogCtrl);
|
||||
|
@ -2,7 +2,7 @@ define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore'),
|
||||
util = require('cryptoLib/util'),
|
||||
util = require('js/crypto/util'),
|
||||
config = require('js/app-config').config,
|
||||
outboxDb = 'email_OUTBOX';
|
||||
|
||||
@ -27,7 +27,7 @@ define(function(require) {
|
||||
this._outboxBusy = false;
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
@ -12,7 +12,7 @@ define(function(require) {
|
||||
var AccountCtrl = function($scope) {
|
||||
userId = appController._emailDao._account.emailAddress;
|
||||
keychain = appController._keychain;
|
||||
pgp = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
|
||||
$scope.state.account = {
|
||||
toggle: function(to) {
|
||||
|
@ -12,7 +12,7 @@ define(function(require) {
|
||||
|
||||
var ContactsCtrl = function($scope) {
|
||||
keychain = appController._keychain,
|
||||
pgp = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
|
||||
$scope.state.contacts = {
|
||||
toggle: function(to) {
|
||||
|
@ -6,7 +6,7 @@ define(function(require) {
|
||||
|
||||
var LoginExistingCtrl = function($scope, $location) {
|
||||
var emailDao = appController._emailDao,
|
||||
pgp = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
|
||||
$scope.incorrect = false;
|
||||
|
||||
|
103
src/js/controller/login-privatekey-download.js
Normal file
103
src/js/controller/login-privatekey-download.js
Normal file
@ -0,0 +1,103 @@
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var appController = require('js/app-controller');
|
||||
|
||||
var LoginPrivateKeyDownloadCtrl = function($scope, $location) {
|
||||
var keychain = appController._keychain,
|
||||
emailDao = appController._emailDao,
|
||||
userId = emailDao._account.emailAddress;
|
||||
|
||||
$scope.step = 1;
|
||||
|
||||
$scope.verifyRecoveryToken = function(callback) {
|
||||
if (!$scope.recoveryToken) {
|
||||
$scope.onError(new Error('Please set the recovery token!'));
|
||||
return;
|
||||
}
|
||||
|
||||
keychain.getUserKeyPair(userId, function(err, keypair) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// remember for storage later
|
||||
$scope.cachedKeypair = keypair;
|
||||
|
||||
keychain.downloadPrivateKey({
|
||||
userId: userId,
|
||||
keyId: keypair.publicKey._id,
|
||||
recoveryToken: $scope.recoveryToken
|
||||
}, function(err, encryptedPrivateKey) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.encryptedPrivateKey = encryptedPrivateKey;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.decryptAndStorePrivateKeyLocally = function() {
|
||||
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
|
||||
|
||||
if (!inputCode) {
|
||||
$scope.onError(new Error('Please enter the keychain code!'));
|
||||
return;
|
||||
}
|
||||
|
||||
var options = $scope.encryptedPrivateKey;
|
||||
options.code = inputCode.toUpperCase();
|
||||
|
||||
keychain.decryptAndStorePrivateKeyLocally(options, function(err, privateKey) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// add private key to cached keypair object
|
||||
$scope.cachedKeypair.privateKey = privateKey;
|
||||
|
||||
// try empty passphrase
|
||||
emailDao.unlock({
|
||||
keypair: $scope.cachedKeypair,
|
||||
passphrase: undefined
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
// go to passphrase login screen
|
||||
$scope.goTo('/login-existing');
|
||||
return;
|
||||
}
|
||||
|
||||
// passphrase is corrent ... go to main app
|
||||
$scope.goTo('/desktop');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.goForward = function() {
|
||||
if ($scope.step === 1) {
|
||||
$scope.verifyRecoveryToken(function() {
|
||||
$scope.step++;
|
||||
$scope.$apply();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.step === 2) {
|
||||
$scope.decryptAndStorePrivateKeyLocally();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.goTo = function(location) {
|
||||
$location.path(location);
|
||||
$scope.$apply();
|
||||
};
|
||||
};
|
||||
|
||||
return LoginPrivateKeyDownloadCtrl;
|
||||
});
|
@ -52,9 +52,28 @@ define(function(require) {
|
||||
if (typeof availableKeys === 'undefined') {
|
||||
// no public key available, start onboarding process
|
||||
goTo('/login-initial');
|
||||
} else if (!availableKeys.privateKey) {
|
||||
// no private key, import key
|
||||
goTo('/login-new-device');
|
||||
|
||||
} else if (availableKeys && !availableKeys.privateKey) {
|
||||
// check if private key is synced
|
||||
appController._keychain.requestPrivateKeyDownload({
|
||||
userId: availableKeys.publicKey.userId,
|
||||
keyId: availableKeys.publicKey._id,
|
||||
}, function(err, privateKeySynced) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (privateKeySynced) {
|
||||
// private key is synced, proceed to download
|
||||
goTo('/login-privatekey-download');
|
||||
return;
|
||||
}
|
||||
|
||||
// no private key, import key file
|
||||
goTo('/login-new-device');
|
||||
});
|
||||
|
||||
} else {
|
||||
// public and private key available, try empty passphrase
|
||||
appController._emailDao.unlock({
|
||||
|
170
src/js/controller/privatekey-upload.js
Normal file
170
src/js/controller/privatekey-upload.js
Normal file
@ -0,0 +1,170 @@
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var appController = require('js/app-controller'),
|
||||
keychain, pgp;
|
||||
|
||||
var PrivateKeyUploadCtrl = function($scope) {
|
||||
keychain = appController._keychain;
|
||||
pgp = keychain._pgp;
|
||||
|
||||
$scope.state.privateKeyUpload = {
|
||||
toggle: function(to) {
|
||||
// open lightbox
|
||||
$scope.state.lightbox = (to) ? 'privatekey-upload' : undefined;
|
||||
if (!to) {
|
||||
return;
|
||||
}
|
||||
|
||||
// show syncing status
|
||||
$scope.step = 4;
|
||||
// check if key is already synced
|
||||
$scope.checkServerForKey(function(privateKeySynced) {
|
||||
if (privateKeySynced) {
|
||||
// close lightbox
|
||||
$scope.state.lightbox = undefined;
|
||||
// show message
|
||||
$scope.onError({
|
||||
title: 'Info',
|
||||
message: 'Your PGP key has already been synced.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// show sync ui if key is not synced
|
||||
$scope.displayUploadUi();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.checkServerForKey = function(callback) {
|
||||
var keyParams = pgp.getKeyParams();
|
||||
keychain.requestPrivateKeyDownload({
|
||||
userId: keyParams.userId,
|
||||
keyId: keyParams._id,
|
||||
}, function(err, privateKeySynced) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (privateKeySynced) {
|
||||
callback(privateKeySynced);
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.displayUploadUi = function() {
|
||||
// go to step 1
|
||||
$scope.step = 1;
|
||||
// generate new code for the user
|
||||
$scope.code = $scope.generateCode();
|
||||
$scope.displayedCode = $scope.code.slice(0, 4) + '-' + $scope.code.slice(4, 8) + '-' + $scope.code.slice(8, 12) + '-' + $scope.code.slice(12, 16) + '-' + $scope.code.slice(16, 20) + '-' + $scope.code.slice(20, 24);
|
||||
};
|
||||
|
||||
$scope.generateCode = function() {
|
||||
function randomString(length, chars) {
|
||||
var result = '';
|
||||
var randomValues = new Uint8Array(length); // get random length number of bytes
|
||||
window.crypto.getRandomValues(randomValues);
|
||||
for (var i = 0; i < length; i++) {
|
||||
result += chars[Math.round(randomValues[i] / 255 * (chars.length - 1))];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return randomString(24, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ');
|
||||
};
|
||||
|
||||
$scope.verifyCode = function() {
|
||||
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
|
||||
|
||||
if (inputCode.toUpperCase() !== $scope.code) {
|
||||
var err = new Error('The code does not match. Please go back and check the generated code.');
|
||||
err.sync = true;
|
||||
$scope.onError(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.setDeviceName = function(callback) {
|
||||
keychain.setDeviceName($scope.deviceName, callback);
|
||||
};
|
||||
|
||||
$scope.encryptAndUploadKey = function(callback) {
|
||||
var userId = appController._emailDao._account.emailAddress;
|
||||
var code = $scope.code;
|
||||
|
||||
// register device to keychain service
|
||||
keychain.registerDevice({
|
||||
userId: userId
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// encrypt private PGP key using code and upload
|
||||
keychain.uploadPrivateKey({
|
||||
userId: userId,
|
||||
code: code
|
||||
}, callback);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.goBack = function() {
|
||||
if ($scope.step > 1) {
|
||||
$scope.step--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.goForward = function() {
|
||||
if ($scope.step < 2) {
|
||||
$scope.step++;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.step === 2 && $scope.verifyCode()) {
|
||||
$scope.step++;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.step === 3) {
|
||||
// set device name to local storage
|
||||
$scope.setDeviceName(function(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// show spinner
|
||||
$scope.step++;
|
||||
$scope.$apply();
|
||||
|
||||
// init key sync
|
||||
$scope.encryptAndUploadKey(function(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// close sync dialog
|
||||
$scope.state.privateKeyUpload.toggle(false);
|
||||
// show success message
|
||||
$scope.onError({
|
||||
title: 'Success',
|
||||
message: 'Whiteout Keychain setup successful!'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
return PrivateKeyUploadCtrl;
|
||||
});
|
@ -5,7 +5,7 @@ define(function(require) {
|
||||
download = require('js/util/download'),
|
||||
angular = require('angular'),
|
||||
str = require('js/app-config').string,
|
||||
emailDao, invitationDao, outbox, crypto, keychain;
|
||||
emailDao, invitationDao, outbox, pgp, keychain;
|
||||
|
||||
//
|
||||
// Controller
|
||||
@ -16,7 +16,7 @@ define(function(require) {
|
||||
emailDao = appController._emailDao;
|
||||
invitationDao = appController._invitationDao;
|
||||
outbox = appController._outboxBo;
|
||||
crypto = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
keychain = appController._keychain;
|
||||
|
||||
// set default value so that the popover height is correct on init
|
||||
@ -47,7 +47,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fpr = crypto.getFingerprint(pubkey.publicKey);
|
||||
var fpr = pgp.getFingerprint(pubkey.publicKey);
|
||||
var formatted = fpr.slice(32);
|
||||
|
||||
$scope.keyId = 'PGP key: ' + formatted;
|
||||
|
@ -10,7 +10,7 @@ define(function(require) {
|
||||
|
||||
var SetPassphraseCtrl = function($scope) {
|
||||
keychain = appController._keychain;
|
||||
pgp = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
|
||||
$scope.state.setPassphrase = {
|
||||
toggle: function(to) {
|
||||
|
@ -4,17 +4,17 @@ define(function(require) {
|
||||
var angular = require('angular'),
|
||||
_ = require('underscore'),
|
||||
appController = require('js/app-controller'),
|
||||
aes = require('cryptoLib/aes-cbc'),
|
||||
util = require('cryptoLib/util'),
|
||||
aes = require('js/crypto/aes-gcm'),
|
||||
util = require('js/crypto/util'),
|
||||
str = require('js/app-config').string,
|
||||
crypto, emailDao, outbox, keychainDao;
|
||||
pgp, emailDao, outbox, keychainDao;
|
||||
|
||||
//
|
||||
// Controller
|
||||
//
|
||||
|
||||
var WriteCtrl = function($scope, $filter) {
|
||||
crypto = appController._crypto;
|
||||
pgp = appController._pgp;
|
||||
emailDao = appController._emailDao,
|
||||
outbox = appController._outboxBo;
|
||||
keychainDao = appController._keychain;
|
||||
@ -218,7 +218,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fpr = crypto.getFingerprint(recipient.key.publicKey);
|
||||
var fpr = pgp.getFingerprint(recipient.key.publicKey);
|
||||
var formatted = fpr.slice(32);
|
||||
|
||||
$scope.keyId = formatted;
|
||||
|
@ -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) {
|
||||
'use strict';
|
||||
|
||||
var util = require('cryptoLib/util'),
|
||||
aes = require('cryptoLib/aes-cbc'),
|
||||
rsa = require('cryptoLib/rsa'),
|
||||
cryptoBatch = require('cryptoLib/crypto-batch'),
|
||||
var aes = require('js/crypto/aes-gcm'),
|
||||
pbkdf2 = require('js/crypto/pbkdf2'),
|
||||
config = require('js/app-config').config;
|
||||
|
||||
var passBasedKey,
|
||||
BATCH_WORKER = '/crypto/crypto-batch-worker.js',
|
||||
PBKDF2_WORKER = '/crypto/pbkdf2-worker.js';
|
||||
var PBKDF2_WORKER = '/crypto/pbkdf2-worker.js';
|
||||
|
||||
var Crypto = function() {
|
||||
|
||||
};
|
||||
var Crypto = function() {};
|
||||
|
||||
/**
|
||||
* Initializes the crypto modules by fetching the user's
|
||||
* encrypted secret key from storage and storing it in memory.
|
||||
* Encrypt plaintext using AES-GCM.
|
||||
* @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) {
|
||||
var self = this;
|
||||
Crypto.prototype.encrypt = function(plaintext, key, iv, callback) {
|
||||
var ct;
|
||||
|
||||
// valdiate input
|
||||
if (!args.emailAddress || !args.keySize || !args.rsaKeySize || typeof args.password !== 'string' || !args.salt) {
|
||||
callback({
|
||||
errMsg: 'Crypto init failed. Not all args set!'
|
||||
});
|
||||
try {
|
||||
ct = aes.encrypt(plaintext, key, iv);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
self.emailAddress = args.emailAddress;
|
||||
self.keySize = args.keySize;
|
||||
self.ivSize = args.keySize;
|
||||
self.rsaKeySize = args.rsaKeySize;
|
||||
callback(null, ct);
|
||||
};
|
||||
|
||||
// derive PBKDF2 from password in web worker thread
|
||||
self.deriveKey(args.password, args.salt, self.keySize, function(err, derivedKey) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Decrypt ciphertext suing AES-GCM
|
||||
* @param {String} ciphertext The base64 encoded ciphertext
|
||||
* @param {String} key The base64 encoded key
|
||||
* @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
|
||||
passBasedKey = derivedKey;
|
||||
|
||||
// check if key exists
|
||||
if (!args.storedKeypair) {
|
||||
// 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);
|
||||
});
|
||||
try {
|
||||
pt = aes.decrypt(ciphertext, key, iv);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
function decryptKeypair(storedKeypair, derivedKey) {
|
||||
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();
|
||||
}
|
||||
callback(null, pt);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -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
|
||||
//
|
||||
|
@ -1,24 +1,25 @@
|
||||
/**
|
||||
* A Wrapper for Forge's PBKDF2 function
|
||||
*/
|
||||
define(['node-forge'], function(forge) {
|
||||
define(['forge'], function(forge) {
|
||||
'use strict';
|
||||
|
||||
var self = {};
|
||||
|
||||
/**
|
||||
* PBKDF2-HMAC-SHA1 key derivation with a random salt and 1000 iterations
|
||||
* @param password [String] The password in UTF8
|
||||
* @param salt [String] The base64 encoded salt
|
||||
* @param keySize [Number] The key size in bits
|
||||
* @return [String] The base64 encoded key
|
||||
* PBKDF2-HMAC-SHA256 key derivation with a random salt and 10000 iterations
|
||||
* @param {String} password The password in UTF8
|
||||
* @param {String} salt The base64 encoded salt
|
||||
* @param {String} keySize The key size in bits
|
||||
* @return {String} The base64 encoded key
|
||||
*/
|
||||
self.getKey = function(password, salt, keySize) {
|
||||
var key = forge.pkcs5.pbkdf2(password, forge.util.decode64(salt), 1000, keySize / 8);
|
||||
var keyBase64 = forge.util.encode64(key);
|
||||
var saltUtf8 = forge.util.decode64(salt);
|
||||
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;
|
||||
});
|
||||
});
|
@ -20,23 +20,23 @@ define(function(require) {
|
||||
var userId, passphrase;
|
||||
|
||||
if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) {
|
||||
callback({
|
||||
errMsg: 'Crypto init failed. Not all options set!'
|
||||
});
|
||||
callback(new Error('Crypto init failed. Not all options set!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// generate keypair (keytype 1=RSA)
|
||||
// generate keypair
|
||||
userId = 'Whiteout User <' + options.emailAddress + '>';
|
||||
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) {
|
||||
if (err) {
|
||||
callback({
|
||||
errMsg: 'Keygeneration failed!',
|
||||
err: err
|
||||
});
|
||||
callback(new Error('Keygeneration failed!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -141,9 +141,7 @@ define(function(require) {
|
||||
|
||||
// check options
|
||||
if (!options.privateKeyArmored || !options.publicKeyArmored) {
|
||||
callback({
|
||||
errMsg: 'Importing keys failed. Not all options set!'
|
||||
});
|
||||
callback(new Error('Importing keys failed. Not all options set!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -158,18 +156,14 @@ define(function(require) {
|
||||
this._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
|
||||
} catch (e) {
|
||||
resetKeys();
|
||||
callback({
|
||||
errMsg: 'Importing keys failed. Parsing error!'
|
||||
});
|
||||
callback(new Error('Importing keys failed. Parsing error!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// decrypt private key with passphrase
|
||||
if (!this._privateKey.decrypt(options.passphrase)) {
|
||||
resetKeys();
|
||||
callback({
|
||||
errMsg: 'Incorrect passphrase!'
|
||||
});
|
||||
callback(new Error('Incorrect passphrase!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -178,9 +172,7 @@ define(function(require) {
|
||||
privKeyId = this._privateKey.getKeyPacket().getKeyId().toHex();
|
||||
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
|
||||
resetKeys();
|
||||
callback({
|
||||
errMsg: 'Key IDs dont match!'
|
||||
});
|
||||
callback(new Error('Key IDs dont match!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -192,9 +184,7 @@ define(function(require) {
|
||||
*/
|
||||
PGP.prototype.exportKeys = function(callback) {
|
||||
if (!this._publicKey || !this._privateKey) {
|
||||
callback({
|
||||
errMsg: 'Could not export keys!'
|
||||
});
|
||||
callback(new Error('Could not export keys!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -215,9 +205,7 @@ define(function(require) {
|
||||
newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined;
|
||||
|
||||
if (!options.privateKeyArmored) {
|
||||
callback({
|
||||
errMsg: 'Private key must be specified to change passphrase!'
|
||||
});
|
||||
callback(new Error('Private key must be specified to change passphrase!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -231,17 +219,13 @@ define(function(require) {
|
||||
try {
|
||||
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
|
||||
} catch (e) {
|
||||
callback({
|
||||
errMsg: 'Importing key failed. Parsing error!'
|
||||
});
|
||||
callback(new Error('Importing key failed. Parsing error!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// decrypt private key with passphrase
|
||||
if (!privKey.decrypt(options.oldPassphrase)) {
|
||||
callback({
|
||||
errMsg: 'Old passphrase incorrect!'
|
||||
});
|
||||
callback(new Error('Old passphrase incorrect!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -253,17 +237,13 @@ define(function(require) {
|
||||
}
|
||||
newKeyArmored = privKey.armor();
|
||||
} catch (e) {
|
||||
callback({
|
||||
errMsg: 'Setting new passphrase failed!'
|
||||
});
|
||||
callback(new Error('Setting new passphrase failed!'));
|
||||
return;
|
||||
}
|
||||
|
||||
// check if new passphrase really works
|
||||
if (!privKey.decrypt(newPassphrase)) {
|
||||
callback({
|
||||
errMsg: 'Decrypting key with new passphrase failed!'
|
||||
});
|
||||
callback(new Error('Decrypting key with new passphrase failed!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -278,9 +258,7 @@ define(function(require) {
|
||||
|
||||
// check keys
|
||||
if (!this._privateKey || publicKeysArmored.length < 1) {
|
||||
callback({
|
||||
errMsg: 'Error encrypting. Keys must be set!'
|
||||
});
|
||||
callback(new Error('Error encrypting. Keys must be set!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -290,10 +268,7 @@ define(function(require) {
|
||||
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
|
||||
});
|
||||
} catch (err) {
|
||||
callback({
|
||||
errMsg: 'Error encrypting plaintext!',
|
||||
err: err
|
||||
});
|
||||
callback(new Error('Error encrypting plaintext!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -309,9 +284,7 @@ define(function(require) {
|
||||
|
||||
// check keys
|
||||
if (!this._privateKey || !publicKeyArmored) {
|
||||
callback({
|
||||
errMsg: 'Error decrypting. Keys must be set!'
|
||||
});
|
||||
callback(new Error('Error decrypting. Keys must be set!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -320,10 +293,7 @@ define(function(require) {
|
||||
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
|
||||
message = openpgp.message.readArmored(ciphertext);
|
||||
} catch (err) {
|
||||
callback({
|
||||
errMsg: 'Error decrypting PGP message!',
|
||||
err: err
|
||||
});
|
||||
callback(new Error('Error decrypting PGP message!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -332,10 +302,7 @@ define(function(require) {
|
||||
|
||||
function onDecrypted(err, decrypted) {
|
||||
if (err) {
|
||||
callback({
|
||||
errMsg: 'Error decrypting PGP message!',
|
||||
err: err
|
||||
});
|
||||
callback(new Error('Error decrypting PGP message!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -347,9 +314,7 @@ define(function(require) {
|
||||
}
|
||||
});
|
||||
if (!signaturesValid) {
|
||||
callback({
|
||||
errMsg: 'Verifying PGP signature failed!'
|
||||
});
|
||||
callback(new Error('Verifying PGP signature failed!'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var util = require('cryptoLib/util'),
|
||||
var util = require('js/crypto/util'),
|
||||
_ = require('underscore'),
|
||||
config = require('js/app-config').config,
|
||||
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
|
||||
*
|
||||
* @param {Object} keychain The keychain DAO handles keys transparently
|
||||
* @param {Object} crypto Orchestrates decryption
|
||||
* @param {Object} pgp Orchestrates decryption
|
||||
* @param {Object} devicestorage Handles persistence to the local indexed db
|
||||
* @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages
|
||||
* @param {Object} mailreader Parses MIME messages received from IMAP
|
||||
*/
|
||||
var EmailDAO = function(keychain, crypto, devicestorage, pgpbuilder, mailreader) {
|
||||
var EmailDAO = function(keychain, pgp, devicestorage, pgpbuilder, mailreader) {
|
||||
this._keychain = keychain;
|
||||
this._crypto = crypto;
|
||||
this._pgp = pgp;
|
||||
this._devicestorage = devicestorage;
|
||||
this._pgpbuilder = pgpbuilder;
|
||||
this._mailreader = mailreader;
|
||||
@ -105,7 +105,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// no keypair for is stored for the user... generate a new one
|
||||
self._crypto.generateKeys({
|
||||
self._pgp.generateKeys({
|
||||
emailAddress: self._account.emailAddress,
|
||||
keySize: self._account.asymKeySize,
|
||||
passphrase: options.passphrase
|
||||
@ -121,8 +121,8 @@ define(function(require) {
|
||||
function handleExistingKeypair(keypair) {
|
||||
var privKeyParams, pubKeyParams;
|
||||
try {
|
||||
privKeyParams = self._crypto.getKeyParams(keypair.privateKey.encryptedKey);
|
||||
pubKeyParams = self._crypto.getKeyParams(keypair.publicKey.publicKey);
|
||||
privKeyParams = self._pgp.getKeyParams(keypair.privateKey.encryptedKey);
|
||||
pubKeyParams = self._pgp.getKeyParams(keypair.publicKey.publicKey);
|
||||
} catch (e) {
|
||||
callback(new Error('Error reading key params!'));
|
||||
return;
|
||||
@ -148,7 +148,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// import existing key pair into crypto module
|
||||
self._crypto.importKeys({
|
||||
self._pgp.importKeys({
|
||||
passphrase: options.passphrase,
|
||||
privateKeyArmored: keypair.privateKey.encryptedKey,
|
||||
publicKeyArmored: keypair.publicKey.publicKey
|
||||
@ -159,14 +159,14 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// set decrypted privateKey to pgpMailer
|
||||
self._pgpbuilder._privateKey = self._crypto._privateKey;
|
||||
self._pgpbuilder._privateKey = self._pgp._privateKey;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function handleGenerated(generatedKeypair) {
|
||||
// import the new key pair into crypto module
|
||||
self._crypto.importKeys({
|
||||
self._pgp.importKeys({
|
||||
passphrase: options.passphrase,
|
||||
privateKeyArmored: generatedKeypair.privateKeyArmored,
|
||||
publicKeyArmored: generatedKeypair.publicKeyArmored
|
||||
@ -196,7 +196,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// set decrypted privateKey to pgpMailer
|
||||
self._pgpbuilder._privateKey = self._crypto._privateKey;
|
||||
self._pgpbuilder._privateKey = self._pgp._privateKey;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
@ -818,7 +818,7 @@ define(function(require) {
|
||||
|
||||
// get the receiver's public key to check the message signature
|
||||
var encryptedNode = filterBodyParts(message.bodyParts, 'encrypted')[0];
|
||||
self._crypto.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted) {
|
||||
self._pgp.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted) {
|
||||
if (err || !decrypted) {
|
||||
showError(err.errMsg || err.message || 'An error occurred during the decryption.');
|
||||
return;
|
||||
|
@ -5,13 +5,27 @@
|
||||
define(function(require) {
|
||||
'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._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
|
||||
* @param {String} uuid The uuid to verify the key
|
||||
@ -170,7 +184,7 @@ define(function(require) {
|
||||
var self = this;
|
||||
|
||||
// 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) {
|
||||
callback(err);
|
||||
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
|
||||
* or fetches it from the cloud. The private key is encrypted.
|
||||
@ -244,7 +744,7 @@ define(function(require) {
|
||||
var self = this;
|
||||
|
||||
// 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) {
|
||||
callback(err);
|
||||
return;
|
||||
@ -364,7 +864,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// lookup in local storage
|
||||
self._localDbDao.read('publickey_' + id, function(err, pubkey) {
|
||||
self._localDbDao.read(DB_PUBLICKEY + '_' + id, function(err, pubkey) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
@ -400,29 +900,29 @@ define(function(require) {
|
||||
*/
|
||||
KeychainDAO.prototype.listLocalPublicKeys = function(callback) {
|
||||
// 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) {
|
||||
this._localDbDao.remove('publickey_' + id, callback);
|
||||
this._localDbDao.remove(DB_PUBLICKEY + '_' + id, callback);
|
||||
};
|
||||
|
||||
KeychainDAO.prototype.lookupPrivateKey = function(id, callback) {
|
||||
// lookup in local storage
|
||||
this._localDbDao.read('privatekey_' + id, callback);
|
||||
this._localDbDao.read(DB_PRIVATEKEY + '_' + id, callback);
|
||||
};
|
||||
|
||||
KeychainDAO.prototype.saveLocalPublicKey = function(pubkey, callback) {
|
||||
// persist public key (email, _id)
|
||||
var pkLookupKey = 'publickey_' + pubkey._id;
|
||||
var pkLookupKey = DB_PUBLICKEY + '_' + pubkey._id;
|
||||
this._localDbDao.persist(pkLookupKey, pubkey, callback);
|
||||
};
|
||||
|
||||
KeychainDAO.prototype.saveLocalPrivateKey = function(privkey, callback) {
|
||||
// persist private key (email, _id)
|
||||
var prkLookupKey = 'privatekey_' + privkey._id;
|
||||
var prkLookupKey = DB_PRIVATEKEY + '_' + privkey._id;
|
||||
this._localDbDao.persist(prkLookupKey, privkey, callback);
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
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') {
|
||||
callback({
|
||||
@ -30,11 +71,11 @@ define(function(require) {
|
||||
options.type = options.type || 'json';
|
||||
|
||||
if (options.type === 'json') {
|
||||
acceptHeader = 'application/json';
|
||||
format = 'application/json';
|
||||
} else if (options.type === 'xml') {
|
||||
acceptHeader = 'application/xml';
|
||||
format = 'application/xml';
|
||||
} else if (options.type === 'text') {
|
||||
acceptHeader = 'text/plain';
|
||||
format = 'text/plain';
|
||||
} else {
|
||||
callback({
|
||||
code: 400,
|
||||
@ -44,14 +85,16 @@ define(function(require) {
|
||||
}
|
||||
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', this._baseUri + options.uri);
|
||||
xhr.setRequestHeader('Accept', acceptHeader);
|
||||
xhr.open(options.method, this._baseUri + options.uri);
|
||||
xhr.setRequestHeader('Accept', format);
|
||||
xhr.setRequestHeader('Content-Type', format);
|
||||
|
||||
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') {
|
||||
res = JSON.parse(xhr.responseText);
|
||||
res = xhr.responseText ? JSON.parse(xhr.responseText) : xhr.responseText;
|
||||
} else {
|
||||
res = xhr.responseText;
|
||||
}
|
||||
@ -69,72 +112,11 @@ define(function(require) {
|
||||
xhr.onerror = function() {
|
||||
callback({
|
||||
code: 42,
|
||||
errMsg: 'Error calling GET on ' + options.uri
|
||||
errMsg: 'Error calling ' + options.method + ' on ' + options.uri
|
||||
});
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined);
|
||||
};
|
||||
|
||||
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: {
|
||||
js: '../js',
|
||||
test: '../../test',
|
||||
cryptoLib: '../js/crypto',
|
||||
underscore: 'underscore/underscore-min',
|
||||
lawnchair: 'lawnchair/lawnchair-git',
|
||||
lawnchairSQL: 'lawnchair/lawnchair-adapter-webkit-sqlite-git',
|
||||
|
@ -32,6 +32,7 @@
|
||||
@import "views/shared";
|
||||
@import "views/add-account";
|
||||
@import "views/account";
|
||||
@import "views/privatekey-upload";
|
||||
@import "views/set-passphrase";
|
||||
@import "views/contacts";
|
||||
@import "views/about";
|
||||
|
@ -1,8 +1,12 @@
|
||||
.view-account {
|
||||
|
||||
a {
|
||||
color: $color-blue;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 50px auto 60px auto;
|
||||
|
||||
|
||||
td {
|
||||
padding-top: 15px;
|
||||
|
||||
|
@ -119,4 +119,15 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-login-privatekey-download {
|
||||
.content {
|
||||
max-width: 500px;
|
||||
|
||||
input.code {
|
||||
margin-right: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
@ -66,7 +66,6 @@
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
padding: 0 $nav-padding;
|
||||
background: $color-white;
|
||||
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>
|
||||
<td>PGP Key ID</td>
|
||||
<td>{{keyId}}</td>
|
||||
<td>{{keyId}} (<a href="https://whiteout.io/revocation.html" title="Click here to reset your account." target="_blank">Revoke key</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PGP Fingerprint</td>
|
||||
|
@ -31,6 +31,10 @@
|
||||
<div class="lightbox" ng-include="'tpl/set-passphrase.html'"></div>
|
||||
</div>
|
||||
|
||||
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'privatekey-upload'}">
|
||||
<div class="lightbox" ng-include="'tpl/privatekey-upload.html'"></div>
|
||||
</div>
|
||||
|
||||
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'contacts'}">
|
||||
<div class="lightbox" ng-include="'tpl/contacts.html'"></div>
|
||||
</div>
|
||||
|
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">
|
||||
<li><a href="#" ng-click="state.account.toggle(true); $event.preventDefault()">Account</a></li>
|
||||
<li><a href="#" ng-click="state.contacts.toggle(true); $event.preventDefault()">Contacts</a></li>
|
||||
<li><a href="#" ng-click="state.privateKeyUpload.toggle(true); $event.preventDefault()">Key sync (experimental)</a></li>
|
||||
<li><a href="#" ng-click="state.about.toggle(true); $event.preventDefault()">About</a></li>
|
||||
</ul>
|
||||
|
||||
|
46
src/tpl/privatekey-upload.html
Normal file
46
src/tpl/privatekey-upload.html
Normal file
@ -0,0 +1,46 @@
|
||||
<div class="lightbox-body" ng-controller="PrivateKeyUploadCtrl">
|
||||
<header>
|
||||
<h2>Setup Key Sync</h2>
|
||||
<button class="close" ng-click="state.privateKeyUpload.toggle(false)" data-action="lightbox-close"></button>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<div class="dialog view-privatekey-upload">
|
||||
|
||||
<div class="step" ng-show="step === 1">
|
||||
<p>Your keychain code can be used to securely backup and sync your PGP key between devices. This feature is experimental and not recommended for production use. <a href="" target="_blank">Learn more</a>.</p>
|
||||
|
||||
<h2>{{displayedCode}}</h2>
|
||||
|
||||
<p>Please write down your keychain code and keep it in a safe place. Whiteout Networks cannot recover a lost code.</p>
|
||||
</div>
|
||||
|
||||
<div class="step" ng-show="step === 2">
|
||||
<p>Please confirm the keychain code you have written down.</p>
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code0" focus-me="step === 2"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code1"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code2"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code3"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code4"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code5">
|
||||
</div>
|
||||
|
||||
<div class="step" ng-show="step === 3">
|
||||
<p>Please enter a memorable name for this device e.g. "MacBook Work".</p>
|
||||
<input type="text" class="input-text" ng-model="deviceName" placeholder="Device name" focus-me="step === 3">
|
||||
</div>
|
||||
|
||||
<div class="step" ng-show="step === 4">
|
||||
<div class="working">
|
||||
<span class="spinner"></span>
|
||||
</div><!--/.working-->
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button ng-show="step > 1 && step < 4" class="btn btn-alt" ng-click="goBack()">Go back</button>
|
||||
<button ng-show="step < 4" class="btn" ng-click="goForward()">Continue</button>
|
||||
</div>
|
||||
|
||||
</div><!-- /.view-privatekey-upload -->
|
||||
</div><!-- /.content -->
|
||||
</div><!-- /.lightbox-body -->
|
@ -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,
|
||||
dummyFingerprint, expectedFingerprint,
|
||||
dummyKeyId, expectedKeyId,
|
||||
emailAddress, keySize, cryptoMock, keychainMock;
|
||||
emailAddress, keySize, pgpMock, keychainMock;
|
||||
|
||||
beforeEach(function() {
|
||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
||||
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
|
||||
expectedFingerprint = '3A2D 39B4 E140 4190 B8B9 49DE 7D7E 9903 6E71 2926';
|
||||
dummyKeyId = '9FEB47936E712926';
|
||||
expectedKeyId = '6E712926';
|
||||
cryptoMock.getFingerprint.returns(dummyFingerprint);
|
||||
cryptoMock.getKeyId.returns(dummyKeyId);
|
||||
pgpMock.getFingerprint.returns(dummyFingerprint);
|
||||
pgpMock.getKeyId.returns(dummyKeyId);
|
||||
emailAddress = 'fred@foo.com';
|
||||
keySize = 1234;
|
||||
appController._emailDao = {
|
||||
@ -34,7 +34,7 @@ define(function(require) {
|
||||
asymKeySize: keySize
|
||||
}
|
||||
};
|
||||
cryptoMock.getKeyParams.returns({
|
||||
pgpMock.getKeyParams.returns({
|
||||
_id: dummyKeyId,
|
||||
fingerprint: dummyFingerprint,
|
||||
userId: emailAddress,
|
@ -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._invitationDao).to.exist;
|
||||
expect(controller._keychain).to.exist;
|
||||
expect(controller._crypto).to.exist;
|
||||
expect(controller._pgp).to.exist;
|
||||
expect(controller._pgpbuilder).to.exist;
|
||||
expect(controller._emailDao).to.exist;
|
||||
expect(controller._outboxBo).to.exist;
|
@ -12,11 +12,11 @@ define(function(require) {
|
||||
describe('Contacts Controller unit test', function() {
|
||||
var scope, contactsCtrl,
|
||||
origKeychain, keychainMock,
|
||||
origCrypto, cryptoMock;
|
||||
origPgp, pgpMock;
|
||||
|
||||
beforeEach(function() {
|
||||
origCrypto = appController._crypto;
|
||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
||||
origPgp = appController._pgp;
|
||||
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
|
||||
origKeychain = appController._keychain;
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
@ -33,7 +33,7 @@ define(function(require) {
|
||||
|
||||
afterEach(function() {
|
||||
// restore the module
|
||||
appController._crypto = origCrypto;
|
||||
appController._pgp = origPgp;
|
||||
appController._keychain = origKeychain;
|
||||
});
|
||||
|
||||
@ -60,7 +60,7 @@ define(function(require) {
|
||||
keychainMock.listLocalPublicKeys.yields(null, [{
|
||||
_id: '12345'
|
||||
}]);
|
||||
cryptoMock.getKeyParams.returns({
|
||||
pgpMock.getKeyParams.returns({
|
||||
fingerprint: 'asdf'
|
||||
});
|
||||
|
||||
@ -92,7 +92,7 @@ define(function(require) {
|
||||
it('should work', function(done) {
|
||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||
|
||||
cryptoMock.getKeyParams.returns({
|
||||
pgpMock.getKeyParams.returns({
|
||||
_id: '12345',
|
||||
userId: 'max@example.com',
|
||||
userIds: []
|
||||
@ -127,7 +127,7 @@ define(function(require) {
|
||||
it('should fail due to error in pgp.getKeyParams', function(done) {
|
||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||
|
||||
cryptoMock.getKeyParams.throws(new Error('WAT'));
|
||||
pgpMock.getKeyParams.throws(new Error('WAT'));
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
@ -140,7 +140,7 @@ define(function(require) {
|
||||
it('should fail due to error in keychain.saveLocalPublicKey', function(done) {
|
||||
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
|
||||
|
||||
cryptoMock.getKeyParams.returns({
|
||||
pgpMock.getKeyParams.returns({
|
||||
_id: '12345',
|
||||
userId: 'max@example.com'
|
||||
});
|
@ -1,121 +1,58 @@
|
||||
define(['js/crypto/crypto', 'cryptoLib/util', 'test/test-data'], function(Crypto, util, testData) {
|
||||
'use strict';
|
||||
define(function(require) {
|
||||
'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 = {
|
||||
user: 'crypto_test@example.com',
|
||||
password: 'Password',
|
||||
keySize: 128,
|
||||
ivSize: 128,
|
||||
rsaKeySize: 1024,
|
||||
salt: util.random(128)
|
||||
};
|
||||
describe('Crypto unit tests', function() {
|
||||
this.timeout(20000);
|
||||
|
||||
var crypto;
|
||||
var crypto,
|
||||
password = 'password',
|
||||
keySize = config.symKeySize,
|
||||
ivSize = config.symIvSize;
|
||||
|
||||
asyncTest("Init without keypair", 4, function() {
|
||||
crypto = new Crypto();
|
||||
// init dependencies
|
||||
ok(crypto, 'Crypto');
|
||||
beforeEach(function() {
|
||||
crypto = new Crypto();
|
||||
});
|
||||
|
||||
// test without passing keys
|
||||
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;
|
||||
afterEach(function() {});
|
||||
|
||||
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() {
|
||||
// test with passing keypair
|
||||
crypto.init({
|
||||
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');
|
||||
crypto.encrypt(plaintext, key, iv, function(err, ciphertext) {
|
||||
expect(err).to.not.exist;
|
||||
expect(ciphertext).to.exist;
|
||||
|
||||
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() {
|
||||
crypto.deriveKey(cryptoTest.password, cryptoTest.salt, cryptoTest.keySize, function(err, key) {
|
||||
ok(!err);
|
||||
equal(util.base642Str(key).length * 8, cryptoTest.keySize, 'Keysize ' + cryptoTest.keySize);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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() {
|
||||
// generate test data
|
||||
cryptoTest.symlist = testData.getEmailCollection(10);
|
||||
crypto.deriveKey(password, salt, keySize, function(err, key) {
|
||||
expect(err).to.not.exist;
|
||||
expect(util.base642Str(key).length * 8).to.equal(keySize);
|
||||
|
||||
crypto.symEncryptList(cryptoTest.symlist, function(err, result) {
|
||||
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;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
module("DeviceStorage");
|
||||
var LawnchairDAO = require('js/dao/lawnchair-dao'),
|
||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||
expect = chai.expect;
|
||||
|
||||
var devicestorageTest = {
|
||||
user: 'devicestorage_test@example.com',
|
||||
password: 'Password',
|
||||
keySize: 128,
|
||||
ivSize: 128,
|
||||
rsaKeySize: 1024
|
||||
};
|
||||
var testUser = 'test@example.com';
|
||||
|
||||
var crypto, storage;
|
||||
describe('Device Storage DAO unit tests', function() {
|
||||
|
||||
asyncTest("Init", 3, function() {
|
||||
// init dependencies
|
||||
storage = new DeviceStorageDAO(new LawnchairDAO());
|
||||
storage.init(devicestorageTest.user, function() {
|
||||
ok(storage, 'DeviceStorageDAO');
|
||||
var storageDao, lawnchairDaoStub;
|
||||
|
||||
// generate test data
|
||||
devicestorageTest.list = testData.getEmailCollection(100);
|
||||
beforeEach(function() {
|
||||
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
|
||||
storageDao = new DeviceStorageDAO(lawnchairDaoStub);
|
||||
});
|
||||
|
||||
// init crypto
|
||||
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;
|
||||
afterEach(function() {});
|
||||
|
||||
// clear db before tests
|
||||
storage.clear(function(err) {
|
||||
ok(!err, 'DB cleared. Error status: ' + err);
|
||||
describe('init', function() {
|
||||
it('should work', function(done) {
|
||||
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() {
|
||||
var receiverPubkeys = [devicestorageTest.generatedKeypair.publicKey];
|
||||
describe('store list', function() {
|
||||
it('should fail', function(done) {
|
||||
var list = [{}];
|
||||
|
||||
crypto.encryptListForUser(devicestorageTest.list, receiverPubkeys, function(err, encryptedList) {
|
||||
ok(!err);
|
||||
equal(encryptedList.length, devicestorageTest.list.length, 'Encrypt list');
|
||||
|
||||
encryptedList.forEach(function(i) {
|
||||
i.sentDate = _.findWhere(devicestorageTest.list, {
|
||||
id: i.id
|
||||
}).sentDate;
|
||||
storageDao.storeList(list, '', function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
devicestorageTest.encryptedList = encryptedList;
|
||||
start();
|
||||
});
|
||||
});
|
||||
it('should work with empty list', function(done) {
|
||||
var list = [];
|
||||
|
||||
asyncTest("Store encrypted list", 1, function() {
|
||||
storage.storeList(devicestorageTest.encryptedList, 'email_inbox', function() {
|
||||
ok(true, 'Store encrypted list');
|
||||
storageDao.storeList(list, 'email', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
start();
|
||||
});
|
||||
});
|
||||
it('should work', function(done) {
|
||||
lawnchairDaoStub.batch.yields();
|
||||
|
||||
asyncTest("List items", 4, function() {
|
||||
var senderPubkeys = [devicestorageTest.generatedKeypair.publicKey];
|
||||
var list = [{
|
||||
foo: 'bar'
|
||||
}];
|
||||
|
||||
var offset = 2,
|
||||
num = 6;
|
||||
|
||||
// list encrypted items from storage
|
||||
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();
|
||||
storageDao.storeList(list, 'email', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(lawnchairDaoStub.batch.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest("Delete List items", 1, function() {
|
||||
// list encrypted items from storage
|
||||
storage.removeList('email_inbox', function(err) {
|
||||
ok(!err);
|
||||
describe('remove list', function() {
|
||||
it('should work', function(done) {
|
||||
lawnchairDaoStub.removeList.yields();
|
||||
|
||||
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>
|
||||
<html>
|
||||
<html style="overflow-y: auto">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JavaScript Unit Tests</title>
|
||||
<link rel="stylesheet" href="../qunit-1.11.0.css">
|
||||
<link rel="stylesheet" href="../lib/mocha.css" />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="qunit"></div>
|
||||
<div id="qunit-fixture"></div>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<script src="../qunit-1.11.0.js"></script>
|
||||
<script>QUnit.config.autostart = false;</script>
|
||||
<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>
|
||||
</html>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,13 @@ define(function(require) {
|
||||
LoginCtrl = require('js/controller/login'),
|
||||
EmailDAO = require('js/dao/email-dao'),
|
||||
Auth = require('js/bo/auth'),
|
||||
appController = require('js/app-controller');
|
||||
appController = require('js/app-controller'),
|
||||
KeychainDAO = require('js/dao/keychain-dao');
|
||||
|
||||
describe('Login Controller unit test', function() {
|
||||
var scope, location, ctrl, origEmailDao, emailDaoMock,
|
||||
var scope, location, ctrl,
|
||||
origEmailDao, emailDaoMock,
|
||||
origKeychain, keychainMock,
|
||||
emailAddress = 'fred@foo.com',
|
||||
startAppStub,
|
||||
checkForUpdateStub,
|
||||
@ -21,14 +24,16 @@ define(function(require) {
|
||||
var hasChrome, hasIdentity;
|
||||
|
||||
beforeEach(function() {
|
||||
hasChrome = !! window.chrome;
|
||||
hasIdentity = !! window.chrome.identity;
|
||||
hasChrome = !!window.chrome;
|
||||
hasIdentity = !!window.chrome.identity;
|
||||
window.chrome = window.chrome || {};
|
||||
window.chrome.identity = window.chrome.identity || {};
|
||||
|
||||
// remember original module to restore later, then replace it
|
||||
origEmailDao = appController._emailDao;
|
||||
origKeychain = appController._keychain;
|
||||
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
appController._auth = authStub = sinon.createStubInstance(Auth);
|
||||
|
||||
startAppStub = sinon.stub(appController, 'start');
|
||||
@ -48,6 +53,7 @@ define(function(require) {
|
||||
|
||||
// restore the app controller module
|
||||
appController._emailDao = origEmailDao;
|
||||
appController._keychain = origKeychain;
|
||||
appController.start.restore && appController.start.restore();
|
||||
appController.checkForUpdate.restore && appController.checkForUpdate.restore();
|
||||
appController.init.restore && appController.init.restore();
|
||||
@ -128,12 +134,42 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should forward to privatekey download login', function(done) {
|
||||
startAppStub.yields();
|
||||
authStub.getEmailAddress.yields(null, emailAddress);
|
||||
initStub.yields(null, {
|
||||
publicKey: 'b'
|
||||
});
|
||||
keychainMock.requestPrivateKeyDownload.yields(null, {});
|
||||
|
||||
angular.module('logintest', []);
|
||||
mocks.module('logintest');
|
||||
mocks.inject(function($controller, $rootScope, $location) {
|
||||
location = $location;
|
||||
sinon.stub(location, 'path', function(path) {
|
||||
expect(path).to.equal('/login-privatekey-download');
|
||||
expect(startAppStub.calledOnce).to.be.true;
|
||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||
expect(authStub.getEmailAddress.calledOnce).to.be.true;
|
||||
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
ctrl = $controller(LoginCtrl, {
|
||||
$location: location,
|
||||
$scope: scope
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should forward to new device login', function(done) {
|
||||
startAppStub.yields();
|
||||
authStub.getEmailAddress.yields(null, emailAddress);
|
||||
initStub.yields(null, {
|
||||
publicKey: 'b'
|
||||
});
|
||||
keychainMock.requestPrivateKeyDownload.yields();
|
||||
|
||||
angular.module('logintest', []);
|
||||
mocks.module('logintest');
|
||||
@ -144,6 +180,7 @@ define(function(require) {
|
||||
expect(startAppStub.calledOnce).to.be.true;
|
||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||
expect(authStub.getEmailAddress.calledOnce).to.be.true;
|
||||
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
235
test/unit/login-privatekey-download-ctrl-test.js
Normal file
235
test/unit/login-privatekey-download-ctrl-test.js
Normal file
@ -0,0 +1,235 @@
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var expect = chai.expect,
|
||||
angular = require('angular'),
|
||||
mocks = require('angularMocks'),
|
||||
LoginPrivateKeyDownloadCtrl = require('js/controller/login-privatekey-download'),
|
||||
EmailDAO = require('js/dao/email-dao'),
|
||||
appController = require('js/app-controller'),
|
||||
KeychainDAO = require('js/dao/keychain-dao');
|
||||
|
||||
describe('Login Private Key Download Controller unit test', function() {
|
||||
var scope, location, ctrl,
|
||||
origEmailDao, emailDaoMock,
|
||||
origKeychain, keychainMock,
|
||||
emailAddress = 'fred@foo.com';
|
||||
|
||||
beforeEach(function(done) {
|
||||
// remember original module to restore later, then replace it
|
||||
origEmailDao = appController._emailDao;
|
||||
origKeychain = appController._keychain;
|
||||
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
emailDaoMock._account = {
|
||||
emailAddress: emailAddress
|
||||
};
|
||||
|
||||
angular.module('login-privatekey-download-test', []);
|
||||
mocks.module('login-privatekey-download-test');
|
||||
mocks.inject(function($controller, $rootScope) {
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
ctrl = $controller(LoginPrivateKeyDownloadCtrl, {
|
||||
$location: location,
|
||||
$scope: scope
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
// restore the app controller module
|
||||
appController._emailDao = origEmailDao;
|
||||
appController._keychain = origKeychain;
|
||||
});
|
||||
|
||||
describe('initialization', function() {
|
||||
it('should work', function() {
|
||||
expect(scope.step).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyRecoveryToken', function() {
|
||||
var testKeypair = {
|
||||
publicKey: {
|
||||
_id: 'id'
|
||||
}
|
||||
};
|
||||
|
||||
it('should fail for empty recovery token', function(done) {
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.recoveryToken = undefined;
|
||||
scope.verifyRecoveryToken();
|
||||
});
|
||||
|
||||
it('should fail in keychain.getUserKeyPair', function(done) {
|
||||
keychainMock.getUserKeyPair.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.recoveryToken = 'token';
|
||||
scope.verifyRecoveryToken();
|
||||
});
|
||||
|
||||
it('should fail in keychain.downloadPrivateKey', function(done) {
|
||||
keychainMock.getUserKeyPair.yields(null, testKeypair);
|
||||
keychainMock.downloadPrivateKey.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(keychainMock.downloadPrivateKey.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.recoveryToken = 'token';
|
||||
scope.verifyRecoveryToken();
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
keychainMock.getUserKeyPair.yields(null, testKeypair);
|
||||
keychainMock.downloadPrivateKey.yields(null, 'encryptedPrivateKey');
|
||||
|
||||
scope.recoveryToken = 'token';
|
||||
scope.verifyRecoveryToken(function() {
|
||||
expect(scope.encryptedPrivateKey).to.equal('encryptedPrivateKey');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('decryptAndStorePrivateKeyLocally', function() {
|
||||
beforeEach(function() {
|
||||
scope.code0 = '0';
|
||||
scope.code1 = '1';
|
||||
scope.code2 = '2';
|
||||
scope.code3 = '3';
|
||||
scope.code4 = '4';
|
||||
scope.code5 = '5';
|
||||
|
||||
scope.encryptedPrivateKey = {
|
||||
encryptedPrivateKey: 'encryptedPrivateKey'
|
||||
};
|
||||
scope.cachedKeypair = {
|
||||
publicKey: {
|
||||
_id: 'keyId'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it('should fail on empty code', function(done) {
|
||||
scope.code0 = '';
|
||||
scope.code1 = '';
|
||||
scope.code2 = '';
|
||||
scope.code3 = '';
|
||||
scope.code4 = '';
|
||||
scope.code5 = '';
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
|
||||
it('should fail on decryptAndStorePrivateKeyLocally', function(done) {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
|
||||
it('should goto /login-existing on emailDao.unlock fail', function(done) {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
|
||||
encryptedKey: 'keyArmored'
|
||||
});
|
||||
emailDaoMock.unlock.yields(42);
|
||||
|
||||
scope.goTo = function(location) {
|
||||
expect(location).to.equal('/login-existing');
|
||||
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
|
||||
it('should goto /desktop on emailDao.unlock success', function(done) {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
|
||||
encryptedKey: 'keyArmored'
|
||||
});
|
||||
emailDaoMock.unlock.yields();
|
||||
|
||||
scope.goTo = function(location) {
|
||||
expect(location).to.equal('/desktop');
|
||||
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
});
|
||||
|
||||
describe('goForward', function() {
|
||||
it('should work in step 1', function() {
|
||||
var verifyRecoveryTokenStub = sinon.stub(scope, 'verifyRecoveryToken');
|
||||
verifyRecoveryTokenStub.yields();
|
||||
scope.step = 1;
|
||||
|
||||
scope.goForward();
|
||||
|
||||
expect(verifyRecoveryTokenStub.calledOnce).to.be.true;
|
||||
expect(scope.step).to.equal(2);
|
||||
verifyRecoveryTokenStub.restore();
|
||||
});
|
||||
it('should work in step 2', function() {
|
||||
var decryptAndStorePrivateKeyLocallyStub = sinon.stub(scope, 'decryptAndStorePrivateKeyLocally');
|
||||
decryptAndStorePrivateKeyLocallyStub.returns();
|
||||
scope.step = 2;
|
||||
|
||||
scope.goForward();
|
||||
|
||||
expect(decryptAndStorePrivateKeyLocallyStub.calledOnce).to.be.true;
|
||||
decryptAndStorePrivateKeyLocallyStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('goTo', function() {
|
||||
it('should work', function(done) {
|
||||
mocks.inject(function($controller, $rootScope, $location) {
|
||||
location = $location;
|
||||
sinon.stub(location, 'path', function(path) {
|
||||
expect(path).to.equal('/desktop');
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
ctrl = $controller(LoginPrivateKeyDownloadCtrl, {
|
||||
$location: location,
|
||||
$scope: scope
|
||||
});
|
||||
});
|
||||
|
||||
scope.goTo('/desktop');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,16 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
require(['../../src/require-config'], function() {
|
||||
|
||||
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';
|
||||
|
||||
startTests();
|
||||
@ -18,17 +24,43 @@ require(['../../src/require-config'], function() {
|
||||
});
|
||||
|
||||
function startTests() {
|
||||
mocha.setup('bdd');
|
||||
|
||||
require(
|
||||
[
|
||||
'test/unit/forge-test',
|
||||
'test/unit/aes-test',
|
||||
'test/unit/rsa-test',
|
||||
'test/unit/keychain-dao-test',
|
||||
'test/unit/oauth-test',
|
||||
'test/unit/auth-test',
|
||||
'test/unit/email-dao-test',
|
||||
'test/unit/app-controller-test',
|
||||
'test/unit/pgp-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() {
|
||||
//Tests loaded, run tests
|
||||
QUnit.start();
|
||||
mocha.run();
|
||||
}
|
||||
);
|
||||
}
|
@ -13,7 +13,7 @@ define(function(require) {
|
||||
keySize = 512,
|
||||
keyId = 'F6F60E9B42CDFF4C',
|
||||
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' +
|
||||
'\r\n' +
|
||||
'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\r\n' +
|
||||
@ -24,7 +24,7 @@ define(function(require) {
|
||||
'=6XMW\r\n' +
|
||||
'-----END PGP PUBLIC KEY BLOCK-----\r\n\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' +
|
||||
'\r\n' +
|
||||
'xcBeBFJYTLwBAf9jGbQlDgGL8ixYw6dzgTBp9xL/BcI88j2yBdCVMPi+8tl0\r\n' +
|
||||
@ -92,7 +92,7 @@ define(function(require) {
|
||||
publicKeyArmored: pubkey
|
||||
}, function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(err.errMsg).to.equal('Incorrect passphrase!');
|
||||
expect(err.message).to.equal('Incorrect passphrase!');
|
||||
|
||||
pgp.exportKeys(function(err, keys) {
|
||||
expect(err).to.exist;
|
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() {
|
||||
it('should fail', function() {
|
||||
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;
|
||||
|
||||
beforeEach(function() {
|
||||
appController._crypto = cryptoMock = sinon.createStubInstance(PGP);
|
||||
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
|
||||
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||
|
||||
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
|
Loading…
Reference in New Issue
Block a user