direct session key manipulation and encryption/decryption

This commit is contained in:
Bart Butler 2015-04-01 00:13:31 -07:00 committed by Tankred Hase
parent 91d35ff99c
commit 60fb6ba18d
6 changed files with 282 additions and 58 deletions

View File

@ -85,10 +85,38 @@ Message.prototype.getSigningKeyIds = function() {
/**
* Decrypt the message
* @param {module:key~Key|String} privateKey private key with decrypted secret data or password
* @param {module:key~Key|String} privateKey private key with decrypted secret data, password or session key
* @param {String} sessionKeyAlgorithm if privateKey is a session key, this must be set to the session key algorithm (i.e. 'aes256').
* Do not set if privateKey is not a session key.
* @return {Array<module:message~Message>} new message with decrypted content
*/
Message.prototype.decrypt = function(privateKey) {
Message.prototype.decrypt = function(privateKey, sessionKeyAlgorithm) {
var keyObj;
if(sessionKeyAlgorithm) {
keyObj = {key: privateKey, algo: sessionKeyAlgorithm};
}
else {
keyObj = this.decryptSessionKey(privateKey);
}
if(keyObj) {
var symEncryptedPacketlist = this.packets.filterByTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected);
if (symEncryptedPacketlist.length !== 0) {
var symEncryptedPacket = symEncryptedPacketlist[0];
symEncryptedPacket.decrypt(keyObj.algo, keyObj.key);
var resultMsg = new Message(symEncryptedPacket.packets);
// remove packets after decryption
symEncryptedPacket.packets = new packet.List();
return resultMsg;
}
}
};
/**
* Decrypt session key
* @param {module:key~Key|String} privateKey private key with decrypted secret data or password
* @return {Object} object with sessionKey, algo
*/
Message.prototype.decryptSessionKey = function(privateKey) {
var keyPacket;
if(String.prototype.isPrototypeOf(privateKey) || typeof privateKey === 'string') {
var symEncryptedSessionKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
@ -129,15 +157,7 @@ Message.prototype.decrypt = function(privateKey) {
}
if (keyPacket) {
var symEncryptedPacketlist = this.packets.filterByTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected);
if (symEncryptedPacketlist.length !== 0) {
var symEncryptedPacket = symEncryptedPacketlist[0];
symEncryptedPacket.decrypt(keyPacket.sessionKeyAlgorithm, keyPacket.sessionKey);
var resultMsg = new Message(symEncryptedPacket.packets);
// remove packets after decryption
symEncryptedPacket.packets = new packet.List();
return resultMsg;
}
return {key: keyPacket.sessionKey, algo: keyPacket.sessionKeyAlgorithm};
}
};
@ -171,14 +191,6 @@ Message.prototype.getText = function() {
*/
Message.prototype.encrypt = function(keys, passwords) {
/** Convert to arrays if necessary */
if(keys && !Array.prototype.isPrototypeOf(keys)) {
keys = [keys]
}
if(passwords && !Array.prototype.isPrototypeOf(passwords)) {
passwords = [passwords]
}
/** Choose symAlgo */
var symAlgo;
if(keys) {
@ -192,32 +204,10 @@ Message.prototype.encrypt = function(keys, passwords) {
}
var sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo));
var packetlist = new packet.List();
if(keys) {
keys.forEach(function(key) {
var encryptionKeyPacket = key.getEncryptionKeyPacket();
if (encryptionKeyPacket) {
var pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey();
pkESKeyPacket.publicKeyId = encryptionKeyPacket.getKeyId();
pkESKeyPacket.publicKeyAlgorithm = encryptionKeyPacket.algorithm;
pkESKeyPacket.sessionKey = sessionKey;
pkESKeyPacket.sessionKeyAlgorithm = enums.read(enums.symmetric, symAlgo);
pkESKeyPacket.encrypt(encryptionKeyPacket);
packetlist.push(pkESKeyPacket);
} else {
throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex());
}
});
}
if(passwords) {
passwords.forEach(function(password) {
var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey();
symEncryptedSessionKeyPacket.sessionKey = sessionKey;
symEncryptedSessionKeyPacket.sessionKeyAlgorithm = enums.read(enums.symmetric, symAlgo);
symEncryptedSessionKeyPacket.encrypt(password);
packetlist.push(symEncryptedSessionKeyPacket);
});
}
var msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords);
var packetlist = msg.packets;
var symEncryptedPacket;
if (config.integrity_protect) {
symEncryptedPacket = new packet.SymEncryptedIntegrityProtected();
@ -229,6 +219,54 @@ Message.prototype.encrypt = function(keys, passwords) {
packetlist.push(symEncryptedPacket);
// remove packets after encryption
symEncryptedPacket.packets = new packet.List();
return msg;
};
/**
* Encrypt a session key
* @param {String} session key for encryption
* @param {String} session key algorithm
* @param {(Array<module:key~Key>|module:key~Key)} public key(s) for message encryption
* @param {(Array<String>|String)} password(s) for message encryption
* @return {Array<module:message~Message>} new message with encrypted content
*/
function encryptSessionKey(sessionKey, symAlgo, keys, passwords) {
/** Convert to arrays if necessary */
if(keys && !Array.prototype.isPrototypeOf(keys)) {
keys = [keys]
}
if(passwords && !Array.prototype.isPrototypeOf(passwords)) {
passwords = [passwords]
}
var packetlist = new packet.List();
if(keys) {
keys.forEach(function(key) {
var encryptionKeyPacket = key.getEncryptionKeyPacket();
if (encryptionKeyPacket) {
var pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey();
pkESKeyPacket.publicKeyId = encryptionKeyPacket.getKeyId();
pkESKeyPacket.publicKeyAlgorithm = encryptionKeyPacket.algorithm;
pkESKeyPacket.sessionKey = sessionKey;
pkESKeyPacket.sessionKeyAlgorithm = symAlgo;
pkESKeyPacket.encrypt(encryptionKeyPacket);
packetlist.push(pkESKeyPacket);
} else {
throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex());
}
});
}
if(passwords) {
passwords.forEach(function(password) {
var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey();
symEncryptedSessionKeyPacket.sessionKey = sessionKey;
symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo;
symEncryptedSessionKeyPacket.encrypt(password);
packetlist.push(symEncryptedSessionKeyPacket);
});
}
return new Message(packetlist);
};
@ -398,6 +436,16 @@ function readArmored(armoredText) {
//TODO how do we want to handle bad text? Exception throwing
//TODO don't accept non-message armored texts
var input = armor.decode(armoredText).data;
return read(input);
}
/**
* reads an OpenPGP binary string message and returns a message object
* @param {String} binary string message
* @return {module:message~Message} new message object
* @static
*/
function read(input) {
var packetlist = new packet.List();
packetlist.read(input);
return new Message(packetlist);
@ -459,7 +507,9 @@ function fromBinary(bytes, filename) {
}
exports.Message = Message;
exports.read = read;
exports.readArmored = readArmored;
exports.readSignedContent = readSignedContent;
exports.fromText = fromText;
exports.fromBinary = fromBinary;
exports.encryptSessionKey = encryptSessionKey;

View File

@ -105,7 +105,11 @@ function encryptMessage(keys, data, passwords, params) {
msg = msg.encrypt(keys, passwords);
if(packets) {
return msg.packets;
var arr = [];
var dataIndex = msg.packets.indexOfTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected)[0];
arr.push(msg.packets.slice(0,dataIndex).write()); // Keys
arr.push(msg.packets.slice(dataIndex,msg.packets.length).write()); // Data
return arr;
}
else {
return armor.encode(enums.armor.message, msg.packets.write());
@ -114,6 +118,29 @@ function encryptMessage(keys, data, passwords, params) {
}, 'Error encrypting message!');
}
/**
* Encrypts session key with keys or passwords
* @param {String} sessionKey sessionKey as a binary string
* @param {String} algo algorithm of sessionKey
* @param {(Array<module:key~Key>|module:key~Key)} keys array of keys or single key, used to encrypt the key
* @param {(Array<String>|String)} passwords passwords for the message
* @return {Promise<Packetlist>} Binary string of key packets
* @static
*/
function encryptSessionKey(sessionKey, algo, keys, passwords) {
if (asyncProxy) {
return asyncProxy.encryptSessionKey(sessionKey, algo, keys, passwords);
}
return execute(function() {
var msg = message.encryptSessionKey(sessionKey, algo, keys, passwords);
return msg.packets.write();
}, 'Error encrypting session key!');
}
/**
* Signs message text and encrypts it
* @param {(Array<module:key~Key>|module:key~Key)} publicKeys array of keys or single key, used to encrypt the message
@ -144,20 +171,27 @@ function signAndEncryptMessage(publicKeys, privateKey, text) {
/**
* Decrypts message
* @param {module:key~Key|String} privateKey private key with decrypted secret key data or string password
* @param {module:key~Key|String} privateKey private key with decrypted secret key data, string password, or session key
* @param {module:message~Message} msg the message object with the encrypted data
* @param {Boolean} binary if true, return literal data binaryString instead of converting from UTF-8
* @param {Object} params parameter object with optional properties binary {Boolean}
* and sessionKeyAlgorithm {String} which must only be set when privateKey is a session key
* @return {Promise<(String|null)>} decrypted message as as native JavaScript string
* or null if no literal data found
* @static
*/
function decryptMessage(privateKey, msg, binary) {
function decryptMessage(privateKey, msg, params) {
if (asyncProxy) {
return asyncProxy.decryptMessage(privateKey, msg, binary);
return asyncProxy.decryptMessage(privateKey, msg, params);
}
var binary, sessionKeyAlgorithm;
if(params) {
binary = params.binary;
sessionKeyAlgorithm = params.sessionKeyAlgorithm;
}
return execute(function() {
msg = msg.decrypt(privateKey);
msg = msg.decrypt(privateKey, sessionKeyAlgorithm);
if(binary) {
return msg.getLiteralData();
}
@ -168,6 +202,26 @@ function decryptMessage(privateKey, msg, binary) {
}, 'Error decrypting message!');
}
/**
* Decrypts message
* @param {module:key~Key|String} privateKey private key with decrypted secret key data or string password
* @param {module:message~Message} msg the message object with the encrypted session key packets
* @return {Promise<Object|null>} decrypted session key and algorithm in object form
* or null if no key packets found
* @static
*/
function decryptSessionKey(privateKey, msg) {
if (asyncProxy) {
return asyncProxy.decryptSessionKey(privateKey, msg);
}
return execute(function() {
var obj = msg.decryptSessionKey(privateKey);
return obj;
}, 'Error decrypting session key!');
}
/**
* Decrypts message and verifies signatures
* @param {module:key~Key} privateKey private key with decrypted secret key data
@ -331,8 +385,10 @@ function onError(message, error) {
exports.initWorker = initWorker;
exports.getWorker = getWorker;
exports.encryptMessage = encryptMessage;
exports.encryptSessionKey = encryptSessionKey;
exports.signAndEncryptMessage = signAndEncryptMessage;
exports.decryptMessage = decryptMessage;
exports.decryptSessionKey = decryptSessionKey;
exports.decryptAndVerifyMessage = decryptAndVerifyMessage;
exports.signClearMessage = signClearMessage;
exports.verifyClearSignedMessage = verifyClearSignedMessage;

View File

@ -159,6 +159,35 @@ AsyncProxy.prototype.encryptMessage = function(keys, data, passwords, params) {
});
};
/**
* Encrypts session key with keys or passwords
* @param {String} sessionKey sessionKey as a binary string
* @param {String} algo algorithm of sessionKey
* @param {(Array<module:key~Key>|module:key~Key)} keys array of keys or single key, used to encrypt the key
* @param {(Array<String>|String)} passwords passwords for the message
*/
AsyncProxy.prototype.encryptSessionKey = function(sessionKey, algo, keys, passwords) {
var self = this;
return self.execute(function() {
if(keys) {
if (!Array.prototype.isPrototypeOf(keys)) {
keys = [keys];
}
keys = keys.map(function(key) {
return key.toPacketlist();
});
}
self.worker.postMessage({
event: 'encrypt-session-key',
sessionKey: sessionKey,
algo: algo,
keys: keys,
passwords: passwords
});
});
};
/**
* Signs message text and encrypts it
* @param {(Array<module:key~Key>|module:key~Key)} publicKeys array of keys or single key, used to encrypt the message
@ -189,9 +218,10 @@ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, te
* Decrypts message
* @param {module:key~Key|String} privateKey private key with decrypted secret key data or string password
* @param {module:message~Message} msg the message object with the encrypted data
* @param {Boolean} binary if true, return literal data binaryString instead of converting from UTF-8
* @param {Object} params parameter object with optional properties binary {Boolean}
* and sessionKeyAlgorithm {String} which must only be set when privateKey is a session key
*/
AsyncProxy.prototype.decryptMessage = function(privateKey, message, binary) {
AsyncProxy.prototype.decryptMessage = function(privateKey, message, params) {
var self = this;
return self.execute(function() {
@ -203,7 +233,29 @@ AsyncProxy.prototype.decryptMessage = function(privateKey, message, binary) {
event: 'decrypt-message',
privateKey: privateKey,
message: message,
binary: binary
params: params
});
});
};
/**
* @param {module:key~Key|String} privateKey private key with decrypted secret key data or string password
* @param {module:message~Message} msg the message object with the encrypted session key packets
* @return {Promise<Object|null>} decrypted session key and algorithm in object form
* or null if no key packets found
*/
AsyncProxy.prototype.decryptSessionKey = function(privateKey, message) {
var self = this;
return self.execute(function() {
if(!(String.prototype.isPrototypeOf(privateKey) || typeof privateKey === 'string')) {
privateKey = privateKey.toPacketlist();
}
self.worker.postMessage({
event: 'decrypt-session-key',
privateKey: privateKey,
message: message
});
});
};

View File

@ -74,6 +74,16 @@ self.onmessage = function (event) {
response({event: 'method-return', err: e.message});
});
break;
case 'encrypt-session-key':
if(msg.keys) {
msg.keys = msg.keys.map(packetlistCloneToKey);
}
window.openpgp.encryptSessionKey(msg.sessionKey, msg.algo, msg.keys, msg.passwords).then(function(data) {
response({event: 'method-return', data: data});
}).catch(function(e) {
response({event: 'method-return', err: e.message});
});
break;
case 'sign-and-encrypt-message':
if (!msg.publicKeys.length) {
msg.publicKeys = [msg.publicKeys];
@ -91,7 +101,18 @@ self.onmessage = function (event) {
msg.privateKey = packetlistCloneToKey(msg.privateKey);
}
msg.message = packetlistCloneToMessage(msg.message.packets);
window.openpgp.decryptMessage(msg.privateKey, msg.message, msg.binary).then(function(data) {
window.openpgp.decryptMessage(msg.privateKey, msg.message, msg.params).then(function(data) {
response({event: 'method-return', data: data});
}).catch(function(e) {
response({event: 'method-return', err: e.message});
});
break;
case 'decrypt-session-key':
if(!(String.prototype.isPrototypeOf(msg.privateKey) || typeof msg.privateKey === 'string')) {
msg.privateKey = packetlistCloneToKey(msg.privateKey);
}
msg.message = packetlistCloneToMessage(msg.message.packets);
window.openpgp.decryptSessionKey(msg.privateKey, msg.message).then(function(data) {
response({event: 'method-return', data: data});
}).catch(function(e) {
response({event: 'method-return', err: e.message});

View File

@ -259,10 +259,15 @@ describe('Basic', function() {
expect(pubKey).to.exist;
openpgp.encryptMessage([pubKey], plaintext, [password1, password2]).then(function(encrypted) {
var params = {
packets: true
};
openpgp.encryptMessage([pubKey], plaintext, [password1, password2], params).then(function(encrypted) {
expect(encrypted).to.exist;
encrypted = encrypted.join('');
encrypted = openpgp.armor.encode(openpgp.enums.armor.message, encrypted);
message = openpgp.message.readArmored(encrypted);
expect(message).to.exist;

View File

@ -272,13 +272,16 @@ describe('High level API', function() {
describe('Decryption', function() {
var msgRSA, msgDE;
var msgRSA, msgDE, msgAES, keys, data;
before(function() {
privKeyRSA.decrypt('hello world');
privKeyDE.decrypt('hello world');
msgRSA = openpgp.message.fromText(plaintext).encrypt([pubKeyRSA],[password1, password2]);
msgDE = openpgp.message.fromText(plaintext).encrypt([pubKeyDE]);
msgAES = openpgp.message.fromText(plaintext).encrypt([],[password1]);
var dataIndex = msgAES.packets.indexOfTag(openpgp.enums.packet.symmetricallyEncrypted, openpgp.enums.packet.symEncryptedIntegrityProtected)[0];
data = msgAES.packets.slice(dataIndex,msgAES.packets.length).write();
});
it('RSA: decryptMessage async', function (done) {
@ -312,6 +315,43 @@ describe('High level API', function() {
done();
});
});
it('RSA: decryptSessionKey/encryptSessionKey/decryptMessage async', function (done) {
openpgp.decryptSessionKey(password1, msgAES).then(function(sk) {
return openpgp.encryptSessionKey(sk.key, sk.algo, pubKeyRSA);
}).then(function(keypacket) {
var msg = openpgp.message.read([keypacket, data].join(''));
return openpgp.decryptMessage(privKeyRSA, msg);
}).then(function(data) {
expect(data).to.exist;
expect(data).to.equal(plaintext);
done();
});
});
it('AES: decryptSessionKey/encryptSessionKey/decryptMessage async', function (done) {
openpgp.decryptSessionKey(password1, msgAES).then(function(sk) {
return openpgp.encryptSessionKey(sk.key, sk.algo, [], password2);
}).then(function(keypacket) {
var msg = openpgp.message.read([keypacket, data].join(''));
return openpgp.decryptMessage(password2, msg);
}).then(function(data) {
expect(data).to.exist;
expect(data).to.equal(plaintext);
done();
});
});
it('AES: decryptSessionKey/decryptMessage with session key async', function (done) {
openpgp.decryptSessionKey(password1, msgAES).then(function(sk) {
var msg = openpgp.message.read(data);
return openpgp.decryptMessage(sk.key, msg, {sessionKeyAlgorithm: sk.algo});
}).then(function(data) {
expect(data).to.exist;
expect(data).to.equal(plaintext);
done();
});
});
});
function verifySignature(data, privKey) {