diff --git a/ChangeLog.md b/ChangeLog.md index f3107ffb0bf..1e05df72bd3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,9 @@ # wolfSSL Release (unreleased) +## Post-Quantum Cryptography (PQC) + +* Added ML-KEM (FIPS 203) key OIDs, SubjectPublicKeyInfo and PKCS#8 encoding, and X.509 certificate support, including issuing an ML-KEM certificate with `wc_MakeCert_ex`. A key initialised with the new `WC_ML_KEM_TYPE_UNSET` takes its parameter set from the DER being decoded. by @Frauschi + ## Behavioral Changes * **Behavioral change (`wc_PufReadSram` health tests the raw SRAM readout)**: diff --git a/certs/include.am b/certs/include.am index 45c467b295c..579b20f3e95 100644 --- a/certs/include.am +++ b/certs/include.am @@ -184,4 +184,5 @@ include certs/lms/include.am include certs/rpk/include.am include certs/acert/include.am include certs/mldsa/include.am +include certs/mlkem/include.am include certs/frodokem/include.am diff --git a/certs/mlkem/README.txt b/certs/mlkem/README.txt new file mode 100644 index 00000000000..06dc0ae3f63 --- /dev/null +++ b/certs/mlkem/README.txt @@ -0,0 +1,34 @@ +ML-KEM (FIPS 203) test key material for wolfSSL tests. + +Per level N in {512, 768, 1024}: + mlkem-cert.der ML-KEM end-entity certificate, DER + mlkem-key.der matching private key, PKCS#8 DER (expandedKey form) + +A KEM cannot sign, so these certificates cannot be self-signed. Each is +issued by the ML-DSA-87 test key in certs/mldsa, so certs/mldsa/mldsa87-cert.der +is the issuer and certs/mldsa/mldsa87-key.der signed them. + +The certificates follow the CNSA 2.0 profile for a key establishment +certificate: the keyUsage extension is marked critical and asserts +keyEncipherment alone. The subjectPublicKeyInfo algorithm is +2.16.840.1.101.3.4.4.N with the parameters field absent, and the signature +algorithm is 2.16.840.1.101.3.4.3.19 (ML-DSA-87). + +Regenerate them with certs/renewcerts.sh, which needs an OpenSSL 3.5+ binary +with the built-in ML-DSA and ML-KEM providers. The ML-DSA section runs first +and produces the issuer; the ML-KEM section then builds each certificate from +a throwaway request whose public key is replaced with the ML-KEM one +("x509 -req -force_pubkey"), because ML-KEM cannot sign a request either. + +The private keys are written with -provparam ml-kem.output_formats=priv-only, +which is the RFC 9935 section 6 expandedKey shape. That form decodes without +expanding a seed, so the tests pass in WOLFSSL_MLKEM_NO_MAKE_KEY builds too; +the OpenSSL default (seed and expanded key together) would not. + +The keys are freshly generated on every run, so the bytes differ each time; +only the structure is reproducible. The files committed here were produced by +wolfSSL itself before renewcerts.sh could make them, so a regenerated +certificate also carries an authorityKeyIdentifier that the committed ones do +not. Nothing in the test suite depends on that. + +These are shared test credentials. Never use them in production. diff --git a/certs/mlkem/include.am b/certs/mlkem/include.am new file mode 100644 index 00000000000..fd8e1a28a5e --- /dev/null +++ b/certs/mlkem/include.am @@ -0,0 +1,12 @@ +# vim:ft=automake +# All paths should be given relative to the root +# + +EXTRA_DIST += \ + certs/mlkem/README.txt \ + certs/mlkem/mlkem512-cert.der \ + certs/mlkem/mlkem512-key.der \ + certs/mlkem/mlkem768-cert.der \ + certs/mlkem/mlkem768-key.der \ + certs/mlkem/mlkem1024-cert.der \ + certs/mlkem/mlkem1024-key.der diff --git a/certs/mlkem/mlkem1024-cert.der b/certs/mlkem/mlkem1024-cert.der new file mode 100644 index 00000000000..f7225d8a5b3 Binary files /dev/null and b/certs/mlkem/mlkem1024-cert.der differ diff --git a/certs/mlkem/mlkem1024-key.der b/certs/mlkem/mlkem1024-key.der new file mode 100644 index 00000000000..28df16d03b2 Binary files /dev/null and b/certs/mlkem/mlkem1024-key.der differ diff --git a/certs/mlkem/mlkem512-cert.der b/certs/mlkem/mlkem512-cert.der new file mode 100644 index 00000000000..751c5746f61 Binary files /dev/null and b/certs/mlkem/mlkem512-cert.der differ diff --git a/certs/mlkem/mlkem512-key.der b/certs/mlkem/mlkem512-key.der new file mode 100644 index 00000000000..b0e75cf45b6 Binary files /dev/null and b/certs/mlkem/mlkem512-key.der differ diff --git a/certs/mlkem/mlkem768-cert.der b/certs/mlkem/mlkem768-cert.der new file mode 100644 index 00000000000..0ada00049fb Binary files /dev/null and b/certs/mlkem/mlkem768-cert.der differ diff --git a/certs/mlkem/mlkem768-key.der b/certs/mlkem/mlkem768-key.der new file mode 100644 index 00000000000..6359be082b6 Binary files /dev/null and b/certs/mlkem/mlkem768-key.der differ diff --git a/certs/renewcerts.sh b/certs/renewcerts.sh index d0743711175..94e15cdeeb3 100755 --- a/certs/renewcerts.sh +++ b/certs/renewcerts.sh @@ -1256,9 +1256,9 @@ EOF ############################################################ # ML-DSA requires an OpenSSL 3.5+ binary with the built-in ML-DSA provider. # Besides key/cert generation the block also produces the expanded-only - # PKCS#8 key.der (-provparam ml-dsa.output_formats=priv, a 3.5+ built-in - # construct) that the PKCS#7 tests decode without keygen-from-seed. The - # probe below requires both keygen and that conversion, so the common + # PKCS#8 key.der (-provparam ml-dsa.output_formats=priv-only, a 3.5+ + # built-in construct) that the PKCS#7 tests decode without keygen-from-seed. + # The probe below requires both keygen and that conversion, so the common # unsuitable binaries (oqsprovider or pre-3.5, which lack the expanded-only # conversion) are rejected here and the block is skipped cleanly rather # than aborting after writing a cert.der but no matching key.der. @@ -1285,7 +1285,7 @@ EOF if ! "$candidate" genpkey -algorithm "mldsa${probe_level}" \ -out "$probe_key" 2>/dev/null || \ ! "$candidate" pkey -in "$probe_key" \ - -provparam ml-dsa.output_formats=priv -outform DER \ + -provparam ml-dsa.output_formats=priv-only -outform DER \ -out /dev/null 2>/dev/null; then probe_ok=0 break @@ -1328,7 +1328,7 @@ EOF # builds too; the seed-and-expanded default would not. The probe # above already verified this binary supports the conversion. "$OPENSSL3" pkey -in "mldsa/mldsa${level}-key.pem" \ - -provparam ml-dsa.output_formats=priv -outform DER \ + -provparam ml-dsa.output_formats=priv-only -outform DER \ -out "mldsa/mldsa${level}-key.der" check_result $? "ML-DSA-${level} key DER conversion" @@ -1399,6 +1399,104 @@ EOF echo "---------------------------------------------------------------------" fi + ############################################################ + #### ML-KEM (FIPS 203) key establishment certificates ### + ############################################################ + # ML-KEM is a KEM, so it cannot sign anything, including a certificate + # request or its own certificate. Each end-entity certificate here is + # issued by the ML-DSA-87 certificate produced above, per RFC 9935 and the + # CNSA 2.0 PKIX profile, using a throwaway request that only carries the + # subject name - "x509 -req -force_pubkey" replaces its public key with the + # ML-KEM one before signing. That request is signed by the ML-DSA-87 CA key + # itself, so no extra key material is needed. + # + # The private keys are written in the priv-only PKCS#8 shape (RFC 9935 + # section 6 expandedKey), which decodes without keygen-from-seed and so + # works in WOLFSSL_MLKEM_NO_MAKE_KEY builds too. The seed-priv default + # would not. + # + # This needs the same OpenSSL 3.5+ binary as the ML-DSA block, plus its + # built-in ML-KEM provider, so it is probed separately: a binary with + # ML-DSA but no ML-KEM still produces the ML-DSA material above. + if [ -n "$OPENSSL3" ] && [ -f mldsa/mldsa87-cert.pem ]; then + mlkem_probe_key="$(mktemp)" + mlkem_ok=1 + for probe_level in 512 768 1024; do + if ! "$OPENSSL3" genpkey -algorithm "ML-KEM-${probe_level}" \ + -out "$mlkem_probe_key" 2>/dev/null || \ + ! "$OPENSSL3" pkey -in "$mlkem_probe_key" \ + -provparam ml-kem.output_formats=priv-only -outform DER \ + -out /dev/null 2>/dev/null; then + mlkem_ok=0 + break + fi + done + rm -f "$mlkem_probe_key" + else + mlkem_ok=0 + fi + + if [ "$mlkem_ok" -eq 1 ]; then + echo "Generating ML-KEM certificates using: $OPENSSL3" + echo "" + mkdir -p mlkem + + # CNSA 2.0 key establishment certificate: keyUsage critical, asserting + # keyEncipherment and nothing else (RFC 9935 section 5). + cat > mlkem/mlkem.ext <[$i]->{add_sum}; } + if (exists $oids->[$i]->{guard}) { + print "#ifdef " . $oids->[$i]->{guard} . "\n"; + } print " /* " . oid_array_to_string(@a) . " */\n"; if ($comment_col == 0) { print " /* " . dotted_to_string($oids->[$i]->{oid}) . " */\n"; @@ -131,6 +134,9 @@ sub print_enum { else { print "\n"; } + if (exists $oids->[$i]->{guard}) { + print "#endif\n"; + } } print "#else\n"; for (my $i = 0; $i < 0+@$oids; $i++) { @@ -143,6 +149,9 @@ sub print_enum { $oid_sum_xors{$sum} = $oids->[$i]->{name}; } + if (exists $oids->[$i]->{guard}) { + print "#ifdef " . $oids->[$i]->{guard} . "\n"; + } print " /* " . oid_array_to_string(@a) . " */\n"; if ($comment_col == 0) { print " /* " . dotted_to_string($oids->[$i]->{oid}) . " */\n"; @@ -161,6 +170,9 @@ sub print_enum { else { print "\n"; } + if (exists $oids->[$i]->{guard}) { + print "#endif\n"; + } } print "#endif\n"; print "};\n\n" @@ -321,6 +333,10 @@ sub print_footer { my @mldsa_2 = ( 2, 16, 840, 1, 101, 3, 4, 3, 17 ); my @mldsa_3 = ( 2, 16, 840, 1, 101, 3, 4, 3, 18 ); my @mldsa_5 = ( 2, 16, 840, 1, 101, 3, 4, 3, 19 ); +# ML-KEM (FIPS 203) key OIDs, NIST arc 2.16.840.1.101.3.4.4.x +my @mlkem_512 = ( 2, 16, 840, 1, 101, 3, 4, 4, 1 ); +my @mlkem_768 = ( 2, 16, 840, 1, 101, 3, 4, 4, 2 ); +my @mlkem_1024 = ( 2, 16, 840, 1, 101, 3, 4, 4, 3 ); my @slhdsa_sha2_128s = (2, 16, 840, 1, 101, 3, 4, 3, 20); my @slhdsa_sha2_128f = (2, 16, 840, 1, 101, 3, 4, 3, 21); my @slhdsa_sha2_192s = (2, 16, 840, 1, 101, 3, 4, 3, 22); @@ -385,6 +401,9 @@ sub print_footer { { name => "HSS_LMS", oid => \@hss_lms }, { name => "XMSS", oid => \@xmss }, { name => "XMSSMT", oid => \@xmssmt }, + { name => "ML_KEM_512", oid => \@mlkem_512 }, + { name => "ML_KEM_768", oid => \@mlkem_768 }, + { name => "ML_KEM_1024", oid => \@mlkem_1024 }, { name => "FRODOKEM_976_SHAKE", oid => \@frodokem_976_shake, add_sum => 100000 }, { name => "FRODOKEM_1344_SHAKE", oid => \@frodokem_1344_shake, add_sum => 100000 }, { name => "EFRODOKEM_976_SHAKE", oid => \@efrodokem_976_shake, add_sum => 100000 }, @@ -483,6 +502,8 @@ sub print_footer { my @policy_const = ( 2, 5, 29, 36 ); my @issue_alt_names = ( 2, 5, 29, 18 ); my @tls_feature = ( 1, 3, 6, 1, 5, 5, 7, 1, 24 ); +# RFC 8737 acmeIdentifier, only compiled in with WOLFSSL_ACME_OID +my @acme_identifier = ( 1, 3, 6, 1, 5, 5, 7, 1, 31 ); my @dns_srv = ( 1, 3, 6, 1, 5, 5, 7, 8, 7 ); my @netscape_ct = ( 2, 16, 840, 1, 113730, 1, 1 ); my @ocsp_nocheck = ( 1, 3, 6, 1, 5, 5, 7, 48, 1, 5 ); @@ -513,6 +534,8 @@ sub print_footer { { name => "POLICY_CONST", oid => \@policy_const }, { name => "ISSUE_ALT_NAMES", oid => \@issue_alt_names }, { name => "TLS_FEATURE", oid => \@tls_feature }, + { name => "ACME_IDENTIFIER", oid => \@acme_identifier, + guard => "WOLFSSL_ACME_OID" }, { name => "DNS_SRV", oid => \@dns_srv }, { name => "NETSCAPE_CT", oid => \@netscape_ct }, { name => "OCSP_NOCHECK", oid => \@ocsp_nocheck }, diff --git a/tests/unit-mcdc/test_asn_certgen_whitebox.c b/tests/unit-mcdc/test_asn_certgen_whitebox.c index 724df78990e..ccc23986ccd 100644 --- a/tests/unit-mcdc/test_asn_certgen_whitebox.c +++ b/tests/unit-mcdc/test_asn_certgen_whitebox.c @@ -1101,12 +1101,12 @@ static void wb_set_keyid_from_pubkey(void) /* cert==NULL -> 1st operand true. */ ret = SetKeyIdFromPublicKey(NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":31441 1st operand true (cert==NULL)"); /* cert!=NULL, all key ptrs NULL -> 2nd operand true. */ ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":31441 2nd operand true (all key ptrs NULL)"); @@ -1118,7 +1118,8 @@ static void wb_set_keyid_from_pubkey(void) RsaKey dummyKey; XMEMSET(&dummyKey, 0, sizeof(dummyKey)); ret = SetKeyIdFromPublicKey(&cert, &dummyKey, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, 99 /* neither SKID_TYPE nor AKID_TYPE */); + NULL, NULL, NULL, NULL, + 99 /* neither SKID_TYPE nor AKID_TYPE */); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":31441 3rd operand true (bad kid_type, white-box only)"); } @@ -3199,7 +3200,7 @@ static void wb_make_cert_buffer_guards(void) cert->subject.commonNameEnc = CTC_UTF8; cert->sigType = CTC_SHA256wRSA; ret = MakeAnyCert(cert, der, DERSZ, NULL, NULL, &rng, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":30119/:30301/:30320/:30353 1st operands false (no key)"); @@ -3254,7 +3255,7 @@ static void wb_make_cert_buffer_guards(void) cert->subject.commonNameEnc = CTC_UTF8; cert->sigType = CTC_SHA256wRSA; ret = MakeCertReq(cert, der, DERSZ, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":30747 1st operand false (no key)"); diff --git a/tests/unit-mcdc/test_asn_fault_whitebox.c b/tests/unit-mcdc/test_asn_fault_whitebox.c index a9086aecc57..83fe84e9d23 100644 --- a/tests/unit-mcdc/test_asn_fault_whitebox.c +++ b/tests/unit-mcdc/test_asn_fault_whitebox.c @@ -87,7 +87,7 @@ * 19. ParseKeyUsageStr()/ParseExtKeyUsageStr() NULL OR .. :28135,:28198 * 20. wc_SetSubjectKeyId()/wc_SetAuthKeyId()/wc_SetIssuer()/ * wc_SetSubject() cert/file NULL OR .... :31812,:31978,:32381,:32404 - * 21. SetKeyIdFromPublicKey() 11-operand cert/key/kid_type OR .... :31594 + * 21. SetKeyIdFromPublicKey() 12-operand cert/key/kid_type OR .... :31594 * 22. GetFormattedTime_ex() buf/len/format OR ................... :15901 * 23. wc_MIME_parse_headers() in/inLen/terminator/headers OR .... :38530 * 24. wc_GetFASCNFromCert() otherName/oidSum AND ................ :27036 @@ -1561,7 +1561,7 @@ static void wb_cert_file_setters_null_args(void) #endif /* ------------------------------------------------------------------------- * - * Section 21: SetKeyIdFromPublicKey() (:31594), the 11-operand chain. + * Section 21: SetKeyIdFromPublicKey() (:31594), the 12-operand chain. * if (cert == NULL || * (rsakey==NULL && eckey==NULL && ed25519Key==NULL && ed448Key==NULL && * falconKey==NULL && mldsaKey==NULL && slhDsaKey==NULL && @@ -1589,19 +1589,19 @@ static void wb_set_keyid_from_pubkey_operands(void) static byte opaque[8]; int ret; - WB_NOTE("SetKeyIdFromPublicKey(): 11-operand cert/key/kid_type OR " + WB_NOTE("SetKeyIdFromPublicKey(): 12-operand cert/key/kid_type OR " "[:31594]"); WB_CHECK(wc_InitCert(&cert) == 0, "wc_InitCert"); /* 1st operand true. */ ret = SetKeyIdFromPublicKey(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, SKID_TYPE); + NULL, NULL, SKID_TYPE); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "cert==NULL"); /* Inner AND all true (no key supplied) -> 2nd operand true. */ ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "every key pointer NULL"); /* --- one row per key-pointer operand: that operand false, guard false. */ @@ -1610,7 +1610,7 @@ static void wb_set_keyid_from_pubkey_operands(void) RsaKey k; if (wc_InitRsaKey(&k, NULL) == 0) { ret = SetKeyIdFromPublicKey(&cert, &k, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "rsakey!=NULL (guard false)"); wc_FreeRsaKey(&k); @@ -1618,7 +1618,7 @@ static void wb_set_keyid_from_pubkey_operands(void) } #else ret = SetKeyIdFromPublicKey(&cert, (RsaKey*)(void*)opaque, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "rsakey!=NULL (guard false, RSA not compiled)"); #endif @@ -1628,7 +1628,7 @@ static void wb_set_keyid_from_pubkey_operands(void) ecc_key k; if (wc_ecc_init(&k) == 0) { ret = SetKeyIdFromPublicKey(&cert, NULL, &k, NULL, NULL, NULL, - NULL, NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "eckey!=NULL (guard false)"); wc_ecc_free(&k); @@ -1636,7 +1636,7 @@ static void wb_set_keyid_from_pubkey_operands(void) } #else ret = SetKeyIdFromPublicKey(&cert, NULL, (ecc_key*)(void*)opaque, NULL, - NULL, NULL, NULL, NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "eckey!=NULL (guard false, ECC not compiled)"); #endif @@ -1646,7 +1646,7 @@ static void wb_set_keyid_from_pubkey_operands(void) ed25519_key k; if (wc_ed25519_init(&k) == 0) { ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, &k, NULL, NULL, - NULL, NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "ed25519Key!=NULL (guard false)"); wc_ed25519_free(&k); @@ -1654,7 +1654,7 @@ static void wb_set_keyid_from_pubkey_operands(void) } #else ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, (ed25519_key*)(void*)opaque, - NULL, NULL, NULL, NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "ed25519Key!=NULL (guard false, Ed25519 not compiled)"); #endif @@ -1664,7 +1664,7 @@ static void wb_set_keyid_from_pubkey_operands(void) ed448_key k; if (wc_ed448_init(&k) == 0) { ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, &k, NULL, - NULL, NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "ed448Key!=NULL (guard false)"); wc_ed448_free(&k); @@ -1672,7 +1672,7 @@ static void wb_set_keyid_from_pubkey_operands(void) } #else ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, - (ed448_key*)(void*)opaque, NULL, NULL, NULL, NULL, SKID_TYPE); + (ed448_key*)(void*)opaque, NULL, NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "ed448Key!=NULL (guard false, Ed448 not compiled)"); #endif @@ -1682,7 +1682,7 @@ static void wb_set_keyid_from_pubkey_operands(void) falcon_key k; if (wc_falcon_init(&k) == 0) { ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, &k, - NULL, NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "falconKey!=NULL (guard false)"); wc_falcon_free(&k); @@ -1690,7 +1690,7 @@ static void wb_set_keyid_from_pubkey_operands(void) } #else ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, - (falcon_key*)(void*)opaque, NULL, NULL, NULL, SKID_TYPE); + (falcon_key*)(void*)opaque, NULL, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "falconKey!=NULL (guard false, Falcon not compiled)"); #endif @@ -1700,7 +1700,7 @@ static void wb_set_keyid_from_pubkey_operands(void) wc_MlDsaKey k; if (wc_MlDsaKey_Init(&k, NULL, INVALID_DEVID) == 0) { ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, NULL, - &k, NULL, NULL, SKID_TYPE); + &k, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "mldsaKey!=NULL (guard false)"); wc_MlDsaKey_Free(&k); @@ -1708,7 +1708,7 @@ static void wb_set_keyid_from_pubkey_operands(void) } #else ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, NULL, - (wc_MlDsaKey*)(void*)opaque, NULL, NULL, SKID_TYPE); + (wc_MlDsaKey*)(void*)opaque, NULL, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "mldsaKey!=NULL (guard false, ML-DSA not compiled)"); #endif @@ -1720,7 +1720,7 @@ static void wb_set_keyid_from_pubkey_operands(void) if (k != NULL) { if (wc_SlhDsaKey_Init(k, NULL, INVALID_DEVID) == 0) { ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, - NULL, NULL, k, NULL, SKID_TYPE); + NULL, NULL, k, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "slhDsaKey!=NULL (guard false)"); wc_SlhDsaKey_Free(k); @@ -1730,7 +1730,7 @@ static void wb_set_keyid_from_pubkey_operands(void) } #else ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, NULL, NULL, - (SlhDsaKey*)(void*)opaque, NULL, SKID_TYPE); + (SlhDsaKey*)(void*)opaque, NULL, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "slhDsaKey!=NULL (guard false, SLH-DSA not compiled)"); #endif @@ -1738,11 +1738,19 @@ static void wb_set_keyid_from_pubkey_operands(void) #if !defined(WOLFSSL_HAVE_FRODOKEM) || defined(WOLFSSL_FRODOKEM_NO_ASN1) /* frodoKey is a void* and its export block is compiled out here. */ ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, (void*)opaque, SKID_TYPE); + NULL, (void*)opaque, NULL, SKID_TYPE); WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), "frodoKey!=NULL (guard false, FrodoKEM not compiled)"); #endif +#if !defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_MLKEM_NO_ASN1) + /* mlKemKey is a void* and its export block is compiled out here. */ + ret = SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, (void*)opaque, SKID_TYPE); + WB_CHECK(ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "mlKemKey!=NULL (guard false, ML-KEM not compiled)"); +#endif + /* --- kid_type operands ------------------------------------------------ * * A key pointer must be non-NULL for the 3rd term to be evaluated at all; * the opaque pointer is safe for the same reason as above whenever its @@ -1754,12 +1762,12 @@ static void wb_set_keyid_from_pubkey_operands(void) RsaKey* kp = haveKey ? &k : NULL; #define WB_SKID_CALL(kt) \ SetKeyIdFromPublicKey(&cert, kp, NULL, NULL, NULL, NULL, NULL, NULL, \ - NULL, (kt)) + NULL, NULL, (kt)) #else int haveKey = 1; #define WB_SKID_CALL(kt) \ SetKeyIdFromPublicKey(&cert, NULL, NULL, NULL, NULL, NULL, NULL, \ - NULL, (void*)opaque, (kt)) + NULL, (void*)opaque, NULL, (kt)) #endif if (haveKey) { /* kid_type == AKID_TYPE -> 2nd operand of the kid_type AND is diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 253f94da20c..53364265e3a 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -325,6 +325,9 @@ ASN Options: #if defined(WOLFSSL_HAVE_FRODOKEM) #include #endif +#if defined(WOLFSSL_HAVE_MLKEM) + #include +#endif #ifdef WOLFSSL_QNX_CAAM #include @@ -4652,14 +4655,15 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, DsaKey* dsaKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, - LmsKey* lmsKey, XmssKey* xmssKey, void* frodoKey); + LmsKey* lmsKey, XmssKey* xmssKey, void* frodoKey, + void* mlKemKey); #ifdef WOLFSSL_CERT_REQ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, DsaKey* dsaKey, ecc_key* eccKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, LmsKey* lmsKey, XmssKey* xmssKey, - void* frodoKey); + void* frodoKey, void* mlKemKey); #endif #endif #endif @@ -4964,6 +4968,20 @@ static int ParseCRL_Extensions(DecodedCRL* dcrl, const byte* buf, word32* inOutI static const byte keyMlDsa_87Oid[] = {96, 134, 72, 1, 101, 3, 4, 3, 19}; #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + /* ML-KEM key OIDs (FIPS 203), NIST arc 2.16.840.1.101.3.4.4.x */ + /* ML-KEM-512: 2.16.840.1.101.3.4.4.1 */ + static const byte keyMlKem_512Oid[] = + {96, 134, 72, 1, 101, 3, 4, 4, 1}; + + /* ML-KEM-768: 2.16.840.1.101.3.4.4.2 */ + static const byte keyMlKem_768Oid[] = + {96, 134, 72, 1, 101, 3, 4, 4, 2}; + + /* ML-KEM-1024: 2.16.840.1.101.3.4.4.3 */ + static const byte keyMlKem_1024Oid[] = + {96, 134, 72, 1, 101, 3, 4, 4, 3}; +#endif /* WOLFSSL_HAVE_MLKEM && !WOLFSSL_MLKEM_NO_ASN1 */ #ifdef WOLFSSL_HAVE_FRODOKEM /* FrodoKEM / eFrodoKEM key OIDs (ISO/IEC 18033-2, arc 1.0.18033.2.2.7.x). * Only the 976 and 1344 parameter sets are standardised (no 640). */ @@ -6278,6 +6296,20 @@ const byte* OidFromId(word32 id, word32 type, word32* oidSz) *oidSz = sizeof(keyMlDsa_87Oid); break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + case ML_KEM_512k: + oid = keyMlKem_512Oid; + *oidSz = sizeof(keyMlKem_512Oid); + break; + case ML_KEM_768k: + oid = keyMlKem_768Oid; + *oidSz = sizeof(keyMlKem_768Oid); + break; + case ML_KEM_1024k: + oid = keyMlKem_1024Oid; + *oidSz = sizeof(keyMlKem_1024Oid); + break; + #endif /* WOLFSSL_HAVE_MLKEM && !WOLFSSL_MLKEM_NO_ASN1 */ #ifdef WOLFSSL_HAVE_SLHDSA case SLH_DSA_SHA2_128Sk: oid = keySlhDsa_Sha2_128sOid; @@ -9389,6 +9421,17 @@ int ToTraditionalInline_ex2(const byte* input, word32* inOutIdx, word32 sz, ret = ASN_PARSE_E; } break; + #endif + #if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + case ML_KEM_512k: + case ML_KEM_768k: + case ML_KEM_1024k: + /* Neither NULL item nor OBJECT_ID item allowed. */ + if ((dataASN[PKCS8KEYASN_IDX_PKEY_ALGO_NULL].tag != 0) || + (dataASN[PKCS8KEYASN_IDX_PKEY_ALGO_OID_CURVE].tag != 0)) { + ret = ASN_PARSE_E; + } + break; #endif /* Other OIDs (DSAk), no parameter validation. */ default: @@ -10006,6 +10049,64 @@ int wc_CheckPrivateKey(const byte* privKey, word32 privKeySz, } else #endif /* WOLFSSL_HAVE_FRODOKEM && !WOLFSSL_FRODOKEM_NO_ASN1 */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + if ((ks == ML_KEM_512k) || (ks == ML_KEM_768k) || (ks == ML_KEM_1024k)) { + /* MlKemKey is large; heap-allocate it to keep the stack frame small. */ + MlKemKey* key_pair = (MlKemKey*)XMALLOC(sizeof(MlKemKey), heap, + DYNAMIC_TYPE_TMP_BUFFER); + word32 keyIdx = 0; + word32 pubSz = 0; + byte* pub = NULL; + int level = 0; + int inited = 0; + + if (key_pair == NULL) { + return MEMORY_E; + } + XMEMSET(key_pair, 0, sizeof(MlKemKey)); + + /* Pin the key to the certificate's parameter set, so a private key + * naming a different one is a mismatch rather than adopted. */ + ret = mlkem_type_from_oid_sum(ks, &level); + if (ret == 0) { + ret = wc_MlKemKey_Init(key_pair, level, heap, INVALID_DEVID); + if (ret == 0) { + inited = 1; + } + } + if (ret == 0) { + ret = wc_MlKemKey_PrivateKeyDecode(key_pair, privKey, privKeySz, + &keyIdx); + } + if (ret == 0) { + ret = wc_MlKemKey_PublicKeySize(key_pair, &pubSz); + } + if (ret == 0) { + pub = (byte*)XMALLOC(pubSz, heap, DYNAMIC_TYPE_TMP_BUFFER); + if (pub == NULL) { + ret = MEMORY_E; + } + } + if (ret == 0) { + ret = wc_MlKemKey_EncodePublicKey(key_pair, pub, pubSz); + } + if (ret == 0) { + WOLFSSL_MSG("Checking ML-KEM key pair"); + ret = ((pubKeySz == pubSz) && + (XMEMCMP(pub, pubKey, pubSz) == 0)) ? 1 : 0; + } + XFREE(pub, heap, DYNAMIC_TYPE_TMP_BUFFER); + /* Only free what was set up. wc_MlKemKey_Init validates the + * parameter set before assigning anything, so a rejected level leaves + * an all-zero struct, and freeing that would hand a zeroed key to a + * crypto callback registered at device id 0. */ + if (inited) { + wc_MlKemKey_Free(key_pair); + } + XFREE(key_pair, heap, DYNAMIC_TYPE_TMP_BUFFER); + } + else +#endif /* WOLFSSL_HAVE_MLKEM && !WOLFSSL_MLKEM_NO_ASN1 */ { ret = 0; } @@ -13119,7 +13220,8 @@ void wc_FreeDecodedCert(DecodedCert* cert) #if defined(HAVE_ED25519) || defined(HAVE_ED448) || defined(HAVE_FALCON) || \ defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA) || \ defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) || \ - (defined(WOLFSSL_HAVE_FRODOKEM) && !defined(WOLFSSL_FRODOKEM_NO_ASN1)) + (defined(WOLFSSL_HAVE_FRODOKEM) && !defined(WOLFSSL_FRODOKEM_NO_ASN1)) || \ + (defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1)) /* Store the key data under the BIT_STRING in dynamically allocated data. * * @param [in, out] cert Certificate object. @@ -13526,8 +13628,10 @@ int SetAsymKeyDerPublic(const byte* pubKey, word32 pubKeyLen, sz = pubKeyLen; } - if ((ret == 0) && (output != NULL)) { - /* Put public key into space provided. */ + if ((ret == 0) && (output != NULL) && (output != pubKey)) { + /* Put public key into space provided. A caller that encoded the key + * straight into its place in the output passes the two equal, and has + * nothing to copy. */ XMEMCPY(output, pubKey, pubKeyLen); } if (ret == 0) { @@ -14139,6 +14243,14 @@ static int GetCertKey(DecodedCert* cert, const byte* source, word32* inOutIdx, ret = StoreKey(cert, source, &srcIdx, maxIdx); break; #endif /* WOLFSSL_HAVE_FRODOKEM && !WOLFSSL_FRODOKEM_NO_ASN1 */ + #if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + case ML_KEM_512k: + case ML_KEM_768k: + case ML_KEM_1024k: + cert->pkCurveOID = cert->keyOID; + ret = StoreKey(cert, source, &srcIdx, maxIdx); + break; + #endif /* WOLFSSL_HAVE_MLKEM && !WOLFSSL_MLKEM_NO_ASN1 */ #ifdef WOLFSSL_HAVE_SLHDSA case SLH_DSA_SHAKE_128Fk: case SLH_DSA_SHAKE_192Fk: @@ -29358,7 +29470,7 @@ static int EncodePublicKey(int keyType, byte* output, int outLen, DsaKey* dsaKey, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, LmsKey* lmsKey, XmssKey* xmssKey, - void* frodoKey) + void* frodoKey, void* mlKemKey) { int ret = 0; @@ -29374,6 +29486,7 @@ static int EncodePublicKey(int keyType, byte* output, int outLen, (void)lmsKey; (void)xmssKey; (void)frodoKey; + (void)mlKemKey; switch (keyType) { #ifndef NO_RSA @@ -29444,6 +29557,15 @@ static int EncodePublicKey(int keyType, byte* output, int outLen, } break; #endif /* WOLFSSL_HAVE_FRODOKEM && !WOLFSSL_FRODOKEM_NO_ASN1 */ + #if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + case MLKEM_KEY: + ret = wc_MlKemKey_PublicKeyToDer((MlKemKey*)mlKemKey, output, + (word32)outLen, 1); + if (ret <= 0) { + ret = PUBLIC_KEY_E; + } + break; + #endif /* WOLFSSL_HAVE_MLKEM && !WOLFSSL_MLKEM_NO_ASN1 */ #if defined(WOLFSSL_HAVE_SLHDSA) case SLH_DSA_SHAKE_128F_KEY: case SLH_DSA_SHAKE_192F_KEY: @@ -30861,7 +30983,8 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, DsaKey* dsaKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, - LmsKey* lmsKey, XmssKey* xmssKey, void* frodoKey) + LmsKey* lmsKey, XmssKey* xmssKey, void* frodoKey, + void* mlKemKey) { /* TODO: issRaw and sbjRaw should be NUL terminated. */ DECL_ASNSETDATA(dataASN, x509CertASN_Length); @@ -30964,6 +31087,11 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, cert->keyType = FRODOKEM_KEY; } #endif /* WOLFSSL_HAVE_FRODOKEM && !WOLFSSL_FRODOKEM_NO_ASN1 */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + else if (mlKemKey != NULL) { + cert->keyType = MLKEM_KEY; + } +#endif /* WOLFSSL_HAVE_MLKEM && !WOLFSSL_MLKEM_NO_ASN1 */ else { ret = BAD_FUNC_ARG; } @@ -31012,7 +31140,7 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, /* Calculate public key encoding size. */ ret = EncodePublicKey(cert->keyType, NULL, 0, rsaKey, eccKey, ed25519Key, ed448Key, dsaKey, falconKey, - mldsaKey, slhDsaKey, lmsKey, xmssKey, frodoKey); + mldsaKey, slhDsaKey, lmsKey, xmssKey, frodoKey, mlKemKey); publicKeySz = (word32)ret; } if (ret >= 0) { @@ -31209,7 +31337,7 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, .data.buffer.length, rsaKey, eccKey, ed25519Key, ed448Key, dsaKey, falconKey, mldsaKey, slhDsaKey, lmsKey, xmssKey, - frodoKey); + frodoKey, mlKemKey); } if ((ret >= 0) && (!dataASN[X509CERTASN_IDX_TBS_EXT_SEQ].noOut)) { /* Encode extensions into buffer. */ @@ -31257,6 +31385,7 @@ int wc_MakeCert_ex(Cert* cert, byte* derBuffer, word32 derSz, int keyType, LmsKey* lmsKey = NULL; XmssKey* xmssKey = NULL; void* frodoKey = NULL; + void* mlKemKey = NULL; if (keyType == RSA_TYPE) rsaKey = (RsaKey*)key; @@ -31312,10 +31441,14 @@ int wc_MakeCert_ex(Cert* cert, byte* derBuffer, word32 derSz, int keyType, else if (keyType == FRODOKEM_TYPE) frodoKey = key; #endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + else if (keyType == MLKEM_TYPE) + mlKemKey = key; +#endif return MakeAnyCert(cert, derBuffer, derSz, rsaKey, eccKey, rng, dsaKey, ed25519Key, ed448Key, falconKey, mldsaKey, - slhDsaKey, lmsKey, xmssKey, frodoKey); + slhDsaKey, lmsKey, xmssKey, frodoKey, mlKemKey); } /* Make an x509 Certificate v3 RSA or ECC from cert input, write to buffer */ @@ -31324,7 +31457,7 @@ int wc_MakeCert(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng) { return MakeAnyCert(cert, derBuffer, derSz, rsaKey, eccKey, rng, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } @@ -31393,7 +31526,7 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, LmsKey* lmsKey, XmssKey* xmssKey, - void* frodoKey) + void* frodoKey, void* mlKemKey) { DECL_ASNSETDATA(dataASN, certReqBodyASN_Length); word32 publicKeySz = 0; @@ -31493,6 +31626,11 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, cert->keyType = FRODOKEM_KEY; } #endif /* WOLFSSL_HAVE_FRODOKEM && !WOLFSSL_FRODOKEM_NO_ASN1 */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + else if (mlKemKey != NULL) { + cert->keyType = MLKEM_KEY; + } +#endif /* WOLFSSL_HAVE_MLKEM && !WOLFSSL_MLKEM_NO_ASN1 */ else { ret = BAD_FUNC_ARG; } @@ -31515,7 +31653,7 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, /* Determine encode public key size. */ ret = EncodePublicKey(cert->keyType, NULL, 0, rsaKey, eccKey, ed25519Key, ed448Key, dsaKey, falconKey, - mldsaKey, slhDsaKey, lmsKey, xmssKey, frodoKey); + mldsaKey, slhDsaKey, lmsKey, xmssKey, frodoKey, mlKemKey); publicKeySz = (word32)ret; } if (ret >= 0) { @@ -31635,7 +31773,7 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, dataASN[CERTREQBODYASN_IDX_SPUBKEYINFO_SEQ].data.buffer.data, (int)dataASN[CERTREQBODYASN_IDX_SPUBKEYINFO_SEQ].data.buffer.length, rsaKey, eccKey, ed25519Key, ed448Key, dsaKey, falconKey, - mldsaKey, slhDsaKey, lmsKey, xmssKey, frodoKey); + mldsaKey, slhDsaKey, lmsKey, xmssKey, frodoKey, mlKemKey); } if ((ret >= 0 && derBuffer != NULL) && (!dataASN[CERTREQBODYASN_IDX_EXT_BODY].noOut)) { @@ -31672,6 +31810,7 @@ int wc_MakeCertReq_ex(Cert* cert, byte* derBuffer, word32 derSz, int keyType, LmsKey* lmsKey = NULL; XmssKey* xmssKey = NULL; void* frodoKey = NULL; + void* mlKemKey = NULL; if (keyType == RSA_TYPE) rsaKey = (RsaKey*)key; @@ -31727,10 +31866,14 @@ int wc_MakeCertReq_ex(Cert* cert, byte* derBuffer, word32 derSz, int keyType, else if (keyType == FRODOKEM_TYPE) frodoKey = key; #endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + else if (keyType == MLKEM_TYPE) + mlKemKey = key; +#endif return MakeCertReq(cert, derBuffer, derSz, rsaKey, dsaKey, eccKey, ed25519Key, ed448Key, falconKey, mldsaKey, - slhDsaKey, lmsKey, xmssKey, frodoKey); + slhDsaKey, lmsKey, xmssKey, frodoKey, mlKemKey); } WOLFSSL_ABI @@ -31738,7 +31881,7 @@ int wc_MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, ecc_key* eccKey) { return MakeCertReq(cert, derBuffer, derSz, rsaKey, NULL, eccKey, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } #endif /* WOLFSSL_CERT_REQ */ @@ -32446,6 +32589,7 @@ static int SetKeyIdFromPublicKey(Cert *cert, RsaKey *rsakey, ecc_key *eckey, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey *slhDsaKey, void* frodoKey, + void* mlKemKey, int kid_type) { byte *buf; @@ -32455,7 +32599,7 @@ static int SetKeyIdFromPublicKey(Cert *cert, RsaKey *rsakey, ecc_key *eckey, if (cert == NULL || (rsakey == NULL && eckey == NULL && ed25519Key == NULL && ed448Key == NULL && falconKey == NULL && mldsaKey == NULL && - slhDsaKey == NULL && frodoKey == NULL) || + slhDsaKey == NULL && frodoKey == NULL && mlKemKey == NULL) || (kid_type != SKID_TYPE && kid_type != AKID_TYPE)) return BAD_FUNC_ARG; @@ -32464,6 +32608,12 @@ static int SetKeyIdFromPublicKey(Cert *cert, RsaKey *rsakey, ecc_key *eckey, if (frodoKey != NULL) { bufSz = FRODOKEM_MAX_PUB_KEY_DER_SIZE; } +#endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + /* An ML-KEM encapsulation key also exceeds MAX_PUBLIC_KEY_SZ. */ + if (mlKemKey != NULL) { + bufSz = MLKEM_MAX_PUB_KEY_DER_SIZE; + } #endif buf = (byte *)XMALLOC(bufSz, cert->heap, DYNAMIC_TYPE_TMP_BUFFER); if (buf == NULL) @@ -32517,6 +32667,12 @@ static int SetKeyIdFromPublicKey(Cert *cert, RsaKey *rsakey, ecc_key *eckey, bufSz, 0); } #endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + if (mlKemKey != NULL) { + bufferSz = wc_MlKemKey_PublicKeyToDer((MlKemKey*)mlKemKey, buf, + bufSz, 0); + } +#endif if (bufferSz <= 0) { XFREE(buf, cert->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -32557,6 +32713,7 @@ int wc_SetSubjectKeyIdFromPublicKey_ex(Cert *cert, int keyType, void* key) wc_MlDsaKey* mldsaKey = NULL; SlhDsaKey* slhDsaKey = NULL; void* frodoKey = NULL; + void* mlKemKey = NULL; if (keyType == RSA_TYPE) rsaKey = (RsaKey*)key; @@ -32592,9 +32749,14 @@ int wc_SetSubjectKeyIdFromPublicKey_ex(Cert *cert, int keyType, void* key) else if (keyType == FRODOKEM_TYPE) frodoKey = key; #endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + else if (keyType == MLKEM_TYPE) + mlKemKey = key; +#endif return SetKeyIdFromPublicKey(cert, rsaKey, eccKey, ed25519Key, ed448Key, falconKey, mldsaKey, slhDsaKey, frodoKey, + mlKemKey, SKID_TYPE); } @@ -32602,7 +32764,7 @@ int wc_SetSubjectKeyIdFromPublicKey_ex(Cert *cert, int keyType, void* key) int wc_SetSubjectKeyIdFromPublicKey(Cert *cert, RsaKey *rsakey, ecc_key *eckey) { return SetKeyIdFromPublicKey(cert, rsakey, eckey, NULL, NULL, NULL, NULL, - NULL, NULL, SKID_TYPE); + NULL, NULL, NULL, SKID_TYPE); } int wc_SetAuthKeyIdFromPublicKey_ex(Cert *cert, int keyType, void* key) @@ -32614,6 +32776,7 @@ int wc_SetAuthKeyIdFromPublicKey_ex(Cert *cert, int keyType, void* key) falcon_key* falconKey = NULL; wc_MlDsaKey* mldsaKey = NULL; SlhDsaKey* slhDsaKey = NULL; + void* mlKemKey = NULL; if (keyType == RSA_TYPE) rsaKey = (RsaKey*)key; @@ -32645,17 +32808,21 @@ int wc_SetAuthKeyIdFromPublicKey_ex(Cert *cert, int keyType, void* key) else if (IsSlhDsaKeyType(keyType)) slhDsaKey = (SlhDsaKey*)key; #endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) + else if (keyType == MLKEM_TYPE) + mlKemKey = key; +#endif return SetKeyIdFromPublicKey(cert, rsaKey, eccKey, ed25519Key, ed448Key, falconKey, mldsaKey, slhDsaKey, NULL, - AKID_TYPE); + mlKemKey, AKID_TYPE); } /* Set SKID from RSA or ECC public key */ int wc_SetAuthKeyIdFromPublicKey(Cert *cert, RsaKey *rsakey, ecc_key *eckey) { return SetKeyIdFromPublicKey(cert, rsakey, eckey, NULL, NULL, NULL, NULL, - NULL, NULL, AKID_TYPE); + NULL, NULL, NULL, AKID_TYPE); } @@ -34678,7 +34845,8 @@ enum { || (defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT)) \ || (defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_IMPORT)) \ || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) \ - || defined(WOLFSSL_HAVE_SLHDSA) || defined(WOLFSSL_HAVE_FRODOKEM)) + || defined(WOLFSSL_HAVE_SLHDSA) || defined(WOLFSSL_HAVE_FRODOKEM) \ + || (defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1))) int DecodeAsymKey_Assign(const byte* input, word32* inOutIdx, word32 inSz, @@ -35319,12 +35487,18 @@ int SetAsymKeyDer(const byte* privKey, word32 privKeyLen, /* Encode private key. */ SetASN_Items(privateKeyASN, dataASN, privateKeyASN_Length, output); - /* Put private value into space provided. */ + /* Put private value into space provided. A caller that encoded the + * key straight into its place in the output passes the two equal, and + * has nothing to copy. */ /* safe cast -- the pointer is actually inside output buffer. */ - XMEMCPY( - (byte*)(wc_ptr_t) - dataASN[PRIVKEYASN_IDX_PKEY_CURVEPKEY].data.buffer.data, - privKey, privKeyLen); + if ((const byte*)(wc_ptr_t) + dataASN[PRIVKEYASN_IDX_PKEY_CURVEPKEY].data.buffer.data + != privKey) { + XMEMCPY( + (byte*)(wc_ptr_t) + dataASN[PRIVKEYASN_IDX_PKEY_CURVEPKEY].data.buffer.data, + privKey, privKeyLen); + } if (pubKey != NULL) { /* Put public value into space provided. */ diff --git a/wolfcrypt/src/asn_orig.c b/wolfcrypt/src/asn_orig.c index a77382f4bb0..d27e2242da1 100644 --- a/wolfcrypt/src/asn_orig.c +++ b/wolfcrypt/src/asn_orig.c @@ -6606,7 +6606,8 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, DsaKey* dsaKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, - LmsKey* lmsKey, XmssKey* xmssKey, void* frodoKey) + LmsKey* lmsKey, XmssKey* xmssKey, void* frodoKey, + void* mlKemKey) { int ret; WC_DECLARE_VAR(der, DerCert, 1, 0); @@ -6626,6 +6627,11 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, "WOLFSSL_ASN_TEMPLATE"); return ALGO_ID_E; } + if (mlKemKey != NULL) { + WOLFSSL_MSG("ML-KEM certificate generation requires " + "WOLFSSL_ASN_TEMPLATE"); + return ALGO_ID_E; + } if (derBuffer == NULL) return BAD_FUNC_ARG; @@ -7256,7 +7262,7 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, LmsKey* lmsKey, XmssKey* xmssKey, - void* frodoKey) + void* frodoKey, void* mlKemKey) { int ret; WC_DECLARE_VAR(der, DerCert, 1, 0); @@ -7273,6 +7279,11 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, "WOLFSSL_ASN_TEMPLATE"); return ALGO_ID_E; } + if (mlKemKey != NULL) { + WOLFSSL_MSG("ML-KEM certificate request generation requires " + "WOLFSSL_ASN_TEMPLATE"); + return ALGO_ID_E; + } if (eccKey) cert->keyType = ECC_KEY; diff --git a/wolfcrypt/src/wc_mlkem.c b/wolfcrypt/src/wc_mlkem.c index 6ca0d7e5465..927e50c2946 100644 --- a/wolfcrypt/src/wc_mlkem.c +++ b/wolfcrypt/src/wc_mlkem.c @@ -87,6 +87,9 @@ #ifdef WOLF_CRYPTO_CB #include #endif +#ifndef WOLFSSL_MLKEM_NO_ASN1 + #include +#endif #ifdef NO_INLINE #include @@ -436,6 +439,9 @@ int wc_MlKemKey_Init(MlKemKey* key, int type, void* heap, int devId) #endif break; #endif + case WC_ML_KEM_TYPE_UNSET: + /* Caller will let a DER decode name the parameter set. */ + break; default: /* No other values supported. */ ret = BAD_FUNC_ARG; @@ -455,7 +461,9 @@ int wc_MlKemKey_Init(MlKemKey* key, int type, void* heap, int devId) key->idLen = 0; key->labelLen = 0; #endif - key->flags = 0; + /* A zeroed object reads as WC_ML_KEM_512, so record the difference + * here rather than inferring it from the type. */ + key->flags = (type == WC_ML_KEM_TYPE_UNSET) ? 0 : MLKEM_FLAG_TYPE_SET; #ifdef WOLFSSL_MLKEM_DYNAMIC_KEYS key->priv = NULL; @@ -815,7 +823,8 @@ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand, } if (ret == 0) { - key->flags = 0; + /* Discards the key material, not the parameter set. */ + key->flags &= MLKEM_FLAG_TYPE_SET; /* Establish parameters based on key type. */ k = mlkemkey_get_k(key); @@ -2849,4 +2858,432 @@ int wc_MlKemKey_EncodePublicKey(MlKemKey* key, unsigned char* out, word32 len) return ret; } +#ifndef WOLFSSL_MLKEM_NO_ASN1 +#if defined(WC_ENABLE_ASYM_KEY_EXPORT) || defined(WC_ENABLE_ASYM_KEY_IMPORT) + +/* Map an ML-KEM key type to its FIPS 203 key-OID sum. + * + * @param [in] type ML-KEM key type (WC_ML_KEM_512/768/1024). + * @param [out] oidSum OID sum (ML_KEM_512k/768k/1024k). + * @return 0 on success. + * @return BAD_FUNC_ARG when the type is not an ML-KEM parameter set. + */ +static int mlkem_type_to_oid_sum(int type, int* oidSum) +{ + int ret = 0; + + switch (type) { + case WC_ML_KEM_512: + *oidSum = ML_KEM_512k; + break; + case WC_ML_KEM_768: + *oidSum = ML_KEM_768k; + break; + case WC_ML_KEM_1024: + *oidSum = ML_KEM_1024k; + break; + default: + /* Kyber round-3 types have no standardised OID. */ + ret = BAD_FUNC_ARG; + break; + } + + return ret; +} + +#endif /* WC_ENABLE_ASYM_KEY_EXPORT || WC_ENABLE_ASYM_KEY_IMPORT */ + +/* Map an ML-KEM key-OID sum to its parameter set. + * + * The inverse of mlkem_type_to_oid_sum, for callers that already hold the OID + * and want to pin the key to it rather than let a decode adopt one. + * + * @param [in] oidSum OID sum (ML_KEM_512k/768k/1024k). + * @param [out] type ML-KEM key type (WC_ML_KEM_512/768/1024). + * @return 0 on success. + * @return BAD_FUNC_ARG when type is NULL or the OID is not an ML-KEM key OID. + */ +int mlkem_type_from_oid_sum(int oidSum, int* type) +{ + int ret = 0; + + if (type == NULL) + return BAD_FUNC_ARG; + + switch (oidSum) { + case ML_KEM_512k: + *type = WC_ML_KEM_512; + break; + case ML_KEM_768k: + *type = WC_ML_KEM_768; + break; + case ML_KEM_1024k: + *type = WC_ML_KEM_1024; + break; + default: + ret = BAD_FUNC_ARG; + break; + } + + return ret; +} + +#ifdef WC_ENABLE_ASYM_KEY_EXPORT +/* Encode the ML-KEM public key as a DER SubjectPublicKeyInfo. + * + * Pass NULL for output to get the size of the encoding. + * + * @param [in] key ML-KEM key object with public key set. + * @param [out] output Buffer for the DER, or NULL to get the length. + * @param [in] len Size of output buffer in bytes. + * @param [in] withAlg Include the SubjectPublicKeyInfo wrapper (1) or emit + * only the raw public key bytes (0). + * @return Length of the encoding in bytes on success. + * @return BAD_FUNC_ARG when key is NULL or the type has no OID. + * @return MEMORY_E when dynamic memory allocation fails. + */ +int wc_MlKemKey_PublicKeyToDer(MlKemKey* key, byte* output, word32 len, + int withAlg) +{ + int ret = 0; + int oidSum = 0; + word32 pubLen = 0; + int sz = 0; + + if (key == NULL) + return BAD_FUNC_ARG; + + if (ret == 0) { + ret = mlkem_type_to_oid_sum(key->type, &oidSum); + } + if (ret == 0) { + ret = wc_MlKemKey_PublicKeySize(key, &pubLen); + } + if (ret == 0) { + /* Length of the encoding. pubKey is only read when output is given, + * so any non-NULL pointer satisfies the argument check here. */ + sz = SetAsymKeyDerPublic((const byte*)key, pubLen, NULL, 0, oidSum, + withAlg); + if (sz < 0) + ret = sz; + } + if ((ret == 0) && (output != NULL)) { + if (len < (word32)sz) { + ret = BUFFER_E; + } + else { + /* The raw key is the last field of the encoding, so write it + * where it will live and let the header be built around it. That + * is what removes the temporary, and with it the need for a heap + * in a WOLFSSL_NO_MALLOC build. */ + ret = wc_MlKemKey_EncodePublicKey(key, + output + ((word32)sz - pubLen), pubLen); + } + } + if ((ret == 0) && (output != NULL)) { + ret = SetAsymKeyDerPublic(output + ((word32)sz - pubLen), pubLen, + output, len, oidSum, withAlg); + } + else if (ret == 0) { + ret = sz; + } + + return ret; +} + +/* Encode the ML-KEM private key as a DER PKCS#8 OneAsymmetricKey. + * + * The expanded decapsulation key is emitted. The seed and seed-with-expanded + * alternatives of the ML-KEM private key are not produced. + * + * Pass NULL for output to get the size of the encoding. + * + * @param [in] key ML-KEM key object with private key set. + * @param [out] output Buffer for the DER, or NULL to get the length. + * @param [in] len Size of output buffer in bytes. + * @return Length of the encoding in bytes on success. + * @return BAD_FUNC_ARG when key is NULL or the type has no OID. + * @return MEMORY_E when dynamic memory allocation fails. + */ +int wc_MlKemKey_PrivateKeyToDer(MlKemKey* key, byte* output, word32 len) +{ + int ret = 0; + int oidSum = 0; + word32 privLen = 0; + int sz = 0; + + if (key == NULL) + return BAD_FUNC_ARG; + + if (ret == 0) { + ret = mlkem_type_to_oid_sum(key->type, &oidSum); + } + if (ret == 0) { + ret = wc_MlKemKey_PrivateKeySize(key, &privLen); + } + if (ret == 0) { + /* Length of the encoding. privKey is only read when output is given, + * so any non-NULL pointer satisfies the argument check here. The + * ML-KEM private key embeds the public key, so none is appended. */ + sz = SetAsymKeyDer((const byte*)key, privLen, NULL, 0, NULL, 0, + oidSum); + if (sz < 0) + ret = sz; + } + if ((ret == 0) && (output != NULL)) { + if (len < (word32)sz) { + ret = BUFFER_E; + } + else { + /* Written where it will live, so the header is built around it + * and no temporary is needed. See wc_MlKemKey_PublicKeyToDer. */ + ret = wc_MlKemKey_EncodePrivateKey(key, + output + ((word32)sz - privLen), privLen); + } + } + if ((ret == 0) && (output != NULL)) { + ret = SetAsymKeyDer(output + ((word32)sz - privLen), privLen, NULL, 0, + output, len, oidSum); + } + else if (ret == 0) { + ret = sz; + } + + return ret; +} +#endif /* WC_ENABLE_ASYM_KEY_EXPORT */ + +#ifdef WC_ENABLE_ASYM_KEY_IMPORT +/* Take the parameter set from a DER algorithm OID sum. + * + * @param [in, out] key ML-KEM key object with no parameter set yet. + * @param [in] oidSum Key OID sum read from the DER. + * @return 0 on success. + * @return BAD_FUNC_ARG when the OID is not an ML-KEM key OID. + * @return NOT_COMPILED_IN when the parameter set is not in this build. + */ +static int mlkem_key_adopt_type(MlKemKey* key, int oidSum) +{ + int type = 0; + int ret = mlkem_type_from_oid_sum(oidSum, &type); + + if (ret == 0) { + switch (type) { +#ifndef WOLFSSL_NO_ML_KEM + #ifdef WOLFSSL_WC_ML_KEM_512 + case WC_ML_KEM_512: + break; + #endif + #ifdef WOLFSSL_WC_ML_KEM_768 + case WC_ML_KEM_768: + break; + #endif + #ifdef WOLFSSL_WC_ML_KEM_1024 + case WC_ML_KEM_1024: + break; + #endif +#endif + default: + ret = NOT_COMPILED_IN; + break; + } + } + if (ret == 0) { + key->type = type; + key->flags |= MLKEM_FLAG_TYPE_SET; + } + + return ret; +} + +/* Decode a DER SubjectPublicKeyInfo into an ML-KEM public key. + * + * Takes wrapped DER, unlike wc_MlKemKey_DecodePublicKey which takes the raw + * encoded key. A key initialized for a parameter set holds the DER to it; one + * initialized WC_ML_KEM_TYPE_UNSET takes it from the algorithm OID. + * + * @param [in, out] key ML-KEM key object. + * @param [in] input DER buffer. + * @param [in] inSz Size of DER buffer in bytes. + * @param [in, out] inOutIdx On in, index into buffer; on out, index after. + * @return 0 on success. + * @return BAD_FUNC_ARG when a pointer is NULL. + * @return ASN_PARSE_E when the DER is invalid or names another parameter set. + */ +int wc_MlKemKey_PublicKeyDecode(MlKemKey* key, const byte* input, word32 inSz, + word32* inOutIdx) +{ + int ret = 0; + int keyType = ANONk; + const byte* pubKey = NULL; + word32 pubKeyLen = 0; + + if ((key == NULL) || (input == NULL) || (inOutIdx == NULL)) { + ret = BAD_FUNC_ARG; + } + if ((ret == 0) && ((key->flags & MLKEM_FLAG_TYPE_SET) != 0)) { + /* A parameter set was named at init, so hold the DER to it. */ + ret = mlkem_type_to_oid_sum(key->type, &keyType); + } + if (ret == 0) { + /* keyType is ANONk when none was named, which auto-detects. */ + ret = DecodeAsymKeyPublic_Assign(input, inOutIdx, inSz, &pubKey, + &pubKeyLen, &keyType); + } + if ((ret == 0) && ((key->flags & MLKEM_FLAG_TYPE_SET) == 0)) { + ret = mlkem_key_adopt_type(key, keyType); + } + if (ret == 0) { + ret = wc_MlKemKey_DecodePublicKey(key, pubKey, pubKeyLen); + } + + return ret; +} + +/* Decode a DER PKCS#8 OneAsymmetricKey into an ML-KEM private key. + * + * All three RFC 9935 Section 6 CHOICE shapes are accepted: the 64 byte seed + * under an implicit [0], the expanded decapsulation key as an OCTET STRING, + * and the SEQUENCE carrying both. A seed is expanded with + * ML-KEM.KeyGen_internal(d,z); when both forms are present the expanded key + * is regenerated from the seed and the two are compared, per RFC 9935 + * Section 8. The parameter set must match the initialized key object, as + * described for wc_MlKemKey_PublicKeyDecode. + * + * A WOLFSSL_MLKEM_NO_MAKE_KEY build cannot expand a seed, so it cannot run + * the Section 8 check either and rejects any key carrying one, including the + * "both" shape. + * + * @param [in, out] key ML-KEM key object. + * @param [in] input DER buffer. + * @param [in] inSz Size of DER buffer in bytes. + * @param [in, out] inOutIdx On in, index into buffer; on out, index after. + * @return 0 on success. + * @return BAD_FUNC_ARG when a pointer is NULL. + * @return ASN_PARSE_E when the DER is invalid, names another parameter set, + * carries a seed that is not 64 bytes, or carries a seed and an + * expanded key that disagree. + * @return MEMORY_E when dynamic memory allocation fails. + * @return NOT_COMPILED_IN when a key carrying a seed is decoded in a + * WOLFSSL_MLKEM_NO_MAKE_KEY build. + */ +int wc_MlKemKey_PrivateKeyDecode(MlKemKey* key, const byte* input, word32 inSz, + word32* inOutIdx) +{ + int ret = 0; + int keyType = ANONk; + const byte* seed = NULL; + word32 seedLen = 0; + const byte* privKey = NULL; + word32 privKeyLen = 0; + const byte* pubKey = NULL; + word32 pubKeyLen = 0; +#ifndef WOLFSSL_MLKEM_NO_MAKE_KEY + int keyExpanded = 0; +#endif + + if ((key == NULL) || (input == NULL) || (inOutIdx == NULL)) { + ret = BAD_FUNC_ARG; + } + if ((ret == 0) && ((key->flags & MLKEM_FLAG_TYPE_SET) != 0)) { + /* A parameter set was named at init, so hold the DER to it. */ + ret = mlkem_type_to_oid_sum(key->type, &keyType); + } + if (ret == 0) { + /* RFC 9935 Section 6 gives privateKey three CHOICE shapes: a 64 byte + * seed under an implicit [0], the expanded key as an OCTET STRING, or + * a SEQUENCE carrying both. The template decoder reports which. */ + ret = DecodeAsymKey_Assign(input, inOutIdx, inSz, &seed, &seedLen, + &privKey, &privKeyLen, &pubKey, &pubKeyLen, &keyType); + } + if ((ret == 0) && ((key->flags & MLKEM_FLAG_TYPE_SET) == 0)) { + ret = mlkem_key_adopt_type(key, keyType); + } +#ifdef WOLFSSL_MLKEM_NO_MAKE_KEY + /* Expanding a seed needs key generation, which this build lacks. That + * rules out the "both" shape too: without the RFC 9935 Section 8 + * comparison, trusting the expanded half would accept a tampered file. */ + if ((ret == 0) && (seed != NULL)) { + WOLFSSL_MSG("ML-KEM seed needs key generation, which is not built"); + ret = NOT_COMPILED_IN; + } + if (ret == 0) { + ret = wc_MlKemKey_DecodePrivateKey(key, privKey, privKeyLen); + } +#else + if ((ret == 0) && (seed != NULL)) { + if (seedLen != WC_ML_KEM_MAKEKEY_RAND_SZ) { + ret = ASN_PARSE_E; + } + else { + /* Expand with ML-KEM.KeyGen_internal(d,z), FIPS 203 algorithm 16, + * taking the first 32 octets as d and the rest as z. */ + ret = wc_MlKemKey_MakeKeyWithRandom(key, seed, (int)seedLen); + if (ret == 0) { + keyExpanded = 1; + } + } + } + if ((ret == 0) && (seed != NULL) && (privKey != NULL)) { + /* The "both" shape. RFC 9935 Section 8: regenerate the expanded form + * from the seed and reject the key when the two disagree. */ + byte* expanded = NULL; + word32 expandedLen = 0; + + ret = wc_MlKemKey_PrivateKeySize(key, &expandedLen); + if ((ret == 0) && (expandedLen != privKeyLen)) { + ret = ASN_PARSE_E; + } + if (ret == 0) { + expanded = (byte*)XMALLOC(expandedLen, key->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (expanded == NULL) { + ret = MEMORY_E; + } + } + if (ret == 0) { + ret = wc_MlKemKey_EncodePrivateKey(key, expanded, expandedLen); + } + if (ret == 0) { + if (XMEMCMP(expanded, privKey, expandedLen) != 0) { + WOLFSSL_MSG("ML-KEM seed and expandedKey disagree"); + ret = ASN_PARSE_E; + } + } + if (expanded != NULL) { + ForceZero(expanded, expandedLen); + XFREE(expanded, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + } + if ((ret != 0) && keyExpanded) { + /* The seed was expanded before it could be checked, so scrub it; a + * failure must not leave a usable private key behind. Gated on the + * expansion having run, not on seed != NULL: a wrong-length seed never + * reaches it, and priv may still be NULL under DYNAMIC_KEYS. */ + #ifdef WOLFSSL_MLKEM_DYNAMIC_KEYS + if (key->priv != NULL) { + ForceZero(key->priv, key->privAllocSz); + } + #else + int scrubK = mlkemkey_get_k(key); + + if (scrubK != 0) { + ForceZero(key->priv, + (size_t)scrubK * MLKEM_N * sizeof(sword16)); + } + #endif + ForceZero(key->z, WC_ML_KEM_SYM_SZ); + key->flags &= MLKEM_FLAG_TYPE_SET; + } + else if ((ret == 0) && (seed == NULL)) { + ret = wc_MlKemKey_DecodePrivateKey(key, privKey, privKeyLen); + } +#endif /* WOLFSSL_MLKEM_NO_MAKE_KEY */ + + return ret; +} +#endif /* WC_ENABLE_ASYM_KEY_IMPORT */ + +#endif /* !WOLFSSL_MLKEM_NO_ASN1 */ + #endif /* WOLFSSL_HAVE_MLKEM */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 472f7631031..dcedf2c49bb 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -57213,6 +57213,837 @@ static wc_test_ret_t mlkem1024_kat(void) } #endif /* !WOLFSSL_NO_KYBER1024 && !WOLFSSL_NO_ML_KEM_1024 */ +#if !defined(WOLFSSL_MLKEM_NO_ASN1) && \ + !defined(WOLFSSL_NO_MALLOC) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) && \ + defined(WC_ENABLE_ASYM_KEY_IMPORT) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) +/* Write a DER tag and length. Returns the header size. SetOctetString and + * friends are library-local, so the few bytes needed here are written out + * directly rather than reaching into asn.c. */ +static word32 mlkem_der_hdr(byte tag, word32 len, byte* out) +{ + word32 i = 0; + + out[i++] = tag; + if (len < 0x80) { + out[i++] = (byte)len; + } + else if (len < 0x100) { + out[i++] = 0x81; + out[i++] = (byte)len; + } + else { + out[i++] = 0x82; + out[i++] = (byte)(len >> 8); + out[i++] = (byte)len; + } + + return i; +} + +/* RFC 9935 Section 8: a seed disagreeing with the expandedKey beside it MUST + * be rejected. Covers the "both" shape; the seed shape comes from the RFC 9936 + * interop vector. Keys are built here to reach every parameter set. */ +static wc_test_ret_t mlkem_seed_consistency_test(void) +{ + wc_test_ret_t ret = 0; + MlKemKey* key = NULL; + byte* der = NULL; + byte* expanded = NULL; + byte seed[WC_ML_KEM_MAKEKEY_RAND_SZ]; + byte oid[] = { 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x04,0x00 }; + word32 expandedSz = 0; + int i, j; + static const int levels[] = { +#if defined(WOLFSSL_WC_ML_KEM_512) && !defined(WOLFSSL_NO_ML_KEM) + WC_ML_KEM_512, +#endif +#if defined(WOLFSSL_WC_ML_KEM_768) && !defined(WOLFSSL_NO_ML_KEM) + WC_ML_KEM_768, +#endif +#if defined(WOLFSSL_WC_ML_KEM_1024) && !defined(WOLFSSL_NO_ML_KEM) + WC_ML_KEM_1024, +#endif + -1 /* WC_ML_KEM_512 is 0, so 0 cannot terminate this list */ + }; + + if (levels[0] == -1) + return 0; + + /* the seed RFC 9935 Appendix C uses throughout its examples */ + for (i = 0; i < (int)sizeof(seed); i++) + seed[i] = (byte)i; + + key = (MlKemKey*)XMALLOC(sizeof(MlKemKey), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (key == NULL) + return WC_TEST_RET_ENC_ERRNO; + + for (i = 0; levels[i] != -1; i++) { + /* expand the seed the way a decoder must, then keep the result */ + ret = wc_MlKemKey_Init(key, levels[i], HEAP_HINT, devId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto out_seed; + } + ret = wc_MlKemKey_MakeKeyWithRandom(key, seed, (int)sizeof(seed)); + if (ret == 0) + ret = wc_MlKemKey_PrivateKeySize(key, &expandedSz); + if (ret != 0) { + wc_MlKemKey_Free(key); + ret = WC_TEST_RET_ENC_EC(ret); + goto out_seed; + } + expanded = (byte*)XMALLOC(expandedSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (expanded == NULL) { + wc_MlKemKey_Free(key); + ret = WC_TEST_RET_ENC_ERRNO; + goto out_seed; + } + ret = wc_MlKemKey_EncodePrivateKey(key, expanded, expandedSz); + wc_MlKemKey_Free(key); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto out_seed; + } + + oid[sizeof(oid) - 1] = (byte)(levels[i] == WC_ML_KEM_512 ? 1 : + (levels[i] == WC_ML_KEM_768 ? 2 : 3)); + + /* 0 - a consistent "both" key, must decode + * 1 - the same with one seed byte flipped, must not + * 2 - the seed on its own, must decode to the same key + * 3 - a seed of the wrong length, must not */ + for (j = 0; j < 4; j++) { + word32 bothSz, pkeySz, algSz, bodySz, n, idx; + + /* both ::= SEQUENCE { OCTET STRING seed, OCTET STRING expanded } */ + bothSz = 2 + (word32)sizeof(seed) + 4 + expandedSz; + /* privateKey OCTET STRING wrapping that SEQUENCE */ + pkeySz = 4 + bothSz; + algSz = 2 + (word32)sizeof(oid); + bodySz = 3 + algSz + 4 + pkeySz; + + der = (byte*)XMALLOC(bodySz + 8, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (der == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto out_seed; + } + + if (j >= 2) { + /* seed on its own, under the implicit [0] */ + word32 seedLen = (j == 3) ? (word32)sizeof(seed) - 1 + : (word32)sizeof(seed); + + pkeySz = 2 + seedLen; + bodySz = 3 + algSz + 2 + pkeySz; + + n = mlkem_der_hdr(ASN_SEQUENCE | ASN_CONSTRUCTED, bodySz, der); + der[n++] = ASN_INTEGER; der[n++] = 1; der[n++] = 0; + n += mlkem_der_hdr(ASN_SEQUENCE | ASN_CONSTRUCTED, + (word32)sizeof(oid), der + n); + XMEMCPY(der + n, oid, sizeof(oid)); + n += (word32)sizeof(oid); + n += mlkem_der_hdr(ASN_OCTET_STRING, pkeySz, der + n); + n += mlkem_der_hdr(ASN_CONTEXT_SPECIFIC | 0, seedLen, der + n); + XMEMCPY(der + n, seed, seedLen); + n += seedLen; + } + else { + n = mlkem_der_hdr(ASN_SEQUENCE | ASN_CONSTRUCTED, bodySz, der); + der[n++] = ASN_INTEGER; der[n++] = 1; der[n++] = 0; + n += mlkem_der_hdr(ASN_SEQUENCE | ASN_CONSTRUCTED, + (word32)sizeof(oid), der + n); + XMEMCPY(der + n, oid, sizeof(oid)); + n += (word32)sizeof(oid); + n += mlkem_der_hdr(ASN_OCTET_STRING, pkeySz, der + n); + n += mlkem_der_hdr(ASN_SEQUENCE | ASN_CONSTRUCTED, bothSz, der + n); + n += mlkem_der_hdr(ASN_OCTET_STRING, (word32)sizeof(seed), der + n); + XMEMCPY(der + n, seed, sizeof(seed)); + if (j == 1) + der[n] ^= 0xFF; /* break the seed */ + n += (word32)sizeof(seed); + n += mlkem_der_hdr(ASN_OCTET_STRING, expandedSz, der + n); + XMEMCPY(der + n, expanded, expandedSz); + n += expandedSz; + } + + ret = wc_MlKemKey_Init(key, levels[i], HEAP_HINT, devId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto out_seed; + } + idx = 0; + ret = wc_MlKemKey_PrivateKeyDecode(key, der, n, &idx); + + if (((j == 0) || (j == 2)) && (ret != 0)) { + wc_MlKemKey_Free(key); + ret = WC_TEST_RET_ENC_EC(ret); /* valid, must decode */ + goto out_seed; + } + if (((j == 1) || (j == 3)) && (ret == 0)) { + wc_MlKemKey_Free(key); + ret = WC_TEST_RET_ENC_NC; /* invalid, must not decode */ + goto out_seed; + } + if (j == 2) { + /* the seed alone must reproduce the expanded key exactly */ + byte* again = (byte*)XMALLOC(expandedSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (again == NULL) { + wc_MlKemKey_Free(key); + ret = WC_TEST_RET_ENC_ERRNO; + goto out_seed; + } + ret = wc_MlKemKey_EncodePrivateKey(key, again, expandedSz); + if (ret == 0 && XMEMCMP(again, expanded, expandedSz) != 0) + ret = WC_TEST_RET_ENC_NC; + XMEMSET(again, 0, expandedSz); + XFREE(again, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (ret != 0) { + wc_MlKemKey_Free(key); + goto out_seed; + } + } + wc_MlKemKey_Free(key); + ret = 0; + + XFREE(der, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + der = NULL; + } + + XFREE(expanded, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + expanded = NULL; + } + +out_seed: + XFREE(der, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(expanded, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +/* Round-trip each enabled ML-KEM parameter set through its DER encodings: + * SubjectPublicKeyInfo for the encapsulation key and PKCS#8 for the expanded + * decapsulation key. A decoded public key must still encapsulate to a shared + * secret the decoded private key recovers. */ +static wc_test_ret_t mlkem_asn1_test(void) +{ + wc_test_ret_t ret = 0; + WC_RNG rng; + int rngInit = 0; + int i; + static const int levels[] = { +#if defined(WOLFSSL_WC_ML_KEM_512) && !defined(WOLFSSL_NO_ML_KEM) + WC_ML_KEM_512, +#endif +#if defined(WOLFSSL_WC_ML_KEM_768) && !defined(WOLFSSL_NO_ML_KEM) + WC_ML_KEM_768, +#endif +#if defined(WOLFSSL_WC_ML_KEM_1024) && !defined(WOLFSSL_NO_ML_KEM) + WC_ML_KEM_1024, +#endif + -1 + }; + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + rngInit = 1; + + for (i = 0; levels[i] != -1; i++) { + /* One key object is reused throughout: a static memory pool has room + * for a single MlKemKey alongside the working buffers ML-KEM needs. */ + MlKemKey* key = NULL; + byte* pubDer = NULL; + byte* privDer = NULL; + byte* ct = NULL; + byte ss[WC_ML_KEM_SS_SZ]; + byte ssDec[WC_ML_KEM_SS_SZ]; + word32 idx = 0; + word32 ctLen = 0; + int pubLen = 0; + int privLen = 0; + int wrongLevel = (levels[i] == WC_ML_KEM_512) ? WC_ML_KEM_1024 : + WC_ML_KEM_512; + int keyInit = 0; + + key = (MlKemKey*)XMALLOC(sizeof(MlKemKey), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + /* the ML-KEM-1024 ciphertext is 1568 bytes: too much for the stack of + * a test that also runs under the crypto callback */ + ct = (byte*)XMALLOC(WC_ML_KEM_MAX_CIPHER_TEXT_SIZE, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if ((key == NULL) || (ct == NULL)) { + ret = WC_TEST_RET_ENC_ERRNO; + goto free_level; + } + + ret = wc_MlKemKey_Init(key, levels[i], HEAP_HINT, devId); + if (ret == 0) { + keyInit = 1; + ret = wc_MlKemKey_MakeKey(key, &rng); + } + + /* The generated key produces the ciphertext every decode is measured + * against. */ + if (ret == 0) + ret = wc_MlKemKey_CipherTextSize(key, &ctLen); + if (ret == 0) + ret = wc_MlKemKey_Encapsulate(key, ct, ss, &rng); + + if (ret == 0) { + pubLen = wc_MlKemKey_PublicKeyToDer(key, NULL, 0, 1); + if (pubLen <= 0) + ret = WC_TEST_RET_ENC_EC(pubLen); + } + if (ret == 0) { + pubDer = (byte*)XMALLOC((word32)pubLen, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (pubDer == NULL) + ret = WC_TEST_RET_ENC_ERRNO; + } + if (ret == 0) { + pubLen = wc_MlKemKey_PublicKeyToDer(key, pubDer, (word32)pubLen, 1); + if (pubLen <= 0) + ret = WC_TEST_RET_ENC_EC(pubLen); + } + if (ret == 0) { + privLen = wc_MlKemKey_PrivateKeyToDer(key, NULL, 0); + if (privLen <= 0) + ret = WC_TEST_RET_ENC_EC(privLen); + } + if (ret == 0) { + privDer = (byte*)XMALLOC((word32)privLen, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (privDer == NULL) + ret = WC_TEST_RET_ENC_ERRNO; + } + if (ret == 0) { + privLen = wc_MlKemKey_PrivateKeyToDer(key, privDer, + (word32)privLen); + if (privLen <= 0) + ret = WC_TEST_RET_ENC_EC(privLen); + } + if (keyInit) { + wc_MlKemKey_Free(key); + keyInit = 0; + } + + /* A DER naming a different parameter set must be refused. */ + if (ret == 0) { + if (wc_MlKemKey_Init(key, wrongLevel, HEAP_HINT, devId) == 0) { + idx = 0; + if (wc_MlKemKey_PublicKeyDecode(key, pubDer, (word32)pubLen, + &idx) == 0) { + ret = WC_TEST_RET_ENC_NC; + } + wc_MlKemKey_Free(key); + } + } + /* Initialised without a parameter set, each decode names it. */ + if (ret == 0) { + ret = wc_MlKemKey_Init(key, WC_ML_KEM_TYPE_UNSET, HEAP_HINT, + devId); + if (ret == 0) { + idx = 0; + ret = wc_MlKemKey_PublicKeyDecode(key, pubDer, (word32)pubLen, + &idx); + if ((ret == 0) && (key->type != levels[i])) + ret = WC_TEST_RET_ENC_NC; + wc_MlKemKey_Free(key); + } + } + if (ret == 0) { + ret = wc_MlKemKey_Init(key, WC_ML_KEM_TYPE_UNSET, HEAP_HINT, + devId); + if (ret == 0) { + idx = 0; + ret = wc_MlKemKey_PrivateKeyDecode(key, privDer, + (word32)privLen, &idx); + if ((ret == 0) && (key->type != levels[i])) + ret = WC_TEST_RET_ENC_NC; + wc_MlKemKey_Free(key); + } + } + + /* The PKCS#8 round trip must recover the original decapsulation key. */ + if (ret == 0) { + ret = wc_MlKemKey_Init(key, levels[i], HEAP_HINT, devId); + if (ret == 0) + keyInit = 1; + } + if (ret == 0) { + idx = 0; + ret = wc_MlKemKey_PrivateKeyDecode(key, privDer, (word32)privLen, + &idx); + } + if (ret == 0) { + XMEMSET(ssDec, 0, sizeof(ssDec)); + ret = wc_MlKemKey_Decapsulate(key, ssDec, ct, ctLen); + } + if (ret == 0) { + if (XMEMCMP(ss, ssDec, WC_ML_KEM_SS_SZ) != 0) + ret = WC_TEST_RET_ENC_NC; + } + if (keyInit) { + wc_MlKemKey_Free(key); + keyInit = 0; + } + + /* The SubjectPublicKeyInfo round trip must encapsulate to a secret + * that same private key recovers. */ + if (ret == 0) { + ret = wc_MlKemKey_Init(key, levels[i], HEAP_HINT, devId); + if (ret == 0) + keyInit = 1; + } + if (ret == 0) { + idx = 0; + ret = wc_MlKemKey_PublicKeyDecode(key, pubDer, (word32)pubLen, + &idx); + } + if (ret == 0) + ret = wc_MlKemKey_Encapsulate(key, ct, ss, &rng); + if (keyInit) { + wc_MlKemKey_Free(key); + keyInit = 0; + } + if (ret == 0) { + ret = wc_MlKemKey_Init(key, levels[i], HEAP_HINT, devId); + if (ret == 0) + keyInit = 1; + } + if (ret == 0) { + idx = 0; + ret = wc_MlKemKey_PrivateKeyDecode(key, privDer, (word32)privLen, + &idx); + } + if (ret == 0) { + XMEMSET(ssDec, 0, sizeof(ssDec)); + ret = wc_MlKemKey_Decapsulate(key, ssDec, ct, ctLen); + } + if (ret == 0) { + if (XMEMCMP(ss, ssDec, WC_ML_KEM_SS_SZ) != 0) + ret = WC_TEST_RET_ENC_NC; + } + +free_level: + XFREE(pubDer, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(privDer, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (keyInit) + wc_MlKemKey_Free(key); + XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ct, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + key = NULL; + ct = NULL; + + if (ret != 0) + break; + } + + if (rngInit) + wc_FreeRng(&rng); + + return ret; +} + +#endif /* ML-KEM ASN.1 round trip */ + +/* The committed mlkem-key.der are the RFC 9935 expandedKey form - see + * certs/mlkem/README.txt for why - so nothing here expands a seed and the test + * runs in WOLFSSL_MLKEM_NO_MAKE_KEY builds too, which is exactly what that key + * format was chosen for. */ +#if !defined(WOLFSSL_MLKEM_NO_ASN1) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_MALLOC) && \ + !defined(NO_ASN) && defined(WC_ENABLE_ASYM_KEY_IMPORT) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) + +/* The largest committed ML-KEM DER is under 7 kB, and a static memory pool has + * no bucket much beyond that. */ +#define MLKEM_CERT_DER_SZ (FOURK_BUF * 2) + +/* Parse the ML-KEM end-entity certificates, confirm the subject public key + * carries the expected ML-KEM parameter set, and confirm the matching private + * key reproduces that same public key. */ +static wc_test_ret_t mlkem_cert_test(void) +{ + wc_test_ret_t ret = 0; + int i; + static const struct { + const char* cert; + const char* key; + int level; + } vectors[] = { +#if defined(WOLFSSL_WC_ML_KEM_512) && !defined(WOLFSSL_NO_ML_KEM) + { CERT_ROOT "mlkem" CERT_PATH_SEP "mlkem512-cert.der", + CERT_ROOT "mlkem" CERT_PATH_SEP "mlkem512-key.der", WC_ML_KEM_512 }, +#endif +#if defined(WOLFSSL_WC_ML_KEM_768) && !defined(WOLFSSL_NO_ML_KEM) + { CERT_ROOT "mlkem" CERT_PATH_SEP "mlkem768-cert.der", + CERT_ROOT "mlkem" CERT_PATH_SEP "mlkem768-key.der", WC_ML_KEM_768 }, +#endif +#if defined(WOLFSSL_WC_ML_KEM_1024) && !defined(WOLFSSL_NO_ML_KEM) + { CERT_ROOT "mlkem" CERT_PATH_SEP "mlkem1024-cert.der", + CERT_ROOT "mlkem" CERT_PATH_SEP "mlkem1024-key.der", WC_ML_KEM_1024 }, +#endif + { NULL, NULL, 0 } + }; + + for (i = 0; vectors[i].cert != NULL; i++) { + /* One key object and one file buffer at a time: a static memory pool + * has no room for both halves of the comparison at once. */ + MlKemKey* key = NULL; + XFILE f = XBADFILE; + byte* derBuf = NULL; + byte* spki = NULL; + byte* pubA = NULL; + byte* pubB = NULL; + word32 spkiSz = MLKEM_MAX_PUB_KEY_DER_SIZE; + word32 pubSz = 0; + word32 idx = 0; + size_t derSz = 0; + int keyInit = 0; + + key = (MlKemKey*)XMALLOC(sizeof(MlKemKey), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + derBuf = (byte*)XMALLOC(MLKEM_CERT_DER_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + spki = (byte*)XMALLOC(spkiSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (key == NULL || derBuf == NULL || spki == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto free_vector; + } + + f = XFOPEN(vectors[i].cert, "rb"); + if (f == XBADFILE) { + ret = WC_TEST_RET_ENC_ERRNO; + goto free_vector; + } + derSz = XFREAD(derBuf, 1, MLKEM_CERT_DER_SZ, f); + XFCLOSE(f); + /* a full buffer means the file did not fit */ + if ((derSz == 0) || (derSz == (size_t)MLKEM_CERT_DER_SZ)) { + ret = WC_TEST_RET_ENC_NC; + goto free_vector; + } + + /* Pull the SubjectPublicKeyInfo straight out of the certificate. */ + ret = wc_GetSubjectPubKeyInfoDerFromCert(derBuf, (word32)derSz, spki, + &spkiSz); + if (ret == 0) { + ret = wc_MlKemKey_Init(key, vectors[i].level, HEAP_HINT, devId); + if (ret == 0) + keyInit = 1; + } + /* Decoding only succeeds when the SPKI names this parameter set. */ + if (ret == 0) { + idx = 0; + ret = wc_MlKemKey_PublicKeyDecode(key, spki, spkiSz, &idx); + } + if (ret == 0) + ret = wc_MlKemKey_PublicKeySize(key, &pubSz); + if (ret == 0) { + pubA = (byte*)XMALLOC(pubSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + pubB = (byte*)XMALLOC(pubSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (pubA == NULL || pubB == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto free_vector; + } + } + if (ret == 0) + ret = wc_MlKemKey_EncodePublicKey(key, pubA, pubSz); + if (keyInit) { + wc_MlKemKey_Free(key); + keyInit = 0; + } + if (ret != 0) + goto free_vector; + + f = XFOPEN(vectors[i].key, "rb"); + if (f == XBADFILE) { + ret = WC_TEST_RET_ENC_ERRNO; + goto free_vector; + } + derSz = XFREAD(derBuf, 1, MLKEM_CERT_DER_SZ, f); + XFCLOSE(f); + if ((derSz == 0) || (derSz == (size_t)MLKEM_CERT_DER_SZ)) { + ret = WC_TEST_RET_ENC_NC; + goto free_vector; + } + + ret = wc_MlKemKey_Init(key, vectors[i].level, HEAP_HINT, devId); + if (ret == 0) + keyInit = 1; + if (ret == 0) { + idx = 0; + ret = wc_MlKemKey_PrivateKeyDecode(key, derBuf, (word32)derSz, + &idx); + } + /* The private key must reproduce the certificate's public key. */ + if (ret == 0) + ret = wc_MlKemKey_EncodePublicKey(key, pubB, pubSz); + if (ret == 0) { + if (XMEMCMP(pubA, pubB, pubSz) != 0) + ret = WC_TEST_RET_ENC_NC; + } + +free_vector: + if (keyInit) + wc_MlKemKey_Free(key); + XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pubA, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pubB, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(spki, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + if (ret != 0) + break; + } + return ret; +} +#endif /* ML-KEM certificate test */ + +/* Certificate generation with an ML-KEM subject key. A KEM cannot sign, so a + * separate key issues it, as certs/renewcerts.sh does. ECDSA rather than + * ML-DSA because it is in far more builds, and the MLKEM_TYPE branches under + * test do not depend on the issuer algorithm. */ +#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) && \ + !defined(WOLFSSL_NO_MALLOC) && \ + defined(WOLFSSL_TEST_CERT) && !defined(WOLFSSL_MLKEM_NO_ASN1) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && !defined(NO_ASN) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) && defined(WC_ENABLE_ASYM_KEY_IMPORT) && \ + !defined(WC_NO_RNG) && defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) && \ + !defined(NO_ECC256) && !defined(NO_SHA256) + +#define MLKEM_CERTGEN_DER_SZ (FOURK_BUF * 2) + +/* Only the string fields are set; wc_InitCert_ex() already zeroed the struct + * and picked the per-field ASN encoding types, which must be preserved. */ +static void mlkem_certgen_name(CertName* n, const char* cn) +{ + XSTRNCPY(n->country, "US", CTC_NAME_SIZE); + n->countryEnc = CTC_PRINTABLE; + XSTRNCPY(n->state, "Montana", CTC_NAME_SIZE); + n->stateEnc = CTC_UTF8; + XSTRNCPY(n->locality, "Bozeman", CTC_NAME_SIZE); + n->localityEnc = CTC_UTF8; + XSTRNCPY(n->org, "wolfSSL", CTC_NAME_SIZE); + n->orgEnc = CTC_UTF8; + /* Leave room for the terminator: cn is not a literal, so copying the full + * field width reads as a possible truncation. */ + XSTRNCPY(n->commonName, cn, CTC_NAME_SIZE - 1); + n->commonName[CTC_NAME_SIZE - 1] = '\0'; + n->commonNameEnc = CTC_UTF8; +} + +/* Issue one certificate for the given ML-KEM parameter set, then read it back + * and confirm what was written. */ +static wc_test_ret_t mlkem_certgen_one(WC_RNG* rng, ecc_key* ca, int level, + int expKeyOID, const char* cn) +{ + wc_test_ret_t ret = 0; + MlKemKey kem[1]; + MlKemKey fromCert[1]; + DecodedCert decode; + Cert* cert = NULL; + byte* der = NULL; + byte* spki = NULL; + byte* pubA = NULL; + byte* pubB = NULL; + word32 spkiSz = MLKEM_MAX_PUB_KEY_DER_SIZE; + word32 pubSz = 0; + word32 idx = 0; + int kemInit = 0; + int certInit = 0; + int decodeInit = 0; + int bodySz = 0; + int certSz = 0; + + cert = (Cert*)XMALLOC(sizeof(Cert), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + der = (byte*)XMALLOC(MLKEM_CERTGEN_DER_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + spki = (byte*)XMALLOC(spkiSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (cert == NULL || der == NULL || spki == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto free_certgen; + } + + if (ret == 0) { + ret = wc_MlKemKey_Init(kem, level, HEAP_HINT, devId); + if (ret == 0) + kemInit = 1; + } + if (ret == 0) + ret = wc_MlKemKey_MakeKey(kem, rng); + + if (ret == 0) { + wc_InitCert_ex(cert, HEAP_HINT, devId); + mlkem_certgen_name(&cert->issuer, "ML-KEM test issuer"); + mlkem_certgen_name(&cert->subject, cn); + cert->daysValid = 365; + cert->selfSigned = 0; /* a KEM cannot sign for itself */ + cert->isCA = 0; + cert->sigType = CTC_SHA256wECDSA; + /* CNSA 2.0 key establishment certificate: keyEncipherment alone. */ + ret = wc_SetKeyUsage(cert, "keyEncipherment"); + } + if (ret == 0) + ret = wc_SetSubjectKeyIdFromPublicKey_ex(cert, MLKEM_TYPE, kem); + + if (ret == 0) { + bodySz = wc_MakeCert_ex(cert, der, MLKEM_CERTGEN_DER_SZ, MLKEM_TYPE, + kem, rng); + if (bodySz <= 0) + ret = WC_TEST_RET_ENC_I(bodySz); + } + if (ret == 0) { + do { +#ifdef WOLFSSL_ASYNC_CRYPT + ret = wc_AsyncWait(ret, &ca->asyncDev, WC_ASYNC_FLAG_CALL_AGAIN); +#endif + if (ret >= 0) { + ret = wc_SignCert_ex(bodySz, cert->sigType, der, + MLKEM_CERTGEN_DER_SZ, ECC_TYPE, ca, rng); + } + } while (ret == WC_NO_ERR_TRACE(WC_PENDING_E)); + certSz = (int)ret; + ret = (certSz > 0) ? 0 : WC_TEST_RET_ENC_I(certSz); + } + + /* The certificate must parse, name the expected ML-KEM parameter set and + * assert keyEncipherment and nothing else. */ + if (ret == 0) { + InitDecodedCert(&decode, der, (word32)certSz, HEAP_HINT); + decodeInit = 1; + ret = ParseCert(&decode, CERT_TYPE, NO_VERIFY, NULL); + } + if (ret == 0) { + if (decode.keyOID != (word32)expKeyOID) + ret = WC_TEST_RET_ENC_NC; + } + if (ret == 0) { + if (!decode.extKeyUsageSet || + decode.extKeyUsage != KEYUSE_KEY_ENCIPHER) { + ret = WC_TEST_RET_ENC_NC; + } + } + if (ret == 0) { + if (!decode.extSubjKeyIdSet) + ret = WC_TEST_RET_ENC_NC; + } + + /* The public key in the certificate must be the one that was generated. */ + if (ret == 0) { + ret = wc_GetSubjectPubKeyInfoDerFromCert(der, (word32)certSz, spki, + &spkiSz); + } + if (ret == 0) { + ret = wc_MlKemKey_Init(fromCert, level, HEAP_HINT, devId); + if (ret == 0) + certInit = 1; + } + if (ret == 0) { + idx = 0; + ret = wc_MlKemKey_PublicKeyDecode(fromCert, spki, spkiSz, &idx); + } + if (ret == 0) + ret = wc_MlKemKey_PublicKeySize(kem, &pubSz); + if (ret == 0) { + pubA = (byte*)XMALLOC(pubSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + pubB = (byte*)XMALLOC(pubSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (pubA == NULL || pubB == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto free_certgen; + } + } + if (ret == 0) + ret = wc_MlKemKey_EncodePublicKey(kem, pubA, pubSz); + if (ret == 0) + ret = wc_MlKemKey_EncodePublicKey(fromCert, pubB, pubSz); + if (ret == 0) { + if (XMEMCMP(pubA, pubB, pubSz) != 0) + ret = WC_TEST_RET_ENC_NC; + } + +free_certgen: + if (decodeInit) + FreeDecodedCert(&decode); + if (certInit) + wc_MlKemKey_Free(fromCert); + if (kemInit) + wc_MlKemKey_Free(kem); + XFREE(pubA, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pubB, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(spki, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(der, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(cert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +static wc_test_ret_t mlkem_certgen_test(void) +{ + wc_test_ret_t ret = 0; + int i; + WC_RNG rng; + ecc_key ca[1]; + int rngInit = 0; + int caInit = 0; + static const struct { + int level; + int keyOID; + const char* cn; + } vectors[] = { +#if defined(WOLFSSL_WC_ML_KEM_512) && !defined(WOLFSSL_NO_ML_KEM) + { WC_ML_KEM_512, ML_KEM_512k, "ML-KEM-512" }, +#endif +#if defined(WOLFSSL_WC_ML_KEM_768) && !defined(WOLFSSL_NO_ML_KEM) + { WC_ML_KEM_768, ML_KEM_768k, "ML-KEM-768" }, +#endif +#if defined(WOLFSSL_WC_ML_KEM_1024) && !defined(WOLFSSL_NO_ML_KEM) + { WC_ML_KEM_1024, ML_KEM_1024k, "ML-KEM-1024" }, +#endif + /* WC_ML_KEM_512 is 0, so -1 terminates the list, not 0. */ + { -1, 0, NULL } + }; + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret == 0) + rngInit = 1; + + /* One issuer key for every parameter set. */ + if (ret == 0) { + ret = wc_ecc_init_ex(ca, HEAP_HINT, devId); + if (ret == 0) + caInit = 1; + } + if (ret == 0) { + ret = wc_ecc_make_key(&rng, 32, ca); +#ifdef WOLFSSL_ASYNC_CRYPT + ret = wc_AsyncWait(ret, &ca->asyncDev, WC_ASYNC_FLAG_NONE); +#endif + } + + for (i = 0; (ret == 0) && (vectors[i].cn != NULL); i++) { + ret = mlkem_certgen_one(&rng, ca, vectors[i].level, vectors[i].keyOID, + vectors[i].cn); + } + + if (caInit) + wc_ecc_free(ca); + if (rngInit) + wc_FreeRng(&rng); + + return ret; +} +#endif /* ML-KEM certificate generation test */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mlkem_test(void) { wc_test_ret_t ret; @@ -57259,15 +58090,15 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mlkem_test(void) int key_inited = 0; static const int testData[][4] = { #ifndef WOLFSSL_NO_ML_KEM - #ifdef WOLFSSL_WC_ML_KEM_512 + #if defined(WOLFSSL_WC_ML_KEM_512) && !defined(WOLFSSL_NO_ML_KEM) { WC_ML_KEM_512, WC_ML_KEM_512_PRIVATE_KEY_SIZE, WC_ML_KEM_512_PUBLIC_KEY_SIZE, WC_ML_KEM_512_CIPHER_TEXT_SIZE }, #endif - #ifdef WOLFSSL_WC_ML_KEM_768 + #if defined(WOLFSSL_WC_ML_KEM_768) && !defined(WOLFSSL_NO_ML_KEM) { WC_ML_KEM_768, WC_ML_KEM_768_PRIVATE_KEY_SIZE, WC_ML_KEM_768_PUBLIC_KEY_SIZE, WC_ML_KEM_768_CIPHER_TEXT_SIZE }, #endif - #ifdef WOLFSSL_WC_ML_KEM_1024 + #if defined(WOLFSSL_WC_ML_KEM_1024) && !defined(WOLFSSL_NO_ML_KEM) { WC_ML_KEM_1024, WC_ML_KEM_1024_PRIVATE_KEY_SIZE, WC_ML_KEM_1024_PUBLIC_KEY_SIZE, WC_ML_KEM_1024_CIPHER_TEXT_SIZE }, #endif @@ -57485,6 +58316,42 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mlkem_test(void) goto out; #endif +#if !defined(WOLFSSL_MLKEM_NO_ASN1) && \ + !defined(WOLFSSL_NO_MALLOC) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) && \ + defined(WC_ENABLE_ASYM_KEY_IMPORT) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) + ret = mlkem_asn1_test(); + if (ret != 0) + goto out; + ret = mlkem_seed_consistency_test(); + if (ret != 0) + goto out; +#endif + +#if !defined(WOLFSSL_MLKEM_NO_ASN1) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_MALLOC) && \ + !defined(NO_ASN) && defined(WC_ENABLE_ASYM_KEY_IMPORT) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) + ret = mlkem_cert_test(); + if (ret != 0) + goto out; +#endif + +#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) && \ + !defined(WOLFSSL_NO_MALLOC) && \ + defined(WOLFSSL_TEST_CERT) && !defined(WOLFSSL_MLKEM_NO_ASN1) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && !defined(NO_ASN) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) && defined(WC_ENABLE_ASYM_KEY_IMPORT) && \ + !defined(WC_NO_RNG) && defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) && \ + !defined(NO_ECC256) && !defined(NO_SHA256) + ret = mlkem_certgen_test(); + if (ret != 0) + goto out; +#endif + out: if (key_inited) @@ -81435,19 +82302,29 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) } else if (info->algo_type == WC_ALGO_TYPE_SEED) { #ifndef WC_NO_RNG - ALIGN32 static byte seed[sizeof(word32)] = { 0x00, 0x00, 0x00, 0x01 }; - word32* seedWord32 = (word32*)seed; + static word32 seedState = 0x00000001; + byte seed[sizeof(word32)]; word32 len; - /* wc_GenerateSeed is a local symbol so we need to fake the entropy. */ + /* wc_GenerateSeed is a local symbol so we need to fake the entropy. + * Step an xorshift rather than a plain counter: _InitRng() runs the + * SP 800-90B health tests over the seed, and counter bytes fail the + * adaptive proportion test once the upper bytes repeat. */ while (info->seed.sz > 0) { + seedState ^= seedState << 13; + seedState ^= seedState >> 17; + seedState ^= seedState << 5; + seed[0] = (byte)(seedState ); + seed[1] = (byte)(seedState >> 8); + seed[2] = (byte)(seedState >> 16); + seed[3] = (byte)(seedState >> 24); + len = (word32)sizeof(seed); if (info->seed.sz < len) len = info->seed.sz; XMEMCPY(info->seed.seed, seed, len); info->seed.seed += len; info->seed.sz -= len; - (*seedWord32)++; } ret = 0; diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 1cccedd6bd7..c732cb8cc1a 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -1987,14 +1987,15 @@ struct DecodedCert { #if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) || \ defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ defined(WOLFSSL_HAVE_SLHDSA) || defined(WOLFSSL_HAVE_LMS) || \ - defined(WOLFSSL_HAVE_XMSS) + defined(WOLFSSL_HAVE_XMSS) || defined(WOLFSSL_HAVE_FRODOKEM) || \ + defined(WOLFSSL_HAVE_MLKEM) word32 pkCurveOID; /* Public Key's curve OID */ #ifdef WOLFSSL_CUSTOM_CURVES int pkCurveSize; /* Public Key's curve size */ #endif #endif /* HAVE_ECC || HAVE_ED25519 || HAVE_ED448 || WOLFSSL_HAVE_MLDSA || * HAVE_FALCON || WOLFSSL_HAVE_SLHDSA || WOLFSSL_HAVE_LMS || - * WOLFSSL_HAVE_XMSS */ + * WOLFSSL_HAVE_XMSS || WOLFSSL_HAVE_FRODOKEM || WOLFSSL_HAVE_MLKEM */ const byte* beforeDate; int beforeDateLen; const byte* afterDate; @@ -2915,7 +2916,8 @@ enum cert_enums { LMS_KEY = 36, XMSS_KEY = 37, XMSSMT_KEY = 38, - FRODOKEM_KEY = 39 + FRODOKEM_KEY = 39, + MLKEM_KEY = 40 }; #ifndef WOLFSSL_NO_DILITHIUM_LEGACY_NAMES @@ -3384,7 +3386,8 @@ WOLFSSL_LOCAL int wolfssl_local_MatchDnsNameConstraint(const char* name, || (defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT)) \ || (defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_IMPORT)) \ || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) \ - || defined(WOLFSSL_HAVE_SLHDSA) || defined(WOLFSSL_HAVE_FRODOKEM)) + || defined(WOLFSSL_HAVE_SLHDSA) || defined(WOLFSSL_HAVE_FRODOKEM) \ + || (defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1))) WOLFSSL_LOCAL int DecodeAsymKey_Assign(const byte* input, word32* inOutIdx, word32 inSz, const byte** seed, word32* seedLen, const byte** privKey, word32* privKeyLen, const byte** pubKey, word32* pubKeyLen, diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h index c975613f690..d74629a8b08 100644 --- a/wolfssl/wolfcrypt/asn_public.h +++ b/wolfssl/wolfcrypt/asn_public.h @@ -184,7 +184,8 @@ enum CertType { LMS_TYPE, XMSS_TYPE, XMSSMT_TYPE, - FRODOKEM_TYPE + FRODOKEM_TYPE, + MLKEM_TYPE }; #ifndef WOLFSSL_NO_DILITHIUM_LEGACY_NAMES diff --git a/wolfssl/wolfcrypt/oid_sum.h b/wolfssl/wolfcrypt/oid_sum.h index 7647b05dea7..31f8f4fb033 100644 --- a/wolfssl/wolfcrypt/oid_sum.h +++ b/wolfssl/wolfcrypt/oid_sum.h @@ -226,6 +226,12 @@ enum Key_Sum { XMSSk = 107, /* 1.3.6.1.5.5.7.6.34 */ /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x23 */ XMSSMTk = 108, /* 1.3.6.1.5.5.7.6.35 */ + /* 0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x04,0x01 */ + ML_KEM_512k = 416, /* 2.16.840.1.101.3.4.4.1 */ + /* 0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x04,0x02 */ + ML_KEM_768k = 417, /* 2.16.840.1.101.3.4.4.2 */ + /* 0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x04,0x03 */ + ML_KEM_1024k = 418, /* 2.16.840.1.101.3.4.4.3 */ /* 0x28,0x81,0x8c,0x71,0x02,0x02,0x07,0x01 */ FRODOKEM_976_SHAKEk = 100434, /* 1.0.18033.2.2.7.1 */ /* 0x28,0x81,0x8c,0x71,0x02,0x02,0x07,0x02 */ @@ -313,6 +319,12 @@ enum Key_Sum { XMSSk = 0x2707012e, /* 1.3.6.1.5.5.7.6.34 */ /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x23 */ XMSSMTk = 0x2607012e, /* 1.3.6.1.5.5.7.6.35 */ + /* 0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x04,0x01 */ + ML_KEM_512k = 0x7ab37afb, /* 2.16.840.1.101.3.4.4.1 */ + /* 0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x04,0x02 */ + ML_KEM_768k = 0x7ab37af8, /* 2.16.840.1.101.3.4.4.2 */ + /* 0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x04,0x03 */ + ML_KEM_1024k = 0x7ab37af9, /* 2.16.840.1.101.3.4.4.3 */ /* 0x28,0x81,0x8c,0x71,0x02,0x02,0x07,0x01 */ FRODOKEM_976_SHAKEk = 0x708b832a, /* 1.0.18033.2.2.7.1 */ /* 0x28,0x81,0x8c,0x71,0x02,0x02,0x07,0x02 */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index b111438a368..646fa8595a2 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -3742,6 +3742,16 @@ #endif /* HAVE_ED448 */ +/* Derived here rather than in wc_mlkem.h, which is included from inside the + * guard that tests this. Names the ASN.1 backend inputs rather than + * WOLFSSL_ASN_TEMPLATE, which has no default until several hundred lines down + * and so is undefined in CMake builds. Keep in step with that block. */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1) && \ + (defined(NO_ASN) || \ + (!defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_ASN_ORIGINAL))) + #define WOLFSSL_MLKEM_NO_ASN1 +#endif + /* RFC 5958 (Asymmetric Key Packages) */ #if !defined(WC_ENABLE_ASYM_KEY_EXPORT) && \ ((defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT)) || \ @@ -3750,6 +3760,7 @@ (defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_EXPORT)) || \ defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) || \ defined(WOLFSSL_HAVE_FRODOKEM) || \ + (defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1)) || \ defined(WOLFSSL_HAVE_SLHDSA) || \ (defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \ (defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY))) @@ -3763,6 +3774,7 @@ (defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_IMPORT)) || \ defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) || \ defined(WOLFSSL_HAVE_FRODOKEM) || \ + (defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ASN1)) || \ defined(WOLFSSL_HAVE_SLHDSA) || \ (defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \ (defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY))) diff --git a/wolfssl/wolfcrypt/wc_mlkem.h b/wolfssl/wolfcrypt/wc_mlkem.h index 4c45786525e..308a07c732b 100644 --- a/wolfssl/wolfcrypt/wc_mlkem.h +++ b/wolfssl/wolfcrypt/wc_mlkem.h @@ -324,6 +324,13 @@ enum { KYBER_LEVEL3 = KYBER768, KYBER_LEVEL5 = KYBER1024, + /* Pass to wc_MlKemKey_Init to set the object up without committing to a + * parameter set, leaving a DER decode to take it from the algorithm OID. + * Deliberately not 0: a zeroed object is indistinguishable from one + * holding WC_ML_KEM_512, so MLKEM_FLAG_TYPE_SET is what actually + * separates the two. */ + WC_ML_KEM_TYPE_UNSET = 0x20, + /* Symmetric data size. */ WC_ML_KEM_SYM_SZ = 32, /* Shared secret size. */ @@ -342,6 +349,8 @@ enum { MLKEM_FLAG_BOTH_SET = 0x0003, MLKEM_FLAG_H_SET = 0x0004, MLKEM_FLAG_A_SET = 0x0008, + /* Set by wc_MlKemKey_Init when a parameter set was named. */ + MLKEM_FLAG_TYPE_SET = 0x0010, /* 2 bits of random used to create noise value. */ MLKEM_CBD_ETA2 = 2, @@ -464,6 +473,33 @@ WOLFSSL_API int wc_MlKemKey_DecodePrivateKey(MlKemKey* key, WOLFSSL_API int wc_MlKemKey_DecodePublicKey(MlKemKey* key, const unsigned char* in, word32 len); +#ifndef WOLFSSL_MLKEM_NO_ASN1 +/* DER (SubjectPublicKeyInfo / PKCS#8) encoding overhead over the raw key. */ +#define MLKEM_ASN1_PUB_OVERHEAD 32 +#define MLKEM_ASN1_PRV_OVERHEAD 40 +/* Maximum DER-encoded key sizes over the enabled parameter sets. Callers may + * also pass a NULL output to the *ToDer functions to get the exact length. */ +#define MLKEM_MAX_PUB_KEY_DER_SIZE \ + (WC_ML_KEM_MAX_PUBLIC_KEY_SIZE + MLKEM_ASN1_PUB_OVERHEAD) +#define MLKEM_MAX_PRV_KEY_DER_SIZE \ + (WC_ML_KEM_MAX_PRIVATE_KEY_SIZE + MLKEM_ASN1_PRV_OVERHEAD) + +WOLFSSL_LOCAL +int mlkem_type_from_oid_sum(int oidSum, int* type); +#ifdef WC_ENABLE_ASYM_KEY_EXPORT +WOLFSSL_API int wc_MlKemKey_PublicKeyToDer(MlKemKey* key, byte* output, + word32 len, int withAlg); +WOLFSSL_API int wc_MlKemKey_PrivateKeyToDer(MlKemKey* key, byte* output, + word32 len); +#endif /* WC_ENABLE_ASYM_KEY_EXPORT */ +#ifdef WC_ENABLE_ASYM_KEY_IMPORT +WOLFSSL_API int wc_MlKemKey_PublicKeyDecode(MlKemKey* key, const byte* input, + word32 inSz, word32* inOutIdx); +WOLFSSL_API int wc_MlKemKey_PrivateKeyDecode(MlKemKey* key, const byte* input, + word32 inSz, word32* inOutIdx); +#endif /* WC_ENABLE_ASYM_KEY_IMPORT */ +#endif /* !WOLFSSL_MLKEM_NO_ASN1 */ + WOLFSSL_API int wc_MlKemKey_PrivateKeySize(MlKemKey* key, word32* len); WOLFSSL_API int wc_MlKemKey_PublicKeySize(MlKemKey* key, word32* len); WOLFSSL_API int wc_MlKemKey_EncodePrivateKey(MlKemKey* key, unsigned char* out,