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 #define TLS13_SUPPORT
#endif #endif
#if defined(TLS13_SUPPORT) && defined(MBEDTLS_SSL_SESSION_TICKETS)
#define HAS_SESSION_TICKETS
#endif
#if defined(THREADING_SUPPORT) #if defined(THREADING_SUPPORT)
static mbedtls_entropy_context ts_entropy; static mbedtls_entropy_context ts_entropy;
@ -291,7 +295,8 @@ mbed_set_ssl_version_min_max(struct Curl_easy *data,
break; break;
#endif #endif
default: 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; 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; 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 /* Always let mbedTLS verify certificates, if verifypeer or verifyhost are
* disabled we clear the corresponding error flags in the verify callback * disabled we clear the corresponding error flags in the verify callback
* function. That is also where we log verification errors. */ * 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 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 ssl_connect_data *connssl = cf->ctx;
struct mbed_ssl_backend_data *backend = struct mbed_ssl_backend_data *backend =
(struct mbed_ssl_backend_data *)connssl->backend; (struct mbed_ssl_backend_data *)connssl->backend;
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); 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); DEBUGASSERT(backend);
if(ssl_config->primary.cache_session) { if(ssl_config->primary.cache_session) {
int ret; int ret;
mbedtls_ssl_session *our_ssl_sessionid; 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 */ /* If there is already a matching session in the cache, delete it */
Curl_ssl_sessionid_lock(data); Curl_ssl_sessionid_lock(data);
retcode = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL, result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL,
our_ssl_sessionid, 0, our_ssl_sessionid, 0,
mbedtls_session_free); mbedtls_session_free);
Curl_ssl_sessionid_unlock(data); Curl_ssl_sessionid_unlock(data);
if(retcode)
return retcode;
} }
return result;
connssl->connecting_state = ssl_connect_done;
return CURLE_OK;
} }
static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, 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 *backend =
(struct mbed_ssl_backend_data *)connssl->backend; (struct mbed_ssl_backend_data *)connssl->backend;
int ret = -1; int ret = -1;
ssize_t len = -1;
(void)data; (void)data;
DEBUGASSERT(backend); DEBUGASSERT(backend);
@ -1324,24 +1327,31 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
if(ret <= 0) { if(ret <= 0) {
CURL_TRC_CF(data, cf, "mbedtls_ssl_read(len=%zu) -> -0x%04X", CURL_TRC_CF(data, cf, "mbedtls_ssl_read(len=%zu) -> -0x%04X",
buffersize, -ret); buffersize, -ret);
if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) switch(ret) {
return 0; #ifdef HAS_SESSION_TICKETS
*curlcode = ((ret == MBEDTLS_ERR_SSL_WANT_READ) case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET:
#ifdef TLS13_SUPPORT mbed_new_session(cf, data);
|| (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) FALLTHROUGH();
#endif #endif
) ? CURLE_AGAIN : CURLE_RECV_ERROR; case MBEDTLS_ERR_SSL_WANT_READ:
if(*curlcode != CURLE_AGAIN) { *curlcode = CURLE_AGAIN;
ret = -1;
break;
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
*curlcode = CURLE_OK;
ret = 0;
break;
default: {
char errorbuf[128]; char errorbuf[128];
mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
failf(data, "ssl_read returned: (-0x%04X) %s", -ret, errorbuf); failf(data, "ssl_read returned: (-0x%04X) %s", -ret, errorbuf);
*curlcode = CURLE_RECV_ERROR;
ret = -1;
break;
}
} }
return -1;
} }
return (ssize_t)ret;
len = ret;
return len;
} }
static size_t mbedtls_version(char *buffer, size_t size) 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. */ } /* repeat step2 until all transactions are done. */
if(ssl_connect_3 == connssl->connecting_state) { if(ssl_connect_3 == connssl->connecting_state) {
retcode = mbed_connect_step3(cf, data); /* For tls1.3 we get notified about new sessions */
if(retcode) #if MBEDTLS_VERSION_NUMBER >= 0x03020000
return retcode; 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) { if(ssl_connect_done == connssl->connecting_state) {

View File

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