Implement AEAD Encrypted Data Packet

This commit is contained in:
Daniel Huigens 2018-03-31 21:45:23 +02:00
parent 85a1b9859b
commit 7b3f51c0d4
5 changed files with 189 additions and 6 deletions

View File

@ -51,6 +51,14 @@ export default {
* @property {Boolean} aead_protect
*/
aead_protect: false,
/**
* Chunk Size Byte for Authenticated Encryption with Additional Data (AEAD) mode
* Only has an effect when aead_protect is set to true.
* Must be an integer value from 0 to 56.
* @memberof module:config
* @property {Integer} aead_chunk_size_byte
*/
aead_chunk_size_byte: 46,
/** Use integrity protection for symmetric encryption
* @memberof module:config
* @property {Boolean} integrity_protect

View File

@ -167,6 +167,16 @@ export default {
'SHA-512': 10
},
/** {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-9.6|RFC4880bis-04, section 9.6}
* @enum {Integer}
* @readonly
*/
aead: {
eax: 1,
ocb: 2,
gcm: 100 // Private algorithm
},
/** A list of packet types and numeric tags associated with them.
* @enum {Integer}
* @readonly

View File

@ -16,17 +16,18 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
/**
* @requires config
* @requires crypto
* @requires enums
* @requires util
*/
import config from '../config';
import crypto from '../crypto';
import enums from '../enums';
import util from '../util';
const VERSION = 1; // A one-octet version number of the data packet.
const IV_LEN = crypto.gcm.ivLength; // currently only AES-GCM is supported
/**
* Implementation of the Symmetrically Encrypted Authenticated Encryption with
@ -40,6 +41,9 @@ const IV_LEN = crypto.gcm.ivLength; // currently only AES-GCM is supported
function SymEncryptedAEADProtected() {
this.tag = enums.packet.symEncryptedAEADProtected;
this.version = VERSION;
this.cipherAlgo = null;
this.aeadAlgo = null;
this.chunkSizeByte = null;
this.iv = null;
this.encrypted = null;
this.packets = null;
@ -56,8 +60,16 @@ SymEncryptedAEADProtected.prototype.read = function (bytes) {
throw new Error('Invalid packet version.');
}
offset++;
this.iv = bytes.subarray(offset, IV_LEN + offset);
offset += IV_LEN;
if (config.aead_protect === 'draft04') {
this.cipherAlgo = bytes[offset++];
this.aeadAlgo = bytes[offset++];
this.chunkSizeByte = bytes[offset++];
} else {
this.aeadAlgo = enums.aead.gcm;
}
const mode = crypto[enums.read(enums.aead, this.aeadAlgo)];
this.iv = bytes.subarray(offset, mode.ivLength + offset);
offset += mode.ivLength;
this.encrypted = bytes.subarray(offset, bytes.length);
};
@ -66,6 +78,9 @@ SymEncryptedAEADProtected.prototype.read = function (bytes) {
* @returns {Uint8Array} The encrypted payload
*/
SymEncryptedAEADProtected.prototype.write = function () {
if (config.aead_protect === 'draft04') {
return util.concatUint8Array([new Uint8Array([this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte]), this.iv, this.encrypted]);
}
return util.concatUint8Array([new Uint8Array([this.version]), this.iv, this.encrypted]);
};
@ -77,7 +92,34 @@ SymEncryptedAEADProtected.prototype.write = function () {
* @async
*/
SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key) {
this.packets.read(await crypto.gcm.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv));
const mode = crypto[enums.read(enums.aead, this.aeadAlgo)];
if (config.aead_protect === 'draft04') {
const cipher = enums.read(enums.symmetric, this.cipherAlgo);
let data = this.encrypted.subarray(0, this.encrypted.length - mode.blockLength);
const authTag = this.encrypted.subarray(this.encrypted.length - mode.blockLength);
const chunkSize = 2 ** (this.chunkSizeByte + 6); // ((uint64_t)1 << (c + 6))
const adataBuffer = new ArrayBuffer(21);
const adataArray = new Uint8Array(adataBuffer, 0, 13);
const adataTagArray = new Uint8Array(adataBuffer);
const adataView = new DataView(adataBuffer);
const chunkIndexArray = new Uint8Array(adataBuffer, 5, 8);
adataArray.set([0xC0 | this.tag, this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte], 0);
adataView.setInt32(13 + 4, data.length - mode.blockLength); // Should be setInt64(13, ...)
const decryptedPromises = [];
for (let chunkIndex = 0; chunkIndex === 0 || data.length;) {
decryptedPromises.push(
mode.decrypt(cipher, data.subarray(0, chunkSize), key, mode.getNonce(this.iv, chunkIndexArray), adataArray)
);
data = data.subarray(chunkSize);
adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...)
}
decryptedPromises.push(
mode.decrypt(cipher, authTag, key, mode.getNonce(this.iv, chunkIndexArray), adataTagArray)
);
this.packets.read(util.concatUint8Array(await Promise.all(decryptedPromises)));
} else {
this.packets.read(await mode.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv));
}
return true;
};
@ -89,7 +131,38 @@ SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorith
* @async
*/
SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) {
this.iv = await crypto.random.getRandomBytes(IV_LEN); // generate new random IV
this.encrypted = await crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv);
this.aeadAlgo = config.aead_protect === 'draft04' ? enums.aead.eax : 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();
if (config.aead_protect === 'draft04') {
this.cipherAlgo = enums.write(enums.symmetric, sessionKeyAlgorithm);
this.chunkSizeByte = config.aead_chunk_size_byte;
const chunkSize = 2 ** (this.chunkSizeByte + 6); // ((uint64_t)1 << (c + 6))
const adataBuffer = new ArrayBuffer(21);
const adataArray = new Uint8Array(adataBuffer, 0, 13);
const adataTagArray = new Uint8Array(adataBuffer);
const adataView = new DataView(adataBuffer);
const chunkIndexArray = new Uint8Array(adataBuffer, 5, 8);
adataArray.set([0xC0 | this.tag, this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte], 0);
adataView.setInt32(13 + 4, data.length); // Should be setInt64(13, ...)
const encryptedPromises = [];
for (let chunkIndex = 0; chunkIndex === 0 || data.length;) {
encryptedPromises.push(
mode.encrypt(sessionKeyAlgorithm, data.subarray(0, chunkSize), key, mode.getNonce(this.iv, chunkIndexArray), adataArray)
);
// We take a chunk of data, encrypt it, and shift `data` to the
// next chunk. After the final chunk, we encrypt a final, empty
// data chunk to get the final authentication tag.
data = data.subarray(chunkSize);
adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...)
}
encryptedPromises.push(
mode.encrypt(sessionKeyAlgorithm, data, key, mode.getNonce(this.iv, chunkIndexArray), adataTagArray)
);
this.encrypted = util.concatUint8Array(await Promise.all(encryptedPromises));
} else {
this.encrypted = await mode.encrypt(sessionKeyAlgorithm, data, key, this.iv);
}
return true;
};

