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
* @param {object} key
* @param {Date} date (optional) use the given date for verification instead of the current time
* @returns {Promise<String>}
* @async
*/
export async function getPreferredHashAlgo(key) {
export async function getPreferredHashAlgo(key, date) {
let hash_algo = config.prefer_hash_algorithm;
let pref_algo = hash_algo;
if (key instanceof Key) {
const primaryUser = await key.getPrimaryUser();
const primaryUser = await key.getPrimaryUser(date);
if (primaryUser && primaryUser.selfCertification.preferredHashAlgorithms) {
[pref_algo] = primaryUser.selfCertification.preferredHashAlgorithms;
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
* @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
* @async
*/
export async function getPreferredSymAlgo(keys) {
export async function getPreferredSymAlgo(keys, date) {
const prioMap = {};
await Promise.all(keys.map(async function(key) {
const primaryUser = await key.getPrimaryUser();
const primaryUser = await key.getPrimaryUser(date);
if (!primaryUser || !primaryUser.selfCertification.preferredSymmetricAlgorithms) {
return config.encryption_cipher;
}
@ -1471,13 +1473,20 @@ export async function getPreferredSymAlgo(keys) {
/**
* Returns the preferred aead algorithm for a 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
*/
export async function getPreferredAeadAlgo(keys) {
export async function getPreferredAeadAlgo(keys, date) {
let supports_aead = true;
const prioMap = {};
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) {
return config.aead_mode;
}
@ -1487,6 +1496,9 @@ export async function getPreferredAeadAlgo(keys) {
entry.count++;
});
}));
if (!supports_aead) {
return null;
}
let prefAlgo = { prio: 0, algo: config.aead_mode };
for (const algo in prioMap) {
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.
* @param {Array<Key>} privateKeys (optional) private keys with decrypted secret data
* @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
* @async
*/
@ -244,7 +244,7 @@ Message.prototype.getText = function() {
* 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<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 {Date} date (optional) override the creation date of the literal package
* @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.');
}
symAlgo = sessionKey.algorithm;
aeadAlgo = sessionKey.aeadAlgorithm || config.aead_mode;
aeadAlgo = sessionKey.aeadAlgorithm;
sessionKey = sessionKey.data;
} else if (keys && keys.length) {
symAlgo = enums.read(enums.symmetric, await getPreferredSymAlgo(keys));
aeadAlgo = enums.read(enums.aead, await getPreferredAeadAlgo(keys));
symAlgo = enums.read(enums.symmetric, await getPreferredSymAlgo(keys, date));
aeadAlgo = await getPreferredAeadAlgo(keys, date);
if (aeadAlgo) {
aeadAlgo = enums.read(enums.aead, aeadAlgo);
}
} else if (passwords && passwords.length) {
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
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);
if (config.aead_protect) {
if (config.aead_protect && (config.aead_protect_version !== 4 || aeadAlgo)) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
symEncryptedPacket.aeadAlgorithm = aeadAlgo;
} else if (config.integrity_protect) {
@ -423,7 +426,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new
}
const onePassSig = new packet.OnePassSignature();
onePassSig.type = signatureType;
onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey);
onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, date);
onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm;
onePassSig.signingKeyId = signingKeyPacket.getKeyId();
if (i === privateKeys.length - 1) {
@ -507,7 +510,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
const signaturePacket = new packet.Signature(date);
signaturePacket.signatureType = signatureType;
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey);
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, date);
await signaturePacket.sign(signingKeyPacket, literalDataPacket);
return signaturePacket;
})).then(signatureList => {

View File

@ -1199,6 +1199,7 @@ p92yZgB3r2+f6/GIe2+7
it('getPreferredAeadAlgo() - one key - OCB', async function() {
const key1 = openpgp.key.readArmored(twoKeys).keys[0];
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]);
expect(prefAlgo).to.equal(openpgp.enums.aead.ocb);
@ -1209,11 +1210,25 @@ p92yZgB3r2+f6/GIe2+7
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 primaryUser2 = await key2.getPrimaryUser();
primaryUser2.selfCertification.features = [7]; // Monkey-patch AEAD feature flag
const prefAlgo = await openpgp.key.getPreferredAeadAlgo([key1, key2]);
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() {
const testPref = function(key) {
// key flags

View File

@ -604,6 +604,7 @@ describe('OpenPGP.js public api tests', function() {
publicKey = openpgp.key.readArmored(pub_key);
expect(publicKey.keys).to.have.length(1);
expect(publicKey.err).to.not.exist;
publicKeyNoAEAD = openpgp.key.readArmored(pub_key);
privateKey = openpgp.key.readArmored(priv_key);
expect(privateKey.keys).to.have.length(1);
expect(privateKey.err).to.not.exist;
@ -679,6 +680,11 @@ describe('OpenPGP.js public api tests', function() {
openpgp.config.use_native = false;
openpgp.config.aead_protect = true;
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.aead_protect = true;
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_version = 4;
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) {
expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/);
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(function (decrypted) {
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 = {
data: openpgp.crypto.generateSessionKey('aes128'),
data: await openpgp.crypto.generateSessionKey('aes128'),
algorithm: 'aes128'
};
const encOpt = {
data: plaintext,
sessionKeys: sessionKey,
sessionKey: sessionKey,
publicKeys: publicKey.keys
};
const decOpt = {
@ -1042,6 +1059,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.encrypt(encOpt).then(function (encrypted) {
expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/);
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(function (decrypted) {
expect(decrypted.data).to.equal(plaintext);
@ -1060,6 +1078,7 @@ describe('OpenPGP.js public api tests', function() {
};
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);
@ -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 () {
const encOpt = {
data: '',
@ -1719,6 +1795,7 @@ describe('OpenPGP.js public api tests', function() {
const pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0];
const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0];
await privKeyDE.decrypt(passphrase);
pubKeyDE.users[0].selfCertifications[0].features = [7]; // Monkey-patch AEAD feature flag
return openpgp.encrypt({
publicKeys: pubKeyDE,
privateKeys: privKeyDE,