Only AEAD-protect when target keys support it

This commit is contained in:
Daniel Huigens 2018-04-11 16:37:40 +02:00
parent e9a360019c
commit e24b46192d
4 changed files with 125 additions and 18 deletions

View File

@ -1402,14 +1402,15 @@ function getExpirationTime(keyPacket, signature) {
/** /**
* Returns the preferred signature hash algorithm of a key * Returns the preferred signature hash algorithm of a key
* @param {object} key * @param {object} key
* @param {Date} date (optional) use the given date for verification instead of the current time
* @returns {Promise<String>} * @returns {Promise<String>}
* @async * @async
*/ */
export async function getPreferredHashAlgo(key) { export async function getPreferredHashAlgo(key, date) {
let hash_algo = config.prefer_hash_algorithm; let hash_algo = config.prefer_hash_algorithm;
let pref_algo = hash_algo; let pref_algo = hash_algo;
if (key instanceof Key) { if (key instanceof Key) {
const primaryUser = await key.getPrimaryUser(); const primaryUser = await key.getPrimaryUser(date);
if (primaryUser && primaryUser.selfCertification.preferredHashAlgorithms) { if (primaryUser && primaryUser.selfCertification.preferredHashAlgorithms) {
[pref_algo] = primaryUser.selfCertification.preferredHashAlgorithms; [pref_algo] = primaryUser.selfCertification.preferredHashAlgorithms;
hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ?
@ -1437,13 +1438,14 @@ export async function getPreferredHashAlgo(key) {
/** /**
* Returns the preferred symmetric algorithm for a set of keys * Returns the preferred symmetric algorithm for a set of keys
* @param {Array<module:key.Key>} keys Set of keys * @param {Array<module:key.Key>} keys Set of keys
* @param {Date} date (optional) use the given date for verification instead of the current time
* @returns {Promise<module:enums.symmetric>} Preferred symmetric algorithm * @returns {Promise<module:enums.symmetric>} Preferred symmetric algorithm
* @async * @async
*/ */
export async function getPreferredSymAlgo(keys) { export async function getPreferredSymAlgo(keys, date) {
const prioMap = {}; const prioMap = {};
await Promise.all(keys.map(async function(key) { await Promise.all(keys.map(async function(key) {
const primaryUser = await key.getPrimaryUser(); const primaryUser = await key.getPrimaryUser(date);
if (!primaryUser || !primaryUser.selfCertification.preferredSymmetricAlgorithms) { if (!primaryUser || !primaryUser.selfCertification.preferredSymmetricAlgorithms) {
return config.encryption_cipher; return config.encryption_cipher;
} }
@ -1471,13 +1473,20 @@ export async function getPreferredSymAlgo(keys) {
/** /**
* Returns the preferred aead algorithm for a set of keys * Returns the preferred aead algorithm for a set of keys
* @param {Array<module:key.Key>} keys Set of keys * @param {Array<module:key.Key>} keys Set of keys
* @returns {Promise<module:enums.aead>} Preferred aead algorithm * @param {Date} date (optional) use the given date for verification instead of the current time
* @returns {Promise<module:enums.aead>} Preferred aead algorithm, or null if the public keys do not support aead
* @async * @async
*/ */
export async function getPreferredAeadAlgo(keys) { export async function getPreferredAeadAlgo(keys, date) {
let supports_aead = true;
const prioMap = {}; const prioMap = {};
await Promise.all(keys.map(async function(key) { await Promise.all(keys.map(async function(key) {
const primaryUser = await key.getPrimaryUser(); const primaryUser = await key.getPrimaryUser(date);
if (!primaryUser || !primaryUser.selfCertification.features ||
!(primaryUser.selfCertification.features[0] & enums.features.aead)) {
supports_aead = false;
return;
}
if (!primaryUser || !primaryUser.selfCertification.preferredAeadAlgorithms) { if (!primaryUser || !primaryUser.selfCertification.preferredAeadAlgorithms) {
return config.aead_mode; return config.aead_mode;
} }
@ -1487,6 +1496,9 @@ export async function getPreferredAeadAlgo(keys) {
entry.count++; entry.count++;
}); });
})); }));
if (!supports_aead) {
return null;
}
let prefAlgo = { prio: 0, algo: config.aead_mode }; let prefAlgo = { prio: 0, algo: config.aead_mode };
for (const algo in prioMap) { for (const algo in prioMap) {
try { try {

View File

@ -93,7 +93,7 @@ Message.prototype.getSigningKeyIds = function() {
* Decrypt the message. Either a private key, a session key, or a password must be specified. * Decrypt the message. Either a private key, a session key, or a password must be specified.
* @param {Array<Key>} privateKeys (optional) private keys with decrypted secret data * @param {Array<Key>} privateKeys (optional) private keys with decrypted secret data
* @param {Array<String>} passwords (optional) passwords used to decrypt * @param {Array<String>} passwords (optional) passwords used to decrypt
* @param {Array<Object>} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String } * @param {Array<Object>} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] }
* @returns {Promise<Message>} new message with decrypted content * @returns {Promise<Message>} new message with decrypted content
* @async * @async
*/ */
@ -244,7 +244,7 @@ Message.prototype.getText = function() {
* Encrypt the message either with public keys, passwords, or both at once. * Encrypt the message either with public keys, passwords, or both at once.
* @param {Array<Key>} keys (optional) public key(s) for message encryption * @param {Array<Key>} keys (optional) public key(s) for message encryption
* @param {Array<String>} passwords (optional) password(s) for message encryption * @param {Array<String>} passwords (optional) password(s) for message encryption
* @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] }
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @param {Date} date (optional) override the creation date of the literal package * @param {Date} date (optional) override the creation date of the literal package
* @returns {Promise<Message>} new message with encrypted content * @returns {Promise<Message>} new message with encrypted content
@ -260,11 +260,14 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
throw new Error('Invalid session key for encryption.'); throw new Error('Invalid session key for encryption.');
} }
symAlgo = sessionKey.algorithm; symAlgo = sessionKey.algorithm;
aeadAlgo = sessionKey.aeadAlgorithm || config.aead_mode; aeadAlgo = sessionKey.aeadAlgorithm;
sessionKey = sessionKey.data; sessionKey = sessionKey.data;
} else if (keys && keys.length) { } else if (keys && keys.length) {
symAlgo = enums.read(enums.symmetric, await getPreferredSymAlgo(keys)); symAlgo = enums.read(enums.symmetric, await getPreferredSymAlgo(keys, date));
aeadAlgo = enums.read(enums.aead, await getPreferredAeadAlgo(keys)); aeadAlgo = await getPreferredAeadAlgo(keys, date);
if (aeadAlgo) {
aeadAlgo = enums.read(enums.aead, aeadAlgo);
}
} else if (passwords && passwords.length) { } else if (passwords && passwords.length) {
symAlgo = enums.read(enums.symmetric, config.encryption_cipher); symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
aeadAlgo = enums.read(enums.aead, config.aead_mode); aeadAlgo = enums.read(enums.aead, config.aead_mode);
@ -278,7 +281,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date); const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date);
if (config.aead_protect) { if (config.aead_protect && (config.aead_protect_version !== 4 || aeadAlgo)) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected(); symEncryptedPacket = new packet.SymEncryptedAEADProtected();
symEncryptedPacket.aeadAlgorithm = aeadAlgo; symEncryptedPacket.aeadAlgorithm = aeadAlgo;
} else if (config.integrity_protect) { } else if (config.integrity_protect) {
@ -423,7 +426,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new
} }
const onePassSig = new packet.OnePassSignature(); const onePassSig = new packet.OnePassSignature();
onePassSig.type = signatureType; onePassSig.type = signatureType;
onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey); onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, date);
onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm; onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm;
onePassSig.signingKeyId = signingKeyPacket.getKeyId(); onePassSig.signingKeyId = signingKeyPacket.getKeyId();
if (i === privateKeys.length - 1) { if (i === privateKeys.length - 1) {
@ -507,7 +510,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
const signaturePacket = new packet.Signature(date); const signaturePacket = new packet.Signature(date);
signaturePacket.signatureType = signatureType; signaturePacket.signatureType = signatureType;
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey); signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, date);
await signaturePacket.sign(signingKeyPacket, literalDataPacket); await signaturePacket.sign(signingKeyPacket, literalDataPacket);
return signaturePacket; return signaturePacket;
})).then(signatureList => { })).then(signatureList => {

View File

@ -1199,6 +1199,7 @@ p92yZgB3r2+f6/GIe2+7
it('getPreferredAeadAlgo() - one key - OCB', async function() { it('getPreferredAeadAlgo() - one key - OCB', async function() {
const key1 = openpgp.key.readArmored(twoKeys).keys[0]; const key1 = openpgp.key.readArmored(twoKeys).keys[0];
const primaryUser = await key1.getPrimaryUser(); const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [7]; // Monkey-patch AEAD feature flag
primaryUser.selfCertification.preferredAeadAlgorithms = [2,1]; primaryUser.selfCertification.preferredAeadAlgorithms = [2,1];
const prefAlgo = await openpgp.key.getPreferredAeadAlgo([key1]); const prefAlgo = await openpgp.key.getPreferredAeadAlgo([key1]);
expect(prefAlgo).to.equal(openpgp.enums.aead.ocb); expect(prefAlgo).to.equal(openpgp.enums.aead.ocb);
@ -1209,11 +1210,25 @@ p92yZgB3r2+f6/GIe2+7
const key1 = keys[0]; const key1 = keys[0];
const key2 = keys[1]; const key2 = keys[1];
const primaryUser = await key1.getPrimaryUser(); const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [7]; // Monkey-patch AEAD feature flag
primaryUser.selfCertification.preferredAeadAlgorithms = [2,1]; primaryUser.selfCertification.preferredAeadAlgorithms = [2,1];
const primaryUser2 = await key2.getPrimaryUser();
primaryUser2.selfCertification.features = [7]; // Monkey-patch AEAD feature flag
const prefAlgo = await openpgp.key.getPreferredAeadAlgo([key1, key2]); const prefAlgo = await openpgp.key.getPreferredAeadAlgo([key1, key2]);
expect(prefAlgo).to.equal(openpgp.config.aead_mode); expect(prefAlgo).to.equal(openpgp.config.aead_mode);
}); });
it('getPreferredAeadAlgo() - two key - one with no support', async function() {
const keys = openpgp.key.readArmored(twoKeys).keys;
const key1 = keys[0];
const key2 = keys[1];
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [7]; // Monkey-patch AEAD feature flag
primaryUser.selfCertification.preferredAeadAlgorithms = [2,1];
const prefAlgo = await openpgp.key.getPreferredAeadAlgo([key1, key2]);
expect(prefAlgo).to.be.null;
});
it('Preferences of generated key', function() { it('Preferences of generated key', function() {
const testPref = function(key) { const testPref = function(key) {
// key flags // key flags

View File

@ -604,6 +604,7 @@ describe('OpenPGP.js public api tests', function() {
publicKey = openpgp.key.readArmored(pub_key); publicKey = openpgp.key.readArmored(pub_key);
expect(publicKey.keys).to.have.length(1); expect(publicKey.keys).to.have.length(1);
expect(publicKey.err).to.not.exist; expect(publicKey.err).to.not.exist;
publicKeyNoAEAD = openpgp.key.readArmored(pub_key);
privateKey = openpgp.key.readArmored(priv_key); privateKey = openpgp.key.readArmored(priv_key);
expect(privateKey.keys).to.have.length(1); expect(privateKey.keys).to.have.length(1);
expect(privateKey.err).to.not.exist; expect(privateKey.err).to.not.exist;
@ -679,6 +680,11 @@ describe('OpenPGP.js public api tests', function() {
openpgp.config.use_native = false; openpgp.config.use_native = false;
openpgp.config.aead_protect = true; openpgp.config.aead_protect = true;
openpgp.config.aead_protect_version = 4; openpgp.config.aead_protect_version = 4;
// Monkey-patch AEAD feature flag
publicKey.keys[0].users[0].selfCertifications[0].features = [7];
publicKey_2000_2008.keys[0].users[0].selfCertifications[0].features = [7];
publicKey_2038_2045.keys[0].users[0].selfCertifications[0].features = [7];
} }
}); });
@ -688,6 +694,11 @@ describe('OpenPGP.js public api tests', function() {
openpgp.config.use_native = true; openpgp.config.use_native = true;
openpgp.config.aead_protect = true; openpgp.config.aead_protect = true;
openpgp.config.aead_protect_version = 4; openpgp.config.aead_protect_version = 4;
// Monkey-patch AEAD feature flag
publicKey.keys[0].users[0].selfCertifications[0].features = [7];
publicKey_2000_2008.keys[0].users[0].selfCertifications[0].features = [7];
publicKey_2038_2045.keys[0].users[0].selfCertifications[0].features = [7];
} }
}); });
@ -697,6 +708,11 @@ describe('OpenPGP.js public api tests', function() {
openpgp.config.aead_protect = true; openpgp.config.aead_protect = true;
openpgp.config.aead_protect_version = 4; openpgp.config.aead_protect_version = 4;
openpgp.config.aead_mode = openpgp.enums.aead.ocb; openpgp.config.aead_mode = openpgp.enums.aead.ocb;
// Monkey-patch AEAD feature flag
publicKey.keys[0].users[0].selfCertifications[0].features = [7];
publicKey_2000_2008.keys[0].users[0].selfCertifications[0].features = [7];
publicKey_2038_2045.keys[0].users[0].selfCertifications[0].features = [7];
} }
}); });
@ -1020,20 +1036,21 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.encrypt(encOpt).then(function (encrypted) { return openpgp.encrypt(encOpt).then(function (encrypted) {
expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/);
decOpt.message = openpgp.message.readArmored(encrypted.data); decOpt.message = openpgp.message.readArmored(encrypted.data);
expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect && openpgp.config.aead_protect_version !== 4);
return openpgp.decrypt(decOpt); return openpgp.decrypt(decOpt);
}).then(function (decrypted) { }).then(function (decrypted) {
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
}); });
}); });
it('should encrypt using custom session key and decrypt using private key', function () { it('should encrypt using custom session key and decrypt using private key', async function () {
const sessionKey = { const sessionKey = {
data: openpgp.crypto.generateSessionKey('aes128'), data: await openpgp.crypto.generateSessionKey('aes128'),
algorithm: 'aes128' algorithm: 'aes128'
}; };
const encOpt = { const encOpt = {
data: plaintext, data: plaintext,
sessionKeys: sessionKey, sessionKey: sessionKey,
publicKeys: publicKey.keys publicKeys: publicKey.keys
}; };
const decOpt = { const decOpt = {
@ -1042,6 +1059,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.encrypt(encOpt).then(function (encrypted) { return openpgp.encrypt(encOpt).then(function (encrypted) {
expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/);
decOpt.message = openpgp.message.readArmored(encrypted.data); decOpt.message = openpgp.message.readArmored(encrypted.data);
expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect && openpgp.config.aead_protect_version !== 4);
return openpgp.decrypt(decOpt); return openpgp.decrypt(decOpt);
}).then(function (decrypted) { }).then(function (decrypted) {
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
@ -1060,6 +1078,7 @@ describe('OpenPGP.js public api tests', function() {
}; };
return openpgp.encrypt(encOpt).then(function (encrypted) { return openpgp.encrypt(encOpt).then(function (encrypted) {
decOpt.message = openpgp.message.readArmored(encrypted.data); decOpt.message = openpgp.message.readArmored(encrypted.data);
expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect);
return openpgp.decrypt(decOpt); return openpgp.decrypt(decOpt);
}).then(async function (decrypted) { }).then(async function (decrypted) {
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
@ -1070,6 +1089,63 @@ describe('OpenPGP.js public api tests', function() {
}); });
}); });
it('should encrypt/sign and decrypt/verify (no AEAD support)', function () {
const encOpt = {
data: plaintext,
publicKeys: publicKeyNoAEAD.keys,
privateKeys: privateKey.keys
};
const decOpt = {
privateKeys: privateKey.keys[0],
publicKeys: publicKeyNoAEAD.keys
};
return openpgp.encrypt(encOpt).then(function (encrypted) {
decOpt.message = openpgp.message.readArmored(encrypted.data);
expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect && openpgp.config.aead_protect_version !== 4);
return openpgp.decrypt(decOpt);
}).then(async function (decrypted) {
expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures[0].valid).to.be.true;
const keyPacket = await privateKey.keys[0].getSigningKeyPacket();
expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex());
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
});
});
it('should encrypt/sign and decrypt/verify with generated key', function () {
const genOpt = {
userIds: [{ name: 'Test User', email: 'text@example.com' }],
numBits: 512
};
if (openpgp.util.getWebCryptoAll()) { genOpt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
return openpgp.generateKey(genOpt).then(function(newKey) {
const newPublicKey = openpgp.key.readArmored(newKey.publicKeyArmored);
const newPrivateKey = openpgp.key.readArmored(newKey.privateKeyArmored);
const encOpt = {
data: plaintext,
publicKeys: newPublicKey.keys,
privateKeys: newPrivateKey.keys
};
const decOpt = {
privateKeys: newPrivateKey.keys[0],
publicKeys: newPublicKey.keys
};
return openpgp.encrypt(encOpt).then(function (encrypted) {
decOpt.message = openpgp.message.readArmored(encrypted.data);
expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect);
return openpgp.decrypt(decOpt);
}).then(async function (decrypted) {
expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures[0].valid).to.be.true;
const keyPacket = await newPrivateKey.keys[0].getSigningKeyPacket();
expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex());
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
});
});
});
it('should encrypt/sign and decrypt/verify with null string input', function () { it('should encrypt/sign and decrypt/verify with null string input', function () {
const encOpt = { const encOpt = {
data: '', data: '',
@ -1719,6 +1795,7 @@ describe('OpenPGP.js public api tests', function() {
const pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0]; const pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0];
const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0];
await privKeyDE.decrypt(passphrase); await privKeyDE.decrypt(passphrase);
pubKeyDE.users[0].selfCertifications[0].features = [7]; // Monkey-patch AEAD feature flag
return openpgp.encrypt({ return openpgp.encrypt({
publicKeys: pubKeyDE, publicKeys: pubKeyDE,
privateKeys: privKeyDE, privateKeys: privKeyDE,