View File

@ -667,6 +667,22 @@ describe('OpenPGP.js public api tests', function() {
}
});
tryTests('EAX mode (asm.js)', tests, {
if: true,
beforeEach: function() {
openpgp.config.use_native = false;
openpgp.config.aead_protect = 'draft04';
}
});
tryTests('EAX mode (native)', tests, {
if: openpgp.util.getWebCryptoAll() || openpgp.util.getNodeCrypto(),
beforeEach: function() {
openpgp.config.use_native = true;
openpgp.config.aead_protect = 'draft04';
}
});
function tests() {
it('Configuration', function() {
openpgp.config.show_version = false;

View File

@ -1,5 +1,6 @@
const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp');
const stub = require('sinon/lib/sinon/stub');
const chai = require('chai');
chai.use(require('chai-as-promised'));
@ -141,6 +142,81 @@ describe("Packet", function() {
});
});
it('Sym. encrypted AEAD protected packet (draft04)', function() {
let aead_protectVal = openpgp.config.aead_protect;
openpgp.config.aead_protect = 'draft04';
const key = new Uint8Array([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]);
const algo = 'aes256';
const literal = new openpgp.packet.Literal();
const enc = new openpgp.packet.SymEncryptedAEADProtected();
const msg = new openpgp.packet.List();
msg.push(enc);
literal.setText('Hello world!');
enc.packets.push(literal);
const msg2 = new openpgp.packet.List();
return enc.encrypt(algo, key).then(function() {
msg2.read(msg.write());
return msg2[0].decrypt(algo, key);
}).then(function() {
expect(msg2[0].packets[0].data).to.deep.equal(literal.data);
}).finally(function() {
openpgp.config.aead_protect = aead_protectVal;
});
});
it('Sym. encrypted AEAD protected packet test vector (draft04)', function() {
// From https://gitlab.com/openpgp-wg/rfc4880bis/commit/00b20923e6233fb6ff1666ecd5acfefceb32907d
let packetBytes = openpgp.util.hex_to_Uint8Array(`
d4 4a 01 07 01 0e b7 32 37 9f 73 c4 92 8d e2 5f
ac fe 65 17 ec 10 5d c1 1a 81 dc 0c b8 a2 f6 f3
d9 00 16 38 4a 56 fc 82 1a e1 1a e8 db cb 49 86
26 55 de a8 8d 06 a8 14 86 80 1b 0f f3 87 bd 2e
ab 01 3d e1 25 95 86 90 6e ab 24 76
`.replace(/\s+/g, ''));
let aead_protectVal = openpgp.config.aead_protect;
let aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte;
openpgp.config.aead_protect = 'draft04';
openpgp.config.aead_chunk_size_byte = 14;
const iv = openpgp.util.hex_to_Uint8Array('b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10'.replace(/\s+/g, ''));
const key = openpgp.util.hex_to_Uint8Array('86 f1 ef b8 69 52 32 9f 24 ac d3 bf d0 e5 34 6d'.replace(/\s+/g, ''));
const algo = 'aes128';
const literal = new openpgp.packet.Literal(0);
const enc = new openpgp.packet.SymEncryptedAEADProtected();
const msg = new openpgp.packet.List();
msg.push(enc);
literal.setBytes(openpgp.util.str_to_Uint8Array('Hello, world!\n'), openpgp.enums.literal.binary);
literal.filename = '';
enc.packets.push(literal);
const msg2 = new openpgp.packet.List();
let randomBytesStub = stub(openpgp.crypto.random, 'getRandomBytes');
randomBytesStub.returns(resolves(iv));
return enc.encrypt(algo, key).then(function() {
const data = msg.write();
expect(data).to.deep.equal(packetBytes);
msg2.read(data);
return msg2[0].decrypt(algo, key);
}).then(function() {
expect(msg2[0].packets[0].data).to.deep.equal(literal.data);
}).finally(function() {
openpgp.config.aead_protect = aead_protectVal;
openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal;
randomBytesStub.restore();
});
});
it('Sym encrypted session key with a compressed packet', async function() {
const msg =
'-----BEGIN PGP MESSAGE-----\n' +