diff --git a/Gruntfile.js b/Gruntfile.js index fe8a923..8b6c7a4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -220,6 +220,16 @@ module.exports = function(grunt) { cwd: 'dist/', src: ['**/*'], dest: 'release/' + }, + nodeWebkit: { + options: { + mode: 'zip', + archive: 'release/whiteout-mail_' + zipName + '.nw' + }, + expand: true, + cwd: 'dist/', + src: ['**/*'], + dest: '/' } }, diff --git a/src/js/controller/login-initial.js b/src/js/controller/login-initial.js index 458043c..3c9c25c 100644 --- a/src/js/controller/login-initial.js +++ b/src/js/controller/login-initial.js @@ -25,6 +25,10 @@ define(function(require) { // scope functions // + $scope.importKey = function() { + $location.path('/login-new-device'); + }; + /* * Taken from jQuery validate.password plug-in 1.0 * http://bassistance.de/jquery-plugins/jquery-plugin-validate.password/ diff --git a/src/js/controller/login-new-device.js b/src/js/controller/login-new-device.js index fbc2d1c..b1b59cc 100644 --- a/src/js/controller/login-new-device.js +++ b/src/js/controller/login-new-device.js @@ -11,7 +11,8 @@ define(function(require) { // attach global error handler errorUtil.attachHandler($scope); - var emailDao = appController._emailDao; + var emailDao = appController._emailDao, + pgp = appController._crypto; $scope.incorrect = false; @@ -27,18 +28,32 @@ define(function(require) { function unlockCrypto() { var userId = emailDao._account.emailAddress; + // check if user already has a public key on the key server emailDao._keychain.getUserKeyPair(userId, function(err, keypair) { if (err) { $scope.onError(err); return; } + keypair = keypair || {}; + + // set parsed private key keypair.privateKey = { - _id: keypair.publicKey._id, + _id: pgp.getKeyId($scope.key.privateKeyArmored), userId: userId, encryptedKey: $scope.key.privateKeyArmored }; + if (!keypair.publicKey) { + // there is no public key on the key server yet... use parsed + keypair.publicKey = { + _id: pgp.getKeyId($scope.key.publicKeyArmored), + userId: userId, + publicKey: $scope.key.publicKeyArmored + }; + } + + // import and validate keypair emailDao.unlock({ keypair: keypair, passphrase: $scope.passphrase diff --git a/src/js/crypto/pgp.js b/src/js/crypto/pgp.js index 9493341..023672c 100644 --- a/src/js/crypto/pgp.js +++ b/src/js/crypto/pgp.js @@ -68,23 +68,33 @@ define(function(require) { return fingerprint(this._publicKey); }; + PGP.prototype.getUserId = function(keyArmored) { + var key = openpgp.key.readArmored(keyArmored).keys[0]; + return key.getUserIds()[0]; + }; + /** * Show a user's key id */ - PGP.prototype.getKeyId = function() { - var pubKeyId, privKeyId; + PGP.prototype.getKeyId = function(keyArmored) { + var key, pubKeyId, privKeyId; - // check keys + // process armored key input + if (keyArmored) { + key = openpgp.key.readArmored(keyArmored).keys[0]; + return key.getKeyPacket().getKeyId().toHex().toUpperCase(); + } + + // check already imported keys if (!this._privateKey || !this._publicKey) { - return; + throw new Error('Cannot read key IDs... keys not set!'); } pubKeyId = this._publicKey.getKeyPacket().getKeyId().toHex().toUpperCase(); privKeyId = this._privateKey.getKeyPacket().getKeyId().toHex().toUpperCase(); if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) { - console.error('Key IDs do not match!'); - return; + throw new Error('Key IDs do not match!'); } return pubKeyId; diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index acb8705..91c21de 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -126,20 +126,7 @@ define(function(require) { if (options.keypair) { // import existing key pair into crypto module - self._crypto.importKeys({ - passphrase: options.passphrase, - privateKeyArmored: options.keypair.privateKey.encryptedKey, - publicKeyArmored: options.keypair.publicKey.publicKey - }, function(err) { - if (err) { - callback(err); - return; - } - - // set decrypted privateKey to pgpMailer - self._pgpbuilder._privateKey = self._crypto._privateKey; - callback(); - }); + handleExistingKeypair(options.keypair); return; } @@ -157,6 +144,44 @@ define(function(require) { handleGenerated(generatedKeypair); }); + function handleExistingKeypair(keypair) { + var pubUserID, privUserID; + + // check if key IDs match + if (!keypair.privateKey._id || keypair.privateKey._id !== keypair.publicKey._id) { + callback({ + errMsg: 'Key IDs dont match!' + }); + return; + } + + // check if the key's user ID matches the current account + pubUserID = self._crypto.getUserId(keypair.publicKey.publicKey); + privUserID = self._crypto.getUserId(keypair.privateKey.encryptedKey); + if (pubUserID.indexOf(self._account.emailAddress) === -1 || privUserID.indexOf(self._account.emailAddress) === -1) { + callback({ + errMsg: 'User IDs dont match!' + }); + return; + } + + // import existing key pair into crypto module + self._crypto.importKeys({ + passphrase: options.passphrase, + privateKeyArmored: keypair.privateKey.encryptedKey, + publicKeyArmored: keypair.publicKey.publicKey + }, function(err) { + if (err) { + callback(err); + return; + } + + // set decrypted privateKey to pgpMailer + self._pgpbuilder._privateKey = self._crypto._privateKey; + callback(); + }); + } + function handleGenerated(generatedKeypair) { // import the new key pair into crypto module self._crypto.importKeys({ @@ -1308,4 +1333,4 @@ define(function(require) { }; return EmailDAO; -}); \ No newline at end of file +}); diff --git a/src/sass/views/_login.scss b/src/sass/views/_login.scss index 23ab7ce..a674748 100644 --- a/src/sass/views/_login.scss +++ b/src/sass/views/_login.scss @@ -27,7 +27,7 @@ p { line-height: 150%; - b { + b, a { color: $color-blue; } } diff --git a/src/tpl/login-initial.html b/src/tpl/login-initial.html index e50b568..bf60e8b 100644 --- a/src/tpl/login-initial.html +++ b/src/tpl/login-initial.html @@ -6,7 +6,7 @@
-

Set passphrase. The passphrase protects your private key. If you forget your passphrase you will not be able to recover your messages.

+

Generate PGP key. Choose a passphrase to protect your new key. If you forget it at a later time you will not be able to read past messages.

Alternatively you can also import an existing PGP key.


diff --git a/src/tpl/login-new-device.html b/src/tpl/login-new-device.html index 196d76b..9a2995f 100644 --- a/src/tpl/login-new-device.html +++ b/src/tpl/login-new-device.html @@ -4,7 +4,7 @@
-

Import keyfile. You are already registered on another device. To access your emails on this device, please import your key file.

+

Import keyfile. To access your emails on this device, please import your existing key file.

diff --git a/test/new-unit/email-dao-test.js b/test/new-unit/email-dao-test.js index 9a3dc50..4f858dd 100644 --- a/test/new-unit/email-dao-test.js +++ b/test/new-unit/email-dao-test.js @@ -354,11 +354,16 @@ define(function(require) { _pgpbuilder: {} }; + pgpStub.getUserId.returns('Whiteout User <' + emailAddress + '>'); + pgpStub.importKeys.withArgs({ passphrase: passphrase, privateKeyArmored: mockKeyPair.privateKey.encryptedKey, publicKeyArmored: mockKeyPair.publicKey.publicKey }).yields(); + pgpStub._privateKey = { + foo: 'bar' + }; dao.unlock({ passphrase: passphrase, @@ -367,6 +372,7 @@ define(function(require) { expect(err).to.not.exist; expect(pgpStub.importKeys.calledOnce).to.be.true; + expect(dao._pgpbuilder._privateKey).to.equal(pgpStub._privateKey); done(); }); diff --git a/test/new-unit/login-new-device-ctrl-test.js b/test/new-unit/login-new-device-ctrl-test.js index a03afc0..52f2672 100644 --- a/test/new-unit/login-new-device-ctrl-test.js +++ b/test/new-unit/login-new-device-ctrl-test.js @@ -4,13 +4,14 @@ define(function(require) { var expect = chai.expect, angular = require('angular'), mocks = require('angularMocks'), + PGP = require('js/crypto/pgp'), LoginNewDeviceCtrl = require('js/controller/login-new-device'), KeychainDAO = require('js/dao/keychain-dao'), EmailDAO = require('js/dao/email-dao'), appController = require('js/app-controller'); describe('Login (new device) Controller unit test', function() { - var scope, ctrl, origEmailDao, emailDaoMock, + var scope, ctrl, origEmailDao, emailDaoMock, pgpMock, emailAddress = 'fred@foo.com', passphrase = 'asd', keyId, @@ -24,8 +25,8 @@ define(function(require) { appController._emailDao = emailDaoMock; keyId = '9FEB47936E712926'; - keychainMock = sinon.createStubInstance(KeychainDAO); - emailDaoMock._keychain = keychainMock; + emailDaoMock._keychain = keychainMock = sinon.createStubInstance(KeychainDAO); + appController._crypto = pgpMock = sinon.createStubInstance(PGP); emailDaoMock._account = { emailAddress: emailAddress, @@ -57,11 +58,14 @@ define(function(require) { }); describe('confirm passphrase', function() { - it('should unlock crypto', function() { + it('should unlock crypto with a public key on the server', function() { scope.passphrase = passphrase; scope.key = { privateKeyArmored: 'b' }; + + pgpMock.getKeyId.returns(keyId); + keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, { _id: keyId, publicKey: 'a' @@ -75,6 +79,25 @@ define(function(require) { expect(keychainMock.getUserKeyPair.calledOnce).to.be.true; }); + it('should unlock crypto with no key on the server', function() { + scope.passphrase = passphrase; + scope.key = { + privateKeyArmored: 'b', + publicKeyArmored: 'a' + }; + + pgpMock.getKeyId.returns(keyId); + + keychainMock.getUserKeyPair.withArgs(emailAddress).yields(); + emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields(); + keychainMock.putUserKeyPair.yields(); + + scope.confirmPassphrase(); + + expect(emailDaoMock.unlock.calledOnce).to.be.true; + expect(keychainMock.getUserKeyPair.calledOnce).to.be.true; + }); + it('should not do anything without passphrase', function() { scope.state.passphrase = ''; diff --git a/test/new-unit/pgp-test.js b/test/new-unit/pgp-test.js index a652891..f322303 100644 --- a/test/new-unit/pgp-test.js +++ b/test/new-unit/pgp-test.js @@ -139,10 +139,22 @@ define(function(require) { }); describe('Get KeyId', function() { - it('should work', function() { + it('should work without param', function() { var keyId = pgp.getKeyId(); expect(keyId).to.equal('F6F60E9B42CDFF4C'); }); + + it('should work with param', function() { + var keyId = pgp.getKeyId(pubkey); + expect(keyId).to.equal('F6F60E9B42CDFF4C'); + }); + }); + + describe('Get UserId', function() { + it('should work with param', function() { + var userId = pgp.getUserId(pubkey); + expect(userId).to.contain(user); + }); }); describe('Get Fingerprint', function() {