diff --git a/doc/dox_comments/header_files/dsa.h b/doc/dox_comments/header_files/dsa.h index f6020026588..c93a25b8416 100644 --- a/doc/dox_comments/header_files/dsa.h +++ b/doc/dox_comments/header_files/dsa.h @@ -162,6 +162,56 @@ int wc_DsaSign(const byte* digest, byte* out, int wc_DsaVerify(const byte* digest, const byte* sig, DsaKey* key, int* answer); +/*! + \ingroup DSA + + \brief This function validates DSA domain parameters and the public + key contained in the given DsaKey. It performs the following checks + (a subset of the requirements in FIPS 186-4 and NIST SP 800-89): + p > 1 and q > 1; q divides (p - 1); 1 < g < p; 1 < y < p; g^q mod p + == 1 (so the multiplicative order of g divides q); and y^q mod p == 1 + (so the multiplicative order of y divides q). The verify + routines (wc_DsaVerify, wc_DsaVerify_ex) call this internally before + any signature math runs, but the function is also exposed so callers + can validate keys at import or decode time. + + \return 0 Returned when the key and domain parameters pass validation. + \return BAD_FUNC_ARG Returned when key is NULL, or when the key fails + any of the validation checks listed above. + \return MEMORY_E Returned on memory allocation failure (small-stack + builds only). + \return MP_INIT_E Returned when mp_int initialization fails. + \return Other negative MP error codes (e.g. MP_EXPTMOD_E) Returned on + internal big-integer failures. + + Note: this function does not run primality tests on p or q. Full + FIPS 186-4 domain-parameter validation additionally requires that p + and q be prime; callers that need that level of assurance should use + wc_DsaImportParamsRawCheck() (which validates p) and/or run + mp_prime_is_prime_ex() on q at import time. + + \param key pointer to a DsaKey structure populated with p, q, g, and y + + _Example_ + \code + DsaKey key; + int ret; + wc_InitDsaKey(&key); + // ... import or decode the public key into &key ... + ret = wc_DsaCheckPubKey(&key); + if (ret != 0) { + // domain parameters or public key are invalid; reject + } + wc_FreeDsaKey(&key); + \endcode + + \sa wc_DsaVerify + \sa wc_DsaPublicKeyDecode + \sa wc_DsaImportParamsRaw + \sa wc_DsaImportParamsRawCheck +*/ +int wc_DsaCheckPubKey(DsaKey* key); + /*! \ingroup DSA diff --git a/tests/api/test_dsa.c b/tests/api/test_dsa.c index 4605d48abbf..230dea65f49 100644 --- a/tests/api/test_dsa.c +++ b/tests/api/test_dsa.c @@ -578,3 +578,143 @@ int test_wc_DsaExportKeyRaw(void) return EXPECT_RESULT(); } /* END test_wc_DsaExportParamsRaw */ + +/* + * Testing wc_DsaCheckPubKey() and DSA verify rejecting malformed public + * keys / domain parameters (e.g. g = 1, y = 1 forgery class). + * + * Requires WOLFSSL_PUBLIC_MP so the test can manipulate mp_int fields + * directly to construct malformed keys without going through the (already + * partially validating) import paths. + */ +int test_wc_DsaCheckPubKey(void) +{ + EXPECT_DECLS; +#if !defined(NO_DSA) && !defined(WC_FIPS_186_5_PLUS) && \ + !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) && defined(WOLFSSL_PUBLIC_MP) + DsaKey key; + int answer = -1; + int ret; + /* Well-formed FIPS 186-4 [L=1024, N=160] domain parameters. + * Same vector as used by test_wc_DsaImportParamsRaw above. */ + const char* p = + "d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d" + "4b725ef341eabb47cf8a7a8a41e792a156b7ce97206c4f9c" + "5ce6fc5ae7912102b6b502e59050b5b21ce263dddb2044b6" + "52236f4d42ab4b5d6aa73189cef1ace778d7845a5c1c1c71" + "47123188f8dc551054ee162b634d60f097f719076640e209" + "80a0093113a8bd73"; + const char* q = "96c5390a8b612c0e422bb2b0ea194a3ec935a281"; + const char* g = + "06b7861abbd35cc89e79c52f68d20875389b127361ca66822" + "138ce4991d2b862259d6b4548a6495b195aa0e0b6137ca37e" + "b23b94074d3c3d300042bdf15762812b6333ef7b07ceba786" + "07610fcc9ee68491dbc1e34cd12615474e52b18bc934fb00c" + "61d39e7da8902291c4434a4e2224c3f4fd9f93cd6f4f17fc0" + "76341a7e7d9"; + /* For verify: a SHA-1-sized digest (any value) — without the fix the + * forgery (r=1, s=1) verifies for ANY digest. */ + byte digest[WC_SHA_DIGEST_SIZE]; + /* signature is r || s, each q-sized (20 bytes for 160-bit q). */ + byte sig[2 * 20]; + + XMEMSET(&key, 0, sizeof(DsaKey)); + XMEMSET(digest, 0xAA, sizeof(digest)); + + ExpectIntEQ(wc_InitDsaKey(&key), 0); + + /* --- Bad-arg coverage. --- */ + ExpectIntEQ(wc_DsaCheckPubKey(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Load good (p, q, g). */ + ExpectIntEQ(wc_DsaImportParamsRaw(&key, p, q, g), 0); + /* Compute a well-formed y = g^x mod p using x = 2 so the baseline + * passes wc_DsaCheckPubKey. */ + ExpectIntEQ(mp_set(&key.x, 2), 0); + ExpectIntEQ(mp_exptmod(&key.g, &key.x, &key.p, &key.y), 0); + key.type = DSA_PUBLIC; + /* Sanity: a well-formed key should pass validation. */ + ExpectIntEQ(wc_DsaCheckPubKey(&key), 0); + + /* Now set g = 1, y = 1, sig = (1, 1). + This should fail validation. */ + ExpectIntEQ(mp_set(&key.g, 1), 0); + ExpectIntEQ(mp_set(&key.y, 1), 0); + XMEMSET(sig, 0, sizeof(sig)); + sig[19] = 0x01; /* r = 1 */ + sig[39] = 0x01; /* s = 1 */ + answer = -1; + ret = wc_DsaVerify(digest, sig, &key, &answer); + ExpectIntEQ(ret, WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntNE(answer, 1); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* g out of range: g = 0 */ + ExpectIntEQ(mp_set(&key.g, 0), 0); + /* restore a valid y for the remaining checks */ + ExpectIntEQ(mp_read_radix(&key.g, g, MP_RADIX_HEX), 0); + ExpectIntEQ(mp_exptmod(&key.g, &key.x, &key.p, &key.y), 0); + ExpectIntEQ(mp_set(&key.g, 0), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* g out of range: g = 1 */ + ExpectIntEQ(mp_set(&key.g, 1), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* g = p (>= p) */ + ExpectIntEQ(mp_copy(&key.p, &key.g), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* g in range [2, p-1] but ord(g) does not divide q. + * For our FIPS-style p, 2 has order (p-1)/k for small k and (p-1)/q + * is not 1, so 2^q mod p != 1 and the check rejects. */ + ExpectIntEQ(mp_set(&key.g, 2), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* y out of range: restore good g and a valid y between cases. */ + ExpectIntEQ(mp_read_radix(&key.g, g, MP_RADIX_HEX), 0); + ExpectIntEQ(mp_exptmod(&key.g, &key.x, &key.p, &key.y), 0); + /* Confirm the restoration produced a valid key. */ + ExpectIntEQ(wc_DsaCheckPubKey(&key), 0); + + /* y = 0 */ + ExpectIntEQ(mp_set(&key.y, 0), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* y = 1 */ + ExpectIntEQ(mp_set(&key.y, 1), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* y = p */ + ExpectIntEQ(mp_copy(&key.p, &key.y), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* y in range but ord(y) does not divide q: y = 2 fails y^q mod p == 1. */ + ExpectIntEQ(mp_set(&key.y, 2), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* q does not divide (p - 1). Replace q with (p - 2). This is plain + * integer arithmetic (no primality assumption on p): for any integer + * p > 3, p - 1 = 1 * (p - 2) + 1, so (p - 1) mod (p - 2) = 1, which + * is deterministically non-zero. q' = p-2 is also > 1 and is not + * compared against p in DsaCheckPubKey, so the divisibility check + * is the only one that fires. */ + ExpectIntEQ(mp_exptmod(&key.g, &key.x, &key.p, &key.y), 0); + ExpectIntEQ(mp_copy(&key.p, &key.q), 0); /* q = p */ + ExpectIntEQ(mp_sub_d(&key.q, 2, &key.q), 0); /* q = p-2 */ + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* Restore the original q for any subsequent checks. */ + ExpectIntEQ(mp_read_radix(&key.q, q, MP_RADIX_HEX), 0); + + /* p, q sanity floors: p = 1 or q = 1 must be rejected. */ + ExpectIntEQ(mp_set(&key.p, 1), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(mp_read_radix(&key.p, p, MP_RADIX_HEX), 0); + ExpectIntEQ(mp_set(&key.q, 1), 0); + ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + wc_FreeDsaKey(&key); +#endif + return EXPECT_RESULT(); +} /* END test_wc_DsaCheckPubKey */ + diff --git a/tests/api/test_dsa.h b/tests/api/test_dsa.h index 167dc081727..7d6dfadf7bf 100644 --- a/tests/api/test_dsa.h +++ b/tests/api/test_dsa.h @@ -34,6 +34,7 @@ int test_wc_DsaImportParamsRaw(void); int test_wc_DsaImportParamsRawCheck(void); int test_wc_DsaExportParamsRaw(void); int test_wc_DsaExportKeyRaw(void); +int test_wc_DsaCheckPubKey(void); #define TEST_DSA_DECLS \ TEST_DECL_GROUP("dsa", test_wc_InitDsaKey), \ @@ -45,6 +46,7 @@ int test_wc_DsaExportKeyRaw(void); TEST_DECL_GROUP("dsa", test_wc_DsaImportParamsRaw), \ TEST_DECL_GROUP("dsa", test_wc_DsaImportParamsRawCheck), \ TEST_DECL_GROUP("dsa", test_wc_DsaExportParamsRaw), \ - TEST_DECL_GROUP("dsa", test_wc_DsaExportKeyRaw) + TEST_DECL_GROUP("dsa", test_wc_DsaExportKeyRaw), \ + TEST_DECL_GROUP("dsa", test_wc_DsaCheckPubKey) #endif /* WOLFCRYPT_TEST_DSA_H */ diff --git a/wolfcrypt/src/dsa.c b/wolfcrypt/src/dsa.c index e881f8470a9..32d1f68c0bd 100644 --- a/wolfcrypt/src/dsa.c +++ b/wolfcrypt/src/dsa.c @@ -94,6 +94,102 @@ void wc_FreeDsaKey(DsaKey* key) } +/* Validate DSA domain parameters and public key. + * + * Performs the following checks (subset of FIPS 186-4 / SP 800-89): + * - p > 1 and q > 1 + * - q divides (p - 1) (FIPS 186-4 A.1.1.2) + * - 1 < g < p + * - 1 < y < p + * - g^q mod p == 1 (i.e. ord(g) divides q) + * - y^q mod p == 1 (i.e. ord(y) divides q) + * + * Note: this routine does not run primality tests on p or q. Full FIPS + * 186-4 domain-parameter validation additionally requires that p and q be + * prime; callers that need that level of assurance should use + * wc_DsaImportParamsRawCheck() (which exercises p) and/or run + * mp_prime_is_prime_ex() on q at import time. + * + * key - pointer to DsaKey populated with p, q, g, and y. + * return 0 on success, BAD_FUNC_ARG when the key fails validation, or a + * negative error code on internal failure. + */ +int wc_DsaCheckPubKey(DsaKey* key) +{ + int err = MP_OKAY; +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + mp_int* tmp = NULL; + mp_int* tmp2 = NULL; +#else + mp_int tmp[1]; + mp_int tmp2[1]; +#endif + + if (key == NULL) + return BAD_FUNC_ARG; + + /* p and q must be at least 2 */ + if (mp_cmp_d(&key->p, 1) != MP_GT || mp_cmp_d(&key->q, 1) != MP_GT) + return BAD_FUNC_ARG; + + /* 1 < g < p */ + if (mp_cmp_d(&key->g, 1) != MP_GT || mp_cmp(&key->g, &key->p) != MP_LT) + return BAD_FUNC_ARG; + + /* 1 < y < p */ + if (mp_cmp_d(&key->y, 1) != MP_GT || mp_cmp(&key->y, &key->p) != MP_LT) + return BAD_FUNC_ARG; + +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + tmp = (mp_int*)XMALLOC(sizeof(*tmp), key->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) + return MEMORY_E; + tmp2 = (mp_int*)XMALLOC(sizeof(*tmp2), key->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp2 == NULL) { + XFREE(tmp, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; + } +#endif + + err = mp_init_multi(tmp, tmp2, NULL, NULL, NULL, NULL); + if (err != MP_OKAY) { +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + XFREE(tmp2, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(tmp, key->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return err; + } + + /* q divides (p - 1): tmp2 = (p - 1) mod q, must be 0. */ + if (err == MP_OKAY) + err = mp_sub_d(&key->p, 1, tmp); + if (err == MP_OKAY) + err = mp_mod(tmp, &key->q, tmp2); + if (err == MP_OKAY && !mp_iszero(tmp2)) + err = BAD_FUNC_ARG; + + /* g^q mod p == 1 */ + if (err == MP_OKAY) + err = mp_exptmod(&key->g, &key->q, &key->p, tmp); + if (err == MP_OKAY && mp_cmp_d(tmp, 1) != MP_EQ) + err = BAD_FUNC_ARG; + + /* y^q mod p == 1 */ + if (err == MP_OKAY) + err = mp_exptmod(&key->y, &key->q, &key->p, tmp); + if (err == MP_OKAY && mp_cmp_d(tmp, 1) != MP_EQ) + err = BAD_FUNC_ARG; + + mp_clear(tmp); + mp_clear(tmp2); +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + XFREE(tmp2, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(tmp, key->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return err; +} + /* validate that (L,N) match allowed sizes from FIPS 186-4, Section 4.2. * modLen - represents L, the size of p (prime modulus) in bits * divLen - represents N, the size of q (prime divisor) in bits @@ -1039,6 +1135,12 @@ int wc_DsaVerify_ex(const byte* digest, word32 digestSz, const byte* sig, return BAD_LENGTH_E; } + /* Validate domain parameters and public key before doing any + * signature math. */ + ret = wc_DsaCheckPubKey(key); + if (ret != 0) + return ret; + do { #ifdef WOLFSSL_SMALL_STACK w = (mp_int *)XMALLOC(sizeof *w, key->heap, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfssl/wolfcrypt/dsa.h b/wolfssl/wolfcrypt/dsa.h index 49d76030c47..29711bbbde9 100644 --- a/wolfssl/wolfcrypt/dsa.h +++ b/wolfssl/wolfcrypt/dsa.h @@ -95,6 +95,7 @@ WOLFSSL_API int wc_DsaKeyToDer(DsaKey* key, byte* output, word32 inLen); WOLFSSL_API int wc_SetDsaPublicKey(byte* output, DsaKey* key, int outLen, int with_header); WOLFSSL_API int wc_DsaKeyToPublicDer(DsaKey* key, byte* output, word32 inLen); +WOLFSSL_API int wc_DsaCheckPubKey(DsaKey* key); #ifdef WOLFSSL_KEY_GEN WOLFSSL_API int wc_MakeDsaKey(WC_RNG *rng, DsaKey *dsa);