diff --git a/doc/dox_comments/header_files-ja/ascon.h b/doc/dox_comments/header_files-ja/ascon.h index 78666aba9ab..f618c1bb814 100644 --- a/doc/dox_comments/header_files-ja/ascon.h +++ b/doc/dox_comments/header_files-ja/ascon.h @@ -233,7 +233,8 @@ int wc_AsconAEAD128_SetNonce(wc_AsconAEAD128* a, const byte* nonce); \brief この関数は、Ascon AEADコンテキストの関連データを設定します。 \return 0 成功時。 - \return BAD_FUNC_ARG コンテキストまたは関連データポインタがNULLの場合。 + \return BAD_FUNC_ARG コンテキストがNULLの場合、または関連データサイズが0より + 大きいのに関連データポインタがNULLの場合。 \return BAD_STATE_E 鍵またはnonceが設定されていない場合。 \param a 初期化されたAscon AEADコンテキストへのポインタ。 diff --git a/doc/dox_comments/header_files/ascon.h b/doc/dox_comments/header_files/ascon.h index bdfaf4ce734..c25c68fea7c 100644 --- a/doc/dox_comments/header_files/ascon.h +++ b/doc/dox_comments/header_files/ascon.h @@ -236,7 +236,8 @@ int wc_AsconAEAD128_SetNonce(wc_AsconAEAD128* a, const byte* nonce); \brief This function sets the associated data for the Ascon AEAD context. \return 0 on success. - \return BAD_FUNC_ARG if the context or associated data pointer is NULL. + \return BAD_FUNC_ARG if the context is NULL, or if the associated data + pointer is NULL while the associated data size is greater than 0. \return BAD_STATE_E if the key or nonce has not been set. \param a pointer to the initialized Ascon AEAD context. diff --git a/src/internal.c b/src/internal.c index dc6e43a1821..941b1e98bfc 100644 --- a/src/internal.c +++ b/src/internal.c @@ -35494,6 +35494,7 @@ static int GetDhPublicKey(WOLFSSL* ssl, const byte* input, word32 size, return ret; } #endif + #if defined(HAVE_ECC) || defined(HAVE_CURVE25519) || defined(HAVE_CURVE448) static int GetEcDiffieHellmanKea(WOLFSSL *ssl, const byte *input, word32 size, DskeArgs *args) @@ -35505,6 +35506,12 @@ static int GetEcDiffieHellmanKea(WOLFSSL *ssl, #endif int curveOid; word16 length; +#ifdef HAVE_CURVE25519 + const byte* peerPub; +#ifndef WOLFSSL_X25519_NO_MASK_PEER + byte maskedPub[CURVE25519_KEYSIZE]; +#endif +#endif if ((args->idx - args->begin) + ENUM_LEN + OPAQUE16_LEN + OPAQUE8_LEN > size) { @@ -35547,7 +35554,12 @@ static int GetEcDiffieHellmanKea(WOLFSSL *ssl, } } - if ((ret = wc_curve25519_check_public(input + args->idx, length, + peerPub = input + args->idx; +#ifndef WOLFSSL_X25519_NO_MASK_PEER + peerPub = MaskCurve25519PeerKey(peerPub, length, maskedPub); +#endif + + if ((ret = wc_curve25519_check_public(peerPub, length, EC25519_LITTLE_ENDIAN)) != 0) { #ifdef WOLFSSL_EXTRA_ALERTS if (ret == WC_NO_ERR_TRACE(BUFFER_E)) @@ -35563,7 +35575,7 @@ static int GetEcDiffieHellmanKea(WOLFSSL *ssl, return ECC_PEERKEY_ERROR; } - if (wc_curve25519_import_public_ex(input + args->idx, + if (wc_curve25519_import_public_ex(peerPub, length, ssl->peerX25519Key, EC25519_LITTLE_ENDIAN) != 0) { return ECC_PEERKEY_ERROR; @@ -38180,6 +38192,27 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, /* end client only parts */ +#if defined(HAVE_CURVE25519) && !defined(WOLFSSL_X25519_NO_MASK_PEER) +/* RFC 7748 Section 5 requires X25519 receivers to clear the reserved high bit + * of the final u-coordinate byte rather than reject a peer key that sets it. + * Returns the pointer the caller should hand to the check and import calls: a + * masked copy in maskBuf for a full-length key, otherwise pub unchanged. + * Shared by the TLS 1.2 (client and server) and TLS 1.3 key exchange paths, + * so it must sit outside the client-only and !WOLFSSL_NO_TLS12 guards. */ +const byte* MaskCurve25519PeerKey(const byte* pub, word32 pubSz, + byte maskBuf[CURVE25519_KEYSIZE]) +{ + if (pubSz != CURVE25519_KEYSIZE) { + return pub; + } + + XMEMCPY(maskBuf, pub, CURVE25519_KEYSIZE); + maskBuf[CURVE25519_KEYSIZE - 1] &= 0x7f; + return maskBuf; +} +#endif /* HAVE_CURVE25519 && !WOLFSSL_X25519_NO_MASK_PEER */ + + #ifndef NO_CERTS #if defined(WOLF_PRIVATE_KEY_ID) || defined(HAVE_PK_CALLBACKS) @@ -44364,6 +44397,12 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], { int ret; int kea = ssl->specs.kea; + #ifdef HAVE_CURVE25519 + const byte* peerPub; + #ifndef WOLFSSL_X25519_NO_MASK_PEER + byte maskedPub[CURVE25519_KEYSIZE]; + #endif + #endif *haveCb = 0; if ((args->idx - args->begin) + OPAQUE8_LEN > size) @@ -44408,7 +44447,12 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], } } - if ((ret = wc_curve25519_check_public(input + args->idx, + peerPub = input + args->idx; + #ifndef WOLFSSL_X25519_NO_MASK_PEER + peerPub = MaskCurve25519PeerKey(peerPub, args->length, maskedPub); + #endif + + if ((ret = wc_curve25519_check_public(peerPub, args->length, EC25519_LITTLE_ENDIAN)) != 0) { #ifdef WOLFSSL_EXTRA_ALERTS if (ret == WC_NO_ERR_TRACE(BUFFER_E)) @@ -44424,7 +44468,7 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], return ECC_PEERKEY_ERROR; } - if (wc_curve25519_import_public_ex(input + args->idx, args->length, + if (wc_curve25519_import_public_ex(peerPub, args->length, ssl->peerX25519Key, EC25519_LITTLE_ENDIAN)) { #ifdef WOLFSSL_EXTRA_ALERTS SendAlert(ssl, alert_fatal, illegal_parameter); diff --git a/src/tls.c b/src/tls.c index 7b3e0d5bc59..d8db5d71148 100644 --- a/src/tls.c +++ b/src/tls.c @@ -9741,6 +9741,11 @@ static int TLSX_KeyShare_ProcessX25519_ex(WOLFSSL* ssl, #ifdef HAVE_CURVE25519 curve25519_key* key = (curve25519_key*)keyShareEntry->key; + const byte* peerPub = keyShareEntry->ke; + word32 peerPubLen = keyShareEntry->keLen; +#ifndef WOLFSSL_X25519_NO_MASK_PEER + byte maskedPub[CURVE25519_KEYSIZE]; +#endif #ifdef WOLFSSL_ASYNC_CRYPT if (keyShareEntry->lastRet == 0) /* don't enter here if WC_PENDING_E */ @@ -9771,15 +9776,18 @@ static int TLSX_KeyShare_ProcessX25519_ex(WOLFSSL* ssl, WOLFSSL_BUFFER(keyShareEntry->ke, keyShareEntry->keLen); #endif - if (wc_curve25519_check_public(keyShareEntry->ke, keyShareEntry->keLen, + #ifndef WOLFSSL_X25519_NO_MASK_PEER + peerPub = MaskCurve25519PeerKey(peerPub, peerPubLen, maskedPub); + #endif + + if (wc_curve25519_check_public(peerPub, peerPubLen, EC25519_LITTLE_ENDIAN) != 0) { ret = ECC_PEERKEY_ERROR; WOLFSSL_ERROR_VERBOSE(ret); } if (ret == 0) { - if (wc_curve25519_import_public_ex(keyShareEntry->ke, - keyShareEntry->keLen, + if (wc_curve25519_import_public_ex(peerPub, peerPubLen, ssl->peerX25519Key, EC25519_LITTLE_ENDIAN) != 0) { ret = ECC_PEERKEY_ERROR; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 76db8428f6e..ce819816389 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -10025,3 +10025,117 @@ int test_tls13_pha_status_request(void) #endif return EXPECT_RESULT(); } + +/* RFC 7748 Section 5: a receiver of a 32-byte X25519 u-coordinate MUST mask + * (clear) the reserved high bit of the final byte rather than reject it. + * Craft a ClientHello whose key_share entry has that bit set and confirm + * the server masks it and proceeds, instead of aborting the handshake with + * ECC_PEERKEY_ERROR the way wc_curve25519_check_public() alone would. + */ +int test_tls13_x25519_keyshare_masks_reserved_bit(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CURVE25519) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + (defined(HAVE_ECC) || !defined(NO_RSA)) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte ch_buf[4096]; + const char* ch_bytes = NULL; + int ch_sz = 0; + int i; + int keShareOff = -1; + int ret; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + +#if defined(HAVE_ECC) + ExpectTrue(wolfSSL_use_certificate_file(ssl_s, eccCertFile, + CERT_FILETYPE) == WOLFSSL_SUCCESS); + ExpectTrue(wolfSSL_use_PrivateKey_file(ssl_s, eccKeyFile, + CERT_FILETYPE) == WOLFSSL_SUCCESS); +#else + ExpectTrue(wolfSSL_use_certificate_file(ssl_s, svrCertFile, + CERT_FILETYPE) == WOLFSSL_SUCCESS); + ExpectTrue(wolfSSL_use_PrivateKey_file(ssl_s, svrKeyFile, + CERT_FILETYPE) == WOLFSSL_SUCCESS); +#endif + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + + /* Force an X25519 key share so the ClientHello carries a key_share + * entry we can find by its fixed group id (0x001D) and length + * (0x0020) markers, without parsing the full extension list. */ + do { + ret = wolfSSL_UseKeyShare(ssl_c, WOLFSSL_ECC_X25519); +#ifdef WOLFSSL_ASYNC_CRYPT + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) + wolfSSL_AsyncPoll(ssl_c, WOLF_POLL_FLAG_CHECK_HW); +#endif + } while (ret == WC_NO_ERR_TRACE(WC_PENDING_E)); + ExpectIntEQ(ret, WOLFSSL_SUCCESS); + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + /* Pull the ClientHello out of the buffer the server will read from + * (client=0), tamper with the X25519 public value in place, then feed + * the modified message back in. */ + ExpectIntEQ(test_memio_get_message(&test_ctx, 0, &ch_bytes, &ch_sz, 0), + 0); + ExpectTrue(ch_sz > 0 && ch_sz <= (int)sizeof(ch_buf)); + if (ch_sz > 0 && ch_sz <= (int)sizeof(ch_buf)) { + XMEMCPY(ch_buf, ch_bytes, (size_t)ch_sz); + + for (i = 0; i + 4 + CURVE25519_KEYSIZE <= ch_sz; i++) { + if (ch_buf[i] == 0x00 && ch_buf[i + 1] == 0x1D && + ch_buf[i + 2] == 0x00 && + ch_buf[i + 3] == CURVE25519_KEYSIZE) { + keShareOff = i; + break; + } + } + ExpectIntGE(keShareOff, 0); + if (keShareOff >= 0) { + /* Set the reserved high bit of the final wire byte of the + * public value -- the bit RFC 7748 requires masking. */ + ch_buf[keShareOff + 4 + CURVE25519_KEYSIZE - 1] |= 0x80; + } + } + + test_memio_clear_buffer(&test_ctx, 0); + if (keShareOff >= 0) { + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)ch_buf, ch_sz), 0); + } + + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); +#ifdef WOLFSSL_X25519_NO_MASK_PEER + /* Masking is compiled out: wc_curve25519_check_public() still sees + * the reserved bit and rejects the key, so the server must abort the + * handshake with ECC_PEERKEY_ERROR instead of masking it. */ + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(ECC_PEERKEY_ERROR)); +#else + /* The bit is masked before the check, so the server accepts the + * share and moves on to building its response flight. */ + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); +#endif + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 85aeb17bc6e..7877fdc695c 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -119,6 +119,7 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void); int test_tls13_KeyUpdate_sender_limit(void); int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); +int test_tls13_x25519_keyshare_masks_reserved_bit(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -215,6 +216,7 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_sender_limit), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ - TEST_DECL_GROUP("tls13", test_tls13_pha_status_request) + TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \ + TEST_DECL_GROUP("tls13", test_tls13_x25519_keyshare_masks_reserved_bit) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index ebc0e7b5245..bc025e5fc54 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2258,6 +2258,11 @@ WOLFSSL_LOCAL int InitSSL_Suites(WOLFSSL* ssl); WOLFSSL_LOCAL int InitSSL_Side(WOLFSSL* ssl, word16 side); +#if defined(HAVE_CURVE25519) && !defined(WOLFSSL_X25519_NO_MASK_PEER) +WOLFSSL_LOCAL const byte* MaskCurve25519PeerKey(const byte* pub, word32 pubSz, + byte maskBuf[CURVE25519_KEYSIZE]); +#endif + WOLFSSL_LOCAL int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, byte type, word32 size, word32 totalSz); /* for sniffer */