Implement preferred AEAD algorithms

This commit is contained in:
Daniel Huigens 2018-04-09 18:34:24 +02:00
parent 93f75f398f
commit 5f97a8c937
9 changed files with 100 additions and 12 deletions

View File

@ -51,6 +51,13 @@ export default {
* @property {Boolean} aead_protect
*/
aead_protect: false,
/**
* Default Authenticated Encryption with Additional Data (AEAD) encryption mode
* Only has an effect when aead_protect is set to true.
* @memberof module:config
* @property {Integer} aead_mode Default AEAD mode {@link module:enums.aead}
*/
aead_mode: enums.aead.eax,
/**
* Chunk Size Byte for Authenticated Encryption with Additional Data (AEAD) mode
* Only has an effect when aead_protect is set to true.

View File

@ -366,7 +366,8 @@ export default {
reason_for_revocation: 29,
features: 30,
signature_target: 31,
embedded_signature: 32
embedded_signature: 32,
preferred_aead_algorithms: 34
},
/** Key flags

View File

@ -1261,6 +1261,11 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) {
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes192);
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.cast5);
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.tripledes);
if (config.aead_protect === 'draft04') {
signaturePacket.preferredAeadAlgorithms = [];
signaturePacket.preferredAeadAlgorithms.push(enums.aead.eax);
signaturePacket.preferredAeadAlgorithms.push(enums.aead.ocb);
}
signaturePacket.preferredHashAlgorithms = [];
// prefer fast asm.js implementations (SHA-256). SHA-1 will not be secure much longer...move to bottom of list
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha256);
@ -1457,3 +1462,35 @@ export async function getPreferredSymAlgo(keys) {
}
return prefAlgo.algo;
}
/**
* 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
* @async
*/
export async function getPreferredAeadAlgo(keys) {
const prioMap = {};
await Promise.all(keys.map(async function(key) {
const primaryUser = await key.getPrimaryUser();
if (!primaryUser || !primaryUser.selfCertification.preferredAeadAlgorithms) {
return config.aead_mode;
}
primaryUser.selfCertification.preferredAeadAlgorithms.forEach(function(algo, index) {
const entry = prioMap[algo] || (prioMap[algo] = { prio: 0, count: 0, algo: algo });
entry.prio += 64 >> index;
entry.count++;
});
}));
let prefAlgo = { prio: 0, algo: config.aead_mode };
for (const algo in prioMap) {
try {
if (enums.read(enums.aead, algo) && // known algorithm
prioMap[algo].count === keys.length && // available for all keys
prioMap[algo].prio > prefAlgo.prio) {
prefAlgo = prioMap[algo];
}
} catch (e) {}
}
return prefAlgo.algo;
}

View File

@ -36,7 +36,7 @@ import enums from './enums';
import util from './util';
import packet from './packet';
import { Signature } from './signature';
import { getPreferredHashAlgo, getPreferredSymAlgo } from './key';
import { getPreferredHashAlgo, getPreferredSymAlgo, getPreferredAeadAlgo } from './key';
/**
@ -252,6 +252,7 @@ Message.prototype.getText = function() {
*/
Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date()) {
let symAlgo;
let aeadAlgo;
let symEncryptedPacket;
if (sessionKey) {
@ -259,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;
sessionKey = sessionKey.data;
} else if (keys && keys.length) {
symAlgo = enums.read(enums.symmetric, await getPreferredSymAlgo(keys));
aeadAlgo = enums.read(enums.aead, await getPreferredAeadAlgo(keys));
} else if (passwords && passwords.length) {
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
aeadAlgo = enums.read(enums.aead, config.aead_mode);
} else {
throw new Error('No keys, passwords, or session key provided.');
}
@ -272,10 +276,11 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
sessionKey = await crypto.generateSessionKey(symAlgo);
}
const msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords, wildcard, date);
const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date);
if (config.aead_protect) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
symEncryptedPacket.aeadAlgorithm = aeadAlgo;
} else if (config.integrity_protect) {
symEncryptedPacket = new packet.SymEncryptedIntegrityProtected();
} else {
@ -291,7 +296,8 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
message: msg,
sessionKey: {
data: sessionKey,
algorithm: symAlgo
algorithm: symAlgo,
aeadAlgorithm: aeadAlgo
}
};
};
@ -300,6 +306,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
* Encrypt a session key either with public keys, passwords, or both at once.
* @param {Uint8Array} sessionKey session key for encryption
* @param {String} symAlgo session key algorithm
* @param {String} aeadAlgo (optional) aead algorithm, e.g. 'eax' or 'ocb'
* @param {Array<Key>} publicKeys (optional) public key(s) for message encryption
* @param {Array<String>} passwords (optional) for message encryption
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
@ -307,7 +314,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
* @returns {Promise<Message>} new message with encrypted content
* @async
*/
export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false, date=new Date()) {
export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKeys, passwords, wildcard=false, date=new Date()) {
const packetlist = new packet.List();
if (publicKeys) {
@ -340,10 +347,13 @@ export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwor
const sum = (accumulator, currentValue) => accumulator + currentValue;
const encryptPassword = async function(sessionKey, symAlgo, password) {
const encryptPassword = async function(sessionKey, symAlgo, aeadAlgo, password) {
const symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey();
symEncryptedSessionKeyPacket.sessionKey = sessionKey;
symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo;
if (aeadAlgo) {
symEncryptedSessionKeyPacket.aeadAlgorithm = aeadAlgo;
}
await symEncryptedSessionKeyPacket.encrypt(password);
if (config.password_collision_check) {
@ -357,7 +367,7 @@ export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwor
return symEncryptedSessionKeyPacket;
};
const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, symAlgo, pwd)));
const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, symAlgo, aeadAlgo, pwd)));
packetlist.concat(results);
}

