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----- -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 -/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 }),