From f629ddcb3150a7df73cb1d385c72f1472f2d3608 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Sun, 5 May 2019 00:02:11 +0200 Subject: [PATCH 1/8] Fix reading and writing unencrypted V5 secret key packets --- src/packet/secret_key.js | 275 +++++++++++++++++++++++---------------- 1 file changed, 161 insertions(+), 114 deletions(-) diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 160a14f5..ddde4b15 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -47,13 +47,33 @@ function SecretKey(date=new Date()) { */ this.tag = enums.packet.secretKey; /** - * Encrypted secret-key data + * Secret-key data */ - this.encrypted = null; + this.keyMaterial = null; /** - * Indicator if secret-key data is encrypted. `this.isEncrypted === false` means data is available in decrypted form. + * Indicates whether secret-key data is encrypted. `this.isEncrypted === false` means data is available in decrypted form. */ this.isEncrypted = null; + /** + * S2K usage + * @type {Integer} + */ + this.s2k_usage = 0; + /** + * S2K object + * @type {type/s2k} + */ + this.s2k = null; + /** + * Symmetric algorithm + * @type {String} + */ + this.symmetric = 'aes256'; + /** + * AEAD algorithm + * @type {String} + */ + this.aead = 'eax'; } SecretKey.prototype = new publicKey(); @@ -99,31 +119,78 @@ function write_cleartext_params(params, algorithm) { */ SecretKey.prototype.read = function (bytes) { // - A Public-Key or Public-Subkey packet, as described above. - const len = this.readPublicKey(bytes); - - bytes = bytes.subarray(len, bytes.length); - + let i = this.readPublicKey(bytes); // - One octet indicating string-to-key usage conventions. Zero // indicates that the secret-key data is not encrypted. 255 or 254 // indicates that a string-to-key specifier is being given. Any // other value is a symmetric-key encryption algorithm identifier. - const isEncrypted = bytes[0]; + this.s2k_usage = bytes[i++]; - if (isEncrypted) { - this.encrypted = bytes; - this.isEncrypted = true; - } else { - // - Plain or encrypted multiprecision integers comprising the secret - // key data. These algorithm-specific fields are as described - // below. - const cleartext = bytes.subarray(1, -2); - if (!util.equalsUint8Array(util.write_checksum(cleartext), bytes.subarray(-2))) { + // - Only for a version 5 packet, a one-octet scalar octet count of + // the next 4 optional fields. + if (this.version === 5) { + i++; + } + + // - [Optional] If string-to-key usage octet was 255, 254, or 253, a + // one-octet symmetric encryption algorithm. + if (this.s2k_usage === 255 || this.s2k_usage === 254 || this.s2k_usage === 253) { + this.symmetric = bytes[i++]; + this.symmetric = enums.read(enums.symmetric, this.symmetric); + + // - [Optional] If string-to-key usage octet was 253, a one-octet + // AEAD algorithm. + if (this.s2k_usage === 253) { + this.aead = bytes[i++]; + this.aead = enums.read(enums.aead, this.aead); + } + + // - [Optional] If string-to-key usage octet was 255, 254, or 253, a + // string-to-key specifier. The length of the string-to-key + // specifier is implied by its type, as described above. + this.s2k = new type_s2k(); + i += this.s2k.read(bytes.subarray(i, bytes.length)); + + if (this.s2k.type === 'gnu-dummy') { + return; + } + } else if (this.s2k_usage) { + this.symmetric = this.s2k_usage; + this.symmetric = enums.read(enums.symmetric, this.symmetric); + } + + // - [Optional] If secret data is encrypted (string-to-key usage octet + // not zero), an Initial Vector (IV) of the same length as the + // cipher's block size. + if (this.s2k_usage) { + this.iv = bytes.subarray( + i, + i + crypto.cipher[this.symmetric].blockSize + ); + + i += this.iv.length; + } + + // - Only for a version 5 packet, a four-octet scalar octet count for + // the following key material. + if (this.version === 5) { + i += 4; + } + + // - Plain or encrypted multiprecision integers comprising the secret + // key data. These algorithm-specific fields are as described + // below. + this.keyMaterial = bytes.subarray(i); + this.isEncrypted = !!this.s2k_usage; + + if (!this.isEncrypted) { + const cleartext = this.keyMaterial.subarray(0, -2); + if (!util.equalsUint8Array(util.write_checksum(cleartext), this.keyMaterial.subarray(-2))) { throw new Error('Key checksum mismatch'); } const privParams = parse_cleartext_params(cleartext, this.algorithm); this.params = this.params.concat(privParams); - this.isEncrypted = false; } }; @@ -134,13 +201,51 @@ SecretKey.prototype.read = function (bytes) { SecretKey.prototype.write = function () { const arr = [this.writePublicKey()]; - if (!this.encrypted) { - arr.push(new Uint8Array([0])); - const cleartextParams = write_cleartext_params(this.params, this.algorithm); - arr.push(cleartextParams); - arr.push(util.write_checksum(cleartextParams)); - } else { - arr.push(this.encrypted); + arr.push(new Uint8Array([this.s2k_usage])); + + const optionalFieldsArr = []; + // - [Optional] If string-to-key usage octet was 255, 254, or 253, a + // one- octet symmetric encryption algorithm. + if (this.s2k_usage === 255 || this.s2k_usage === 254 || this.s2k_usage === 253) { + optionalFieldsArr.push(enums.write(enums.symmetric, this.symmetric)); + + // - [Optional] If string-to-key usage octet was 253, a one-octet + // AEAD algorithm. + if (this.s2k_usage === 253) { + optionalFieldsArr.push(enums.write(enums.aead, this.aead)); + } + + // - [Optional] If string-to-key usage octet was 255, 254, or 253, a + // string-to-key specifier. The length of the string-to-key + // specifier is implied by its type, as described above. + optionalFieldsArr.push(...this.s2k.write()); + } + + // - [Optional] If secret data is encrypted (string-to-key usage octet + // not zero), an Initial Vector (IV) of the same length as the + // cipher's block size. + if (this.s2k_usage) { + optionalFieldsArr.push(...this.iv); + } + + if (this.version === 5) { + arr.push(new Uint8Array([optionalFieldsArr.length])); + } + arr.push(new Uint8Array(optionalFieldsArr)); + + if (!this.s2k || this.s2k.type !== 'gnu-dummy') { + if (!this.s2k_usage) { + const cleartextParams = write_cleartext_params(this.params, this.algorithm); + this.keyMaterial = util.concatUint8Array([ + cleartextParams, + util.write_checksum(cleartextParams) + ]); + } + + if (this.version === 5) { + arr.push(util.writeNumber(this.keyMaterial.length, 4)); + } + arr.push(this.keyMaterial); } return util.concatUint8Array(arr); @@ -164,48 +269,36 @@ SecretKey.prototype.isDecrypted = function() { * @async */ SecretKey.prototype.encrypt = async function (passphrase) { - if (this.isDecrypted() && this.encrypted) { // gnu-dummy + if (this.s2k && this.s2k.type === 'gnu-dummy') { return false; } if (this.isDecrypted() && !passphrase) { - this.encrypted = null; + this.s2k_usage = 0; return false; } else if (!passphrase) { throw new Error('The key must be decrypted before removing passphrase protection.'); } - const s2k = new type_s2k(); - s2k.salt = await crypto.random.getRandomBytes(8); - const symmetric = 'aes256'; + this.s2k = new type_s2k(); + this.s2k.salt = await crypto.random.getRandomBytes(8); const cleartext = write_cleartext_params(this.params, this.algorithm); - const key = await produceEncryptionKey(s2k, passphrase, symmetric); - const blockLen = crypto.cipher[symmetric].blockSize; - const iv = await crypto.random.getRandomBytes(blockLen); - - let arr; + const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric); + const blockLen = crypto.cipher[this.symmetric].blockSize; + this.iv = await crypto.random.getRandomBytes(blockLen); if (this.version === 5) { - const aead = 'eax'; - const optionalFields = util.concatUint8Array([new Uint8Array([enums.write(enums.symmetric, symmetric), enums.write(enums.aead, aead)]), s2k.write(), iv]); - arr = [new Uint8Array([253, optionalFields.length])]; - arr.push(optionalFields); - const mode = crypto[aead]; - const modeInstance = await mode(symmetric, key); - const encrypted = await modeInstance.encrypt(cleartext, iv.subarray(0, mode.ivLength), new Uint8Array()); - arr.push(util.writeNumber(encrypted.length, 4)); - arr.push(encrypted); + this.s2k_usage = 253; + const mode = crypto[this.aead]; + const modeInstance = await mode(this.symmetric, key); + this.keyMaterial = await modeInstance.encrypt(cleartext, this.iv.subarray(0, mode.ivLength), new Uint8Array()); } else { - arr = [new Uint8Array([254, enums.write(enums.symmetric, symmetric)])]; - arr.push(s2k.write()); - arr.push(iv); - arr.push(crypto.cfb.encrypt(symmetric, key, util.concatUint8Array([ + this.s2k_usage = 254; + this.keyMaterial = crypto.cfb.encrypt(this.symmetric, key, util.concatUint8Array([ cleartext, await crypto.hash.sha1(cleartext) - ]), iv)); + ]), this.iv); } - - this.encrypted = util.concatUint8Array(arr); return true; }; @@ -225,87 +318,40 @@ async function produceEncryptionKey(s2k, passphrase, algorithm) { * @async */ SecretKey.prototype.decrypt = async function (passphrase) { + if (this.s2k.type === 'gnu-dummy') { + this.isEncrypted = false; + return false; + } + if (this.isDecrypted()) { throw new Error('Key packet is already decrypted.'); } - let i = 0; - let symmetric; - let aead; let key; - - const s2k_usage = this.encrypted[i++]; - - // - Only for a version 5 packet, a one-octet scalar octet count of - // the next 4 optional fields. - if (this.version === 5) { - i++; - } - - // - [Optional] If string-to-key usage octet was 255, 254, or 253, a - // one-octet symmetric encryption algorithm. - if (s2k_usage === 255 || s2k_usage === 254 || s2k_usage === 253) { - symmetric = this.encrypted[i++]; - symmetric = enums.read(enums.symmetric, symmetric); - - // - [Optional] If string-to-key usage octet was 253, a one-octet - // AEAD algorithm. - if (s2k_usage === 253) { - aead = this.encrypted[i++]; - aead = enums.read(enums.aead, aead); - } - - // - [Optional] If string-to-key usage octet was 255, 254, or 253, a - // string-to-key specifier. The length of the string-to-key - // specifier is implied by its type, as described above. - const s2k = new type_s2k(); - i += s2k.read(this.encrypted.subarray(i, this.encrypted.length)); - - if (s2k.type === 'gnu-dummy') { - this.isEncrypted = false; - return false; - } - key = await produceEncryptionKey(s2k, passphrase, symmetric); + if (this.s2k_usage === 255 || this.s2k_usage === 254 || this.s2k_usage === 253) { + key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric); } else { - symmetric = s2k_usage; - symmetric = enums.read(enums.symmetric, symmetric); key = await crypto.hash.md5(passphrase); } - // - [Optional] If secret data is encrypted (string-to-key usage octet - // not zero), an Initial Vector (IV) of the same length as the - // cipher's block size. - const iv = this.encrypted.subarray( - i, - i + crypto.cipher[symmetric].blockSize - ); - - i += iv.length; - - // - Only for a version 5 packet, a four-octet scalar octet count for - // the following key material. - if (this.version === 5) { - i += 4; - } - - const ciphertext = this.encrypted.subarray(i, this.encrypted.length); let cleartext; - if (aead) { - const mode = crypto[aead]; + if (this.s2k_usage === 253) { + const mode = crypto[this.aead]; try { - const modeInstance = await mode(symmetric, key); - cleartext = await modeInstance.decrypt(ciphertext, iv.subarray(0, mode.ivLength), new Uint8Array()); + const modeInstance = await mode(this.symmetric, key); + cleartext = await modeInstance.decrypt(this.keyMaterial, this.iv.subarray(0, mode.ivLength), new Uint8Array()); } catch(err) { if (err.message === 'Authentication tag mismatch') { throw new Error('Incorrect key passphrase: ' + err.message); } + throw err; } } else { - const cleartextWithHash = await crypto.cfb.decrypt(symmetric, key, ciphertext, iv); + const cleartextWithHash = await crypto.cfb.decrypt(this.symmetric, key, this.keyMaterial, this.iv); let hash; let hashlen; - if (s2k_usage === 255) { + if (this.s2k_usage === 255) { hashlen = 2; cleartext = cleartextWithHash.subarray(0, -hashlen); hash = util.write_checksum(cleartext); @@ -323,7 +369,8 @@ SecretKey.prototype.decrypt = async function (passphrase) { const privParams = parse_cleartext_params(cleartext, this.algorithm); this.params = this.params.concat(privParams); this.isEncrypted = false; - this.encrypted = null; + this.keyMaterial = null; + this.s2k_usage = 0; return true; }; @@ -338,7 +385,7 @@ SecretKey.prototype.generate = async function (bits, curve) { * Clear private params, return to initial state */ SecretKey.prototype.clearPrivateParams = function () { - if (!this.encrypted) { + if (!this.keyMaterial) { throw new Error('If secret key is not encrypted, clearing private params is irreversible.'); } const algo = enums.write(enums.publicKey, this.algorithm); From 735d6d088f96419ae016adf84c50f843f6d2deb3 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Mon, 3 Jun 2019 16:46:26 +0200 Subject: [PATCH 2/8] Implement V5 signatures --- src/cleartext.js | 4 +-- src/key.js | 5 ++-- src/message.js | 27 +++++++++-------- src/packet/literal.js | 19 +++++++++--- src/packet/one_pass_signature.js | 16 ++++------ src/packet/signature.js | 51 ++++++++++++++++++++++---------- test/general/openpgp.js | 36 ++++++++++++++++++++++ 7 files changed, 112 insertions(+), 46 deletions(-) diff --git a/src/cleartext.js b/src/cleartext.js index 93850a54..4f7c73c9 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -89,7 +89,7 @@ CleartextMessage.prototype.signDetached = async function(privateKeys, signature= const literalDataPacket = new packet.Literal(); literalDataPacket.setText(this.text); - return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds)); + return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds, true)); }; /** @@ -115,7 +115,7 @@ CleartextMessage.prototype.verifyDetached = function(signature, keys, date=new D const literalDataPacket = new packet.Literal(); // we assume that cleartext signature is generated based on UTF8 cleartext literalDataPacket.setText(this.text); - return createVerificationObjects(signatureList, [literalDataPacket], keys, date); + return createVerificationObjects(signatureList, [literalDataPacket], keys, date, true); }; /** diff --git a/src/key.js b/src/key.js index 26033bfd..a783352f 100644 --- a/src/key.js +++ b/src/key.js @@ -922,9 +922,10 @@ User.prototype.isRevoked = async function(primaryKey, certificate, key, date=new * @param {Object} signatureProperties (optional) properties to write on the signature packet before signing * @param {Date} date (optional) override the creationtime of the signature * @param {Object} userId (optional) user ID + * @param {Object} detached (optional) whether to create a detached signature packet * @returns {module:packet/signature} signature packet */ -export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userId) { +export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userId, detached=false) { if (!signingKeyPacket.isDecrypted()) { throw new Error('Private key is not decrypted.'); } @@ -932,7 +933,7 @@ export async function createSignaturePacket(dataToSign, privateKey, signingKeyPa Object.assign(signaturePacket, signatureProperties); signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userId); - await signaturePacket.sign(signingKeyPacket, dataToSign); + await signaturePacket.sign(signingKeyPacket, dataToSign, detached); return signaturePacket; } diff --git a/src/message.js b/src/message.js index 30f4e2e6..72d82482 100644 --- a/src/message.js +++ b/src/message.js @@ -474,7 +474,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new }); packetlist.push(literalDataPacket); - packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature, date)); + packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, false)); return new Message(packetlist); }; @@ -513,7 +513,7 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null, if (!literalDataPacket) { throw new Error('No literal data packet to sign.'); } - return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds)); + return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds, true)); }; /** @@ -523,10 +523,11 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null, * @param {Signature} signature (optional) any existing detached signature to append * @param {Date} date (optional) override the creationtime of the signature * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] + * @param {Boolean} detached (optional) whether to create detached signature packets * @returns {Promise} list of signature packets * @async */ -export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date(), userIds=[]) { +export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date(), userIds=[], detached=false) { const packetlist = new packet.List(); // If data packet was created from Uint8Array, use binary, otherwise use text @@ -543,7 +544,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig throw new Error(`Could not find valid signing key packet in key ${ privateKey.getKeyId().toHex()}`); } - return createSignaturePacket(literalDataPacket, privateKey, signingKey.keyPacket, { signatureType }, date, userId); + return createSignaturePacket(literalDataPacket, privateKey, signingKey.keyPacket, { signatureType }, date, userId, detached); })).then(signatureList => { signatureList.forEach(signaturePacket => packetlist.push(signaturePacket)); }); @@ -578,7 +579,7 @@ Message.prototype.verify = async function(keys, date=new Date(), streaming) { onePassSig.correspondingSigReject = reject; }); onePassSig.signatureData = stream.fromAsync(async () => (await onePassSig.correspondingSig).signatureData); - onePassSig.hashed = await onePassSig.hash(onePassSig.signatureType, literalDataList[0], undefined, streaming); + onePassSig.hashed = await onePassSig.hash(onePassSig.signatureType, literalDataList[0], undefined, false, streaming); })); msg.packets.stream = stream.transformPair(msg.packets.stream, async (readable, writable) => { const reader = stream.getReader(readable); @@ -598,9 +599,9 @@ Message.prototype.verify = async function(keys, date=new Date(), streaming) { await writer.abort(e); } }); - return createVerificationObjects(onePassSigList, literalDataList, keys, date); + return createVerificationObjects(onePassSigList, literalDataList, keys, date, false); } - return createVerificationObjects(signatureList, literalDataList, keys, date); + return createVerificationObjects(signatureList, literalDataList, keys, date, false); }; /** @@ -618,7 +619,7 @@ Message.prototype.verifyDetached = function(signature, keys, date=new Date()) { throw new Error('Can only verify message with one literal data packet.'); } const signatureList = signature.packets; - return createVerificationObjects(signatureList, literalDataList, keys, date); + return createVerificationObjects(signatureList, literalDataList, keys, date, true); }; /** @@ -628,11 +629,12 @@ Message.prototype.verifyDetached = function(signature, keys, date=new Date()) { * @param {Array} keys array of keys to verify signatures * @param {Date} date Verify the signature against the given date, * i.e. check signature creation time < date < expiration time + * @param {Boolean} detached (optional) whether to verify detached signature packets * @returns {Promise>} list of signer's keyid and validity of signature * @async */ -async function createVerificationObject(signature, literalDataList, keys, date=new Date()) { +async function createVerificationObject(signature, literalDataList, keys, date=new Date(), detached=false) { let primaryKey = null; let signingKey = null; await Promise.all(keys.map(async function(key) { @@ -651,7 +653,7 @@ async function createVerificationObject(signature, literalDataList, keys, date=n if (!signingKey) { return null; } - const verified = await signature.verify(signingKey.keyPacket, signature.signatureType, literalDataList[0]); + const verified = await signature.verify(signingKey.keyPacket, signature.signatureType, literalDataList[0], detached); const sig = await signaturePacket; if (sig.isExpired(date) || !( sig.created >= signingKey.getCreationTime() && @@ -689,15 +691,16 @@ async function createVerificationObject(signature, literalDataList, keys, date=n * @param {Array} keys array of keys to verify signatures * @param {Date} date Verify the signature against the given date, * i.e. check signature creation time < date < expiration time + * @param {Boolean} detached (optional) whether to verify detached signature packets * @returns {Promise>} list of signer's keyid and validity of signature * @async */ -export async function createVerificationObjects(signatureList, literalDataList, keys, date=new Date()) { +export async function createVerificationObjects(signatureList, literalDataList, keys, date=new Date(), detached=false) { return Promise.all(signatureList.filter(function(signature) { return ['text', 'binary'].includes(enums.read(enums.signature, signature.signatureType)); }).map(async function(signature) { - return createVerificationObject(signature, literalDataList, keys, date); + return createVerificationObject(signature, literalDataList, keys, date, detached); })); } diff --git a/src/packet/literal.js b/src/packet/literal.js index b2586615..6750da4a 100644 --- a/src/packet/literal.js +++ b/src/packet/literal.js @@ -139,19 +139,30 @@ Literal.prototype.read = async function(bytes) { }; /** - * Creates a string representation of the packet + * Creates a Uint8Array representation of the packet, excluding the data * - * @returns {Uint8Array | ReadableStream} Uint8Array representation of the packet + * @returns {Uint8Array} Uint8Array representation of the packet */ -Literal.prototype.write = function() { +Literal.prototype.writeHeader = function() { const filename = util.encode_utf8(this.filename); const filename_length = new Uint8Array([filename.length]); const format = new Uint8Array([enums.write(enums.literal, this.format)]); const date = util.writeDate(this.date); + + return util.concatUint8Array([format, filename_length, filename, date]); +}; + +/** + * Creates a Uint8Array representation of the packet + * + * @returns {Uint8Array | ReadableStream} Uint8Array representation of the packet + */ +Literal.prototype.write = function() { + const header = this.writeHeader(); const data = this.getBytes(); - return util.concat([format, filename_length, filename, date, data]); + return util.concat([header, data]); }; export default Literal; diff --git a/src/packet/one_pass_signature.js b/src/packet/one_pass_signature.js index 91b607ad..d7a79d27 100644 --- a/src/packet/one_pass_signature.js +++ b/src/packet/one_pass_signature.js @@ -16,12 +16,14 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA /** + * @requires web-stream-tools * @requires packet/signature * @requires type/keyid * @requires enums * @requires util */ +import stream from 'web-stream-tools'; import Signature from './signature'; import type_keyid from '../type/keyid'; import enums from '../enums'; @@ -127,18 +129,12 @@ OnePassSignature.prototype.postCloneTypeFix = function() { this.issuerKeyId = type_keyid.fromClone(this.issuerKeyId); }; -OnePassSignature.prototype.hash = function() { - const version = this.version; - this.version = 4; - try { - return Signature.prototype.hash.apply(this, arguments); - } finally { - this.version = version; - } -}; +OnePassSignature.prototype.hash = Signature.prototype.hash; OnePassSignature.prototype.toHash = Signature.prototype.toHash; OnePassSignature.prototype.toSign = Signature.prototype.toSign; -OnePassSignature.prototype.calculateTrailer = Signature.prototype.calculateTrailer; +OnePassSignature.prototype.calculateTrailer = function(...args) { + return stream.fromAsync(async () => (await this.correspondingSig).calculateTrailer(...args)); +}; OnePassSignature.prototype.verify = async function() { const correspondingSig = await this.correspondingSig; diff --git a/src/packet/signature.js b/src/packet/signature.js index 5b01da2a..45cb2261 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -47,7 +47,7 @@ import config from '../config'; */ function Signature(date=new Date()) { this.tag = enums.packet.signature; - this.version = 4; + this.version = 4; // This is set to 5 below if we sign with a V5 key. this.signatureType = null; this.hashAlgorithm = null; this.publicKeyAlgorithm = null; @@ -106,7 +106,7 @@ Signature.prototype.read = function (bytes) { let i = 0; this.version = bytes[i++]; - if (this.version !== 4) { + if (this.version !== 4 && this.version !== 5) { throw new Error('Version ' + this.version + ' of the signature is unsupported.'); } @@ -148,15 +148,19 @@ Signature.prototype.write = function () { * Signs provided data. This needs to be done prior to serialization. * @param {module:packet.SecretKey} key private key used to sign the message. * @param {Object} data Contains packets to be signed. + * @param {Boolean} detached (optional) whether to create a detached signature * @returns {Promise} * @async */ -Signature.prototype.sign = async function (key, data) { +Signature.prototype.sign = async function (key, data, detached=false) { const signatureType = enums.write(enums.signature, this.signatureType); const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); - const arr = [new Uint8Array([4, signatureType, publicKeyAlgorithm, hashAlgorithm])]; + if (key.version === 5) { + this.version = 5; + } + const arr = [new Uint8Array([this.version, signatureType, publicKeyAlgorithm, hashAlgorithm])]; if (key.version === 5) { // We could also generate this subpacket for version 4 keys, but for @@ -172,8 +176,8 @@ Signature.prototype.sign = async function (key, data) { this.signatureData = util.concat(arr); - const toHash = this.toHash(signatureType, data); - const hash = await this.hash(signatureType, data, toHash); + const toHash = this.toHash(signatureType, data, detached); + const hash = await this.hash(signatureType, data, toHash, detached); this.signedHashValue = stream.slice(stream.clone(hash), 0, 2); @@ -628,28 +632,42 @@ Signature.prototype.toSign = function (type, data) { }; -Signature.prototype.calculateTrailer = function () { +Signature.prototype.calculateTrailer = function (data, detached) { let length = 0; return stream.transform(stream.clone(this.signatureData), value => { length += value.length; }, () => { - const first = new Uint8Array([4, 0xFF]); //Version, ? - return util.concat([first, util.writeNumber(length, 4)]); + const arr = []; + if (this.version === 5 && (this.signatureType === enums.signature.binary || this.signatureType === enums.signature.text)) { + if (detached) { + arr.push(new Uint8Array(6)); + } else { + arr.push(data.writeHeader()); + } + } + arr.push(new Uint8Array([this.version, 0xFF])); + if (this.version === 5) { + arr.push(new Uint8Array(4)); + } + arr.push(util.writeNumber(length, 4)); + // For v5, this should really be writeNumber(length, 8) rather than the + // hardcoded 4 zero bytes above + return util.concat(arr); }); }; -Signature.prototype.toHash = function(signatureType, data) { +Signature.prototype.toHash = function(signatureType, data, detached=false) { const bytes = this.toSign(signatureType, data); - return util.concat([bytes, this.signatureData, this.calculateTrailer()]); + return util.concat([bytes, this.signatureData, this.calculateTrailer(data, detached)]); }; -Signature.prototype.hash = async function(signatureType, data, toHash, streaming=true) { +Signature.prototype.hash = async function(signatureType, data, toHash, detached=false, streaming=true) { const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); - if (!toHash) toHash = this.toHash(signatureType, data); + if (!toHash) toHash = this.toHash(signatureType, data, detached); if (!streaming && util.isStream(toHash)) { - return stream.fromAsync(async () => this.hash(signatureType, data, await stream.readToEnd(toHash))); + return stream.fromAsync(async () => this.hash(signatureType, data, await stream.readToEnd(toHash), detached)); } return crypto.hash.digest(hashAlgorithm, toHash); }; @@ -661,10 +679,11 @@ Signature.prototype.hash = async function(signatureType, data, toHash, streaming * module:packet.SecretSubkey|module:packet.SecretKey} key the public key to verify the signature * @param {module:enums.signature} signatureType expected signature type * @param {String|Object} data data which on the signature applies + * @param {Boolean} detached (optional) whether to verify a detached signature * @returns {Promise} True if message is verified, else false. * @async */ -Signature.prototype.verify = async function (key, signatureType, data) { +Signature.prototype.verify = async function (key, signatureType, data, detached=false) { const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); @@ -677,7 +696,7 @@ Signature.prototype.verify = async function (key, signatureType, data) { if (this.hashed) { hash = this.hashed; } else { - toHash = this.toHash(signatureType, data); + toHash = this.toHash(signatureType, data, detached); hash = await this.hash(signatureType, data, toHash); } hash = await stream.readToEnd(hash); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index faa44e28..65146234 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -1305,6 +1305,42 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { }); }); + it('should encrypt/sign and decrypt/verify with generated key and detached signatures', function () { + const genOpt = { + userIds: [{ name: 'Test User', email: 'text@example.com' }], + numBits: 512 + }; + if (openpgp.util.getWebCryptoAll()) { genOpt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + + return openpgp.generateKey(genOpt).then(async function(newKey) { + const newPublicKey = await openpgp.key.readArmored(newKey.publicKeyArmored); + const newPrivateKey = await openpgp.key.readArmored(newKey.privateKeyArmored); + + const encOpt = { + message: openpgp.message.fromText(plaintext), + publicKeys: newPublicKey.keys, + privateKeys: newPrivateKey.keys, + detached: true + }; + const decOpt = { + privateKeys: newPrivateKey.keys[0], + publicKeys: newPublicKey.keys + }; + return openpgp.encrypt(encOpt).then(async function (encrypted) { + decOpt.message = await openpgp.message.readArmored(encrypted.data); + decOpt.signature = await openpgp.signature.readArmored(encrypted.signature); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect); + return openpgp.decrypt(decOpt); + }).then(async function (decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.true; + const signingKey = await newPrivateKey.keys[0].getSigningKey(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); + }); + }); + }); + it('should encrypt/sign and decrypt/verify with null string input', function () { const encOpt = { message: openpgp.message.fromText(''), From c8729a02958f7903a7f5e8836e8d9953d0f07ebf Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Fri, 7 Jun 2019 16:04:11 +0200 Subject: [PATCH 3/8] Fix serializing GNU stripped-keys --- src/packet/secret_key.js | 7 ++++++- src/type/s2k.js | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index ddde4b15..accb225b 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -224,7 +224,7 @@ SecretKey.prototype.write = function () { // - [Optional] If secret data is encrypted (string-to-key usage octet // not zero), an Initial Vector (IV) of the same length as the // cipher's block size. - if (this.s2k_usage) { + if (this.s2k_usage && this.s2k.type !== 'gnu-dummy') { optionalFieldsArr.push(...this.iv); } @@ -385,6 +385,11 @@ SecretKey.prototype.generate = async function (bits, curve) { * Clear private params, return to initial state */ SecretKey.prototype.clearPrivateParams = function () { + if (this.s2k && this.s2k.type === 'gnu-dummy') { + this.isEncrypted = true; + return; + } + if (!this.keyMaterial) { throw new Error('If secret key is not encrypted, clearing private params is irreversible.'); } diff --git a/src/type/s2k.js b/src/type/s2k.js index 02d86f36..99ea32ca 100644 --- a/src/type/s2k.js +++ b/src/type/s2k.js @@ -117,6 +117,10 @@ S2K.prototype.read = function (bytes) { * @returns {Uint8Array} binary representation of s2k */ S2K.prototype.write = function () { + if (this.type === 'gnu-dummy') { + return new Uint8Array([101, 0, ...util.str_to_Uint8Array('GNU'), 1]); + } + const arr = [new Uint8Array([enums.write(enums.s2k, this.type), enums.write(enums.hash, this.algorithm)])]; switch (this.type) { From 8312399f9d2ef3c8e7ca0dd65a865c503f5ea6f0 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Fri, 7 Jun 2019 17:10:39 +0200 Subject: [PATCH 4/8] Update V5 key hashing for signatures to rfc4880bis-07 --- src/packet/public_key.js | 12 ++++++------ src/packet/signature.js | 2 +- test/general/key.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 2d6c6895..30be0afa 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -169,11 +169,14 @@ PublicKey.prototype.write = function () { PublicKey.prototype.writePublicKey = PublicKey.prototype.write; /** - * Write an old version packet - it's used by some of the internal routines. + * Write packet in order to be hashed; either for a signature or a fingerprint. */ -PublicKey.prototype.writeOld = function () { +PublicKey.prototype.writeForHash = function (version) { const bytes = this.writePublicKey(); + if (version === 5) { + return util.concatUint8Array([new Uint8Array([0x9A]), util.writeNumber(bytes.length, 4), bytes]); + } return util.concatUint8Array([new Uint8Array([0x99]), util.writeNumber(bytes.length, 2), bytes]); }; @@ -218,13 +221,10 @@ PublicKey.prototype.getFingerprintBytes = function () { if (this.fingerprint) { return this.fingerprint; } - let toHash; + const toHash = this.writeForHash(this.version); if (this.version === 5) { - const bytes = this.writePublicKey(); - toHash = util.concatUint8Array([new Uint8Array([0x9A]), util.writeNumber(bytes.length, 4), bytes]); this.fingerprint = Sha256.bytes(toHash); } else if (this.version === 4) { - toHash = this.writeOld(); this.fingerprint = Sha1.bytes(toHash); } return this.fingerprint; diff --git a/src/packet/signature.js b/src/packet/signature.js index 45cb2261..e425cef5 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -618,7 +618,7 @@ Signature.prototype.toSign = function (type, data) { if (data.key === undefined) { throw new Error('Key packet is required for this signature.'); } - return data.key.writeOld(); + return data.key.writeForHash(this.version); case t.key_revocation: return this.toSign(t.key, data); diff --git a/test/general/key.js b/test/general/key.js index 075a87da..491c72fb 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1644,6 +1644,23 @@ iCzXvu4VCEMxMYOkOV4857v958DC7Z7W6BYEYpa9DP0O2zAwDmhu/kRFfKVQ -----END PGP PUBLIC KEY BLOCK----- `; +const v5_sample_key = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd +fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA +Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC +X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI +CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9 +M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA +MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD +AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF +GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb +DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7 +TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw== +=IiS2 +-----END PGP PRIVATE KEY BLOCK----- +`; + function versionSpecificTests() { it('Preferences of generated key', function() { const testPref = function(key) { @@ -2162,6 +2179,18 @@ function versionSpecificTests() { }); }); }); + + it('Parses V5 sample key', async function() { + // sec ed25519 2019-03-20 [SC] + // 19347BC9872464025F99DF3EC2E0000ED9884892E1F7B3EA4C94009159569B54 + // uid emma.goldman@example.net + // ssb cv25519 2019-03-20 [E] + // E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965 + const { keys: [key] } = await openpgp.key.readArmored(v5_sample_key); + expect(key.primaryKey.getFingerprint()).to.equal('19347bc9872464025f99df3ec2e0000ed9884892e1f7b3ea4c94009159569b54'); + expect(key.subKeys[0].getFingerprint()).to.equal('e4557c2b02ffbf4b04f87401ec336af7133d0f85be7fd09baefd9caeb8c93965'); + expect(await key.verifyPrimaryKey()).to.equal(openpgp.enums.keyStatus.valid); + }); } describe('Key', function() { From 9bb1710a9ffe379e22e71a6e1ffa8e1120629cbb Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Fri, 7 Jun 2019 17:11:45 +0200 Subject: [PATCH 5/8] Remove unused writeOldHeader function --- src/packet/packet.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/packet/packet.js b/src/packet/packet.js index 11a62695..ee4e85f4 100644 --- a/src/packet/packet.js +++ b/src/packet/packet.js @@ -98,23 +98,6 @@ export default { return util.concatUint8Array([this.writeTag(tag_type), this.writeSimpleLength(length)]); }, - /** - * Writes a packet header Version 3 with the given tag_type and length to a - * string - * - * @param {Integer} tag_type Tag type - * @param {Integer} length Length of the payload - * @returns {String} String of the header - */ - writeOldHeader: function(tag_type, length) { - if (length < 256) { - return new Uint8Array([0x80 | (tag_type << 2), length]); - } else if (length < 65536) { - return util.concatUint8Array([new Uint8Array([0x80 | (tag_type << 2) | 1]), util.writeNumber(length, 2)]); - } - return util.concatUint8Array([new Uint8Array([0x80 | (tag_type << 2) | 2]), util.writeNumber(length, 4)]); - }, - /** * Whether the packet type supports partial lengths per RFC4880 * @param {Integer} tag_type Tag type From 80c535eeb7511b9f239ef882cbcfacc50d7d7990 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Mon, 12 Aug 2019 15:32:35 +0200 Subject: [PATCH 6/8] Separate config option to use V5 keys from AEAD config option --- src/config/config.js | 8 ++++++++ src/key.js | 3 +++ src/packet/public_key.js | 2 +- test/general/key.js | 8 ++++++-- test/general/openpgp.js | 6 +++++- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 9e53b925..f88c70df 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -76,6 +76,14 @@ export default { * @property {Integer} aead_chunk_size_byte */ aead_chunk_size_byte: 12, + /** + * Use V5 keys. + * **NOT INTEROPERABLE WITH OTHER OPENPGP IMPLEMENTATIONS** + * **FUTURE OPENPGP.JS VERSIONS MAY BREAK COMPATIBILITY WHEN USING THIS OPTION** + * @memberof module:config + * @property {Boolean} v5_keys + */ + v5_keys: false, /** * {@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) diff --git a/src/key.js b/src/key.js index a783352f..430c0f03 100644 --- a/src/key.js +++ b/src/key.js @@ -1526,6 +1526,9 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { if (config.aead_protect && config.aead_protect_version === 4) { signaturePacket.features || (signaturePacket.features = [0]); signaturePacket.features[0] |= enums.features.aead; + } + if (config.v5_keys) { + signaturePacket.features || (signaturePacket.features = [0]); signaturePacket.features[0] |= enums.features.v5_keys; } if (options.keyExpirationTime > 0) { diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 30be0afa..ec743da0 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -56,7 +56,7 @@ function PublicKey(date=new Date()) { * Packet version * @type {Integer} */ - this.version = config.aead_protect && config.aead_protect_version === 4 ? 5 : 4; + this.version = config.v5_keys ? 5 : 4; /** * Key creation date. * @type {Date} diff --git a/test/general/key.js b/test/general/key.js index 491c72fb..d56e6a62 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1680,7 +1680,7 @@ function versionSpecificTests() { expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha256, hash.sha512, hash.sha1]); const compr = openpgp.enums.compression; expect(key.users[0].selfCertifications[0].preferredCompressionAlgorithms).to.eql([compr.zlib, compr.zip]); - expect(key.users[0].selfCertifications[0].features).to.eql(openpgp.config.aead_protect && openpgp.config.aead_protect_version === 4 ? [7] : [1]); + expect(key.users[0].selfCertifications[0].features).to.eql(openpgp.config.v5_keys ? [7] : [1]); }; const opt = {numBits: 512, userIds: 'test ', passphrase: 'hello'}; if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys @@ -1717,7 +1717,7 @@ function versionSpecificTests() { expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha224, hash.sha256, hash.sha512, hash.sha1]); const compr = openpgp.enums.compression; expect(key.users[0].selfCertifications[0].preferredCompressionAlgorithms).to.eql([compr.zlib, compr.zip]); - expect(key.users[0].selfCertifications[0].features).to.eql(openpgp.config.aead_protect && openpgp.config.aead_protect_version === 4 ? [7] : [1]); + expect(key.users[0].selfCertifications[0].features).to.eql(openpgp.config.v5_keys ? [7] : [1]); }; const opt = {numBits: 512, userIds: 'test ', passphrase: 'hello'}; if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys @@ -2208,17 +2208,21 @@ describe('Key', function() { describe('V4', versionSpecificTests); + let v5_keysVal; let aead_protectVal; let aead_protect_versionVal; tryTests('V5', versionSpecificTests, { if: !openpgp.config.saucelabs, beforeEach: function() { + v5_keysVal = openpgp.config.v5_keys; aead_protectVal = openpgp.config.aead_protect; aead_protect_versionVal = openpgp.config.aead_protect_version; + openpgp.config.v5_keys = true; openpgp.config.aead_protect = true; openpgp.config.aead_protect_version = 4; }, afterEach: function() { + openpgp.config.v5_keys = v5_keysVal; openpgp.config.aead_protect = aead_protectVal; openpgp.config.aead_protect_version = aead_protect_versionVal; } diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 65146234..e6758b09 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -696,6 +696,7 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { let aead_protect_versionVal; let aead_modeVal; let aead_chunk_size_byteVal; + let v5_keysVal; beforeEach(async function() { publicKey = await openpgp.key.readArmored(pub_key); @@ -723,6 +724,7 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { aead_protect_versionVal = openpgp.config.aead_protect_version; aead_modeVal = openpgp.config.aead_mode; aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; + v5_keysVal = openpgp.config.v5_keys; }); afterEach(function() { @@ -732,6 +734,7 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { openpgp.config.aead_protect_version = aead_protect_versionVal; openpgp.config.aead_mode = aead_modeVal; openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; + openpgp.config.v5_keys = v5_keysVal; }); it('Configuration', async function() { @@ -854,11 +857,12 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { } }); - tryTests('GCM mode (draft04)', tests, { + tryTests('GCM mode (V5 keys)', tests, { if: true, beforeEach: function() { openpgp.config.aead_protect = true; openpgp.config.aead_mode = openpgp.enums.aead.experimental_gcm; + openpgp.config.v5_keys = true; // Monkey-patch AEAD feature flag publicKey.keys[0].users[0].selfCertifications[0].features = [7]; From a184ef6ec420e364233d65726d7b7f986aacab6e Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Mon, 12 Aug 2019 15:44:50 +0200 Subject: [PATCH 7/8] Remove support for the previous draft00 AEAD --- README.md | 2 - src/config/config.js | 10 +- src/key.js | 4 +- src/message.js | 4 +- src/openpgp.js | 5 +- src/packet/sym_encrypted_aead_protected.js | 144 ++++++++++----------- src/packet/sym_encrypted_session_key.js | 2 +- test/general/key.js | 8 +- test/general/openpgp.js | 17 +-- test/general/packet.js | 35 +---- test/general/streaming.js | 8 +- 11 files changed, 88 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index 888a4624..e9d09d57 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,6 @@ library to convert back and forth between them. openpgp.config.aead_mode = openpgp.enums.aead.experimental_gcm // **Non-standard**, fastest ``` - We previously also implemented an [earlier version](https://tools.ietf.org/html/draft-ford-openpgp-format-00) of the draft (using GCM), which you could enable by setting `openpgp.config.aead_protect = true`. If you need to stay compatible with that version, you need to set `openpgp.config.aead_protect_version = 0`. - * For environments that don't provide native crypto, the library falls back to [asm.js](https://caniuse.com/#feat=asmjs) implementations of AES, SHA-1, and SHA-256. We use [Rusha](https://github.com/srijs/rusha) and [asmCrypto Lite](https://github.com/openpgpjs/asmcrypto-lite) (a minimal subset of asmCrypto.js built specifically for OpenPGP.js). diff --git a/src/config/config.js b/src/config/config.js index f88c70df..b584001b 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -48,19 +48,11 @@ export default { * Use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption. * **NOT INTEROPERABLE WITH OTHER OPENPGP IMPLEMENTATIONS** * **FUTURE OPENPGP.JS VERSIONS MAY BREAK COMPATIBILITY WHEN USING THIS OPTION** + * @see {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07|RFC4880bis-07} * @memberof module:config * @property {Boolean} aead_protect */ aead_protect: false, - /** - * Use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption. - * 0 means we implement a variant of {@link https://tools.ietf.org/html/draft-ford-openpgp-format-00|this IETF draft}. - * 4 means we implement {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04|RFC4880bis-04}. - * Note that this determines how AEAD packets are parsed even when aead_protect is set to false - * @memberof module:config - * @property {Integer} aead_protect_version - */ - aead_protect_version: 4, /** * Default Authenticated Encryption with Additional Data (AEAD) encryption mode * Only has an effect when aead_protect is set to true. diff --git a/src/key.js b/src/key.js index 430c0f03..0d441a91 100644 --- a/src/key.js +++ b/src/key.js @@ -1500,7 +1500,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { enums.symmetric.cast5, enums.symmetric.tripledes ], config.encryption_cipher); - if (config.aead_protect && config.aead_protect_version === 4) { + if (config.aead_protect) { signaturePacket.preferredAeadAlgorithms = createdPreferredAlgos([ enums.aead.eax, enums.aead.ocb @@ -1523,7 +1523,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { signaturePacket.features = [0]; signaturePacket.features[0] |= enums.features.modification_detection; } - if (config.aead_protect && config.aead_protect_version === 4) { + if (config.aead_protect) { signaturePacket.features || (signaturePacket.features = [0]); signaturePacket.features[0] |= enums.features.aead; } diff --git a/src/message.js b/src/message.js index 72d82482..5b2bc51c 100644 --- a/src/message.js +++ b/src/message.js @@ -299,7 +299,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard sessionKey = sessionKey.data; } else if (keys && keys.length) { symAlgo = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date, userIds)); - if (config.aead_protect && config.aead_protect_version === 4 && await isAeadSupported(keys, date, userIds)) { + if (config.aead_protect && await isAeadSupported(keys, date, userIds)) { aeadAlgo = enums.read(enums.aead, await getPreferredAlgo('aead', keys, date, userIds)); } } else if (passwords && passwords.length) { @@ -315,7 +315,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date, userIds); - if (config.aead_protect && (config.aead_protect_version !== 4 || aeadAlgo)) { + if (config.aead_protect && aeadAlgo) { symEncryptedPacket = new packet.SymEncryptedAEADProtected(); symEncryptedPacket.aeadAlgorithm = aeadAlgo; } else if (config.integrity_protect) { diff --git a/src/openpgp.js b/src/openpgp.js index e86b32ee..4d10c8a6 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -712,8 +712,5 @@ function onError(message, error) { * @returns {Boolean} If authenticated encryption should be used */ function nativeAEAD() { - return config.aead_protect && ( - ((config.aead_protect_version !== 4 || config.aead_mode === enums.aead.experimental_gcm) && util.getWebCrypto()) || - (config.aead_protect_version === 4 && config.aead_mode === enums.aead.eax && util.getWebCrypto()) - ); + return config.aead_protect && (config.aead_mode === enums.aead.eax || config.aead_mode === enums.aead.experimental_gcm) && util.getWebCrypto(); } diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index 6332c058..70bbdaef 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -63,13 +63,9 @@ SymEncryptedAEADProtected.prototype.read = async function (bytes) { if (await reader.readByte() !== VERSION) { // The only currently defined value is 1. throw new Error('Invalid packet version.'); } - if (config.aead_protect_version === 4) { - this.cipherAlgo = await reader.readByte(); - this.aeadAlgo = await reader.readByte(); - this.chunkSizeByte = await reader.readByte(); - } else { - this.aeadAlgo = enums.aead.experimental_gcm; - } + this.cipherAlgo = await reader.readByte(); + this.aeadAlgo = await reader.readByte(); + this.chunkSizeByte = await reader.readByte(); const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; this.iv = await reader.readBytes(mode.ivLength); this.encrypted = reader.remainder(); @@ -81,10 +77,7 @@ SymEncryptedAEADProtected.prototype.read = async function (bytes) { * @returns {Uint8Array | ReadableStream} The encrypted payload */ SymEncryptedAEADProtected.prototype.write = function () { - if (config.aead_protect_version === 4) { - return util.concat([new Uint8Array([this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte]), this.iv, this.encrypted]); - } - return util.concat([new Uint8Array([this.version]), this.iv, this.encrypted]); + return util.concat([new Uint8Array([this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte]), this.iv, this.encrypted]); }; /** @@ -96,9 +89,6 @@ SymEncryptedAEADProtected.prototype.write = function () { * @async */ SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key, streaming) { - if (config.aead_protect_version !== 4) { - this.cipherAlgo = enums.write(enums.symmetric, sessionKeyAlgorithm); - } await this.packets.read(await this.crypt('decrypt', key, stream.clone(this.encrypted), streaming), streaming); return true; }; @@ -112,7 +102,7 @@ SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorith */ SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key, streaming) { this.cipherAlgo = enums.write(enums.symmetric, sessionKeyAlgorithm); - this.aeadAlgo = config.aead_protect_version === 4 ? enums.write(enums.aead, this.aeadAlgorithm) : enums.aead.experimental_gcm; + this.aeadAlgo = enums.write(enums.aead, this.aeadAlgorithm); const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV this.chunkSizeByte = config.aead_chunk_size_byte; @@ -133,69 +123,65 @@ SymEncryptedAEADProtected.prototype.crypt = async function (fn, key, data, strea const cipher = enums.read(enums.symmetric, this.cipherAlgo); const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; const modeInstance = await mode(cipher, key); - if (config.aead_protect_version === 4) { - const tagLengthIfDecrypting = fn === 'decrypt' ? 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 | this.tag, this.version, this.cipherAlgo, this.aeadAlgo, 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) => { - const reader = stream.getReader(readable); - const buffer = new TransformStream({}, { - highWaterMark: streaming ? util.getHardwareConcurrency() * 2 ** (config.aead_chunk_size_byte + 6) : Infinity, - size: array => array.length - }); - stream.pipe(buffer.readable, writable); - const writer = stream.getWriter(buffer.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); - } 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); - done = true; - } - cryptedBytes += chunk.length - tagLengthIfDecrypting; - queuedBytes += 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 -= chunk.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); - } + const tagLengthIfDecrypting = fn === 'decrypt' ? 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 | this.tag, this.version, this.cipherAlgo, this.aeadAlgo, 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) => { + const reader = stream.getReader(readable); + const buffer = new TransformStream({}, { + highWaterMark: streaming ? util.getHardwareConcurrency() * 2 ** (config.aead_chunk_size_byte + 6) : Infinity, + size: array => array.length }); - } else { - return modeInstance[fn](await stream.readToEnd(data), this.iv); - } + stream.pipe(buffer.readable, writable); + const writer = stream.getWriter(buffer.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); + } 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); + done = true; + } + cryptedBytes += chunk.length - tagLengthIfDecrypting; + queuedBytes += 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 -= chunk.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); + } + }); }; diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index a526c3f5..b1422080 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -49,7 +49,7 @@ import util from '../util'; */ function SymEncryptedSessionKey() { this.tag = enums.packet.symEncryptedSessionKey; - this.version = config.aead_protect && config.aead_protect_version === 4 ? 5 : 4; + this.version = config.aead_protect ? 5 : 4; this.sessionKey = null; this.sessionKeyEncryptionAlgorithm = null; this.sessionKeyAlgorithm = 'aes256'; diff --git a/test/general/key.js b/test/general/key.js index d56e6a62..f6e6d09f 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1672,7 +1672,7 @@ function versionSpecificTests() { expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_storage).to.equal(keyFlags.encrypt_storage); const sym = openpgp.enums.symmetric; expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes256, sym.aes128, sym.aes192, sym.cast5, sym.tripledes]); - if (openpgp.config.aead_protect && openpgp.config.aead_protect_version === 4) { + if (openpgp.config.aead_protect) { const aead = openpgp.enums.aead; expect(key.users[0].selfCertifications[0].preferredAeadAlgorithms).to.eql([aead.eax, aead.ocb]); } @@ -1709,7 +1709,7 @@ function versionSpecificTests() { expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_storage).to.equal(keyFlags.encrypt_storage); const sym = openpgp.enums.symmetric; expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes192, sym.aes256, sym.aes128, sym.cast5, sym.tripledes]); - if (openpgp.config.aead_protect && openpgp.config.aead_protect_version === 4) { + if (openpgp.config.aead_protect) { const aead = openpgp.enums.aead; expect(key.users[0].selfCertifications[0].preferredAeadAlgorithms).to.eql([aead.experimental_gcm, aead.eax, aead.ocb]); } @@ -2210,21 +2210,17 @@ describe('Key', function() { let v5_keysVal; let aead_protectVal; - let aead_protect_versionVal; tryTests('V5', versionSpecificTests, { if: !openpgp.config.saucelabs, beforeEach: function() { v5_keysVal = openpgp.config.v5_keys; aead_protectVal = openpgp.config.aead_protect; - aead_protect_versionVal = openpgp.config.aead_protect_version; openpgp.config.v5_keys = true; openpgp.config.aead_protect = true; - openpgp.config.aead_protect_version = 4; }, afterEach: function() { openpgp.config.v5_keys = v5_keysVal; openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; } }); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index e6758b09..1d76dd40 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -693,7 +693,6 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { let zero_copyVal; let use_nativeVal; let aead_protectVal; - let aead_protect_versionVal; let aead_modeVal; let aead_chunk_size_byteVal; let v5_keysVal; @@ -721,7 +720,6 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { zero_copyVal = openpgp.config.zero_copy; use_nativeVal = openpgp.config.use_native; aead_protectVal = openpgp.config.aead_protect; - aead_protect_versionVal = openpgp.config.aead_protect_version; aead_modeVal = openpgp.config.aead_mode; aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; v5_keysVal = openpgp.config.v5_keys; @@ -731,7 +729,6 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { openpgp.config.zero_copy = zero_copyVal; openpgp.config.use_native = use_nativeVal; openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; openpgp.config.aead_mode = aead_modeVal; openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; openpgp.config.v5_keys = v5_keysVal; @@ -849,14 +846,6 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { } }); - tryTests('GCM mode', tests, { - if: !openpgp.config.saucelabs, - beforeEach: function() { - openpgp.config.aead_protect = true; - openpgp.config.aead_protect_version = 0; - } - }); - tryTests('GCM mode (V5 keys)', tests, { if: true, beforeEach: function() { @@ -1199,7 +1188,7 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { return openpgp.encrypt(encOpt).then(async function (encrypted) { expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); decOpt.message = await openpgp.message.readArmored(encrypted.data); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect && openpgp.config.aead_protect_version !== 4); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(false); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1222,7 +1211,7 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { return openpgp.encrypt(encOpt).then(async function (encrypted) { expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); decOpt.message = await openpgp.message.readArmored(encrypted.data); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect && openpgp.config.aead_protect_version !== 4); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(false); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1264,7 +1253,7 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() { }; return openpgp.encrypt(encOpt).then(async function (encrypted) { decOpt.message = await openpgp.message.readArmored(encrypted.data); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect && openpgp.config.aead_protect_version !== 4); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(false); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); diff --git a/test/general/packet.js b/test/general/packet.js index 53a40fb8..6451aaf1 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -150,11 +150,9 @@ describe("Packet", function() { }); }); - it('Sym. encrypted AEAD protected packet (draft04)', async function() { + it('Sym. encrypted AEAD protected packet (AEAD)', async function() { let aead_protectVal = openpgp.config.aead_protect; - let aead_protect_versionVal = openpgp.config.aead_protect_version; openpgp.config.aead_protect = true; - openpgp.config.aead_protect_version = 4; const testText = input.createSomeMessage(); 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]); @@ -177,7 +175,6 @@ describe("Packet", function() { expect(await openpgp.stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); } finally { openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; } }); @@ -201,17 +198,15 @@ describe("Packet", function() { return cryptStub; } - it('Sym. encrypted AEAD protected packet is encrypted in parallel (GCM, draft04)', async function() { + it('Sym. encrypted AEAD protected packet is encrypted in parallel (AEAD, GCM)', async function() { const webCrypto = openpgp.util.getWebCrypto(); if (!webCrypto) return; const encryptStub = cryptStub(webCrypto, 'encrypt'); const decryptStub = cryptStub(webCrypto, 'decrypt'); let aead_protectVal = openpgp.config.aead_protect; - let aead_protect_versionVal = openpgp.config.aead_protect_version; let aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; openpgp.config.aead_protect = true; - openpgp.config.aead_protect_version = 4; openpgp.config.aead_chunk_size_byte = 0; const testText = input.createSomeMessage(); @@ -238,14 +233,13 @@ describe("Packet", function() { expect(decryptStub.callCount > 1).to.be.true; } finally { openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; encryptStub.restore(); decryptStub.restore(); } }); - it('Sym. encrypted AEAD protected packet test vector (draft04)', async function() { + it('Sym. encrypted AEAD protected packet test vector (AEAD)', async function() { // From https://gitlab.com/openpgp-wg/rfc4880bis/commit/00b20923e6233fb6ff1666ecd5acfefceb32907d let packetBytes = openpgp.util.hex_to_Uint8Array(` @@ -257,10 +251,8 @@ describe("Packet", function() { `.replace(/\s+/g, '')); let aead_protectVal = openpgp.config.aead_protect; - let aead_protect_versionVal = openpgp.config.aead_protect_version; let aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; openpgp.config.aead_protect = true; - openpgp.config.aead_protect_version = 4; 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, '')); @@ -290,7 +282,6 @@ describe("Packet", function() { expect(await openpgp.stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); } finally { openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; randomBytesStub.restore(); } @@ -495,11 +486,9 @@ describe("Packet", function() { expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); }); - it('Sym. encrypted session key reading/writing (draft04)', async function() { + it('Sym. encrypted session key reading/writing (AEAD)', async function() { let aead_protectVal = openpgp.config.aead_protect; - let aead_protect_versionVal = openpgp.config.aead_protect_version; openpgp.config.aead_protect = true; - openpgp.config.aead_protect_version = 4; try { const passphrase = 'hello'; @@ -533,19 +522,16 @@ describe("Packet", function() { expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); } finally { openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; } }); - it('Sym. encrypted session key reading/writing test vector (EAX, draft04)', async function() { + it('Sym. encrypted session key reading/writing test vector (EAX, AEAD)', async function() { // From https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b20923/back.mkd#sample-aead-eax-encryption-and-decryption let aead_protectVal = openpgp.config.aead_protect; - let aead_protect_versionVal = openpgp.config.aead_protect_version; 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 = true; - openpgp.config.aead_protect_version = 4; openpgp.config.aead_chunk_size_byte = 14; openpgp.config.s2k_iteration_count_byte = 0x90; @@ -608,22 +594,19 @@ describe("Packet", function() { expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); } finally { openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; openpgp.config.s2k_iteration_count_byte = s2k_iteration_count_byteVal; randomBytesStub.restore(); } }); - it('Sym. encrypted session key reading/writing test vector (OCB, draft04)', async function() { + it('Sym. encrypted session key reading/writing test vector (AEAD, OCB)', async function() { // From https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b20923/back.mkd#sample-aead-ocb-encryption-and-decryption let aead_protectVal = openpgp.config.aead_protect; - let aead_protect_versionVal = openpgp.config.aead_protect_version; 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 = true; - openpgp.config.aead_protect_version = 4; openpgp.config.aead_chunk_size_byte = 14; openpgp.config.s2k_iteration_count_byte = 0x90; @@ -687,7 +670,6 @@ describe("Packet", function() { expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); } finally { openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; openpgp.config.s2k_iteration_count_byte = s2k_iteration_count_byteVal; randomBytesStub.restore(); @@ -873,11 +855,9 @@ V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+ }); }); - it('Writing and encryption of a secret key packet. (draft04)', async function() { + it('Writing and encryption of a secret key packet. (AEAD)', async function() { let aead_protectVal = openpgp.config.aead_protect; - let aead_protect_versionVal = openpgp.config.aead_protect_version; openpgp.config.aead_protect = true; - openpgp.config.aead_protect_version = 4; const key = new openpgp.packet.List(); key.push(new openpgp.packet.SecretKey()); @@ -905,7 +885,6 @@ V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+ expect(key[0].params.toString()).to.equal(key2[0].params.toString()); } finally { openpgp.config.aead_protect = aead_protectVal; - openpgp.config.aead_protect_version = aead_protect_versionVal; } }); diff --git a/test/general/streaming.js b/test/general/streaming.js index e9ee2a1b..72aab6fe 100644 --- a/test/general/streaming.js +++ b/test/general/streaming.js @@ -353,7 +353,7 @@ function tests() { expect(verified.signatures).to.exist.and.have.length(1); }); - it('Encrypt and decrypt larger message roundtrip (draft04)', async function() { + it('Encrypt and decrypt larger message roundtrip (AEAD)', async function() { let aead_protectValue = openpgp.config.aead_protect; let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; openpgp.config.aead_protect = true; @@ -382,7 +382,7 @@ function tests() { } }); - it('Encrypt and decrypt larger text message roundtrip (draft04)', async function() { + it('Encrypt and decrypt larger text message roundtrip (AEAD)', async function() { let aead_protectValue = openpgp.config.aead_protect; let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; openpgp.config.aead_protect = true; @@ -449,7 +449,7 @@ function tests() { expect(canceled).to.be.true; }); - it('Input stream should be canceled when canceling decrypted stream (draft04)', async function() { + it('Input stream should be canceled when canceling decrypted stream (AEAD)', async function() { let aead_protectValue = openpgp.config.aead_protect; let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; openpgp.config.aead_protect = true; @@ -527,7 +527,7 @@ function tests() { expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100); }); - it("Don't pull entire input stream when we're not pulling decrypted stream (draft04)", async function() { + it("Don't pull entire input stream when we're not pulling decrypted stream (AEAD)", async function() { let aead_protectValue = openpgp.config.aead_protect; let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; openpgp.config.aead_protect = true; From 8f54c00fd3dd43a27e14ad5efba11498e92886c9 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Mon, 12 Aug 2019 16:29:31 +0200 Subject: [PATCH 8/8] Bump link to draft spec in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9d09d57..38a9324f 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ library to convert back and forth between them. * If the user's browser supports [native WebCrypto](https://caniuse.com/#feat=cryptography) via the `window.crypto.subtle` API, this will be used. Under Node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is used. This can be deactivated by setting `openpgp.config.use_native = false`. -* The library implements the [IETF proposal](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04) for authenticated encryption using native AES-EAX, OCB, or GCM. This makes symmetric encryption up to 30x faster on supported platforms. Since the specification has not been finalized and other OpenPGP implementations haven't adopted it yet, the feature is currently behind a flag. **Note: activating this setting can break compatibility with other OpenPGP implementations, and also with future versions of OpenPGP.js. Don't use it with messages you want to store on disk or in a database.** You can enable it by setting `openpgp.config.aead_protect = true`. +* The library implements the [IETF proposal](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07) for authenticated encryption using native AES-EAX, OCB, or GCM. This makes symmetric encryption up to 30x faster on supported platforms. Since the specification has not been finalized and other OpenPGP implementations haven't adopted it yet, the feature is currently behind a flag. **Note: activating this setting can break compatibility with other OpenPGP implementations, and also with future versions of OpenPGP.js. Don't use it with messages you want to store on disk or in a database.** You can enable it by setting `openpgp.config.aead_protect = true`. You can change the AEAD mode by setting one of the following options: