From 7ab99502a9843de18623cb22f20612670a207773 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] 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 bbac7e6205..da9b90132a 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -569,8 +569,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 7d9878c206..7c016ecea0 100644 --- a/src/crypto/public_key/post_quantum/kem/ecc_kem.js +++ b/src/crypto/public_key/post_quantum/kem/ecc_kem.js @@ -52,3 +52,12 @@ export async function decaps(algo, 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 298832a4f3..0a2da99b41 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 affef7016c..eee3a9cd97 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 a51d2fbf12..12d62539f8 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 e76e49473d..4767cea4eb 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 () => {