Implement version 5 Symmetric-Key Encrypted Session Key packet

This commit is contained in:
Daniel Huigens 2018-04-04 15:37:48 +02:00
parent 7b3f51c0d4
commit 17ad654d60
4 changed files with 180 additions and 21 deletions

View File

@ -59,6 +59,13 @@ export default {
* @property {Integer} aead_chunk_size_byte
*/
aead_chunk_size_byte: 46,
/**
* {@link https://tools.ietf.org/html/rfc4880#section-3.7.1.3|RFC4880 3.7.1.3}:
* Iteration Count Byte for S2K (String to Key)
* @memberof module:config
* @property {Integer} s2k_iteration_count_byte
*/
s2k_iteration_count_byte: 96,
/** Use integrity protection for symmetric encryption
* @memberof module:config
* @property {Boolean} integrity_protect

View File

@ -17,12 +17,14 @@
/**
* @requires type/s2k
* @requires config
* @requires crypto
* @requires enums
* @requires util
*/
import type_s2k from '../type/s2k';
import config from '../config';
import crypto from '../crypto';
import enums from '../enums';
import util from '../util';
@ -47,12 +49,14 @@ import util from '../util';
*/
function SymEncryptedSessionKey() {
this.tag = enums.packet.symEncryptedSessionKey;
this.version = 4;
this.version = config.aead_protect === 'draft04' ? 5 : 4;
this.sessionKey = null;
this.sessionKeyEncryptionAlgorithm = null;
this.sessionKeyAlgorithm = 'aes256';
this.aeadAlgorithm = 'eax';
this.encrypted = null;
this.s2k = null;
this.iv = null;
}
/**
@ -66,22 +70,35 @@ function SymEncryptedSessionKey() {
* @returns {module:packet.SymEncryptedSessionKey} Object representation
*/
SymEncryptedSessionKey.prototype.read = function(bytes) {
let offset = 0;
// A one-octet version number. The only currently defined version is 4.
[this.version] = bytes;
this.version = bytes[offset++];
// A one-octet number describing the symmetric algorithm used.
const algo = enums.read(enums.symmetric, bytes[1]);
const algo = enums.read(enums.symmetric, bytes[offset++]);
if (this.version === 5) {
// A one-octet AEAD algorithm.
this.aeadAlgorithm = enums.read(enums.aead, bytes[offset++]);
}
// A string-to-key (S2K) specifier, length as defined above.
this.s2k = new type_s2k();
const s2klength = this.s2k.read(bytes.subarray(2, bytes.length));
offset += this.s2k.read(bytes.subarray(offset, bytes.length));
// Optionally, the encrypted session key itself, which is decrypted
// with the string-to-key object.
const done = s2klength + 2;
if (this.version === 5) {
const mode = crypto[this.aeadAlgorithm];
if (done < bytes.length) {
this.encrypted = bytes.subarray(done, bytes.length);
// A starting initialization vector of size specified by the AEAD
// algorithm.
this.iv = bytes.subarray(offset, offset += mode.ivLength);
}
// The encrypted session key itself, which is decrypted with the
// string-to-key object. This is optional in version 4.
if (this.version === 5 || offset < bytes.length) {
this.encrypted = bytes.subarray(offset, bytes.length);
this.sessionKeyEncryptionAlgorithm = algo;
} else {
this.sessionKeyAlgorithm = algo;
@ -93,11 +110,18 @@ SymEncryptedSessionKey.prototype.write = function() {
this.sessionKeyAlgorithm :
this.sessionKeyEncryptionAlgorithm;
let bytes = util.concatUint8Array([new Uint8Array([this.version, enums.write(enums.symmetric, algo)]), this.s2k.write()]);
let bytes;
if (this.encrypted !== null) {
bytes = util.concatUint8Array([bytes, this.encrypted]);
if (this.version === 5) {
bytes = util.concatUint8Array([new Uint8Array([this.version, enums.write(enums.symmetric, algo), enums.write(enums.aead, this.aeadAlgorithm)]), this.s2k.write(), this.iv, this.encrypted]);
} else {
bytes = util.concatUint8Array([new Uint8Array([this.version, enums.write(enums.symmetric, algo)]), this.s2k.write()]);
if (this.encrypted !== null) {
bytes = util.concatUint8Array([bytes, this.encrypted]);
}
}
return bytes;
};
@ -115,14 +139,19 @@ SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) {
const length = crypto.cipher[algo].keySize;
const key = this.s2k.produce_key(passphrase, length);
if (this.encrypted === null) {
this.sessionKey = key;
} else {
if (this.version === 5) {
const mode = crypto[this.aeadAlgorithm];
const adata = new Uint8Array([0xC0 | this.tag, this.version, enums.write(enums.symmetric, this.sessionKeyEncryptionAlgorithm), enums.write(enums.aead, this.aeadAlgorithm)]);
this.sessionKey = await mode.decrypt(algo, this.encrypted, key, this.iv, adata);
} else if (this.encrypted !== null) {
const decrypted = crypto.cfb.normalDecrypt(algo, key, this.encrypted, null);
this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]);
this.sessionKey = decrypted.subarray(1, decrypted.length);
} else {
this.sessionKey = key;
}
return true;
};
@ -145,14 +174,21 @@ SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) {
const length = crypto.cipher[algo].keySize;
const key = this.s2k.produce_key(passphrase, length);
const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]);
if (this.sessionKey === null) {
this.sessionKey = await crypto.generateSessionKey(this.sessionKeyAlgorithm);
}
const private_key = util.concatUint8Array([algo_enum, this.sessionKey]);
this.encrypted = crypto.cfb.normalEncrypt(algo, key, private_key, null);
if (this.version === 5) {
const mode = crypto[this.aeadAlgorithm];
this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV
const adata = new Uint8Array([0xC0 | this.tag, this.version, enums.write(enums.symmetric, this.sessionKeyEncryptionAlgorithm), enums.write(enums.aead, this.aeadAlgorithm)]);
this.encrypted = await mode.encrypt(algo, this.sessionKey, key, this.iv, adata);
} else {
const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]);
const private_key = util.concatUint8Array([algo_enum, this.sessionKey]);
this.encrypted = crypto.cfb.normalEncrypt(algo, key, private_key, null);
}
return true;
};

