Skip to content

Commit c922080

Browse files
authored
Merge pull request #10374 from kareem-wolfssl/zd21699
Enable all-zero shared secret check for Curve448/25519 by default. Ensure post_handshake_auth extension was sent before accepting post-handshake CertificateRequest message.
2 parents eaadfb1 + bd95858 commit c922080

11 files changed

Lines changed: 271 additions & 19 deletions

File tree

.wolfssl_known_macro_extras

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,6 @@ WOLFSSL_ECC_BLIND_K
752752
WOLFSSL_ECC_GEN_REJECT_SAMPLING
753753
WOLFSSL_ECC_NO_SMALL_STACK
754754
WOLFSSL_ECC_SIGALG_PARAMS_NULL_ALLOWED
755-
WOLFSSL_ECDHX_SHARED_NOT_ZERO
756755
WOLFSSL_ECDSA_MATCH_HASH
757756
WOLFSSL_ECDSA_SET_K_ONE_LOOP
758757
WOLFSSL_EC_POINT_CMP_JACOBIAN
@@ -831,6 +830,7 @@ WOLFSSL_NO_DH186
831830
WOLFSSL_NO_DILITHIUM_LEGACY_GATES
832831
WOLFSSL_NO_DILITHIUM_LEGACY_NAMES
833832
WOLFSSL_NO_DTLS_SIZE_CHECK
833+
WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK
834834
WOLFSSL_NO_ETM_ALERT
835835
WOLFSSL_NO_FENCE
836836
WOLFSSL_NO_ISSUERHASH_TDPEER

doc/dox_comments/header_files/ssl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14232,10 +14232,13 @@ int wolfSSL_CTX_allow_post_handshake_auth(WOLFSSL_CTX* ctx);
1423214232
This is useful when connecting to a web server that has some pages that
1423314233
require client authentication and others that don't.
1423414234

14235+
This function must be called before wolfSSL_connect() on the WOLFSSL object.
14236+
1423514237
\param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new().
1423614238

1423714239
\return BAD_FUNC_ARG if ssl is NULL or not using TLS v1.3.
1423814240
\return SIDE_ERROR if called with a server.
14241+
\return BAD_STATE_E if called after the handshake has started.
1423914242
\return 0 if successful.
1424014243

1424114244
_Example_

src/tls13.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13189,6 +13189,21 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type)
1318913189
WOLFSSL_ERROR_VERBOSE(OUT_OF_ORDER_E);
1319013190
return OUT_OF_ORDER_E;
1319113191
}
13192+
/* RFC 8446 4.6.2: A client that receives a post-handshake
13193+
* CertificateRequest message without having sent the
13194+
* "post_handshake_auth" extension MUST send an
13195+
* "unexpected_message" fatal alert. wolfSSL_allow_post_handshake_auth()
13196+
* must be called before wolfSSL_connect() so postHandshakeAuth
13197+
* reflects whether the extension was offered. */
13198+
if (ssl->options.serverState >= SERVER_FINISHED_COMPLETE &&
13199+
ssl->options.clientState == CLIENT_FINISHED_COMPLETE &&
13200+
!ssl->options.postHandshakeAuth) {
13201+
WOLFSSL_MSG("Post-handshake CertificateRequest received "
13202+
"without having sent post_handshake_auth "
13203+
"extension");
13204+
WOLFSSL_ERROR_VERBOSE(OUT_OF_ORDER_E);
13205+
return OUT_OF_ORDER_E;
13206+
}
1319213207
#endif
1319313208
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
1319413209
/* Server's authenticating with PSK must not send this. */
@@ -14889,14 +14904,20 @@ int wolfSSL_CTX_allow_post_handshake_auth(WOLFSSL_CTX* ctx)
1488914904
*
1489014905
* ssl The SSL/TLS object.
1489114906
* returns BAD_FUNC_ARG when ssl is NULL, or not using TLS v1.3,
14892-
* SIDE_ERROR when not a client and 0 on success.
14907+
* SIDE_ERROR when not a client, BAD_STATE_E when called after the handshake
14908+
* has started, and 0 on success.
14909+
*
14910+
* Must be called before wolfSSL_connect() so the post_handshake_auth
14911+
* extension can be included in the ClientHello.
1489314912
*/
1489414913
int wolfSSL_allow_post_handshake_auth(WOLFSSL* ssl)
1489514914
{
1489614915
if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version))
1489714916
return BAD_FUNC_ARG;
1489814917
if (ssl->options.side == WOLFSSL_SERVER_END)
1489914918
return SIDE_ERROR;
14919+
if (ssl->options.handShakeState != NULL_STATE)
14920+
return BAD_STATE_E;
1490014921

