Fixes for symmetrically encrypted session keys

This commit is contained in:
Bart Butler 2015-03-15 17:17:06 -07:00 committed by Tankred Hase
parent 6c4e0ed6a0
commit 2e4e9387a0
9 changed files with 243 additions and 107 deletions

View File

@ -247,7 +247,12 @@ module.exports = {
var pos = 0;
var cyphertext = '';
var tempBlock = '';
blockc = iv.substring(0, block_size);
if (iv === null)
for (i = 0; i < block_size; i++) {
blockc += String.fromCharCode(0);
}
else
blockc = iv.substring(0, block_size);
while (plaintext.length > block_size * pos) {
var encblock = cipherfn.encrypt(util.str2bin(blockc));
blocki = plaintext.substring((pos * block_size), (pos * block_size) + block_size);

View File

@ -85,31 +85,54 @@ Message.prototype.getSigningKeyIds = function() {
/**
* Decrypt the message
* @param {module:key~Key} privateKey private key with decrypted secret data
* @param {module:key~Key|String} privateKey private key with decrypted secret data or password
* @return {Array<module:message~Message>} new message with decrypted content
*/
Message.prototype.decrypt = function(privateKey) {
var encryptionKeyIds = this.getEncryptionKeyIds();
if (!encryptionKeyIds.length) {
// nothing to decrypt return unmodified message
return this;
}
var privateKeyPacket = privateKey.getKeyPacket(encryptionKeyIds);
if (!privateKeyPacket.isDecrypted) throw new Error('Private key is not decrypted.');
var pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
var pkESKeyPacket;
for (var i = 0; i < pkESKeyPacketlist.length; i++) {
if (pkESKeyPacketlist[i].publicKeyId.equals(privateKeyPacket.getKeyId())) {
pkESKeyPacket = pkESKeyPacketlist[i];
pkESKeyPacket.decrypt(privateKeyPacket);
break;
var keyPacket;
if(String.prototype.isPrototypeOf(privateKey) || typeof privateKey === 'string') {
var symEncryptedSessionKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
var symLength = symEncryptedSessionKeyPacketlist.length;
for (var i = 0; i < symLength; i++) {
keyPacket = symEncryptedSessionKeyPacketlist[i];
try {
keyPacket.decrypt(privateKey);
break;
}
catch(err) {
if(i === (symLength-1)) {
throw err;
}
}
}
if(!keyPacket) {
throw new Error('No symmetrically encrypted session key packet found.');
}
}
if (pkESKeyPacket) {
else {
var encryptionKeyIds = this.getEncryptionKeyIds();
if (!encryptionKeyIds.length) {
// nothing to decrypt return unmodified message
return this;
}
var privateKeyPacket = privateKey.getKeyPacket(encryptionKeyIds);
if (!privateKeyPacket.isDecrypted) throw new Error('Private key is not decrypted.');
var pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
for (var i = 0; i < pkESKeyPacketlist.length; i++) {
if (pkESKeyPacketlist[i].publicKeyId.equals(privateKeyPacket.getKeyId())) {
keyPacket = pkESKeyPacketlist[i];
keyPacket.decrypt(privateKeyPacket);
break;
}
}
}
if (keyPacket) {
var symEncryptedPacketlist = this.packets.filterByTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected);
if (symEncryptedPacketlist.length !== 0) {
var symEncryptedPacket = symEncryptedPacketlist[0];
symEncryptedPacket.decrypt(pkESKeyPacket.sessionKeyAlgorithm, pkESKeyPacket.sessionKey);
symEncryptedPacket.decrypt(keyPacket.sessionKeyAlgorithm, keyPacket.sessionKey);
var resultMsg = new Message(symEncryptedPacket.packets);
// remove packets after decryption
symEncryptedPacket.packets = new packet.List();
@ -142,27 +165,59 @@ Message.prototype.getText = function() {
/**
* Encrypt the message
* @param {Array<module:key~Key>} keys array of keys, used to encrypt the message
* @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
*/
Message.prototype.encrypt = function(keys) {
var packetlist = new packet.List();
var symAlgo = keyModule.getPreferredSymAlgo(keys);
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) {
symAlgo = keyModule.getPreferredSymAlgo(keys);
}
else if(passwords) {
symAlgo = config.encryption_cipher;
}
else {
throw new Error('No keys or passwords');
}
var sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo));
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());
}
});
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 symEncryptedPacket;
if (config.integrity_protect) {
symEncryptedPacket = new packet.SymEncryptedIntegrityProtected();
@ -366,13 +421,17 @@ function readSignedContent(content, detachedSignature) {
/**
* creates new message object from text
* @param {String} text
* @param {String} filename (optional)
* @return {module:message~Message} new message object
* @static
*/
function fromText(text) {
function fromText(text, filename) {
var literalDataPacket = new packet.Literal();
// text will be converted to UTF8
literalDataPacket.setText(text);
if(filename !== undefined) {
literalDataPacket.setFilename(filename);
}
var literalDataPacketlist = new packet.List();
literalDataPacketlist.push(literalDataPacket);
return new Message(literalDataPacketlist);
@ -381,7 +440,7 @@ function fromText(text) {
/**
* creates new message object from binary data
* @param {String} bytes
* @param {String} filename
* @param {String} filename (optional)
* @return {module:message~Message} new message object
* @static
*/
@ -391,6 +450,9 @@ function fromBinary(bytes, filename) {
literalDataPacket.setFilename(filename);
}
literalDataPacket.setBytes(bytes, enums.read(enums.literal, enums.literal.binary));
if(filename !== undefined) {
literalDataPacket.setFilename(filename);
}
var literalDataPacketlist = new packet.List();
literalDataPacketlist.push(literalDataPacket);
return new Message(literalDataPacketlist);

View File

@ -72,28 +72,24 @@ function getWorker() {
}
/**
* Encrypts message text with keys
* @param {(Array<module:key~Key>|module:key~Key)} keys array of keys or single key, used to encrypt the message
* @param {String} text message as native JavaScript string
* @return {Promise<String>} encrypted ASCII armored message
* Encrypts message text/data with keys or passwords
* @param {(Array<module:key~Key>|module:key~Key)} keys array of keys or single key, used to encrypt the message
* @param {String} text text message as native JavaScript string
* @param {(Array<String>|String)} passwords passwords for the message
* @return {Promise<String>} encrypted ASCII armored message
* @static
*/
function encryptMessage(keys, text) {
if (!keys.length) {
keys = [keys];
}
function encryptMessage(keys, text, passwords) {
if (asyncProxy) {
return asyncProxy.encryptMessage(keys, text);
return asyncProxy.encryptMessage(keys, text, passwords);
}
return execute(function() {
var msg, armored;
msg = message.fromText(text);
msg = msg.encrypt(keys);
armored = armor.encode(enums.armor.message, msg.packets.write());
return armored;
msg = msg.encrypt(keys, passwords);
return armor.encode(enums.armor.message, msg.packets.write());
}, 'Error encrypting message!');
}
@ -127,10 +123,10 @@ function signAndEncryptMessage(publicKeys, privateKey, text) {
/**
* Decrypts message
* @param {module:key~Key} privateKey private key with decrypted secret key data
* @param {module:message~Message} msg the message object with the encrypted data
* @return {Promise<(String|null)>} decrypted message as as native JavaScript string
* or null if no literal data found
* @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
* @return {Promise<(String|null)>} decrypted message as as native JavaScript string
* or null if no literal data found
* @static
*/
function decryptMessage(privateKey, msg) {
@ -141,7 +137,6 @@ function decryptMessage(privateKey, msg) {
return execute(function() {
msg = msg.decrypt(privateKey);
return msg.getText();
}, 'Error decrypting message!');
}

View File

@ -47,6 +47,7 @@ module.exports = SymEncryptedSessionKey;
function SymEncryptedSessionKey() {
this.tag = enums.packet.symEncryptedSessionKey;
this.version = 4;
this.sessionKey = null;
this.sessionKeyEncryptionAlgorithm = null;
this.sessionKeyAlgorithm = 'aes256';
this.encrypted = null;
@ -109,7 +110,6 @@ SymEncryptedSessionKey.prototype.decrypt = function(passphrase) {
this.sessionKeyEncryptionAlgorithm :
this.sessionKeyAlgorithm;
var length = crypto.cipher[algo].keySize;
var key = this.s2k.produce_key(passphrase, length);
@ -117,30 +117,38 @@ SymEncryptedSessionKey.prototype.decrypt = function(passphrase) {
this.sessionKey = key;
} else {
var decrypted = crypto.cfb.decrypt(
this.sessionKeyEncryptionAlgorithm, key, this.encrypted, true);
decrypted = decrypted.join('');
var decrypted = crypto.cfb.normalDecrypt(
algo, key, this.encrypted, null);
this.sessionKeyAlgorithm = enums.read(enums.symmetric,
decrypted[0].keyCodeAt());
decrypted.charCodeAt(0));
this.sessionKey = decrypted.substr(1);
}
};
SymEncryptedSessionKey.prototype.encrypt = function(passphrase) {
var length = crypto.getKeyLength(this.sessionKeyEncryptionAlgorithm);
var algo = this.sessionKeyEncryptionAlgorithm !== null ?
this.sessionKeyEncryptionAlgorithm :
this.sessionKeyAlgorithm;
this.sessionKeyEncryptionAlgorithm = algo;
var length = crypto.cipher[algo].keySize;
var key = this.s2k.produce_key(passphrase, length);
var private_key = String.fromCharCode(
enums.write(enums.symmetric, this.sessionKeyAlgorithm)) +
var algo_enum = String.fromCharCode(
enums.write(enums.symmetric, this.sessionKeyAlgorithm));
crypto.getRandomBytes(
crypto.getKeyLength(this.sessionKeyAlgorithm));
var private_key;
if(this.sessionKey === null) {
this.sessionKey = crypto.getRandomBytes(crypto.cipher[this.sessionKeyAlgorithm].keySize);
}
private_key = algo_enum + this.sessionKey;
this.encrypted = crypto.cfb.encrypt(
crypto.getPrefixRandom(this.sessionKeyEncryptionAlgorithm),
this.sessionKeyEncryptionAlgorithm, key, private_key, true);
this.encrypted = crypto.cfb.normalEncrypt(
algo, key, private_key, null);
};
/**

View File

@ -191,9 +191,9 @@ S2K.prototype.produce_key = function (passphrase, numBytes) {
module.exports.fromClone = function (clone) {
var s2k = new S2K();
this.algorithm = clone.algorithm;
this.type = clone.type;
this.c = clone.c;
this.salt = clone.salt;
s2k.algorithm = clone.algorithm;
s2k.type = clone.type;
s2k.c = clone.c;
s2k.salt = clone.salt;
return s2k;
};

View File

@ -130,24 +130,28 @@ AsyncProxy.prototype.terminate = function() {
};
/**
* Encrypts message text with keys
* @param {(Array<module:key~Key>|module:key~Key)} keys array of keys or single key, used to encrypt the message
* @param {String} text message as native JavaScript string
* Encrypts message text/data with keys or passwords
* @param {(Array<module:key~Key>|module:key~Key)} keys array of keys or single key, used to encrypt the message
* @param {String} text text message as native JavaScript string/binary string
* @param {(Array<String>|String)} passwords passwords for the message
*/
AsyncProxy.prototype.encryptMessage = function(keys, text) {
AsyncProxy.prototype.encryptMessage = function(keys, text, passwords) {
var self = this;
return self.execute(function() {
if (!keys.length) {
keys = [keys];
if(keys) {
if (!Array.prototype.isPrototypeOf(keys)) {
keys = [keys];
}
keys = keys.map(function(key) {
return key.toPacketlist();
});
}
keys = keys.map(function(key) {
return key.toPacketlist();
});
self.worker.postMessage({
event: 'encrypt-message',
keys: keys,
text: text
text: text,
passwords: passwords
});
});
};
@ -180,14 +184,18 @@ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, te
/**
* Decrypts message
* @param {module:key~Key} privateKey private key with decrypted secret key data
* @param {module:message~Message} message the message object with the encrypted data
* @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
*/
AsyncProxy.prototype.decryptMessage = function(privateKey, message) {
var self = this;
return self.execute(function() {
privateKey = privateKey.toPacketlist();
if(!(String.prototype.isPrototypeOf(privateKey) || typeof privateKey === 'string')) {
privateKey = privateKey.toPacketlist();
}
self.worker.postMessage({
event: 'decrypt-message',
privateKey: privateKey,

View File

@ -65,11 +65,10 @@ self.onmessage = function (event) {
window.openpgp.crypto.random.randomBuffer.set(msg.buf);
break;
case 'encrypt-message':
if (!msg.keys.length) {
msg.keys = [msg.keys];
if(msg.keys) {
msg.keys = msg.keys.map(packetlistCloneToKey);
}
msg.keys = msg.keys.map(packetlistCloneToKey);
window.openpgp.encryptMessage(msg.keys, msg.text).then(function(data) {
window.openpgp.encryptMessage(msg.keys, msg.text, msg.passwords).then(function(data) {
response({event: 'method-return', data: data});
}).catch(function(e) {
response({event: 'method-return', err: e.message});
@ -88,7 +87,9 @@ self.onmessage = function (event) {
});
break;
case 'decrypt-message':
msg.privateKey = packetlistCloneToKey(msg.privateKey);
if(!(String.prototype.isPrototypeOf(msg.privateKey) || typeof msg.privateKey === 'string')) {
msg.privateKey = packetlistCloneToKey(msg.privateKey);
}
msg.message = packetlistCloneToMessage(msg.message.packets);
window.openpgp.decryptMessage(msg.privateKey, msg.message).then(function(data) {
response({event: 'method-return', data: data});

View File

@ -243,6 +243,9 @@ describe('Basic', function() {
var plaintext = 'short message\nnext line\n한국어/조선말';
var password1 = 'I am a password';
var password2 = 'I am another password';
var privKey, message, keyids;
it('Test initialization', function (done) {
@ -256,7 +259,7 @@ describe('Basic', function() {
expect(pubKey).to.exist;
openpgp.encryptMessage([pubKey], plaintext).then(function(encrypted) {
openpgp.encryptMessage([pubKey], plaintext, [password1, password2]).then(function(encrypted) {
expect(encrypted).to.exist;
@ -315,6 +318,22 @@ describe('Basic', function() {
});
});
it('Decrypt with password1 leads to the same result', function (done) {
openpgp.decryptMessage(password1, message).then(function(decrypted) {
expect(decrypted).to.exist;
expect(decrypted).to.equal(plaintext);
done();
});
});
it('Decrypt with password2 leads to the same result', function (done) {
openpgp.decryptMessage(password2, message).then(function(decrypted) {
expect(decrypted).to.exist;
expect(decrypted).to.equal(plaintext);
done();
});
});
it('Decrypt message 2x', function(done) {
var decrypted1;

View File

@ -157,20 +157,23 @@ var priv_key_de =
'-----END PGP PRIVATE KEY BLOCK-----'].join('\n');
var plaintext = 'short message\nnext line\n한국어/조선말';
var plaintext = 'short message\nnext line\n한국어/조선말';
var pubKeyRSA, privKeyRSA, pubKeyDE, privKeyDE;
var password1 = 'I am a password';
var password2 = 'I am another password';
function initKeys() {
pubKeyRSA = openpgp.key.readArmored(pub_key_rsa).keys[0];
expect(pubKeyRSA).to.exist;
privKeyRSA = openpgp.key.readArmored(priv_key_rsa).keys[0];
expect(privKeyRSA).to.exist;
pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0];
expect(pubKeyDE).to.exist;
privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0];
expect(privKeyDE).to.exist;
}
var pubKeyRSA, privKeyRSA, pubKeyDE, privKeyDE;
function initKeys() {
pubKeyRSA = openpgp.key.readArmored(pub_key_rsa).keys[0];
expect(pubKeyRSA).to.exist;
privKeyRSA = openpgp.key.readArmored(priv_key_rsa).keys[0];
expect(privKeyRSA).to.exist;
pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0];
expect(pubKeyDE).to.exist;
privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0];
expect(privKeyDE).to.exist;
}
describe('Init Worker', function() {
@ -215,8 +218,8 @@ describe('High level API', function() {
});
describe('Encryption', function() {
it('RSA: encryptMessage async', function (done) {
openpgp.encryptMessage([pubKeyRSA], plaintext).then(function(data) {
it('AES: encryptMessage one password async', function (done) {
openpgp.encryptMessage([], plaintext, password1).then(function(data) {
expect(data).to.exist;
expect(data).to.match(/^-----BEGIN PGP MESSAGE/);
var msg = openpgp.message.readArmored(data);
@ -235,6 +238,26 @@ describe('High level API', function() {
});
});
it('RSA: encryptMessage one key one password async', function (done) {
openpgp.encryptMessage(pubKeyRSA, plaintext, password1).then(function(data) {
expect(data).to.exist;
expect(data).to.match(/^-----BEGIN PGP MESSAGE/);
var msg = openpgp.message.readArmored(data);
expect(msg).to.be.an.instanceof(openpgp.message.Message);
done();
});
});
it('RSA: encryptMessage one key two passwords async', function (done) {
openpgp.encryptMessage(pubKeyRSA, plaintext, [password1, password2]).then(function(data) {
expect(data).to.exist;
expect(data).to.match(/^-----BEGIN PGP MESSAGE/);
var msg = openpgp.message.readArmored(data);
expect(msg).to.be.an.instanceof(openpgp.message.Message);
done();
});
});
it('ELG: encryptMessage async', function (done) {
openpgp.encryptMessage([pubKeyDE], plaintext).then(function(data) {
expect(data).to.exist;
@ -254,7 +277,7 @@ describe('High level API', function() {
before(function() {
privKeyRSA.decrypt('hello world');
privKeyDE.decrypt('hello world');
msgRSA = openpgp.message.fromText(plaintext).encrypt([pubKeyRSA]);
msgRSA = openpgp.message.fromText(plaintext).encrypt([pubKeyRSA],[password1, password2]);
msgDE = openpgp.message.fromText(plaintext).encrypt([pubKeyDE]);
});
@ -274,6 +297,21 @@ describe('High level API', function() {
});
});
it('AES: decryptMessage password1 async', function (done) {
openpgp.decryptMessage(password1, msgRSA).then(function(data) {
expect(data).to.exist;
expect(data).to.equal(plaintext);
done();
});
});
it('AES: decryptMessage password2 async', function (done) {
openpgp.decryptMessage(password2, msgRSA).then(function(data) {
expect(data).to.exist;
expect(data).to.equal(plaintext);
done();
});
});
});
function verifySignature(data, privKey) {