Rename config.ignoreMdcError, drop config.integrityProtect and allow V4 keys to be AEAD-encrypted (#1261)

* Rename `config.ignoreMdcError` to `config.allowUnauthenticatedMessages`

* Do not support creating sym. enc. messages without integrity protection

* Use `config.aeadProtect` to determine SKESK encryption mode
This commit is contained in:
larabr 2021-03-03 18:05:40 +01:00 committed by GitHub
parent f41412a5a2
commit 6e2a787ff8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 26 additions and 30 deletions

4
openpgp.d.ts vendored
View File

@ -305,10 +305,10 @@ interface Config {
compression: enums.compression; compression: enums.compression;
showVersion: boolean; showVersion: boolean;
showComment: boolean; showComment: boolean;
integrityProtect: boolean;
deflateLevel: number; deflateLevel: number;
aeadProtect: boolean; aeadProtect: boolean;
ignoreMdcError: boolean; allowUnauthenticatedMessages: boolean;
allowUnauthenticatedStream: boolean;
checksumRequired: boolean; checksumRequired: boolean;
minRsaBits: number; minRsaBits: number;
passwordCollisionCheck: boolean; passwordCollisionCheck: boolean;

View File

@ -82,19 +82,20 @@ export default {
* @property {Integer} s2kIterationCountByte * @property {Integer} s2kIterationCountByte
*/ */
s2kIterationCountByte: 224, s2kIterationCountByte: 224,
/** Use integrity protection for symmetric encryption
* @memberof module:config
* @property {Boolean} integrityProtect
*/
integrityProtect: true,
/** /**
* Allow decryption of messages without integrity protection.
* This is an **insecure** setting:
* - message modifications cannot be detected, thus processing the decrypted data is potentially unsafe.
* - it enables downgrade attacks against integrity-protected messages.
* @memberof module:config * @memberof module:config
* @property {Boolean} ignoreMdcError Fail on decrypt if message is not integrity protected * @property {Boolean} allowUnauthenticatedMessages
*/ */
ignoreMdcError: false, allowUnauthenticatedMessages: false,
/** /**
* Allow streaming unauthenticated data before its integrity has been checked.
* This setting is **insecure** if the partially decrypted message is processed further or displayed to the user.
* @memberof module:config * @memberof module:config
* @property {Boolean} allowUnauthenticatedStream Stream unauthenticated data before integrity has been checked * @property {Boolean} allowUnauthenticatedStream
*/ */
allowUnauthenticatedStream: false, allowUnauthenticatedStream: false,
/** /**

View File

@ -181,16 +181,13 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
if (index === 0) { if (index === 0) {
signaturePacket.isPrimaryUserID = true; signaturePacket.isPrimaryUserID = true;
} }
if (config.integrityProtect) { // integrity protection always enabled
signaturePacket.features = [0]; signaturePacket.features = [0];
signaturePacket.features[0] |= enums.features.modificationDetection; signaturePacket.features[0] |= enums.features.modificationDetection;
}
if (config.aeadProtect) { if (config.aeadProtect) {
signaturePacket.features || (signaturePacket.features = [0]);
signaturePacket.features[0] |= enums.features.aead; signaturePacket.features[0] |= enums.features.aead;
} }
if (config.v5Keys) { if (config.v5Keys) {
signaturePacket.features || (signaturePacket.features = [0]);
signaturePacket.features[0] |= enums.features.v5Keys; signaturePacket.features[0] |= enums.features.v5Keys;
} }
if (options.keyExpirationTime > 0) { if (options.keyExpirationTime > 0) {

View File

@ -326,10 +326,8 @@ export class Message {
if (aeadAlgorithm) { if (aeadAlgorithm) {
symEncryptedPacket = new AEADEncryptedDataPacket(); symEncryptedPacket = new AEADEncryptedDataPacket();
symEncryptedPacket.aeadAlgorithm = aeadAlgorithm; symEncryptedPacket.aeadAlgorithm = aeadAlgorithm;
} else if (config.integrityProtect) {
symEncryptedPacket = new SymEncryptedIntegrityProtectedDataPacket();
} else { } else {
symEncryptedPacket = new SymmetricallyEncryptedDataPacket(); symEncryptedPacket = new SymEncryptedIntegrityProtectedDataPacket();
} }
symEncryptedPacket.packets = this.packets; symEncryptedPacket.packets = this.packets;

View File

@ -296,7 +296,7 @@ class SecretKeyPacket extends PublicKeyPacket {
const blockLen = crypto.cipher[this.symmetric].blockSize; const blockLen = crypto.cipher[this.symmetric].blockSize;
this.iv = await crypto.random.getRandomBytes(blockLen); this.iv = await crypto.random.getRandomBytes(blockLen);
if (this.version === 5) { if (config.aeadProtect) {
this.s2k_usage = 253; this.s2k_usage = 253;
this.aead = 'eax'; this.aead = 'eax';
const mode = crypto[this.aead]; const mode = crypto[this.aead];

View File

@ -75,8 +75,8 @@ class SymmetricallyEncryptedDataPacket {
*/ */
async decrypt(sessionKeyAlgorithm, key, streaming, config = defaultConfig) { async decrypt(sessionKeyAlgorithm, key, streaming, config = defaultConfig) {
// If MDC errors are not being ignored, all missing MDC packets in symmetrically encrypted data should throw an error // If MDC errors are not being ignored, all missing MDC packets in symmetrically encrypted data should throw an error
if (!config.ignoreMdcError) { if (!config.allowUnauthenticatedMessages) {
throw new Error('Decryption failed due to missing MDC.'); throw new Error('Message is not authenticated.');
} }
this.encrypted = await stream.readToEnd(this.encrypted); this.encrypted = await stream.readToEnd(this.encrypted);

View File

@ -67,9 +67,9 @@ module.exports = () => describe("Packet", function() {
it('Symmetrically encrypted packet without integrity protection - allow decryption', async function() { it('Symmetrically encrypted packet without integrity protection - allow decryption', async function() {
const aeadProtectVal = openpgp.config.aeadProtect; const aeadProtectVal = openpgp.config.aeadProtect;
const ignoreMdcErrorVal = openpgp.config.ignoreMdcError; const allowUnauthenticatedMessagesVal = openpgp.config.allowUnauthenticatedMessages;
openpgp.config.aeadProtect = false; openpgp.config.aeadProtect = false;
openpgp.config.ignoreMdcError = true; openpgp.config.allowUnauthenticatedMessages = true;
const message = new openpgp.PacketList(); const message = new openpgp.PacketList();
const testText = input.createSomeMessage(); const testText = input.createSomeMessage();
@ -94,7 +94,7 @@ module.exports = () => describe("Packet", function() {
expect(await stringify(msg2[0].packets[0].data)).to.equal(stringify(literal.data)); expect(await stringify(msg2[0].packets[0].data)).to.equal(stringify(literal.data));
} finally { } finally {
openpgp.config.aeadProtect = aeadProtectVal; openpgp.config.aeadProtect = aeadProtectVal;
openpgp.config.ignoreMdcError = ignoreMdcErrorVal; openpgp.config.allowUnauthenticatedMessages = allowUnauthenticatedMessagesVal;
} }
}); });
@ -120,7 +120,7 @@ module.exports = () => describe("Packet", function() {
const msg2 = new openpgp.PacketList(); const msg2 = new openpgp.PacketList();
await msg2.read(message.write(), { SymmetricallyEncryptedDataPacket: openpgp.SymmetricallyEncryptedDataPacket }); await msg2.read(message.write(), { SymmetricallyEncryptedDataPacket: openpgp.SymmetricallyEncryptedDataPacket });
await expect(msg2[0].decrypt(algo, key, undefined, openpgp.config)).to.eventually.be.rejectedWith('Decryption failed due to missing MDC.'); await expect(msg2[0].decrypt(algo, key, undefined, openpgp.config)).to.eventually.be.rejectedWith('Message is not authenticated.');
} finally { } finally {
openpgp.config.aeadProtect = aeadProtectVal; openpgp.config.aeadProtect = aeadProtectVal;
} }
@ -838,12 +838,12 @@ V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+
const rsa = openpgp.enums.publicKey.rsaEncryptSign; const rsa = openpgp.enums.publicKey.rsaEncryptSign;
const { privateParams, publicParams } = await crypto.generateParams(rsa, 1024, 65537); const { privateParams, publicParams } = await crypto.generateParams(rsa, 1024, 65537);
const secretKeyPacket = new openpgp.SecretKeyPacket(undefined, { ...openpgp.config, v5Keys: true }); const secretKeyPacket = new openpgp.SecretKeyPacket();
secretKeyPacket.privateParams = privateParams; secretKeyPacket.privateParams = privateParams;
secretKeyPacket.publicParams = publicParams; secretKeyPacket.publicParams = publicParams;
secretKeyPacket.algorithm = "rsaSign"; secretKeyPacket.algorithm = "rsaSign";
secretKeyPacket.isEncrypted = false; secretKeyPacket.isEncrypted = false;
await secretKeyPacket.encrypt('hello', openpgp.config); await secretKeyPacket.encrypt('hello', { ...openpgp.config, aeadProtect: true });
expect(secretKeyPacket.s2k_usage).to.equal(253); expect(secretKeyPacket.s2k_usage).to.equal(253);
const raw = new openpgp.PacketList(); const raw = new openpgp.PacketList();
@ -860,12 +860,12 @@ V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+
it('Writing and encryption of a secret key packet (CFB)', async function() { it('Writing and encryption of a secret key packet (CFB)', async function() {
const rsa = openpgp.enums.publicKey.rsaEncryptSign; const rsa = openpgp.enums.publicKey.rsaEncryptSign;
const { privateParams, publicParams } = await crypto.generateParams(rsa, 1024, 65537); const { privateParams, publicParams } = await crypto.generateParams(rsa, 1024, 65537);
const secretKeyPacket = new openpgp.SecretKeyPacket(undefined, { ...openpgp.config, v5Keys: false }); const secretKeyPacket = new openpgp.SecretKeyPacket();
secretKeyPacket.privateParams = privateParams; secretKeyPacket.privateParams = privateParams;
secretKeyPacket.publicParams = publicParams; secretKeyPacket.publicParams = publicParams;
secretKeyPacket.algorithm = "rsaSign"; secretKeyPacket.algorithm = "rsaSign";
secretKeyPacket.isEncrypted = false; secretKeyPacket.isEncrypted = false;
await secretKeyPacket.encrypt('hello', openpgp.config); await secretKeyPacket.encrypt('hello', { ...openpgp.config, aeadProtect: false });
expect(secretKeyPacket.s2k_usage).to.equal(254); expect(secretKeyPacket.s2k_usage).to.equal(254);
const raw = new openpgp.PacketList(); const raw = new openpgp.PacketList();