Skip to content
Open
72 changes: 68 additions & 4 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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

/**
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -43997,14 +44056,18 @@ 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)
return;
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];
Expand Down Expand Up @@ -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) {
Expand Down
17 changes: 16 additions & 1 deletion src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 16 additions & 0 deletions src/ssl_sess.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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. */
Expand Down
5 changes: 5 additions & 0 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
&current->sess_free_cb_ctx);
Expand Down
56 changes: 56 additions & 0 deletions tests/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
Loading
Loading