From 5f97a8c9377394d681b3094a2bbac9dadd954d82 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Mon, 9 Apr 2018 18:34:24 +0200 Subject: [PATCH] Implement preferred AEAD algorithms --- src/config/config.js | 7 ++++ src/enums.js | 3 +- src/key.js | 37 ++++++++++++++++++++++ src/message.js | 22 +++++++++---- src/openpgp.js | 7 ++-- src/packet/signature.js | 9 ++++++ src/packet/sym_encrypted_aead_protected.js | 3 +- src/packet/sym_encrypted_session_key.js | 2 +- test/general/key.js | 22 +++++++++++++ 9 files changed, 100 insertions(+), 12 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 1373430d..e2cfef90 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -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. diff --git a/src/enums.js b/src/enums.js index ab9451a6..62297ca1 100644 --- a/src/enums.js +++ b/src/enums.js @@ -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 diff --git a/src/key.js b/src/key.js index 5f21d9f0..c6784f0e 100644 --- a/src/key.js +++ b/src/key.js @@ -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} keys Set of keys + * @returns {Promise} 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; +} diff --git a/src/message.js b/src/message.js index f0e1211f..57f43ba4 100644 --- a/src/message.js +++ b/src/message.js @@ -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} publicKeys (optional) public key(s) for message encryption * @param {Array} 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} 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); } diff --git a/src/openpgp.js b/src/openpgp.js index 4b32fdf0..fbbb4148 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -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} publicKeys (optional) array of public keys or single key, used to encrypt the key * @param {String|Array} 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')); } diff --git a/src/packet/signature.js b/src/packet/signature.js index 1b629cce..e3210e01 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -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); } diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index 1749e945..766a3bb8 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -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(); diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index eea69222..9e4d6de0 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -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; diff --git a/test/general/key.js b/test/general/key.js index 3248edcd..eb396e38 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -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;