Skip to content

Commit

Permalink
rustls: add support for setting TLS version and ciphers
Browse files Browse the repository at this point in the history
Add support for CURLOPT_SSLVERSION, CURLOPT_TLS13_CIPHERS and
CURLOPT_SSL_CIPHER_LIST.
  • Loading branch information
jan2000 committed Aug 13, 2024
1 parent d76389d commit f4c3356
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 51 deletions.
2 changes: 2 additions & 0 deletions docs/libcurl/opts/CURLOPT_SSLVERSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ Since 8.10.0 wolfSSL is fully supported. Before 8.10.0 the MAX macros were not
supported with wolfSSL and the other macros did not set a minimum, but
restricted the TLS version to only the specified one.

rustls support added in 8.10.0.

# %AVAILABILITY%

# RETURN VALUE
Expand Down
45 changes: 26 additions & 19 deletions lib/vtls/cipher_suite.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
***************************************************************************/
#include "curl_setup.h"

#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_BEARSSL)
#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \
defined(USE_BEARSSL) || defined(USE_RUSTLS)
#include "cipher_suite.h"
#include "curl_printf.h"
#include "strcase.h"
Expand Down Expand Up @@ -190,6 +191,28 @@ struct cs_entry {

/* !checksrc! disable COMMANOSPACE all */
static const struct cs_entry cs_list [] = {
/* TLS 1.3 ciphers */
#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_RUSTLS)
CS_ENTRY(0x1301, TLS,AES,128,GCM,SHA256,,,),
CS_ENTRY(0x1302, TLS,AES,256,GCM,SHA384,,,),
CS_ENTRY(0x1303, TLS,CHACHA20,POLY1305,SHA256,,,,),
CS_ENTRY(0x1304, TLS,AES,128,CCM,SHA256,,,),
CS_ENTRY(0x1305, TLS,AES,128,CCM,8,SHA256,,),
#endif
/* TLS 1.2 ciphers */
CS_ENTRY(0xC02B, TLS,ECDHE,ECDSA,WITH,AES,128,GCM,SHA256),
CS_ENTRY(0xC02B, ECDHE,ECDSA,AES128,GCM,SHA256,,,),
CS_ENTRY(0xC02C, TLS,ECDHE,ECDSA,WITH,AES,256,GCM,SHA384),
CS_ENTRY(0xC02C, ECDHE,ECDSA,AES256,GCM,SHA384,,,),
CS_ENTRY(0xC02F, TLS,ECDHE,RSA,WITH,AES,128,GCM,SHA256),
CS_ENTRY(0xC02F, ECDHE,RSA,AES128,GCM,SHA256,,,),
CS_ENTRY(0xC030, TLS,ECDHE,RSA,WITH,AES,256,GCM,SHA384),
CS_ENTRY(0xC030, ECDHE,RSA,AES256,GCM,SHA384,,,),
CS_ENTRY(0xCCA8, TLS,ECDHE,RSA,WITH,CHACHA20,POLY1305,SHA256,),
CS_ENTRY(0xCCA8, ECDHE,RSA,CHACHA20,POLY1305,,,,),
CS_ENTRY(0xCCA9, TLS,ECDHE,ECDSA,WITH,CHACHA20,POLY1305,SHA256,),
CS_ENTRY(0xCCA9, ECDHE,ECDSA,CHACHA20,POLY1305,,,,),
#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_BEARSSL)
CS_ENTRY(0x002F, TLS,RSA,WITH,AES,128,CBC,SHA,),
CS_ENTRY(0x002F, AES128,SHA,,,,,,),
CS_ENTRY(0x0035, TLS,RSA,WITH,AES,256,CBC,SHA,),
Expand Down Expand Up @@ -234,26 +257,15 @@ static const struct cs_entry cs_list [] = {
CS_ENTRY(0xC029, ECDH,RSA,AES128,SHA256,,,,),
CS_ENTRY(0xC02A, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA384),
CS_ENTRY(0xC02A, ECDH,RSA,AES256,SHA384,,,,),
CS_ENTRY(0xC02B, TLS,ECDHE,ECDSA,WITH,AES,128,GCM,SHA256),
CS_ENTRY(0xC02B, ECDHE,ECDSA,AES128,GCM,SHA256,,,),
CS_ENTRY(0xC02C, TLS,ECDHE,ECDSA,WITH,AES,256,GCM,SHA384),
CS_ENTRY(0xC02C, ECDHE,ECDSA,AES256,GCM,SHA384,,,),
CS_ENTRY(0xC02D, TLS,ECDH,ECDSA,WITH,AES,128,GCM,SHA256),
CS_ENTRY(0xC02D, ECDH,ECDSA,AES128,GCM,SHA256,,,),
CS_ENTRY(0xC02E, TLS,ECDH,ECDSA,WITH,AES,256,GCM,SHA384),
CS_ENTRY(0xC02E, ECDH,ECDSA,AES256,GCM,SHA384,,,),
CS_ENTRY(0xC02F, TLS,ECDHE,RSA,WITH,AES,128,GCM,SHA256),
CS_ENTRY(0xC02F, ECDHE,RSA,AES128,GCM,SHA256,,,),
CS_ENTRY(0xC030, TLS,ECDHE,RSA,WITH,AES,256,GCM,SHA384),
CS_ENTRY(0xC030, ECDHE,RSA,AES256,GCM,SHA384,,,),
CS_ENTRY(0xC031, TLS,ECDH,RSA,WITH,AES,128,GCM,SHA256),
CS_ENTRY(0xC031, ECDH,RSA,AES128,GCM,SHA256,,,),
CS_ENTRY(0xC032, TLS,ECDH,RSA,WITH,AES,256,GCM,SHA384),
CS_ENTRY(0xC032, ECDH,RSA,AES256,GCM,SHA384,,,),
CS_ENTRY(0xCCA8, TLS,ECDHE,RSA,WITH,CHACHA20,POLY1305,SHA256,),
CS_ENTRY(0xCCA8, ECDHE,RSA,CHACHA20,POLY1305,,,,),
CS_ENTRY(0xCCA9, TLS,ECDHE,ECDSA,WITH,CHACHA20,POLY1305,SHA256,),
CS_ENTRY(0xCCA9, ECDHE,ECDSA,CHACHA20,POLY1305,,,,),
#endif
#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS)
CS_ENTRY(0x0001, TLS,RSA,WITH,NULL,MD5,,,),
CS_ENTRY(0x0001, NULL,MD5,,,,,,),
Expand Down Expand Up @@ -327,11 +339,6 @@ static const struct cs_entry cs_list [] = {
CS_ENTRY(0x00B8, RSA,PSK,NULL,SHA256,,,,),
CS_ENTRY(0x00B9, TLS,RSA,PSK,WITH,NULL,SHA384,,),
CS_ENTRY(0x00B9, RSA,PSK,NULL,SHA384,,,,),
CS_ENTRY(0x1301, TLS,AES,128,GCM,SHA256,,,),
CS_ENTRY(0x1302, TLS,AES,256,GCM,SHA384,,,),
CS_ENTRY(0x1303, TLS,CHACHA20,POLY1305,SHA256,,,,),
CS_ENTRY(0x1304, TLS,AES,128,CCM,SHA256,,,),
CS_ENTRY(0x1305, TLS,AES,128,CCM,8,SHA256,,),
CS_ENTRY(0xC001, TLS,ECDH,ECDSA,WITH,NULL,SHA,,),
CS_ENTRY(0xC001, ECDH,ECDSA,NULL,SHA,,,,),
CS_ENTRY(0xC006, TLS,ECDHE,ECDSA,WITH,NULL,SHA,,),
Expand Down Expand Up @@ -881,4 +888,4 @@ int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size,
}

#endif /* defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \
defined(USE_BEARSSL) */
defined(USE_BEARSSL) || defined(USE_RUSTLS) */
5 changes: 3 additions & 2 deletions lib/vtls/cipher_suite.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

#include "curl_setup.h"

#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_BEARSSL)
#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \
defined(USE_BEARSSL) || defined(USE_RUSTLS)
#include <stdint.h>

/* Lookup IANA id for cipher suite string, returns 0 if not recognized */
Expand All @@ -43,5 +44,5 @@ int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size,
bool prefer_rfc);

#endif /* defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \
defined(USE_BEARSSL) */
defined(USE_BEARSSL) || defined(USE_RUSTLS) */
#endif /* HEADER_CURL_CIPHER_SUITE_H */
186 changes: 183 additions & 3 deletions lib/vtls/rustls.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "strerror.h"
#include "multiif.h"
#include "connect.h" /* for the connect timeout */
#include "cipher_suite.h"

struct rustls_ssl_backend_data
{
Expand Down Expand Up @@ -426,6 +427,101 @@ read_file_into(const char *filename,
return fclose(f) == 0;
}

static void
cr_get_selected_ciphers(struct Curl_easy *data,
const char *ciphers12,
const char *ciphers13,
const struct rustls_supported_ciphersuite **selected,
size_t *selected_size)
{
size_t supported_len = *selected_size;
size_t default_len = rustls_default_ciphersuites_len();
const struct rustls_supported_ciphersuite *entry;
const char *ciphers = ciphers12;
size_t count = 0, default13_count = 0, i, j;
const char *ptr, *end;

DEBUGASSERT(default_len <= supported_len);

if(!ciphers13) {
/* Add default TLSv1.3 ciphers to selection */
for(j = 0; j < default_len; j++) {
struct rustls_str s;
entry = rustls_default_ciphersuites_get_entry(j);
s = rustls_supported_ciphersuite_get_name(entry);
if(s.len < 5 || strncmp(s.data, "TLS13", 5) != 0)
continue;

selected[count++] = entry;
}

default13_count = count;

if(!ciphers)
ciphers = "";
}
else
ciphers = ciphers13;

add_ciphers:
for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) {
uint16_t id = Curl_cipher_suite_walk_str(&ptr, &end);

/* Check if cipher is supported */
if(id) {
for(i = 0; i < supported_len; i++) {
entry = rustls_all_ciphersuites_get_entry(i);
if(rustls_supported_ciphersuite_get_suite(entry) == id)
break;
}
if(i == supported_len)
id = 0;
}
if(!id) {
if(ptr[0] != '\0')
infof(data, "rustls: unknown cipher in list: \"%.*s\"",
(int) (end - ptr), ptr);
continue;
}

/* No duplicates allowed (so selected cannot overflow) */
for(i = 0; i < count && selected[i] != entry; i++);
if(i < count) {
if(i >= default13_count)
infof(data, "rustls: duplicate cipher in list: \"%.*s\"",
(int) (end - ptr), ptr);
continue;
}

selected[count++] = entry;
}

if(ciphers == ciphers13 && ciphers12) {
ciphers = ciphers12;
goto add_ciphers;
}

if(!ciphers12) {
/* Add default TLSv1.2 ciphers to selection */
for(j = 0; j < default_len; j++) {
struct rustls_str s;
entry = rustls_default_ciphersuites_get_entry(j);
s = rustls_supported_ciphersuite_get_name(entry);
if(s.len < 5 || strncmp(s.data, "TLS13", 5) == 0)
continue;

/* No duplicates allowed (so selected cannot overflow) */
for(i = 0; i < count && selected[i] != entry; i++);
if(i < count)
continue;

selected[count++] = entry;
}
}

*selected_size = count;
}

static CURLcode
cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
struct rustls_ssl_backend_data *const backend)
Expand All @@ -450,7 +546,74 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
DEBUGASSERT(backend);
rconn = backend->conn;

config_builder = rustls_client_config_builder_new();
{
uint16_t tls_versions[2] = {
RUSTLS_TLS_VERSION_TLSV1_2,
RUSTLS_TLS_VERSION_TLSV1_3,
};
size_t tls_versions_len = 2;
const struct rustls_supported_ciphersuite **cipher_suites;
size_t cipher_suites_len = rustls_default_ciphersuites_len();

switch(conn_config->version) {
case CURL_SSLVERSION_DEFAULT:
case CURL_SSLVERSION_TLSv1:
case CURL_SSLVERSION_TLSv1_1:
case CURL_SSLVERSION_TLSv1_2:
break;
case CURL_SSLVERSION_TLSv1_3:
tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3;
tls_versions_len = 1;
break;
default:
failf(data, "rustls: unrecognized minimum TLS version value");
return CURLE_SSL_ENGINE_INITFAILED;
}

switch(conn_config->version_max) {
case CURL_SSLVERSION_MAX_DEFAULT:
case CURL_SSLVERSION_MAX_NONE:
case CURL_SSLVERSION_MAX_TLSv1_3:
break;
case CURL_SSLVERSION_MAX_TLSv1_2:
if(tls_versions[0] == RUSTLS_TLS_VERSION_TLSV1_2) {
tls_versions_len = 1;
break;
}
FALLTHROUGH();
case CURL_SSLVERSION_MAX_TLSv1_1:
case CURL_SSLVERSION_MAX_TLSv1_0:
default:
failf(data, "rustls: unsupported maximum TLS version value");
return CURLE_SSL_ENGINE_INITFAILED;
}

cipher_suites = malloc(sizeof(cipher_suites) * (cipher_suites_len));
if(!cipher_suites)
return CURLE_OUT_OF_MEMORY;

cr_get_selected_ciphers(data,
conn_config->cipher_list,
conn_config->cipher_list13,
cipher_suites, &cipher_suites_len);
if(cipher_suites_len == 0) {
failf(data, "rustls: no supported cipher in list");
free(cipher_suites);
return CURLE_SSL_CIPHER;
}

result = rustls_client_config_builder_new_custom(cipher_suites,
cipher_suites_len,
tls_versions,
tls_versions_len,
&config_builder);
free(cipher_suites);
if(result != RUSTLS_RESULT_OK) {
failf(data, "rustls: failed to create client config");
return CURLE_SSL_ENGINE_INITFAILED;
}
}

if(connssl->alpn) {
struct alpn_proto_buf proto;
rustls_slice_bytes alpn[ALPN_ENTRIES_MAX];
Expand Down Expand Up @@ -629,7 +792,6 @@ cr_connect_common(struct Curl_cfilter *cf,
*/
connssl->io_need = CURL_SSL_IO_NEED_NONE;
if(!rustls_connection_is_handshaking(rconn)) {
infof(data, "Done handshaking");
/* rustls claims it is no longer handshaking *before* it has
* send its FINISHED message off. We attempt to let it write
* one more time. Oh my.
Expand All @@ -644,6 +806,22 @@ cr_connect_common(struct Curl_cfilter *cf,
return tmperr;
}
/* REALLY Done with the handshake. */
{
uint16_t proto = rustls_connection_get_protocol_version(rconn);
const rustls_supported_ciphersuite *rcipher =
rustls_connection_get_negotiated_ciphersuite(rconn);
uint16_t cipher = rcipher ?
rustls_supported_ciphersuite_get_suite(rcipher) : 0;
char buf[64] = "";
const char *ver = "TLS version unknown";
if(proto == RUSTLS_TLS_VERSION_TLSV1_3)
ver = "TLSv1.3";
if(proto == RUSTLS_TLS_VERSION_TLSV1_2)
ver = "TLSv1.2";
Curl_cipher_suite_get_str(cipher, buf, sizeof(buf), true);
infof(data, "rustls: handshake complete, %s, cipher: %s",
ver, buf);
}
connssl->state = ssl_connection_complete;
*done = TRUE;
return CURLE_OK;
Expand Down Expand Up @@ -847,7 +1025,9 @@ static size_t cr_version(char *buffer, size_t size)
const struct Curl_ssl Curl_ssl_rustls = {
{ CURLSSLBACKEND_RUSTLS, "rustls" },
SSLSUPP_CAINFO_BLOB | /* supports */
SSLSUPP_HTTPS_PROXY,
SSLSUPP_HTTPS_PROXY |
SSLSUPP_CIPHER_LIST |
SSLSUPP_TLS13_CIPHERSUITES,
sizeof(struct rustls_ssl_backend_data),

Curl_none_init, /* init */
Expand Down
2 changes: 0 additions & 2 deletions tests/http/test_17_ssl_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,6 @@ def test_17_07_ssl_ciphers(self, env: Env, httpd, nghttpx, ciphers, succeed, rep
extra_args = []
if env.curl_uses_lib('gnutls'):
pytest.skip('GnuTLS does not support setting ciphers by name')
if env.curl_uses_lib('rustls-ffi'):
pytest.skip('rustls-ffi does not support setting ciphers')
if ciphers[0] & 0xFF00 == 0x1300:
# test setting TLSv1.3 ciphers
if env.curl_uses_lib('bearssl'):
Expand Down
Loading

0 comments on commit f4c3356

Please sign in to comment.