[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/',
src: ['**/*'],
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.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/

View File

@ -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

View File

@ -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;

View File

@ -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;
});
});

View File

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

View File

@ -6,7 +6,7 @@
<div class="content" ng-switch on="state.ui">
<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>
<div>
<label class="input-error-message" ng-class="{'passphrase-label-ok': passphraseRating >= 2}">{{passphraseMsg}}</label><br>

View File

@ -4,7 +4,7 @@
</div><!--/logo-->
<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>
<div>

View File

@ -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();
});

View File

@ -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 = '';

View File

@ -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() {