1490114922
ssl->options.postHandshakeAuth = 1;
1490214923

tests/api/test_curve25519.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,51 @@ int test_wc_curve25519_shared_secret_ex(void)
353353
return EXPECT_RESULT();
354354
} /* END test_wc_curve25519_shared_secret_ex */
355355

356+
/*
357+
* Testing that wc_curve25519_shared_secret_ex rejects an all-zero shared
358+
* secret (RFC 7748 section 6.1). This is the default behavior; users that
359+
* need the legacy behavior can opt out with WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK.
360+
*/
361+
int test_wc_curve25519_shared_secret_zero_check(void)
362+
{
363+
EXPECT_DECLS;
364+
#if defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_KEY_IMPORT) && \
365+
!defined(WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK)
366+
curve25519_key private_key;
367+
curve25519_key public_key;
368+
WC_RNG rng;
369+
byte out[CURVE25519_KEYSIZE];
370+
word32 outLen = sizeof(out);
371+
/* All-zero public key is a low-order point that yields an all-zero
372+
* shared secret for any private key. */
373+
byte zero_pub[CURVE25519_KEYSIZE];
374+
375+
XMEMSET(&rng, 0, sizeof(WC_RNG));
376+
XMEMSET(zero_pub, 0, sizeof(zero_pub));
377+
378+
ExpectIntEQ(wc_curve25519_init(&private_key), 0);
379+
ExpectIntEQ(wc_curve25519_init(&public_key), 0);
380+
ExpectIntEQ(wc_InitRng(&rng), 0);
381+
#ifdef WOLFSSL_CURVE25519_BLINDING
382+
ExpectIntEQ(wc_curve25519_set_rng(&private_key, &rng), 0);
383+
#endif
384+
385+
ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &private_key),
386+
0);
387+
ExpectIntEQ(wc_curve25519_import_public_ex(zero_pub, sizeof(zero_pub),
388+
&public_key, EC25519_LITTLE_ENDIAN), 0);
389+
390+
ExpectIntEQ(wc_curve25519_shared_secret_ex(&private_key, &public_key, out,
391+
&outLen, EC25519_BIG_ENDIAN),
392+
WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E));
393+
394+
DoExpectIntEQ(wc_FreeRng(&rng), 0);
395+
wc_curve25519_free(&private_key);
396+
wc_curve25519_free(&public_key);
397+
#endif
398+
return EXPECT_RESULT();
399+
} /* END test_wc_curve25519_shared_secret_zero_check */
400+
356401
/*
357402
* Testing wc_curve25519_make_pub
358403
*/

