diff --git a/src/internal.c b/src/internal.c index f3657517e65..4f270b9c84d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -41623,7 +41623,13 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (!session) { WOLFSSL_MSG("Session lookup for resume failed"); ssl->options.resuming = 0; - } else { + } + else if (ClientAuthRequired(ssl) && !session->peerAuthOk) { + WOLFSSL_MSG("Session lacks client auth, do full handshake"); + ssl->options.resuming = 0; + ssl->options.peerAuthGood = 0; + } + else { if (MatchSuite(ssl, &clSuites) < 0) { WOLFSSL_MSG("Unsupported cipher suite, OldClientHello"); return UNSUPPORTED_SUITE; @@ -41659,6 +41665,25 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif /* OLD_HELLO_ALLOWED */ +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) +/* Would a full handshake on this connection require a verified client + * certificate? Mirrors DoClientKeyExchange, the empty-Certificate rule in + * ProcessPeerCerts and DoTls13Finished. */ +int ClientAuthRequired(const WOLFSSL* ssl) +{ + if (ssl->options.side != WOLFSSL_SERVER_END) + return 0; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) + /* Sends no CertificateRequest in the handshake, so it records no + * outcome to inherit. */ + if (ssl->options.verifyPostHandshake) + return 0; +#endif + return ssl->options.mutualAuth || + (ssl->options.verifyPeer && ssl->options.failNoCert); +} +#endif /* !NO_CERTS && !WOLFSSL_NO_CLIENT_AUTH */ + #ifndef WOLFSSL_NO_TLS12 /** @@ -41707,6 +41732,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif ) { int secretSz = SECRET_LEN; + /* The secret comes from the callback, and nothing records an auth + * outcome for it. */ + if (ClientAuthRequired(ssl)) { + WOLFSSL_MSG("Session secret callback cannot assert client auth," + " do full handshake"); + ssl->options.resuming = 0; + ssl->options.peerAuthGood = 0; + return ret; + } WOLFSSL_MSG("Calling session secret callback"); ret = wc_RNG_GenerateBlock(ssl->rng, ssl->arrays->serverRandom, RAN_LEN); @@ -41754,6 +41788,12 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) ssl->options.resuming = 0; return ret; } + if (ClientAuthRequired(ssl) && !session->peerAuthOk) { + WOLFSSL_MSG("Session lacks client auth, do full handshake"); + ssl->options.resuming = 0; + ssl->options.peerAuthGood = 0; + return ret; + } #if defined(HAVE_SESSION_TICKET) && \ (defined(HAVE_SNI) || defined(HAVE_ALPN)) /* Do not resume session if sniHash/alpnHash do not match. */ @@ -41956,6 +41996,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) /* Reset to sane value for SCR */ ssl->options.resuming = 0; ssl->arrays->sessionIDSz = 0; +#ifdef HAVE_SESSION_TICKET + /* Only this ClientHello may set them, in TLSX_SessionTicket_Parse(), + * which ignores the extension during SCR - so an SCR hello inherits + * the connection's. */ + if (!IsSCR(ssl)) { + ssl->options.useTicket = 0; + ssl->options.createTicket = 0; + } +#endif /* protocol version, random and session id length check */ if (OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN > helloSz) @@ -43443,6 +43492,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) it->suite[0] = ssl->options.cipherSuite0; it->suite[1] = ssl->options.cipherSuite; + /* Both arms below only add to this, and an async re-entry may not + * have re-zeroed the buffer. A resumed connection sends no + * Certificate, so it carries what it resumed. */ + it->flags = 0; + if (ssl->options.resuming ? ssl->session->peerAuthOk : + (ssl->options.havePeerCert && ssl->options.havePeerVerify)) { + it->flags |= WOLFSSL_TICKET_FLAG_PEER_AUTH; + } + #ifdef WOLFSSL_EARLY_DATA c32toa(ssl->options.maxEarlyDataSz, it->maxEarlyDataSz); #endif @@ -43457,7 +43515,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifndef NO_ASN_TIME c32toa(LowResTimer(), it->timestamp); #endif - it->haveEMS = (byte) ssl->options.haveEMS; + if (ssl->options.haveEMS) + it->flags |= WOLFSSL_TICKET_FLAG_EMS; } else { #ifdef WOLFSSL_TLS13 @@ -43997,6 +44056,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifdef HAVE_ALPN XMEMCPY(ssl->session->alpnHash, it->alpnHash, TICKET_BINDING_HASH_SZ); #endif + /* After the DupSession above, which would otherwise overwrite it. */ + ssl->session->peerAuthOk = + (it->flags & WOLFSSL_TICKET_FLAG_PEER_AUTH) ? 1 : 0; if (!IsAtLeastTLSv1_3(ssl->version)) { if (ssl->arrays == NULL) @@ -44004,7 +44066,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) XMEMCPY(ssl->arrays->masterSecret, it->msecret, SECRET_LEN); /* Copy the haveExtendedMasterSecret property from the ticket to * the saved session, so the property may be checked later. */ - ssl->session->haveEMS = it->haveEMS; + ssl->session->haveEMS = + (it->flags & WOLFSSL_TICKET_FLAG_EMS) ? 1 : 0; ato32((const byte*)&it->timestamp, &ssl->session->bornOn); #ifndef NO_RESUME_SUITE_CHECK ssl->session->cipherSuite0 = it->suite[0]; @@ -44085,7 +44148,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) c32toa((word32)(milliBornOn >> 32), it->timestamp); c32toa((word32)milliBornOn , it->timestamp + OPAQUE32_LEN); #endif - it->haveEMS = (byte)sess->haveEMS; + it->flags = (byte)((sess->haveEMS ? WOLFSSL_TICKET_FLAG_EMS : 0) | + (sess->peerAuthOk ? WOLFSSL_TICKET_FLAG_PEER_AUTH : 0)); c32toa(sess->ticketAdd, it->ageAdd); c16toa(sess->namedGroup, it->namedGroup); if (sess->ticketNonce.len <= MAX_TICKET_NONCE_STATIC_SZ) { diff --git a/src/ssl.c b/src/ssl.c index 2443fbd7829..d8ff8ac5bfe 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5800,6 +5800,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.connReset = 0; ssl->options.sentNotify = 0; ssl->options.closeNotify = 0; + /* Per-connection budgets, not cumulative across a reused object. */ + ssl->options.alertCount = 0; + ssl->options.emptyRecordCount = 0; ssl->options.sendVerify = 0; ssl->options.serverState = NULL_STATE; ssl->options.clientState = NULL_STATE; @@ -5823,8 +5826,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, DYNAMIC_TYPE_TMP_BUFFER); ssl->buffers.certVerifyMsg.buffer = NULL; ssl->buffers.certVerifyMsg.length = 0; - ssl->fragOffset = 0; #endif + ssl->fragOffset = 0; + ssl->options.buildingMsg = 0; ssl->options.processReply = 0; /* doProcessInit */ ssl->options.havePeerVerify = 0; ssl->options.havePeerCert = 0; @@ -5933,7 +5937,18 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, #endif #endif ssl->options.rejectTicket = 0; + ssl->options.useTicket = 0; + ssl->options.createTicket = 0; #endif + /* A server keeps its session across the reset, but the next client + * has not asked for it. A client is the side that resumes what it + * held, so it keeps both. */ + if ((ssl->options.side == WOLFSSL_SERVER_END) && + (ssl->session != NULL)) { + ssl->session->haveAltSessionID = 0; + ForceZero(ssl->session->altSessionID, ID_LEN); + ssl->session->peerAuthOk = 0; + } #ifdef WOLFSSL_EARLY_DATA ssl->earlyData = no_early_data; ssl->earlyDataSz = 0; diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 5217856870d..47cc7a18499 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -2825,6 +2825,8 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif + /* peerAuthOk, last so an older reader stops before it. */ + size += OPAQUE8_LEN; if (p != NULL) { unsigned char *data; @@ -2920,6 +2922,7 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif + data[idx++] = sess->peerAuthOk; } #endif @@ -3226,6 +3229,11 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, #endif #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #endif + /* Absent from a blob written before the field existed, which leaves the + * zero a new session carries. */ + if (i - idx == OPAQUE8_LEN) { + s->peerAuthOk = (data[idx++] != 0); + } (void)idx; if (sess != NULL) { @@ -3795,6 +3803,14 @@ void SetupSession(WOLFSSL* ssl) session->haveEMS = 1; else session->haveEMS = ssl->options.haveEMS; + /* Server only: on a client these flags describe the server certificate, + * a different statement that nothing reads. A resumed connection sends no + * Certificate, so it keeps what it inherited. */ + if (!ssl->options.resuming && + (ssl->options.side == WOLFSSL_SERVER_END)) { + session->peerAuthOk = (byte)(ssl->options.havePeerCert && + ssl->options.havePeerVerify); + } #ifdef WOLFSSL_SESSION_ID_CTX /* If using compatibility layer then check for and copy over session context * id. */ diff --git a/src/tls13.c b/src/tls13.c index 7ca1e3e0458..ea7fbf5f8ea 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -6890,6 +6890,11 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, #endif if (ret == 0) DoClientTicketFinalize(ssl, current->it, current->sess); + if (ret == 0 && ClientAuthRequired(ssl) && + !ssl->session->peerAuthOk) { + WOLFSSL_MSG("Ticket session lacks client auth, skipping PSK"); + ret = WOLFSSL_FATAL_ERROR; + } if (current->sess_free_cb != NULL) { current->sess_free_cb(ssl, current->sess, ¤t->sess_free_cb_ctx); diff --git a/tests/api.c b/tests/api.c index fad41d325ac..b74f23eb92f 100644 --- a/tests/api.c +++ b/tests/api.c @@ -22427,6 +22427,61 @@ static int test_wolfSSL_sigalg_info(void) return EXPECT_RESULT(); } +/* peerAuthOk is the last field, so a blob written before it existed is simply + * short and an older reader ignores the trailing byte. */ +static int test_wolfSSL_i2d_SSL_SESSION_peer_auth(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_EXT_CACHE) && \ + !defined(NO_SESSION_CACHE) + WOLFSSL_SESSION* sess = NULL; + WOLFSSL_SESSION* restored = NULL; + unsigned char* der = NULL; + const unsigned char* ptr = NULL; + unsigned char* pp = NULL; + int sz = 0; + + ExpectNotNull(sess = wolfSSL_SESSION_new()); + if (sess != NULL) { + sess->peerAuthOk = 1; + sess->isSetup = 1; + } + ExpectIntGT((sz = wolfSSL_i2d_SSL_SESSION(sess, NULL)), 0); + ExpectNotNull(der = (unsigned char*)XMALLOC((size_t)sz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + pp = der; + ExpectIntGT(wolfSSL_i2d_SSL_SESSION(sess, &pp), 0); + + /* Round trip keeps it. */ + ptr = der; + ExpectNotNull(restored = wolfSSL_d2i_SSL_SESSION(NULL, &ptr, (long)sz)); + if (restored != NULL) + ExpectIntEQ(restored->peerAuthOk, 1); + wolfSSL_SESSION_free(restored); + restored = NULL; + + /* One byte shorter, as written before the field existed: must import and + * must not claim the peer authenticated. */ + ptr = der; + ExpectNotNull(restored = wolfSSL_d2i_SSL_SESSION(NULL, &ptr, (long)sz - 1)); + if (restored != NULL) + ExpectIntEQ(restored->peerAuthOk, 0); + wolfSSL_SESSION_free(restored); + restored = NULL; + + /* A length past the blob must not read whatever follows it. */ + ptr = der; + ExpectNotNull(restored = wolfSSL_d2i_SSL_SESSION(NULL, &ptr, (long)sz + 8)); + if (restored != NULL) + ExpectIntEQ(restored->peerAuthOk, 0); + wolfSSL_SESSION_free(restored); + + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_SESSION_free(sess); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_d2i_SSL_SESSION_bounds_check(void) { EXPECT_DECLS; @@ -43620,6 +43675,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_ciphersuite_auth), TEST_DECL(test_wolfSSL_sigalg_info), /* Can't memory test as tcp_connect aborts. */ + TEST_DECL(test_wolfSSL_i2d_SSL_SESSION_peer_auth), TEST_DECL(test_wolfSSL_d2i_SSL_SESSION_bounds_check), TEST_DECL(test_wolfSSL_sk_GENERAL_NAME), TEST_DECL(test_wolfSSL_GENERAL_NAME_print), diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index a3d343d88ee..b20ab6c15ff 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3098,6 +3098,610 @@ int test_tls12_resume_ticket_wrong_suite(void) return EXPECT_RESULT(); } +/* Catches per-connection Options state that wolfSSL_clear() forgets, rather + * than one field at a time. Every Options is born inside + * XMEMSET(ssl, 0, sizeof(WOLFSSL)), so padding and bitfield slack are zero in + * both objects and only written fields can differ. */ +int test_tls12_clear_resets_options(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *fresh = NULL; + struct test_memio_ctx test_ctx; + char msg[] = "test"; + char reply[sizeof(msg)]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Data both ways and a bidirectional close, so record-layer and shutdown + * state is covered too, not just the handshake. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_c, reply, (int)sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SHUTDOWN_NOT_DONE); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_shutdown(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + /* Same CTX, never handshaked, so its Options is what a reset should + * produce. Created after the handshake so the CTX is fully configured. */ + ExpectNotNull(fresh = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + + if ((ssl_s != NULL) && (fresh != NULL)) { + /* Negotiated results, kept on purpose for the accessors. */ + ssl_s->options.cipherSuite0 = fresh->options.cipherSuite0; + ssl_s->options.cipherSuite = fresh->options.cipherSuite; + ssl_s->options.hashAlgo = fresh->options.hashAlgo; + ssl_s->options.sigAlgo = fresh->options.sigAlgo; + ssl_s->options.haveDH = fresh->options.haveDH; + /* wolfSSL_get_shutdown() reports a completed bidirectional shutdown + * from this after a clear, by documented intent. */ + ssl_s->options.shutdownDone = fresh->options.shutdownDone; + /* Reset only under WOLFSSL_ASYNC_CRYPT. */ + ssl_s->options.asyncState = fresh->options.asyncState; + /* Same condition the fields are declared under. */ +#if !defined(NO_DH) && !defined(WOLFSSL_OLD_PRIME_CHECK) && \ + !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + ssl_s->options.dhDoKeyTest = fresh->options.dhDoKeyTest; + ssl_s->options.dhKeyTested = fresh->options.dhKeyTested; +#endif + /* // NOLINTNEXTLINE(bugprone-suspicious-memory-comparison) */ + ExpectIntEQ(XMEMCMP(&ssl_s->options, &fresh->options, sizeof(Options)), + 0); + if (EXPECT_FAIL()) { + /* Offsets rather than names: Options is mostly bitfields. */ + const byte* a = (const byte*)&ssl_s->options; + const byte* b = (const byte*)&fresh->options; + word32 k; + for (k = 0; k < (word32)sizeof(Options); k++) { + if (a[k] != b[k]) { + fprintf(stderr, "Options differ at byte %u:" + " reused=0x%02x fresh=0x%02x\n", k, a[k], b[k]); + } + } + } + } + + wolfSSL_free(fresh); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* mutual_auth is an alternative to failNoCert in the full handshake, so + * resumption has to agree. */ +int test_tls12_resume_ticket_mutual_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = 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_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s2, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(wolfSSL_mutual_auth(ssl_s2, 1), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + test_memio_do_handshake(ssl_c2, ssl_s2, 20, NULL); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A post-handshake-auth server records no outcome, so it must still resume. */ +int test_tls13_resume_psk_post_handshake_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_POST_HANDSHAKE_AUTH) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + 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_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT | WOLFSSL_VERIFY_POST_HANDSHAKE, + NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + 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)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + 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_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT | WOLFSSL_VERIFY_POST_HANDSHAKE, + NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 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(); +} + +/* TLS 1.3: a PSK must not stand in for the required client certificate. */ +int test_tls13_resume_psk_client_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + + 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_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Drives the NewSessionTicket onto the client session. */ + 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)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + 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_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + /* The skipped PSK becomes a full handshake. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + + 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(); +} + +/* A session that did authenticate the client must still resume. */ +int test_tls13_resume_psk_client_auth_ok(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + byte readBuf[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* CTXs first: credentials must precede the objects inheriting them. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + 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_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + 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)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + 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_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 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(); +} + +/* The outcome has to survive an i2d/d2i external cache. No internal cache and + * no ticket, so only the deserialized session can carry it. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(OPENSSL_EXTRA) && \ + defined(HAVE_EXT_CACHE) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) +static byte test_extcache_der[2048]; +static int test_extcache_derSz = 0; +static int test_extcache_gets = 0; + +static int test_extcache_new_cb(WOLFSSL* ssl, WOLFSSL_SESSION* sess) +{ + unsigned char* p = test_extcache_der; + int sz; + + (void)ssl; + sz = wolfSSL_i2d_SSL_SESSION(sess, NULL); + if ((sz > 0) && (sz <= (int)sizeof(test_extcache_der))) { + sz = wolfSSL_i2d_SSL_SESSION(sess, &p); + if (sz > 0) + test_extcache_derSz = sz; + } + return 0; +} + +static WOLFSSL_SESSION* test_extcache_get_cb(WOLFSSL* ssl, + const unsigned char* id, int len, int* ref) +{ + const unsigned char* p = test_extcache_der; + + (void)ssl; + (void)id; + (void)len; + test_extcache_gets++; + /* A fresh object each time, so wolfSSL owns it. */ + *ref = 0; + if (test_extcache_derSz <= 0) + return NULL; + return wolfSSL_d2i_SSL_SESSION(NULL, &p, (long)test_extcache_derSz); +} +#endif + +int test_tls12_ext_cache_client_auth_resume(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(OPENSSL_EXTRA) && \ + defined(HAVE_EXT_CACHE) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + + test_extcache_derSz = 0; + test_extcache_gets = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* CTXs first: credentials must precede the objects inheriting them. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + /* Only the external cache answers. */ + if (ctx_s != NULL) { + wolfSSL_CTX_set_session_cache_mode(ctx_s, + WOLFSSL_SESS_CACHE_SERVER | WOLFSSL_SESS_CACHE_NO_INTERNAL); + wolfSSL_CTX_sess_set_new_cb(ctx_s, test_extcache_new_cb); + wolfSSL_CTX_sess_set_get_cb(ctx_s, test_extcache_get_cb); + } + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + ExpectIntGT(test_extcache_derSz, 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + /* The server can only get it back through d2i. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_set_verify(ssl_s2, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); + ExpectIntGT(test_extcache_gets, 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A ticket minted under VERIFY_NONE, replayed on the same CTX against a + * connection requiring a client certificate, must be declined. */ +int test_tls12_resume_ticket_client_auth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = 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_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectIntGT(sess->ticketLen, 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 0); + + /* Same CTX, so the ticket decrypts. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s2, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + + /* The decline becomes a full handshake. */ + ExpectIntNE(test_memio_do_handshake(ssl_c2, ssl_s2, 20, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 0); + if (ssl_s2 != NULL) { + ExpectIntEQ(ssl_s2->options.resuming, 0); + /* Rejected for the certificate, not accepted off the ticket. */ + ExpectIntEQ(ssl_s2->error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + } + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A client that does present its certificate must still abbreviate. */ +int test_tls12_resume_ticket_client_auth_ok(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* CTXs first: credentials must precede the objects inheriting them. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s2, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s2), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* useTicket and peerAuthOk are per-handshake: an object reused with + * wolfSSL_clear() must not carry either into the next ClientHello, or the + * retained session is resumed for a client that never held it. The id offered + * below is dropped from the cache, so only stale state could resume it. */ +int test_tls12_reuse_clears_use_ticket(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_CERTS) && !defined(NO_RSA) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL, *ctx_s2 = NULL; + WOLFSSL *ssl_c = NULL, *ssl_c2 = NULL, *ssl_c3 = NULL; + WOLFSSL *ssl_s = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION* sess = NULL; + struct test_memio_ctx test_ctx; + int useTicketAfterClear = -1; + int peerAuthAfterClear = -1; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* CTXs first: credentials must precede the objects inheriting them. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->options.useTicket, 1); +#ifndef WOLFSSL_NO_CLIENT_AUTH + ExpectIntEQ(ssl_s->session->peerAuthOk, 1); +#endif + } + /* A different statement on the client, and unread there. */ + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->session->peerAuthOk, 0); + + /* From a server issuing no tickets, so the next ClientHello carries a + * session id only. Not modified in place: it may be cache-backed. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s2, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c2)); + /* Premise, not the fix: a ticket would resume legitimately. */ + ExpectIntEQ(sess->ticketLen, 0); + /* The two ends share one cache in-process. */ + ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_s2, sess), 1); + + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + /* Assert last, or a failure here skips the behavioural half. */ + if (ssl_s != NULL) { + useTicketAfterClear = ssl_s->options.useTicket; + peerAuthAfterClear = ssl_s->session->peerAuthOk; + } + + /* Point the reused server at the new transport. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c3, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (ssl_s != NULL) { + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + } + ExpectIntEQ(wolfSSL_set_session(ssl_c3, sess), WOLFSSL_SUCCESS); + /* May fail; what matters is it did not hand over the retained session. */ + if ((ssl_c3 != NULL) && (ssl_s != NULL)) + test_memio_do_handshake(ssl_c3, ssl_s, 10, NULL); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.resuming, 0); + ExpectIntEQ(useTicketAfterClear, 0); + ExpectIntEQ(peerAuthAfterClear, 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_c3); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_s2); +#endif + return EXPECT_RESULT(); +} + /* A ticket the server can't honor must fall back to a full handshake (RFC 5077 * 3.4), even under a different suite than the cached ticket session - the * F-5811 suite check must not abort it. The second handshake uses a fresh diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 20e6e17fd15..779635c0710 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -56,6 +56,15 @@ int test_tls_version_mask_alert_record(void); int test_tls_version_error_alert_mapping(void); int test_tls12_etm_failed_resumption(void); int test_tls12_resume_ticket_wrong_suite(void); +int test_tls12_clear_resets_options(void); +int test_tls12_resume_ticket_mutual_auth(void); +int test_tls13_resume_psk_post_handshake_auth(void); +int test_tls13_resume_psk_client_auth(void); +int test_tls13_resume_psk_client_auth_ok(void); +int test_tls12_ext_cache_client_auth_resume(void); +int test_tls12_resume_ticket_client_auth(void); +int test_tls12_resume_ticket_client_auth_ok(void); +int test_tls12_reuse_clears_use_ticket(void); int test_tls12_resume_ticket_decline_fallback(void); int test_tls12_ticket_dropped_on_bad_finished(void); int test_tls12_ticket_cached_after_finished(void); @@ -115,6 +124,15 @@ int test_tls12_aesgcm_record_nonce_unique(void); TEST_DECL_GROUP("tls", test_tls_version_error_alert_mapping), \ TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_wrong_suite), \ + TEST_DECL_GROUP("tls", test_tls12_clear_resets_options), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_mutual_auth), \ + TEST_DECL_GROUP("tls", test_tls13_resume_psk_post_handshake_auth), \ + TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth), \ + TEST_DECL_GROUP("tls", test_tls13_resume_psk_client_auth_ok), \ + TEST_DECL_GROUP("tls", test_tls12_ext_cache_client_auth_resume), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_client_auth), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_client_auth_ok), \ + TEST_DECL_GROUP("tls", test_tls12_reuse_clears_use_ticket), \ TEST_DECL_GROUP("tls", test_tls12_resume_ticket_decline_fallback), \ TEST_DECL_GROUP("tls", test_tls12_ticket_dropped_on_bad_finished), \ TEST_DECL_GROUP("tls", test_tls12_ticket_cached_after_finished), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 1ffeac00b5c..ea1cef9a041 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2312,6 +2312,12 @@ WOLFSSL_LOCAL int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, WOLFSSL_LOCAL int CookiePolicyEnable(WOLFSSL* ssl); WOLFSSL_LOCAL int CheckCookieState(WOLFSSL* ssl); #endif +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) +WOLFSSL_LOCAL int ClientAuthRequired(const WOLFSSL* ssl); +#else +/* No client certificate can be asked for, so none can be required. */ +#define ClientAuthRequired(ssl) 0 +#endif WOLFSSL_LOCAL int DoClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, word32 helloSz); #ifdef WOLFSSL_TLS13 @@ -3829,12 +3835,20 @@ WOLFSSL_LOCAL int SetupClientSecureRenegotiation(WOLFSSL* ssl); /* Our ticket format. All members need to be a byte or array of byte to * avoid alignment issues */ +/* Bits of InternalTicket.flags. Bit 0 is where this byte has always held + * haveEMS, so sizeof(InternalTicket) and every offset are unchanged and an + * older ticket parses with the newer bits clear. An older peer handed a ticket + * carrying bit 1 copies the whole byte into haveEMS and fails the handshake + * cleanly, so a shared TLS 1.2 ticket key needs matching versions. */ +#define WOLFSSL_TICKET_FLAG_EMS 0x01 +#define WOLFSSL_TICKET_FLAG_PEER_AUTH 0x02 + typedef struct InternalTicket { ProtocolVersion pv; /* version when ticket created */ byte suite[SUITE_LEN]; /* cipher suite when created */ byte msecret[SECRET_LEN]; /* master secret */ byte timestamp[TIMESTAMP_LEN]; /* born on */ - byte haveEMS; /* have extended master secret */ + byte flags; /* WOLFSSL_TICKET_FLAG_* */ #ifdef WOLFSSL_TLS13 byte ageAdd[AGEADD_LEN]; /* Obfuscation of age */ byte namedGroup[NAMEDGROUP_LEN]; /* Named group used */ @@ -5148,6 +5162,9 @@ struct WOLFSSL_SESSION { byte masterSecret[SECRET_LEN]; /* stored secret */ word16 haveEMS; /* ext master secret flag */ + /* Client presented a certificate and proved key possession. Server + * sessions only; after heap so wolfSSL_DupSession carries it. */ + byte peerAuthOk; #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) WOLFSSL_X509* peer; /* peer cert */ #endif /* SESSION_CERTS && OPENSSL_EXTRA */ diff --git a/wolfssl/ssl_sess.h b/wolfssl/ssl_sess.h index 7e31681924d..b6d3e6841f2 100644 --- a/wolfssl/ssl_sess.h +++ b/wolfssl/ssl_sess.h @@ -163,7 +163,9 @@ #if defined(PERSIST_SESSION_CACHE) && !defined(SESSION_CACHE_DYNAMIC_MEM) /* for persistence, if changes to layout need to increment and modify save_session_cache() and restore_session_cache and memory versions too */ - #define WOLFSSL_CACHE_VERSION 3 + /* 4: WOLFSSL_SESSION gained peerAuthOk; padding can hide it from + * the sessionSz check. */ + #define WOLFSSL_CACHE_VERSION 4 /* Session Cache Header information */ typedef struct {