From 0e6ac11d15156eacde72355fe3189ff4ccea98db Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:46:42 -0700 Subject: [PATCH] - Add ability to obtain Dilithium security level (parameters) from a DER encoded key based on the algorithm type OID - Add test coverage for decoding DER keys without level specified --- wolfcrypt/src/dilithium.c | 135 ++++++++++++++++++++++++++++++++++++-- wolfcrypt/test/test.c | 111 +++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 4 deletions(-) diff --git a/wolfcrypt/src/dilithium.c b/wolfcrypt/src/dilithium.c index ce01042c86..44f9e7972a 100644 --- a/wolfcrypt/src/dilithium.c +++ b/wolfcrypt/src/dilithium.c @@ -9501,6 +9501,82 @@ int wc_dilithium_export_key(dilithium_key* key, byte* priv, word32 *privSz, #ifndef WOLFSSL_DILITHIUM_NO_ASN1 +/* Maps ASN.1 OID to wolfCrypt security level macros */ +static int mapOidToSecLevel(word32 oid) +{ + switch (oid) { + case ML_DSA_LEVEL2k: + return WC_ML_DSA_44; + case ML_DSA_LEVEL3k: + return WC_ML_DSA_65; + case ML_DSA_LEVEL5k: + return WC_ML_DSA_87; +#ifdef WOLFSSL_DILITHIUM_FIPS204_DRAFT + case DILITHIUM_LEVEL2k: + return WC_ML_DSA_44_DRAFT; + case DILITHIUM_LEVEL3k: + return WC_ML_DSA_65_DRAFT; + case DILITHIUM_LEVEL5k: + return WC_ML_DSA_87_DRAFT; +#endif + default: + return ASN_UNKNOWN_OID_E; + } +} + +/* Get security level from DER encoded key. Returns a positive security level + * (e.g. WC_ML_DSA_44, etc.) on success, or a negative value on error. + * + * Expected ASN.1 Structure: + * + * SEQUENCE { -- Outer wrapper + * version INTEGER, -- Version number (usually 0) + * algorithm SEQUENCE { -- AlgorithmIdentifier + * algorithm OBJECT IDENTIFIER -- OID identifying Dilithium variant + * } + * -- Note: Remaining key data after algorithm is ignored for this function + * } + */ +static int getSecLevelFromDer(const byte* der, word32 derSz) +{ + word32 idx = 0; + int seqSz, algSeqSz; + int ret; + word32 oid = 0; + int version; + + if (der == NULL || derSz == 0) { + return BAD_FUNC_ARG; + } + + /* Parse outer SEQUENCE wrapper and get its size + * Advances idx past the SEQUENCE header */ + if ((ret = GetSequence(der, &idx, &seqSz, derSz)) < 0) { + return ret; + } + + /* Parse and skip over version INTEGER + * Advances idx past the version field */ + if ((ret = GetMyVersion(der, &idx, &version, derSz)) < 0) { + return ret; + } + + /* Parse AlgorithmIdentifier SEQUENCE and get its size + * Advances idx past the SEQUENCE header */ + if ((ret = GetSequence(der, &idx, &algSeqSz, derSz)) < 0) { + return ret; + } + + /* Parse OID value from AlgorithmIdentifier + * Advances idx past the OID, stores numeric OID value + * oidSigType indicates this is a signature algorithm OID */ + if ((ret = GetObjectId(der, &idx, &oid, oidSigType, derSz - idx)) < 0) { + return ret; + } + + return mapOidToSecLevel(oid); +} + #if defined(WOLFSSL_DILITHIUM_PRIVATE_KEY) /* Decode the DER encoded Dilithium key. @@ -9508,11 +9584,18 @@ int wc_dilithium_export_key(dilithium_key* key, byte* priv, word32 *privSz, * @param [in] input Array holding DER encoded data. * @param [in, out] inOutIdx On in, index into array of start of DER encoding. * On out, index into array after DER encoding. - * @param [in, out] key Dilithium key to store key. - * @param [in] inSz Total size of data in array. + * @param [in, out] key Dilithium key structure to hold the decoded key. + * If the security level is set in the key structure on + * input, the DER key will be decoded as such and will + * fail if there is a mismatch. If the level and + * parameters are not set in the key structure on + * input, the level will be detected from the DER + * file based on the algorithm OID, appropriately + * decoded, then updated in the key structure on + * output. + * @param [in] inSz Total size of the input DER buffer array. * @return 0 on success. * @return BAD_FUNC_ARG when input, inOutIdx or key is NULL or inSz is 0. - * @return BAD_FUNC_ARG when level not set. * @return Other negative on parse error. */ int wc_Dilithium_PrivateKeyDecode(const byte* input, word32* inOutIdx, @@ -9530,6 +9613,23 @@ int wc_Dilithium_PrivateKeyDecode(const byte* input, word32* inOutIdx, ret = BAD_FUNC_ARG; } + /* If expected security level not set in key, detect it from DER */ + if (key->level == 0 || key->params == NULL +#ifdef WOLFSSL_DILITHIUM_FIPS204_DRAFT + || key->params->level == 0) +#endif + ) { + int level; + level = getSecLevelFromDer(input + *inOutIdx, inSz - *inOutIdx); + if (level < 0) { + ret = level; + } + else { + /* Set params based on level parsed from DER*/ + ret = wc_dilithium_set_level(key, level); + } + } + if (ret == 0) { /* Get OID sum for level. */ #if defined(WOLFSSL_DILITHIUM_FIPS204_DRAFT) @@ -9756,7 +9856,15 @@ static int dilithium_check_type(const byte* input, word32* inOutIdx, byte type, * @param [in] input Array holding DER encoded data. * @param [in, out] inOutIdx On in, index into array of start of DER encoding. * On out, index into array after DER encoding. - * @param [in, out] key Dilithium key to store key. + * @param [in, out] key Dilithium key structure to hold the decoded key. + * If the security level is set in the key structure + * on input, the DER key will be decoded as such + * and will fail if there is a mismatch. If the level + * and parameters are not set in the key structure on + * input, the level will be detected from the DER file + * based on the algorithm OID, appropriately decoded, + * then updated in the key structure on output. + * updated in the key structure on output. * @param [in] inSz Total size of data in array. * @return 0 on success. * @return BAD_FUNC_ARG when input, inOutIdx or key is NULL or inSz is 0. @@ -9775,6 +9883,25 @@ int wc_Dilithium_PublicKeyDecode(const byte* input, word32* inOutIdx, ret = BAD_FUNC_ARG; } +#if !defined(WOLFSSL_DILITHIUM_NO_ASN1) + /* If expected security level not set in key, detect it from DER */ + if (key->level == 0 || key->params == NULL +#ifdef WOLFSSL_DILITHIUM_FIPS204_DRAFT + || key->params->level == 0) +#endif + ) { + int level; + level = getSecLevelFromDer(input + *inOutIdx, inSz - *inOutIdx); + if (level < 0) { + ret = level; + } + else { + /* Set params based on level parsed from DER*/ + ret = wc_dilithium_set_level(key, level); + } + } +#endif /* !WOLFSSL_DILITHIUM_NO_ASN1 */ + if (ret == 0) { /* Try to import the key directly. */ ret = wc_dilithium_import_public(input, inSz, key); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 10dabe7960..f105641ea3 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -45669,6 +45669,112 @@ static wc_test_ret_t dilithium_param_test(int param, WC_RNG* rng) } #endif +/* Tests decoding a key from DER without the security level specified */ +static wc_test_ret_t test_dilithium_decode_level(const byte* rawKey, + word32 rawKeySz, + int expectedLevel) +{ + int ret; + dilithium_key key; + word32 idx; + byte* der; + word32 derSz; + + /* DER encoding adds ~256 bytes of overhead to raw key */ + const word32 estimatedDerSz = rawKeySz + 256; + + /* Allocate DER buffer */ + der = (byte*)XMALLOC(estimatedDerSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (der == NULL) { + return MEMORY_E; + } + + /* Initialize key */ + ret = wc_dilithium_init(&key); + + /* Import raw key, setting the security level */ + if (ret == 0) { + ret = wc_dilithium_set_level(&key, expectedLevel); + } + if (ret == 0) { + ret = wc_dilithium_import_private(rawKey, rawKeySz, &key); + } + + /* Export raw key as DER */ + if (ret == 0) { + ret = wc_Dilithium_PrivateKeyToDer(&key, der, estimatedDerSz); + if (ret >= 0) { + derSz = ret; + ret = 0; + } + } + + /* Free and reinit key to test fresh decode */ + if (ret == 0) { + wc_dilithium_free(&key); + ret = wc_dilithium_init(&key); + } + + /* Test decoding without setting security level - should auto-detect */ + if (ret == 0) { + idx = 0; + ret = wc_Dilithium_PrivateKeyDecode(der, &idx, &key, derSz); + } + + /* Verify detected security level */ + if (ret == 0 && key.level != expectedLevel) { + printf("Dilithium key decode failed to detect level.\n" + "\tExpected level=%d\n\tGot level=%d\n", + expectedLevel, key.level); + ret = WC_TEST_RET_ENC_NC; + } + + /* Cleanup */ + XFREE(der, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_dilithium_free(&key); + return ret; +} + +/* Test Dilithium private key decoding and security level detection */ +static wc_test_ret_t dilithium_decode_test(void) +{ + wc_test_ret_t ret; + const byte* privKey; + word32 privKeySz; + +#ifndef WOLFSSL_NO_ML_DSA_44 + /* Test ML-DSA-44 */ + privKey = bench_dilithium_level2_key; + privKeySz = sizeof_bench_dilithium_level2_key; + ret = test_dilithium_decode_level(privKey, privKeySz, WC_ML_DSA_44); + if (ret != 0) { + return ret; + } +#endif /* WOLFSSL_NO_ML_DSA_44 */ + +#ifndef WOLFSSL_NO_ML_DSA_65 + /* Test ML-DSA-65 */ + privKey = bench_dilithium_level3_key; + privKeySz = sizeof_bench_dilithium_level3_key; + ret = test_dilithium_decode_level(privKey, privKeySz, WC_ML_DSA_65); + if (ret != 0) { + return ret; + } +#endif /* WOLFSSL_NO_ML_DSA_65 */ + +#ifndef WOLFSSL_NO_ML_DSA_87 + /* Test ML-DSA-87 */ + privKey = bench_dilithium_level5_key; + privKeySz = sizeof_bench_dilithium_level5_key; + ret = test_dilithium_decode_level(privKey, privKeySz, WC_ML_DSA_87); + if (ret != 0) { + return ret; + } +#endif /* WOLFSSL_NO_ML_DSA_87 */ + + return ret; +} + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t dilithium_test(void) { wc_test_ret_t ret; @@ -45727,6 +45833,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t dilithium_test(void) #endif #endif + ret = dilithium_decode_test(); + if (ret != 0) { + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + } + #if !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) || \ !defined(WOLFSSL_DILITHIUM_NO_VERIFY) out: