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:
parent
09999f991a
commit
1b94e7b5ce
10
Gruntfile.js
10
Gruntfile.js
@ -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: '/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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/
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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({
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
p {
|
p {
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
|
|
||||||
b {
|
b, a {
|
||||||
color: $color-blue;
|
color: $color-blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
@ -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 = '';
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
Loading…
Reference in New Issue
Block a user