diff --git a/src/crypto/hkdf.js b/src/crypto/hkdf.js index c08720b1..91ff9a2e 100644 --- a/src/crypto/hkdf.js +++ b/src/crypto/hkdf.js @@ -9,7 +9,7 @@ import util from '../util'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); -export default async function HKDF(hashAlgo, inputKey, salt, info, outLen) { +export default async function computeHKDF(hashAlgo, inputKey, salt, info, outLen) { const hash = enums.read(enums.webHash, hashAlgo); if (!hash) throw new Error('Hash algo not supported with HKDF'); diff --git a/src/key/helper.js b/src/key/helper.js index 55fb5ff2..766f41c9 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -308,7 +308,7 @@ export async function isAEADSupported(keys, date = new Date(), userIDs = [], con await Promise.all(keys.map(async function(key, i) { const selfCertification = await key.getPrimarySelfSignature(date, userIDs[i], config); if (!selfCertification.features || - !(selfCertification.features[0] & enums.features.aead)) { + !(selfCertification.features[0] & enums.features.seipdv2)) { supported = false; } })); diff --git a/src/message.js b/src/message.js index 611dfbc8..a17f523c 100644 --- a/src/message.js +++ b/src/message.js @@ -392,13 +392,12 @@ export class Message { const msg = await Message.encryptSessionKey(sessionKeyData, algorithmName, aeadAlgorithmName, encryptionKeys, passwords, wildcard, encryptionKeyIDs, date, userIDs, config); - let symEncryptedPacket; + const symEncryptedPacket = new SymEncryptedIntegrityProtectedDataPacket(); if (aeadAlgorithmName) { - symEncryptedPacket = new AEADEncryptedDataPacket(); + symEncryptedPacket.version = 2; symEncryptedPacket.aeadAlgorithm = enums.write(enums.aead, aeadAlgorithmName); - } else { - symEncryptedPacket = new SymEncryptedIntegrityProtectedDataPacket(); } + symEncryptedPacket.packets = this.packets; const algorithm = enums.write(enums.symmetric, algorithmName); diff --git a/src/packet/aead_encrypted_data.js b/src/packet/aead_encrypted_data.js index 5f42a8fb..60bdc3a7 100644 --- a/src/packet/aead_encrypted_data.js +++ b/src/packet/aead_encrypted_data.js @@ -21,6 +21,7 @@ import enums from '../enums'; import util from '../util'; import defaultConfig from '../config'; import { UnsupportedError } from './packet'; +import { runAEAD } from './sym_encrypted_integrity_protected_data'; import LiteralDataPacket from './literal_data'; import CompressedDataPacket from './compressed_data'; @@ -101,7 +102,7 @@ class AEADEncryptedDataPacket { */ async decrypt(sessionKeyAlgorithm, key, config = defaultConfig) { this.packets = await PacketList.fromBinary( - await this.crypt('decrypt', key, stream.clone(this.encrypted)), + await runAEAD(this, 'decrypt', key, stream.clone(this.encrypted)), allowedPackets, config ); @@ -122,86 +123,7 @@ class AEADEncryptedDataPacket { this.iv = crypto.random.getRandomBytes(ivLength); // generate new random IV this.chunkSizeByte = config.aeadChunkSizeByte; const data = this.packets.write(); - this.encrypted = await this.crypt('encrypt', key, data); - } - - /** - * En/decrypt the payload. - * @param {encrypt|decrypt} fn - Whether to encrypt or decrypt - * @param {Uint8Array} key - The session key used to en/decrypt the payload - * @param {Uint8Array | ReadableStream} data - The data to en/decrypt - * @returns {Promise>} - * @async - */ - async crypt(fn, key, data) { - const mode = crypto.getAEADMode(this.aeadAlgorithm); - const modeInstance = await mode(this.cipherAlgorithm, key); - const tagLengthIfDecrypting = fn === 'decrypt' ? mode.tagLength : 0; - const tagLengthIfEncrypting = fn === 'encrypt' ? mode.tagLength : 0; - const chunkSize = 2 ** (this.chunkSizeByte + 6) + tagLengthIfDecrypting; // ((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 | AEADEncryptedDataPacket.tag, this.version, this.cipherAlgorithm, this.aeadAlgorithm, this.chunkSizeByte], 0); - let chunkIndex = 0; - let latestPromise = Promise.resolve(); - let cryptedBytes = 0; - let queuedBytes = 0; - const iv = this.iv; - return stream.transformPair(data, async (readable, writable) => { - if (util.isStream(readable) !== 'array') { - const buffer = new TransformStream({}, { - highWaterMark: util.getHardwareConcurrency() * 2 ** (this.chunkSizeByte + 6), - size: array => array.length - }); - stream.pipe(buffer.readable, writable); - writable = buffer.writable; - } - const reader = stream.getReader(readable); - const writer = stream.getWriter(writable); - try { - while (true) { - let chunk = await reader.readBytes(chunkSize + tagLengthIfDecrypting) || new Uint8Array(); - const finalChunk = chunk.subarray(chunk.length - tagLengthIfDecrypting); - chunk = chunk.subarray(0, chunk.length - tagLengthIfDecrypting); - let cryptedPromise; - let done; - if (!chunkIndex || chunk.length) { - reader.unshift(finalChunk); - cryptedPromise = modeInstance[fn](chunk, mode.getNonce(iv, chunkIndexArray), adataArray); - queuedBytes += chunk.length - tagLengthIfDecrypting + tagLengthIfEncrypting; - } else { - // After the last chunk, we either encrypt a final, empty - // data chunk to get the final authentication tag or - // validate that final authentication tag. - adataView.setInt32(13 + 4, cryptedBytes); // Should be setInt64(13, ...) - cryptedPromise = modeInstance[fn](finalChunk, mode.getNonce(iv, chunkIndexArray), adataTagArray); - queuedBytes += tagLengthIfEncrypting; - done = true; - } - cryptedBytes += chunk.length - tagLengthIfDecrypting; - // eslint-disable-next-line no-loop-func - latestPromise = latestPromise.then(() => cryptedPromise).then(async crypted => { - await writer.ready; - await writer.write(crypted); - queuedBytes -= crypted.length; - }).catch(err => writer.abort(err)); - if (done || queuedBytes > writer.desiredSize) { - await latestPromise; // Respect backpressure - } - if (!done) { - adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...) - } else { - await writer.close(); - break; - } - } - } catch (e) { - await writer.abort(e); - } - }); + this.encrypted = await runAEAD(this, 'encrypt', key, data); } } diff --git a/src/packet/sym_encrypted_integrity_protected_data.js b/src/packet/sym_encrypted_integrity_protected_data.js index d1099a9f..3f462c90 100644 --- a/src/packet/sym_encrypted_integrity_protected_data.js +++ b/src/packet/sym_encrypted_integrity_protected_data.js @@ -17,6 +17,7 @@ import * as stream from '@openpgp/web-stream-tools'; import crypto from '../crypto'; +import computeHKDF from '../crypto/hkdf'; import enums from '../enums'; import util from '../util'; import defaultConfig from '../config'; @@ -36,8 +37,6 @@ const allowedPackets = /*#__PURE__*/ util.constructAllowedPackets([ SignaturePacket ]); -const VERSION = 1; // A one-octet version number of the data packet. - /** * Implementation of the Sym. Encrypted Integrity Protected Data Packet (Tag 18) * @@ -54,28 +53,55 @@ class SymEncryptedIntegrityProtectedDataPacket { } constructor() { - this.version = VERSION; + this.version = 1; + + // The following 4 fields are for V2 only. + /** @type {enums.symmetric} */ + this.cipherAlgorithm = null; + /** @type {enums.aead} */ + this.aeadAlgorithm = null; + this.chunkSizeByte = null; + this.salt = null; + this.encrypted = null; this.packets = null; } async read(bytes) { await stream.parse(bytes, async reader => { - const version = await reader.readByte(); - // - A one-octet version number. The only currently defined value is 1. - if (version !== VERSION) { + this.version = await reader.readByte(); + // - A one-octet version number with value 1 or 2. + if (this.version !== 1 && this.version !== 2) { throw new UnsupportedError(`Version ${version} of the SEIP packet is unsupported.`); } + if (this.version === 2) { + // - A one-octet cipher algorithm. + this.cipherAlgorithm = await reader.readByte(); + // - A one-octet AEAD algorithm. + this.aeadAlgorithm = await reader.readByte(); + // - A one-octet chunk size. + this.chunkSizeByte = await reader.readByte(); + // - Thirty-two octets of salt. The salt is used to derive the message key and must be unique. + this.salt = await reader.readBytes(32); + } + + // For V1: // - Encrypted data, the output of the selected symmetric-key cipher // operating in Cipher Feedback mode with shift amount equal to the // block size of the cipher (CFB-n where n is the block size). + // For V2: + // - Encrypted data, the output of the selected symmetric-key cipher operating in the given AEAD mode. + // - A final, summary authentication tag for the AEAD mode. this.encrypted = reader.remainder(); }); } write() { - return util.concat([new Uint8Array([VERSION]), this.encrypted]); + if (this.version === 2) { + return util.concat([new Uint8Array([this.version, this.cipherAlgorithm, this.aeadAlgorithm, this.chunkSizeByte]), this.salt, this.encrypted]); + } + return util.concat([new Uint8Array([this.version]), this.encrypted]); } /** @@ -88,18 +114,27 @@ class SymEncryptedIntegrityProtectedDataPacket { * @async */ async encrypt(sessionKeyAlgorithm, key, config = defaultConfig) { - const { blockSize } = crypto.getCipher(sessionKeyAlgorithm); - let bytes = this.packets.write(); if (stream.isArrayStream(bytes)) bytes = await stream.readToEnd(bytes); - const prefix = await crypto.getPrefixRandom(sessionKeyAlgorithm); - const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet - const tohash = util.concat([prefix, bytes, mdc]); - const hash = await crypto.hash.sha1(stream.passiveClone(tohash)); - const plaintext = util.concat([tohash, hash]); + if (this.version === 2) { + this.cipherAlgorithm = sessionKeyAlgorithm; - this.encrypted = await crypto.mode.cfb.encrypt(sessionKeyAlgorithm, key, plaintext, new Uint8Array(blockSize), config); + this.salt = crypto.random.getRandomBytes(32); + this.chunkSizeByte = config.aeadChunkSizeByte; + this.encrypted = await runAEAD(this, 'encrypt', key, bytes); + } else { + const { blockSize } = crypto.getCipher(sessionKeyAlgorithm); + + const prefix = await crypto.getPrefixRandom(sessionKeyAlgorithm); + const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet + + const tohash = util.concat([prefix, bytes, mdc]); + const hash = await crypto.hash.sha1(stream.passiveClone(tohash)); + const plaintext = util.concat([tohash, hash]); + + this.encrypted = await crypto.mode.cfb.encrypt(sessionKeyAlgorithm, key, plaintext, new Uint8Array(blockSize), config); + } return true; } @@ -113,33 +148,152 @@ class SymEncryptedIntegrityProtectedDataPacket { * @async */ async decrypt(sessionKeyAlgorithm, key, config = defaultConfig) { - const { blockSize } = crypto.getCipher(sessionKeyAlgorithm); let encrypted = stream.clone(this.encrypted); if (stream.isArrayStream(encrypted)) encrypted = await stream.readToEnd(encrypted); - const decrypted = await crypto.mode.cfb.decrypt(sessionKeyAlgorithm, key, encrypted, new Uint8Array(blockSize)); - // there must be a modification detection code packet as the - // last packet and everything gets hashed except the hash itself - const realHash = stream.slice(stream.passiveClone(decrypted), -20); - const tohash = stream.slice(decrypted, 0, -20); - const verifyHash = Promise.all([ - stream.readToEnd(await crypto.hash.sha1(stream.passiveClone(tohash))), - stream.readToEnd(realHash) - ]).then(([hash, mdc]) => { - if (!util.equalsUint8Array(hash, mdc)) { - throw new Error('Modification detected.'); + let packetbytes; + if (this.version === 2) { + packetbytes = await runAEAD(this, 'decrypt', key, encrypted); + } else { + const { blockSize } = crypto.getCipher(sessionKeyAlgorithm); + const decrypted = await crypto.mode.cfb.decrypt(sessionKeyAlgorithm, key, encrypted, new Uint8Array(blockSize)); + + // there must be a modification detection code packet as the + // last packet and everything gets hashed except the hash itself + const realHash = stream.slice(stream.passiveClone(decrypted), -20); + const tohash = stream.slice(decrypted, 0, -20); + const verifyHash = Promise.all([ + stream.readToEnd(await crypto.hash.sha1(stream.passiveClone(tohash))), + stream.readToEnd(realHash) + ]).then(([hash, mdc]) => { + if (!util.equalsUint8Array(hash, mdc)) { + throw new Error('Modification detected.'); + } + return new Uint8Array(); + }); + const bytes = stream.slice(tohash, blockSize + 2); // Remove random prefix + packetbytes = stream.slice(bytes, 0, -2); // Remove MDC packet + packetbytes = stream.concat([packetbytes, stream.fromAsync(() => verifyHash)]); + if (!util.isStream(encrypted) || !config.allowUnauthenticatedStream) { + packetbytes = await stream.readToEnd(packetbytes); } - return new Uint8Array(); - }); - const bytes = stream.slice(tohash, blockSize + 2); // Remove random prefix - let packetbytes = stream.slice(bytes, 0, -2); // Remove MDC packet - packetbytes = stream.concat([packetbytes, stream.fromAsync(() => verifyHash)]); - if (!util.isStream(encrypted) || !config.allowUnauthenticatedStream) { - packetbytes = await stream.readToEnd(packetbytes); } + this.packets = await PacketList.fromBinary(packetbytes, allowedPackets, config); return true; } } export default SymEncryptedIntegrityProtectedDataPacket; + +/** + * En/decrypt the payload. + * @param {encrypt|decrypt} fn - Whether to encrypt or decrypt + * @param {Uint8Array} key - The session key used to en/decrypt the payload + * @param {Uint8Array | ReadableStream} data - The data to en/decrypt + * @returns {Promise>} + * @async + */ +export async function runAEAD(packet, fn, key, data) { + const isSEIPDv2 = packet instanceof SymEncryptedIntegrityProtectedDataPacket && packet.version === 2; + const isAEADP = !isSEIPDv2 && packet.constructor.tag === enums.packet.aeadEncryptedData; // no `instanceof` to avoid importing the corresponding class (circular import) + if (!isSEIPDv2 && !isAEADP) throw new Error('Unexpected packet type'); + + const mode = crypto.getAEADMode(packet.aeadAlgorithm); + const tagLengthIfDecrypting = fn === 'decrypt' ? mode.tagLength : 0; + const tagLengthIfEncrypting = fn === 'encrypt' ? mode.tagLength : 0; + const chunkSize = 2 ** (packet.chunkSizeByte + 6) + tagLengthIfDecrypting; // ((uint64_t)1 << (c + 6)) + const chunkIndexSizeIfAEADEP = isAEADP ? 8 : 0; + const adataBuffer = new ArrayBuffer(13 + chunkIndexSizeIfAEADEP); + const adataArray = new Uint8Array(adataBuffer, 0, 5 + chunkIndexSizeIfAEADEP); + const adataTagArray = new Uint8Array(adataBuffer); + const adataView = new DataView(adataBuffer); + const chunkIndexArray = new Uint8Array(adataBuffer, 5, 8); + adataArray.set([0xC0 | packet.constructor.tag, packet.version, packet.cipherAlgorithm, packet.aeadAlgorithm, packet.chunkSizeByte], 0); + let chunkIndex = 0; + let latestPromise = Promise.resolve(); + let cryptedBytes = 0; + let queuedBytes = 0; + let iv; + let ivView; + if (isSEIPDv2) { + const { keySize } = crypto.getCipher(packet.cipherAlgorithm); + const { ivLength } = mode; + const info = new Uint8Array(adataBuffer, 0, 5); + const derived = await computeHKDF(enums.hash.sha256, key, packet.salt, info, keySize + ivLength); + key = derived.subarray(0, keySize); + iv = derived.subarray(keySize); // The last 8 bytes of HKDF output are unneeded, but this avoids one copy. + iv.fill(0, iv.length - 8); + ivView = new DataView(iv.buffer, iv.byteOffset, iv.byteLength); + } else { // AEADEncryptedDataPacket + iv = packet.iv; + // ivView is unused in this case + } + const modeInstance = await mode(packet.cipherAlgorithm, key); + return stream.transformPair(data, async (readable, writable) => { + if (util.isStream(readable) !== 'array') { + const buffer = new TransformStream({}, { + highWaterMark: util.getHardwareConcurrency() * 2 ** (packet.chunkSizeByte + 6), + size: array => array.length + }); + stream.pipe(buffer.readable, writable); + writable = buffer.writable; + } + const reader = stream.getReader(readable); + const writer = stream.getWriter(writable); + try { + while (true) { + let chunk = await reader.readBytes(chunkSize + tagLengthIfDecrypting) || new Uint8Array(); + const finalChunk = chunk.subarray(chunk.length - tagLengthIfDecrypting); + chunk = chunk.subarray(0, chunk.length - tagLengthIfDecrypting); + let cryptedPromise; + let done; + let nonce; + if (isSEIPDv2) { // SEIPD V2 + nonce = iv; + } else { // AEADEncryptedDataPacket + nonce = iv.slice(); + for (let i = 0; i < 8; i++) { + nonce[iv.length - 8 + i] ^= chunkIndexArray[i]; + } + } + if (!chunkIndex || chunk.length) { + reader.unshift(finalChunk); + cryptedPromise = modeInstance[fn](chunk, nonce, adataArray); + queuedBytes += chunk.length - tagLengthIfDecrypting + tagLengthIfEncrypting; + } else { + // After the last chunk, we either encrypt a final, empty + // data chunk to get the final authentication tag or + // validate that final authentication tag. + adataView.setInt32(5 + chunkIndexSizeIfAEADEP + 4, cryptedBytes); // Should be setInt64(5 + chunkIndexSizeIfAEADEP, ...) + cryptedPromise = modeInstance[fn](finalChunk, nonce, adataTagArray); + queuedBytes += tagLengthIfEncrypting; + done = true; + } + cryptedBytes += chunk.length - tagLengthIfDecrypting; + // eslint-disable-next-line no-loop-func + latestPromise = latestPromise.then(() => cryptedPromise).then(async crypted => { + await writer.ready; + await writer.write(crypted); + queuedBytes -= crypted.length; + }).catch(err => writer.abort(err)); + if (done || queuedBytes > writer.desiredSize) { + await latestPromise; // Respect backpressure + } + if (!done) { + if (isSEIPDv2) { // SEIPD V2 + ivView.setInt32(iv.length - 4, ++chunkIndex); // Should be setInt64(iv.length - 8, ...) + } else { // AEADEncryptedDataPacket + adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...) + } + } else { + await writer.close(); + break; + } + } + } catch (e) { + await writer.ready.catch(() => {}); + await writer.abort(e); + } + }); +} diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index e50873e4..323ae475 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -119,6 +119,7 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 1); await openpgp.decrypt({ message: encryptedMessage, passwords, config }); }); @@ -131,6 +132,7 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 1); await openpgp.decrypt({ message: encryptedMessage, passwords, config }); }); @@ -142,7 +144,8 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); - assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); + assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 2); await openpgp.decrypt({ message: encryptedMessage, passwords, config }); }); @@ -154,7 +157,8 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); - assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); + assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 2); await openpgp.decrypt({ message: encryptedMessage, passwords, config }); }); @@ -176,6 +180,7 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 1); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption await new Promise(resolve => { @@ -201,6 +206,7 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 1); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption await new Promise(resolve => { @@ -225,7 +231,8 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); - assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); + assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 2); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption await new Promise(resolve => { @@ -250,7 +257,8 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); - assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); + assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 2); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption await new Promise(resolve => { @@ -276,6 +284,7 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 1); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption await new Promise(resolve => { @@ -301,6 +310,7 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 1); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, @@ -329,7 +339,8 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); - assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); + assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + assert.ok(encryptedMessage.packets[1].version === 2); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); // read out output stream to trigger decryption await new Promise(resolve => { diff --git a/test/general/config.js b/test/general/config.js index e522097a..0d5393d1 100644 --- a/test/general/config.js +++ b/test/general/config.js @@ -272,6 +272,7 @@ n9/quqtmyOtYOA6gXNCw0Fal3iANKBmsPmYI const { packets: [skesk, encData] } = encrypted; expect(skesk.version).to.equal(4); // cfb expect(encData.constructor.tag).to.equal(openpgp.enums.packet.symEncryptedIntegrityProtectedData); + expect(encData.version).to.equal(1); const { packets: [literal] } = await encrypted.decrypt(null, passwords, null, encrypted.fromStream, openpgp.config); expect(literal.constructor.tag).to.equal(openpgp.enums.packet.literalData); @@ -284,7 +285,8 @@ n9/quqtmyOtYOA6gXNCw0Fal3iANKBmsPmYI const encrypted2 = await openpgp.readMessage({ armoredMessage: armored2 }); const { packets: [skesk2, encData2] } = encrypted2; expect(skesk2.version).to.equal(5); - expect(encData2.constructor.tag).to.equal(openpgp.enums.packet.aeadEncryptedData); + expect(encData2.constructor.tag).to.equal(openpgp.enums.packet.symEncryptedIntegrityProtectedData); + expect(encData2.version).to.equal(2); const { packets: [compressed] } = await encrypted2.decrypt(null, passwords, null, encrypted2.fromStream, openpgp.config); expect(compressed.constructor.tag).to.equal(openpgp.enums.packet.compressedData); expect(compressed.algorithm).to.equal(openpgp.enums.compression.zip); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 15c5f9d8..c0f19df8 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -2300,10 +2300,10 @@ XfA3pqV4mTzF openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.experimentalGCM; openpgp.config.v6Keys = true; - // Monkey-patch AEAD feature flag - publicKey.users[0].selfCertifications[0].features = [7]; - publicKey_2000_2008.users[0].selfCertifications[0].features = [7]; - publicKey_2038_2045.users[0].selfCertifications[0].features = [7]; + // Monkey-patch SEIPD V2 feature flag + publicKey.users[0].selfCertifications[0].features = [9]; + publicKey_2000_2008.users[0].selfCertifications[0].features = [9]; + publicKey_2038_2045.users[0].selfCertifications[0].features = [9]; } }); @@ -2313,10 +2313,10 @@ XfA3pqV4mTzF openpgp.config.aeadProtect = true; openpgp.config.aeadChunkSizeByte = 0; - // Monkey-patch AEAD feature flag - publicKey.users[0].selfCertifications[0].features = [7]; - publicKey_2000_2008.users[0].selfCertifications[0].features = [7]; - publicKey_2038_2045.users[0].selfCertifications[0].features = [7]; + // Monkey-patch SEIPD V2 feature flag + publicKey.users[0].selfCertifications[0].features = [9]; + publicKey_2000_2008.users[0].selfCertifications[0].features = [9]; + publicKey_2038_2045.users[0].selfCertifications[0].features = [9]; } }); @@ -2326,10 +2326,10 @@ XfA3pqV4mTzF openpgp.config.aeadProtect = true; openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.ocb; - // Monkey-patch AEAD feature flag - publicKey.users[0].selfCertifications[0].features = [7]; - publicKey_2000_2008.users[0].selfCertifications[0].features = [7]; - publicKey_2038_2045.users[0].selfCertifications[0].features = [7]; + // Monkey-patch SEIPD V2 feature flag + publicKey.users[0].selfCertifications[0].features = [9]; + publicKey_2000_2008.users[0].selfCertifications[0].features = [9]; + publicKey_2038_2045.users[0].selfCertifications[0].features = [9]; } }); @@ -2626,7 +2626,7 @@ XfA3pqV4mTzF return openpgp.encrypt(encOpt).then(async function (encrypted) { expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); decOpt.message = await openpgp.readMessage({ armoredMessage: encrypted }); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.aeadEncryptedData)).to.equal(false); + expect(decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedIntegrityProtectedData).version === 2).to.equal(false); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -2649,7 +2649,7 @@ XfA3pqV4mTzF return openpgp.encrypt(encOpt).then(async function (encrypted) { expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); decOpt.message = await openpgp.readMessage({ armoredMessage: encrypted }); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.aeadEncryptedData)).to.equal(false); + expect(decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedIntegrityProtectedData).version === 2).to.equal(false); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -2668,7 +2668,7 @@ XfA3pqV4mTzF }; return openpgp.encrypt(encOpt).then(async function (encrypted) { decOpt.message = await openpgp.readMessage({ armoredMessage: encrypted }); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.aeadEncryptedData)).to.equal(openpgp.config.aeadProtect); + expect(decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedIntegrityProtectedData).version === 2).to.equal(openpgp.config.aeadProtect); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -2692,7 +2692,7 @@ XfA3pqV4mTzF }; return openpgp.encrypt(encOpt).then(async function (encrypted) { decOpt.message = await openpgp.readMessage({ armoredMessage: encrypted }); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.aeadEncryptedData)).to.equal(openpgp.config.aeadProtect); + expect(decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedIntegrityProtectedData).version === 2).to.equal(openpgp.config.aeadProtect); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -2715,7 +2715,7 @@ XfA3pqV4mTzF }; return openpgp.encrypt(encOpt).then(async function (encrypted) { decOpt.message = await openpgp.readMessage({ armoredMessage: encrypted }); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.aeadEncryptedData)).to.equal(false); + expect(decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedIntegrityProtectedData).version === 2).to.equal(false); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -2746,7 +2746,7 @@ XfA3pqV4mTzF }; return openpgp.encrypt(encOpt).then(async function (encrypted) { decOpt.message = await openpgp.readMessage({ armoredMessage: encrypted }); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.aeadEncryptedData)).to.equal(openpgp.config.aeadProtect); + expect(decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedIntegrityProtectedData).version === 2).to.equal(openpgp.config.aeadProtect); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -2775,7 +2775,7 @@ XfA3pqV4mTzF detached: true }); const message = await openpgp.readMessage({ armoredMessage: encrypted }); - expect(!!message.packets.findPacket(openpgp.enums.packet.aeadEncryptedData)).to.equal(openpgp.config.aeadProtect); + expect(message.packets.findPacket(openpgp.enums.packet.symEncryptedIntegrityProtectedData).version === 2).to.equal(openpgp.config.aeadProtect); const decrypted = await openpgp.decrypt({ message, signature: await openpgp.readSignature({ armoredSignature: signed }), diff --git a/test/general/packet.js b/test/general/packet.js index dca36ead..8d77cdfe 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -152,7 +152,7 @@ export default () => describe('Packet', function() { expect(await stringify(msg2[0].packets[0].data)).to.equal(stringify(literal.data)); }); - it('Sym. encrypted AEAD protected packet', function() { + it('Sym. encrypted AEAD protected packet (AEADP)', function() { const aeadProtectVal = openpgp.config.aeadProtect; openpgp.config.aeadProtect = false; @@ -201,7 +201,7 @@ export default () => describe('Packet', function() { return cryptStub; } - it('Sym. encrypted AEAD protected packet is encrypted in parallel (AEAD, GCM)', async function() { + it('Sym. encrypted AEAD protected packet is encrypted in parallel (AEADP, GCM)', async function() { const webCrypto = util.getWebCrypto(); if (!webCrypto || util.getNodeCrypto()) return; const encryptStub = cryptStub(webCrypto, 'encrypt'); @@ -236,7 +236,7 @@ export default () => describe('Packet', function() { } }); - it('Sym. encrypted AEAD protected packet test vector (AEAD)', async function() { + it('AEAD Encrypted Data packet test vector (AEADP)', async function() { // From https://gitlab.com/openpgp-wg/rfc4880bis/commit/00b20923e6233fb6ff1666ecd5acfefceb32907d const nodeCrypto = util.getNodeCrypto(); @@ -516,7 +516,7 @@ export default () => describe('Packet', function() { } }); - it('Sym. encrypted session key reading/writing test vector (EAX, AEAD)', async function() { + it('Sym. encrypted session key reading/writing test vector (AEAD, EAX)', async function() { // From https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b20923/back.mkd#sample-aead-eax-encryption-and-decryption const nodeCrypto = util.getNodeCrypto();