diff --git a/.github/test-suite/config.json.template b/.github/test-suite/config.json.template index 8e9a9d3ff..32c74d177 100644 --- a/.github/test-suite/config.json.template +++ b/.github/test-suite/config.json.template @@ -4,7 +4,8 @@ "id": "sop-openpgpjs-branch", "path": "__SOP_OPENPGPJS__", "env": { - "OPENPGPJS_PATH": "__OPENPGPJS_BRANCH__" + "OPENPGPJS_PATH": "__OPENPGPJS_BRANCH__", + "OPENPGPJS_CUSTOM_PROFILES": "{\"generate-key\": { \"post-quantum\": { \"description\": \"generate post-quantum v6 keys (relying on ML-DSA + ML-KEM)\", \"options\": { \"type\": \"pqc\", \"config\": { \"v6Keys\": true } } } } }" } }, { diff --git a/openpgp.d.ts b/openpgp.d.ts index c4da17916..c117482f8 100644 --- a/openpgp.d.ts +++ b/openpgp.d.ts @@ -704,7 +704,7 @@ export type EllipticCurveName = 'ed25519Legacy' | 'curve25519Legacy' | 'nistP256 interface GenerateKeyOptions { userIDs: MaybeArray; passphrase?: string; - type?: 'ecc' | 'rsa' | 'curve25519' | 'curve448'; + type?: 'ecc' | 'rsa' | 'curve25519' | 'curve448' | 'pqc'; curve?: EllipticCurveName; rsaBits?: number; keyExpirationTime?: number; diff --git a/package-lock.json b/package-lock.json index 24411a3d8..620fd4c13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "LGPL-3.0+", "devDependencies": { "@noble/ciphers": "^1.0.0", - "@noble/curves": "^1.6.0", + "@noble/curves": "^1.7.0", "@noble/ed25519": "^1.7.3", "@noble/hashes": "^1.5.0", "@noble/post-quantum": "^0.2.1", @@ -944,12 +944,13 @@ } }, "node_modules/@noble/curves": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", - "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz", + "integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==", "dev": true, + "license": "MIT", "dependencies": { - "@noble/hashes": "1.5.0" + "@noble/hashes": "1.6.0" }, "engines": { "node": "^14.21.3 || >=16" @@ -971,10 +972,11 @@ ] }, "node_modules/@noble/hashes": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", - "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "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" }, @@ -995,19 +997,6 @@ "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", diff --git a/package.json b/package.json index d199bd229..ab76ace24 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ }, "devDependencies": { "@noble/ciphers": "^1.0.0", - "@noble/curves": "^1.6.0", + "@noble/curves": "^1.7.0", "@noble/ed25519": "^1.7.3", "@noble/hashes": "^1.5.0", "@noble/post-quantum": "^0.2.1", diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 2c5a78e2c..293ab2866 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -331,8 +331,9 @@ export async function parsePrivateKeyParams(algo, bytes, publicParams) { } 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 } }; + const mldsaSeed = util.readExactSubarray(bytes, read, read + 32); read += mldsaSeed.length; + const { mldsaSecretKey } = await publicKey.postQuantum.signature.mldsaExpandSecretSeed(algo, mldsaSeed); + return { read, privateParams: { eccSecretKey, mldsaSecretKey, mldsaSeed } }; } default: throw new UnsupportedError('Unknown public key encryption algorithm.'); @@ -428,7 +429,8 @@ export function serializeParams(algo, params) { ]); const excludedFields = { - [enums.publicKey.pqc_mlkem_x25519]: new Set(['mlkemSecretKey']) // only `mlkemSeed` is serialized + [enums.publicKey.pqc_mlkem_x25519]: new Set(['mlkemSecretKey']), // only `mlkemSeed` is serialized + [enums.publicKey.pqc_mldsa_ed25519]: new Set(['mldsaSecretKey']) // only `mldsaSeed` is serialized }; const orderedParams = Object.keys(params).map(name => { @@ -506,8 +508,8 @@ export async function generateParams(algo, bits, oid, symmetric) { publicParams: { eccPublicKey, mlkemPublicKey } })); case enums.publicKey.pqc_mldsa_ed25519: - return publicKey.postQuantum.signature.generate(algo).then(({ eccSecretKey, eccPublicKey, mldsaSecretKey, mldsaPublicKey }) => ({ - privateParams: { eccSecretKey, mldsaSecretKey }, + return publicKey.postQuantum.signature.generate(algo).then(({ eccSecretKey, eccPublicKey, mldsaSeed, mldsaSecretKey, mldsaPublicKey }) => ({ + privateParams: { eccSecretKey, mldsaSeed, mldsaSecretKey }, publicParams: { eccPublicKey, mldsaPublicKey } })); case enums.publicKey.dsa: @@ -607,9 +609,9 @@ export async function validateParams(algo, publicParams, privateParams) { return publicKey.postQuantum.kem.validateParams(algo, eccPublicKey, eccSecretKey, mlkemPublicKey, mlkemSeed); } case enums.publicKey.pqc_mldsa_ed25519: { - const { eccSecretKey, mldsaSecretKey } = privateParams; + const { eccSecretKey, mldsaSeed } = privateParams; const { eccPublicKey, mldsaPublicKey } = publicParams; - return publicKey.postQuantum.signature.validateParams(algo, eccPublicKey, eccSecretKey, mldsaPublicKey, mldsaSecretKey); + return publicKey.postQuantum.signature.validateParams(algo, eccPublicKey, eccSecretKey, mldsaPublicKey, mldsaSeed); } default: throw new Error('Unknown public key algorithm.'); diff --git a/src/crypto/public_key/post_quantum/index.js b/src/crypto/public_key/post_quantum/index.js index 982b28be7..cf803ef88 100644 --- a/src/crypto/public_key/post_quantum/index.js +++ b/src/crypto/public_key/post_quantum/index.js @@ -1,5 +1,7 @@ import * as kem from './kem/index'; +import * as signature from './signature'; export { - kem + kem, + signature }; 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 3789459c6..0651547b0 100644 --- a/src/crypto/public_key/post_quantum/kem/ml_kem.js +++ b/src/crypto/public_key/post_quantum/kem/ml_kem.js @@ -24,7 +24,7 @@ export async function generate(algo) { 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 { ml_kem768 } = await import('../noble_post_quantum'); const { publicKey: encapsulationKey, secretKey: decapsulationKey } = ml_kem768.keygen(seed); return { mlkemPublicKey: encapsulationKey, mlkemSecretKey: decapsulationKey }; @@ -37,7 +37,7 @@ export async function expandSecretSeed(algo, seed) { 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 { ml_kem768 } = await import('../noble_post_quantum'); const { cipherText: mlkemCipherText, sharedSecret: mlkemKeyShare } = ml_kem768.encapsulate(mlkemRecipientPublicKey); return { mlkemCipherText, mlkemKeyShare }; @@ -50,7 +50,7 @@ export async function encaps(algo, mlkemRecipientPublicKey) { 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 { ml_kem768 } = await import('../noble_post_quantum'); const mlkemKeyShare = ml_kem768.decapsulate(mlkemCipherText, mlkemSecretKey); return mlkemKeyShare; diff --git a/src/crypto/public_key/post_quantum/noble_post_quantum.ts b/src/crypto/public_key/post_quantum/noble_post_quantum.ts new file mode 100644 index 000000000..de77098ef --- /dev/null +++ b/src/crypto/public_key/post_quantum/noble_post_quantum.ts @@ -0,0 +1,10 @@ +/** + * This file is needed to dynamic import noble-post-quantum libs. + * Separate dynamic imports are not convenient as they result in multiple chunks, + * which ultimately share a lot of code and need to be imported together + * when it comes to Proton's ML-DSA + ML-KEM keys. + */ + +export { ml_kem768 } from '@noble/post-quantum/ml-kem'; +export { ml_dsa65 } from '@noble/post-quantum/ml-dsa'; + diff --git a/src/crypto/public_key/post_quantum/signature/ecc_dsa.js b/src/crypto/public_key/post_quantum/signature/ecc_dsa.js new file mode 100644 index 000000000..9bf3dfbe4 --- /dev/null +++ b/src/crypto/public_key/post_quantum/signature/ecc_dsa.js @@ -0,0 +1,46 @@ +import * as eddsa from '../../elliptic/eddsa'; +import enums from '../../../../enums'; + +export async function generate(algo) { + switch (algo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const { A, seed } = await eddsa.generate(enums.publicKey.ed25519); + return { + eccPublicKey: A, + eccSecretKey: seed + }; + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function sign(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, dataDigest) { + switch (signatureAlgo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const { RS: eccSignature } = await eddsa.sign(enums.publicKey.ed25519, hashAlgo, null, eccPublicKey, eccSecretKey, dataDigest); + + return { eccSignature }; + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function verify(signatureAlgo, hashAlgo, eccPublicKey, dataDigest, eccSignature) { + switch (signatureAlgo) { + case enums.publicKey.pqc_mldsa_ed25519: + return eddsa.verify(enums.publicKey.ed25519, hashAlgo, { RS: eccSignature }, null, eccPublicKey, dataDigest); + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function validateParams(algo, eccPublicKey, eccSecretKey) { + switch (algo) { + case enums.publicKey.pqc_mldsa_ed25519: + return eddsa.validateParams(enums.publicKey.ed25519, eccPublicKey, eccSecretKey); + default: + throw new Error('Unsupported signature algorithm'); + } +} diff --git a/src/crypto/public_key/post_quantum/signature/index.js b/src/crypto/public_key/post_quantum/signature/index.js new file mode 100644 index 000000000..a1b1dc436 --- /dev/null +++ b/src/crypto/public_key/post_quantum/signature/index.js @@ -0,0 +1,2 @@ +export { generate, sign, verify, validateParams, getRequiredHashAlgo } from './signature'; +export { expandSecretSeed as mldsaExpandSecretSeed } from './ml_dsa'; diff --git a/src/crypto/public_key/post_quantum/signature/ml_dsa.js b/src/crypto/public_key/post_quantum/signature/ml_dsa.js new file mode 100644 index 000000000..0165e71e6 --- /dev/null +++ b/src/crypto/public_key/post_quantum/signature/ml_dsa.js @@ -0,0 +1,69 @@ +import enums from '../../../../enums'; +import util from '../../../../util'; +import { getRandomBytes } from '../../../random'; + +export async function generate(algo) { + switch (algo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const mldsaSeed = getRandomBytes(32); + const { mldsaSecretKey, mldsaPublicKey } = await expandSecretSeed(algo, mldsaSeed); + + return { mldsaSeed, mldsaSecretKey, mldsaPublicKey }; + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +/** + * Expand ML-DSA 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<{ mldsaPublicKey: Uint8Array, mldsaSecretKey: Uint8Array }>} + */ +export async function expandSecretSeed(algo, seed) { + switch (algo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const { ml_dsa65 } = await import('../noble_post_quantum'); + const { secretKey: mldsaSecretKey, publicKey: mldsaPublicKey } = ml_dsa65.keygen(seed); + + return { mldsaSecretKey, mldsaPublicKey }; + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function sign(algo, mldsaSecretKey, dataDigest) { + switch (algo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const { ml_dsa65 } = await import('../noble_post_quantum'); + const mldsaSignature = ml_dsa65.sign(mldsaSecretKey, dataDigest); + return { mldsaSignature }; + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function verify(algo, mldsaPublicKey, dataDigest, mldsaSignature) { + switch (algo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const { ml_dsa65 } = await import('../noble_post_quantum'); + return ml_dsa65.verify(mldsaPublicKey, dataDigest, mldsaSignature); + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function validateParams(algo, mldsaPublicKey, mldsaSeed) { + switch (algo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const { mldsaPublicKey: expectedPublicKey } = await expandSecretSeed(algo, mldsaSeed); + return util.equalsUint8Array(mldsaPublicKey, expectedPublicKey); + } + default: + throw new Error('Unsupported signature algorithm'); + } +} diff --git a/src/crypto/public_key/post_quantum/signature/signature.js b/src/crypto/public_key/post_quantum/signature/signature.js new file mode 100644 index 000000000..fcdfc745c --- /dev/null +++ b/src/crypto/public_key/post_quantum/signature/signature.js @@ -0,0 +1,70 @@ +import enums from '../../../../enums'; +import * as mldsa from './ml_dsa'; +import * as eccdsa from './ecc_dsa'; + +export async function generate(algo) { + switch (algo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const { eccSecretKey, eccPublicKey } = await eccdsa.generate(algo); + const { mldsaSeed, mldsaSecretKey, mldsaPublicKey } = await mldsa.generate(algo); + return { eccSecretKey, eccPublicKey, mldsaSeed, mldsaSecretKey, mldsaPublicKey }; + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function sign(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, mldsaSecretKey, dataDigest) { + if (hashAlgo !== getRequiredHashAlgo(signatureAlgo)) { + // The signature hash algo MUST be set to the specified algorithm, see + // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1. + throw new Error('Unexpected hash algorithm for PQC signature'); + } + + switch (signatureAlgo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const { eccSignature } = await eccdsa.sign(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, dataDigest); + const { mldsaSignature } = await mldsa.sign(signatureAlgo, mldsaSecretKey, dataDigest); + + return { eccSignature, mldsaSignature }; + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function verify(signatureAlgo, hashAlgo, eccPublicKey, mldsaPublicKey, dataDigest, { eccSignature, mldsaSignature }) { + if (hashAlgo !== getRequiredHashAlgo(signatureAlgo)) { + // The signature hash algo MUST be set to the specified algorithm, see + // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1. + throw new Error('Unexpected hash algorithm for PQC signature'); + } + + switch (signatureAlgo) { + case enums.publicKey.pqc_mldsa_ed25519: { + const eccVerifiedPromise = eccdsa.verify(signatureAlgo, hashAlgo, eccPublicKey, dataDigest, eccSignature); + const mldsaVerifiedPromise = mldsa.verify(signatureAlgo, mldsaPublicKey, dataDigest, mldsaSignature); + const verified = await eccVerifiedPromise && await mldsaVerifiedPromise; + return verified; + } + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export function getRequiredHashAlgo(signatureAlgo) { + // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1. + switch (signatureAlgo) { + case enums.publicKey.pqc_mldsa_ed25519: + return enums.hash.sha3_256; + default: + throw new Error('Unsupported signature algorithm'); + } +} + +export async function validateParams(algo, eccPublicKey, eccSecretKey, mldsaPublicKey, mldsaSeed) { + const eccValidationPromise = eccdsa.validateParams(algo, eccPublicKey, eccSecretKey); + const mldsaValidationPromise = mldsa.validateParams(algo, mldsaPublicKey, mldsaSeed); + const valid = await eccValidationPromise && await mldsaValidationPromise; + return valid; +} diff --git a/src/crypto/signature.js b/src/crypto/signature.js index e8c04ba7e..9b7d6a89f 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -70,6 +70,12 @@ export function parseSignatureParams(algo, signature) { const mac = new ShortByteString(); read += mac.read(signature.subarray(read)); return { read, signatureParams: { mac } }; } + case enums.publicKey.pqc_mldsa_ed25519: { + const eccSignatureSize = 2 * publicKey.elliptic.eddsa.getPayloadSize(enums.publicKey.ed25519); + const eccSignature = util.readExactSubarray(signature, read, read + eccSignatureSize); read += eccSignature.length; + const mldsaSignature = util.readExactSubarray(signature, read, read + 3309); read += mldsaSignature.length; + return { read, signatureParams: { eccSignature, mldsaSignature } }; + } default: throw new UnsupportedError('Unknown signature algorithm.'); } @@ -134,6 +140,10 @@ export async function verify(algo, hashAlgo, signature, publicParams, privatePar const { keyMaterial } = privateParams; return publicKey.hmac.verify(algo.getValue(), keyMaterial, signature.mac.data, hashed); } + case enums.publicKey.pqc_mldsa_ed25519: { + const { eccPublicKey, mldsaPublicKey } = publicParams; + return publicKey.postQuantum.signature.verify(algo, hashAlgo, eccPublicKey, mldsaPublicKey, hashed, signature); + } default: throw new Error('Unknown signature algorithm.'); } @@ -195,6 +205,11 @@ export async function sign(algo, hashAlgo, publicKeyParams, privateKeyParams, da const mac = await publicKey.hmac.sign(algo.getValue(), keyMaterial, hashed); return { mac: new ShortByteString(mac) }; } + case enums.publicKey.pqc_mldsa_ed25519: { + const { eccPublicKey } = publicKeyParams; + const { eccSecretKey, mldsaSecretKey } = privateKeyParams; + return publicKey.postQuantum.signature.sign(algo, hashAlgo, eccSecretKey, eccPublicKey, mldsaSecretKey, hashed); + } default: throw new Error('Unknown signature algorithm.'); } diff --git a/src/enums.js b/src/enums.js index 0872bcd4b..2d842b77d 100644 --- a/src/enums.js +++ b/src/enums.js @@ -110,6 +110,9 @@ export default { ed448: 28, /** Post-quantum ML-KEM-768 + X25519 (Encrypt only) */ pqc_mlkem_x25519: 105, + /** Post-quantum ML-DSA-64 + Ed25519 (Sign only) */ + pqc_mldsa_ed25519: 107, + /** 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 44d9f821d..6db537582 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -117,6 +117,12 @@ export async function createBindingSignature(subkey, primaryKey, options, config * @async */ export async function getPreferredHashAlgo(targetKeys, signingKeyPacket, date = new Date(), targetUserIDs = [], config) { + if (signingKeyPacket.algorithm === enums.publicKey.pqc_mldsa_ed25519) { + // For PQC, the returned hash algo MUST be set to the specified algorithm, see + // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1. + return crypto.publicKey.postQuantum.signature.getRequiredHashAlgo(signingKeyPacket.algorithm); + } + /** * If `preferredSenderAlgo` appears in the prefs of all recipients, we pick it; otherwise, we use the * strongest supported algo (`defaultAlgo` is always implicitly supported by all keys). @@ -405,7 +411,7 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) { switch (options.type) { case 'pqc': if (options.sign) { - throw new Error('Post-quantum signing algorithms are not yet supported.'); + options.algorithm = enums.publicKey.pqc_mldsa_ed25519; } else { options.algorithm = enums.publicKey.pqc_mlkem_x25519; } @@ -468,6 +474,7 @@ export function validateSigningKeyPacket(keyPacket, signature, config) { case enums.publicKey.ed25519: case enums.publicKey.ed448: case enums.publicKey.hmac: + case enums.publicKey.pqc_mldsa_ed25519: if (!signature.keyFlags && !config.allowMissingKeyFlags) { throw new Error('None of the key flags is set: consider passing `config.allowMissingKeyFlags`'); } diff --git a/src/packet/public_key.js b/src/packet/public_key.js index e444f7504..09e2fe45a 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -138,9 +138,13 @@ class PublicKeyPacket { ) { throw new Error('Legacy curve25519 cannot be used with v6 keys'); } + // The composite ML-DSA + EdDSA schemes MUST be used only 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'); + if (this.version !== 6 && ( + this.algorithm === enums.publicKey.pqc_mldsa_ed25519 || + this.algorithm === enums.publicKey.pqc_mlkem_x25519 + )) { + throw new Error('Unexpected key version: ML-DSA and 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 7356ac1e5..e94e00a62 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -532,7 +532,10 @@ 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) { + if (this.version !== 6 && ( + this.algorithm === enums.publicKey.pqc_mldsa_ed25519 || + 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); diff --git a/test/crypto/postQuantum.js b/test/crypto/postQuantum.js index 2f58ae0d5..5d93a84d9 100644 --- a/test/crypto/postQuantum.js +++ b/test/crypto/postQuantum.js @@ -1,9 +1,593 @@ +/* eslint-disable max-lines */ 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'; +import { sign, verify } from '../../src/crypto/signature.js'; + +// Test vector from https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-06.html#appendix-A.2.1 +const mldsaEd25519AndMlkemX25519PrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +-----END PGP PRIVATE KEY BLOCK-----`; + +const mldsaEd25519AndMlkemX25519PublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +AAAAAAAAAAAABQwRGCAn +-----END PGP PUBLIC KEY BLOCK-----`; export default () => describe('PQC', function () { it('ML-KEM + X25519 - Generate/encrypt/decrypt', async function () { @@ -155,4 +739,471 @@ zdmQk3zZBuwE/KAXTAY= }); expect(decryptedData).to.equal('Testing\n'); }); + + it('ML-DSA + Ed25519 - Generate/sign/verify', async function () { + const digest = new Uint8Array(32).fill(1); + const hashAlgo = openpgp.enums.hash.sha3_256; + + const { privateParams, publicParams } = await generateParams(openpgp.enums.publicKey.pqc_mldsa_ed25519); + const signature = await sign(openpgp.enums.publicKey.pqc_mldsa_ed25519, hashAlgo, publicParams, privateParams, null, digest); + const verified = await verify(openpgp.enums.publicKey.pqc_mldsa_ed25519, hashAlgo, signature, publicParams, null, null, digest); + expect(verified).to.be.true; + }); + + it('ML-DSA + Ed25519 - private key is correctly serialized using the seed instead of the expanded secret key material', async function () { + const armoredKey = mldsaEd25519AndMlkemX25519PrivateKey; + + const { data: expectedBinaryKey } = await openpgp.unarmor(armoredKey); + + const privateKey = await openpgp.readKey({ armoredKey }); + expect(privateKey.write()).to.deep.equal(expectedBinaryKey); + }); + + it('ML-DSA + Ed25519 - Test vector', async function () { + const privateKey = await openpgp.readKey({ armoredKey: mldsaEd25519AndMlkemX25519PrivateKey }); + const publicKey = await openpgp.readKey({ armoredKey: mldsaEd25519AndMlkemX25519PublicKey }); + + // `getSigningKey()` internally verifies the ML-DSA binding sigs + const signingKey1 = await privateKey.getSigningKey(); + const signingKey2 = await publicKey.getSigningKey(); + + expect(signingKey1.getKeyID().equals(signingKey2.getKeyID())).to.be.true; + }); + + it('ML-DSA + Ed25519 - throws on unexpected signature algorithm', async () => { + // The signature hash algo MUST be set to the specified algorithm, see + // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1. + const privateKeyWithInvalidSignatures = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +QkebRJCZxZG/RCVh66PXOpv3JKQY2VscDzf7x22FDxuCDiKR72Mi15XydCi6fnQI +ug1/j6olqArvt51WpBoM/aN5uD7fuzqcv5OaCXuhyck6lhLRfp0/YRaYdlciihOb +1jHtdO4545qpeb/qETayE7R88zrUQLkiNsjdGdxHgs+MHl64KFC3D6mDvkqnFdqb +7Nd4FxDCBGv5ktQlKFKhGWtrR7PWnOYif6JqzzgsxWnC1nmVEAeWtw17s4tuke4c +Rh5owUNJBZZygKBJsn37H5cXoZHw8uzmESNxgMxw9zVUoeOc+l4eX5ynM+FxmXhx +IhOH1sQluFb1MpQglVPBrWzz/2NKvV9tsQq+lISpoewliGawL6Vh7X8qyvzad1Lm +ofEp0p9RrUwl/iGveCR0bdX11D5P/JyetN97OmQoLGmI9FWWrCZ46rlqMuH6+53i +ejXDUGXOnn0D27y3MdmFtklIMOMNapEt5Bz7iFbtA0uxPBD6TjlYwP5l6FEzjVyE +/KeOyv+oVZzTFsKW2mfyfPrj2N+DyPrMknZzI2joNGUkKfcuRcO681OU6tX3bj01 +sTWo7F6wg4ZQdT6Aje2ryScj24eHAjbGRqI7VQGvDx/kYmSXdrbhHG4Hnmk6mhG5 +NO1kR6Wu8EioJ2wCjfxBmzHGu4RP2+pWji8KIVeIy3LYfyKpgP8jxx62VbGYrUV4 +Iag+OPBukfe3QLhr3p5iMnoPI0ihwNYmCzCvJ76IKLmfjwchvT/W0a29syQuKcNa +ofjSihIlUs3ug1T/ZG3Xyl7NF2h0YFHnF7Rpm3WRSZQsePbMGmwiEck0UFP/Z/DP +Fe1V8lkH3hEty4NtMUqtn5F1VHalFs4C5UYKHtBRuZvqjvr0xnbZDFouCc7m4Vjp +lzsS1tSjLl6EAyonAbbQ3vCtTn3v9k8Ro8J1p9mRHA1NdBCTI9J8Lkml+f1IIn8g +VdHTTiYcqDxM/zRPmC5ve6fAD+BFh7qvDkIGqpB0FIkHnstjp2/XFaxJ1Xso7e0Q +Yo30tzVyBFNu8KQF8Okeh2Mz6M2oKn8GInjeDMWD9DxlZwHOnlcWuXFeurj79G5Y +iUffg9el18KCwIMrnDD48xUqv1gYDRy/1LAp3AE3nnm5cIPq1Rp4Nsi8DQBOW/ZL +J7HW1IXs6o2U7Hytlx+KLZ2a3ETlZ5Nu86OwZYhErsLxDd3wF/85NYUzMxvO5uAV +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 +-----END PGP PRIVATE KEY BLOCK-----` }); + + await expect(privateKeyWithInvalidSignatures.getSigningKey()).to.be.rejectedWith(/Unexpected hash algorithm for PQC signature/); + }); + + it('ML-DSA + ML-KEM - Test vector: decrypt/verify', async function () { + const privateKey = await openpgp.readKey({ armoredKey: mldsaEd25519AndMlkemX25519PrivateKey }); + + const publicKey = privateKey.toPublic(); + + // NB: this is NOT the test vector from draft-ietf-openpgp-pqc-06, + // since we use a different KEM combiner + const armoredMessage = `-----BEGIN PGP MESSAGE----- + +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 +6LfQlrZ3DteWPwEVWDEBfKKhFyTzzgk= +-----END PGP MESSAGE-----`; + + const { data: decryptedData, signatures: [{ verified }] } = await openpgp.decrypt({ + message: await openpgp.readMessage({ armoredMessage }), + decryptionKeys: privateKey, + verificationKeys: publicKey + }); + expect(decryptedData).to.equal('Testing\n'); + expect(await verified).to.be.true; + }); + + it('ML-DSA + ML-KEM - encrypt/sign and decrypt/verify', async function () { + const privateKey = await openpgp.readKey({ armoredKey: mldsaEd25519AndMlkemX25519PrivateKey }); + const publicKey = privateKey.toPublic(); + + const plaintext = 'Testing\n'; + const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ text: plaintext }), signingKeys: privateKey, encryptionKeys: publicKey }); + const { data, signatures: [{ verified }] } = await openpgp.decrypt({ message: await openpgp.readMessage({ armoredMessage: encrypted }), verificationKeys: publicKey, decryptionKeys: privateKey }); + expect(data).to.equal(plaintext); + await expect(verified).to.eventually.be.true; + }); }); diff --git a/test/crypto/validate.js b/test/crypto/validate.js index 7c8209268..ca448c74e 100644 --- a/test/crypto/validate.js +++ b/test/crypto/validate.js @@ -315,31 +315,41 @@ export default () => { }); describe('PQC parameter validation', function() { + let pqcSigningKey; let pqcEncryptionSubkey; before(async () => { - const key = await generatePrivateKeyObject({ type: 'symmetric', subkeys: [{ type: 'pqc' }], config: { v6Keys: true } }); - pqcEncryptionSubkey = key.subkeys[0]; + pqcSigningKey = await generatePrivateKeyObject({ type: 'pqc', config: { v6Keys: true } }); + pqcEncryptionSubkey = pqcSigningKey.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(pqcSigningKey.keyPacket.validate()).to.not.be.rejected; 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 keyPacket = await cloneKeyPacket(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 keyPacket = await cloneKeyPacket(pqcEncryptionSubkey); + const { eccPublicKey } = keyPacket.publicParams; + eccPublicKey[0]++; + await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + }); + + it('detect invalid ML-DSA public key part', async function() { + const keyPacket = await cloneKeyPacket(pqcSigningKey); + const { mldsaPublicKey } = keyPacket.publicParams; + mldsaPublicKey[0]++; + await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + }); + + it('detect invalid ECC part', async function() { + const keyPacket = await cloneKeyPacket(pqcSigningKey); const { eccPublicKey } = keyPacket.publicParams; eccPublicKey[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); diff --git a/test/general/key.js b/test/general/key.js index 9adedb91b..97387e309 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -4615,15 +4615,19 @@ 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() { + it('should throw when trying to add a ML-KEM or ML-DSA 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); + // try adding an ML-KEM subkey 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); + // try adding an ML-DSA subkey + await expect(v4Key.addSubkey({ type: 'pqc', sign: true })).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() {