tests/api/test_curve25519.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ int test_wc_curve25519_export_key_raw(void);
3030
int test_wc_curve25519_export_key_raw_ex(void);
3131
int test_wc_curve25519_make_key(void);
3232
int test_wc_curve25519_shared_secret_ex(void);
33+
int test_wc_curve25519_shared_secret_zero_check(void);
3334
int test_wc_curve25519_make_pub(void);
3435
int test_wc_curve25519_export_public_ex(void);
3536
int test_wc_curve25519_export_private_raw_ex(void);
@@ -45,6 +46,7 @@ int test_wc_Curve25519KeyToDer_oneasymkey_version(void);
4546
TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_key_raw_ex), \
4647
TEST_DECL_GROUP("curve25519", test_wc_curve25519_make_key), \
4748
TEST_DECL_GROUP("curve25519", test_wc_curve25519_shared_secret_ex), \
49+
TEST_DECL_GROUP("curve25519", test_wc_curve25519_shared_secret_zero_check),\
4850
TEST_DECL_GROUP("curve25519", test_wc_curve25519_make_pub), \
4951
TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_public_ex), \
5052
TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_private_raw_ex), \

tests/api/test_curve448.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,48 @@ int test_wc_curve448_shared_secret_ex(void)
116116
return EXPECT_RESULT();
117117
} /* END test_wc_curve448_shared_secret_ex */
118118

119+
/*
120+
* Testing that wc_curve448_shared_secret_ex rejects an all-zero shared
121+
* secret (RFC 7748 section 6.2). This is the default behavior; users that
122+
* need the legacy behavior can opt out with WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK.
123+
*/
124+
int test_wc_curve448_shared_secret_zero_check(void)
125+
{
126+
EXPECT_DECLS;
127+
#if defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_IMPORT) && \
128+
defined(HAVE_CURVE448_SHARED_SECRET) && \
129+
!defined(WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK)
130+
curve448_key private_key;
131+
curve448_key public_key;
132+
WC_RNG rng;
133+
byte out[CURVE448_KEY_SIZE];
134+
word32 outLen = sizeof(out);
135+
/* All-zero public key is a low-order point that yields an all-zero
136+
* shared secret for any private key. */
137+
byte zero_pub[CURVE448_PUB_KEY_SIZE];
138+
139+
XMEMSET(&rng, 0, sizeof(WC_RNG));
140+
XMEMSET(zero_pub, 0, sizeof(zero_pub));
141+
142+
ExpectIntEQ(wc_curve448_init(&private_key), 0);
143+
ExpectIntEQ(wc_curve448_init(&public_key), 0);
144+
ExpectIntEQ(wc_InitRng(&rng), 0);
145+
146+
ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, &private_key), 0);
147+
ExpectIntEQ(wc_curve448_import_public_ex(zero_pub, sizeof(zero_pub),
148+
&public_key, EC448_LITTLE_ENDIAN), 0);
149+
150+
ExpectIntEQ(wc_curve448_shared_secret_ex(&private_key, &public_key, out,
151+
&outLen, EC448_BIG_ENDIAN),
152+
WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E));
153+
154+
DoExpectIntEQ(wc_FreeRng(&rng), 0);
155+
wc_curve448_free(&private_key);
156+
wc_curve448_free(&public_key);
157+
#endif
158+
return EXPECT_RESULT();
159+
} /* END test_wc_curve448_shared_secret_zero_check */
160+
119161
/*
120162
* Testing test_wc_curve448_export_public_ex
121163
*/

tests/api/test_curve448.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
int test_wc_curve448_make_key(void);
2828
int test_wc_curve448_shared_secret_ex(void);
29+
int test_wc_curve448_shared_secret_zero_check(void);
2930
int test_wc_curve448_export_public_ex(void);
3031
int test_wc_curve448_export_private_raw_ex(void);
3132
int test_wc_curve448_export_key_raw(void);
@@ -39,6 +40,7 @@ int test_wc_Curve448PrivateKeyToDer_oneasymkey_version(void);
3940
#define TEST_CURVE448_DECLS \
4041
TEST_DECL_GROUP("curve448", test_wc_curve448_make_key), \
4142
TEST_DECL_GROUP("curve448", test_wc_curve448_shared_secret_ex), \
43+
TEST_DECL_GROUP("curve448", test_wc_curve448_shared_secret_zero_check), \
4244
TEST_DECL_GROUP("curve448", test_wc_curve448_export_public_ex), \
4345
TEST_DECL_GROUP("curve448", test_wc_curve448_export_private_raw_ex), \
4446
TEST_DECL_GROUP("curve448", test_wc_curve448_export_key_raw), \

