aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/curl/lib/vtls/rustls.c
diff options
context:
space:
mode:
authorMaxim Yurchuk <maxim-yurchuk@ydb.tech>2024-10-04 17:24:16 +0300
committerGitHub <noreply@github.com>2024-10-04 17:24:16 +0300
commita1e4766748b5924d3879ab6a0259b28ec3c5d535 (patch)
tree4480150864228623d6c9101a4ba8c049bda9aa90 /contrib/libs/curl/lib/vtls/rustls.c
parent6536467764bed7822214815ce92ed4dcd5bf409b (diff)
parenta46fe128b9c9c84438fc2aac337aeefdaecb99df (diff)
downloadydb-a1e4766748b5924d3879ab6a0259b28ec3c5d535.tar.gz
Merge pull request #10090 from ydb-platform/mergelibs-241004-1110
Library import 241004-1110
Diffstat (limited to 'contrib/libs/curl/lib/vtls/rustls.c')
-rw-r--r--contrib/libs/curl/lib/vtls/rustls.c753
1 files changed, 563 insertions, 190 deletions
diff --git a/contrib/libs/curl/lib/vtls/rustls.c b/contrib/libs/curl/lib/vtls/rustls.c
index d267e26463..6c05093937 100644
--- a/contrib/libs/curl/lib/vtls/rustls.c
+++ b/contrib/libs/curl/lib/vtls/rustls.c
@@ -7,6 +7,7 @@
*
* Copyright (C) Jacob Hoffman-Andrews,
* <github@hoffman-andrews.com>
+ * Copyright (C) kpcyrd, <kpcyrd@archlinux.org>
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -40,12 +41,16 @@
#include "strerror.h"
#include "multiif.h"
#include "connect.h" /* for the connect timeout */
+#error #include "cipher_suite.h"
+#include "rand.h"
struct rustls_ssl_backend_data
{
const struct rustls_client_config *config;
struct rustls_connection *conn;
- bool data_pending;
+ size_t plain_out_buffered;
+ BIT(data_in_pending);
+ BIT(sent_shutdown);
};
/* For a given rustls_result error code, return the best-matching CURLcode. */
@@ -60,7 +65,7 @@ static CURLcode map_error(rustls_result r)
case RUSTLS_RESULT_NULL_PARAMETER:
return CURLE_BAD_FUNCTION_ARGUMENT;
default:
- return CURLE_READ_ERROR;
+ return CURLE_RECV_ERROR;
}
}
@@ -73,7 +78,7 @@ cr_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data)
(void)data;
DEBUGASSERT(ctx && ctx->backend);
backend = (struct rustls_ssl_backend_data *)ctx->backend;
- return backend->data_pending;
+ return backend->data_in_pending;
}
struct io_ctx {
@@ -85,6 +90,7 @@ static int
read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n)
{
struct io_ctx *io_ctx = userdata;
+ struct ssl_connect_data *const connssl = io_ctx->cf->ctx;
CURLcode result;
int ret = 0;
ssize_t nread = Curl_conn_cf_recv(io_ctx->cf->next, io_ctx->data,
@@ -96,7 +102,11 @@ read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n)
else
ret = EINVAL;
}
- *out_n = (int)nread;
+ else if(nread == 0)
+ connssl->peer_closed = TRUE;
+ *out_n = (uintptr_t)nread;
+ CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next recv(len=%zu) -> %zd, %d",
+ len, nread, result);
return ret;
}
@@ -107,7 +117,8 @@ write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n)
CURLcode result;
int ret = 0;
ssize_t nwritten = Curl_conn_cf_send(io_ctx->cf->next, io_ctx->data,
- (const char *)buf, len, &result);
+ (const char *)buf, len, FALSE,
+ &result);
if(nwritten < 0) {
nwritten = 0;
if(CURLE_AGAIN == result)
@@ -115,11 +126,9 @@ write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n)
else
ret = EINVAL;
}
- *out_n = (int)nwritten;
- /*
- CURL_TRC_CFX(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d",
- len, nwritten, result));
- */
+ *out_n = (uintptr_t)nwritten;
+ CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d",
+ len, nwritten, result);
return ret;
}
@@ -146,7 +155,7 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf,
char buffer[STRERROR_LEN];
failf(data, "reading from socket: %s",
Curl_strerror(io_error, buffer, sizeof(buffer)));
- *err = CURLE_READ_ERROR;
+ *err = CURLE_RECV_ERROR;
return -1;
}
@@ -156,27 +165,27 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf,
size_t errorlen;
rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
failf(data, "rustls_connection_process_new_packets: %.*s",
- errorlen, errorbuf);
+ (int)errorlen, errorbuf);
*err = map_error(rresult);
return -1;
}
- backend->data_pending = TRUE;
+ backend->data_in_pending = TRUE;
*err = CURLE_OK;
return (ssize_t)tls_bytes_read;
}
/*
* On each run:
- * - Read a chunk of bytes from the socket into rustls' TLS input buffer.
- * - Tell rustls to process any new packets.
- * - Read out as many plaintext bytes from rustls as possible, until hitting
+ * - Read a chunk of bytes from the socket into Rustls' TLS input buffer.
+ * - Tell Rustls to process any new packets.
+ * - Read out as many plaintext bytes from Rustls as possible, until hitting
* error, EOF, or EAGAIN/EWOULDBLOCK, or plainbuf/plainlen is filled up.
*
- * It's okay to call this function with plainbuf == NULL and plainlen == 0.
- * In that case, it will copy bytes from the socket into rustls' TLS input
- * buffer, and process packets, but won't consume bytes from rustls' plaintext
- * output buffer.
+ * it is okay to call this function with plainbuf == NULL and plainlen == 0. In
+ * that case, it will copy bytes from the socket into Rustls' TLS input
+ * buffer, and process packets, but will not consume bytes from Rustls'
+ * plaintext output buffer.
*/
static ssize_t
cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
@@ -196,7 +205,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
rconn = backend->conn;
while(plain_bytes_copied < plainlen) {
- if(!backend->data_pending) {
+ if(!backend->data_in_pending) {
if(tls_recv_more(cf, data, err) < 0) {
if(*err != CURLE_AGAIN) {
nread = -1;
@@ -207,26 +216,26 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
}
rresult = rustls_connection_read(rconn,
- (uint8_t *)plainbuf + plain_bytes_copied,
- plainlen - plain_bytes_copied,
- &n);
+ (uint8_t *)plainbuf + plain_bytes_copied,
+ plainlen - plain_bytes_copied,
+ &n);
if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) {
- backend->data_pending = FALSE;
+ backend->data_in_pending = FALSE;
}
else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) {
failf(data, "rustls: peer closed TCP connection "
- "without first closing TLS connection");
- *err = CURLE_READ_ERROR;
+ "without first closing TLS connection");
+ *err = CURLE_RECV_ERROR;
nread = -1;
goto out;
}
else if(rresult != RUSTLS_RESULT_OK) {
- /* n always equals 0 in this case, don't need to check it */
+ /* n always equals 0 in this case, do not need to check it */
char errorbuf[255];
size_t errorlen;
rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
- failf(data, "rustls_connection_read: %.*s", errorlen, errorbuf);
- *err = CURLE_READ_ERROR;
+ failf(data, "rustls_connection_read: %.*s", (int)errorlen, errorbuf);
+ *err = CURLE_RECV_ERROR;
nread = -1;
goto out;
}
@@ -261,15 +270,51 @@ out:
return nread;
}
+static CURLcode cr_flush_out(struct Curl_cfilter *cf, struct Curl_easy *data,
+ struct rustls_connection *rconn)
+{
+ struct io_ctx io_ctx;
+ rustls_io_result io_error;
+ size_t tlswritten = 0;
+ size_t tlswritten_total = 0;
+ CURLcode result = CURLE_OK;
+
+ io_ctx.cf = cf;
+ io_ctx.data = data;
+
+ while(rustls_connection_wants_write(rconn)) {
+ io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx,
+ &tlswritten);
+ if(io_error == EAGAIN || io_error == EWOULDBLOCK) {
+ CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes",
+ tlswritten_total);
+ return CURLE_AGAIN;
+ }
+ else if(io_error) {
+ char buffer[STRERROR_LEN];
+ failf(data, "writing to socket: %s",
+ Curl_strerror(io_error, buffer, sizeof(buffer)));
+ return CURLE_SEND_ERROR;
+ }
+ if(tlswritten == 0) {
+ failf(data, "EOF in swrite");
+ return CURLE_SEND_ERROR;
+ }
+ CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten);
+ tlswritten_total += tlswritten;
+ }
+ return result;
+}
+
/*
* On each call:
- * - Copy `plainlen` bytes into rustls' plaintext input buffer (if > 0).
- * - Fully drain rustls' plaintext output buffer into the socket until
+ * - Copy `plainlen` bytes into Rustls' plaintext input buffer (if > 0).
+ * - Fully drain Rustls' plaintext output buffer into the socket until
* we get either an error or EAGAIN/EWOULDBLOCK.
*
- * It's okay to call this function with plainbuf == NULL and plainlen == 0.
- * In that case, it won't read anything into rustls' plaintext input buffer.
- * It will only drain rustls' plaintext output buffer into the socket.
+ * it is okay to call this function with plainbuf == NULL and plainlen == 0.
+ * In that case, it will not read anything into Rustls' plaintext input buffer.
+ * It will only drain Rustls' plaintext output buffer into the socket.
*/
static ssize_t
cr_send(struct Curl_cfilter *cf, struct Curl_easy *data,
@@ -279,29 +324,46 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data,
struct rustls_ssl_backend_data *const backend =
(struct rustls_ssl_backend_data *)connssl->backend;
struct rustls_connection *rconn = NULL;
- struct io_ctx io_ctx;
size_t plainwritten = 0;
- size_t tlswritten = 0;
- size_t tlswritten_total = 0;
rustls_result rresult;
- rustls_io_result io_error;
char errorbuf[256];
size_t errorlen;
+ const unsigned char *buf = plainbuf;
+ size_t blen = plainlen;
+ ssize_t nwritten = 0;
DEBUGASSERT(backend);
rconn = backend->conn;
+ DEBUGASSERT(rconn);
+
+ CURL_TRC_CF(data, cf, "cf_send(len=%zu)", plainlen);
+
+ /* If a previous send blocked, we already added its plain bytes
+ * to rustsls and must not do that again. Flush the TLS bytes and,
+ * if successful, deduct the previous plain bytes from the current
+ * send. */
+ if(backend->plain_out_buffered) {
+ *err = cr_flush_out(cf, data, rconn);
+ CURL_TRC_CF(data, cf, "cf_send: flushing %zu previously added bytes -> %d",
+ backend->plain_out_buffered, *err);
+ if(*err)
+ return -1;
+ if(blen > backend->plain_out_buffered) {
+ blen -= backend->plain_out_buffered;
+ buf += backend->plain_out_buffered;
+ }
+ else
+ blen = 0;
+ nwritten += (ssize_t)backend->plain_out_buffered;
+ backend->plain_out_buffered = 0;
+ }
- CURL_TRC_CF(data, cf, "cf_send: %ld plain bytes", plainlen);
-
- io_ctx.cf = cf;
- io_ctx.data = data;
-
- if(plainlen > 0) {
- rresult = rustls_connection_write(rconn, plainbuf, plainlen,
- &plainwritten);
+ if(blen > 0) {
+ CURL_TRC_CF(data, cf, "cf_send: adding %zu plain bytes to Rustls", blen);
+ rresult = rustls_connection_write(rconn, buf, blen, &plainwritten);
if(rresult != RUSTLS_RESULT_OK) {
rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
- failf(data, "rustls_connection_write: %.*s", errorlen, errorbuf);
+ failf(data, "rustls_connection_write: %.*s", (int)errorlen, errorbuf);
*err = CURLE_WRITE_ERROR;
return -1;
}
@@ -312,57 +374,151 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data,
}
}
- while(rustls_connection_wants_write(rconn)) {
- io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx,
- &tlswritten);
- if(io_error == EAGAIN || io_error == EWOULDBLOCK) {
- CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes",
- tlswritten_total);
- *err = CURLE_AGAIN;
- return -1;
- }
- else if(io_error) {
- char buffer[STRERROR_LEN];
- failf(data, "writing to socket: %s",
- Curl_strerror(io_error, buffer, sizeof(buffer)));
- *err = CURLE_WRITE_ERROR;
- return -1;
- }
- if(tlswritten == 0) {
- failf(data, "EOF in swrite");
- *err = CURLE_WRITE_ERROR;
- return -1;
+ *err = cr_flush_out(cf, data, rconn);
+ if(*err) {
+ if(CURLE_AGAIN == *err) {
+ /* The TLS bytes may have been partially written, but we fail the
+ * complete send() and remember how much we already added to Rustls. */
+ CURL_TRC_CF(data, cf, "cf_send: EAGAIN, remember we added %zu plain"
+ " bytes already to Rustls", blen);
+ backend->plain_out_buffered = plainwritten;
+ if(nwritten) {
+ *err = CURLE_OK;
+ return (ssize_t)nwritten;
+ }
}
- CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten);
- tlswritten_total += tlswritten;
+ return -1;
}
+ else
+ nwritten += (ssize_t)plainwritten;
- return plainwritten;
+ CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %d, %zd",
+ plainlen, *err, nwritten);
+ return nwritten;
}
-/* A server certificate verify callback for rustls that always returns
+/* A server certificate verify callback for Rustls that always returns
RUSTLS_RESULT_OK, or in other words disable certificate verification. */
-static enum rustls_result
+static uint32_t
cr_verify_none(void *userdata UNUSED_PARAM,
const rustls_verify_server_cert_params *params UNUSED_PARAM)
{
return RUSTLS_RESULT_OK;
}
-static bool
-cr_hostname_is_ip(const char *hostname)
+static int
+read_file_into(const char *filename,
+ struct dynbuf *out)
+{
+ FILE *f = fopen(filename, FOPEN_READTEXT);
+ if(!f) {
+ return 0;
+ }
+
+ while(!feof(f)) {
+ uint8_t buf[256];
+ size_t rr = fread(buf, 1, sizeof(buf), f);
+ if(rr == 0 ||
+ CURLE_OK != Curl_dyn_addn(out, buf, rr)) {
+ fclose(f);
+ return 0;
+ }
+ }
+
+ 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)
{
- struct in_addr in;
-#ifdef ENABLE_IPV6
- struct in6_addr in6;
- if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) {
- return true;
+ size_t supported_len = *selected_size;
+ size_t default_len = rustls_default_crypto_provider_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++) {
+ entry = rustls_default_crypto_provider_ciphersuites_get(j);
+ if(rustls_supported_ciphersuite_protocol_version(entry) !=
+ RUSTLS_TLS_VERSION_TLSV1_3)
+ continue;
+
+ selected[count++] = entry;
+ }
+
+ default13_count = count;
+
+ if(!ciphers)
+ ciphers = "";
}
-#endif /* ENABLE_IPV6 */
- if(Curl_inet_pton(AF_INET, hostname, &in) > 0) {
- return true;
+ 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_default_crypto_provider_ciphersuites_get(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;
}
- return false;
+
+ if(!ciphers12) {
+ /* Add default TLSv1.2 ciphers to selection */
+ for(j = 0; j < default_len; j++) {
+ entry = rustls_default_crypto_provider_ciphersuites_get(j);
+ if(rustls_supported_ciphersuite_protocol_version(entry) ==
+ RUSTLS_TLS_VERSION_TLSV1_3)
+ 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
@@ -371,23 +527,126 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
{
struct ssl_connect_data *connssl = cf->ctx;
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct rustls_crypto_provider_builder *custom_provider_builder = NULL;
+ const struct rustls_crypto_provider *custom_provider = NULL;
struct rustls_connection *rconn = NULL;
struct rustls_client_config_builder *config_builder = NULL;
- struct rustls_root_cert_store *roots = NULL;
+ const struct rustls_root_cert_store *roots = NULL;
+ struct rustls_root_cert_store_builder *roots_builder = NULL;
+ struct rustls_web_pki_server_cert_verifier_builder *verifier_builder = NULL;
+ struct rustls_server_cert_verifier *server_cert_verifier = NULL;
const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
const char * const ssl_cafile =
/* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
(ca_info_blob ? NULL : conn_config->CAfile);
const bool verifypeer = conn_config->verifypeer;
- const char *hostname = connssl->peer.hostname;
char errorbuf[256];
size_t errorlen;
- int result;
+ rustls_result result;
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_crypto_provider_ciphersuites_len();
+
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ case CURL_SSLVERSION_TLSv1_0:
+ 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: unsupported 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_crypto_provider_builder_new_from_default(
+ &custom_provider_builder);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data,
+ "rustls: failed to create crypto provider builder from default");
+ return CURLE_SSL_ENGINE_INITFAILED;
+ }
+
+ result =
+ rustls_crypto_provider_builder_set_cipher_suites(
+ custom_provider_builder,
+ cipher_suites,
+ cipher_suites_len);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data,
+ "rustls: failed to set ciphersuites for crypto provider builder");
+ rustls_crypto_provider_builder_free(custom_provider_builder);
+ return CURLE_SSL_ENGINE_INITFAILED;
+ }
+
+ result = rustls_crypto_provider_builder_build(
+ custom_provider_builder, &custom_provider);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "rustls: failed to build custom crypto provider");
+ rustls_crypto_provider_builder_free(custom_provider_builder);
+ return CURLE_SSL_ENGINE_INITFAILED;
+ }
+
+ result = rustls_client_config_builder_new_custom(custom_provider,
+ 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;
+ }
+ }
+
+ rustls_crypto_provider_builder_free(custom_provider_builder);
+ rustls_crypto_provider_free(custom_provider);
+
if(connssl->alpn) {
struct alpn_proto_buf proto;
rustls_slice_bytes alpn[ALPN_ENTRIES_MAX];
@@ -405,63 +664,101 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
if(!verifypeer) {
rustls_client_config_builder_dangerous_set_certificate_verifier(
config_builder, cr_verify_none);
- /* rustls doesn't support IP addresses (as of 0.19.0), and will reject
- * connections created with an IP address, even when certificate
- * verification is turned off. Set a placeholder hostname and disable
- * SNI. */
- if(cr_hostname_is_ip(hostname)) {
- rustls_client_config_builder_set_enable_sni(config_builder, false);
- hostname = "example.invalid";
- }
}
- else if(ca_info_blob) {
- roots = rustls_root_cert_store_new();
+ else if(ca_info_blob || ssl_cafile) {
+ roots_builder = rustls_root_cert_store_builder_new();
+
+ if(ca_info_blob) {
+ /* Enable strict parsing only if verification is not disabled. */
+ result = rustls_root_cert_store_builder_add_pem(roots_builder,
+ ca_info_blob->data,
+ ca_info_blob->len,
+ verifypeer);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "rustls: failed to parse trusted certificates from blob");
+ rustls_root_cert_store_builder_free(roots_builder);
+ rustls_client_config_builder_free(config_builder);
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+ else if(ssl_cafile) {
+ /* Enable strict parsing only if verification is not disabled. */
+ result = rustls_root_cert_store_builder_load_roots_from_file(
+ roots_builder, ssl_cafile, verifypeer);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "rustls: failed to load trusted certificates");
+ rustls_root_cert_store_builder_free(roots_builder);
+ rustls_client_config_builder_free(config_builder);
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
- /* Enable strict parsing only if verification isn't disabled. */
- result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data,
- ca_info_blob->len, verifypeer);
+ result = rustls_root_cert_store_builder_build(roots_builder, &roots);
+ rustls_root_cert_store_builder_free(roots_builder);
if(result != RUSTLS_RESULT_OK) {
- failf(data, "rustls: failed to parse trusted certificates from blob");
- rustls_root_cert_store_free(roots);
- rustls_client_config_free(
- rustls_client_config_builder_build(config_builder));
+ failf(data, "rustls: failed to build trusted root certificate store");
+ rustls_client_config_builder_free(config_builder);
return CURLE_SSL_CACERT_BADFILE;
}
- result = rustls_client_config_builder_use_roots(config_builder, roots);
+ verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(roots);
rustls_root_cert_store_free(roots);
- if(result != RUSTLS_RESULT_OK) {
- failf(data, "rustls: failed to load trusted certificates");
- rustls_client_config_free(
- rustls_client_config_builder_build(config_builder));
- return CURLE_SSL_CACERT_BADFILE;
+
+ if(conn_config->CRLfile) {
+ struct dynbuf crl_contents;
+ Curl_dyn_init(&crl_contents, SIZE_MAX);
+ if(!read_file_into(conn_config->CRLfile, &crl_contents)) {
+ failf(data, "rustls: failed to read revocation list file");
+ Curl_dyn_free(&crl_contents);
+ rustls_web_pki_server_cert_verifier_builder_free(verifier_builder);
+ return CURLE_SSL_CRL_BADFILE;
+ }
+
+ result = rustls_web_pki_server_cert_verifier_builder_add_crl(
+ verifier_builder,
+ Curl_dyn_uptr(&crl_contents),
+ Curl_dyn_len(&crl_contents));
+ Curl_dyn_free(&crl_contents);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "rustls: failed to parse revocation list");
+ rustls_web_pki_server_cert_verifier_builder_free(verifier_builder);
+ return CURLE_SSL_CRL_BADFILE;
+ }
}
- }
- else if(ssl_cafile) {
- result = rustls_client_config_builder_load_roots_from_file(
- config_builder, ssl_cafile);
+
+ result = rustls_web_pki_server_cert_verifier_builder_build(
+ verifier_builder, &server_cert_verifier);
+ rustls_web_pki_server_cert_verifier_builder_free(verifier_builder);
if(result != RUSTLS_RESULT_OK) {
- failf(data, "rustls: failed to load trusted certificates");
- rustls_client_config_free(
- rustls_client_config_builder_build(config_builder));
+ failf(data, "rustls: failed to build certificate verifier");
+ rustls_server_cert_verifier_free(server_cert_verifier);
+ rustls_client_config_builder_free(config_builder);
return CURLE_SSL_CACERT_BADFILE;
}
+
+ rustls_client_config_builder_set_server_verifier(config_builder,
+ server_cert_verifier);
+ rustls_server_cert_verifier_free(server_cert_verifier);
}
- backend->config = rustls_client_config_builder_build(config_builder);
- DEBUGASSERT(rconn == NULL);
- {
- /* rustls claims to manage ip address hostnames as well here. So,
- * if we have an SNI, we use it, otherwise we pass the hostname */
- char *server = connssl->peer.sni?
- connssl->peer.sni : connssl->peer.hostname;
- result = rustls_client_connection_new(backend->config, server, &rconn);
+ result = rustls_client_config_builder_build(
+ config_builder,
+ &backend->config);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "rustls: failed to build client config");
+ rustls_client_config_free(backend->config);
+ return CURLE_SSL_ENGINE_INITFAILED;
}
+
+ DEBUGASSERT(rconn == NULL);
+ result = rustls_client_connection_new(backend->config,
+ connssl->peer.hostname, &rconn);
if(result != RUSTLS_RESULT_OK) {
rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen);
- failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf);
+ failf(data, "rustls_client_connection_new: %.*s", (int)errorlen, errorbuf);
return CURLE_COULDNT_CONNECT;
}
+ DEBUGASSERT(rconn);
rustls_connection_set_userdata(rconn, backend);
backend->conn = rconn;
return CURLE_OK;
@@ -510,9 +807,12 @@ cr_connect_common(struct Curl_cfilter *cf,
DEBUGASSERT(backend);
- if(ssl_connection_none == connssl->state) {
+ CURL_TRC_CF(data, cf, "cr_connect_common, state=%d", connssl->state);
+ *done = FALSE;
+ if(!backend->conn) {
result = cr_init_backend(cf, data,
(struct rustls_ssl_backend_data *)connssl->backend);
+ CURL_TRC_CF(data, cf, "cr_connect_common, init backend -> %d", result);
if(result != CURLE_OK) {
return result;
}
@@ -524,22 +824,47 @@ cr_connect_common(struct Curl_cfilter *cf,
/* Read/write data until the handshake is done or the socket would block. */
for(;;) {
/*
- * Connection has been established according to rustls. Set send/recv
+ * Connection has been established according to Rustls. Set send/recv
* handlers, and update the state machine.
*/
+ connssl->io_need = CURL_SSL_IO_NEED_NONE;
if(!rustls_connection_is_handshaking(rconn)) {
- infof(data, "Done handshaking");
- /* Done with the handshake. Set up callbacks to send/receive data. */
- connssl->state = ssl_connection_complete;
-
+ /* 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.
+ */
cr_set_negotiated_alpn(cf, data, rconn);
-
+ cr_send(cf, data, NULL, 0, &tmperr);
+ if(tmperr == CURLE_AGAIN) {
+ connssl->io_need = CURL_SSL_IO_NEED_SEND;
+ return CURLE_OK;
+ }
+ else if(tmperr != CURLE_OK) {
+ return tmperr;
+ }
+ /* REALLY Done with the handshake. */
+ {
+ uint16_t proto = rustls_connection_get_protocol_version(rconn);
+ uint16_t cipher = rustls_connection_get_negotiated_ciphersuite(rconn);
+ 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;
}
+ connssl->connecting_state = ssl_connect_2;
wants_read = rustls_connection_wants_read(rconn);
- wants_write = rustls_connection_wants_write(rconn);
+ wants_write = rustls_connection_wants_write(rconn) ||
+ backend->plain_out_buffered;
DEBUGASSERT(wants_read || wants_write);
writefd = wants_write?sockfd:CURL_SOCKET_BAD;
readfd = wants_read?sockfd:CURL_SOCKET_BAD;
@@ -555,32 +880,35 @@ cr_connect_common(struct Curl_cfilter *cf,
socket_check_timeout = blocking?timeout_ms:0;
- what = Curl_socket_check(
- readfd, CURL_SOCKET_BAD, writefd, socket_check_timeout);
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
+ socket_check_timeout);
if(what < 0) {
/* fatal error */
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
return CURLE_SSL_CONNECT_ERROR;
}
if(blocking && 0 == what) {
- failf(data, "rustls connection timeout after %d ms",
- socket_check_timeout);
+ failf(data, "rustls: connection timeout after %" FMT_TIMEDIFF_T " ms",
+ socket_check_timeout);
return CURLE_OPERATION_TIMEDOUT;
}
if(0 == what) {
- infof(data, "Curl_socket_check: %s would block",
+ CURL_TRC_CF(data, cf, "Curl_socket_check: %s would block",
wants_read&&wants_write ? "writing and reading" :
wants_write ? "writing" : "reading");
- *done = FALSE;
+ if(wants_write)
+ connssl->io_need |= CURL_SSL_IO_NEED_SEND;
+ if(wants_read)
+ connssl->io_need |= CURL_SSL_IO_NEED_RECV;
return CURLE_OK;
}
/* socket is readable or writable */
if(wants_write) {
- infof(data, "rustls_connection wants us to write_tls.");
+ CURL_TRC_CF(data, cf, "rustls_connection wants us to write_tls.");
cr_send(cf, data, NULL, 0, &tmperr);
if(tmperr == CURLE_AGAIN) {
- infof(data, "writing would block");
+ CURL_TRC_CF(data, cf, "writing would block");
/* fall through */
}
else if(tmperr != CURLE_OK) {
@@ -589,14 +917,13 @@ cr_connect_common(struct Curl_cfilter *cf,
}
if(wants_read) {
- infof(data, "rustls_connection wants us to read_tls.");
-
+ CURL_TRC_CF(data, cf, "rustls_connection wants us to read_tls.");
if(tls_recv_more(cf, data, &tmperr) < 0) {
if(tmperr == CURLE_AGAIN) {
- infof(data, "reading would block");
+ CURL_TRC_CF(data, cf, "reading would block");
/* fall through */
}
- else if(tmperr == CURLE_READ_ERROR) {
+ else if(tmperr == CURLE_RECV_ERROR) {
return CURLE_SSL_CONNECT_ERROR;
}
else {
@@ -607,7 +934,7 @@ cr_connect_common(struct Curl_cfilter *cf,
}
/* We should never fall through the loop. We should return either because
- the handshake is done or because we can't read/write without blocking. */
+ the handshake is done or because we cannot read/write without blocking. */
DEBUGASSERT(false);
}
@@ -619,37 +946,12 @@ cr_connect_nonblocking(struct Curl_cfilter *cf,
}
static CURLcode
-cr_connect_blocking(struct Curl_cfilter *cf UNUSED_PARAM,
- struct Curl_easy *data UNUSED_PARAM)
+cr_connect_blocking(struct Curl_cfilter *cf, struct Curl_easy *data)
{
bool done; /* unused */
return cr_connect_common(cf, data, true, &done);
}
-static void cr_adjust_pollset(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- struct easy_pollset *ps)
-{
- if(!cf->connected) {
- curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data);
- struct ssl_connect_data *const connssl = cf->ctx;
- struct rustls_ssl_backend_data *const backend =
- (struct rustls_ssl_backend_data *)connssl->backend;
- struct rustls_connection *rconn = NULL;
-
- (void)data;
- DEBUGASSERT(backend);
- rconn = backend->conn;
-
- if(rustls_connection_wants_write(rconn)) {
- Curl_pollset_add_out(data, ps, sock);
- }
- if(rustls_connection_wants_read(rconn)) {
- Curl_pollset_add_in(data, ps, sock);
- }
- }
-}
-
static void *
cr_get_internals(struct ssl_connect_data *connssl,
CURLINFO info UNUSED_PARAM)
@@ -660,24 +962,85 @@ cr_get_internals(struct ssl_connect_data *connssl,
return &backend->conn;
}
-static void
-cr_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+static CURLcode
+cr_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool send_shutdown, bool *done)
{
struct ssl_connect_data *connssl = cf->ctx;
struct rustls_ssl_backend_data *backend =
(struct rustls_ssl_backend_data *)connssl->backend;
- CURLcode tmperr = CURLE_OK;
- ssize_t n = 0;
+ CURLcode result = CURLE_OK;
+ ssize_t nwritten, nread;
+ char buf[1024];
+ size_t i;
DEBUGASSERT(backend);
+ if(!backend->conn || cf->shutdown) {
+ *done = TRUE;
+ goto out;
+ }
- if(backend->conn) {
- rustls_connection_send_close_notify(backend->conn);
- n = cr_send(cf, data, NULL, 0, &tmperr);
- if(n < 0) {
- failf(data, "rustls: error sending close_notify: %d", tmperr);
+ connssl->io_need = CURL_SSL_IO_NEED_NONE;
+ *done = FALSE;
+
+ if(!backend->sent_shutdown) {
+ /* do this only once */
+ backend->sent_shutdown = TRUE;
+ if(send_shutdown) {
+ rustls_connection_send_close_notify(backend->conn);
+ }
+ }
+
+ nwritten = cr_send(cf, data, NULL, 0, &result);
+ if(nwritten < 0) {
+ if(result == CURLE_AGAIN) {
+ connssl->io_need = CURL_SSL_IO_NEED_SEND;
+ result = CURLE_OK;
+ goto out;
}
+ DEBUGASSERT(result);
+ CURL_TRC_CF(data, cf, "shutdown send failed: %d", result);
+ goto out;
+ }
+
+ for(i = 0; i < 10; ++i) {
+ nread = cr_recv(cf, data, buf, (int)sizeof(buf), &result);
+ if(nread <= 0)
+ break;
+ }
+ if(nread > 0) {
+ /* still data coming in? */
+ }
+ else if(nread == 0) {
+ /* We got the close notify alert and are done. */
+ *done = TRUE;
+ }
+ else if(result == CURLE_AGAIN) {
+ connssl->io_need = CURL_SSL_IO_NEED_RECV;
+ result = CURLE_OK;
+ }
+ else {
+ DEBUGASSERT(result);
+ CURL_TRC_CF(data, cf, "shutdown, error: %d", result);
+ }
+
+out:
+ cf->shutdown = (result || *done);
+ return result;
+}
+
+static void
+cr_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct rustls_ssl_backend_data *backend =
+ (struct rustls_ssl_backend_data *)connssl->backend;
+
+ (void)data;
+ DEBUGASSERT(backend);
+ if(backend->conn) {
rustls_connection_free(backend->conn);
backend->conn = NULL;
}
@@ -693,28 +1056,38 @@ static size_t cr_version(char *buffer, size_t size)
return msnprintf(buffer, size, "%.*s", (int)ver.len, ver.data);
}
+static CURLcode
+cr_random(struct Curl_easy *data, unsigned char *entropy, size_t length)
+{
+ rustls_result rresult = 0;
+ (void)data;
+ rresult =
+ rustls_default_crypto_provider_random(entropy, length);
+ return map_error(rresult);
+}
+
const struct Curl_ssl Curl_ssl_rustls = {
{ CURLSSLBACKEND_RUSTLS, "rustls" },
SSLSUPP_CAINFO_BLOB | /* supports */
- SSLSUPP_TLS13_CIPHERSUITES |
- SSLSUPP_HTTPS_PROXY,
+ SSLSUPP_HTTPS_PROXY |
+ SSLSUPP_CIPHER_LIST |
+ SSLSUPP_TLS13_CIPHERSUITES,
sizeof(struct rustls_ssl_backend_data),
Curl_none_init, /* init */
Curl_none_cleanup, /* cleanup */
cr_version, /* version */
Curl_none_check_cxn, /* check_cxn */
- Curl_none_shutdown, /* shutdown */
+ cr_shutdown, /* shutdown */
cr_data_pending, /* data_pending */
- Curl_none_random, /* random */
+ cr_random, /* random */
Curl_none_cert_status_request, /* cert_status_request */
cr_connect_blocking, /* connect */
cr_connect_nonblocking, /* connect_nonblocking */
- cr_adjust_pollset, /* adjust_pollset */
+ Curl_ssl_adjust_pollset, /* adjust_pollset */
cr_get_internals, /* get_internals */
cr_close, /* close_one */
Curl_none_close_all, /* close_all */
- Curl_none_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */
@@ -722,9 +1095,9 @@ const struct Curl_ssl Curl_ssl_rustls = {
NULL, /* sha256sum */
NULL, /* associate_connection */
NULL, /* disassociate_connection */
- NULL, /* free_multi_ssl_backend_data */
cr_recv, /* recv decrypted data */
cr_send, /* send data to encrypt */
+ NULL, /* get_channel_binding */
};
#endif /* USE_RUSTLS */