diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index cd97b9567e..a3b39e670d 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -31,6 +31,12 @@ #ifdef HAVE_ED25519 #include #endif +#ifdef HAVE_ED448 + #include +#endif +#ifdef HAVE_DILITHIUM + #include +#endif #if defined(WC_ENABLE_ASYM_KEY_EXPORT) && defined(HAVE_ED25519) static int test_SetAsymKeyDer_once(byte* privKey, word32 privKeySz, byte* pubKey, @@ -1350,3 +1356,433 @@ int test_wc_DecodeObjectId(void) return EXPECT_RESULT(); } + +#if defined(HAVE_PKCS8) && !defined(NO_ASN) && \ + (defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN)) && \ + (defined(HAVE_ED25519) || \ + (defined(HAVE_ED448) && defined(HAVE_ED448_KEY_EXPORT) && \ + defined(WOLFSSL_KEY_GEN)) || \ + (defined(HAVE_DILITHIUM) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_ASN1))) +/* Run ToTraditional_ex() on a copy of der and assert the algId, returned + * length, and the inner OCTET STRING tag/length at the start of the + * (in-place rewritten) buffer. */ +static int test_ToTraditional_ex_once(const byte* der, word32 derSz, + word32 expectAlgId, word32 expectPrivKeySz) +{ + EXPECT_DECLS; + byte* copy = NULL; + word32 algId = 0; + int ret; + + copy = (byte*)XMALLOC(derSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(copy); + if (copy != NULL) { + XMEMCPY(copy, der, derSz); + ret = ToTraditional_ex(copy, derSz, &algId); + ExpectIntGT(ret, 0); + ExpectIntEQ(algId, expectAlgId); + if (ret > 0) { + /* Inner CurvePrivateKey OCTET STRING header + privKey bytes. */ + ExpectIntEQ(copy[0], ASN_OCTET_STRING); + /* DER short form: length fits in one byte (< 128). */ + if (expectPrivKeySz < 0x80) { + ExpectIntEQ(copy[1], (byte)expectPrivKeySz); + } + /* DER long form, 1 length byte (128..255). */ + else if (expectPrivKeySz < 0x100) { + /* 0x81: long form indicator, 1 length byte follows. */ + ExpectIntEQ(copy[1], 0x81); + ExpectIntEQ(copy[2], (byte)expectPrivKeySz); + } + /* DER long form, 2 length bytes (256..65535). */ + else { + /* 0x82: long form indicator, 2 length bytes follow. */ + ExpectIntEQ(copy[1], 0x82); + ExpectIntEQ(((word32)copy[2] << 8) | copy[3], expectPrivKeySz); + } + } + } + XFREE(copy, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + return EXPECT_RESULT(); +} +#endif + +/* Hand crafted PKCS#8 v0 and v1 Ed25519 buffers to test parser directly. */ +int test_ToTraditional_ex_handcrafted(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS8) && defined(HAVE_ED25519) && \ + (defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN)) + /* Ed25519 algorithm OID body (1.3.101.112). */ + static const byte algId[] = { 43, 101, 112 }; + const word32 privKeySz = ED25519_KEY_SIZE; + const word32 pubKeySz = ED25519_PUB_KEY_SIZE; + byte der[128]; + word32 sz; + word32 outerLenIdx; + /* Filler bytes for the dummy private/public key bodies */ + const byte keyPat = 0xCC; + const byte pubPat = 0xDD; + + /* v0: SEQ { INTEGER 0, SEQ { OID }, OCTET STRING { OCTET STRING priv } } */ + sz = 0; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + outerLenIdx = sz; + der[sz++] = 0; /* outer length, filled in below */ + der[sz++] = ASN_INTEGER; + der[sz++] = 1; + der[sz++] = 0x00; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + der[sz++] = (byte)(sizeof(algId) + 2); + der[sz++] = ASN_OBJECT_ID; + der[sz++] = (byte)sizeof(algId); + XMEMCPY(der + sz, algId, sizeof(algId)); sz += sizeof(algId); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)(privKeySz + 2); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)privKeySz; + XMEMSET(der + sz, keyPat, privKeySz); sz += privKeySz; + der[outerLenIdx] = (byte)(sz - outerLenIdx - 1); + + EXPECT_TEST(test_ToTraditional_ex_once(der, sz, ED25519k, privKeySz)); + + /* v1: same plus [1] publicKey trailer. */ + sz = 0; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + outerLenIdx = sz; + der[sz++] = 0; + der[sz++] = ASN_INTEGER; + der[sz++] = 1; + der[sz++] = 0x01; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + der[sz++] = (byte)(sizeof(algId) + 2); + der[sz++] = ASN_OBJECT_ID; + der[sz++] = (byte)sizeof(algId); + XMEMCPY(der + sz, algId, sizeof(algId)); sz += sizeof(algId); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)(privKeySz + 2); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)privKeySz; + XMEMSET(der + sz, keyPat, privKeySz); sz += privKeySz; + /* [1] publicKey trailer */ + der[sz++] = ASN_CONTEXT_SPECIFIC | ASN_ASYMKEY_PUBKEY; + der[sz++] = (byte)pubKeySz; + XMEMSET(der + sz, pubPat, pubKeySz); sz += pubKeySz; + der[outerLenIdx] = (byte)(sz - outerLenIdx - 1); + + EXPECT_TEST(test_ToTraditional_ex_once(der, sz, ED25519k, privKeySz)); + + /* v1 without publicKey: should still accept per RFC 5958. */ + sz = 0; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + outerLenIdx = sz; + der[sz++] = 0; + der[sz++] = ASN_INTEGER; + der[sz++] = 1; + der[sz++] = 0x01; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + der[sz++] = (byte)(sizeof(algId) + 2); + der[sz++] = ASN_OBJECT_ID; + der[sz++] = (byte)sizeof(algId); + XMEMCPY(der + sz, algId, sizeof(algId)); sz += sizeof(algId); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)(privKeySz + 2); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)privKeySz; + XMEMSET(der + sz, keyPat, privKeySz); sz += privKeySz; + der[outerLenIdx] = (byte)(sz - outerLenIdx - 1); + + EXPECT_TEST(test_ToTraditional_ex_once(der, sz, ED25519k, privKeySz)); +#endif /* HAVE_PKCS8 && HAVE_ED25519 */ + return EXPECT_RESULT(); +} + +/* Encoder/parser round trip: ToTraditional_ex() must accept both forms created + * by SetAsymKeyDer() (v0 with PrivateKeyToDer, v1 with KeyToDer). */ +int test_ToTraditional_ex_roundtrip(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS8) && \ + (defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN)) + +#if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_EXPORT) && \ + defined(WOLFSSL_KEY_GEN) + { + ed25519_key key; + WC_RNG rng; + byte der[256]; + int derSz = 0; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ed25519_init(&key), 0); + ExpectIntEQ(wc_ed25519_make_key(&rng, ED25519_KEY_SIZE, &key), 0); + + if (EXPECT_SUCCESS()) { + ExpectIntGT(derSz = wc_Ed25519KeyToDer(&key, der, sizeof(der)), 0); + EXPECT_TEST(test_ToTraditional_ex_once(der, (word32)derSz, ED25519k, + ED25519_KEY_SIZE)); + + derSz = wc_Ed25519PrivateKeyToDer(&key, der, sizeof(der)); + ExpectIntGT(derSz, 0); + EXPECT_TEST(test_ToTraditional_ex_once(der, (word32)derSz, ED25519k, + ED25519_KEY_SIZE)); + } + + wc_ed25519_free(&key); + wc_FreeRng(&rng); + } +#endif /* HAVE_ED25519 */ + +#if defined(HAVE_ED448) && defined(HAVE_ED448_KEY_EXPORT) && \ + defined(WOLFSSL_KEY_GEN) + { + ed448_key key; + WC_RNG rng; + byte der[256]; + int derSz = 0; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ed448_init(&key), 0); + ExpectIntEQ(wc_ed448_make_key(&rng, ED448_KEY_SIZE, &key), 0); + + if (EXPECT_SUCCESS()) { + ExpectIntGT(derSz = wc_Ed448KeyToDer(&key, der, sizeof(der)), 0); + EXPECT_TEST(test_ToTraditional_ex_once(der, (word32)derSz, ED448k, + ED448_KEY_SIZE)); + + derSz = wc_Ed448PrivateKeyToDer(&key, der, sizeof(der)); + ExpectIntGT(derSz, 0); + EXPECT_TEST(test_ToTraditional_ex_once(der, (word32)derSz, ED448k, + ED448_KEY_SIZE)); + } + + wc_ed448_free(&key); + wc_FreeRng(&rng); + } +#endif /* HAVE_ED448 */ + +#if defined(HAVE_DILITHIUM) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_ASN1) + { + /* dilithium.h errors out if HAVE_DILITHIUM is set with no levels + * enabled, so at least one entry below is always compiled in. */ + static const struct { + int wcLevel; + word32 oidSum; + word32 privKeySz; + } variants[] = { + #ifndef WOLFSSL_NO_ML_DSA_44 + { WC_ML_DSA_44, ML_DSA_LEVEL2k, ML_DSA_LEVEL2_KEY_SIZE }, + #endif + #ifndef WOLFSSL_NO_ML_DSA_65 + { WC_ML_DSA_65, ML_DSA_LEVEL3k, ML_DSA_LEVEL3_KEY_SIZE }, + #endif + #ifndef WOLFSSL_NO_ML_DSA_87 + { WC_ML_DSA_87, ML_DSA_LEVEL5k, ML_DSA_LEVEL5_KEY_SIZE }, + #endif + }; + + const word32 derMaxSz = DILITHIUM_MAX_BOTH_KEY_DER_SIZE; + byte* der = NULL; + WC_RNG rng; + size_t i; + int derSz; + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectNotNull(der = (byte*)XMALLOC(derMaxSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + + for (i = 0; i < sizeof(variants) / sizeof(variants[0]); i++) { + dilithium_key key; + + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_dilithium_init(&key), 0); + ExpectIntEQ(wc_dilithium_set_level(&key, variants[i].wcLevel), 0); + ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); + + if (EXPECT_SUCCESS()) { + ExpectIntGT(derSz = wc_Dilithium_KeyToDer(&key, der, derMaxSz), + 0); + EXPECT_TEST(test_ToTraditional_ex_once(der, (word32)derSz, + variants[i].oidSum, variants[i].privKeySz)); + + derSz = wc_Dilithium_PrivateKeyToDer(&key, der, derMaxSz); + ExpectIntGT(derSz, 0); + EXPECT_TEST(test_ToTraditional_ex_once(der, (word32)derSz, + variants[i].oidSum, variants[i].privKeySz)); + } + + wc_dilithium_free(&key); + } + + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + } +#endif /* HAVE_DILITHIUM */ + +#endif /* HAVE_PKCS8 */ + return EXPECT_RESULT(); +} + +/* Trailing garbage that is neither [0] attributes nor [1] publicKey must + * still be rejected. */ +int test_ToTraditional_ex_negative(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS8) && defined(HAVE_ED25519) && \ + defined(HAVE_ED25519_KEY_EXPORT) && defined(WOLFSSL_KEY_GEN) && \ + defined(WOLFSSL_ASN_TEMPLATE) && \ + (defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN)) + ed25519_key key; + WC_RNG rng; + byte der[256]; + byte copy[256]; + int derSz = 0; + word32 algId; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ed25519_init(&key), 0); + ExpectIntEQ(wc_ed25519_make_key(&rng, ED25519_KEY_SIZE, &key), 0); + ExpectIntGT(derSz = wc_Ed25519PrivateKeyToDer(&key, der, sizeof(der)), 0); + + if (EXPECT_SUCCESS() && (derSz > 0) && + ((size_t)derSz + 1 <= sizeof(copy))) { + /* Append one byte of trailing data, grow outer SEQ length to cover. + * Ed25519 PKCS#8 outer SEQ is under 128 bytes, expect DER short form + * so the negative path is always exercised. */ + XMEMCPY(copy, der, (size_t)derSz); + ExpectTrue(copy[1] < 0x80); + copy[1] = (byte)(copy[1] + 1); + copy[derSz] = 0x05; + algId = 0; + ExpectIntLT(ToTraditional_ex(copy, (word32)(derSz + 1), &algId), 0); + } + + /* publicKey trailer is permitted only when version == v1 */ + if (EXPECT_SUCCESS() && (derSz > 0) && + ((size_t)derSz + 2 + ED25519_PUB_KEY_SIZE <= sizeof(copy))) { + word32 trailerSz = 2 + ED25519_PUB_KEY_SIZE; + XMEMCPY(copy, der, (size_t)derSz); + ExpectTrue(copy[1] < (byte)(0x80 - trailerSz)); + copy[1] = (byte)(copy[1] + trailerSz); + copy[derSz] = ASN_CONTEXT_SPECIFIC | ASN_ASYMKEY_PUBKEY; + copy[derSz + 1] = ED25519_PUB_KEY_SIZE; + XMEMSET(copy + derSz + 2, 0xDD, ED25519_PUB_KEY_SIZE); + algId = 0; + ExpectIntLT(ToTraditional_ex(copy, + (word32)(derSz + (int)trailerSz), &algId), 0); + } + + /* v1 buffer (with publicKey) plus extra trailing garbage. */ + ExpectIntGT(derSz = wc_Ed25519KeyToDer(&key, der, sizeof(der)), 0); + if (EXPECT_SUCCESS() && (derSz > 0) && + ((size_t)derSz + 1 <= sizeof(copy))) { + XMEMCPY(copy, der, (size_t)derSz); + ExpectTrue(copy[1] < 0x80); + copy[1] = (byte)(copy[1] + 1); + copy[derSz] = 0x05; + algId = 0; + ExpectIntLT(ToTraditional_ex(copy, (word32)(derSz + 1), &algId), 0); + } + + wc_ed25519_free(&key); + wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} + +/* ML-DSA AlgorithmIdentifier has no parameters per FIPS 204. Verify + * ToTraditional_ex() rejects a PKCS#8 whose algoSeq carries trailing NULL + * or OBJECT_ID parameters. Template parser only (legacy is lenient). */ +int test_ToTraditional_ex_mldsa_bad_params(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS8) && defined(HAVE_DILITHIUM) && \ + defined(WOLFSSL_ASN_TEMPLATE) && \ + (defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN)) + /* ML-DSA-65 OID body: 2.16.840.1.101.3.4.3.18 */ + static const byte mldsaOid[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x03, 0x12 }; + /* Single-arc OID body, used only to occupy the OBJECT_ID slot. */ + static const byte extraOid[] = { 0x01 }; + byte der[64]; + byte copy[64]; + word32 sz; + word32 outerLenIdx; + word32 algId; + const word32 privKeySz = 4; + const byte privBody = 0xAA; + + /* Bad case, algoSeq = { OID, NULL } */ + sz = 0; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + outerLenIdx = sz; + der[sz++] = 0; /* outer length, filled in below */ + der[sz++] = ASN_INTEGER; + der[sz++] = 1; + der[sz++] = 0x00; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + der[sz++] = (byte)(sizeof(mldsaOid) + 2 + 2); + der[sz++] = ASN_OBJECT_ID; + der[sz++] = (byte)sizeof(mldsaOid); + XMEMCPY(der + sz, mldsaOid, sizeof(mldsaOid)); sz += sizeof(mldsaOid); + /* Disallowed, NULL parameter after the ML-DSA OID. */ + der[sz++] = ASN_TAG_NULL; + der[sz++] = 0; + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)(privKeySz + 2); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)privKeySz; + XMEMSET(der + sz, privBody, privKeySz); sz += privKeySz; + der[outerLenIdx] = (byte)(sz - outerLenIdx - 1); + + XMEMCPY(copy, der, sz); + algId = 0; + ExpectIntLT(ToTraditional_ex(copy, sz, &algId), 0); + + /* Bad case, algoSeq = { OID, OBJECT_ID } */ + sz = 0; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + outerLenIdx = sz; + der[sz++] = 0; + der[sz++] = ASN_INTEGER; + der[sz++] = 1; + der[sz++] = 0x00; + der[sz++] = ASN_SEQUENCE | ASN_CONSTRUCTED; + der[sz++] = (byte)(sizeof(mldsaOid) + 2 + sizeof(extraOid) + 2); + der[sz++] = ASN_OBJECT_ID; + der[sz++] = (byte)sizeof(mldsaOid); + XMEMCPY(der + sz, mldsaOid, sizeof(mldsaOid)); sz += sizeof(mldsaOid); + /* Disallowed, OBJECT_ID parameter after the ML-DSA OID. */ + der[sz++] = ASN_OBJECT_ID; + der[sz++] = (byte)sizeof(extraOid); + XMEMCPY(der + sz, extraOid, sizeof(extraOid)); sz += sizeof(extraOid); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)(privKeySz + 2); + der[sz++] = ASN_OCTET_STRING; + der[sz++] = (byte)privKeySz; + XMEMSET(der + sz, privBody, privKeySz); sz += privKeySz; + der[outerLenIdx] = (byte)(sz - outerLenIdx - 1); + + XMEMCPY(copy, der, sz); + algId = 0; + ExpectIntLT(ToTraditional_ex(copy, sz, &algId), 0); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index 598c255f6b..a49fe0c481 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -34,6 +34,10 @@ int test_wc_DecodeRsaPssParams(void); int test_SerialNumber0_RootCA(void); int test_DecodeAltNames_length_underflow(void); int test_wc_DecodeObjectId(void); +int test_ToTraditional_ex_handcrafted(void); +int test_ToTraditional_ex_roundtrip(void); +int test_ToTraditional_ex_negative(void); +int test_ToTraditional_ex_mldsa_bad_params(void); #define TEST_ASN_DECLS \ TEST_DECL_GROUP("asn", test_SetAsymKeyDer), \ @@ -45,6 +49,10 @@ int test_wc_DecodeObjectId(void); TEST_DECL_GROUP("asn", test_wc_DecodeRsaPssParams), \ TEST_DECL_GROUP("asn", test_SerialNumber0_RootCA), \ TEST_DECL_GROUP("asn", test_DecodeAltNames_length_underflow), \ - TEST_DECL_GROUP("asn", test_wc_DecodeObjectId) + TEST_DECL_GROUP("asn", test_wc_DecodeObjectId), \ + TEST_DECL_GROUP("asn", test_ToTraditional_ex_handcrafted), \ + TEST_DECL_GROUP("asn", test_ToTraditional_ex_roundtrip), \ + TEST_DECL_GROUP("asn", test_ToTraditional_ex_negative), \ + TEST_DECL_GROUP("asn", test_ToTraditional_ex_mldsa_bad_params) #endif /* WOLFCRYPT_TEST_ASN_H */ diff --git a/tests/api/test_ossl_x509_crypto.c b/tests/api/test_ossl_x509_crypto.c index 01f5735480..8b0fb74ba8 100644 --- a/tests/api/test_ossl_x509_crypto.c +++ b/tests/api/test_ossl_x509_crypto.c @@ -71,6 +71,108 @@ int test_wolfSSL_X509_check_private_key(void) return EXPECT_RESULT(); } +/* EVP_PKCS82PKEY() must populate pkey.ptr/pkey_sz for ML-DSA so + * X509_check_private_key() (wc_CheckPrivateKey) can redecode the DER, and + * d2i_PKCS8_PKEY() must keep the full PKCS#8 wrapper for ML-DSA level recovery + * from the AlgorithmIdentifier. */ +int test_wolfSSL_X509_check_private_key_mldsa(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && \ + !defined(NO_BIO) && !defined(NO_CHECK_PRIVATE_KEY) && \ + defined(HAVE_DILITHIUM) && !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \ + !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \ + (defined(OPENSSL_ALL) || defined(WOLFSSL_WPAS_SMALL)) && \ + (!defined(WOLFSSL_NO_ML_DSA_44) || !defined(WOLFSSL_NO_ML_DSA_65) || \ + !defined(WOLFSSL_NO_ML_DSA_87)) + static const struct { + const char* keyPath; + const char* certPath; + const char* mismatchCertPath; /* NULL if no other level available */ + } cases[] = { + #if !defined(WOLFSSL_NO_ML_DSA_44) + { "./certs/mldsa/mldsa44-key.pem", + "./certs/mldsa/mldsa44-cert.der", + #if !defined(WOLFSSL_NO_ML_DSA_65) + "./certs/mldsa/mldsa65-cert.der" + #elif !defined(WOLFSSL_NO_ML_DSA_87) + "./certs/mldsa/mldsa87-cert.der" + #else + NULL + #endif + }, + #endif + #if !defined(WOLFSSL_NO_ML_DSA_65) + { "./certs/mldsa/mldsa65-key.pem", + "./certs/mldsa/mldsa65-cert.der", + #if !defined(WOLFSSL_NO_ML_DSA_87) + "./certs/mldsa/mldsa87-cert.der" + #elif !defined(WOLFSSL_NO_ML_DSA_44) + "./certs/mldsa/mldsa44-cert.der" + #else + NULL + #endif + }, + #endif + #if !defined(WOLFSSL_NO_ML_DSA_87) + { "./certs/mldsa/mldsa87-key.pem", + "./certs/mldsa/mldsa87-cert.der", + #if !defined(WOLFSSL_NO_ML_DSA_44) + "./certs/mldsa/mldsa44-cert.der" + #elif !defined(WOLFSSL_NO_ML_DSA_65) + "./certs/mldsa/mldsa65-cert.der" + #else + NULL + #endif + }, + #endif + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + PKCS8_PRIV_KEY_INFO* pt = NULL; + EVP_PKEY* pkey = NULL; + X509* x509 = NULL; + X509* mismatchX509 = NULL; + BIO* bio = NULL; + byte* buf = NULL; + size_t sz = 0; + + ExpectIntEQ(load_file(cases[i].keyPath, &buf, &sz), 0); + + ExpectNotNull(bio = BIO_new_mem_buf((void*)buf, (int)sz)); + ExpectNotNull(pt = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL)); + + ExpectNotNull(pkey = EVP_PKCS82PKEY(pt)); + if (pkey != NULL) { + ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM); + /* pkey.ptr must hold the DER so that X509_check_private_key() to + * wc_CheckPrivateKey() can re-decode it. */ + ExpectNotNull(pkey->pkey.ptr); + ExpectIntGT(pkey->pkey_sz, 0); + } + + ExpectNotNull(x509 = X509_load_certificate_file( + cases[i].certPath, SSL_FILETYPE_ASN1)); + ExpectIntEQ(X509_check_private_key(x509, pkey), 1); + + if (cases[i].mismatchCertPath != NULL) { + ExpectNotNull(mismatchX509 = X509_load_certificate_file( + cases[i].mismatchCertPath, SSL_FILETYPE_ASN1)); + ExpectIntEQ(X509_check_private_key(mismatchX509, pkey), 0); + } + + X509_free(mismatchX509); + X509_free(x509); + EVP_PKEY_free(pkey); + PKCS8_PRIV_KEY_INFO_free(pt); + BIO_free(bio); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_X509_verify(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_x509_crypto.h b/tests/api/test_ossl_x509_crypto.h index fe09acf6d1..cf40bca9a3 100644 --- a/tests/api/test_ossl_x509_crypto.h +++ b/tests/api/test_ossl_x509_crypto.h @@ -25,6 +25,7 @@ #include int test_wolfSSL_X509_check_private_key(void); +int test_wolfSSL_X509_check_private_key_mldsa(void); int test_wolfSSL_X509_verify(void); int test_wolfSSL_X509_sign(void); int test_wolfSSL_X509_sign2(void); @@ -32,6 +33,8 @@ int test_wolfSSL_make_cert(void); #define TEST_OSSL_X509_CRYPTO_DECLS \ TEST_DECL_GROUP("ossl_x509_crypto", test_wolfSSL_X509_check_private_key), \ + TEST_DECL_GROUP("ossl_x509_crypto", \ + test_wolfSSL_X509_check_private_key_mldsa), \ TEST_DECL_GROUP("ossl_x509_crypto", test_wolfSSL_X509_verify), \ TEST_DECL_GROUP("ossl_x509_crypto", test_wolfSSL_X509_sign), \ TEST_DECL_GROUP("ossl_x509_crypto", test_wolfSSL_X509_sign2), \ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 1d9882870f..8e4f01207a 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -8738,9 +8738,8 @@ int wc_RsaPrivateKeyValidate(const byte* input, word32* inOutIdx, int* keySz, #endif /* NO_RSA */ #ifdef WOLFSSL_ASN_TEMPLATE -/* ASN.1 template for a PKCS #8 key. - * Ignoring optional attributes and public key. - * PKCS #8: RFC 5958, 2 - PrivateKeyInfo +/* ASN.1 template for a PKCS #8 PrivateKeyInfo / RFC 5958 OneAsymmetricKey. + * Includes the optional [0] attributes and [1] publicKey trailing fields. */ static const ASNItem pkcs8KeyASN[] = { /* SEQ */ { 0, ASN_SEQUENCE, 1, 1, 0 }, @@ -8753,9 +8752,10 @@ static const ASNItem pkcs8KeyASN[] = { /* PKEY_ALGO_PARAM_SEQ */ { 2, ASN_SEQUENCE, 1, 0, 1 }, #endif /* PKEY_DATA */ { 1, ASN_OCTET_STRING, 0, 0, 0 }, -/* OPTIONAL Attributes IMPLICIT [0] */ +/* Attributes [0] OPTIONAL */ { 1, ASN_CONTEXT_SPECIFIC | 0, 1, 0, 1 }, -/* [[2: publicKey [1] PublicKey OPTIONAL ]] */ +/* publicKey [1] OPTIONAL */ + { 1, ASN_CONTEXT_SPECIFIC | 1, 0, 0, 1 }, }; enum { PKCS8KEYASN_IDX_SEQ = 0, @@ -8769,6 +8769,7 @@ enum { #endif PKCS8KEYASN_IDX_PKEY_DATA, PKCS8KEYASN_IDX_PKEY_ATTRIBUTES, + PKCS8KEYASN_IDX_PKEY_PUBKEY, WOLF_ENUM_DUMMY_LAST_ELEMENT(PKCS8KEYASN_IDX) }; @@ -8828,13 +8829,15 @@ int ToTraditionalInline_ex2(const byte* input, word32* inOutIdx, word32 sz, /* Key type OID. */ oid = dataASN[PKCS8KEYASN_IDX_PKEY_ALGO_OID_KEY].data.oid.sum; - /* Version 1 includes an optional public key. - * If public key is included then the parsing will fail as it did not - * use all the data. - */ + /* Only v1(0) and v2(1) are supported (RFC 5958). The [1] publicKey + * trailer is permitted only when version == v1. */ if (version > PKCS8v1) { ret = ASN_PARSE_E; } + else if ((version < PKCS8v1) && + (dataASN[PKCS8KEYASN_IDX_PKEY_PUBKEY].tag != 0)) { + ret = ASN_PARSE_E; + } } if (ret == 0) { switch (oid) { @@ -8930,9 +8933,71 @@ int ToTraditionalInline_ex2(const byte* input, word32* inOutIdx, word32 sz, } break; #endif - /* DSAk not supported. */ - /* Falcon, Dilithium and SLH-DSA not supported. */ - /* Ignore OID lookup failures. */ + #ifdef HAVE_FALCON + case FALCON_LEVEL1k: + case FALCON_LEVEL5k: + /* 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 + #ifdef HAVE_DILITHIUM + #ifdef WOLFSSL_DILITHIUM_FIPS204_DRAFT + case DILITHIUM_LEVEL2k: + case DILITHIUM_LEVEL3k: + case DILITHIUM_LEVEL5k: + #endif + case ML_DSA_LEVEL2k: + case ML_DSA_LEVEL3k: + case ML_DSA_LEVEL5k: + /* 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 + #ifdef WOLFSSL_HAVE_SLHDSA + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + /* 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 + #ifdef WOLFSSL_HAVE_LMS + case HSS_LMSk: + /* 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 + #ifdef WOLFSSL_HAVE_XMSS + case XMSSk: + /* 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: break; } @@ -9032,9 +9097,9 @@ int wc_GetPkcs8TraditionalOffset(byte* input, word32* inOutIdx, word32 sz) int wc_CreatePKCS8Key(byte* out, word32* outSz, byte* key, word32 keySz, int algoID, const byte* curveOID, word32 oidSz) { - /* pkcs8KeyASN_Length-1, the -1 is because we are not adding the optional - * set of attributes */ - DECL_ASNSETDATA(dataASN, pkcs8KeyASN_Length-1); + /* pkcs8KeyASN_Length-2, the -2 is because we are not adding the optional + * set of attributes or publicKey */ + DECL_ASNSETDATA(dataASN, pkcs8KeyASN_Length-2); word32 sz = 0; int ret = 0; word32 keyIdx = 0; @@ -9061,7 +9126,7 @@ int wc_CreatePKCS8Key(byte* out, word32* outSz, byte* key, word32 keySz, #endif if (ret == 0) - CALLOC_ASNSETDATA(dataASN, pkcs8KeyASN_Length-1, ret, NULL); + CALLOC_ASNSETDATA(dataASN, pkcs8KeyASN_Length-2, ret, NULL); if (ret == 0) { /* Only support default PKCS #8 format - v0. */ @@ -9087,7 +9152,7 @@ int wc_CreatePKCS8Key(byte* out, word32* outSz, byte* key, word32 keySz, SetASN_Buffer(&dataASN[PKCS8KEYASN_IDX_PKEY_DATA], key, keySz); /* Get the size of the DER encoding. */ - ret = SizeASN_Items(pkcs8KeyASN, dataASN, pkcs8KeyASN_Length-1, &sz); + ret = SizeASN_Items(pkcs8KeyASN, dataASN, pkcs8KeyASN_Length-2, &sz); } if ((ret == 0) || (ret == WC_NO_ERR_TRACE(LENGTH_ONLY_E))) { /* Always return the calculated size. */ @@ -9100,7 +9165,7 @@ int wc_CreatePKCS8Key(byte* out, word32* outSz, byte* key, word32 keySz, } if (ret == 0) { /* Encode PKCS #8 key into buffer. */ - SetASN_Items(pkcs8KeyASN, dataASN, pkcs8KeyASN_Length-1, out); + SetASN_Items(pkcs8KeyASN, dataASN, pkcs8KeyASN_Length-2, out); ret = (int)sz; } diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index b3fc0c4d17..6851f1cd70 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -942,7 +942,8 @@ static int d2iTryDilithiumKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, return WOLFSSL_FATAL_ERROR; } - return d2i_make_pkey(out, NULL, 0, priv, WC_EVP_PKEY_DILITHIUM); + /* Copy the consumed DER into pkey->pkey.ptr when the input was DER */ + return d2i_make_pkey(out, mem, keyIdx, priv, WC_EVP_PKEY_DILITHIUM); } #endif /* HAVE_DILITHIUM */ @@ -1696,6 +1697,10 @@ WOLFSSL_PKCS8_PRIV_KEY_INFO* wolfSSL_d2i_PKCS8_PKEY( DerBuffer rawDer; EncryptedInfo info; int advanceLen = 0; +#ifdef HAVE_DILITHIUM + word32 outerIdx = 0; + int outerLen = 0; +#endif /* Clear the encryption information and DER buffer. */ XMEMSET(&info, 0, sizeof(info)); @@ -1737,10 +1742,31 @@ WOLFSSL_PKCS8_PRIV_KEY_INFO* wolfSSL_d2i_PKCS8_PKEY( } if (algId == DHk) { /* Special case for DH as we expect the DER buffer to be always - * be in PKCS8 format */ + * in PKCS8 format */ rawDer.buffer = pkcs8Der->buffer; rawDer.length = inOutIdx + (word32)ret; } + #ifdef HAVE_DILITHIUM + else if ( + #ifdef WOLFSSL_DILITHIUM_FIPS204_DRAFT + (algId == DILITHIUM_LEVEL2k) || + (algId == DILITHIUM_LEVEL3k) || + (algId == DILITHIUM_LEVEL5k) || + #endif + (algId == ML_DSA_LEVEL2k) || + (algId == ML_DSA_LEVEL3k) || + (algId == ML_DSA_LEVEL5k)) { + + /* Keep full PKCS#8 wrapper for level recovery from + * AlgorithmIdentifier parameters */ + rawDer.buffer = pkcs8Der->buffer; + rawDer.length = inOutIdx + (word32)ret; + if (GetSequence(pkcs8Der->buffer, &outerIdx, &outerLen, + pkcs8Der->length) >= 0) { + rawDer.length = outerIdx + (word32)outerLen; + } + } + #endif else { rawDer.buffer = pkcs8Der->buffer + inOutIdx; rawDer.length = (word32)ret;