Skip to content

Commit

Permalink
Support caching of unlocked keys
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Oberndörfer committed Feb 25, 2013
1 parent e423bd5 commit 9ffa1f1
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 74 deletions.
48 changes: 29 additions & 19 deletions chrome/lib/pgpViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,30 +332,35 @@ define(function(require, exports, module) {
message: 'Could not read this encrypted message'
}
}
result.keymat = null;
result.privkey = null;
result.sesskey = null;
result.userid = '';
result.keyid = '';
result.primkeyid = '';
var primarykey;
// Find the private (sub)key for the session key of the message
for (var i = 0; i < result.message.sessionKeys.length; i++) {
outer: for (var i = 0; i < result.message.sessionKeys.length; i++) {
for (var j = 0; j < openpgp.keyring.privateKeys.length; j++) {
if (openpgp.keyring.privateKeys[j].obj.privateKeyPacket.publicKey.getKeyId() == result.message.sessionKeys[i].keyId.bytes) {
result.keymat = { key: openpgp.keyring.privateKeys[j].obj, keymaterial: openpgp.keyring.privateKeys[j].obj.privateKeyPacket};
result.privkey = { keymaterial: openpgp.keyring.privateKeys[j].obj.privateKeyPacket };
result.sesskey = result.message.sessionKeys[i];
break;
primarykey = openpgp.keyring.privateKeys[j].obj;
break outer;
}
for (var k = 0; k < openpgp.keyring.privateKeys[j].obj.subKeys.length; k++) {
if (openpgp.keyring.privateKeys[j].obj.subKeys[k].publicKey.getKeyId() == result.message.sessionKeys[i].keyId.bytes) {
result.keymat = { key: openpgp.keyring.privateKeys[j].obj, keymaterial: openpgp.keyring.privateKeys[j].obj.subKeys[k]};
result.privkey = { keymaterial: openpgp.keyring.privateKeys[j].obj.subKeys[k] };
result.sesskey = result.message.sessionKeys[i];
break;
primarykey = openpgp.keyring.privateKeys[j].obj;
break outer;
}
}
}
}
if (result.keymat != null) {
result.userid = decode_utf8(result.keymat.key.userIds[0].text);
result.keyid = util.hexstrdump(result.keymat.key.getKeyId()).toUpperCase();
if (result.privkey != null) {
result.userid = decode_utf8(primarykey.userIds[0].text);
result.primkeyid = util.hexstrdump(primarykey.getKeyId()).toUpperCase();
result.keyid = util.hexstrdump(result.privkey.keymaterial.publicKey.getKeyId()).toUpperCase();
} else {
// unknown private key
result.keyid = util.hexstrdump(result.message.sessionKeys[0].keyId.bytes).toUpperCase();
Expand All @@ -372,18 +377,22 @@ define(function(require, exports, module) {
return result;
}

function decryptMessage(message, passwd, callback) {
function unlockKey(privkey, passwd) {
try {
if (message.keymat.keymaterial.decryptSecretMPIs(passwd)) {
var decryptedMsg = message.message.decrypt(message.keymat, message.sesskey);
decryptedMsg = decode_utf8(decryptedMsg);
callback(null, decryptedMsg);
} else {
callback({
type: 'wrong-password',
message: 'Wrong password'
});
return privkey.keymaterial.decryptSecretMPIs(passwd);
} catch (e) {
throw {
type: 'error',
message: 'Could not unlock the private key'
}
}
}

function decryptMessage(message, callback) {
try {
var decryptedMsg = message.message.decrypt(message.privkey, message.sesskey);
decryptedMsg = decode_utf8(decryptedMsg);
callback(null, decryptedMsg);
} catch (e) {
callback({
type: 'error',
Expand Down Expand Up @@ -442,6 +451,7 @@ define(function(require, exports, module) {
exports.generateKey = generateKey;
exports.readMessage = readMessage;
exports.decryptMessage = decryptMessage;
exports.unlockKey = unlockKey;
exports.encryptMessage = encryptMessage;
exports.getWatchList = getWatchList;
exports.setWatchList = setWatchList;
Expand Down
81 changes: 48 additions & 33 deletions common/lib/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ define(function (require, exports, module) {
break;
case 'pwd-dialog-init':
// pass over keyid and userid to dialog
pwdPort.postMessage({event: 'message-userid', userid: dMessageBuffer[id].userid, keyid: dMessageBuffer[id].keyid, cache: prefs.data.security.password_cache});
pwdPort.postMessage({event: 'message-userid', userid: dMessageBuffer[id].userid, keyid: dMessageBuffer[id].primkeyid, cache: prefs.data.security.password_cache});
break;
case 'dframe-display-popup':
// decrypt popup potentially needs pwd dialog
Expand All @@ -153,61 +153,63 @@ define(function (require, exports, module) {
case 'dframe-armored-message':
try {
var message = model.readMessage(msg.data);
// add message in buffer
dMessageBuffer[id] = message;
// password in cache?
var pwd = pwdCache.get(message.keyid);
if (!pwd) {
// password or unlocked key in cache?
var cache = pwdCache.get(message.primkeyid, message.keyid);
if (!cache) {
// add message in buffer
dMessageBuffer[id] = message;
// open password dialog
if (prefs.data.security.display_decrypted == mvelo.DISPLAY_INLINE) {
mvelo.windows.openPopup('common/ui/modal/pwdDialog.html?id=' + id, {width: 462, height: 377, modal: true});
} else if (prefs.data.security.display_decrypted == mvelo.DISPLAY_POPUP) {
dDialogPorts[id].postMessage({event: 'show-pwd-dialog'});
}
} else {
model.decryptMessage(dMessageBuffer[id], pwd, function(err, message) {
if (err) {
// display error message in decrypt dialog
dDialogPorts[id].postMessage({event: 'error-message', error: err.message});
} else {
// decrypted correctly
message = mvelo.util.parseHTML(message); // sanitize message
dDialogPorts[id].postMessage({event: 'decrypted-message', message: message});
if (!cache.key) {
// unlock key
var unlocked = model.unlockKey(message.privkey, cache.password);
if (!unlocked) {
throw {
type: 'error',
message: 'Password caching does not support different passphrases for primary key and subkeys'
}
}
});
// set unlocked key in cache
pwdCache.set(message);
} else {
// take unlocked key from cache
message.privkey = cache.key;
}
decryptMessage(message, id);
}
} catch (e) {
// display error message in decrypt dialog
dDialogPorts[id].postMessage({event: 'error-message', error: e.message});
}
break;
case 'pwd-dialog-ok':
model.decryptMessage(dMessageBuffer[id], msg.password, function(err, message) {
if (err) {
if (err.type === 'wrong-password') {
pwdPort.postMessage({event: 'wrong-password'});
} else {
pwdPort.postMessage({event: 'correct-password'});
// display error message in decrypt dialog
dDialogPorts[id].postMessage({event: 'error-message', error: err.message});
}
} else {
// decrypted correctly
var message = dMessageBuffer[id];
try {
if (model.unlockKey(message.privkey, msg.password)) {
// password correct
if (msg.cache != prefs.data.security.password_cache) {
// update pwd cache status
prefs.update({security: {password_cache: msg.cache}});
}
if (msg.cache) {
// set password in cache
pwdCache.set(dMessageBuffer[id].keyid, msg.password);
// set unlocked key and password in cache
pwdCache.set(message, msg.password);
}
// sanitize message
message = mvelo.util.parseHTML(message);
// close pwd dialog
pwdPort.postMessage({event: 'correct-password'});
dDialogPorts[id].postMessage({event: 'decrypted-message', message: message});
decryptMessage(message, id);
} else {
// password wrong
pwdPort.postMessage({event: 'wrong-password'});
}
});
} catch (e) {
// display error message in decrypt dialog
dDialogPorts[id].postMessage({event: 'error-message', error: e.message});
}
break;
case 'encrypt-dialog-init':
// send content
Expand Down Expand Up @@ -335,6 +337,19 @@ define(function (require, exports, module) {
}
}

function decryptMessage(message, id) {
model.decryptMessage(message, function(err, msgText) {
if (err) {
// display error message in decrypt dialog
dDialogPorts[id].postMessage({event: 'error-message', error: err.message});
} else {
// decrypted correctly
msgText = mvelo.util.parseHTML(msgText); // sanitize message
dDialogPorts[id].postMessage({event: 'decrypted-message', message: msgText});
}
});
}

function removePortByRef(port) {
function deletePort(portHash, port) {
for (var p in portHash) {
Expand Down
53 changes: 34 additions & 19 deletions common/lib/pwdCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ define(function (require, exports, module) {

var prefs = require('common/lib/prefs');

// password cache
// password and key cache
var cache;
// caching active
var active;
// timeout in minutes
var timeout;
Expand Down Expand Up @@ -57,33 +58,47 @@ define(function (require, exports, module) {
}

/**
* Get password from cache
* @param {String} keyid
* @return {String} password
* Get password and unlocked key from cache
* @param {String} primkeyid primary key id
* @param {String} keyid requested unlocked key
* @return {Object} password of key, if available unlocked key for keyid
*/
function get(keyid) {
function get(primkeyid, keyid) {
if (active) {
if (cache[keyid]) {
return cache[keyid].password;
if (cache[primkeyid]) {
return {
password: cache[primkeyid].password,
key: cache[primkeyid][keyid]
}
}
}
}

/**
* Set password in cache, start timeout
* @param {String} keyid
* @param {String} password
* Set key and password in cache, start timeout
* @param {Object} message
* primkeyid: key ID of the primary key
* keyid: key ID of key that should be cached
* privkey: private key packet of keyid, expected unlocked
* @param {String} pwd password, optional
*/
function set(keyid, password) {
if (cache[keyid]) {
// clear timer if entry already exists
mvelo.util.clearTimeout(cache[keyid].timer);
function set(message, pwd) {
// primary key id is main key of cache
var entry = cache[message.primkeyid];
if (entry) {
// set unlocked private key for this keyid
if (!entry[message.keyid]) {
entry[message.keyid] = message.privkey;
}
} else {
var newEntry = cache[message.primkeyid] = {};
newEntry.password = pwd;
newEntry[message.keyid] = message.privkey;
// clear after timeout
newEntry.timer = mvelo.util.setTimeout(function() {
delete cache[message.primkeyid];
}, timeout * 60 * 1000);
}
cache[keyid] = {password: password};
// clear after timeout
cache[keyid].timer = mvelo.util.setTimeout(function() {
delete cache[keyid];
}, timeout * 60 * 1000);
}

exports.get = get;
Expand Down
10 changes: 10 additions & 0 deletions common/ui/inline/dialogs/decryptInline.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,13 @@ html, body {
#errorwell {
margin-left: 0;
}

.spinner {
background-image: url('chrome-extension://__MSG_@@extension_id__/common/dep/kendoui/styles/Default/loading-image.gif');
background-repeat: no-repeat;
background-position: center;
}

.spinner-large {
background-position: center 160px;
}
13 changes: 12 additions & 1 deletion common/ui/inline/dialogs/decryptInline.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
// shares ID with DecryptFrame
var id;
var watermark;
var spinnerTimer;

function init() {
//console.log('decryptDialog init');
var qs = jQuery.parseQuerystring();
id = 'dDialog-' + qs['id'];
// open port to background page
Expand All @@ -37,6 +37,15 @@
});
$(window).on('resize', resizeFont);
addErrorView();
// show spinner
spinnerTimer = setTimeout(showSpinner, 600);
}

function showSpinner() {
$('body').addClass('spinner');
if ($('body').height() + 2 > mvelo.LARGE_FRAME) {
$('body').addClass('spinner-large');
}
}

function addWrapper() {
Expand Down Expand Up @@ -90,6 +99,8 @@
}

function showErrorMsg(msg) {
$('body').removeClass('spinner');
clearTimeout(spinnerTimer);
$('#errorbox').show();
$('#errorwell').showAlert('Error', msg, 'error')
.find('.alert').prepend($('<button/>', {type: 'button', class: 'close', html: '&times;'}))
Expand Down
10 changes: 10 additions & 0 deletions common/ui/modal/decryptPopup.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
* Copyright (C) 2012 Thomas Oberndörfer
*/

html, body {
height: 100%;
}

.busy {
cursor: wait !important;
}
Expand Down Expand Up @@ -87,6 +91,12 @@
margin-left: 0;
}

.spinner {
background-image: url('chrome-extension://__MSG_@@extension_id__/common/dep/kendoui/styles/Default/loading-image.gif');
background-repeat: no-repeat;
background-position: center;
}

#secureCode {
float: right;
padding: 4px 14px;
Expand Down
5 changes: 3 additions & 2 deletions common/ui/modal/decryptPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
var pwd, sandbox;

function init() {
//console.log('decryptDialog init');
var qs = jQuery.parseQuerystring();
id = qs['id'];
name = 'dDialog-' + id
Expand All @@ -39,6 +38,7 @@
$(window).unload(onClose);
$('#closeBtn').click(onClose);
$('#copyBtn').click(onCopy);
$('body').addClass('spinner');
}

function onClose() {
Expand Down Expand Up @@ -110,7 +110,8 @@
}

function messageListener(msg) {
//console.log('decrypt dialog messageListener: ', JSON.stringify(msg));
// remove spinner for all events
$('body').removeClass('spinner');
switch (msg.event) {
case 'decrypted-message':
showMessageArea();
Expand Down
Loading

0 comments on commit 9ffa1f1

Please sign in to comment.