View File

@ -395,6 +395,7 @@ export function verify({ message, publicKeys, signature=null, date=new Date() })
* or passwords must be specified.
* @param {Uint8Array} data the session key to be encrypted e.g. 16 random bytes (for aes128)
* @param {String} algorithm algorithm of the symmetric session key e.g. 'aes128' or 'aes256'
* @param {String} aeadAlgorithm (optional) aead algorithm, e.g. 'eax' or 'ocb'
* @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, used to encrypt the key
* @param {String|Array<String>} passwords (optional) passwords for the message
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
@ -402,16 +403,16 @@ export function verify({ message, publicKeys, signature=null, date=new Date() })
* @async
* @static
*/
export function encryptSessionKey({ data, algorithm, publicKeys, passwords, wildcard=false }) {
export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard=false }) {
checkBinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords);
if (asyncProxy) { // use web worker if available
return asyncProxy.delegate('encryptSessionKey', { data, algorithm, publicKeys, passwords, wildcard });
return asyncProxy.delegate('encryptSessionKey', { data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard });
}
return Promise.resolve().then(async function() {
return { message: await messageLib.encryptSessionKey(data, algorithm, publicKeys, passwords, wildcard) };
return { message: await messageLib.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard) };
}).catch(onError.bind(null, 'Error encrypting session key'));
}

View File

@ -84,6 +84,7 @@ function Signature(date=new Date()) {
this.signatureTargetHashAlgorithm = null;
this.signatureTargetHash = null;
this.embeddedSignature = null;
this.preferredAeadAlgorithms = null;
this.verified = null;
this.revoked = null;
@ -355,6 +356,10 @@ Signature.prototype.write_all_sub_packets = function () {
if (this.embeddedSignature !== null) {
arr.push(write_sub_packet(sub.embedded_signature, this.embeddedSignature.write()));
}
if (this.preferredAeadAlgorithms !== null) {
bytes = util.str_to_Uint8Array(util.Uint8Array_to_str(this.preferredAeadAlgorithms));
arr.push(write_sub_packet(sub.preferred_aead_algorithms, bytes));
}
const result = util.concatUint8Array(arr);
const length = util.writeNumber(result.length, 2);
@ -531,6 +536,10 @@ Signature.prototype.read_sub_packet = function (bytes) {
this.embeddedSignature = new Signature();
this.embeddedSignature.read(bytes.subarray(mypos, bytes.length));
break;
case 34:
// Preferred AEAD Algorithms
read_array.call(this, 'preferredAeadAlgorithms', bytes.subarray(mypos, bytes.length));
break;
default:
util.print_debug("Unknown signature subpacket type " + type + " @:" + mypos);
}

View File

@ -42,6 +42,7 @@ function SymEncryptedAEADProtected() {
this.tag = enums.packet.symEncryptedAEADProtected;
this.version = VERSION;
this.cipherAlgo = null;
this.aeadAlgorithm = 'eax';
this.aeadAlgo = null;
this.chunkSizeByte = null;
this.iv = null;
@ -131,7 +132,7 @@ SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorith
* @async
*/
SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) {
this.aeadAlgo = config.aead_protect === 'draft04' ? enums.aead.eax : enums.aead.gcm;
this.aeadAlgo = config.aead_protect === 'draft04' ? enums.write(enums.aead, this.aeadAlgorithm) : enums.aead.gcm;
const mode = crypto[enums.read(enums.aead, this.aeadAlgo)];
this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV
let data = this.packets.write();

View File

@ -53,7 +53,7 @@ function SymEncryptedSessionKey() {
this.sessionKey = null;
this.sessionKeyEncryptionAlgorithm = null;
this.sessionKeyAlgorithm = 'aes256';
this.aeadAlgorithm = 'eax';
this.aeadAlgorithm = enums.read(enums.aead, config.aead_mode);
this.encrypted = null;
this.s2k = null;
this.iv = null;

View File

@ -1192,6 +1192,24 @@ p92yZgB3r2+f6/GIe2+7
expect(prefAlgo).to.equal(openpgp.config.encryption_cipher);
});
it('getPreferredAeadAlgo() - one key - OCB', async function() {
const key1 = openpgp.key.readArmored(twoKeys).keys[0];
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.preferredAeadAlgorithms = [2,1];
const prefAlgo = await openpgp.key.getPreferredAeadAlgo([key1]);
expect(prefAlgo).to.equal(openpgp.enums.aead.ocb);
});
it('getPreferredAeadAlgo() - two key - one without pref', async function() {
const keys = openpgp.key.readArmored(twoKeys).keys;
const key1 = keys[0];
const key2 = keys[1];
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.preferredAeadAlgorithms = [2,1];
const prefAlgo = await openpgp.key.getPreferredAeadAlgo([key1, key2]);
expect(prefAlgo).to.equal(openpgp.config.aead_mode);
});
it('Preferences of generated key', function() {
const testPref = function(key) {
// key flags
@ -1202,6 +1220,10 @@ p92yZgB3r2+f6/GIe2+7
expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_storage).to.equal(keyFlags.encrypt_storage);
const sym = openpgp.enums.symmetric;
expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes256, sym.aes128, sym.aes192, sym.cast5, sym.tripledes]);
if (openpgp.config.aead_protect === 'draft04') {
const aead = openpgp.enums.aead;
expect(key.users[0].selfCertifications[0].preferredAeadAlgorithms).to.eql([aead.eax, aead.ocb]);
}
const hash = openpgp.enums.hash;
expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha256, hash.sha512, hash.sha1]);
const compr = openpgp.enums.compression;