Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions doc/dox_comments/header_files/dsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
140 changes: 140 additions & 0 deletions tests/api/test_dsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

4 changes: 3 additions & 1 deletion tests/api/test_dsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -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), \
Expand All @@ -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 */
102 changes: 102 additions & 0 deletions wolfcrypt/src/dsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions wolfssl/wolfcrypt/dsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
kareem-wolfssl marked this conversation as resolved.
WOLFSSL_API int wc_DsaCheckPubKey(DsaKey* key);

#ifdef WOLFSSL_KEY_GEN
WOLFSSL_API int wc_MakeDsaKey(WC_RNG *rng, DsaKey *dsa);
Expand Down
Loading