Skip to content

Commit de0c707

Browse files
ejohnstownphilljj
authored andcommitted
Reject a negative DH e or f
Classic DH e and f are mpints (RFC 4251 section 5), so a value with the top bit set and no leading zero is negative. KeyAgreeDh_client() and KeyAgreeDh_server() reject one before the range check, and the key exchange ends with the usual KEY_EXCHANGE_FAILED disconnect. - add test_KeyAgreeDh_rejectsNegativePeer() for both sides - pad e in test_DhGexGroup16KeyAgree() the way SendKexDhInit() does Issue: F-15532
1 parent c9c4c1f commit de0c707

2 files changed

Lines changed: 142 additions & 10 deletions

File tree

‎src/internal.c‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8410,8 +8410,12 @@ static int KeyAgreeDh_client(WOLFSSH* ssh, byte hashId,
84108410

84118411
/* Reject a peer public value outside the safe range [2, p-2] before key
84128412
* agreement. Keeps wolfSSH independent of whether the linked wolfSSL
8413-
* validates the peer key inside wc_DhAgree. */
8414-
ret = wc_DhCheckPubKey(&ssh->handshake->privKey.dh, f, fSz);
8413+
* validates the peer key inside wc_DhAgree. f is an mpint, so a set
8414+
* top bit makes it negative (RFC 4251 section 5). */
8415+
if (fSz > 0 && (f[0] & 0x80) != 0)
8416+
ret = WS_PARSE_E;
8417+
else
8418+
ret = wc_DhCheckPubKey(&ssh->handshake->privKey.dh, f, fSz);
84158419
if (ret != 0) {
84168420
/* Peer-driven reject; log at DEBUG like other peer-input rejections
84178421
* so a remote peer cannot flood the error log. */
@@ -17352,10 +17356,24 @@ static int KeyAgreeDh_server(WOLFSSH* ssh, byte hashId, byte* f, word32* fSz)
1735217356
PRIVATE_KEY_LOCK();
1735317357
}
1735417358
/* Reject a peer public value outside the safe range [2, p-2] before
17355-
* key agreement, independent of the linked wolfSSL's own check. */
17356-
if (ret == 0)
17357-
ret = wc_DhCheckPubKey(privKey, ssh->handshake->e,
17358-
ssh->handshake->eSz);
17359+
* key agreement, independent of the linked wolfSSL's own check.
17360+
* e is an mpint, so a set top bit makes it negative (RFC 4251
17361+
* section 5). */
17362+
if (ret == 0) {
17363+
if (ssh->handshake->eSz > 0
17364+
&& (ssh->handshake->e[0] & 0x80) != 0)
17365+
ret = WS_PARSE_E;
17366+
else
17367+
ret = wc_DhCheckPubKey(privKey, ssh->handshake->e,
17368+
ssh->handshake->eSz);
17369+
if (ret != 0) {
17370+
/* Peer-driven reject; log at DEBUG like other peer-input
17371+
* rejections so a remote peer cannot flood the error log. */
17372+
WLOG(WS_LOG_DEBUG,
17373+
"Peer DH public key out of range, %d", ret);
17374+
ret = WS_CRYPTO_FAILED;
17375+
}
17376+
}
1735917377
if (ret == 0) {
1736017378
PRIVATE_KEY_UNLOCK();
1736117379
ret = wc_DhAgree(privKey, ssh->k, &ssh->kSz, y_ptr, ySz,

‎tests/unit.c‎

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3540,9 +3540,13 @@ static int test_DhGexGroup16KeyAgree(void)
35403540
goto out;
35413541
}
35423542

3543-
/* Hand the client's public value to the server and run the agreement. */
3544-
WMEMCPY(ssh->handshake->e, cPub, cPubSz);
3545-
ssh->handshake->eSz = cPubSz;
3543+
/* Hand the client's public value to the server as the mpint
3544+
* SendKexDhInit() would send, and run the agreement. */
3545+
ssh->handshake->eSz = 0;
3546+
if (cPub[0] & 0x80)
3547+
ssh->handshake->e[ssh->handshake->eSz++] = 0;
3548+
WMEMCPY(ssh->handshake->e + ssh->handshake->eSz, cPub, cPubSz);
3549+
ssh->handshake->eSz += cPubSz;
35463550
ssh->kSz = MAX_KEX_KEY_SZ;
35473551

35483552
ret = wolfSSH_TestKeyAgreeDh_server(ssh, WC_HASH_TYPE_SHA256, f, &fSz);
@@ -20678,7 +20682,7 @@ static int test_KeyAgreeDh_server_rejectsOutOfRangePeer(void)
2067820682
hs->eSz = 1;
2067920683
fSz = (word32)sizeof(f);
2068020684
ret = wolfSSH_TestKeyAgreeDh_server(ssh, WC_HASH_TYPE_SHA256, f, &fSz);
20681-
if (ret == WS_SUCCESS) {
20685+
if (ret != WS_CRYPTO_FAILED) {
2068220686
result = -743;
2068320687
break;
2068420688
}
@@ -20750,6 +20754,111 @@ static int test_KeyAgreeDh_client_rejectsOutOfRangePeer(void)
2075020754
return result;
2075120755
}
2075220756
#endif /* HAVE_FFDHE_2048 */
20757+
20758+
/* Verify both DH key agreement sides reject a peer public value encoded
20759+
* as a negative mpint (RFC 4251 section 5). 0x80 is 128 read unsigned,
20760+
* inside [2, p-2], so only the sign check can reject it; 0x00 0x80 is
20761+
* the same value encoded correctly and must be accepted. */
20762+
static int test_KeyAgreeDh_rejectsNegativePeer(void)
20763+
{
20764+
WOLFSSH_CTX* ctx = NULL;
20765+
WOLFSSH* ssh = NULL;
20766+
HandshakeInfo* hs = NULL;
20767+
byte f[MAX_KEX_KEY_SZ];
20768+
word32 fSz;
20769+
int ret;
20770+
int result = 0;
20771+
static const byte negPeer[1] = { 0x80 };
20772+
static const byte posPeer[2] = { 0x00, 0x80 };
20773+
byte kexId;
20774+
20775+
/* server: the peer value is the client's e */
20776+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
20777+
if (ctx == NULL)
20778+
return -760;
20779+
ssh = wolfSSH_new(ctx);
20780+
if (ssh == NULL || ssh->handshake == NULL) {
20781+
result = -761;
20782+
goto cleanup;
20783+
}
20784+
#ifndef WOLFSSH_NO_DH_GROUP14_SHA256
20785+
kexId = ID_DH_GROUP14_SHA256;
20786+
#elif !defined(WOLFSSH_NO_DH_GROUP16_SHA512)
20787+
kexId = ID_DH_GROUP16_SHA512;
20788+
#elif !defined(WOLFSSH_NO_DH_GROUP14_SHA1)
20789+
kexId = ID_DH_GROUP14_SHA1;
20790+
#elif !defined(WOLFSSH_NO_DH_GROUP1_SHA1)
20791+
kexId = ID_DH_GROUP1_SHA1;
20792+
#else
20793+
/* No fixed DH group enabled; fail rather than silently pass. */
20794+
result = -762;
20795+
goto cleanup;
20796+
#endif
20797+
hs = ssh->handshake;
20798+
hs->kexId = kexId;
20799+
20800+
WMEMCPY(hs->e, negPeer, sizeof(negPeer));
20801+
hs->eSz = (word32)sizeof(negPeer);
20802+
fSz = (word32)sizeof(f);
20803+
ret = wolfSSH_TestKeyAgreeDh_server(ssh, WC_HASH_TYPE_SHA256, f, &fSz);
20804+
if (ret != WS_CRYPTO_FAILED) {
20805+
result = -763;
20806+
goto cleanup;
20807+
}
20808+
20809+
WMEMCPY(hs->e, posPeer, sizeof(posPeer));
20810+
hs->eSz = (word32)sizeof(posPeer);
20811+
fSz = (word32)sizeof(f);
20812+
ret = wolfSSH_TestKeyAgreeDh_server(ssh, WC_HASH_TYPE_SHA256, f, &fSz);
20813+
if (ret != WS_SUCCESS) {
20814+
result = -764;
20815+
goto cleanup;
20816+
}
20817+
20818+
wolfSSH_free(ssh);
20819+
wolfSSH_CTX_free(ctx);
20820+
20821+
/* client: the peer value is the server's f */
20822+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
20823+
if (ctx == NULL)
20824+
return -765;
20825+
ssh = wolfSSH_new(ctx);
20826+
if (ssh == NULL || ssh->handshake == NULL) {
20827+
result = -766;
20828+
goto cleanup;
20829+
}
20830+
ssh->handshake->kexId = kexId;
20831+
20832+
/* The hook frees the DH key on return, so set it up for each call. */
20833+
if (wolfSSH_TestSetDhKexKey(ssh) != 0) {
20834+
result = -767;
20835+
goto cleanup;
20836+
}
20837+
ret = wolfSSH_TestKeyAgreeDh_client(ssh, WC_HASH_TYPE_SHA256,
20838+
negPeer, (word32)sizeof(negPeer));
20839+
if (ret != WS_CRYPTO_FAILED) {
20840+
result = -768;
20841+
goto cleanup;
20842+
}
20843+
20844+
if (wolfSSH_TestSetDhKexKey(ssh) != 0) {
20845+
result = -769;
20846+
goto cleanup;
20847+
}
20848+
ret = wolfSSH_TestKeyAgreeDh_client(ssh, WC_HASH_TYPE_SHA256,
20849+
posPeer, (word32)sizeof(posPeer));
20850+
if (ret != WS_SUCCESS) {
20851+
result = -770;
20852+
goto cleanup;
20853+
}
20854+
20855+
cleanup:
20856+
if (ssh != NULL)
20857+
wolfSSH_free(ssh);
20858+
if (ctx != NULL)
20859+
wolfSSH_CTX_free(ctx);
20860+
return result;
20861+
}
2075320862
#endif /* !WOLFSSH_NO_DH */
2075420863

2075520864
#if !defined(WOLFSSH_NO_ECDH) && !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256)
@@ -26512,6 +26621,11 @@ int wolfSSH_UnitTest(int argc, char** argv)
2651226621
(unitResult == 0 ? "SUCCESS" : "FAILED"));
2651326622
testResult = testResult || unitResult;
2651426623
#endif
26624+
26625+
unitResult = test_KeyAgreeDh_rejectsNegativePeer();
26626+
printf("KeyAgreeDh_rejectsNegativePeer: %s (result=%d)\n",
26627+
(unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult);
26628+
testResult = testResult || unitResult;
2651526629
#endif /* !WOLFSSH_NO_DH */
2651626630

2651726631
#if !defined(WOLFSSH_NO_ECDH) && !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256)

0 commit comments

Comments
 (0)