tests/api/test_tls13.c

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5575,6 +5575,127 @@ int test_tls13_zero_inner_content_type(void)
55755575
return EXPECT_RESULT();
55765576
}
55775577

5578+
/* RFC 8446 Section 4.6.2: A client that receives a post-handshake
5579+
* CertificateRequest message without having sent the "post_handshake_auth"
5580+
* extension MUST send an "unexpected_message" fatal alert.
5581+
*
5582+
* This test completes a TLS 1.3 handshake in which the client never enabled
5583+
* post-handshake auth (so no extension was sent in the ClientHello), then
5584+
* forces the server to transmit a post-handshake CertificateRequest anyway.
5585+
* The client must reject the message with an unexpected_message fatal
5586+
* alert. */
5587+
int test_tls13_post_handshake_auth_no_ext(void)
5588+
{
5589+
EXPECT_DECLS;
5590+
#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \
5591+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
5592+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
5593+
WOLFSSL_CTX *ctx_c = NULL;
5594+
WOLFSSL_CTX *ctx_s = NULL;
5595+
WOLFSSL *ssl_c = NULL;
5596+
WOLFSSL *ssl_s = NULL;
5597+
struct test_memio_ctx test_ctx;
5598+
WOLFSSL_ALERT_HISTORY h;
5599+
char readBuf[8];
5600+
5601+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
5602+
XMEMSET(&h, 0, sizeof(h));
5603+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
5604+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
5605+
/* Keep the post-handshake byte stream simple by suppressing the server's
5606+
* NewSessionTicket so the only post-handshake record from the server is
5607+
* the CertificateRequest under test. */
5608+
ExpectIntEQ(wolfSSL_no_ticket_TLSv13(ssl_s), 0);
5609+
5610+
/* Intentionally do NOT call wolfSSL_allow_post_handshake_auth() on the
5611+
* client so the post_handshake_auth extension is omitted from the
5612+
* ClientHello. */
5613+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
5614+
5615+
/* The server's wolfSSL_request_certificate() refuses to send a
5616+
* post-handshake CertificateRequest unless its postHandshakeAuth flag is
5617+
* set (normally set when parsing the client's extension). To exercise
5618+
* the receive-side check we are testing, simulate a server that sends
5619+
* one anyway by toggling the flag directly. */
5620+
if (ssl_s != NULL)
5621+
ssl_s->options.postHandshakeAuth = 1;
5622+
/* OPENSSL_COMPATIBLE_DEFAULTS may leave groupMessages set on ssl_s; that
5623+
* suppresses SendBuffered() for the post-handshake CertificateRequest. */
5624+
ExpectIntEQ(wolfSSL_clear_group_messages(ssl_s), WOLFSSL_SUCCESS);
5625+
ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS);
5626+
ExpectIntGT(test_ctx.c_len, 0);
5627+
5628+
/* The client must reject the unsolicited CertificateRequest. */
5629+
ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)),
5630+
WOLFSSL_FATAL_ERROR);
5631+
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
5632+
WC_NO_ERR_TRACE(OUT_OF_ORDER_E));
5633+
5634+
/* And the client must transmit a fatal unexpected_message alert. */
5635+
ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS);
5636+
ExpectIntEQ(h.last_tx.code, unexpected_message);
5637+
ExpectIntEQ(h.last_tx.level, alert_fatal);
5638+
5639+
wolfSSL_free(ssl_c);
5640+
wolfSSL_CTX_free(ctx_c);
5641+
wolfSSL_free(ssl_s);
5642+
wolfSSL_CTX_free(ctx_s);
5643+
#endif
5644+
return EXPECT_RESULT();
5645+
}
5646+
5647+
/* wolfSSL_allow_post_handshake_auth() must be called before the handshake
5648+
* starts. A late call must fail with BAD_STATE_E and must not enable
5649+
* post-handshake authentication for the connection. */
5650+
int test_tls13_post_handshake_auth_late_allow(void)
5651+
{
5652+
EXPECT_DECLS;
5653+
#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \
5654+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
5655+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
5656+
WOLFSSL_CTX *ctx_c = NULL;
5657+
WOLFSSL_CTX *ctx_s = NULL;
5658+
WOLFSSL *ssl_c = NULL;
5659+
WOLFSSL *ssl_s = NULL;
5660+
struct test_memio_ctx test_ctx;
5661+
WOLFSSL_ALERT_HISTORY h;
5662+
char readBuf[8];
5663+
5664+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
5665+
XMEMSET(&h, 0, sizeof(h));
5666+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
5667+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
5668+
ExpectIntEQ(wolfSSL_no_ticket_TLSv13(ssl_s), 0);
5669+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
5670+
5671+
/* Too late: handshake is complete. */
5672+
ExpectIntEQ(wolfSSL_allow_post_handshake_auth(ssl_c),
5673+
WC_NO_ERR_TRACE(BAD_STATE_E));
5674+
5675+
if (ssl_s != NULL)
5676+
ssl_s->options.postHandshakeAuth = 1;
5677+
/* OPENSSL_COMPATIBLE_DEFAULTS may leave groupMessages set on ssl_s; that
5678+
* suppresses SendBuffered() for the post-handshake CertificateRequest. */
5679+
ExpectIntEQ(wolfSSL_clear_group_messages(ssl_s), WOLFSSL_SUCCESS);
5680+
ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS);
5681+
ExpectIntGT(test_ctx.c_len, 0);
5682+
5683+
ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)),
5684+
WOLFSSL_FATAL_ERROR);
5685+
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
5686+
WC_NO_ERR_TRACE(OUT_OF_ORDER_E));
5687+
ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS);
5688+
ExpectIntEQ(h.last_tx.code, unexpected_message);
5689+
ExpectIntEQ(h.last_tx.level, alert_fatal);
5690+
5691+
wolfSSL_free(ssl_c);
5692+
wolfSSL_CTX_free(ctx_c);
5693+
wolfSSL_free(ssl_s);
5694+
wolfSSL_CTX_free(ctx_s);
5695+
#endif
5696+
return EXPECT_RESULT();
5697+
}
5698+
55785699
/* Test that a TLS 1.3-capable client rejects downgrade sentinels in a
55795700
* downgraded ServerHello random for both TLS 1.2 and TLS 1.1-or-lower. */
55805701
int test_tls13_downgrade_sentinel(void)

tests/api/test_tls13.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ int test_tls13_corrupted_finished(void);
5959
int test_tls13_peerauth_failsafe(void);
6060
int test_tls13_hrr_bad_cookie(void);
6161
int test_tls13_zero_inner_content_type(void);
62+
int test_tls13_post_handshake_auth_no_ext(void);
63+
int test_tls13_post_handshake_auth_late_allow(void);
6264
int test_tls13_downgrade_sentinel(void);
6365
int test_tls13_serverhello_bad_cipher_suites(void);
6466
int test_tls13_cert_with_extern_psk_apis(void);
@@ -111,6 +113,8 @@ int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void);
111113
TEST_DECL_GROUP("tls13", test_tls13_peerauth_failsafe), \
112114
TEST_DECL_GROUP("tls13", test_tls13_hrr_bad_cookie), \
113115
TEST_DECL_GROUP("tls13", test_tls13_zero_inner_content_type), \
116+
TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_no_ext), \
117+
TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_late_allow), \
114118
TEST_DECL_GROUP("tls13", test_tls13_downgrade_sentinel), \
115119
TEST_DECL_GROUP("tls13", test_tls13_serverhello_bad_cipher_suites), \
116120
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_apis), \

0 commit comments

Comments
 (0)