Skip to content

Commit e886d50

Browse files
Validate DSA parameters when verifying DSA key.
Thanks to Kr0emer for the report.
1 parent 980fc51 commit e886d50

4 files changed

Lines changed: 246 additions & 1 deletion

File tree

tests/api/test_dsa.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,3 +578,142 @@ int test_wc_DsaExportKeyRaw(void)
578578
return EXPECT_RESULT();
579579
} /* END test_wc_DsaExportParamsRaw */
580580

581+
582+
/*
583+
* Testing wc_DsaCheckPubKey() and DSA verify rejecting malformed public
584+
* keys / domain parameters (e.g. g = 1, y = 1 forgery class).
585+
*
586+
* Requires WOLFSSL_PUBLIC_MP so the test can manipulate mp_int fields
587+
* directly to construct malformed keys without going through the (already
588+
* partially validating) import paths.
589+
*/
590+
int test_wc_DsaCheckPubKey(void)
591+
{
592+
EXPECT_DECLS;
593+
#if !defined(NO_DSA) && !defined(WC_FIPS_186_5_PLUS) && \
594+
!defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) && defined(WOLFSSL_PUBLIC_MP)
595+
DsaKey key;
596+
int answer = -1;
597+
int ret;
598+
/* Well-formed FIPS 186-4 [L=1024, N=160] domain parameters.
599+
* Same vector as used by test_wc_DsaImportParamsRaw above. */
600+
const char* p =
601+
"d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d"
602+
"4b725ef341eabb47cf8a7a8a41e792a156b7ce97206c4f9c"
603+
"5ce6fc5ae7912102b6b502e59050b5b21ce263dddb2044b6"
604+
"52236f4d42ab4b5d6aa73189cef1ace778d7845a5c1c1c71"
605+
"47123188f8dc551054ee162b634d60f097f719076640e209"
606+
"80a0093113a8bd73";
607+
const char* q = "96c5390a8b612c0e422bb2b0ea194a3ec935a281";
608+
const char* g =
609+
"06b7861abbd35cc89e79c52f68d20875389b127361ca66822"
610+
"138ce4991d2b862259d6b4548a6495b195aa0e0b6137ca37e"
611+
"b23b94074d3c3d300042bdf15762812b6333ef7b07ceba786"
612+
"07610fcc9ee68491dbc1e34cd12615474e52b18bc934fb00c"
613+
"61d39e7da8902291c4434a4e2224c3f4fd9f93cd6f4f17fc0"
614+
"76341a7e7d9";
615+
/* For verify: a SHA-1-sized digest (any value) — without the fix the
616+
* forgery (r=1, s=1) verifies for ANY digest. */
617+
byte digest[WC_SHA_DIGEST_SIZE];
618+
/* signature is r || s, each q-sized (20 bytes for 160-bit q). */
619+
byte sig[2 * 20];
620+
621+
XMEMSET(&key, 0, sizeof(DsaKey));
622+
XMEMSET(digest, 0xAA, sizeof(digest));
623+
624+
ExpectIntEQ(wc_InitDsaKey(&key), 0);
625+
626+
/* --- Bad-arg coverage. --- */
627+
ExpectIntEQ(wc_DsaCheckPubKey(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
628+
629+
/* Load good (p, q, g). */
630+
ExpectIntEQ(wc_DsaImportParamsRaw(&key, p, q, g), 0);
631+
/* Compute a well-formed y = g^x mod p using x = 2 so the baseline
632+
* passes wc_DsaCheckPubKey. */
633+
ExpectIntEQ(mp_set(&key.x, 2), 0);
634+
ExpectIntEQ(mp_exptmod(&key.g, &key.x, &key.p, &key.y), 0);
635+
key.type = DSA_PUBLIC;
636+
/* Sanity: a well-formed key should pass validation. */
637+
ExpectIntEQ(wc_DsaCheckPubKey(&key), 0);
638+
639+
/* Now set g = 1, y = 1, sig = (1, 1).
640+
This should fail validation. */
641+
ExpectIntEQ(mp_set(&key.g, 1), 0);
642+
ExpectIntEQ(mp_set(&key.y, 1), 0);
643+
XMEMSET(sig, 0, sizeof(sig));
644+
sig[19] = 0x01; /* r = 1 */
645+
sig[39] = 0x01; /* s = 1 */
646+
answer = -1;
647+
ret = wc_DsaVerify(digest, sig, &key, &answer);
648+
ExpectIntEQ(ret, WC_NO_ERR_TRACE(BAD_FUNC_ARG));
649+
ExpectIntNE(answer, 1);
650+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
651+
652+
/* g out of range: g = 0 */
653+
ExpectIntEQ(mp_set(&key.g, 0), 0);
654+
/* restore a valid y for the remaining checks */
655+
ExpectIntEQ(mp_read_radix(&key.g, g, MP_RADIX_HEX), 0);
656+
ExpectIntEQ(mp_exptmod(&key.g, &key.x, &key.p, &key.y), 0);
657+
ExpectIntEQ(mp_set(&key.g, 0), 0);
658+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
659+
660+
/* g out of range: g = 1 */
661+
ExpectIntEQ(mp_set(&key.g, 1), 0);
662+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
663+
664+
/* g = p (>= p) */
665+
ExpectIntEQ(mp_copy(&key.p, &key.g), 0);
666+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
667+
668+
/* g in range [2, p-1] but NOT in the order-q subgroup.
669+
* g = 2 will generate a subgroup of order != q, so 2^q mod p != 1. */
670+
ExpectIntEQ(mp_set(&key.g, 2), 0);
671+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
672+
673+
/* y out of range: restore good g and a valid y between cases. */
674+
ExpectIntEQ(mp_read_radix(&key.g, g, MP_RADIX_HEX), 0);
675+
ExpectIntEQ(mp_exptmod(&key.g, &key.x, &key.p, &key.y), 0);
676+
/* Confirm the restoration produced a valid key. */
677+
ExpectIntEQ(wc_DsaCheckPubKey(&key), 0);
678+
679+
/* y = 0 */
680+
ExpectIntEQ(mp_set(&key.y, 0), 0);
681+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
682+
683+
/* y = 1 */
684+
ExpectIntEQ(mp_set(&key.y, 1), 0);
685+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
686+
687+
/* y = p */
688+
ExpectIntEQ(mp_copy(&key.p, &key.y), 0);
689+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
690+
691+
/* y in range but NOT in the order-q subgroup: y = 2. */
692+
ExpectIntEQ(mp_set(&key.y, 2), 0);
693+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
694+
695+
/* q does not divide (p - 1). Replace q with (p - 2). This is plain
696+
* integer arithmetic (no primality assumption on p): for any integer
697+
* p > 3, p - 1 = 1 * (p - 2) + 1, so (p - 1) mod (p - 2) = 1, which
698+
* is deterministically non-zero. q' = p-2 is also > 1 and is not
699+
* compared against p in DsaCheckPubKey, so the divisibility check
700+
* is the only one that fires. */
701+
ExpectIntEQ(mp_exptmod(&key.g, &key.x, &key.p, &key.y), 0);
702+
ExpectIntEQ(mp_copy(&key.p, &key.q), 0); /* q = p */
703+
ExpectIntEQ(mp_sub_d(&key.q, 2, &key.q), 0); /* q = p-2 */
704+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
705+
/* Restore the original q for any subsequent checks. */
706+
ExpectIntEQ(mp_read_radix(&key.q, q, MP_RADIX_HEX), 0);
707+
708+
/* p, q sanity floors: p = 1 or q = 1 must be rejected. */
709+
ExpectIntEQ(mp_set(&key.p, 1), 0);
710+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
711+
ExpectIntEQ(mp_read_radix(&key.p, p, MP_RADIX_HEX), 0);
712+
ExpectIntEQ(mp_set(&key.q, 1), 0);
713+
ExpectIntEQ(wc_DsaCheckPubKey(&key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
714+
715+
wc_FreeDsaKey(&key);
716+
#endif
717+
return EXPECT_RESULT();
718+
} /* END test_wc_DsaCheckPubKey */
719+

tests/api/test_dsa.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ int test_wc_DsaImportParamsRaw(void);
3434
int test_wc_DsaImportParamsRawCheck(void);
3535
int test_wc_DsaExportParamsRaw(void);
3636
int test_wc_DsaExportKeyRaw(void);
37+
int test_wc_DsaCheckPubKey(void);
3738

3839
#define TEST_DSA_DECLS \
3940
TEST_DECL_GROUP("dsa", test_wc_InitDsaKey), \
@@ -45,6 +46,7 @@ int test_wc_DsaExportKeyRaw(void);
4546
TEST_DECL_GROUP("dsa", test_wc_DsaImportParamsRaw), \
4647
TEST_DECL_GROUP("dsa", test_wc_DsaImportParamsRawCheck), \
4748
TEST_DECL_GROUP("dsa", test_wc_DsaExportParamsRaw), \
48-
TEST_DECL_GROUP("dsa", test_wc_DsaExportKeyRaw)
49+
TEST_DECL_GROUP("dsa", test_wc_DsaExportKeyRaw), \
50+
TEST_DECL_GROUP("dsa", test_wc_DsaCheckPubKey)
4951

5052
#endif /* WOLFCRYPT_TEST_DSA_H */

wolfcrypt/src/dsa.c

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,102 @@ void wc_FreeDsaKey(DsaKey* key)
9494
}
9595

9696

97+
/* Validate DSA domain parameters and public key.
98+
*
99+
* Performs the following checks (subset of FIPS 186-4 / SP 800-89):
100+
* - p > 1 and q > 1
101+
* - q divides (p - 1) (FIPS 186-4 A.1.1.2)
102+
* - 1 < g < p
103+
* - 1 < y < p
104+
* - g^q mod p == 1 (g generates the order-q subgroup)
105+
* - y^q mod p == 1 (y is in the order-q subgroup)
106+
*
107+
* Note: this routine does not run primality tests on p or q. Full FIPS
108+
* 186-4 domain-parameter validation additionally requires that p and q be
109+
* prime; callers that need that level of assurance should use
110+
* wc_DsaImportParamsRawCheck() (which exercises p) and/or run
111+
* mp_prime_is_prime_ex() on q at import time.
112+
*
113+
* key - pointer to DsaKey populated with p, q, g, and y.
114+
* return 0 on success, BAD_FUNC_ARG when the key fails validation, or a
115+
* negative error code on internal failure.
116+
*/
117+
int wc_DsaCheckPubKey(DsaKey* key)
118+
{
119+
int err = MP_OKAY;
120+
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
121+
mp_int* tmp = NULL;
122+
mp_int* tmp2 = NULL;
123+
#else
124+
mp_int tmp[1];
125+
mp_int tmp2[1];
126+
#endif
127+
128+
if (key == NULL)
129+
return BAD_FUNC_ARG;
130+
131+
/* p and q must be at least 2 */
132+
if (mp_cmp_d(&key->p, 1) != MP_GT || mp_cmp_d(&key->q, 1) != MP_GT)
133+
return BAD_FUNC_ARG;
134+
135+
/* 1 < g < p */
136+
if (mp_cmp_d(&key->g, 1) != MP_GT || mp_cmp(&key->g, &key->p) != MP_LT)
137+
return BAD_FUNC_ARG;
138+
139+
/* 1 < y < p */
140+
if (mp_cmp_d(&key->y, 1) != MP_GT || mp_cmp(&key->y, &key->p) != MP_LT)
141+
return BAD_FUNC_ARG;
142+
143+
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
144+
tmp = (mp_int*)XMALLOC(sizeof(*tmp), key->heap, DYNAMIC_TYPE_TMP_BUFFER);
145+
if (tmp == NULL)
146+
return MEMORY_E;
147+
tmp2 = (mp_int*)XMALLOC(sizeof(*tmp2), key->heap, DYNAMIC_TYPE_TMP_BUFFER);
148+
if (tmp2 == NULL) {
149+
XFREE(tmp, key->heap, DYNAMIC_TYPE_TMP_BUFFER);
150+
return MEMORY_E;
151+
}
152+
#endif
153+
154+
err = mp_init_multi(tmp, tmp2, NULL, NULL, NULL, NULL);
155+
if (err != MP_OKAY) {
156+
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
157+
XFREE(tmp2, key->heap, DYNAMIC_TYPE_TMP_BUFFER);
158+
XFREE(tmp, key->heap, DYNAMIC_TYPE_TMP_BUFFER);
159+
#endif
160+
return err;
161+
}
162+
163+
/* q divides (p - 1): tmp2 = (p - 1) mod q, must be 0. */
164+
if (err == MP_OKAY)
165+
err = mp_sub_d(&key->p, 1, tmp);
166+
if (err == MP_OKAY)
167+
err = mp_mod(tmp, &key->q, tmp2);
168+
if (err == MP_OKAY && !mp_iszero(tmp2))
169+
err = BAD_FUNC_ARG;
170+
171+
/* g^q mod p == 1 */
172+
if (err == MP_OKAY)
173+
err = mp_exptmod(&key->g, &key->q, &key->p, tmp);
174+
if (err == MP_OKAY && mp_cmp_d(tmp, 1) != MP_EQ)
175+
err = BAD_FUNC_ARG;
176+
177+
/* y^q mod p == 1 */
178+
if (err == MP_OKAY)
179+
err = mp_exptmod(&key->y, &key->q, &key->p, tmp);
180+
if (err == MP_OKAY && mp_cmp_d(tmp, 1) != MP_EQ)
181+
err = BAD_FUNC_ARG;
182+
183+
mp_clear(tmp);
184+
mp_clear(tmp2);
185+
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
186+
XFREE(tmp2, key->heap, DYNAMIC_TYPE_TMP_BUFFER);
187+
XFREE(tmp, key->heap, DYNAMIC_TYPE_TMP_BUFFER);
188+
#endif
189+
190+
return err;
191+
}
192+
97193
/* validate that (L,N) match allowed sizes from FIPS 186-4, Section 4.2.
98194
* modLen - represents L, the size of p (prime modulus) in bits
99195
* divLen - represents N, the size of q (prime divisor) in bits
@@ -1070,6 +1166,13 @@ int wc_DsaVerify_ex(const byte* digest, word32 digestSz, const byte* sig,
10701166
break;
10711167
}
10721168

1169+
/* Validate domain parameters and public key before doing any
1170+
* signature math. */
1171+
ret = wc_DsaCheckPubKey(key);
1172+
if (ret != 0) {
1173+
break;
1174+
}
1175+
10731176
/* set r and s from signature */
10741177
if (mp_read_unsigned_bin(r, sig, (word32)qSz) != MP_OKAY ||
10751178
mp_read_unsigned_bin(s, sig + qSz, (word32)qSz) != MP_OKAY) {

wolfssl/wolfcrypt/dsa.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ WOLFSSL_API int wc_DsaKeyToDer(DsaKey* key, byte* output, word32 inLen);
9595
WOLFSSL_API int wc_SetDsaPublicKey(byte* output, DsaKey* key,
9696
int outLen, int with_header);
9797
WOLFSSL_API int wc_DsaKeyToPublicDer(DsaKey* key, byte* output, word32 inLen);
98+
WOLFSSL_API int wc_DsaCheckPubKey(DsaKey* key);
9899

99100
#ifdef WOLFSSL_KEY_GEN
100101
WOLFSSL_API int wc_MakeDsaKey(WC_RNG *rng, DsaKey *dsa);

0 commit comments

Comments
 (0)