Implement AEAD Encrypted Data Packet
This commit is contained in:
parent
85a1b9859b
commit
7b3f51c0d4
|
@ -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
|
||||
|
|
10
src/enums.js
10
src/enums.js
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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' +
|
||||
|
|
Loading…
Reference in New Issue
Block a user