encrypt/decrypt/sign/verify will always return promises

Note: publicKeyEncryptedSessionKey uses promises, symEncryptedSessionKey does not
This commit is contained in:
Mahrud Sayrafi 2018-01-04 01:27:37 -08:00 committed by Sanjana Rajan
parent 1a714cec73
commit 21ae66c604
12 changed files with 182 additions and 156 deletions

View File

@ -330,6 +330,7 @@ module.exports = {
// Custom silencers:
"camelcase": 0,
"no-debugger": 0,
"require-await": 0,
"no-multi-assign": 0,
"no-underscore-dangle": 0,
"one-var-declaration-per-line": 0,

View File

@ -72,9 +72,9 @@ export default {
* @param {module:type/mpi} data Data to be encrypted as MPI
* @return {Array<module:type/mpi|module:type/oid|module:type/kdf|module:type/ecdh_symkey>} encrypted session key parameters
*/
publicKeyEncrypt: function(algo, publicParams, data, fingerprint) {
publicKeyEncrypt: async function(algo, publicParams, data, fingerprint) {
var types = this.getEncSessionKeyParamTypes(algo);
var result = (async function() {
return (async function() {
var m;
switch (algo) {
case 'rsa_encrypt':
@ -107,8 +107,6 @@ export default {
return [];
}
}());
return result;
},
/**
@ -120,9 +118,9 @@ export default {
* @return {module:type/mpi} returns a big integer containing the decrypted data; otherwise null
*/
publicKeyDecrypt: function(algo, keyIntegers, dataIntegers, fingerprint) {
publicKeyDecrypt: async function(algo, keyIntegers, dataIntegers, fingerprint) {
var p;
var bn = (function() {
return new type_mpi(await (async function() {
switch (algo) {
case 'rsa_encrypt_sign':
case 'rsa_encrypt':
@ -157,10 +155,7 @@ export default {
default:
return null;
}
}());
var result = new type_mpi(bn);
return result;
}()));
},
/** Returns the types comprising the private key of an algorithm

View File

@ -105,7 +105,7 @@ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) {
* @param {String} fingerprint Recipient fingerprint
* @return {Uint8Array} Value derived from session
*/
function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) {
async function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) {
fingerprint = util.hex2Uint8Array(fingerprint);
const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint);
const curve = curves.get(oid);

View File

@ -64,8 +64,7 @@ export default {
const s = msg_MPIs[1].toBigInteger();
m = data;
const Q = publickey_MPIs[1].toBigInteger();
const result = await ecdsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q);
return result;
return ecdsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q);
default:
throw new Error('Invalid signature algorithm.');
}

View File

@ -92,30 +92,28 @@ Message.prototype.getSigningKeyIds = function() {
* @param {String} password (optional) password used to decrypt
* @return {Message} new message with decrypted content
*/
Message.prototype.decrypt = function(privateKey, sessionKey, password) {
return Promise.resolve().then(() => {
const keyObj = sessionKey || this.decryptSessionKey(privateKey, password);
if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) {
throw new Error('Invalid session key for decryption.');
}
Message.prototype.decrypt = async function(privateKey, sessionKey, password) {
const keyObj = sessionKey || await this.decryptSessionKey(privateKey, password);
if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) {
throw new Error('Invalid session key for decryption.');
}
const symEncryptedPacketlist = this.packets.filterByTag(
enums.packet.symmetricallyEncrypted,
enums.packet.symEncryptedIntegrityProtected,
enums.packet.symEncryptedAEADProtected
);
const symEncryptedPacketlist = this.packets.filterByTag(
enums.packet.symmetricallyEncrypted,
enums.packet.symEncryptedIntegrityProtected,
enums.packet.symEncryptedAEADProtected
);
if (symEncryptedPacketlist.length === 0) {
return;
}
if (symEncryptedPacketlist.length === 0) {
return;
}
const symEncryptedPacket = symEncryptedPacketlist[0];
return symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data).then(() => {
const resultMsg = new Message(symEncryptedPacket.packets);
symEncryptedPacket.packets = new packet.List(); // remove packets after decryption
return resultMsg;
});
});
const symEncryptedPacket = symEncryptedPacketlist[0];
await symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data);
const resultMsg = new Message(symEncryptedPacket.packets);
symEncryptedPacket.packets = new packet.List(); // remove packets after decryption
return resultMsg;
};
/**
@ -126,56 +124,64 @@ Message.prototype.decrypt = function(privateKey, sessionKey, password) {
* { data:Uint8Array, algorithm:String }
*/
Message.prototype.decryptSessionKey = function(privateKey, password) {
var keyPacket;
if (password) {
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(password);
break;
var keyPacket, results, error;
return Promise.resolve().then(async () => {
if (password) {
var symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
// FIXME need a circuit breaker here
if (!symESKeyPacketlist) {
throw new Error('No symmetrically encrypted session key packet found.');
}
catch(err) {
if (i === (symLength - 1)) {
throw err;
results = await Promise.all(symESKeyPacketlist.map(async function(packet) {
try {
await packet.decrypt(password);
return packet;
} catch (err) {
error = err;
}
}));
keyPacket = results.find(result => result !== undefined);
} else if (privateKey) {
var encryptionKeyIds = this.getEncryptionKeyIds();
if (!encryptionKeyIds.length) {
// nothing to decrypt
return;
}
}
if (!keyPacket) {
throw new Error('No symmetrically encrypted session key packet found.');
}
} else if (privateKey) {
var encryptionKeyIds = this.getEncryptionKeyIds();
if (!encryptionKeyIds.length) {
// nothing to decrypt
return;
}
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 j = 0; j < pkESKeyPacketlist.length; j++) {
if (pkESKeyPacketlist[j].publicKeyId.equals(privateKeyPacket.getKeyId())) {
keyPacket = pkESKeyPacketlist[j];
keyPacket.decrypt(privateKeyPacket);
break;
var privateKeyPacket = privateKey.getKeyPacket(encryptionKeyIds);
if (!privateKeyPacket.isDecrypted) {
throw new Error('Private key is not decrypted.');
}
var pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
if (!pkESKeyPacketlist) {
throw new Error('No public key encrypted session key packet found.');
}
// FIXME need a circuit breaker here
results = await Promise.all(pkESKeyPacketlist.map(async function(packet) {
if (packet.publicKeyId.equals(privateKeyPacket.getKeyId())) {
try {
await packet.decrypt(privateKeyPacket)
return packet;
} catch (err) {
error = err;
}
}
}));
keyPacket = results.find(result => result !== undefined);
} else {
throw new Error('No key or password specified.');
}
} else {
throw new Error('No key or password specified.');
}
if (keyPacket) {
return {
data: keyPacket.sessionKey,
algorithm: keyPacket.sessionKeyAlgorithm
};
}
}).then(() => {
if (keyPacket) {
return {
data: keyPacket.sessionKey,
algorithm: keyPacket.sessionKeyAlgorithm
};
} else {
throw new Error('Session key decryption failed.');
}
});
};
/**
@ -218,7 +224,15 @@ Message.prototype.getText = function() {
*/
Message.prototype.encrypt = function(keys, passwords, sessionKey) {
let symAlgo, msg, symEncryptedPacket;
return Promise.resolve().then(() => {
return Promise.resolve().then(async () => {
if (keys) {
symAlgo = enums.read(enums.symmetric, keyModule.getPreferredSymAlgo(keys));
} else if (passwords) {
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
} else {
throw new Error('No keys or passwords');
}
if (sessionKey) {
if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) {
throw new Error('Invalid session key for encryption.');
@ -237,7 +251,7 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) {
sessionKey = crypto.generateSessionKey(symAlgo);
}
msg = encryptSessionKey(sessionKey, symAlgo, keys, passwords);
msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords);
if (config.aead_protect) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
@ -272,38 +286,41 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) {
* @return {Message} new message with encrypted content
*/
export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) {
var packetlist = new packet.List();
var results, packetlist = new packet.List();
if (publicKeys) {
publicKeys.forEach(function(key) {
var encryptionKeyPacket = key.getEncryptionKeyPacket();
if (encryptionKeyPacket) {
return Promise.resolve().then(async () => {
if (publicKeys) {
results = await Promise.all(publicKeys.map(async function(key) {
var encryptionKeyPacket = key.getEncryptionKeyPacket();
if (!encryptionKeyPacket) {
throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex());
}
var pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey();
pkESKeyPacket.publicKeyId = encryptionKeyPacket.getKeyId();
pkESKeyPacket.publicKeyAlgorithm = encryptionKeyPacket.algorithm;
pkESKeyPacket.sessionKey = sessionKey;
pkESKeyPacket.sessionKeyAlgorithm = symAlgo;
pkESKeyPacket.encrypt(encryptionKeyPacket);
await pkESKeyPacket.encrypt(encryptionKeyPacket);
delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption
packetlist.push(pkESKeyPacket);
} else {
throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex());
}
});
}
return pkESKeyPacket;
}));
packetlist.concat(results);
}
if (passwords) {
passwords.forEach(function(password) {
var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey();
symEncryptedSessionKeyPacket.sessionKey = sessionKey;
symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo;
symEncryptedSessionKeyPacket.encrypt(password);
delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption
packetlist.push(symEncryptedSessionKeyPacket);
});
}
return new Message(packetlist);
if (passwords) {
results = await Promise.all(passwords.map(async function(password) {
var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey();
symEncryptedSessionKeyPacket.sessionKey = sessionKey;
symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo;
await symEncryptedSessionKeyPacket.encrypt(password);
delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption
return symEncryptedSessionKeyPacket;
}));
packetlist.concat(results);
}
}).then(() => {
return new Message(packetlist);
});
}
/**
@ -365,13 +382,11 @@ Message.prototype.sign = async function(privateKeys=[], signature=null) {
packetlist.push(literalDataPacket);
// FIXME does the order matter here? It used to be n-1..0
await Promise.all(privateKeys.map(async function(privateKey) {
await Promise.all(privateKeys.reverse().map(async function(privateKey) {
var signingKeyPacket = privateKey.getSigningKeyPacket();
var signaturePacket = new packet.Signature();
signaturePacket.signatureType = signatureType;
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
// FIXME FIXME were we just signing with the last key?
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
if (!signingKeyPacket.isDecrypted) {
throw new Error('Private key is not decrypted.');

View File

@ -394,10 +394,8 @@ export function encryptSessionKey({ data, algorithm, publicKeys, passwords }) {
return asyncProxy.delegate('encryptSessionKey', { data, algorithm, publicKeys, passwords });
}
return execute(() => ({
message: messageLib.encryptSessionKey(data, algorithm, publicKeys, passwords)
return execute(async () => ({
message: await messageLib.encryptSessionKey(data, algorithm, publicKeys, passwords)
}), 'Error encrypting session key');
}

View File

@ -149,6 +149,20 @@ Packetlist.prototype.forEach = function (callback) {
}
};
/**
* Returns an array containing return values of callback
* on each element
*/
Packetlist.prototype.map = function (callback) {
var packetArray = [];
for (var i = 0; i < this.length; i++) {
packetArray.push(callback(this[i], i, this));
}
return packetArray;
};
/**
* Traverses packet tree and returns first matching packet
* @param {module:enums.packet} type The packet type

View File

@ -132,12 +132,12 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) {
* Private key with secMPIs unlocked
* @return {String} The unencrypted session key
*/
PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) {
var result = crypto.publicKeyDecrypt(
PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) {
var result = (await crypto.publicKeyDecrypt(
this.publicKeyAlgorithm,
key.params,
this.encrypted,
key.fingerprint).toBytes();
key.fingerprint)).toBytes();
var checksum;
var decoded;
@ -155,8 +155,7 @@ PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) {
throw new Error('Checksum mismatch');
} else {
this.sessionKey = key;
this.sessionKeyAlgorithm =
enums.read(enums.symmetric, decoded.charCodeAt(0));
this.sessionKeyAlgorithm = enums.read(enums.symmetric, decoded.charCodeAt(0));
}
};

View File

@ -133,7 +133,7 @@ SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm
this.packets.read(decrypted.subarray(0, decrypted.length - 22));
}
return Promise.resolve();
return true;
};

View File

@ -122,9 +122,7 @@ SymEncryptedSessionKey.prototype.decrypt = function(passphrase) {
var decrypted = crypto.cfb.normalDecrypt(
algo, key, this.encrypted, null);
this.sessionKeyAlgorithm = enums.read(enums.symmetric,
decrypted[0]);
this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]);
this.sessionKey = decrypted.subarray(1,decrypted.length);
}
};
@ -147,8 +145,7 @@ SymEncryptedSessionKey.prototype.encrypt = function(passphrase) {
}
private_key = util.concatUint8Array([algo_enum, this.sessionKey]);
this.encrypted = crypto.cfb.normalEncrypt(
algo, key, private_key, null);
this.encrypted = crypto.cfb.normalEncrypt(algo, key, private_key, null);
};
/**

View File

@ -372,12 +372,14 @@ describe('API functional testing', function() {
"rsa_encrypt_sign", RSApubMPIs, RSAUnencryptedData
).then(RSAEncryptedData => {
var data = openpgp.crypto.publicKeyDecrypt("rsa_encrypt_sign", RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData).write();
data = util.Uint8Array2str(data.subarray(2, data.length));
openpgp.crypto.publicKeyDecrypt("rsa_encrypt_sign", RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData).then(data => {
data = data.write();
data = util.Uint8Array2str(data.subarray(2, data.length));
var result = openpgp.crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength());
expect(result).to.equal(symmKey);
done();
var result = openpgp.crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength());
expect(result).to.equal(symmKey);
done();
});
});
});
@ -389,12 +391,14 @@ describe('API functional testing', function() {
"elgamal", ElgamalpubMPIs, ElgamalUnencryptedData
).then(ElgamalEncryptedData => {
var data = openpgp.crypto.publicKeyDecrypt("elgamal", ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData).write();
data = util.Uint8Array2str(data.subarray(2, data.length));
var data = openpgp.crypto.publicKeyDecrypt("elgamal", ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData).then(data => {
data = data.write();
data = util.Uint8Array2str(data.subarray(2, data.length));
var result = openpgp.crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength());
expect(result).to.equal(symmKey);
done();
var result = openpgp.crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength());
expect(result).to.equal(symmKey);
done();
});
});
});
});

View File

@ -198,10 +198,11 @@ describe("Packet", function() {
msg2.read(msg.write());
msg2[0].decrypt({ params: mpi });
msg2[0].decrypt({ params: mpi }).then(() => {
expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey));
expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm);
expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey));
expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm);
});
});
});
});
@ -243,10 +244,11 @@ describe("Packet", function() {
enc.encrypt(key).then(() => {
enc.decrypt(key);
enc.decrypt(key).then(() => {
expect(stringify(enc.sessionKey)).to.equal(stringify(secret));
done();
expect(stringify(enc.sessionKey)).to.equal(stringify(secret));
done();
});
});
});
@ -305,13 +307,14 @@ describe("Packet", function() {
var msg = new openpgp.packet.List();
msg.read(openpgp.armor.decode(armored_msg).data);
msg[0].decrypt(key);
msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey);
msg[0].decrypt(key).then(() => {
msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey);
var text = stringify(msg[1].packets[0].packets[0].data);
var text = stringify(msg[1].packets[0].packets[0].data);
expect(text).to.equal('Hello world!');
done();
expect(text).to.equal('Hello world!');
done();
});
});
it('Sym encrypted session key reading/writing', function(done) {
@ -335,7 +338,6 @@ describe("Packet", function() {
enc.packets.push(literal);
enc.encrypt(algo, key);
var msg2 = new openpgp.packet.List();
msg2.read(msg.write());
@ -368,13 +370,14 @@ describe("Packet", function() {
var msg = new openpgp.packet.List();
msg.read(openpgp.armor.decode(armored_msg).data);
msg[0].decrypt(key);
msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey);
msg[0].decrypt(key).then(() => {
msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey);
var text = stringify(msg[1].packets[0].packets[0].data);
var text = stringify(msg[1].packets[0].packets[0].data);
expect(text).to.equal('Hello world!');
done();
expect(text).to.equal('Hello world!');
done();
});
});
it('Secret key reading with signature verification.', function() {
@ -418,14 +421,15 @@ describe("Packet", function() {
var msg = new openpgp.packet.List();
msg.read(openpgp.armor.decode(armored_msg).data);
msg[0].decrypt(key[3]);
msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey);
msg[0].decrypt(key[3]).then(() => {
msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey);
var payload = msg[1].packets[0].packets;
var payload = msg[1].packets[0].packets;
expect(payload[2].verify(
key[0], payload[1]
)).to.eventually.be.true.notify(done);
expect(payload[2].verify(
key[0], payload[1]
)).to.eventually.be.true.notify(done);
});
});
it('Writing and encryption of a secret key packet.', function() {