From cddfe34a9c8b2699ca6999280dd523b2ee7b909f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 22 Jun 2026 12:18:05 +0200 Subject: [PATCH] Enable support for mandatory PSKs Add a new option to require that an external Pre-Shared Key is negotiated for a handshake to succeed, configured via the new APIs wolfSSL_CTX_require_psk()/wolfSSL_require_psk(). When set, a handshake that completes without negotiating an external PSK is aborted with PSK_MISSING_ERROR instead of falling back to a certificate handshake, so the PSK acts as an additional security factor. This is a TLS 1.3 / DTLS 1.3 feature. In (D)TLS 1.2 the use of a PSK is determined by the negotiated cipher suite, so a mandatory PSK is instead configured there by restricting the cipher suite list to PSK suites; the new APIs therefore reject non-TLS-1.3 contexts with BAD_FUNC_ARG. The requirement applies to external PSKs only (not session tickets): session-ticket resumption is exempt. To preserve forward secrecy a mandatory external PSK must also use an (EC)DHE key exchange; a pure psk_ke handshake is rejected with PSK_KEY_ERROR. When used with WOLFSSL_CERT_WITH_EXTERN_PSK, it also ensures that peers are properly authenticated with both the PSK and via certificates. The new APIs live alongside the existing wolfSSL_[CTX_]no_dhe_psk()/ only_dhe_psk() PSK options and do not depend on certificate support, so the feature is usable in NO_CERTS (PSK-only) builds. Added unit tests for the new APIs and enforcement. --- doc/dox_comments/header_files/ssl.h | 65 +++ src/internal.c | 5 + src/tls13.c | 90 +++- tests/api.c | 2 +- tests/api/test_tls13.c | 635 +++++++++++++++++++++++++++- tests/api/test_tls13.h | 22 + wolfssl/error-ssl.h | 1 + wolfssl/internal.h | 2 + wolfssl/ssl.h | 4 + 9 files changed, 815 insertions(+), 11 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 1a56a4ae7c7..cd977369ce3 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -14125,6 +14125,71 @@ int wolfSSL_CTX_no_dhe_psk(WOLFSSL_CTX* ctx); */ int wolfSSL_no_dhe_psk(WOLFSSL* ssl); +/*! + \ingroup Setup + + \brief This function is called on a TLS v1.3 / DTLS v1.3 context to require + that an external Pre-Shared Key is negotiated for the handshake to succeed. + When set, a handshake that completes without negotiating an external PSK is + aborted with PSK_MISSING_ERROR instead of falling back to a certificate + handshake, so the PSK acts as an additional security factor. The requirement + keys off the external-PSK callback (it has no effect unless one is + registered) and session-ticket resumption is exempt. To preserve forward + secrecy a mandatory external PSK must also use an (EC)DHE key exchange; a + pure psk_ke handshake is rejected with PSK_KEY_ERROR. This applies to TLS 1.3 + and DTLS 1.3 only; in (D)TLS 1.2 the use of a PSK is determined by the + negotiated cipher suite, so a mandatory PSK is instead configured by + restricting the cipher suite list to (preferably (EC)DHE-)PSK suites. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + + \return BAD_FUNC_ARG if ctx is NULL or not at least TLS v1.3. + \return 0 if successful. + + _Example_ + \code + int ret; + WOLFSSL_CTX* ctx; + ... + ret = wolfSSL_CTX_require_psk(ctx); + if (ret != 0) { + // failed to make a PSK mandatory + } + \endcode + + \sa wolfSSL_require_psk +*/ +int wolfSSL_CTX_require_psk(WOLFSSL_CTX* ctx); + +/*! + \ingroup Setup + + \brief This function is called on a TLS v1.3 / DTLS v1.3 wolfSSL object to + require that an external Pre-Shared Key is negotiated for the handshake to + succeed. See wolfSSL_CTX_require_psk() for the full behaviour. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + + \return BAD_FUNC_ARG if ssl is NULL or not at least TLS v1.3. + \return 0 if successful. + + _Example_ + \code + int ret; + WOLFSSL* ssl; + ... + ret = wolfSSL_require_psk(ssl); + if (ret != 0) { + // failed to make a PSK mandatory + } + \endcode + + \sa wolfSSL_CTX_require_psk +*/ +int wolfSSL_require_psk(WOLFSSL* ssl); + /*! \ingroup IO diff --git a/src/internal.c b/src/internal.c index fed9d370dea..4184e43359e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -7278,6 +7278,7 @@ int SetSSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) ssl->options.verifyNone = ctx->verifyNone; ssl->options.failNoCert = ctx->failNoCert; ssl->options.failNoCertxPSK = ctx->failNoCertxPSK; + ssl->options.failNoPSK = ctx->failNoPSK; ssl->options.sendVerify = ctx->sendVerify; ssl->options.partialWrite = ctx->partialWrite; @@ -27954,6 +27955,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e) case DUPE_ENTRY_E: return "duplicate entry error"; + case PSK_MISSING_ERROR: + return "psk missing error"; + case GETTIME_ERROR: return "gettimeofday() error"; @@ -36208,6 +36212,7 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, return missing_extension; case WC_NO_ERR_TRACE(MATCH_SUITE_ERROR): case WC_NO_ERR_TRACE(MISSING_HANDSHAKE_DATA): + case WC_NO_ERR_TRACE(PSK_MISSING_ERROR): return handshake_failure; case WC_NO_ERR_TRACE(VERSION_ERROR): return wolfssl_alert_protocol_version; diff --git a/src/tls13.c b/src/tls13.c index ecfd5946dd8..a2eecbc6d4f 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -4386,7 +4386,11 @@ static int SetupPskKey(WOLFSSL* ssl, PreSharedKey* psk, int clientHello) return PSK_KEY_ERROR; } } - else if (ssl->options.onlyPskDheKe) { + else if (ssl->options.onlyPskDheKe || + (ssl->options.failNoPSK && !psk->resumption)) { + /* A mandatory external PSK (failNoPSK) must be combined with + * (EC)DHE for forward secrecy, so reject a pure psk_ke + * negotiation. Session-ticket resumption is exempt. */ WOLFSSL_ERROR_VERBOSE(PSK_KEY_ERROR); return PSK_KEY_ERROR; } @@ -5905,6 +5909,13 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, while (psk != NULL && !psk->chosen) psk = psk->next; if (psk == NULL) { + /* havePSK is only set by an external-PSK callback, so a peer + * relying solely on session-ticket resumption is unaffected. */ + if (ssl->options.havePSK && ssl->options.failNoPSK) { + WOLFSSL_MSG("Server did not negotiate a mandatory PSK"); + WOLFSSL_ERROR_VERBOSE(PSK_MISSING_ERROR); + return PSK_MISSING_ERROR; + } ssl->options.resuming = 0; ssl->arrays->psk_keySz = 0; XMEMSET(ssl->arrays->psk_key, 0, MAX_PSK_KEY_LEN); @@ -6668,6 +6679,16 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz, #endif if (usingPSK) *usingPSK = 0; + + /* No PSK extension at all: if a mandatory external PSK is configured, + * refuse the connection rather than continue without one. havePSK is + * only set by an external-PSK callback, so a peer relying solely on + * session-ticket resumption is unaffected. */ + if (ssl->options.havePSK && ssl->options.failNoPSK) { + WOLFSSL_ERROR_VERBOSE(PSK_MISSING_ERROR); + return PSK_MISSING_ERROR; + } + /* Hash data up to binders for deriving binders in PSK extension. */ ret = HashInput(ssl, input, (int)helloSz); return ret; @@ -6729,6 +6750,16 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz, #endif if (!*usingPSK) { + /* No suitable PSK was negotiated. When a mandatory external PSK is + * configured, fail with a dedicated error instead of falling back to a + * certificate handshake. This must run before the no-certificate + * BAD_BINDER check below so a PSK-only server (no cert) still reports + * PSK_MISSING_ERROR. havePSK is only set by an external-PSK callback, so + * a peer relying solely on session-ticket resumption is unaffected. */ + if (ssl->options.havePSK && ssl->options.failNoPSK) { + WOLFSSL_ERROR_VERBOSE(PSK_MISSING_ERROR); + return PSK_MISSING_ERROR; + } #ifndef NO_CERTS if (ssl->buffers.certificate == NULL #ifdef WOLFSSL_CERT_SETUP_CB @@ -6890,7 +6921,11 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz, ssl->namedGroup = ssl->session->namedGroup; *usingPSK = 2; /* generate new ephemeral key */ } - else if (ssl->options.onlyPskDheKe) { + else if (ssl->options.onlyPskDheKe || + (ssl->options.failNoPSK && !ssl->options.resuming)) { + /* A mandatory external PSK (failNoPSK) must be combined with + * (EC)DHE for forward secrecy, so reject a pure psk_ke + * negotiation. Session-ticket resumption is exempt. */ return PSK_KEY_ERROR; } else @@ -6909,6 +6944,8 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz, } else { #ifdef WOLFSSL_CERT_WITH_EXTERN_PSK + /* If no PSK is found, we remove the extension to make sure it + * is not sent back to the client */ TLSX_Remove(&ssl->extensions, TLSX_CERT_WITH_EXTERN_PSK, ssl->heap); ssl->options.certWithExternPsk = 0; #endif @@ -11849,6 +11886,21 @@ int DoTls13Finished(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } #endif +#if !defined(NO_CERTS) && !defined(NO_PSK) && \ + defined(WOLFSSL_CERT_WITH_EXTERN_PSK) + /* Verify the server sent a certificate if requested */ + if (ssl->options.side == WOLFSSL_CLIENT_END && ssl->options.pskNegotiated && + ssl->options.failNoCert) { + if ((TLSX_Find(ssl->extensions, TLSX_CERT_WITH_EXTERN_PSK) != NULL) && + (!ssl->options.havePeerCert || !ssl->options.havePeerVerify)) { + ret = NO_PEER_CERT; + WOLFSSL_MSG("TLS v1.3 server did not present peer cert"); + DoCertFatalAlert(ssl, ret); + goto cleanup; + } + } +#endif + /* check against totalSz */ if (*inOutIdx + size > totalSz) { ret = BUFFER_E; @@ -14965,6 +15017,40 @@ int wolfSSL_only_dhe_psk(WOLFSSL* ssl) } #endif /* HAVE_SUPPORTED_CURVES */ +/* Require that an external Pre-Shared Key is negotiated for the handshake to + * succeed. TLS 1.3 / DTLS 1.3 only - in (D)TLS 1.2 the use of a PSK is + * determined by the negotiated cipher suite, so a mandatory PSK is configured + * there by restricting the cipher suite list to PSK suites. + * + * ctx The SSL/TLS CTX object. + * returns BAD_FUNC_ARG when ctx is NULL or not at least TLS v1.3, 0 on success. + */ +int wolfSSL_CTX_require_psk(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL || !IsAtLeastTLSv1_3(ctx->method->version)) + return BAD_FUNC_ARG; + + ctx->failNoPSK = 1; + + return 0; +} + +/* Require that an external Pre-Shared Key is negotiated for the handshake to + * succeed. See wolfSSL_CTX_require_psk(). + * + * ssl The SSL/TLS object. + * returns BAD_FUNC_ARG when ssl is NULL or not at least TLS v1.3, 0 on success. + */ +int wolfSSL_require_psk(WOLFSSL* ssl) +{ + if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version)) + return BAD_FUNC_ARG; + + ssl->options.failNoPSK = 1; + + return 0; +} + int Tls13UpdateKeys(WOLFSSL* ssl) { if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version)) diff --git a/tests/api.c b/tests/api.c index 32b90b9a079..bf4d5631ea0 100644 --- a/tests/api.c +++ b/tests/api.c @@ -28250,7 +28250,7 @@ static int error_test(void) #endif { -9, WC_SPAN1_FIRST_E + 1 }, { -300, -300 }, - { -335, -336 }, + { -336, -336 }, { -346, -349 }, { -356, -356 }, { -358, -358 }, diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index e4d5c3821dc..ca9ebe0a086 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -1038,6 +1038,49 @@ int test_tls13_cert_with_extern_psk_handshake(void) return EXPECT_RESULT(); } +int test_tls13_cert_with_extern_psk_client_requires_cert(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && \ + !defined(NO_PSK) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && !defined(NO_CERTS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + 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); + + /* The client requests cert_with_extern_psk and mandates a peer certificate + * (WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT). The server negotiates the external + * PSK but does NOT enable cert_with_extern_psk, so it completes a PSK-only + * handshake without sending a Certificate/CertificateVerify. The client + * must therefore reject in DoTls13Finished with NO_PEER_CERT instead of + * accepting a PSK-only handshake. */ + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_cwep_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_cwep_server_cb); + /* Only the client requests cert_with_extern_psk. */ + ExpectIntEQ(wolfSSL_set_cert_with_extern_psk(ssl_c, 1), WOLFSSL_SUCCESS); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(NO_PEER_CERT)); + ExpectIntEQ(ssl_c->options.pskNegotiated, 1); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + int test_tls13_cert_with_extern_psk_requires_key_share(void) { EXPECT_DECLS; @@ -1135,15 +1178,19 @@ int test_tls13_cert_with_extern_psk_rejects_resumption(void) wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + /* Load on ssl_s, not ctx_s: ssl_s already exists (test_memio_setup created + * it) and shares the CTX key buffers. Reloading on ctx_s would free those + * buffers, leaving ssl_s with a dangling key that crashes when the server + * decodes it for CertificateVerify. */ #if defined(HAVE_ECC) - ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_s, eccCertFile, + ExpectTrue(wolfSSL_use_certificate_file(ssl_s, eccCertFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); - ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_s, eccKeyFile, + ExpectTrue(wolfSSL_use_PrivateKey_file(ssl_s, eccKeyFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); #else - ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + ExpectTrue(wolfSSL_use_certificate_file(ssl_s, svrCertFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); - ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + ExpectTrue(wolfSSL_use_PrivateKey_file(ssl_s, svrKeyFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); #endif @@ -1328,6 +1375,10 @@ int test_tls13_cert_with_extern_psk_sh_missing_key_share(void) wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + /* Load on ssl_s, not ctx_s: ssl_s already exists (test_memio_setup created + * it) and shares the CTX key buffers. Reloading on ctx_s would free those + * buffers, leaving ssl_s with a dangling key that crashes when the server + * decodes it for CertificateVerify. */ #if defined(HAVE_ECC) ExpectTrue(wolfSSL_use_certificate_file(ssl_s, eccCertFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); @@ -1423,15 +1474,19 @@ int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void) wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + /* Load on ssl_s, not ctx_s: ssl_s already exists (test_memio_setup created + * it) and shares the CTX key buffers. Reloading on ctx_s would free those + * buffers, leaving ssl_s with a dangling key that crashes when the server + * decodes it for CertificateVerify. */ #if defined(HAVE_ECC) - ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_s, eccCertFile, + ExpectTrue(wolfSSL_use_certificate_file(ssl_s, eccCertFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); - ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_s, eccKeyFile, + ExpectTrue(wolfSSL_use_PrivateKey_file(ssl_s, eccKeyFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); #else - ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + ExpectTrue(wolfSSL_use_certificate_file(ssl_s, svrCertFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); - ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + ExpectTrue(wolfSSL_use_PrivateKey_file(ssl_s, svrKeyFile, CERT_FILETYPE) == WOLFSSL_SUCCESS); #endif @@ -1502,6 +1557,570 @@ int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void) return EXPECT_RESULT(); } +int test_tls13_fail_if_no_psk_api(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + /* NULL arguments are rejected. */ + ExpectIntEQ(wolfSSL_CTX_require_psk(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_require_psk(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* wolfSSL_CTX_require_psk() sets the failNoPSK bit on the CTX, and a new + * SSL object inherits it. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_require_psk(ctx), 0); + ExpectIntEQ(ctx->failNoPSK, 1); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ssl->options.failNoPSK, 1); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* wolfSSL_require_psk() sets the option directly on the SSL object. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ssl->options.failNoPSK, 0); + ExpectIntEQ(wolfSSL_require_psk(ssl), 0); + ExpectIntEQ(ssl->options.failNoPSK, 1); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + +#ifndef WOLFSSL_NO_TLS12 + /* The API is TLS 1.3 only - a TLS 1.2 context is rejected so users are not + * misled into thinking the option applies to TLS 1.2. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectIntEQ(wolfSSL_CTX_require_psk(ctx), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* 32-byte external PSK used by the fail-if-no-PSK test callbacks. */ +static const unsigned char test_tls13_fnp_psk[32] = { + 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, + 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, + 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, + 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A +}; + +static unsigned int test_tls13_fnp_client_cb(WOLFSSL* ssl, const char* hint, + char* identity, unsigned int id_max_len, unsigned char* key, + unsigned int key_max_len) +{ + (void)ssl; + (void)hint; + if (id_max_len == 0 || key_max_len < sizeof(test_tls13_fnp_psk)) + return 0; + XSTRNCPY(identity, "fnp_client", id_max_len); + XMEMCPY(key, test_tls13_fnp_psk, sizeof(test_tls13_fnp_psk)); + return (unsigned int)sizeof(test_tls13_fnp_psk); +} + +static unsigned int test_tls13_fnp_server_cb(WOLFSSL* ssl, const char* id, + unsigned char* key, unsigned int key_max_len) +{ + (void)ssl; + if (key_max_len < sizeof(test_tls13_fnp_psk) || id == NULL) + return 0; + if (XSTRCMP(id, "fnp_client") != 0) + return 0; + XMEMCPY(key, test_tls13_fnp_psk, sizeof(test_tls13_fnp_psk)); + return (unsigned int)sizeof(test_tls13_fnp_psk); +} + +/* Server PSK callback that finds no PSK for any offered identity (returns 0), + * mirroring an application whose lookup misses. */ +static unsigned int test_tls13_fnp_reject_server_cb(WOLFSSL* ssl, + const char* id, unsigned char* key, unsigned int key_max_len) +{ + (void)ssl; + (void)id; + (void)key; + (void)key_max_len; + return 0; +} +#endif + +int test_tls13_fail_if_no_psk_handshake(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + const char appMsg[] = "fail_if_no_psk test"; + char readBuf[sizeof(appMsg)]; + int readSz; + + 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); + + /* Both endpoints require a PSK to be negotiated. */ + wolfSSL_require_psk(ssl_c); + wolfSSL_require_psk(ssl_s); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_fnp_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_fnp_server_cb); + + /* A PSK is configured on both ends, so the handshake completes. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_c->options.failNoPSK, 1); + ExpectIntEQ(ssl_s->options.failNoPSK, 1); + ExpectIntEQ(ssl_c->options.pskNegotiated, 1); + ExpectIntEQ(ssl_s->options.pskNegotiated, 1); + + /* Application data flows over the PSK-derived keys. */ + ExpectIntEQ(wolfSSL_write(ssl_c, appMsg, (int)XSTRLEN(appMsg)), + (int)XSTRLEN(appMsg)); + readSz = wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)); + ExpectIntEQ(readSz, (int)XSTRLEN(appMsg)); + ExpectIntEQ(XMEMCMP(readBuf, appMsg, (size_t)readSz), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_fail_if_no_psk_rejects_no_psk(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !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; + + 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); + + /* The server requires a PSK and has one configured, but the client offers + * none. The server must abort the handshake with PSK_MISSING_ERROR rather + * than fall back to a certificate-only handshake. */ + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_require_psk(ssl_s); +#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 + /* Only the server installs a PSK callback; the client sends no PSK. */ + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_fnp_server_cb); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(PSK_MISSING_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_fail_if_no_psk_client_no_psk_configured(void) +{ + EXPECT_DECLS; +/* WOLFSSL_QT force-enables havePSK on every CTX (see wolfSSL_CTX_new), so the + * "no PSK configured" precondition this test relies on cannot be set up there. */ +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && !defined(WOLFSSL_QT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !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; + + 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); + + /* The client requires a PSK but configures none. Because failNoPSK is gated + * on havePSK, this must NOT hard-fail: a normal certificate handshake is + * expected to succeed instead of raising PSK_MISSING_ERROR. */ + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, NULL); + wolfSSL_require_psk(ssl_c); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); +#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); + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_c, caEccCertFile, + NULL) == 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); + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, + NULL) == WOLFSSL_SUCCESS); +#endif + + ExpectIntEQ(ssl_c->options.failNoPSK, 1); + ExpectIntEQ(ssl_c->options.havePSK, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_c->options.pskNegotiated, 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_fail_if_no_psk_client_rejects(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !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; + + 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); + + /* The client requires a PSK and has one configured, but the server has no + * PSK callback so it never selects one. The client must abort with + * PSK_MISSING_ERROR rather than fall back to a cert-only handshake. */ + wolfSSL_require_psk(ssl_c); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); +#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 + /* Only the client installs a PSK callback. */ + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_fnp_client_cb); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(PSK_MISSING_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_fail_if_no_psk_requires_dhe(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* Baseline: a pure psk_ke handshake (no DHE) succeeds when the PSK is + * optional, confirming the setup itself is valid. */ + 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); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_fnp_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_fnp_server_cb); + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_c), 0); + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_s->options.pskNegotiated, 1); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; ssl_s = NULL; ctx_c = NULL; ctx_s = NULL; + + /* With failNoPSK on the server, the same pure psk_ke negotiation (no + * forward secrecy) must be rejected with PSK_KEY_ERROR. */ + 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); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_require_psk(ssl_s); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_fnp_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_fnp_server_cb); + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_c), 0); + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_fail_if_no_psk_client_requires_dhe(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* Only the client mandates the PSK (failNoPSK). The server does NOT set + * failNoPSK and is configured for pure psk_ke (wolfSSL_no_dhe_psk), so it + * accepts the PSK and replies with a ServerHello that omits a key_share. + * When the client processes that ServerHello in SetupPskKey, the mandatory + * external PSK has no (EC)DHE for forward secrecy, so the client must + * reject it with PSK_KEY_ERROR. This exercises the client-side branch that + * the server-only requires_dhe test cannot reach (there the server rejects + * the pure psk_ke negotiation first). */ + 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); + wolfSSL_require_psk(ssl_c); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_fnp_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_fnp_server_cb); + /* Force the server to negotiate a pure psk_ke (no (EC)DHE) exchange. */ + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_s), 0); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(PSK_KEY_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_fail_if_no_psk_resumption_exempt_from_dhe(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && defined(HAVE_SESSION_TICKET) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !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; + WOLFSSL_SESSION *sess = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + + /* Step 1: full TLS 1.3 handshake to obtain a session ticket. The server + * cert/key are already loaded by test_memio_setup; the default ticket + * encryption callback lets a fresh server CTX below decrypt the ticket. */ + 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); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Drain the NewSessionTicket post-handshake message. */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + /* Step 2: resume with a pure psk_ke (no (EC)DHE) exchange while the server + * has the mandatory-PSK requirement set. A session-ticket resumption is + * exempt from the mandatory-(EC)DHE rule, so the handshake must succeed + * (an external PSK in the same situation would be rejected). */ + 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); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_require_psk(ssl_s); + ExpectIntEQ(wolfSSL_no_dhe_psk(ssl_c), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_c->options.resuming, 1); + ExpectIntEQ(ssl_s->options.resuming, 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_fail_if_no_psk_server_rejects_offered_psk(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* Baseline: the client offers a PSK but the server's callback rejects it + * (returns 0). Without failNoPSK the server (which has a certificate from + * test_memio_setup) falls back to a normal certificate handshake, which + * succeeds and negotiates no PSK. */ + 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); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_fnp_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_fnp_reject_server_cb); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_s->options.pskNegotiated, 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* With failNoPSK the same rejected-PSK negotiation must abort with + * PSK_MISSING_ERROR instead of falling back to the certificate handshake. */ + 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); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_require_psk(ssl_s); + wolfSSL_set_psk_client_callback(ssl_c, test_tls13_fnp_client_cb); + wolfSSL_set_psk_server_callback(ssl_s, test_tls13_fnp_reject_server_cb); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(PSK_MISSING_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_fail_if_no_psk_no_cert_server(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Build the CTXs manually so the server has NO certificate configured, + * matching a PSK-only deployment. The client offers a PSK, the server's + * callback rejects it, and failNoPSK is set: the server must report + * PSK_MISSING_ERROR (not BAD_BINDER, and no certificate fallback). */ + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + /* PSK callbacks are set on the CTX so PSK cipher suites are available when + * the (cert-less) SSL objects are created. */ + wolfSSL_CTX_set_psk_client_callback(ctx_c, test_tls13_fnp_client_cb); + + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + wolfSSL_CTX_set_psk_server_callback(ctx_s, test_tls13_fnp_reject_server_cb); + wolfSSL_CTX_require_psk(ctx_s); + + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(PSK_MISSING_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + #if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ !defined(NO_WOLFSSL_SERVER) && defined(HAVE_ECC) && \ defined(BUILD_TLS_AES_128_GCM_SHA256) && \ diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index e16c97e1656..f7042568e83 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -68,10 +68,21 @@ int test_tls13_serverhello_bad_cipher_suites(void); int test_tls13_psk_no_cert_bad_binder(void); int test_tls13_cert_with_extern_psk_apis(void); int test_tls13_cert_with_extern_psk_handshake(void); +int test_tls13_cert_with_extern_psk_client_requires_cert(void); int test_tls13_cert_with_extern_psk_requires_key_share(void); int test_tls13_cert_with_extern_psk_rejects_resumption(void); int test_tls13_cert_with_extern_psk_sh_missing_key_share(void); int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void); +int test_tls13_fail_if_no_psk_api(void); +int test_tls13_fail_if_no_psk_handshake(void); +int test_tls13_fail_if_no_psk_rejects_no_psk(void); +int test_tls13_fail_if_no_psk_client_no_psk_configured(void); +int test_tls13_fail_if_no_psk_client_rejects(void); +int test_tls13_fail_if_no_psk_requires_dhe(void); +int test_tls13_fail_if_no_psk_client_requires_dhe(void); +int test_tls13_fail_if_no_psk_resumption_exempt_from_dhe(void); +int test_tls13_fail_if_no_psk_server_rejects_offered_psk(void); +int test_tls13_fail_if_no_psk_no_cert_server(void); int test_tls13_ticket_peer_cert_reverify(void); int test_tls13_clear_preserves_psk_dhe(void); int test_tls13_cipher_fuzz_aes128_gcm_sha256(void); @@ -130,10 +141,21 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void); TEST_DECL_GROUP("tls13", test_tls13_psk_no_cert_bad_binder), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_apis), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_client_requires_cert), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_requires_key_share), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_rejects_resumption), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_missing_key_share), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_api), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_rejects_no_psk), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_no_psk_configured), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_rejects), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_requires_dhe), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_requires_dhe), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_resumption_exempt_from_dhe), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_server_rejects_offered_psk), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_no_cert_server), \ TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify), \ TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_gcm_sha256), \ diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index 379008afb01..2bb162c1ba9 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -83,6 +83,7 @@ enum wolfSSL_ErrorCodes { SERVER_HINT_ERROR = -332, /* psk server hint error */ PSK_KEY_ERROR = -333, /* psk key error */ DUPE_ENTRY_E = -334, /* duplicate entry error */ + PSK_MISSING_ERROR = -335, /* psk missing */ GETTIME_ERROR = -337, /* gettimeofday failed ??? */ GETITIMER_ERROR = -338, /* getitimer failed ??? */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 3cd37c739bd..650fa016e95 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3993,6 +3993,7 @@ struct WOLFSSL_CTX { byte verifyNone:1; byte failNoCert:1; byte failNoCertxPSK:1; /* fail if no cert with the exception of PSK*/ + byte failNoPSK:1; /* fail if no PSK is negotiated */ byte sessionCacheOff:1; byte sessionCacheFlushOff:1; #ifdef HAVE_EXT_CACHE @@ -5133,6 +5134,7 @@ struct Options { word16 verifyNone:1; word16 failNoCert:1; word16 failNoCertxPSK:1; /* fail for no cert except with PSK */ + word16 failNoPSK:1; /* fail if no PSK is negotiated */ word16 downgrade:1; /* allow downgrade of versions */ word16 resuming:1; #ifdef HAVE_SECURE_RENEGOTIATION diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 738828b127b..470df6fc8f4 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1499,6 +1499,8 @@ WOLFSSL_API int wolfSSL_CTX_no_dhe_psk(WOLFSSL_CTX* ctx); WOLFSSL_API int wolfSSL_no_dhe_psk(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_CTX_only_dhe_psk(WOLFSSL_CTX* ctx); WOLFSSL_API int wolfSSL_only_dhe_psk(WOLFSSL* ssl); +WOLFSSL_API int wolfSSL_CTX_require_psk(WOLFSSL_CTX* ctx); +WOLFSSL_API int wolfSSL_require_psk(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_update_keys(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_key_update_response(WOLFSSL* ssl, int* required); WOLFSSL_API int wolfSSL_CTX_allow_post_handshake_auth(WOLFSSL_CTX* ctx); @@ -3191,6 +3193,8 @@ enum { /* ssl Constants */ WOLFSSL_VERIFY_CLIENT_ONCE = 1 << 2, WOLFSSL_VERIFY_POST_HANDSHAKE = 1 << 3, WOLFSSL_VERIFY_FAIL_EXCEPT_PSK = 1 << 4, + /* 1 << 5 reserved (previously WOLFSSL_VERIFY_FAIL_IF_NO_PSK; a mandatory + * PSK is now requested with wolfSSL_[CTX_]require_psk()). */ WOLFSSL_VERIFY_DEFAULT = 1 << 9, WOLFSSL_SESS_CACHE_OFF = 0x0000,