View File

@ -24,15 +24,17 @@
* places, currently: to encrypt the secret part of private keys in the
* private keyring, and to convert passphrases to encryption keys for
* symmetrically encrypted messages.
* @requires config
* @requires crypto
* @requires enums
* @requires util
* @module type/s2k
*/
import config from '../config';
import crypto from '../crypto';
import enums from '../enums.js';
import util from '../util.js';
import crypto from '../crypto';
/**
* @constructor
@ -42,7 +44,8 @@ function S2K() {
this.algorithm = 'sha256';
/** @type {module:enums.s2k} */
this.type = 'iterated';
this.c = 96;
/** @type {Integer} */
this.c = config.s2k_iteration_count_byte;
/** Eight bytes of salt in a binary string.
* @type {String}
*/

View File

@ -415,6 +415,119 @@ describe("Packet", function() {
expect(stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data));
});
it('Sym. encrypted session key reading/writing (draft04)', async function() {
let aead_protectVal = openpgp.config.aead_protect;
openpgp.config.aead_protect = 'draft04';
try {
const passphrase = 'hello';
const algo = 'aes256';
const literal = new openpgp.packet.Literal();
const key_enc = new openpgp.packet.SymEncryptedSessionKey();
const enc = new openpgp.packet.SymEncryptedAEADProtected();
const msg = new openpgp.packet.List();
msg.push(key_enc);
msg.push(enc);
key_enc.sessionKeyAlgorithm = algo;
await key_enc.encrypt(passphrase);
const key = key_enc.sessionKey;
literal.setText('Hello world!');
enc.packets.push(literal);
await enc.encrypt(algo, key);
const msg2 = new openpgp.packet.List();
msg2.read(msg.write());
await msg2[0].decrypt(passphrase);
const key2 = msg2[0].sessionKey;
await msg2[1].decrypt(msg2[0].sessionKeyAlgorithm, key2);
expect(stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data));
} finally {
openpgp.config.aead_protect = aead_protectVal;
}
});
it('Sym. encrypted session key reading/writing test vector (draft04)', async function() {
// From https://gitlab.com/openpgp-wg/rfc4880bis/commit/00b20923e6233fb6ff1666ecd5acfefceb32907d
let aead_protectVal = openpgp.config.aead_protect;
let aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte;
let s2k_iteration_count_byteVal = openpgp.config.s2k_iteration_count_byte;
openpgp.config.aead_protect = 'draft04';
openpgp.config.aead_chunk_size_byte = 14;
openpgp.config.s2k_iteration_count_byte = 0x90;
let salt = openpgp.util.hex_to_Uint8Array(`cd5a9f70fbe0bc65`);
let sessionKey = openpgp.util.hex_to_Uint8Array(`86 f1 ef b8 69 52 32 9f 24 ac d3 bf d0 e5 34 6d`.replace(/\s+/g, ''));
let sessionIV = openpgp.util.hex_to_Uint8Array(`bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35`.replace(/\s+/g, ''));
let dataIV = openpgp.util.hex_to_Uint8Array(`b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10`.replace(/\s+/g, ''));
let randomBytesStub = stub(openpgp.crypto.random, 'getRandomBytes');
randomBytesStub.onCall(0).returns(resolves(salt));
randomBytesStub.onCall(1).returns(resolves(sessionKey));
randomBytesStub.onCall(2).returns(resolves(sessionIV));
randomBytesStub.onCall(3).returns(resolves(dataIV));
let packetBytes = openpgp.util.hex_to_Uint8Array(`
c3 3e 05 07 01 03 08 cd 5a 9f 70 fb e0 bc 65 90
bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35
9d ee 19 d0 7c 34 46 c4 31 2a 34 ae 19 67 a2 fb
7e 92 8e a5 b4 fa 80 12 bd 45 6d 17 38 c6 3c 36
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, ''));
try {
const passphrase = 'password';
const algo = 'aes128';
const literal = new openpgp.packet.Literal(0);
const key_enc = new openpgp.packet.SymEncryptedSessionKey();
const enc = new openpgp.packet.SymEncryptedAEADProtected();
const msg = new openpgp.packet.List();
msg.push(key_enc);
msg.push(enc);
key_enc.sessionKeyAlgorithm = algo;
await key_enc.encrypt(passphrase);
const key = key_enc.sessionKey;
literal.setBytes(openpgp.util.str_to_Uint8Array('Hello, world!\n'), openpgp.enums.literal.binary);
literal.filename = '';
enc.packets.push(literal);
await enc.encrypt(algo, key);
const data = msg.write();
expect(data).to.deep.equal(packetBytes);
const msg2 = new openpgp.packet.List();
msg2.read(data);
await msg2[0].decrypt(passphrase);
const key2 = msg2[0].sessionKey;
await msg2[1].decrypt(msg2[0].sessionKeyAlgorithm, key2);
expect(stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data));
} finally {
openpgp.config.aead_protect = aead_protectVal;
openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal;
openpgp.config.s2k_iteration_count_byte = s2k_iteration_count_byteVal;
randomBytesStub.restore();
}
});
it('Secret key encryption/decryption test', async function() {
const armored_msg =
'-----BEGIN PGP MESSAGE-----\n' +