mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-07-04 03:52:29 +00:00
WIP: Add ML-KEM
This commit is contained in:
parent
c7ae1d88d8
commit
b9b5667a3f
27
package-lock.json
generated
27
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"@noble/ciphers": "^1.0.0",
|
||||
"@noble/curves": "^1.6.0",
|
||||
"@noble/hashes": "^1.5.0",
|
||||
"@noble/post-quantum": "^0.2.0",
|
||||
"@openpgp/jsdoc": "^3.6.11",
|
||||
"@openpgp/seek-bzip": "^1.0.5-git",
|
||||
"@openpgp/tweetnacl": "^1.0.4-1",
|
||||
@ -968,6 +969,32 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/post-quantum": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.0.tgz",
|
||||
"integrity": "sha512-6dXxLXv9qCdj22zTBIRN1J8RrF+OUWQD1vJHNcqCu4JAlSo7KnaRVc+ikDPqvgky43Rn7NGQoWqeo4wv8TAJ/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/post-quantum/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/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -65,6 +65,7 @@
|
||||
"@noble/ciphers": "^1.0.0",
|
||||
"@noble/curves": "^1.6.0",
|
||||
"@noble/hashes": "^1.5.0",
|
||||
"@noble/post-quantum": "^0.2.0",
|
||||
"@openpgp/jsdoc": "^3.6.11",
|
||||
"@openpgp/seek-bzip": "^1.0.5-git",
|
||||
"@openpgp/tweetnacl": "^1.0.4-1",
|
||||
|
@ -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, mlkemPublicKey } = publicKeyParams;
|
||||
const { eccCipherText, mlkemCipherText, C } = sessionKeyParams;
|
||||
return publicKey.postQuantum.kem.decrypt(keyAlgo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, mlkemPublicKey, C.wrappedKey);
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown public key encryption algorithm.');
|
||||
}
|
||||
@ -230,6 +242,16 @@ 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 } };
|
||||
}
|
||||
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.');
|
||||
}
|
||||
@ -242,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:
|
||||
@ -301,6 +323,17 @@ 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 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;
|
||||
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.');
|
||||
}
|
||||
@ -364,6 +397,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,9 +422,20 @@ 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,
|
||||
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);
|
||||
@ -450,6 +500,16 @@ 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, mlkemSeed, mlkemSecretKey, mlkemPublicKey }) => ({
|
||||
privateParams: { eccSecretKey, mlkemSeed, 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.');
|
||||
@ -541,6 +601,16 @@ export async function validateParams(algo, publicParams, privateParams) {
|
||||
return keySize === keyMaterial.length &&
|
||||
util.equalsUint8Array(digest, await hash.sha256(hashSeed));
|
||||
}
|
||||
case enums.publicKey.pqc_mlkem_x25519: {
|
||||
const { eccSecretKey, mlkemSeed } = privateParams;
|
||||
const { eccPublicKey, mlkemPublicKey } = publicParams;
|
||||
return publicKey.postQuantum.kem.validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSeed);
|
||||
}
|
||||
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.');
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
5
src/crypto/public_key/post_quantum/index.js
Normal file
5
src/crypto/public_key/post_quantum/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import * as kem from './kem/index';
|
||||
|
||||
export {
|
||||
kem
|
||||
};
|
62
src/crypto/public_key/post_quantum/kem/ecc_kem.js
Normal file
62
src/crypto/public_key/post_quantum/kem/ecc_kem.js
Normal file
@ -0,0 +1,62 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
2
src/crypto/public_key/post_quantum/kem/index.js
Normal file
2
src/crypto/public_key/post_quantum/kem/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export { generate, encrypt, decrypt, validateParams } from './kem';
|
||||
export { expandSecretSeed as mlkemExpandSecretSeed } from './ml_kem';
|
52
src/crypto/public_key/post_quantum/kem/kem.js
Normal file
52
src/crypto/public_key/post_quantum/kem/kem.js
Normal file
@ -0,0 +1,52 @@
|
||||
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, mlkemSeed, mlkemSecretKey } = await mlKem.generate(algo);
|
||||
|
||||
return { eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSeed, 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 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, mlkemPublicKey, encryptedSessionKeyData) {
|
||||
const eccKeyShare = await eccKem.decaps(algo, eccCipherText, eccSecretKey, eccPublicKey);
|
||||
const mlkemKeyShare = await mlKem.decaps(algo, mlkemCipherText, mlkemSecretKey);
|
||||
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(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([
|
||||
mlkemCipherText,
|
||||
ecdhCipherText,
|
||||
mlkemPublicKey,
|
||||
ecdhPublicKey,
|
||||
new Uint8Array([algo])
|
||||
]);
|
||||
const domainSeparation = util.encodeUTF8('OpenPGPCompositeKDFv1');
|
||||
|
||||
const kek = kmac256(key, encData, { personalization: domainSeparation }); // output length: 256 bits
|
||||
return kek;
|
||||
}
|
||||
|
||||
export async function validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSeed) {
|
||||
const eccValidationPromise = eccKem.validateParams(algo, eccPublicKey, eccSecretKey);
|
||||
const mlkemValidationPromise = mlKem.validateParams(algo, mlkemPublicKey, mlkemSeed);
|
||||
const valid = await eccValidationPromise && await mlkemValidationPromise;
|
||||
return valid;
|
||||
}
|
72
src/crypto/public_key/post_quantum/kem/ml_kem.js
Normal file
72
src/crypto/public_key/post_quantum/kem/ml_kem.js
Normal file
@ -0,0 +1,72 @@
|
||||
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(seed);
|
||||
|
||||
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 { ml_kem768 } = await import('@noble/post-quantum/ml-kem');
|
||||
const { cipherText: mlkemCipherText, sharedSecret: mlkemKeyShare } = ml_kem768.encapsulate(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 { ml_kem768 } = await import('@noble/post-quantum/ml-kem');
|
||||
const mlkemKeyShare = ml_kem768.decapsulate(mlkemCipherText, mlkemSecretKey);
|
||||
|
||||
return mlkemKeyShare;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported KEM algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateParams(algo, mlkemPublicKey, mlkemSeed) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.pqc_mlkem_x25519: {
|
||||
const { mlkemPublicKey: expectedPublicKey } = await expandSecretSeed(algo, mlkemSeed);
|
||||
return util.equalsUint8Array(mlkemPublicKey, expectedPublicKey);
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported KEM algorithm');
|
||||
}
|
||||
}
|
@ -96,6 +96,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 */
|
||||
|
@ -397,6 +397,13 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) {
|
||||
options.sign = options.sign || false;
|
||||
|
||||
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);
|
||||
@ -474,6 +481,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`');
|
||||
}
|
||||
@ -496,7 +504,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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
@ -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;
|
||||
|
@ -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');
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
158
test/crypto/postQuantum.js
Normal file
158
test/crypto/postQuantum.js
Normal file
@ -0,0 +1,158 @@
|
||||
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 - 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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
|
||||
BoWQifC9dbHD5JNv0/6CMXFZagQABA==
|
||||
-----END PGP PRIVATE KEY BLOCK-----`
|
||||
});
|
||||
|
||||
const { data: decryptedData } = await openpgp.decrypt({
|
||||
message: await openpgp.readMessage({ armoredMessage }),
|
||||
decryptionKeys: privateKey
|
||||
});
|
||||
expect(decryptedData).to.equal('Testing\n');
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
@ -314,6 +314,38 @@ export default () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PQC parameter validation', function() {
|
||||
let pqcEncryptionSubkey;
|
||||
before(async () => {
|
||||
const key = await generatePrivateKeyObject({ type: 'symmetric', subkeys: [{ type: 'pqc', config: { v6Keys: true } }] });
|
||||
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 () => {
|
||||
|
@ -4607,6 +4607,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 }),
|
||||
|
Loading…
x
Reference in New Issue
Block a user