Skip to content

Commit

Permalink
Switch to seed format for ML-KEM private key, update test vectors (dr…
Browse files Browse the repository at this point in the history
…aft 5)
  • Loading branch information
larabr committed Nov 25, 2024
1 parent f82c3e0 commit f7b3e44
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 141 deletions.
24 changes: 17 additions & 7 deletions src/crypto/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/crypto/public_key/post_quantum/kem/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { generate, encrypt, decrypt, validateParams } from './kem';
export { expandSecretSeed as mlkemExpandSecretSeed } from './ml_kem';
8 changes: 4 additions & 4 deletions src/crypto/public_key/post_quantum/kem/kem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
30 changes: 24 additions & 6 deletions src/crypto/public_key/post_quantum/kem/ml_kem.js
Original file line number Diff line number Diff line change
@@ -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 };
}
Expand Down Expand Up @@ -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');
Expand Down
4 changes: 2 additions & 2 deletions src/packet/secret_key.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down Expand Up @@ -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');
Expand Down
8 changes: 4 additions & 4 deletions test/crypto/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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');

Expand Down
Loading

0 comments on commit f7b3e44

Please sign in to comment.