From a1196af7d879d1be437b494447ee39096a07d1e9 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:07:52 +0100 Subject: [PATCH 1/9] Add ML-KEM (draft) + X25519 Use ML-KEM instead of Kyber, implement message decryption --- package-lock.json | 28 ++++ package.json | 1 + src/crypto/crypto.js | 44 +++++- src/crypto/public_key/index.js | 5 +- src/crypto/public_key/post_quantum/index.js | 5 + .../public_key/post_quantum/kem/ecc_kem.js | 53 +++++++ .../public_key/post_quantum/kem/index.js | 1 + src/crypto/public_key/post_quantum/kem/kem.js | 73 ++++++++++ .../public_key/post_quantum/kem/ml_kem.js | 43 ++++++ src/enums.js | 2 + src/key/helper.js | 4 +- .../public_key_encrypted_session_key.js | 12 +- test/crypto/index.js | 2 + test/crypto/postQuantum.js | 136 ++++++++++++++++++ 14 files changed, 401 insertions(+), 8 deletions(-) create mode 100644 src/crypto/public_key/post_quantum/index.js create mode 100644 src/crypto/public_key/post_quantum/kem/ecc_kem.js create mode 100644 src/crypto/public_key/post_quantum/kem/index.js create mode 100644 src/crypto/public_key/post_quantum/kem/kem.js create mode 100644 src/crypto/public_key/post_quantum/kem/ml_kem.js create mode 100644 test/crypto/postQuantum.js diff --git a/package-lock.json b/package-lock.json index 066b886f3..f79c23dd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@noble/curves": "^1.6.0", "@noble/ed25519": "^1.7.3", "@noble/hashes": "^1.5.0", + "@openpgp/crystals-kyber-js": "^1.1.1", "@openpgp/jsdoc": "^3.6.11", "@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/tweetnacl": "^1.0.4-1", @@ -1025,6 +1026,33 @@ "node": ">=12.4.0" } }, + "node_modules/@openpgp/crystals-kyber-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@openpgp/crystals-kyber-js/-/crystals-kyber-js-1.1.1.tgz", + "integrity": "sha512-Q4azKQpc2/SEPendXQs6IpnD2RNlPY2b7nwg5VNZ05FYYICnqYeH8R/NaTLJ2kVpitAWJpiNRC7bh7VlWz1T8g==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@openpgp/crystals-kyber-js/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@openpgp/jsdoc": { "version": "3.6.11", "resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz", diff --git a/package.json b/package.json index f7ccdfd25..44ddd6851 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@noble/curves": "^1.6.0", "@noble/ed25519": "^1.7.3", "@noble/hashes": "^1.5.0", + "@openpgp/crystals-kyber-js": "^1.1.1", "@openpgp/jsdoc": "^3.6.11", "@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/tweetnacl": "^1.0.4-1", diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 490e34f94..5d8214c4e 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -96,6 +96,12 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, pri const c = await modeInstance.encrypt(data, iv, new Uint8Array()); return { aeadMode: new AEADEnum(aeadMode), iv, c: new ShortByteString(c) }; } + case enums.publicKey.pqc_mlkem_x25519: { + const { eccPublicKey, mlkemPublicKey } = publicParams; + const { eccCipherText, mlkemCipherText, wrappedKey } = await publicKey.postQuantum.kem.encrypt(keyAlgo, eccPublicKey, mlkemPublicKey, data); + const C = ECDHXSymmetricKey.fromObject({ algorithm: symmetricAlgo, wrappedKey }); + return { eccCipherText, mlkemCipherText, C }; + } default: return []; } @@ -115,8 +121,8 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, pri * @throws {Error} on sensitive decryption error, unless `randomPayload` is given * @async */ -export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams, sessionKeyParams, fingerprint, randomPayload) { - switch (algo) { +export async function publicKeyDecrypt(keyAlgo, publicKeyParams, privateKeyParams, sessionKeyParams, fingerprint, randomPayload) { + switch (keyAlgo) { case enums.publicKey.rsaEncryptSign: case enums.publicKey.rsaEncrypt: { const { c } = sessionKeyParams; @@ -146,7 +152,7 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams, throw new Error('AES session key expected'); } return publicKey.elliptic.ecdhX.decrypt( - algo, ephemeralPublicKey, C.wrappedKey, A, k); + keyAlgo, ephemeralPublicKey, C.wrappedKey, A, k); } case enums.publicKey.aead: { const { cipher: algo } = publicKeyParams; @@ -159,6 +165,12 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams, const modeInstance = await mode(algoValue, keyMaterial); return modeInstance.decrypt(c.data, iv, new Uint8Array()); } + case enums.publicKey.pqc_mlkem_x25519: { + const { eccSecretKey, mlkemSecretKey } = privateKeyParams; + const { eccPublicKey } = publicKeyParams; + const { eccCipherText, mlkemCipherText, C } = sessionKeyParams; + return publicKey.postQuantum.kem.decrypt(keyAlgo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, C.wrappedKey); + } default: throw new Error('Unknown public key encryption algorithm.'); } @@ -230,6 +242,11 @@ export function parsePublicKeyParams(algo, bytes) { const digest = bytes.subarray(read, read + digestLength); read += digestLength; return { read: read, publicParams: { cipher: algo, digest } }; } + case enums.publicKey.pqc_mlkem_x25519: { + const eccPublicKey = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.x25519)); read += eccPublicKey.length; + const mlkemPublicKey = util.readExactSubarray(bytes, read, read + 1184); read += mlkemPublicKey.length; + return { read, publicParams: { eccPublicKey, mlkemPublicKey } }; + } default: throw new UnsupportedError('Unknown public key encryption algorithm.'); } @@ -301,6 +318,11 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) { const keyMaterial = bytes.subarray(read, read + keySize); read += keySize; return { read, privateParams: { hashSeed, keyMaterial } }; } + case enums.publicKey.pqc_mlkem_x25519: { + const eccSecretKey = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.x25519)); read += eccSecretKey.length; + const mlkemSecretKey = util.readExactSubarray(bytes, read, read + 2400); read += mlkemSecretKey.length; + return { read, privateParams: { eccSecretKey, mlkemSecretKey } }; + } default: throw new UnsupportedError('Unknown public key encryption algorithm.'); } @@ -364,6 +386,12 @@ export function parseEncSessionKeyParams(algo, bytes) { return { aeadMode, iv, c }; } + case enums.publicKey.pqc_mlkem_x25519: { + const eccCipherText = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.x25519)); read += eccCipherText.length; + const mlkemCipherText = util.readExactSubarray(bytes, read, read + 1088); read += mlkemCipherText.length; + const C = new ECDHXSymmetricKey(); C.read(bytes.subarray(read)); + return { eccCipherText, mlkemCipherText, C }; // eccCipherText || mlkemCipherText || len(C) || C + } default: throw new UnsupportedError('Unknown public key encryption algorithm.'); } @@ -383,7 +411,8 @@ export function serializeParams(algo, params) { enums.publicKey.ed448, enums.publicKey.x448, enums.publicKey.aead, - enums.publicKey.hmac + enums.publicKey.hmac, + enums.publicKey.pqc_mlkem_x25519 ]); const orderedParams = Object.keys(params).map(name => { const param = params[name]; @@ -450,6 +479,11 @@ export async function generateParams(algo, bits, oid, symmetric) { const keyMaterial = generateSessionKey(symmetric); return createSymmetricParams(keyMaterial, new SymAlgoEnum(symmetric)); } + case enums.publicKey.pqc_mlkem_x25519: + return publicKey.postQuantum.kem.generate(algo).then(({ eccSecretKey, eccPublicKey, mlkemSecretKey, mlkemPublicKey }) => ({ + privateParams: { eccSecretKey, mlkemSecretKey }, + publicParams: { eccPublicKey, mlkemPublicKey } + })); case enums.publicKey.dsa: case enums.publicKey.elgamal: throw new Error('Unsupported algorithm for key generation.'); @@ -541,6 +575,8 @@ export async function validateParams(algo, publicParams, privateParams) { return keySize === keyMaterial.length && util.equalsUint8Array(digest, await hash.sha256(hashSeed)); } + case enums.publicKey.pqc_mlkem_x25519: + throw new Error('TODO'); default: throw new Error('Unknown public key algorithm.'); } diff --git a/src/crypto/public_key/index.js b/src/crypto/public_key/index.js index b20774abc..e5aa238ab 100644 --- a/src/crypto/public_key/index.js +++ b/src/crypto/public_key/index.js @@ -8,6 +8,7 @@ import * as elgamal from './elgamal'; import * as elliptic from './elliptic'; import * as dsa from './dsa'; import * as hmac from './hmac'; +import * as postQuantum from './post_quantum'; export default { /** @see module:crypto/public_key/rsa */ @@ -19,5 +20,7 @@ export default { /** @see module:crypto/public_key/dsa */ dsa: dsa, /** @see module:crypto/public_key/hmac */ - hmac: hmac + hmac: hmac, + /** @see module:crypto/public_key/post_quantum */ + postQuantum }; diff --git a/src/crypto/public_key/post_quantum/index.js b/src/crypto/public_key/post_quantum/index.js new file mode 100644 index 000000000..982b28be7 --- /dev/null +++ b/src/crypto/public_key/post_quantum/index.js @@ -0,0 +1,5 @@ +import * as kem from './kem/index'; + +export { + kem +}; diff --git a/src/crypto/public_key/post_quantum/kem/ecc_kem.js b/src/crypto/public_key/post_quantum/kem/ecc_kem.js new file mode 100644 index 000000000..04bb845ad --- /dev/null +++ b/src/crypto/public_key/post_quantum/kem/ecc_kem.js @@ -0,0 +1,53 @@ +import * as ecdhX from '../../elliptic/ecdh_x'; +import hash from '../../../hash'; +import util from '../../../../util'; +import enums from '../../../../enums'; + +export async function generate(algo) { + switch (algo) { + case enums.publicKey.pqc_mlkem_x25519: { + const { A, k } = await ecdhX.generate(enums.publicKey.x25519); + return { + eccPublicKey: A, + eccSecretKey: k + }; + } + default: + throw new Error('Unsupported KEM algorithm'); + } +} + +export async function encaps(eccAlgo, eccRecipientPublicKey) { + switch (eccAlgo) { + case enums.publicKey.pqc_mlkem_x25519: { + const { ephemeralPublicKey: eccCipherText, sharedSecret: eccSharedSecret } = await ecdhX.generateEphemeralEncryptionMaterial(enums.publicKey.x25519, eccRecipientPublicKey); + const eccKeyShare = await hash.sha3_256(util.concatUint8Array([ + eccSharedSecret, + eccCipherText, + eccRecipientPublicKey + ])); + return { + eccCipherText, + eccKeyShare + }; + } + default: + throw new Error('Unsupported KEM algorithm'); + } +} + +export async function decaps(eccAlgo, eccCipherText, eccSecretKey, eccPublicKey) { + switch (eccAlgo) { + case enums.publicKey.pqc_mlkem_x25519: { + const eccSharedSecret = await ecdhX.recomputeSharedSecret(enums.publicKey.x25519, eccCipherText, eccPublicKey, eccSecretKey); + const eccKeyShare = await hash.sha3_256(util.concatUint8Array([ + eccSharedSecret, + eccCipherText, + eccPublicKey + ])); + return eccKeyShare; + } + default: + throw new Error('Unsupported KEM algorithm'); + } +} diff --git a/src/crypto/public_key/post_quantum/kem/index.js b/src/crypto/public_key/post_quantum/kem/index.js new file mode 100644 index 000000000..298832a4f --- /dev/null +++ b/src/crypto/public_key/post_quantum/kem/index.js @@ -0,0 +1 @@ +export { generate, encrypt, decrypt } from './kem'; diff --git a/src/crypto/public_key/post_quantum/kem/kem.js b/src/crypto/public_key/post_quantum/kem/kem.js new file mode 100644 index 000000000..6fec55df3 --- /dev/null +++ b/src/crypto/public_key/post_quantum/kem/kem.js @@ -0,0 +1,73 @@ +import * as eccKem from './ecc_kem'; +import * as mlKem from './ml_kem'; +import * as aesKW from '../../../aes_kw'; +import util from '../../../../util'; +import enums from '../../../../enums'; + +export async function generate(algo) { + const { eccPublicKey, eccSecretKey } = await eccKem.generate(algo); + const { mlkemPublicKey, mlkemSecretKey } = await mlKem.generate(algo); + + return { eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSecretKey }; +} + +export async function encrypt(algo, eccPublicKey, mlkemPublicKey, sessioneKeyData) { + const { eccKeyShare, eccCipherText } = await eccKem.encaps(algo, eccPublicKey); + const { mlkemKeyShare, mlkemCipherText } = await mlKem.encaps(algo, mlkemPublicKey); + const fixedInfo = new Uint8Array([algo]); + const kek = await multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, 256); + const wrappedKey = await aesKW.wrap(enums.symmetric.aes256, kek, sessioneKeyData); // C + return { eccCipherText, mlkemCipherText, wrappedKey }; +} + +export async function decrypt(algo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, encryptedSessionKeyData) { + const eccKeyShare = await eccKem.decaps(algo, eccCipherText, eccSecretKey, eccPublicKey); + const mlkemKeyShare = await mlKem.decaps(algo, mlkemCipherText, mlkemSecretKey); + const fixedInfo = new Uint8Array([algo]); + const kek = await multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, 256); + const sessionKey = await aesKW.unwrap(enums.symmetric.aes256, kek, encryptedSessionKeyData); + return sessionKey; +} + +async function multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, outputBits) { + // multiKeyCombine(eccKeyShare, eccCipherText, + // mlkemKeyShare, mlkemCipherText, + // fixedInfo, oBits) + // + // Input: + // eccKeyShare - the ECC key share encoded as an octet string + // eccCipherText - the ECC ciphertext encoded as an octet string + // mlkemKeyShare - the ML-KEM key share encoded as an octet string + // mlkemCipherText - the ML-KEM ciphertext encoded as an octet string + // fixedInfo - the fixed information octet string + // oBits - the size of the output keying material in bits + // + // Constants: + // domSeparation - the UTF-8 encoding of the string + // "OpenPGPCompositeKeyDerivationFunction" + // counter - the fixed 4 byte value 0x00000001 + // customizationString - the UTF-8 encoding of the string "KDF" + if (outputBits !== 256) { + throw new Error('Unsupported output size'); + } + const { kmac256 } = await import('@noble/hashes/sha3-addons'); + // const { eccKeyShare, eccCiphertext } = await publicKey.pqc.kem.ecdhX(keyAlgo, publicParams.A); + // const { keyShare: mlkemKeyShare, cipherText: mlkemCipherText } = await publicKey.pqc.kem.ml(keyAlgo, publicParams.publicKey); + const eccData = util.concatUint8Array([eccKeyShare, eccCipherText]); // eccKeyShare || eccCipherText + const mlkemData = util.concatUint8Array([mlkemKeyShare, mlkemCipherText]); //mlkemKeyShare || mlkemCipherText + // const fixedInfo = new Uint8Array([keyAlgo]); + const encData = util.concatUint8Array([ + new Uint8Array([0, 0, 0, 1]), + eccData, + mlkemData, + fixedInfo + ]); // counter || eccData || mlkemData || fixedInfo + + const mb = kmac256( + util.encodeUTF8('OpenPGPCompositeKeyDerivationFunction'), + encData, + { personalization: util.encodeUTF8('KDF') } + ); + + return mb; +} diff --git a/src/crypto/public_key/post_quantum/kem/ml_kem.js b/src/crypto/public_key/post_quantum/kem/ml_kem.js new file mode 100644 index 000000000..a51d2fbf1 --- /dev/null +++ b/src/crypto/public_key/post_quantum/kem/ml_kem.js @@ -0,0 +1,43 @@ +import enums from '../../../../enums'; + +export async function generate(algo) { + switch (algo) { + case enums.publicKey.pqc_mlkem_x25519: { + const { MlKem768 } = await import('@openpgp/crystals-kyber-js'); + const kyberInstance = new MlKem768(); + const [encapsulationKey, decapsulationKey] = await kyberInstance.generateKeyPair(); + + return { mlkemPublicKey: encapsulationKey, mlkemSecretKey: decapsulationKey }; + } + default: + throw new Error('Unsupported KEM algorithm'); + } +} + +export async function encaps(algo, mlkemRecipientPublicKey) { + switch (algo) { + case enums.publicKey.pqc_mlkem_x25519: { + const { MlKem768 } = await import('@openpgp/crystals-kyber-js'); + const kyberInstance = new MlKem768(); + const [mlkemCipherText, mlkemKeyShare] = await kyberInstance.encap(mlkemRecipientPublicKey); + + return { mlkemCipherText, mlkemKeyShare }; + } + default: + throw new Error('Unsupported KEM algorithm'); + } +} + +export async function decaps(algo, mlkemCipherText, mlkemSecretKey) { + switch (algo) { + case enums.publicKey.pqc_mlkem_x25519: { + const { MlKem768 } = await import('@openpgp/crystals-kyber-js'); + const kyberInstance = new MlKem768(); + const mlkemKeyShare = await kyberInstance.decap(mlkemCipherText, mlkemSecretKey); + + return mlkemKeyShare; + } + default: + throw new Error('Unsupported KEM algorithm'); + } +} diff --git a/src/enums.js b/src/enums.js index 04402ed71..0872bcd4b 100644 --- a/src/enums.js +++ b/src/enums.js @@ -108,6 +108,8 @@ export default { ed25519: 27, /** Ed448 (Sign only) */ ed448: 28, + /** Post-quantum ML-KEM-768 + X25519 (Encrypt only) */ + pqc_mlkem_x25519: 105, /** Persistent symmetric keys: encryption algorithm */ aead: 100, /** Persistent symmetric keys: authentication algorithm */ diff --git a/src/key/helper.js b/src/key/helper.js index 7900dcf79..a94f1013e 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -480,6 +480,7 @@ export function validateEncryptionKeyPacket(keyPacket, signature, config) { case enums.publicKey.x25519: case enums.publicKey.x448: case enums.publicKey.aead: + case enums.publicKey.pqc_mlkem_x25519: if (!signature.keyFlags && !config.allowMissingKeyFlags) { throw new Error('None of the key flags is set: consider passing `config.allowMissingKeyFlags`'); } @@ -502,7 +503,8 @@ export function validateDecryptionKeyPacket(keyPacket, signature, config) { case enums.publicKey.elgamal: case enums.publicKey.ecdh: case enums.publicKey.x25519: - case enums.publicKey.x448: { + case enums.publicKey.x448: + case enums.publicKey.pqc_mlkem_x25519: { const isValidSigningKeyPacket = !signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.signData) !== 0; if (isValidSigningKeyPacket && config.allowInsecureDecryptionWithSigningKeys) { // This is only relevant for RSA keys, all other signing algorithms cannot decrypt diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index 00013d7b5..17a420dcf 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -21,6 +21,12 @@ import enums from '../enums'; import util from '../util'; import { UnsupportedError } from './packet'; +const algosWithV3CleartextSessionKeyAlgorithm = new Set([ + enums.publicKey.x25519, + enums.publicKey.x448, + enums.publicKey.pqc_mlkem_x25519 +]); + /** * Public-Key Encrypted Session Key Packets (Tag 1) * @@ -128,7 +134,7 @@ class PublicKeyEncryptedSessionKeyPacket { } this.publicKeyAlgorithm = bytes[offset++]; this.encrypted = crypto.parseEncSessionKeyParams(this.publicKeyAlgorithm, bytes.subarray(offset)); - if (this.publicKeyAlgorithm === enums.publicKey.x25519 || this.publicKeyAlgorithm === enums.publicKey.x448) { + if (algosWithV3CleartextSessionKeyAlgorithm.has(this.publicKeyAlgorithm)) { if (this.version === 3) { this.sessionKeyAlgorithm = enums.write(enums.symmetric, this.encrypted.C.algorithm); } else if (this.encrypted.C.algorithm !== null) { @@ -211,7 +217,7 @@ class PublicKeyEncryptedSessionKeyPacket { if (this.version === 3) { // v3 Montgomery curves have cleartext cipher algo - const hasEncryptedAlgo = this.publicKeyAlgorithm !== enums.publicKey.x25519 && this.publicKeyAlgorithm !== enums.publicKey.x448; + const hasEncryptedAlgo = !algosWithV3CleartextSessionKeyAlgorithm.has(this.publicKeyAlgorithm); this.sessionKeyAlgorithm = hasEncryptedAlgo ? sessionKeyAlgorithm : this.sessionKeyAlgorithm; if (sessionKey.length !== crypto.getCipherParams(this.sessionKeyAlgorithm).keySize) { @@ -240,6 +246,7 @@ function encodeSessionKey(version, keyAlgo, cipherAlgo, sessionKeyData) { ]); case enums.publicKey.x25519: case enums.publicKey.x448: + case enums.publicKey.pqc_mlkem_x25519: return sessionKeyData; default: throw new Error('Unsupported public key algorithm'); @@ -288,6 +295,7 @@ function decodeSessionKey(version, keyAlgo, decryptedData, randomSessionKey) { } case enums.publicKey.x25519: case enums.publicKey.x448: + case enums.publicKey.pqc_mlkem_x25519: return { sessionKeyAlgorithm: null, sessionKey: decryptedData diff --git a/test/crypto/index.js b/test/crypto/index.js index ac315a065..4f90ebf32 100644 --- a/test/crypto/index.js +++ b/test/crypto/index.js @@ -14,6 +14,7 @@ import testEAX from './eax'; import testOCB from './ocb'; import testRSA from './rsa'; import testValidate from './validate'; +import testPQC from './postQuantum'; export default () => describe('Crypto', function () { testBigInteger(); @@ -32,4 +33,5 @@ export default () => describe('Crypto', function () { testOCB(); testRSA(); testValidate(); + testPQC(); }); diff --git a/test/crypto/postQuantum.js b/test/crypto/postQuantum.js new file mode 100644 index 000000000..4e95ad3f5 --- /dev/null +++ b/test/crypto/postQuantum.js @@ -0,0 +1,136 @@ +import { use as chaiUse, expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/newline-after-import +chaiUse(chaiAsPromised); + +import openpgp from '../initOpenpgp.js'; +import { generateParams, publicKeyEncrypt, publicKeyDecrypt } from '../../src/crypto/crypto.js'; + +export default () => describe('PQC', function () { + it('ML-KEM + X25519 - Generate/encrypt/decrypt', async function () { + const sessionKey = { data: new Uint8Array(16).fill(1), algorithm: 'aes128' }; + + const { privateParams, publicParams } = await generateParams(openpgp.enums.publicKey.pqc_mlkem_x25519); + const encryptedSessionKeyParams = await publicKeyEncrypt(openpgp.enums.publicKey.pqc_mlkem_x25519, undefined, publicParams, null, sessionKey.data); + const decryptedSessionKey = await publicKeyDecrypt(openpgp.enums.publicKey.pqc_mlkem_x25519, publicParams, privateParams, encryptedSessionKeyParams); + expect(decryptedSessionKey).to.deep.equal(sessionKey.data); + }); + + it('ML-KEM + X25519 - Test vector', async function () { + const armoredMessage = `-----BEGIN PGP MESSAGE----- + +wcPUA/qyFfu1TBzoaRaET03uDIyjNeUBk84Hj31jS8SkuW7T/fY78wQZOA89 +MWj0RSoythNA9LJY7749Zlunxl896ws3tIfvy7FzMZ0/6kPEYvADZby9kwpv +X6Ia3DNAKlhiJfXM7oWXaPUQ/rZWnLm+eEnhmkXqE82Vz3nfml7h3k5LQJo6 +f43LX2+euqHDMhdxpffMD1INkABN1Ttzth/ezQ2/5Uob/YvxT4lROGmBTl9g +ua9xFN2Wo4cMxe5XxLYy8QUp+kH5Q+EdPmW9HYFu3YA1xFEALstmx9xM2oRg +KA84QZUVxcBmQekHbFiXkYpGVaZwqa/3oDqTW2tQvqDucShWkwXBryDYrPqm +cSOCdbOlyWxWHRTamgvBy0351dnEVTT+pm9DYDkpmdb0ToBpPebC8sy/WHWV +mRLLkvUPe+IGkgL/1ehNMPVvEACRScV5GviYvs26NruofJMQXZ3BbxARcZ++ +vOTVXl+kC7x+LbQOS4gAyN3RdfBSxrKPH2LBYI7Ccd1pUl/u0AnS6fksDT6n +ofECxwvkLks/CEmGyWaxNCfAVVAPz8S4g3f8jxhK4jtRWhD0PpY3MLCH3bug +JlYTzQm2J59NT9Knm+9PXSKny/5GVNOpfwFfWLgrK58y4Y5DfK0FVhEnLpwW +yqV49y4ujJEWp3Sd9NSCw+oIzi9XwHduj1JP7T+QZn3ED5uW596Q5+kHpiv1 +njg4JRJ7xJK9wC51I47kUmkppo0Er04DUx/1hfXxOmitVcmzDeo8CwVJsin5 +6ac9UPhYLHyTRy7do/cAfd5nxTNSUBI2DB903zbySaI3JGGgEzxOc0RmoHNF +Ku9ajrK9uBZrMMjT0tIbRD+oWiN4h1tYVrPJVZTLiWZGKaVr0PJCBN8rSI9x +Hx9/8Vapp9or0tN/XBTf4In0/gmO/Rr65/46Al8vkWrgcpY2TFuB8vdygUUc +rgboLHZejnWyUVO2+PbEhtbE2PM5sUBLCLK5U1xQphm72bwuwk2cxZihsk98 +L81bhl8z9yDNwei0uQ2fru+Gpp+6cKfBad0eXcC9K8WFwKpsrueDau3U8aG9 +gUqSFpAy9wVha/xagmdWYFSl1d4YtBanpGUhRrlD9iSRUhVJUhtwFAwqhpnE +/6KiJDpi43O9Mq1Re3YivAjR/PeuaWPzY9STqUZGnt9lQ4Wx9WlJw9FY8fU8 +QivWGW7inxhoN/T1or1Wx2iKdK6Oe6ZHBtKDypy9fwtam0SOyT31DFdq3pop +RXPWnbhzPZxgzGs79a42fFZbTD6iz50FeuJYGfHx8ahHPQlPbemS4JhJ0BcF +dtt9pzfJ3tAwxfAnhGJrJUJ/MOba+32uhjFYiwaU7o7ur2ak1K0eqqN3kAW4 +QwDQRpsJaMcO+JdpgSDVdYEkOMyFPLqf8sWUjjXUCVSrct9RI6T5QvUgsgdD +jE06bMep6ElLug6LonwnyjnaUbSgiKG1LoH1HN44ltX+sTiTcrQR0xyv4qDt +Jlswct6sjc0pCYRti9kpbvSKNkFIyao7JiXNo5lrJ5BSZLe9NlxLP6RP6yYl +KIuejZDSRAHCoPc7ma1tGPoVA8BaezF5gCdfaHla2hDaecWB83Ys0VzWCflp +q2MMqQ1h2UVunrMAQ/rYb8GQdkShXzPIF78mXeES +-----END PGP MESSAGE-----`; + const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=jowz +-----END PGP PRIVATE KEY BLOCK-----` }); + + const { data: decryptedData } = await openpgp.decrypt({ + message: await openpgp.readMessage({ armoredMessage }), + decryptionKeys: privateKey + }); + expect(decryptedData).to.equal('hello world\n'); + }); +}); From fc4382edc0deac4fbf317895cd7643a900885d74 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Sun, 5 Nov 2023 11:23:36 +0100 Subject: [PATCH 2/9] Add PQC key generation --- src/key/helper.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/key/helper.js b/src/key/helper.js index a94f1013e..44d9f821d 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -403,6 +403,13 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) { } switch (options.type) { + case 'pqc': + if (options.sign) { + throw new Error('Post-quantum signing algorithms are not yet supported.'); + } else { + options.algorithm = enums.publicKey.pqc_mlkem_x25519; + } + break; case 'ecc': // NB: this case also handles legacy eddsa and x25519 keys, based on `options.curve` try { options.curve = enums.write(enums.curve, options.curve); From 8570434ace391d141b9c27d60b20fd51e13bff8e Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:06:09 +0100 Subject: [PATCH 3/9] Add PQC KEM key validation --- src/crypto/crypto.js | 7 ++-- .../public_key/post_quantum/kem/ecc_kem.js | 9 ++++++ .../public_key/post_quantum/kem/index.js | 2 +- src/crypto/public_key/post_quantum/kem/kem.js | 7 ++++ .../public_key/post_quantum/kem/ml_kem.js | 14 ++++++++ test/crypto/validate.js | 32 +++++++++++++++++++ 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 5d8214c4e..592ab6811 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -575,8 +575,11 @@ export async function validateParams(algo, publicParams, privateParams) { return keySize === keyMaterial.length && util.equalsUint8Array(digest, await hash.sha256(hashSeed)); } - case enums.publicKey.pqc_mlkem_x25519: - throw new Error('TODO'); + case enums.publicKey.pqc_mlkem_x25519: { + const { eccSecretKey, mlkemSecretKey } = privateParams; + const { eccPublicKey, mlkemPublicKey } = publicParams; + return publicKey.postQuantum.kem.validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSecretKey); + } default: throw new Error('Unknown public key algorithm.'); } diff --git a/src/crypto/public_key/post_quantum/kem/ecc_kem.js b/src/crypto/public_key/post_quantum/kem/ecc_kem.js index 04bb845ad..4c41f2afe 100644 --- a/src/crypto/public_key/post_quantum/kem/ecc_kem.js +++ b/src/crypto/public_key/post_quantum/kem/ecc_kem.js @@ -51,3 +51,12 @@ export async function decaps(eccAlgo, eccCipherText, eccSecretKey, eccPublicKey) throw new Error('Unsupported KEM algorithm'); } } + +export async function validateParams(algo, eccPublicKey, eccSecretKey) { + switch (algo) { + case enums.publicKey.pqc_mlkem_x25519: + return ecdhX.validateParams(enums.publicKey.x25519, eccPublicKey, eccSecretKey); + default: + throw new Error('Unsupported KEM algorithm'); + } +} diff --git a/src/crypto/public_key/post_quantum/kem/index.js b/src/crypto/public_key/post_quantum/kem/index.js index 298832a4f..0a2da99b4 100644 --- a/src/crypto/public_key/post_quantum/kem/index.js +++ b/src/crypto/public_key/post_quantum/kem/index.js @@ -1 +1 @@ -export { generate, encrypt, decrypt } from './kem'; +export { generate, encrypt, decrypt, validateParams } from './kem'; diff --git a/src/crypto/public_key/post_quantum/kem/kem.js b/src/crypto/public_key/post_quantum/kem/kem.js index 6fec55df3..3ee2d5626 100644 --- a/src/crypto/public_key/post_quantum/kem/kem.js +++ b/src/crypto/public_key/post_quantum/kem/kem.js @@ -71,3 +71,10 @@ async function multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemC return mb; } + +export async function validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSecretKey) { + const eccValidationPromise = eccKem.validateParams(algo, eccPublicKey, eccSecretKey); + const mlkemValidationPromise = mlKem.validateParams(algo, mlkemPublicKey, mlkemSecretKey); + const valid = await eccValidationPromise && await mlkemValidationPromise; + return valid; +} diff --git a/src/crypto/public_key/post_quantum/kem/ml_kem.js b/src/crypto/public_key/post_quantum/kem/ml_kem.js index a51d2fbf1..12d62539f 100644 --- a/src/crypto/public_key/post_quantum/kem/ml_kem.js +++ b/src/crypto/public_key/post_quantum/kem/ml_kem.js @@ -1,4 +1,5 @@ import enums from '../../../../enums'; +import util from '../../../../util'; export async function generate(algo) { switch (algo) { @@ -41,3 +42,16 @@ export async function decaps(algo, mlkemCipherText, mlkemSecretKey) { throw new Error('Unsupported KEM algorithm'); } } + +export async function validateParams(algo, mlkemPublicKey, mlkemSecretKey) { + switch (algo) { + case enums.publicKey.pqc_mlkem_x25519: { + // TODO confirm this is the best option performance- & security-wise (is key re-generation faster?) + const { mlkemCipherText: validationCipherText, mlkemKeyShare: validationKeyShare } = await encaps(algo, mlkemPublicKey); + const resultingKeyShare = await decaps(algo, validationCipherText, mlkemSecretKey); + return util.equalsUint8Array(resultingKeyShare, validationKeyShare); + } + default: + throw new Error('Unsupported KEM algorithm'); + } +} diff --git a/test/crypto/validate.js b/test/crypto/validate.js index 8ed63089c..fde15d3ea 100644 --- a/test/crypto/validate.js +++ b/test/crypto/validate.js @@ -314,6 +314,38 @@ export default () => { }); }); + describe('PQC parameter validation', function() { + let pqcEncryptionSubkey; + before(async () => { + const key = await generatePrivateKeyObject({ type: 'symmetric', subkeys: [{ type: 'pqc' }] }); + pqcEncryptionSubkey = key.subkeys[0]; + }); + + async function cloneSubeyPacket(subkey) { + const subkeyPacket = new openpgp.SecretSubkeyPacket(); + await subkeyPacket.read(subkey.keyPacket.write()); + return subkeyPacket; + } + + it('generated params are valid', async function() { + await expect(pqcEncryptionSubkey.keyPacket.validate()).to.not.be.rejected; + }); + + it('detect invalid ML-KEM public key part', async function() { + const keyPacket = await cloneSubeyPacket(pqcEncryptionSubkey); + const { mlkemPublicKey } = keyPacket.publicParams; + mlkemPublicKey[0]++; + await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + }); + + it('detect invalid ECC-KEM key part', async function() { + const keyPacket = await cloneSubeyPacket(pqcEncryptionSubkey); + const { eccPublicKey } = keyPacket.publicParams; + eccPublicKey[0]++; + await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + }); + }); + describe('DSA parameter validation', function() { let dsaKey; before(async () => { From 9787c3052bd0346bd08db9c537c1f7850f0a7451 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:27:09 +0200 Subject: [PATCH 4/9] Update multiKeyCombine --- src/crypto/crypto.js | 27 ++- src/crypto/public_key/post_quantum/kem/kem.js | 65 ++--- test/crypto/postQuantum.js | 223 +++++++++--------- 3 files changed, 163 insertions(+), 152 deletions(-) diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 592ab6811..540d4c947 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -167,9 +167,9 @@ export async function publicKeyDecrypt(keyAlgo, publicKeyParams, privateKeyParam } case enums.publicKey.pqc_mlkem_x25519: { const { eccSecretKey, mlkemSecretKey } = privateKeyParams; - const { eccPublicKey } = publicKeyParams; + const { eccPublicKey, mlkemPublicKey } = publicKeyParams; const { eccCipherText, mlkemCipherText, C } = sessionKeyParams; - return publicKey.postQuantum.kem.decrypt(keyAlgo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, C.wrappedKey); + return publicKey.postQuantum.kem.decrypt(keyAlgo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, mlkemPublicKey, C.wrappedKey); } default: throw new Error('Unknown public key encryption algorithm.'); @@ -247,6 +247,11 @@ export function parsePublicKeyParams(algo, bytes) { const mlkemPublicKey = util.readExactSubarray(bytes, read, read + 1184); read += mlkemPublicKey.length; return { read, publicParams: { eccPublicKey, mlkemPublicKey } }; } + case enums.publicKey.pqc_mldsa_ed25519: { + const eccPublicKey = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.ed25519)); read += eccPublicKey.length; + const mldsaPublicKey = util.readExactSubarray(bytes, read, read + 1952); read += mldsaPublicKey.length; + return { read, publicParams: { eccPublicKey, mldsaPublicKey } }; + } default: throw new UnsupportedError('Unknown public key encryption algorithm.'); } @@ -323,6 +328,11 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) { const mlkemSecretKey = util.readExactSubarray(bytes, read, read + 2400); read += mlkemSecretKey.length; return { read, privateParams: { eccSecretKey, mlkemSecretKey } }; } + case enums.publicKey.pqc_mldsa_ed25519: { + const eccSecretKey = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.ed25519)); read += eccSecretKey.length; + const mldsaSecretKey = util.readExactSubarray(bytes, read, read + 4032); read += mldsaSecretKey.length; + return { read, privateParams: { eccSecretKey, mldsaSecretKey } }; + } default: throw new UnsupportedError('Unknown public key encryption algorithm.'); } @@ -412,7 +422,8 @@ export function serializeParams(algo, params) { enums.publicKey.x448, enums.publicKey.aead, enums.publicKey.hmac, - enums.publicKey.pqc_mlkem_x25519 + enums.publicKey.pqc_mlkem_x25519, + enums.publicKey.pqc_mldsa_ed25519 ]); const orderedParams = Object.keys(params).map(name => { const param = params[name]; @@ -484,6 +495,11 @@ export async function generateParams(algo, bits, oid, symmetric) { privateParams: { eccSecretKey, mlkemSecretKey }, publicParams: { eccPublicKey, mlkemPublicKey } })); + case enums.publicKey.pqc_mldsa_ed25519: + return publicKey.postQuantum.signature.generate(algo).then(({ eccSecretKey, eccPublicKey, mldsaSecretKey, mldsaPublicKey }) => ({ + privateParams: { eccSecretKey, mldsaSecretKey }, + publicParams: { eccPublicKey, mldsaPublicKey } + })); case enums.publicKey.dsa: case enums.publicKey.elgamal: throw new Error('Unsupported algorithm for key generation.'); @@ -580,6 +596,11 @@ export async function validateParams(algo, publicParams, privateParams) { const { eccPublicKey, mlkemPublicKey } = publicParams; return publicKey.postQuantum.kem.validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSecretKey); } + case enums.publicKey.pqc_mldsa_ed25519: { + const { eccSecretKey, mldsaSecretKey } = privateParams; + const { eccPublicKey, mldsaPublicKey } = publicParams; + return publicKey.postQuantum.signature.validateParams(algo, eccPublicKey, eccSecretKey, mldsaPublicKey, mldsaSecretKey); + } default: throw new Error('Unknown public key algorithm.'); } diff --git a/src/crypto/public_key/post_quantum/kem/kem.js b/src/crypto/public_key/post_quantum/kem/kem.js index 3ee2d5626..50c019522 100644 --- a/src/crypto/public_key/post_quantum/kem/kem.js +++ b/src/crypto/public_key/post_quantum/kem/kem.js @@ -3,6 +3,7 @@ import * as mlKem from './ml_kem'; import * as aesKW from '../../../aes_kw'; import util from '../../../../util'; import enums from '../../../../enums'; +import hash from '../../../hash'; export async function generate(algo) { const { eccPublicKey, eccSecretKey } = await eccKem.generate(algo); @@ -14,62 +15,38 @@ export async function generate(algo) { export async function encrypt(algo, eccPublicKey, mlkemPublicKey, sessioneKeyData) { const { eccKeyShare, eccCipherText } = await eccKem.encaps(algo, eccPublicKey); const { mlkemKeyShare, mlkemCipherText } = await mlKem.encaps(algo, mlkemPublicKey); - const fixedInfo = new Uint8Array([algo]); - const kek = await multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, 256); + const kek = await multiKeyCombine(algo, eccKeyShare, eccCipherText, eccPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey); const wrappedKey = await aesKW.wrap(enums.symmetric.aes256, kek, sessioneKeyData); // C return { eccCipherText, mlkemCipherText, wrappedKey }; } -export async function decrypt(algo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, encryptedSessionKeyData) { +export async function decrypt(algo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, mlkemPublicKey, encryptedSessionKeyData) { const eccKeyShare = await eccKem.decaps(algo, eccCipherText, eccSecretKey, eccPublicKey); const mlkemKeyShare = await mlKem.decaps(algo, mlkemCipherText, mlkemSecretKey); - const fixedInfo = new Uint8Array([algo]); - const kek = await multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, 256); + const kek = await multiKeyCombine(algo, eccKeyShare, eccCipherText, eccPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey); const sessionKey = await aesKW.unwrap(enums.symmetric.aes256, kek, encryptedSessionKeyData); return sessionKey; } -async function multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, outputBits) { - // multiKeyCombine(eccKeyShare, eccCipherText, - // mlkemKeyShare, mlkemCipherText, - // fixedInfo, oBits) - // - // Input: - // eccKeyShare - the ECC key share encoded as an octet string - // eccCipherText - the ECC ciphertext encoded as an octet string - // mlkemKeyShare - the ML-KEM key share encoded as an octet string - // mlkemCipherText - the ML-KEM ciphertext encoded as an octet string - // fixedInfo - the fixed information octet string - // oBits - the size of the output keying material in bits - // - // Constants: - // domSeparation - the UTF-8 encoding of the string - // "OpenPGPCompositeKeyDerivationFunction" - // counter - the fixed 4 byte value 0x00000001 - // customizationString - the UTF-8 encoding of the string "KDF" - if (outputBits !== 256) { - throw new Error('Unsupported output size'); - } - const { kmac256 } = await import('@noble/hashes/sha3-addons'); - // const { eccKeyShare, eccCiphertext } = await publicKey.pqc.kem.ecdhX(keyAlgo, publicParams.A); - // const { keyShare: mlkemKeyShare, cipherText: mlkemCipherText } = await publicKey.pqc.kem.ml(keyAlgo, publicParams.publicKey); - const eccData = util.concatUint8Array([eccKeyShare, eccCipherText]); // eccKeyShare || eccCipherText - const mlkemData = util.concatUint8Array([mlkemKeyShare, mlkemCipherText]); //mlkemKeyShare || mlkemCipherText - // const fixedInfo = new Uint8Array([keyAlgo]); +async function multiKeyCombine(algo, ecdhKeyShare, ecdhCipherText, ecdhPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey) { const encData = util.concatUint8Array([ + // counter new Uint8Array([0, 0, 0, 1]), - eccData, - mlkemData, - fixedInfo - ]); // counter || eccData || mlkemData || fixedInfo - - const mb = kmac256( - util.encodeUTF8('OpenPGPCompositeKeyDerivationFunction'), - encData, - { personalization: util.encodeUTF8('KDF') } - ); - - return mb; + // eccData + ecdhKeyShare, + ecdhCipherText, + ecdhPublicKey, + // mlkemData + mlkemKeyShare, + mlkemCipherText, + mlkemPublicKey, + // fixedInfo + new Uint8Array([algo]), + util.encodeUTF8('OpenPGPCompositeKDFv1') // domSeparation + ]); + + const kek = await hash.digest(enums.hash.sha3_256, encData); + return kek; } export async function validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSecretKey) { diff --git a/test/crypto/postQuantum.js b/test/crypto/postQuantum.js index 4e95ad3f5..6825e2aa7 100644 --- a/test/crypto/postQuantum.js +++ b/test/crypto/postQuantum.js @@ -18,119 +18,132 @@ export default () => describe('PQC', function () { it('ML-KEM + X25519 - Test vector', async function () { const armoredMessage = `-----BEGIN PGP MESSAGE----- -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 -----END PGP MESSAGE-----`; const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- -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 -=jowz +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 +/d8lkIgM -----END PGP PRIVATE KEY BLOCK-----` }); const { data: decryptedData } = await openpgp.decrypt({ message: await openpgp.readMessage({ armoredMessage }), decryptionKeys: privateKey }); - expect(decryptedData).to.equal('hello world\n'); + expect(decryptedData).to.equal('Testing\n'); }); }); From fd73658146dc9ba1d3daa4f50cb3acfec7834315 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:36:51 +0200 Subject: [PATCH 5/9] Switch to standardized version of ML-KEM --- package-lock.json | 55 +++++++++---------- package.json | 2 +- .../public_key/post_quantum/kem/ml_kem.js | 15 ++--- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index f79c23dd1..24411a3d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@noble/curves": "^1.6.0", "@noble/ed25519": "^1.7.3", "@noble/hashes": "^1.5.0", - "@openpgp/crystals-kyber-js": "^1.1.1", + "@noble/post-quantum": "^0.2.1", "@openpgp/jsdoc": "^3.6.11", "@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/tweetnacl": "^1.0.4-1", @@ -982,6 +982,32 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@noble/post-quantum": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", + "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.6.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/post-quantum/node_modules/@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1026,33 +1052,6 @@ "node": ">=12.4.0" } }, - "node_modules/@openpgp/crystals-kyber-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@openpgp/crystals-kyber-js/-/crystals-kyber-js-1.1.1.tgz", - "integrity": "sha512-Q4azKQpc2/SEPendXQs6IpnD2RNlPY2b7nwg5VNZ05FYYICnqYeH8R/NaTLJ2kVpitAWJpiNRC7bh7VlWz1T8g==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@openpgp/crystals-kyber-js/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@openpgp/jsdoc": { "version": "3.6.11", "resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz", diff --git a/package.json b/package.json index 44ddd6851..d199bd229 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@noble/curves": "^1.6.0", "@noble/ed25519": "^1.7.3", "@noble/hashes": "^1.5.0", - "@openpgp/crystals-kyber-js": "^1.1.1", + "@noble/post-quantum": "^0.2.1", "@openpgp/jsdoc": "^3.6.11", "@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/tweetnacl": "^1.0.4-1", diff --git a/src/crypto/public_key/post_quantum/kem/ml_kem.js b/src/crypto/public_key/post_quantum/kem/ml_kem.js index 12d62539f..72906e586 100644 --- a/src/crypto/public_key/post_quantum/kem/ml_kem.js +++ b/src/crypto/public_key/post_quantum/kem/ml_kem.js @@ -4,9 +4,8 @@ import util from '../../../../util'; export async function generate(algo) { switch (algo) { case enums.publicKey.pqc_mlkem_x25519: { - const { MlKem768 } = await import('@openpgp/crystals-kyber-js'); - const kyberInstance = new MlKem768(); - const [encapsulationKey, decapsulationKey] = await kyberInstance.generateKeyPair(); + const { ml_kem768 } = await import('@noble/post-quantum/ml-kem'); + const { publicKey: encapsulationKey, secretKey: decapsulationKey } = ml_kem768.keygen(); return { mlkemPublicKey: encapsulationKey, mlkemSecretKey: decapsulationKey }; } @@ -18,9 +17,8 @@ export async function generate(algo) { export async function encaps(algo, mlkemRecipientPublicKey) { switch (algo) { case enums.publicKey.pqc_mlkem_x25519: { - const { MlKem768 } = await import('@openpgp/crystals-kyber-js'); - const kyberInstance = new MlKem768(); - const [mlkemCipherText, mlkemKeyShare] = await kyberInstance.encap(mlkemRecipientPublicKey); + const { ml_kem768 } = await import('@noble/post-quantum/ml-kem'); + const { cipherText: mlkemCipherText, sharedSecret: mlkemKeyShare } = ml_kem768.encapsulate(mlkemRecipientPublicKey); return { mlkemCipherText, mlkemKeyShare }; } @@ -32,9 +30,8 @@ export async function encaps(algo, mlkemRecipientPublicKey) { export async function decaps(algo, mlkemCipherText, mlkemSecretKey) { switch (algo) { case enums.publicKey.pqc_mlkem_x25519: { - const { MlKem768 } = await import('@openpgp/crystals-kyber-js'); - const kyberInstance = new MlKem768(); - const mlkemKeyShare = await kyberInstance.decap(mlkemCipherText, mlkemSecretKey); + const { ml_kem768 } = await import('@noble/post-quantum/ml-kem'); + const mlkemKeyShare = ml_kem768.decapsulate(mlkemCipherText, mlkemSecretKey); return mlkemKeyShare; } From bd72f557e272e0d5215bfb5db103717f74a809ab Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:58:57 +0200 Subject: [PATCH 6/9] Update multiKeyCombine: re-introduce to KMAC --- src/crypto/public_key/post_quantum/kem/kem.js | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/crypto/public_key/post_quantum/kem/kem.js b/src/crypto/public_key/post_quantum/kem/kem.js index 50c019522..e4373ada6 100644 --- a/src/crypto/public_key/post_quantum/kem/kem.js +++ b/src/crypto/public_key/post_quantum/kem/kem.js @@ -3,7 +3,6 @@ import * as mlKem from './ml_kem'; import * as aesKW from '../../../aes_kw'; import util from '../../../../util'; import enums from '../../../../enums'; -import hash from '../../../hash'; export async function generate(algo) { const { eccPublicKey, eccSecretKey } = await eccKem.generate(algo); @@ -29,23 +28,19 @@ export async function decrypt(algo, eccCipherText, mlkemCipherText, eccSecretKey } async function multiKeyCombine(algo, ecdhKeyShare, ecdhCipherText, ecdhPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey) { + const { kmac256 } = await import('@noble/hashes/sha3-addons'); + + const key = util.concatUint8Array([mlkemKeyShare, ecdhKeyShare]); const encData = util.concatUint8Array([ - // counter - new Uint8Array([0, 0, 0, 1]), - // eccData - ecdhKeyShare, - ecdhCipherText, - ecdhPublicKey, - // mlkemData - mlkemKeyShare, mlkemCipherText, + ecdhCipherText, mlkemPublicKey, - // fixedInfo - new Uint8Array([algo]), - util.encodeUTF8('OpenPGPCompositeKDFv1') // domSeparation + ecdhPublicKey, + new Uint8Array([algo]) ]); + const domainSeparation = util.encodeUTF8('OpenPGPCompositeKDFv1'); - const kek = await hash.digest(enums.hash.sha3_256, encData); + const kek = kmac256(key, encData, { personalization: domainSeparation }); // output length: 256 bits return kek; } From 4a25f21319580458f86b383515db005759434394 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:49:13 +0200 Subject: [PATCH 7/9] Switch to seed format for ML-KEM private key, update test vectors (draft 5) --- src/crypto/crypto.js | 24 +- .../public_key/post_quantum/kem/index.js | 1 + src/crypto/public_key/post_quantum/kem/kem.js | 8 +- .../public_key/post_quantum/kem/ml_kem.js | 30 ++- src/packet/secret_key.js | 4 +- test/crypto/crypto.js | 8 +- test/crypto/postQuantum.js | 245 +++++++++--------- 7 files changed, 179 insertions(+), 141 deletions(-) diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 540d4c947..2c5a78e2c 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -264,7 +264,7 @@ export function parsePublicKeyParams(algo, bytes) { * @param {Object} publicParams - (ECC and symmetric only) public params, needed to format some private params * @returns {{ read: Number, privateParams: Object }} Number of read bytes plus the key parameters referenced by name. */ -export function parsePrivateKeyParams(algo, bytes, publicParams) { +export async function parsePrivateKeyParams(algo, bytes, publicParams) { let read = 0; switch (algo) { case enums.publicKey.rsaEncrypt: @@ -325,8 +325,9 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) { } case enums.publicKey.pqc_mlkem_x25519: { const eccSecretKey = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.x25519)); read += eccSecretKey.length; - const mlkemSecretKey = util.readExactSubarray(bytes, read, read + 2400); read += mlkemSecretKey.length; - return { read, privateParams: { eccSecretKey, mlkemSecretKey } }; + const mlkemSeed = util.readExactSubarray(bytes, read, read + 64); read += mlkemSeed.length; + const { mlkemSecretKey } = await publicKey.postQuantum.kem.mlkemExpandSecretSeed(algo, mlkemSeed); + return { read, privateParams: { eccSecretKey, mlkemSecretKey, mlkemSeed } }; } case enums.publicKey.pqc_mldsa_ed25519: { const eccSecretKey = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.ed25519)); read += eccSecretKey.length; @@ -425,7 +426,16 @@ export function serializeParams(algo, params) { enums.publicKey.pqc_mlkem_x25519, enums.publicKey.pqc_mldsa_ed25519 ]); + + const excludedFields = { + [enums.publicKey.pqc_mlkem_x25519]: new Set(['mlkemSecretKey']) // only `mlkemSeed` is serialized + }; + const orderedParams = Object.keys(params).map(name => { + if (excludedFields[algo]?.has(name)) { + return new Uint8Array(); + } + const param = params[name]; if (!util.isUint8Array(param)) return param.write(); return algosWithNativeRepresentation.has(algo) ? param : util.uint8ArrayToMPI(param); @@ -491,8 +501,8 @@ export async function generateParams(algo, bits, oid, symmetric) { return createSymmetricParams(keyMaterial, new SymAlgoEnum(symmetric)); } case enums.publicKey.pqc_mlkem_x25519: - return publicKey.postQuantum.kem.generate(algo).then(({ eccSecretKey, eccPublicKey, mlkemSecretKey, mlkemPublicKey }) => ({ - privateParams: { eccSecretKey, mlkemSecretKey }, + return publicKey.postQuantum.kem.generate(algo).then(({ eccSecretKey, eccPublicKey, mlkemSeed, mlkemSecretKey, mlkemPublicKey }) => ({ + privateParams: { eccSecretKey, mlkemSeed, mlkemSecretKey }, publicParams: { eccPublicKey, mlkemPublicKey } })); case enums.publicKey.pqc_mldsa_ed25519: @@ -592,9 +602,9 @@ export async function validateParams(algo, publicParams, privateParams) { util.equalsUint8Array(digest, await hash.sha256(hashSeed)); } case enums.publicKey.pqc_mlkem_x25519: { - const { eccSecretKey, mlkemSecretKey } = privateParams; + const { eccSecretKey, mlkemSeed } = privateParams; const { eccPublicKey, mlkemPublicKey } = publicParams; - return publicKey.postQuantum.kem.validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSecretKey); + return publicKey.postQuantum.kem.validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSeed); } case enums.publicKey.pqc_mldsa_ed25519: { const { eccSecretKey, mldsaSecretKey } = privateParams; diff --git a/src/crypto/public_key/post_quantum/kem/index.js b/src/crypto/public_key/post_quantum/kem/index.js index 0a2da99b4..399750ad7 100644 --- a/src/crypto/public_key/post_quantum/kem/index.js +++ b/src/crypto/public_key/post_quantum/kem/index.js @@ -1 +1,2 @@ export { generate, encrypt, decrypt, validateParams } from './kem'; +export { expandSecretSeed as mlkemExpandSecretSeed } from './ml_kem'; diff --git a/src/crypto/public_key/post_quantum/kem/kem.js b/src/crypto/public_key/post_quantum/kem/kem.js index e4373ada6..da38db8b8 100644 --- a/src/crypto/public_key/post_quantum/kem/kem.js +++ b/src/crypto/public_key/post_quantum/kem/kem.js @@ -6,9 +6,9 @@ import enums from '../../../../enums'; export async function generate(algo) { const { eccPublicKey, eccSecretKey } = await eccKem.generate(algo); - const { mlkemPublicKey, mlkemSecretKey } = await mlKem.generate(algo); + const { mlkemPublicKey, mlkemSeed, mlkemSecretKey } = await mlKem.generate(algo); - return { eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSecretKey }; + return { eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSeed, mlkemSecretKey }; } export async function encrypt(algo, eccPublicKey, mlkemPublicKey, sessioneKeyData) { @@ -44,9 +44,9 @@ async function multiKeyCombine(algo, ecdhKeyShare, ecdhCipherText, ecdhPublicKey return kek; } -export async function validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSecretKey) { +export async function validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSeed) { const eccValidationPromise = eccKem.validateParams(algo, eccPublicKey, eccSecretKey); - const mlkemValidationPromise = mlKem.validateParams(algo, mlkemPublicKey, mlkemSecretKey); + const mlkemValidationPromise = mlKem.validateParams(algo, mlkemPublicKey, mlkemSeed); const valid = await eccValidationPromise && await mlkemValidationPromise; return valid; } diff --git a/src/crypto/public_key/post_quantum/kem/ml_kem.js b/src/crypto/public_key/post_quantum/kem/ml_kem.js index 72906e586..3789459c6 100644 --- a/src/crypto/public_key/post_quantum/kem/ml_kem.js +++ b/src/crypto/public_key/post_quantum/kem/ml_kem.js @@ -1,11 +1,31 @@ import enums from '../../../../enums'; import util from '../../../../util'; +import { getRandomBytes } from '../../../random'; export async function generate(algo) { + switch (algo) { + case enums.publicKey.pqc_mlkem_x25519: { + const mlkemSeed = getRandomBytes(64); + const { mlkemSecretKey, mlkemPublicKey } = await expandSecretSeed(algo, mlkemSeed); + + return { mlkemSeed, mlkemSecretKey, mlkemPublicKey }; + } + default: + throw new Error('Unsupported KEM algorithm'); + } +} + +/** + * Expand ML-KEM secret seed and retrieve the secret and public key material + * @param {module:enums.publicKey} algo - Public key algorithm + * @param {Uint8Array} seed - secret seed to expand + * @returns {Promise<{ mlkemPublicKey: Uint8Array, mlkemSecretKey: Uint8Array }>} + */ +export async function expandSecretSeed(algo, seed) { switch (algo) { case enums.publicKey.pqc_mlkem_x25519: { const { ml_kem768 } = await import('@noble/post-quantum/ml-kem'); - const { publicKey: encapsulationKey, secretKey: decapsulationKey } = ml_kem768.keygen(); + const { publicKey: encapsulationKey, secretKey: decapsulationKey } = ml_kem768.keygen(seed); return { mlkemPublicKey: encapsulationKey, mlkemSecretKey: decapsulationKey }; } @@ -40,13 +60,11 @@ export async function decaps(algo, mlkemCipherText, mlkemSecretKey) { } } -export async function validateParams(algo, mlkemPublicKey, mlkemSecretKey) { +export async function validateParams(algo, mlkemPublicKey, mlkemSeed) { switch (algo) { case enums.publicKey.pqc_mlkem_x25519: { - // TODO confirm this is the best option performance- & security-wise (is key re-generation faster?) - const { mlkemCipherText: validationCipherText, mlkemKeyShare: validationKeyShare } = await encaps(algo, mlkemPublicKey); - const resultingKeyShare = await decaps(algo, validationCipherText, mlkemSecretKey); - return util.equalsUint8Array(resultingKeyShare, validationKeyShare); + const { mlkemPublicKey: expectedPublicKey } = await expandSecretSeed(algo, mlkemSeed); + return util.equalsUint8Array(mlkemPublicKey, expectedPublicKey); } default: throw new Error('Unsupported KEM algorithm'); diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 70a16b8e0..c10836b74 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -221,7 +221,7 @@ class SecretKeyPacket extends PublicKeyPacket { } } try { - const { read, privateParams } = crypto.parsePrivateKeyParams(this.algorithm, cleartext, this.publicParams); + const { read, privateParams } = await crypto.parsePrivateKeyParams(this.algorithm, cleartext, this.publicParams); if (read < cleartext.length) { throw new Error('Error reading MPIs'); } @@ -479,7 +479,7 @@ class SecretKeyPacket extends PublicKeyPacket { } try { - const { privateParams } = crypto.parsePrivateKeyParams(this.algorithm, cleartext, this.publicParams); + const { privateParams } = await crypto.parsePrivateKeyParams(this.algorithm, cleartext, this.publicParams); this.privateParams = privateParams; } catch (err) { throw new Error('Error reading MPIs'); diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 20a46b8e4..45ef319c2 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -6,7 +6,7 @@ import openpgp from '../initOpenpgp.js'; import crypto from '../../src/crypto'; import util from '../../src/util.js'; -export default () => describe('API functional testing', function() { +export default () => describe('API functional testing', async function() { const RSAPublicKeyMaterial = util.concatUint8Array([ new Uint8Array([0x08,0x00,0xac,0x15,0xb3,0xd6,0xd2,0x0f,0xf0,0x7a,0xdd,0x21,0xb7, 0xbf,0x61,0xfa,0xca,0x93,0x86,0xc8,0x55,0x5a,0x4b,0xa6,0xa4,0x1a, @@ -196,15 +196,15 @@ export default () => describe('API functional testing', function() { const algoRSA = openpgp.enums.publicKey.rsaEncryptSign; const RSAPublicParams = crypto.parsePublicKeyParams(algoRSA, RSAPublicKeyMaterial).publicParams; - const RSAPrivateParams = crypto.parsePrivateKeyParams(algoRSA, RSAPrivateKeyMaterial).privateParams; + const RSAPrivateParams = (await crypto.parsePrivateKeyParams(algoRSA, RSAPrivateKeyMaterial)).privateParams; const algoDSA = openpgp.enums.publicKey.dsa; const DSAPublicParams = crypto.parsePublicKeyParams(algoDSA, DSAPublicKeyMaterial).publicParams; - const DSAPrivateParams = crypto.parsePrivateKeyParams(algoDSA, DSAPrivateKeyMaterial).privateParams; + const DSAPrivateParams = (await crypto.parsePrivateKeyParams(algoDSA, DSAPrivateKeyMaterial)).privateParams; const algoElGamal = openpgp.enums.publicKey.elgamal; const elGamalPublicParams = crypto.parsePublicKeyParams(algoElGamal, elGamalPublicKeyMaterial).publicParams; - const elGamalPrivateParams = crypto.parsePrivateKeyParams(algoElGamal, elGamalPrivateKeyMaterial).privateParams; + const elGamalPrivateParams = (await crypto.parsePrivateKeyParams(algoElGamal, elGamalPrivateKeyMaterial)).privateParams; const data = util.stringToUint8Array('foobar'); diff --git a/test/crypto/postQuantum.js b/test/crypto/postQuantum.js index 6825e2aa7..a1c899dea 100644 --- a/test/crypto/postQuantum.js +++ b/test/crypto/postQuantum.js @@ -15,130 +15,139 @@ export default () => describe('PQC', function () { expect(decryptedSessionKey).to.deep.equal(sessionKey.data); }); + it('ML-KEM + X25519 - private key is correctly serialized using the seed instead of the expanded secret key material', async function () { + const armoredKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +BoWQifC9dbHD5JNv0/6CMXFZagQABA== +-----END PGP PRIVATE KEY BLOCK-----`; + + const { data: expectedBinaryKey } = await openpgp.unarmor(armoredKey); + + const privateKey = await openpgp.readKey({ armoredKey }); + expect(privateKey.write()).to.deep.equal(expectedBinaryKey); + }); + it('ML-KEM + X25519 - Test vector', async function () { const armoredMessage = `-----BEGIN PGP MESSAGE----- -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 -----END PGP MESSAGE-----`; + const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- -xUsGUdDGgBsAAAAgsJV1qyvdl+EenEB4IFvP5/7Ci5XJ1rk8Yh967qV1rb0A8q5N -oCO2TM6GoqWftH02oIwWpAr+kvA+4CH7N3cpPSrCrwYfGwoAAABABQJR0MaAIqEG -UjQyQjRSVAUCGc7/KG6cjkeeyIdX+VNUOImEoC19C1kCGwMCHgkDCwkHAxUKCAIW -AAUnCQIHAgAAAADhOyBW8CPDe5FreFmlonhfVhr2EPw3WFLyd6mKRhkQm3VBfw7Q -w7eermL9Cr5O7Ah0JxmIkT18jgKQr9AwWa3nm2mcbjSoib2WVzm5EiW3f3lgflfr -ySQFpSICzPl2QcAcrgjNLlBRQyB1c2VyIChUZXN0IEtleSkgPHBxYy10ZXN0LWtl -eUBleGFtcGxlLmNvbT7CmwYTGwoAAAAsBQJR0MaAIqEGUjQyQjRSVAUCGc7/KG6c -jkeeyIdX+VNUOImEoC19C1kCGQEAAAAAg2ogTEbKVVlbWsejQHkq7xo8ipM7dv6H -z2AekkJqupKVR+/oy+2j6ri+/B2K6k1v1y5quzirhs87fB5AxZC6ZoFDvC0kZOvo -14fPF07wCx0jwJVOWuRFVsVw7pQJHbNzgkIAx82LBlHQxoBpAAAEwLRbSSpvve2p -Ih3hHweqq2VdRo+7Zf7whYHyXM/UifsniwMKSrubvsmLgCyiEwMip3ZlTSxIFDaF -EMVtVvCSJ7XFZ0WslTJnZ/CENPgxbVgn6CC2b8UEb8olS3AxlSiqJSRP0OrOJdfP -WJI1A+p7Vmw1CZQq2oVPUlE96SVUrFxfk7XCYpcTpIQb+mFB4ULCesat5tud7Tau -UJpMKssUf0I74EUjahoR46pPReKzlSqfvhpgXSASZpBg8IZBY7VbgTnLInGTTnEr -rScVlDnAwcdYvuZMQYO5EjS6LOxn1aVfU+iH+Rir2AyFzsYl6ICHciPAsKKa+Sk7 -UPFBrIRG1qgn7FF0n5epHeiFCRNb87wSqlp0h+d8L3jPmDq4zoQPKDViasoHYXLD -7KoJTIxP2eGzjMRlg3oD9ph3ZnyOTIsx/4SDtxW3q+JU8RFoI0dZEdURwaoIITWi -tldtPUmtBuJshceEDSWopuwLzBuVTnYDpTy94ZtDBKmgPnmSmPOKZ6THucmiJGUm -WmAKkyo7kWAwYRsE2ZYqLzIJFmZFzRLIThipiZhR/9h2GemQklMJqYs25cEGx6FW -zXRv8Palm7yOAicH/ldHUOtU3oFIXthOatwSrQApJ7HHvksx59ZtLFtBgHm5eRmY -YleJsJLGCPssa7pK2hIwgLlmCLSAavFqYjuocWIYKLmw5vNXXRWIjPBbTpVXbUO5 -U9F/67gggSWBJXCZlfgcluO422aN22m8aONiTgZtmjcC2elci5yRKGBbeKmFTcVs -ZbpbY6ZCKFRyzbqmMGYe0mqN6lh7R5dNiBuJZQg04mYuSzWCF3mumlJTRtlN9Miy -6LyWApJSTQdgc3awS0mjUrgU1Ia0AjMFKcxJA6iHd6iAxWMbUqxOSoTOTUlMr3lt -paNGEMGpaHwMoQs99xSI1zG9pYmfeIl6LfZSwnI4LsBvNOBiUhNUC/aYIILEm7qj -Tpw5YdI+6jSl+palLlcMDzt0LgMN8rY6UlZJBGNFSAKSNSWXdFYMByKKGSCj91TD -WPlOLvWKntSLk5eLodhgmRGqx5GZECgWS4wDARY00rl17dV53GejXrUtJaYcnam5 -pKoTSaPJTuY25Kyy+oB7aHpV0vA87JaeRCsqkjcS5IQKdtceUskXNRa2f7CTrfQR -hOGk0gSA4Jx8+Fw8uGWLGJx6m1lSyWcMX5HL7hJkFhEKebYjdALGXMV1wxNiUHCI -vxCjX/AkwHEDvAN6qhULrcZlmngSbeBysOFud2a8PIS2p7RCAatO+TpFgoR+1CgV -JIdiRpM0WrMfS9iBERhtYaLH1oUjBpcV7zpgNdkT4ClfbTpgu3oPnWBogDjMXKUe -pSfFx0l1tNGRLCCFVit8xxA4Q+phutInyXUAHJiEfHIR4jxTd/FwQ3pDoKxTesY+ -XsGtVJxe9oMrXSlt6uymn6zKQlQsw8odvHhp5/NWqkCh9/xQvmIlERsVVjyJ0FNF -/+HNT9KrECCj6+cujDbEN6UmRlFvlMcxFzYaTnWa1cshSVCCa1aYZddWrDdxOwMf -ObUw8TukY7A2RqcdpmpA68SLoWwNAgtFG1xWV43yC/P3XTsqTmgHRUGboDkVs9K8 -1+Byg4jhKWcAksr2fFDB4wkkaZcB3uUOXuQQ2etC1aCrboS5vTeMVJVS+ssLkxle -KLZ3kH9pazHbNTKQWclexAe48RImOk1PlmN9HHMgUwgJI5H8e3a7cQw8x7Yh5wce -yAdhuwRGcT99CqtaQb0aeTz9xxh642roMy46rCQp2A/g1QbZIqqVe6lb4qkJ8YdM -dG4SrE3UzD3tuAyu3L9Ql79qxxdB4Jt7wp+dPETaoZba+aMWZ68ZxDEjQJcgyrN9 -XCBNcLcU+SpjBXPK13yeCdAVGUhA1c0qB4PKVY5/e07Kc8qGgyrlJCCb05OQQKWG -mmVcJnDDIZSLM4VPd3cAgWhv5rIk/BPWQ6CGps6njH1WNaI6sTr35wcfWlMahs0w -mUPkKMG0AWwT9VBCBU7huFN7Rw2DXBdQUlQDO8WzVLXFt6sZvF+XgZ840woQ8I29 -BmW55qSY2hdtMsKqkU31Nbscxa5wRsu2KSirXF3JoZkTacU/taIRmmIwGXl0zBlM -8Hp9hJOdAZAAPAYwCj8FdmD4AyDiHHDkuJsLfL80CnKck2wYbBE/BoGRKwVul1Jr -gh4KC4DS+WfKZQYam5KLAytFMUJf8TDiYYNmVr9TOVNAoCj4XKs7BQ7KZ5MMnCWi -EEsH9im2mBrHDKXLCrFK8IY54B5ae8uDKWwOuhTtlHki5CTVHHRKaorYawvMqTZ4 -HCO+6Jrj8rm7YFxhxwPihVHIl10SK2Q2tX8ygidCKc1yPBh4lKyvyryPwL6i5sM4 -sU5glM9bZgPKfHosk4uNdqZQ5FyIaohJ8aocQpr0JVQv8rp0UjBEDBqDeIhepohd -cp5KhA1kND4vQbfjusdVtgUorAqyAw0YSoeDLAfC5syaJqo8K06CM8y7O3VqB8Rs -ZJb8Eb7mGYdH9U8m3MTjestO5LcTAyqoBJvC4TTgp6F9dJ55HJ3rzFx19wMqGhLV -Abcw/JWJagrvYqTGozbiEcLheFNmKik4eGoG9mS1Ebhwhbmg5LD6kZXFK7hJOnkb -cTdz0ynSqlPk1oJkh8Pa1gVG4IWgEJISZWEb036BmTASRc5EYVetuBujMYQKuWeI -RrumhH3GiZBw1RIyrDYYMk37OHf0MLhahBeldJsqRoLcErOSu0T9xwmeczWoIDtZ -Q8794LDkCoY6wpYFF5Scq64HgmQaS5kSQH9UtTIgbLoBmQiDUIyrx8LoBqhOdQPR -0y60NWjSXLbs0VjxrIVMZmdlxH//gknkDLlSgSqbbAkG+7T9clLS44lVYD22N03n -Mil8pHWju6yYW3eFaylzI7jLEVZ5cLw15bd1JHEvRpOBxV8Fdn+p4RKoRrUN4EQm -1olEK4TsWY+uV2RCV4PEBQpOQxGZZxhMRa/AKnD3I1LjSlNh9SLXNbVIp69bPK9N -qS8MGBGeWBzEARhXea9mBiUisSFSZrwneYALPBXH0h4xerZWV2GH9bu12gwBmJbB -k64rwZg/dqDiCM16/C0Np0Aza4oTVsOJ6BrdZh70xFZq+Dizeg85TMywkl9Ma1BT -AsMOZ45sAEwIBhUX6Colkae023ouMgj1pnFV5Rc8cTSRcGUM1ZHW8AeLAwpKu5u+ -yYuALKITAyKndmVNLEgUNoUQxW1W8JIntcVnRayVMmdn8IQ0+DFtWCfoILZvxQRv -yiVLcDGVKKolJE/Q6s4l189YkjUD6ntWbDUJlCrahU9SUT3pJVSsXF+TtcJilxOk -hBv6YUHhQsJ6xq3m253tNq5QmkwqyxR/QjvgRSNqGhHjqk9F4rOVKp++GmBdIBJm -kGDwhkFjtVuBOcsicZNOcSutJxWUOcDBx1i+5kxBg7kSNLos7GfVpV9T6If5GKvY -DIXOxiXogIdyI8Cwopr5KTtQ8UGshEbWqCfsUXSfl6kd6IUJE1vzvBKqWnSH53wv -eM+YOrjOhA8oNWJqygdhcsPsqglMjE/Z4bOMxGWDegP2mHdmfI5MizH/hIO3Fber -4lTxEWgjR1kR1RHBqgghNaK2V209Sa0G4myFx4QNJaim7AvMG5VOdgOlPL3hm0ME -qaA+eZKY84pnpMe5yaIkZSZaYAqTKjuRYDBhGwTZliovMgkWZkXNEshOGKmJmFH/ -2HYZ6ZCSUwmpizblwQbHoVbNdG/w9qWbvI4CJwf+V0dQ61TegUhe2E5q3BKtACkn -sce+SzHn1m0sW0GAebl5GZhiV4mwksYI+yxrukraEjCAuWYItIBq8WpiO6hxYhgo -ubDm81ddFYiM8FtOlVdtQ7lT0X/ruCCBJYElcJmV+ByW47jbZo3babxo42JOBm2a -NwLZ6VyLnJEoYFt4qYVNxWxlultjpkIoVHLNuqYwZh7Sao3qWHtHl02IG4llCDTi -Zi5LNYIXea6aUlNG2U30yLLovJYCklJNB2BzdrBLSaNSuBTUhrQCMwUpzEkDqId3 -qIDFYxtSrE5KhM5NSUyveW2lo0YQwalofAyhCz33FIjXMb2liZ94iXot9lLCcjgu -wG804GJSE1QL9pgggsSbuqNOnDlh0j7qNKX6lqUuVwwPO3QuAw3ytjpSVkkEY0VI -ApI1JZd0VgwHIooZIKP3VMNY+U4u9Yqe1IuTl4uh2GCZEarHkZkQKBZLjAMBFjTS -uXXt1XncZ6NetS0lphydqbmkqhNJo8lO5jbkrLL6gHtoelXS8Dzslp5EKyqSNxLk -hAp21x5SyRc1FrZ/sJOt9BGE4aTSBIDgnHz4XDy4ZYsYnHqbWVLJZwxfkcvuEmQW -EQp5tiN0AsZcxXXDE2JQcIi/EKNf8CTAcQO8A3qqFQutxmWaeBJt4HKw4W53Zrw8 -hLantEIBq075OkWChH7UKBUkh2JGkzRasx9L2IERGG1hosfWhSMGlxXvOmA12RPg -KV9tOmC7eg+dYGiAOMxcpR6lJ8XHSXW00ZEsIIVWK3zHEDhD6mG60ifJdQAcmIR8 -chHiPFN38XBDekOgrFN6xj5ewa1UnF72gytdKW3q7KafrMpCVCzDyh28eGnn81aq -QKH3/FC+YiURGxVWPInQU0X/4c1P0qsQIKPr5y6MNsQ3pSZGUW+UxzEXNhpOdZrV -yyFJUIJrVphl11asN3E7Ax85tTDxO6RjsDZGpx2makDrxIuhbA0CC0UbXFZXjfIL -8/ddOypOaAdFQZugORWz0rzX4HKDiOEpZ7+6jJ8tjNCQrKgJg1wGCpAN0VnrtFrs -2l6Q0GteA6B+fwfjuRabwerw1ro7lcwOA5EiA6XO30P+pLG07ms2MCfCmwYYGwoA -AAAsBQJR0MaAIqEGUjQyQjRSVAUCGc7/KG6cjkeeyIdX+VNUOImEoC19C1kCGwwA -AAAA5kEgPwatbx3FHPIy9J9mGUEpUE03oRRPE8N4lJ2eAIMhciCEHp3BzYVGvW3O -aPYmjcu4JTREPJM6HP7yR+ZEg+Bld9lBSVmEdMJnOX2ZHOdEoRV4bm1U4aPuhrKL -/d8lkIgM ------END PGP PRIVATE KEY BLOCK-----` }); +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 +BoWQifC9dbHD5JNv0/6CMXFZagQABA== +-----END PGP PRIVATE KEY BLOCK-----` + }); const { data: decryptedData } = await openpgp.decrypt({ message: await openpgp.readMessage({ armoredMessage }), From 9056ab92184a7ea58b89b0fea8d17f24cb1a79ad Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:27:26 +0200 Subject: [PATCH 8/9] Disallow generating and parsing v4 keys of type ML-KEM --- src/packet/public_key.js | 4 ++++ src/packet/secret_key.js | 3 +++ test/crypto/validate.js | 4 ++-- test/general/key.js | 11 +++++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/packet/public_key.js b/src/packet/public_key.js index a7de9be9d..e444f7504 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -138,6 +138,10 @@ class PublicKeyPacket { ) { throw new Error('Legacy curve25519 cannot be used with v6 keys'); } + // The composite ML-KEM + ECDH schemes MUST be used only with v6 keys. + if (this.version !== 6 && this.algorithm === enums.publicKey.pqc_mlkem_x25519) { + throw new Error('Unexpected key version: ML-KEM algorithms can only be used with v6 keys'); + } this.publicParams = publicParams; pos += read; diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index c10836b74..7356ac1e5 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -532,6 +532,9 @@ class SecretKeyPacket extends PublicKeyPacket { )) { throw new Error(`Cannot generate v6 keys of type 'ecc' with curve ${curve}. Generate a key of type 'curve25519' instead`); } + if (this.version !== 6 && this.algorithm === enums.publicKey.pqc_mlkem_x25519) { + throw new Error(`Cannot generate v${this.version} keys of type 'pqc'. Generate a v6 key instead`); + } const { privateParams, publicParams } = await crypto.generateParams(this.algorithm, bits, curve, symmetric); this.privateParams = privateParams; this.publicParams = publicParams; diff --git a/test/crypto/validate.js b/test/crypto/validate.js index fde15d3ea..7c8209268 100644 --- a/test/crypto/validate.js +++ b/test/crypto/validate.js @@ -81,7 +81,7 @@ async function cloneKeyPacket(key) { } async function generatePrivateKeyObject(options) { - const config = { rejectCurves: new Set() }; + const config = { rejectCurves: new Set(), ...options.config }; const { privateKey } = await openpgp.generateKey({ ...options, userIDs: [{ name: 'Test', email: 'test@test.com' }], format: 'object', config }); return privateKey; } @@ -317,7 +317,7 @@ export default () => { describe('PQC parameter validation', function() { let pqcEncryptionSubkey; before(async () => { - const key = await generatePrivateKeyObject({ type: 'symmetric', subkeys: [{ type: 'pqc' }] }); + const key = await generatePrivateKeyObject({ type: 'symmetric', subkeys: [{ type: 'pqc' }], config: { v6Keys: true } }); pqcEncryptionSubkey = key.subkeys[0]; }); diff --git a/test/general/key.js b/test/general/key.js index 50025e842..9adedb91b 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -4615,6 +4615,17 @@ I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg== expect(v6Key.subkeys).to.have.length(1); }); + it('should throw when trying to add a ML-KEM PQC key to a v4 key', async function() { + const v4Key = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: priv_key_rsa }), + passphrase: 'hello world' + }); + expect(v4Key.keyPacket.version).to.equal(4); + expect(v4Key.subkeys).to.have.length(1); + await expect(v4Key.addSubkey({ type: 'pqc', sign: false })).to.be.rejectedWith(/Cannot generate v4 keys of type 'pqc'/); + expect(v4Key.subkeys).to.have.length(1); + }); + it('should throw when trying to encrypt a subkey separately from key', async function() { const privateKey = await openpgp.decryptKey({ privateKey: await openpgp.readKey({ armoredKey: priv_key_rsa }), From ba928ae31f92cccf3369affadd69e62af69cc57e Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:44:36 +0100 Subject: [PATCH 9/9] Update multiKeyCombine: switch to LAMPS-aligned-and-NIST-compatible proposal Proposal 2a from https://mailarchive.ietf.org/arch/msg/openpgp/NMTCy707LICtxIhP3Xt1U5C8MF0/ --- src/crypto/public_key/post_quantum/kem/kem.js | 17 +- test/crypto/postQuantum.js | 226 +++++++++--------- 2 files changed, 123 insertions(+), 120 deletions(-) diff --git a/src/crypto/public_key/post_quantum/kem/kem.js b/src/crypto/public_key/post_quantum/kem/kem.js index da38db8b8..2c93542f8 100644 --- a/src/crypto/public_key/post_quantum/kem/kem.js +++ b/src/crypto/public_key/post_quantum/kem/kem.js @@ -3,6 +3,7 @@ import * as mlKem from './ml_kem'; import * as aesKW from '../../../aes_kw'; import util from '../../../../util'; import enums from '../../../../enums'; +import hash from '../../../hash'; export async function generate(algo) { const { eccPublicKey, eccSecretKey } = await eccKem.generate(algo); @@ -28,19 +29,21 @@ export async function decrypt(algo, eccCipherText, mlkemCipherText, eccSecretKey } async function multiKeyCombine(algo, ecdhKeyShare, ecdhCipherText, ecdhPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey) { - const { kmac256 } = await import('@noble/hashes/sha3-addons'); - - const key = util.concatUint8Array([mlkemKeyShare, ecdhKeyShare]); + // LAMPS-aligned and NIST compatible combiner, proposed in: https://mailarchive.ietf.org/arch/msg/openpgp/NMTCy707LICtxIhP3Xt1U5C8MF0/ + // 2a. KDF(mlkemSS || tradSS || tradCT || tradPK || Domain) + // where Domain is "Domain" for LAMPS, and "mlkemCT || mlkemPK || algId" for OpenPGP const encData = util.concatUint8Array([ - mlkemCipherText, + mlkemKeyShare, + ecdhKeyShare, ecdhCipherText, - mlkemPublicKey, ecdhPublicKey, + // domSep + mlkemCipherText, + mlkemPublicKey, new Uint8Array([algo]) ]); - const domainSeparation = util.encodeUTF8('OpenPGPCompositeKDFv1'); - const kek = kmac256(key, encData, { personalization: domainSeparation }); // output length: 256 bits + const kek = await hash.digest(enums.hash.sha3_256, encData); return kek; } diff --git a/test/crypto/postQuantum.js b/test/crypto/postQuantum.js index a1c899dea..2f58ae0d5 100644 --- a/test/crypto/postQuantum.js +++ b/test/crypto/postQuantum.js @@ -18,47 +18,48 @@ export default () => describe('PQC', function () { it('ML-KEM + X25519 - private key is correctly serialized using the seed instead of the expanded secret key material', async function () { const armoredKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- -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 -BoWQifC9dbHD5JNv0/6CMXFZagQABA== +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 +zdmQk3zZBuwE/KAXTAY= -----END PGP PRIVATE KEY BLOCK-----`; const { data: expectedBinaryKey } = await openpgp.unarmor(armoredKey); @@ -70,82 +71,81 @@ BoWQifC9dbHD5JNv0/6CMXFZagQABA== it('ML-KEM + X25519 - Test vector', async function () { const armoredMessage = `-----BEGIN PGP MESSAGE----- -wcPtBiEGVrSmanmpRfWJ0fSGnhAPXuAkNJhxdH1utflntzaDWSJpKMYMwW51QMqU -ybrp5IxkE11EchQ+4CJX4GR82u38j1TkkMTI0Q+AWKlxREu4kujxt/1OiaeIfvZy -+sd5N07Ee86U1boyzCj5ypd5l1W61BE1d9iOc1VTfbVUDy6c21KO6Pki2Ls8R6gH -zGK2FT2F3RHyHIsF0ae5Ctg52E82moqzj9KCKghrgQe/2rNDzRDH4hc0G+rh6sbu -tb0eDnDIp0fvx/6Zroj9AQuUonJYAKLKD4RCFaO9+eXsqhIGVNLNdsBm5cDhyy65 -TKrG5FLaCbnDLoCzn6zvw9JrYwnnyN+XCQd4cMU4rs9bTdFti6f1gxksqkm3ChVs -fjsT5QspDB6RBALSA0+O101ONuh+r0Cssl5rZvSf1f8B/n4j4tds4hUlaCREoGpn -igJpo0TYPb0b37AgElVf9BmqCxo4SceoT1Go1QgyUL+1WPsCueCzzoMXxA02niAD -rFHkIbg/9600HD5yiAAsFGPMH/8rdmSCamtOKQoQmPQY7MJOOqOjkxPZWb0waAL8 -dCe6D/yt1z07EVxbF2kAirRCYu396JJ3U0vTilxPi/7OoETCp2wUkKuxLAce9ul1 -LYEPY+XN+faacpl9xwBLLTBZA8OV7vD3MChPcTwZsrlQBA0UALbVyzwMBlzh086M -OmezZ36KaiTSEXn5zPxFt9b4q3HBks655hAwJ2+rAV9rJy4trXWEDlz86oOf7MP8 -gXxmbEetvDDSdnEpnxR5GwsqljnO8UhTVXXFsp0LNCORmwA+n1t8UjQssj9uwO2T -9Y9UJOKQosuSwDvCR64zOGTPwn1w6FSZK34hzeOYYaFTsZjP5QtkrIvvlXdkp/6f -2bL9S4dEaSjWZMoR0NSKtvoY6Vjj342tPlwUhS28uP8w5/MZJgX/vJHSZAoi7vCy -fMOklznFInSs85vADxGVxGuaAVZcz8KlGXkTH0EfhKRhOaRrZALID6jF363cwcB/ -i1YH56Fc5f8wixPwTu9ntZ36q/FMisQZKbJxA3YQO4XCSBzunYUqOFdtg3fJntnH -dHx6nQS0JXCkjDc7gd6Yr7NbcRWUidE/oHSBBpBwiRQju8M8cXaeHMzFczUQjPx/ -k8Xtr7gwEcVdGBdSbS0RBwVy5eiIGYVUAVTg8773bdhXvD4yTVRuPGbYm474MtlE -bgkUch8PxInr8+muA1AcKg3uqwWbcpX/Q56RHIYNbU22Vcl3Nq6UwKqqHaeKjdL4 -aHauPmHOWxgK+lHvZS2Lhg8T1Su0qsO0xOIeZpfOEAr+aNrjpGr7Bj5eOJOBjJQT -1jEHhgIK37QaplKTBf3kc/TH7w1AIpVuJPzi4IXGRy6uwvdfQuOAeYv1c5LnOsKH -dmTZgsg6tSOV+3eSKoQmnTecOoEddtVfQsXRx+QGxsbvSM2B5qyCSo8fFgbeCajs -yRdjjPV7A+exaF/WgAszi+nD/Zka0xIE3g1nCCSRn27NAtrM4jaNHlKg4DZNAC9u -3dsfp/lAeSjDHjkLzOQep10o7Gg+1qFvNwGjOvHCX+LyVEcGIlH1dF+JjfZobWMr -0sBWAgkCDDhxVuabuQ83wJb46Gor24w4/x4ugBmr7KrjzM14lyjnB8uDnTpkfpoB -L5vCzO7FQfnbUWha456roBnRAOUhcqGhdqTPChnrt/ie/PUSfWZlZZh9aS+U50eK -WGIJER2n2A1WfEnYfy155ipf3z1D+ritS9p7hzlVOQpb/xdVHnga9gfrpWljyX0L -YRIL7wh5YjHL940kwgDtA9ZWZ8R3PLPkgOE7Jw/xUTz+QXqRK4R9SubGttmoQy7Y -liWLjUnl5sbm/rsSqmAHdOdz4WYdwWO5eJoJ3/rH0uGZQEHQq6U/iYidTHp+OS8h -Ww4/1zLtOw89HhwLpSN0vk87TV3ZgYVTZlVFwOOEKasNf9VhWIvFS48= +wcPUA7w9P/Y4tvvTaUZQqpafFZ6WRHDl5y8FJcqxWgQ8kK3P++vG3p2aW0Vb +BuxUbAzplFMVjybzf8n0qa/92R6IeFKWMilaM+iAf81VyTIBv12lfQy27RFx +itM0v/CAWrEvPf1RkB0ZnEX9lKhIfBp7QRSVEp2QOh3pWtYQ6AkS0JtOGeiI +befPigAVtmDlnYHYYEFbrKlE8pvLF2bMCJkHredQue7AmsGLpnXZpna19rvG +3kB38YiM6777jxW8yb7h77jOZgnclJXYkSHly7LKa6z6omOYqn6rA5izbE0e +s/yqucighBVnjiwNEwrPlQbogh6EtRsGXkYLPGRSVSM2MCSsigl2Xs2cgWJV +7outF2Qr8BauBRqHW5ScJF/yAmVP7NfQ9PfoUPMiHFJE8U6ffow6lnNWzhn2 +Wqp8GkBccTmzwD/6XXf23yKGO6AIc1xvNpE99QBzTkPslPuXDiocMHfAXpbW +HzVyOwXu/tQmRNe+ISNpQsMmQDUM05kq0diazBXOiZGRMRFndEpa1Qhhgj7V +4GgLyr/HdylNbzbiUGFjovN8/pUwO6APu1jU/CmFEUrjjOFpXaOLZ6svt5mW +iNlVNfWVq35CrJdWVFInLjD/GmsQNSiPe34LcDS3TVRzAhS5HehTVrkFDOD+ +e7wBnHFKDquYTzc3pqP3gHmzCLueUfo3Z6cTgXz6T3+RlRfZDNQzLZ7XHC3W +VWuwjViv0mQncau6bCnhhCK4VwAqXCvjy9m598cqKQZ5Sf3DAGY1+ZGqbID+ +0zCdVKC0v4eUEGg2D9K1f1XiTfaJv6AO0sS5RqjAZxBpw2/m5ruxMyJxqTE1 +h0GZLEY1ZQKxe9E0bDvTi0ogbYicLEokgRyKN/ojZE+hwpA7msjLVV7E0tDg +rz8jxp4AwQ8O/FXX/X0Dp04QsZgJhdwEYt4HPTogSff/NaC8KNSTzxEvJIMa +N+dPAp3Ypz7pGU6y73odwBCJ58nAPaI06f3Ev4gWJai8sUxeHe6amQX+SYqL +jbTP838PgbRbNCwBUiH2I5WX2bdIeRbEiQerVr7f76Kpdo2FC93A8VlW/wyK +VYWINPBfB/ktLAzmohR9wq+E+uMCOnyz6+vE1Bf2fbljGBSBjyUwED07tlDd +B9EvUlLgr7TAiideveSXHAx0G0qK5NBzfoY3Ca3WETiqkDTWpBSPDQUsbXrF +u2QUJKIcXcQptuiC+nKFFbDoFvCJzgcuRS/H1ZyOcI0dgmx9T1GGrID65R0z +FqdpenxdaHncrMmOlYI/cwyrfhHV0wZB6Y0XAv5Ujwsfg0fdCxJclkAI1k/x +N2xIhjwFdh62sKZqCJgfgKlrkSv4DDUVAooR/yHIIKpeJQgC2ZUfL3XLNycJ +eBf5V5ZmyRhIVgAThhb4cGVtNahP0IuIHk7eMdALC5Yhl+PTlSlhqVkbyi05 +Ae7dcaTW/KAEzZw2mqvVQX2MABaShv49reFhsCfI7EWbdI0CpTQbrCxp9mTI +Lw2nVxF5SGApCQtVJ+cajQ48p2MNwYMbsnMU9oEjy89nXZfU0G3CBRnAnhUP +Sw5WYSDSOgGTtzkIxvyRfaLftZRNfT/5XmvtpCqVWwBKHJ1/qcLEIRWfenqT +M/UQCkZrgB2Ry+4Oc1hgibIUzOM= +=8HM/ -----END PGP MESSAGE-----`; const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- -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 -BoWQifC9dbHD5JNv0/6CMXFZagQABA== +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 +zdmQk3zZBuwE/KAXTAY= -----END PGP PRIVATE KEY BLOCK-----` });