mbedTLS: fix handling of TLSv1.3 sessions

For TLSv1.3, if supported, observer special return code to retrieve
newly arrived session from mbedTLS.

Adjust test expectations now that TLSv1.3 session resumption works in
mbedTLS >= 3.6.0.

Based on #14135 by @ad-chaos
Closes #15245
This commit is contained in:
Stefan Eissing 2024-10-10 12:47:41 +02:00 committed by Daniel Stenberg
parent 513904c264
commit 3455d360ce
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
2 changed files with 63 additions and 34 deletions

View File

@ -118,6 +118,10 @@ struct mbed_ssl_backend_data {
#define TLS13_SUPPORT
#endif
#if defined(TLS13_SUPPORT) && defined(MBEDTLS_SSL_SESSION_TICKETS)
#define HAS_SESSION_TICKETS
#endif
#if defined(THREADING_SUPPORT)
static mbedtls_entropy_context ts_entropy;
@ -291,7 +295,8 @@ mbed_set_ssl_version_min_max(struct Curl_easy *data,
break;
#endif
default:
failf(data, "mbedTLS: unsupported minimum TLS version value");
failf(data, "mbedTLS: unsupported minimum TLS version value: %x",
conn_config->version);
return CURLE_SSL_CONNECT_ERROR;
}
@ -807,6 +812,12 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
return CURLE_SSL_CONNECT_ERROR;
}
#ifdef MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED
/* New in mbedTLS 3.6.1, need to enable, default is now disabled */
mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(&backend->config,
MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED);
#endif
/* Always let mbedTLS verify certificates, if verifypeer or verifyhost are
* disabled we clear the corresponding error flags in the verify callback
* function. That is also where we log verification errors. */
@ -1113,17 +1124,15 @@ static void mbedtls_session_free(void *sessionid, size_t idsize)
}
static CURLcode
mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
mbed_new_session(struct Curl_cfilter *cf, struct Curl_easy *data)
{
CURLcode retcode = CURLE_OK;
struct ssl_connect_data *connssl = cf->ctx;
struct mbed_ssl_backend_data *backend =
(struct mbed_ssl_backend_data *)connssl->backend;
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
CURLcode result = CURLE_OK;
DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
DEBUGASSERT(backend);
if(ssl_config->primary.cache_session) {
int ret;
mbedtls_ssl_session *our_ssl_sessionid;
@ -1145,17 +1154,12 @@ mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
/* If there is already a matching session in the cache, delete it */
Curl_ssl_sessionid_lock(data);
retcode = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL,
our_ssl_sessionid, 0,
mbedtls_session_free);
result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL,
our_ssl_sessionid, 0,
mbedtls_session_free);
Curl_ssl_sessionid_unlock(data);
if(retcode)
return retcode;
}
connssl->connecting_state = ssl_connect_done;
return CURLE_OK;
return result;
}
static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data,
@ -1314,7 +1318,6 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
struct mbed_ssl_backend_data *backend =
(struct mbed_ssl_backend_data *)connssl->backend;
int ret = -1;
ssize_t len = -1;
(void)data;
DEBUGASSERT(backend);
@ -1324,24 +1327,31 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
if(ret <= 0) {
CURL_TRC_CF(data, cf, "mbedtls_ssl_read(len=%zu) -> -0x%04X",
buffersize, -ret);
if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)
return 0;
*curlcode = ((ret == MBEDTLS_ERR_SSL_WANT_READ)
#ifdef TLS13_SUPPORT
|| (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
switch(ret) {
#ifdef HAS_SESSION_TICKETS
case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET:
mbed_new_session(cf, data);
FALLTHROUGH();
#endif
) ? CURLE_AGAIN : CURLE_RECV_ERROR;
if(*curlcode != CURLE_AGAIN) {
case MBEDTLS_ERR_SSL_WANT_READ:
*curlcode = CURLE_AGAIN;
ret = -1;
break;
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
*curlcode = CURLE_OK;
ret = 0;
break;
default: {
char errorbuf[128];
mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
failf(data, "ssl_read returned: (-0x%04X) %s", -ret, errorbuf);
*curlcode = CURLE_RECV_ERROR;
ret = -1;
break;
}
}
return -1;
}
len = ret;
return len;
return (ssize_t)ret;
}
static size_t mbedtls_version(char *buffer, size_t size)
@ -1486,9 +1496,22 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data,
} /* repeat step2 until all transactions are done. */
if(ssl_connect_3 == connssl->connecting_state) {
retcode = mbed_connect_step3(cf, data);
if(retcode)
return retcode;
/* For tls1.3 we get notified about new sessions */
#if MBEDTLS_VERSION_NUMBER >= 0x03020000
struct ssl_connect_data *ctx = cf->ctx;
struct mbed_ssl_backend_data *backend =
(struct mbed_ssl_backend_data *)ctx->backend;
if(mbedtls_ssl_get_version_number(&backend->ssl) <=
MBEDTLS_SSL_VERSION_TLS1_2) {
#else
{ /* no TLSv1.3 supported here */
#endif
retcode = mbed_new_session(cf, data);
if(retcode)
return retcode;
}
connssl->connecting_state = ssl_connect_done;
}
if(ssl_connect_done == connssl->connecting_state) {

View File

@ -71,10 +71,13 @@ class TestSSLUse:
exp_resumed = 'Initial' # Rustls does not support sessions, TODO
if env.curl_uses_lib('bearssl') and tls_max == '1.3':
pytest.skip('BearSSL does not support TLSv1.3')
if env.curl_uses_lib('mbedtls') and tls_max == '1.3':
if env.curl_uses_lib('mbedtls') and tls_max == '1.3' and \
not env.curl_lib_version_at_least('mbedtls', '3.6.0'):
pytest.skip('mbedtls TLSv1.3 session resume not working in 3.6.0')
curl = CurlClient(env=env)
run_env = os.environ.copy()
run_env['CURL_DEBUG'] = 'ssl'
curl = CurlClient(env=env, run_env=run_env)
# tell the server to close the connection after each request
urln = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo?'\
f'id=[0-{count-1}]&close'
@ -91,9 +94,9 @@ class TestSSLUse:
djson = json.load(f)
assert djson['HTTPS'] == 'on', f'{i}: {djson}'
if i == 0:
assert djson['SSL_SESSION_RESUMED'] == 'Initial', f'{i}: {djson}'
assert djson['SSL_SESSION_RESUMED'] == 'Initial', f'{i}: {djson}\n{r.dump_logs()}'
else:
assert djson['SSL_SESSION_RESUMED'] == exp_resumed, f'{i}: {djson}'
assert djson['SSL_SESSION_RESUMED'] == exp_resumed, f'{i}: {djson}\n{r.dump_logs()}'
# use host name with trailing dot, verify handshake
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
@ -220,6 +223,9 @@ class TestSSLUse:
if tls_proto == 'TLSv1.3':
pytest.skip('BearSSL does not support TLSv1.3')
tls_proto = 'TLSv1.2'
elif env.curl_uses_lib('mbedtls') and not env.curl_lib_version_at_least('mbedtls', '3.6.0'):
if tls_proto == 'TLSv1.3':
pytest.skip('mbedTLS < 3.6.0 does not support TLSv1.3')
elif env.curl_uses_lib('sectransp'): # not in CI, so untested
if tls_proto == 'TLSv1.3':
pytest.skip('Secure Transport does not support TLSv1.3')