| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) Jacob Hoffman-Andrews, <[email protected]> |
| * Copyright (C) kpcyrd, <[email protected]> |
| * Copyright (C) Daniel McCarney, <[email protected]> |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at https://curl.se/docs/copyright.html. |
| * |
| * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| * copies of the Software, and permit persons to whom the Software is |
| * furnished to do so, under the terms of the COPYING file. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| * SPDX-License-Identifier: curl |
| * |
| ***************************************************************************/ |
| #include "../curl_setup.h" |
| |
| #ifdef USE_RUSTLS |
| |
| #include <rustls.h> |
| |
| #include "../curlx/fopen.h" |
| #include "../curlx/strerr.h" |
| #include "../urldata.h" |
| #include "../curl_trc.h" |
| #include "vtls.h" |
| #include "vtls_int.h" |
| #include "rustls.h" |
| #include "keylog.h" |
| #include "cipher_suite.h" |
| #include "x509asn1.h" |
| |
| struct rustls_ssl_backend_data { |
| const struct rustls_client_config *config; |
| struct rustls_connection *conn; |
| size_t plain_out_buffered; |
| BIT(data_in_pending); |
| BIT(sent_shutdown); |
| }; |
| |
| /* For a given rustls_result error code, return the best-matching CURLcode. */ |
| static CURLcode map_error(const rustls_result r) |
| { |
| if(rustls_result_is_cert_error(r)) { |
| return CURLE_PEER_FAILED_VERIFICATION; |
| } |
| switch(r) { |
| case RUSTLS_RESULT_OK: |
| return CURLE_OK; |
| case RUSTLS_RESULT_NULL_PARAMETER: |
| return CURLE_BAD_FUNCTION_ARGUMENT; |
| default: |
| return CURLE_RECV_ERROR; |
| } |
| } |
| |
| static void rustls_failf(struct Curl_easy *data, const rustls_result rr, |
| const char *msg) |
| { |
| char errorbuf[STRERROR_LEN]; |
| size_t errorlen; |
| rustls_error(rr, errorbuf, sizeof(errorbuf), &errorlen); |
| failf(data, "%s: %.*s", msg, (int)errorlen, errorbuf); |
| } |
| |
| static bool cr_data_pending(struct Curl_cfilter *cf, |
| const struct Curl_easy *data) |
| { |
| const struct ssl_connect_data *ctx = cf->ctx; |
| struct rustls_ssl_backend_data *backend; |
| |
| (void)data; |
| DEBUGASSERT(ctx && ctx->backend); |
| backend = (struct rustls_ssl_backend_data *)ctx->backend; |
| return backend->data_in_pending; |
| } |
| |
| struct io_ctx { |
| struct Curl_cfilter *cf; |
| struct Curl_easy *data; |
| }; |
| |
| static int read_cb(void *userdata, uint8_t *buf, uintptr_t len, |
| uintptr_t *out_n) |
| { |
| const struct io_ctx *io_ctx = userdata; |
| struct ssl_connect_data * const connssl = io_ctx->cf->ctx; |
| CURLcode result; |
| int ret = 0; |
| size_t nread; |
| |
| result = Curl_conn_cf_recv(io_ctx->cf->next, io_ctx->data, |
| (char *)buf, len, &nread); |
| if(result) { |
| nread = 0; |
| /* !checksrc! disable ERRNOVAR 4 */ |
| if(CURLE_AGAIN == result) |
| ret = EAGAIN; |
| else |
| ret = EINVAL; |
| } |
| 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) -> %d, %zu", |
| (size_t)len, result, nread); |
| return ret; |
| } |
| |
| static int write_cb(void *userdata, const uint8_t *buf, uintptr_t len, |
| uintptr_t *out_n) |
| { |
| const struct io_ctx *io_ctx = userdata; |
| CURLcode result; |
| int ret = 0; |
| size_t nwritten; |
| |
| result = Curl_conn_cf_send(io_ctx->cf->next, io_ctx->data, |
| buf, len, FALSE, &nwritten); |
| if(result) { |
| nwritten = 0; |
| if(CURLE_AGAIN == result) |
| ret = EAGAIN; |
| else |
| ret = EINVAL; |
| } |
| *out_n = (uintptr_t)nwritten; |
| CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %d, %zu", |
| len, result, nwritten); |
| return ret; |
| } |
| |
| static ssize_t tls_recv_more(struct Curl_cfilter *cf, |
| struct Curl_easy *data, CURLcode *err) |
| { |
| const struct ssl_connect_data * const connssl = cf->ctx; |
| struct rustls_ssl_backend_data * const backend = |
| (struct rustls_ssl_backend_data *)connssl->backend; |
| struct io_ctx io_ctx; |
| size_t tls_bytes_read = 0; |
| rustls_io_result io_error; |
| rustls_result rresult = 0; |
| |
| io_ctx.cf = cf; |
| io_ctx.data = data; |
| io_error = rustls_connection_read_tls(backend->conn, read_cb, &io_ctx, |
| &tls_bytes_read); |
| if(io_error == EAGAIN || io_error == EWOULDBLOCK) { |
| *err = CURLE_AGAIN; |
| return -1; |
| } |
| else if(io_error) { |
| char buffer[STRERROR_LEN]; |
| failf(data, "reading from socket: %s", |
| curlx_strerror(io_error, buffer, sizeof(buffer))); |
| *err = CURLE_RECV_ERROR; |
| return -1; |
| } |
| |
| rresult = rustls_connection_process_new_packets(backend->conn); |
| if(rresult != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rresult, "rustls_connection_process_new_packets"); |
| *err = map_error(rresult); |
| return -1; |
| } |
| |
| backend->data_in_pending = TRUE; |
| *err = CURLE_OK; |
| return (ssize_t)tls_bytes_read; |
| } |
| |
| /* |
| * Filter receive method implementation. `plainbuf` and `plainlen` |
| * are always not NULL/0. |
| */ |
| static CURLcode cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, |
| char *plainbuf, size_t plainlen, size_t *pnread) |
| { |
| const 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; |
| CURLcode result = CURLE_OK; |
| size_t n = 0; |
| rustls_result rresult = 0; |
| bool eof = FALSE; |
| |
| DEBUGASSERT(backend); |
| *pnread = 0; |
| rconn = backend->conn; |
| |
| while(*pnread < plainlen) { |
| if(!backend->data_in_pending) { |
| if(tls_recv_more(cf, data, &result) < 0) { |
| if(result != CURLE_AGAIN) { |
| goto out; |
| } |
| result = CURLE_OK; |
| break; |
| } |
| } |
| |
| rresult = rustls_connection_read(rconn, |
| (uint8_t *)plainbuf + *pnread, |
| plainlen - *pnread, |
| &n); |
| if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { |
| backend->data_in_pending = FALSE; |
| } |
| else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { |
| failf(data, "rustls: peer closed TCP connection " |
| "without first closing TLS connection"); |
| result = CURLE_RECV_ERROR; |
| goto out; |
| } |
| else if(rresult != RUSTLS_RESULT_OK) { |
| /* n always equals 0 in this case, do not need to check it */ |
| rustls_failf(data, rresult, "rustls_connection_read"); |
| result = CURLE_RECV_ERROR; |
| goto out; |
| } |
| else if(n == 0) { |
| /* n == 0 indicates clean EOF, but we may have read some other |
| plaintext bytes before we reached this. Break out of the loop |
| so we can figure out whether to return success or EOF. */ |
| eof = TRUE; |
| break; |
| } |
| else { |
| *pnread += n; |
| } |
| } |
| |
| if(!eof && !*pnread) { |
| result = CURLE_AGAIN; |
| } |
| |
| out: |
| CURL_TRC_CF(data, cf, "rustls_recv(len=%zu) -> %d, %zu", |
| plainlen, result, *pnread); |
| return result; |
| } |
| |
| 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; |
| |
| 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", |
| curlx_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 CURLE_OK; |
| } |
| |
| /* |
| * On each call: |
| * - 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 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 CURLcode cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, |
| const void *plainbuf, size_t plainlen, |
| size_t *pnwritten) |
| { |
| const 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; |
| size_t plainwritten = 0; |
| const unsigned char *buf = plainbuf; |
| CURLcode result = CURLE_OK; |
| size_t blen = plainlen; |
| |
| DEBUGASSERT(backend); |
| *pnwritten = 0; |
| 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) { |
| result = cr_flush_out(cf, data, rconn); |
| CURL_TRC_CF(data, cf, "cf_send: flushing %zu previously added bytes -> %d", |
| backend->plain_out_buffered, result); |
| if(result) |
| return result; |
| if(blen > backend->plain_out_buffered) { |
| blen -= backend->plain_out_buffered; |
| buf += backend->plain_out_buffered; |
| } |
| else |
| blen = 0; |
| *pnwritten += (ssize_t)backend->plain_out_buffered; |
| backend->plain_out_buffered = 0; |
| } |
| |
| if(blen > 0) { |
| rustls_result rresult; |
| 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_failf(data, rresult, "rustls_connection_write"); |
| result = CURLE_WRITE_ERROR; |
| goto out; |
| } |
| else if(plainwritten == 0) { |
| failf(data, "rustls_connection_write: EOF"); |
| result = CURLE_WRITE_ERROR; |
| goto out; |
| } |
| } |
| |
| result = cr_flush_out(cf, data, rconn); |
| if(result) { |
| if(CURLE_AGAIN == result) { |
| /* The TLS bytes may have been partially written, but we fail the |
| * complete send() and remember how much we already added to Rustls. */ |
| backend->plain_out_buffered = plainwritten; |
| if(*pnwritten) { |
| result = CURLE_OK; |
| } |
| } |
| goto out; |
| } |
| else |
| *pnwritten += (ssize_t)plainwritten; |
| |
| out: |
| CURL_TRC_CF(data, cf, "rustls_send(len=%zu) -> %d, %zd", |
| plainlen, result, *pnwritten); |
| return result; |
| } |
| |
| /* A server certificate verify callback for Rustls that always returns |
| RUSTLS_RESULT_OK, or in other words disable certificate verification. */ |
| static uint32_t cr_verify_none(void *userdata, |
| const rustls_verify_server_cert_params *params) |
| { |
| (void)userdata; |
| (void)params; |
| return RUSTLS_RESULT_OK; |
| } |
| |
| static int read_file_into(const char *filename, struct dynbuf *out) |
| { |
| FILE *f = curlx_fopen(filename, FOPEN_READTEXT); |
| if(!f) { |
| return 0; |
| } |
| |
| for(;;) { |
| uint8_t buf[256]; |
| const size_t rr = fread(buf, 1, sizeof(buf), f); |
| if((!rr && !feof(f)) || |
| curlx_dyn_addn(out, buf, rr)) { |
| curlx_fclose(f); |
| return 0; |
| } |
| if(rr < sizeof(buf)) |
| break; |
| } |
| |
| return curlx_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) |
| { |
| const size_t supported_len = *selected_size; |
| const size_t default_len = rustls_default_crypto_provider_ciphersuites_len(); |
| const struct rustls_supported_ciphersuite *entry = NULL; |
| 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 = ""; |
| } |
| 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; |
| } |
| |
| 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 void cr_keylog_log_cb(struct rustls_str label, |
| const uint8_t *client_random, |
| size_t client_random_len, const uint8_t *secret, |
| size_t secret_len) |
| { |
| char clabel[KEYLOG_LABEL_MAXLEN]; |
| (void)client_random_len; |
| DEBUGASSERT(client_random_len == CLIENT_RANDOM_SIZE); |
| /* Turning a "rustls_str" into a null delimited "c" string */ |
| curl_msnprintf(clabel, sizeof(clabel), "%.*s", (int)label.len, label.data); |
| Curl_tls_keylog_write(clabel, client_random, secret, secret_len); |
| } |
| |
| static CURLcode |
| init_config_builder(struct Curl_easy *data, |
| const struct ssl_primary_config *conn_config, |
| struct rustls_client_config_builder **config_builder) |
| { |
| const struct rustls_supported_ciphersuite **cipher_suites = NULL; |
| struct rustls_crypto_provider_builder *custom_provider_builder = NULL; |
| const struct rustls_crypto_provider *custom_provider = NULL; |
| |
| uint16_t tls_versions[2] = { |
| RUSTLS_TLS_VERSION_TLSV1_2, |
| RUSTLS_TLS_VERSION_TLSV1_3, |
| }; |
| size_t tls_versions_len = 2; |
| size_t cipher_suites_len = rustls_default_crypto_provider_ciphersuites_len(); |
| |
| CURLcode result = CURLE_OK; |
| rustls_result rr; |
| |
| 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"); |
| result = CURLE_BAD_FUNCTION_ARGUMENT; |
| goto cleanup; |
| } |
| |
| 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"); |
| result = CURLE_BAD_FUNCTION_ARGUMENT; |
| goto cleanup; |
| } |
| |
| #ifdef USE_ECH |
| if(ECH_ENABLED(data)) { |
| tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3; |
| tls_versions_len = 1; |
| infof(data, "rustls: ECH enabled, forcing TLSv1.3"); |
| } |
| #endif /* USE_ECH */ |
| |
| cipher_suites = curlx_malloc(sizeof(*cipher_suites) * (cipher_suites_len)); |
| if(!cipher_suites) { |
| result = CURLE_OUT_OF_MEMORY; |
| goto cleanup; |
| } |
| |
| 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"); |
| result = CURLE_SSL_CIPHER; |
| goto cleanup; |
| } |
| |
| rr = rustls_crypto_provider_builder_new_from_default( |
| &custom_provider_builder); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, |
| "failed to create crypto provider builder from default"); |
| result = CURLE_SSL_CIPHER; |
| goto cleanup; |
| } |
| |
| rr = |
| rustls_crypto_provider_builder_set_cipher_suites( |
| custom_provider_builder, |
| cipher_suites, |
| cipher_suites_len); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, |
| "failed to set ciphersuites for crypto provider builder"); |
| result = CURLE_SSL_CIPHER; |
| goto cleanup; |
| } |
| |
| rr = rustls_crypto_provider_builder_build(custom_provider_builder, |
| &custom_provider); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to build custom crypto provider"); |
| result = CURLE_SSL_CIPHER; |
| goto cleanup; |
| } |
| |
| rr = rustls_client_config_builder_new_custom(custom_provider, |
| tls_versions, |
| tls_versions_len, |
| config_builder); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to create client config builder"); |
| result = CURLE_SSL_CIPHER; |
| goto cleanup; |
| } |
| |
| cleanup: |
| if(cipher_suites) { |
| curlx_free(cipher_suites); |
| } |
| if(custom_provider_builder) { |
| rustls_crypto_provider_builder_free(custom_provider_builder); |
| } |
| if(custom_provider) { |
| rustls_crypto_provider_free(custom_provider); |
| } |
| return result; |
| } |
| |
| static void |
| init_config_builder_alpn(struct Curl_easy *data, |
| const struct ssl_connect_data *connssl, |
| struct rustls_client_config_builder *config_builder) |
| { |
| struct alpn_proto_buf proto; |
| rustls_slice_bytes alpn[ALPN_ENTRIES_MAX]; |
| size_t i; |
| |
| for(i = 0; i < connssl->alpn->count; ++i) { |
| alpn[i].data = (const uint8_t *)connssl->alpn->entries[i]; |
| alpn[i].len = strlen(connssl->alpn->entries[i]); |
| } |
| rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, |
| connssl->alpn->count); |
| Curl_alpn_to_proto_str(&proto, connssl->alpn); |
| infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); |
| } |
| |
| static CURLcode init_config_builder_verifier_crl( |
| struct Curl_easy *data, |
| const struct ssl_primary_config *conn_config, |
| struct rustls_web_pki_server_cert_verifier_builder *builder) |
| { |
| CURLcode result = CURLE_OK; |
| struct dynbuf crl_contents; |
| rustls_result rr; |
| |
| curlx_dyn_init(&crl_contents, DYN_CRLFILE_SIZE); |
| if(!read_file_into(conn_config->CRLfile, &crl_contents)) { |
| failf(data, "rustls: failed to read revocation list file"); |
| result = CURLE_SSL_CRL_BADFILE; |
| goto cleanup; |
| } |
| |
| rr = rustls_web_pki_server_cert_verifier_builder_add_crl( |
| builder, |
| curlx_dyn_uptr(&crl_contents), |
| curlx_dyn_len(&crl_contents)); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to parse revocation list"); |
| result = CURLE_SSL_CRL_BADFILE; |
| goto cleanup; |
| } |
| |
| cleanup: |
| curlx_dyn_free(&crl_contents); |
| return result; |
| } |
| |
| static CURLcode |
| init_config_builder_verifier(struct Curl_easy *data, |
| struct rustls_client_config_builder *builder, |
| const struct ssl_primary_config *conn_config, |
| const struct curl_blob *ca_info_blob, |
| const char * const ssl_cafile) |
| { |
| 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; |
| rustls_result rr = RUSTLS_RESULT_OK; |
| CURLcode result = CURLE_OK; |
| |
| roots_builder = rustls_root_cert_store_builder_new(); |
| if(ca_info_blob) { |
| rr = rustls_root_cert_store_builder_add_pem(roots_builder, |
| ca_info_blob->data, |
| ca_info_blob->len, |
| 1); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to parse trusted certificates from blob"); |
| result = CURLE_SSL_CACERT_BADFILE; |
| goto cleanup; |
| } |
| } |
| else if(ssl_cafile) { |
| rr = rustls_root_cert_store_builder_load_roots_from_file(roots_builder, |
| ssl_cafile, |
| 1); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to load trusted certificates"); |
| result = CURLE_SSL_CACERT_BADFILE; |
| goto cleanup; |
| } |
| } |
| |
| rr = rustls_root_cert_store_builder_build(roots_builder, &roots); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to build trusted root certificate store"); |
| result = CURLE_SSL_CACERT_BADFILE; |
| goto cleanup; |
| } |
| |
| verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(roots); |
| if(!verifier_builder) { |
| result = CURLE_OUT_OF_MEMORY; |
| goto cleanup; |
| } |
| |
| if(conn_config->CRLfile) { |
| result = init_config_builder_verifier_crl(data, |
| conn_config, |
| verifier_builder); |
| if(result) { |
| goto cleanup; |
| } |
| } |
| |
| rr = rustls_web_pki_server_cert_verifier_builder_build( |
| verifier_builder, &server_cert_verifier); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to build certificate verifier"); |
| result = CURLE_SSL_CACERT_BADFILE; |
| goto cleanup; |
| } |
| |
| rustls_client_config_builder_set_server_verifier(builder, |
| server_cert_verifier); |
| cleanup: |
| if(roots_builder) { |
| rustls_root_cert_store_builder_free(roots_builder); |
| } |
| if(roots) { |
| rustls_root_cert_store_free(roots); |
| } |
| if(verifier_builder) { |
| rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); |
| } |
| if(server_cert_verifier) { |
| rustls_server_cert_verifier_free(server_cert_verifier); |
| } |
| |
| return result; |
| } |
| |
| static CURLcode init_config_builder_platform_verifier( |
| struct Curl_easy *data, |
| struct rustls_client_config_builder *builder) |
| { |
| struct rustls_server_cert_verifier *server_cert_verifier = NULL; |
| CURLcode result = CURLE_OK; |
| rustls_result rr; |
| |
| rr = rustls_platform_server_cert_verifier(&server_cert_verifier); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to create platform certificate verifier"); |
| result = CURLE_SSL_CACERT_BADFILE; |
| goto cleanup; |
| } |
| |
| rustls_client_config_builder_set_server_verifier(builder, |
| server_cert_verifier); |
| |
| cleanup: |
| if(server_cert_verifier) { |
| rustls_server_cert_verifier_free(server_cert_verifier); |
| } |
| return result; |
| } |
| |
| static CURLcode |
| init_config_builder_keylog(struct Curl_easy *data, |
| struct rustls_client_config_builder *builder) |
| { |
| rustls_result rr; |
| |
| Curl_tls_keylog_open(); |
| if(!Curl_tls_keylog_enabled()) { |
| return CURLE_OK; |
| } |
| |
| rr = rustls_client_config_builder_set_key_log(builder, |
| cr_keylog_log_cb, |
| NULL); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "rustls_client_config_builder_set_key_log"); |
| Curl_tls_keylog_close(); |
| return map_error(rr); |
| } |
| |
| return CURLE_OK; |
| } |
| |
| static CURLcode |
| init_config_builder_client_auth(struct Curl_easy *data, |
| const struct ssl_primary_config *conn_config, |
| const struct ssl_config_data *ssl_config, |
| struct rustls_client_config_builder *builder) |
| { |
| struct dynbuf cert_contents; |
| struct dynbuf key_contents; |
| rustls_result rr; |
| const struct rustls_certified_key *certified_key = NULL; |
| CURLcode result = CURLE_OK; |
| |
| if(conn_config->clientcert && !ssl_config->key) { |
| failf(data, "rustls: must provide key with certificate '%s'", |
| conn_config->clientcert); |
| return CURLE_SSL_CERTPROBLEM; |
| } |
| else if(!conn_config->clientcert && ssl_config->key) { |
| failf(data, "rustls: must provide certificate with key '%s'", |
| ssl_config->key); |
| return CURLE_SSL_CERTPROBLEM; |
| } |
| |
| curlx_dyn_init(&cert_contents, DYN_CERTFILE_SIZE); |
| curlx_dyn_init(&key_contents, DYN_KEYFILE_SIZE); |
| |
| if(!read_file_into(conn_config->clientcert, &cert_contents)) { |
| failf(data, "rustls: failed to read client certificate file: '%s'", |
| conn_config->clientcert); |
| result = CURLE_SSL_CERTPROBLEM; |
| goto cleanup; |
| } |
| |
| if(!read_file_into(ssl_config->key, &key_contents)) { |
| failf(data, "rustls: failed to read key file: '%s'", ssl_config->key); |
| result = CURLE_SSL_CERTPROBLEM; |
| goto cleanup; |
| } |
| |
| rr = rustls_certified_key_build(curlx_dyn_uptr(&cert_contents), |
| curlx_dyn_len(&cert_contents), |
| curlx_dyn_uptr(&key_contents), |
| curlx_dyn_len(&key_contents), |
| &certified_key); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "rustls: failed to build certified key"); |
| result = CURLE_SSL_CERTPROBLEM; |
| goto cleanup; |
| } |
| |
| rr = rustls_certified_key_keys_match(certified_key); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, |
| "rustls: client certificate and keypair files do not match:"); |
| result = CURLE_SSL_CERTPROBLEM; |
| goto cleanup; |
| } |
| |
| rr = rustls_client_config_builder_set_certified_key(builder, |
| &certified_key, |
| 1); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "rustls: failed to set certified key"); |
| result = CURLE_SSL_CERTPROBLEM; |
| goto cleanup; |
| } |
| |
| cleanup: |
| curlx_dyn_free(&cert_contents); |
| curlx_dyn_free(&key_contents); |
| if(certified_key) { |
| rustls_certified_key_free(certified_key); |
| } |
| return result; |
| } |
| |
| #ifdef USE_ECH |
| static CURLcode |
| init_config_builder_ech(struct Curl_easy *data, |
| const struct ssl_connect_data *connssl, |
| struct rustls_client_config_builder *builder) |
| { |
| const rustls_hpke *hpke = rustls_supported_hpke(); |
| unsigned char *ech_config = NULL; |
| size_t ech_config_len = 0; |
| struct Curl_dns_entry *dns = NULL; |
| struct Curl_https_rrinfo *rinfo = NULL; |
| CURLcode result = CURLE_OK; |
| rustls_result rr; |
| |
| if(!hpke) { |
| failf(data, |
| "rustls: ECH unavailable, rustls-ffi built without " |
| "HPKE compatible crypto provider"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto cleanup; |
| } |
| |
| if(data->set.str[STRING_ECH_PUBLIC]) { |
| failf(data, "rustls: ECH outername not supported"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto cleanup; |
| } |
| |
| if(data->set.tls_ech == CURLECH_GREASE) { |
| rr = rustls_client_config_builder_enable_ech_grease(builder, hpke); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "rustls: failed to configure ECH GREASE"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto cleanup; |
| } |
| return CURLE_OK; |
| } |
| |
| if(data->set.tls_ech & CURLECH_CLA_CFG && data->set.str[STRING_ECH_CONFIG]) { |
| const char *b64 = data->set.str[STRING_ECH_CONFIG]; |
| size_t decode_result; |
| if(!b64) { |
| infof(data, "rustls: ECHConfig from command line empty"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto cleanup; |
| } |
| /* rustls-ffi expects the raw TLS encoded ECHConfigList bytes */ |
| decode_result = curlx_base64_decode(b64, &ech_config, &ech_config_len); |
| if(decode_result || !ech_config) { |
| infof(data, "rustls: cannot base64 decode ECHConfig from command line"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto cleanup; |
| } |
| } |
| else { |
| if(connssl->peer.hostname) { |
| dns = Curl_dnscache_get(data, connssl->peer.hostname, |
| connssl->peer.port, data->conn->ip_version); |
| } |
| if(!dns) { |
| failf(data, "rustls: ECH requested but no DNS info available"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto cleanup; |
| } |
| rinfo = dns->hinfo; |
| if(!rinfo || !rinfo->echconfiglist) { |
| failf(data, "rustls: ECH requested but no ECHConfig available"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto cleanup; |
| } |
| ech_config = rinfo->echconfiglist; |
| ech_config_len = rinfo->echconfiglist_len; |
| } |
| |
| rr = rustls_client_config_builder_enable_ech(builder, |
| ech_config, |
| ech_config_len, |
| hpke); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "rustls: failed to configure ECH"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto cleanup; |
| } |
| cleanup: |
| /* if we base64 decoded, we can free now */ |
| if(data->set.tls_ech & CURLECH_CLA_CFG && data->set.str[STRING_ECH_CONFIG]) { |
| curlx_free(ech_config); |
| } |
| if(dns) { |
| Curl_resolv_unlink(data, &dns); |
| } |
| return result; |
| } |
| #endif /* USE_ECH */ |
| |
| static CURLcode cr_init_backend(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct rustls_ssl_backend_data * const backend) |
| { |
| const struct ssl_connect_data *connssl = cf->ctx; |
| const struct ssl_primary_config *conn_config = |
| Curl_ssl_cf_get_primary_config(cf); |
| struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); |
| struct rustls_connection *rconn = NULL; |
| struct rustls_client_config_builder *config_builder = 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); |
| CURLcode result = CURLE_OK; |
| rustls_result rr; |
| |
| DEBUGASSERT(backend); |
| rconn = backend->conn; |
| |
| result = init_config_builder(data, conn_config, &config_builder); |
| if(result != CURLE_OK) { |
| return result; |
| } |
| |
| if(connssl->alpn) { |
| init_config_builder_alpn(data, connssl, config_builder); |
| } |
| |
| if(!conn_config->verifypeer) { |
| rustls_client_config_builder_dangerous_set_certificate_verifier( |
| config_builder, cr_verify_none); |
| } |
| else if(ssl_config->native_ca_store) { |
| result = init_config_builder_platform_verifier(data, config_builder); |
| if(result != CURLE_OK) { |
| rustls_client_config_builder_free(config_builder); |
| return result; |
| } |
| } |
| else if(ca_info_blob || ssl_cafile) { |
| result = init_config_builder_verifier(data, |
| config_builder, |
| conn_config, |
| ca_info_blob, |
| ssl_cafile); |
| if(result != CURLE_OK) { |
| rustls_client_config_builder_free(config_builder); |
| return result; |
| } |
| } |
| |
| if(conn_config->clientcert || ssl_config->key) { |
| result = init_config_builder_client_auth(data, |
| conn_config, |
| ssl_config, |
| config_builder); |
| if(result != CURLE_OK) { |
| rustls_client_config_builder_free(config_builder); |
| return result; |
| } |
| } |
| |
| #ifdef USE_ECH |
| if(ECH_ENABLED(data)) { |
| result = init_config_builder_ech(data, connssl, config_builder); |
| if(result != CURLE_OK && data->set.tls_ech & CURLECH_HARD) { |
| rustls_client_config_builder_free(config_builder); |
| return result; |
| } |
| } |
| #endif /* USE_ECH */ |
| |
| result = init_config_builder_keylog(data, config_builder); |
| if(result != CURLE_OK) { |
| rustls_client_config_builder_free(config_builder); |
| return result; |
| } |
| |
| rr = rustls_client_config_builder_build(config_builder, &backend->config); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "failed to build client config"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| DEBUGASSERT(rconn == NULL); |
| rr = rustls_client_connection_new(backend->config, |
| connssl->peer.hostname, |
| &rconn); |
| if(rr != RUSTLS_RESULT_OK) { |
| rustls_failf(data, rr, "rustls_client_connection_new"); |
| rustls_client_config_free(backend->config); |
| backend->config = NULL; |
| return CURLE_COULDNT_CONNECT; |
| } |
| DEBUGASSERT(rconn); |
| rustls_connection_set_userdata(rconn, backend); |
| backend->conn = rconn; |
| |
| return result; |
| } |
| |
| static void cr_set_negotiated_alpn(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| const struct rustls_connection *rconn) |
| { |
| struct ssl_connect_data * const connssl = cf->ctx; |
| const uint8_t *protocol = NULL; |
| size_t len = 0; |
| |
| rustls_connection_get_alpn_protocol(rconn, &protocol, &len); |
| Curl_alpn_set_negotiated(cf, data, connssl, protocol, len); |
| } |
| |
| /* Given an established network connection, do a TLS handshake. |
| * |
| * This function will set `*done` to true once the handshake is complete. |
| * This function never reads the value of `*done*`. |
| */ |
| static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, |
| bool *done) |
| { |
| struct ssl_connect_data * const connssl = cf->ctx; |
| const struct rustls_ssl_backend_data * const backend = |
| (struct rustls_ssl_backend_data *)connssl->backend; |
| const struct rustls_connection *rconn = NULL; |
| CURLcode tmperr = CURLE_OK; |
| int result; |
| bool wants_read; |
| bool wants_write; |
| |
| DEBUGASSERT(backend); |
| |
| CURL_TRC_CF(data, cf, "cr_connect, 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, init backend -> %d", result); |
| if(result != CURLE_OK) { |
| return result; |
| } |
| connssl->state = ssl_connection_negotiating; |
| } |
| rconn = backend->conn; |
| |
| /* Read/write data until the handshake is done or the socket would block. */ |
| for(;;) { |
| /* |
| * 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)) { |
| /* 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. |
| */ |
| size_t nwritten; |
| cr_set_negotiated_alpn(cf, data, rconn); |
| tmperr = cr_send(cf, data, NULL, 0, &nwritten); |
| 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. */ |
| { |
| const uint16_t proto = rustls_connection_get_protocol_version(rconn); |
| const rustls_str ciphersuite_name = |
| rustls_connection_get_negotiated_ciphersuite_name(rconn); |
| const rustls_str kex_group_name = |
| rustls_connection_get_negotiated_key_exchange_group_name(rconn); |
| 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"; |
| infof(data, |
| "rustls: handshake complete, %s, ciphersuite: %.*s, " |
| "key exchange group: %.*s", |
| ver, |
| (int) ciphersuite_name.len, |
| ciphersuite_name.data, |
| (int) kex_group_name.len, |
| kex_group_name.data); |
| } |
| if(data->set.ssl.certinfo) { |
| size_t num_certs = 0; |
| size_t i; |
| while(rustls_connection_get_peer_certificate(rconn, num_certs)) { |
| num_certs++; |
| if(num_certs > MAX_ALLOWED_CERT_AMOUNT) { |
| failf(data, "%zu certificates is more than allowed (%u)", |
| num_certs, MAX_ALLOWED_CERT_AMOUNT); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| result = Curl_ssl_init_certinfo(data, (int)num_certs); |
| if(result) |
| return result; |
| for(i = 0; i < num_certs; i++) { |
| const rustls_certificate *cert; |
| const unsigned char *der_data; |
| size_t der_len; |
| rustls_result rresult = RUSTLS_RESULT_OK; |
| cert = rustls_connection_get_peer_certificate(rconn, i); |
| DEBUGASSERT(cert); /* Should exist since we counted already */ |
| rresult = rustls_certificate_get_der(cert, &der_data, &der_len); |
| if(rresult != RUSTLS_RESULT_OK) { |
| char errorbuf[255]; |
| size_t errorlen; |
| rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); |
| failf(data, |
| "Failed getting DER of server certificate #%zu: %.*s", i, |
| (int)errorlen, errorbuf); |
| return map_error(rresult); |
| } |
| { |
| const char *beg; |
| const char *end; |
| beg = (const char *)der_data; |
| end = (const char *)(der_data + der_len); |
| result = Curl_extract_certinfo(data, (int)i, beg, end); |
| if(result) |
| return result; |
| } |
| } |
| } |
| |
| 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) || |
| backend->plain_out_buffered; |
| DEBUGASSERT(wants_read || wants_write); |
| |
| if(wants_write) { |
| size_t nwritten; |
| CURL_TRC_CF(data, cf, "rustls_connection wants us to write_tls."); |
| tmperr = cr_send(cf, data, NULL, 0, &nwritten); |
| if(tmperr == CURLE_AGAIN) { |
| CURL_TRC_CF(data, cf, "writing would block"); |
| connssl->io_need = CURL_SSL_IO_NEED_SEND; |
| return CURLE_OK; |
| } |
| else if(tmperr != CURLE_OK) { |
| return tmperr; |
| } |
| } |
| |
| if(wants_read) { |
| CURL_TRC_CF(data, cf, "rustls_connection wants us to read_tls."); |
| if(tls_recv_more(cf, data, &tmperr) < 0) { |
| if(tmperr == CURLE_AGAIN) { |
| CURL_TRC_CF(data, cf, "reading would block"); |
| connssl->io_need = CURL_SSL_IO_NEED_RECV; |
| return CURLE_OK; |
| } |
| else if(tmperr == CURLE_RECV_ERROR) { |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| else { |
| return tmperr; |
| } |
| } |
| } |
| } |
| |
| /* We should never fall through the loop. We should return either because |
| the handshake is done or because we cannot read/write without blocking. */ |
| DEBUGASSERT(FALSE); |
| } |
| |
| static void *cr_get_internals(struct ssl_connect_data *connssl, CURLINFO info) |
| { |
| struct rustls_ssl_backend_data *backend = |
| (struct rustls_ssl_backend_data *)connssl->backend; |
| (void)info; |
| DEBUGASSERT(backend); |
| return backend->conn; |
| } |
| |
| static CURLcode cr_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, |
| const 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 result = CURLE_OK; |
| size_t i, nread = 0, nwritten; |
| |
| DEBUGASSERT(backend); |
| if(!backend->conn || cf->shutdown) { |
| *done = TRUE; |
| goto out; |
| } |
| |
| 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); |
| } |
| } |
| |
| result = cr_send(cf, data, NULL, 0, &nwritten); |
| if(result) { |
| 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) { |
| char buf[1024]; |
| result = cr_recv(cf, data, buf, (int)sizeof(buf), &nread); |
| if(result) |
| break; |
| } |
| |
| if(result == CURLE_AGAIN) { |
| connssl->io_need = CURL_SSL_IO_NEED_RECV; |
| result = CURLE_OK; |
| } |
| else if(result) { |
| DEBUGASSERT(result); |
| CURL_TRC_CF(data, cf, "shutdown, error: %d", result); |
| } |
| else if(nread == 0) { |
| /* We got the close notify alert and are done. */ |
| *done = TRUE; |
| } |
| |
| out: |
| cf->shutdown = (result || *done); |
| return result; |
| } |
| |
| static void cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) |
| { |
| const 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; |
| } |
| if(backend->config) { |
| rustls_client_config_free(backend->config); |
| backend->config = NULL; |
| } |
| } |
| |
| static size_t cr_version(char *buffer, size_t size) |
| { |
| const struct rustls_str ver = rustls_version(); |
| return curl_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); |
| } |
| |
| static void cr_cleanup(void) |
| { |
| Curl_tls_keylog_close(); |
| } |
| |
| const struct Curl_ssl Curl_ssl_rustls = { |
| { CURLSSLBACKEND_RUSTLS, "rustls" }, |
| SSLSUPP_CAINFO_BLOB | /* supports */ |
| SSLSUPP_HTTPS_PROXY | |
| SSLSUPP_CIPHER_LIST | |
| SSLSUPP_TLS13_CIPHERSUITES | |
| SSLSUPP_CERTINFO | |
| SSLSUPP_ECH, |
| sizeof(struct rustls_ssl_backend_data), |
| |
| NULL, /* init */ |
| cr_cleanup, /* cleanup */ |
| cr_version, /* version */ |
| cr_shutdown, /* shutdown */ |
| cr_data_pending, /* data_pending */ |
| cr_random, /* random */ |
| NULL, /* cert_status_request */ |
| cr_connect, /* connect */ |
| Curl_ssl_adjust_pollset, /* adjust_pollset */ |
| cr_get_internals, /* get_internals */ |
| cr_close, /* close_one */ |
| NULL, /* close_all */ |
| NULL, /* set_engine */ |
| NULL, /* set_engine_default */ |
| NULL, /* engines_list */ |
| NULL, /* sha256sum */ |
| cr_recv, /* recv decrypted data */ |
| cr_send, /* send data to encrypt */ |
| NULL, /* get_channel_binding */ |
| }; |
| |
| #endif /* USE_RUSTLS */ |