1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-26 10:52:17 -05:00

[WO-184] implement support for importing an existing pgp key

This commit is contained in:
Tankred Hase 2014-03-05 20:14:23 +01:00
parent 09999f991a
commit 1b94e7b5ce
11 changed files with 136 additions and 31 deletions

View File

@ -220,6 +220,16 @@ module.exports = function(grunt) {
cwd: 'dist/', cwd: 'dist/',
src: ['**/*'], src: ['**/*'],
dest: 'release/' dest: 'release/'
},
nodeWebkit: {
options: {
mode: 'zip',
archive: 'release/whiteout-mail_' + zipName + '.nw'
},
expand: true,
cwd: 'dist/',
src: ['**/*'],
dest: '/'
} }
}, },

View File

@ -25,6 +25,10 @@ define(function(require) {
// scope functions // scope functions
// //
$scope.importKey = function() {
$location.path('/login-new-device');
};
/* /*
* Taken from jQuery validate.password plug-in 1.0 * Taken from jQuery validate.password plug-in 1.0
* http://bassistance.de/jquery-plugins/jquery-plugin-validate.password/ * http://bassistance.de/jquery-plugins/jquery-plugin-validate.password/

View File

@ -11,7 +11,8 @@ define(function(require) {
// attach global error handler // attach global error handler
errorUtil.attachHandler($scope); errorUtil.attachHandler($scope);
var emailDao = appController._emailDao; var emailDao = appController._emailDao,
pgp = appController._crypto;
$scope.incorrect = false; $scope.incorrect = false;
@ -27,18 +28,32 @@ define(function(require) {
function unlockCrypto() { function unlockCrypto() {
var userId = emailDao._account.emailAddress; var userId = emailDao._account.emailAddress;
// check if user already has a public key on the key server
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) { emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) { if (err) {
$scope.onError(err); $scope.onError(err);
return; return;
} }
keypair = keypair || {};
// set parsed private key
keypair.privateKey = { keypair.privateKey = {
_id: keypair.publicKey._id, _id: pgp.getKeyId($scope.key.privateKeyArmored),
userId: userId, userId: userId,
encryptedKey: $scope.key.privateKeyArmored 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({ emailDao.unlock({
keypair: keypair, keypair: keypair,
passphrase: $scope.passphrase passphrase: $scope.passphrase

View File

@ -68,23 +68,33 @@ define(function(require) {
return fingerprint(this._publicKey); 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 * Show a user's key id
*/ */
PGP.prototype.getKeyId = function() { PGP.prototype.getKeyId = function(keyArmored) {
var pubKeyId, privKeyId; 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) { if (!this._privateKey || !this._publicKey) {
return; throw new Error('Cannot read key IDs... keys not set!');
} }
pubKeyId = this._publicKey.getKeyPacket().getKeyId().toHex().toUpperCase(); pubKeyId = this._publicKey.getKeyPacket().getKeyId().toHex().toUpperCase();
privKeyId = this._privateKey.getKeyPacket().getKeyId().toHex().toUpperCase(); privKeyId = this._privateKey.getKeyPacket().getKeyId().toHex().toUpperCase();
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) { if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
console.error('Key IDs do not match!'); throw new Error('Key IDs do not match!');
return;
} }
return pubKeyId; return pubKeyId;

View File

@ -126,20 +126,7 @@ define(function(require) {
if (options.keypair) { if (options.keypair) {
// import existing key pair into crypto module // import existing key pair into crypto module
self._crypto.importKeys({ handleExistingKeypair(options.keypair);
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();
});
return; return;
} }
@ -157,6 +144,44 @@ define(function(require) {
handleGenerated(generatedKeypair); 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) { function handleGenerated(generatedKeypair) {
// import the new key pair into crypto module // import the new key pair into crypto module
self._crypto.importKeys({ self._crypto.importKeys({

View File

@ -27,7 +27,7 @@
p { p {
line-height: 150%; line-height: 150%;
b { b, a {
color: $color-blue; color: $color-blue;
} }
} }

View File

@ -6,7 +6,7 @@
<div class="content" ng-switch on="state.ui"> <div class="content" ng-switch on="state.ui">
<div ng-switch-when="1"> <div ng-switch-when="1">
<p><b>Set passphrase.</b> The passphrase protects your private key. If you forget your passphrase you will not be able to recover your messages.</p> <p><b>Generate PGP key.</b> 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.</p><p>Alternatively you can also <a href="#" ng-click="$event.preventDefault(); importKey()">import an existing PGP key</a>.</p>
<form> <form>
<div> <div>
<label class="input-error-message" ng-class="{'passphrase-label-ok': passphraseRating >= 2}">{{passphraseMsg}}</label><br> <label class="input-error-message" ng-class="{'passphrase-label-ok': passphraseRating >= 2}">{{passphraseMsg}}</label><br>

View File

@ -4,7 +4,7 @@
</div><!--/logo--> </div><!--/logo-->
<div class="content"> <div class="content">
<p><b>Import keyfile.</b> You are already registered on another device. To access your emails on this device, please import your key file.</p> <p><b>Import keyfile.</b> To access your emails on this device, please import your existing key file.</p>
<form> <form>
<div> <div>

View File

@ -354,11 +354,16 @@ define(function(require) {
_pgpbuilder: {} _pgpbuilder: {}
}; };
pgpStub.getUserId.returns('Whiteout User <' + emailAddress + '>');
pgpStub.importKeys.withArgs({ pgpStub.importKeys.withArgs({
passphrase: passphrase, passphrase: passphrase,
privateKeyArmored: mockKeyPair.privateKey.encryptedKey, privateKeyArmored: mockKeyPair.privateKey.encryptedKey,
publicKeyArmored: mockKeyPair.publicKey.publicKey publicKeyArmored: mockKeyPair.publicKey.publicKey
}).yields(); }).yields();
pgpStub._privateKey = {
foo: 'bar'
};
dao.unlock({ dao.unlock({
passphrase: passphrase, passphrase: passphrase,
@ -367,6 +372,7 @@ define(function(require) {
expect(err).to.not.exist; expect(err).to.not.exist;
expect(pgpStub.importKeys.calledOnce).to.be.true; expect(pgpStub.importKeys.calledOnce).to.be.true;
expect(dao._pgpbuilder._privateKey).to.equal(pgpStub._privateKey);
done(); done();
}); });

View File

@ -4,13 +4,14 @@ define(function(require) {
var expect = chai.expect, var expect = chai.expect,
angular = require('angular'), angular = require('angular'),
mocks = require('angularMocks'), mocks = require('angularMocks'),
PGP = require('js/crypto/pgp'),
LoginNewDeviceCtrl = require('js/controller/login-new-device'), LoginNewDeviceCtrl = require('js/controller/login-new-device'),
KeychainDAO = require('js/dao/keychain-dao'), KeychainDAO = require('js/dao/keychain-dao'),
EmailDAO = require('js/dao/email-dao'), EmailDAO = require('js/dao/email-dao'),
appController = require('js/app-controller'); appController = require('js/app-controller');
describe('Login (new device) Controller unit test', function() { describe('Login (new device) Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, var scope, ctrl, origEmailDao, emailDaoMock, pgpMock,
emailAddress = 'fred@foo.com', emailAddress = 'fred@foo.com',
passphrase = 'asd', passphrase = 'asd',
keyId, keyId,
@ -24,8 +25,8 @@ define(function(require) {
appController._emailDao = emailDaoMock; appController._emailDao = emailDaoMock;
keyId = '9FEB47936E712926'; keyId = '9FEB47936E712926';
keychainMock = sinon.createStubInstance(KeychainDAO); emailDaoMock._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
emailDaoMock._keychain = keychainMock; appController._crypto = pgpMock = sinon.createStubInstance(PGP);
emailDaoMock._account = { emailDaoMock._account = {
emailAddress: emailAddress, emailAddress: emailAddress,
@ -57,11 +58,14 @@ define(function(require) {
}); });
describe('confirm passphrase', function() { 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.passphrase = passphrase;
scope.key = { scope.key = {
privateKeyArmored: 'b' privateKeyArmored: 'b'
}; };
pgpMock.getKeyId.returns(keyId);
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, { keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId, _id: keyId,
publicKey: 'a' publicKey: 'a'
@ -75,6 +79,25 @@ define(function(require) {
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true; 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() { it('should not do anything without passphrase', function() {
scope.state.passphrase = ''; scope.state.passphrase = '';

View File

@ -139,10 +139,22 @@ define(function(require) {
}); });
describe('Get KeyId', function() { describe('Get KeyId', function() {
it('should work', function() { it('should work without param', function() {
var keyId = pgp.getKeyId(); var keyId = pgp.getKeyId();
expect(keyId).to.equal('F6F60E9B42CDFF4C'); 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() { describe('Get Fingerprint', function() {