summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.inc2
-rw-r--r--lib/curl_setup.h2
-rw-r--r--lib/http.c2
-rw-r--r--lib/multi.c2
-rw-r--r--lib/vtls/rustls.c496
-rw-r--r--lib/vtls/rustls.h32
-rw-r--r--lib/vtls/vtls.c5
-rw-r--r--lib/vtls/vtls.h1
8 files changed, 539 insertions, 3 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index dddb4a491..90ec6aa05 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -50,6 +50,7 @@ LIB_VTLS_CFILES = \
vtls/mesalink.c \
vtls/nss.c \
vtls/openssl.c \
+ vtls/rustls.c \
vtls/schannel.c \
vtls/schannel_verify.c \
vtls/sectransp.c \
@@ -66,6 +67,7 @@ LIB_VTLS_HFILES = \
vtls/mesalink.h \
vtls/nssg.h \
vtls/openssl.h \
+ vtls/rustls.h \
vtls/schannel.h \
vtls/sectransp.h \
vtls/vtls.h \
diff --git a/lib/curl_setup.h b/lib/curl_setup.h
index 9cef5f7d0..6c9e2d738 100644
--- a/lib/curl_setup.h
+++ b/lib/curl_setup.h
@@ -616,7 +616,7 @@ int netware_init(void);
defined(USE_MBEDTLS) || \
defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || \
defined(USE_SECTRANSP) || defined(USE_GSKIT) || defined(USE_MESALINK) || \
- defined(USE_BEARSSL)
+ defined(USE_BEARSSL) || defined(USE_RUSTLS)
#define USE_SSL /* SSL support has been enabled */
#endif
diff --git a/lib/http.c b/lib/http.c
index 72aa96297..806f639dc 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -1552,7 +1552,7 @@ static int https_getsock(struct Curl_easy *data,
{
(void)data;
if(conn->handler->flags & PROTOPT_SSL)
- return Curl_ssl_getsock(conn, socks);
+ return Curl_ssl->getsock(conn, socks);
return GETSOCK_BLANK;
}
#endif /* USE_SSL */
diff --git a/lib/multi.c b/lib/multi.c
index 85707a1af..e0862551d 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -905,7 +905,7 @@ static int waitconnect_getsock(struct connectdata *conn,
#ifdef USE_SSL
#ifndef CURL_DISABLE_PROXY
if(CONNECT_FIRSTSOCKET_PROXY_SSL())
- return Curl_ssl_getsock(conn, sock);
+ return Curl_ssl->getsock(conn, sock);
#endif
#endif
diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c
new file mode 100644
index 000000000..7e00ce7c0
--- /dev/null
+++ b/lib/vtls/rustls.c
@@ -0,0 +1,496 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2020, Jacob Hoffman-Andrews, <github@hoffman-andrews.com>
+ *
+ * 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.
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_RUSTLS
+
+#include "curl_printf.h"
+
+#include <errno.h>
+#include <crustls.h>
+
+#include "urldata.h"
+#include "sendf.h"
+#include "vtls.h"
+#include "select.h"
+
+#include "multiif.h"
+
+struct ssl_backend_data
+{
+ const struct rustls_client_config *config;
+ struct rustls_client_session *session;
+ bool data_pending;
+};
+
+/* For a given rustls_result error code, return the best-matching CURLcode. */
+static CURLcode map_error(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_READ_ERROR;
+ }
+}
+
+static bool
+cr_data_pending(const struct connectdata *conn, int sockindex)
+{
+ struct Curl_easy *data = conn->data;
+ const struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+ struct ssl_backend_data *backend = connssl->backend;
+ infof(data, "rustls_data_pending %d\n", backend->data_pending);
+ return backend->data_pending;
+}
+
+static CURLcode
+cr_connect(struct Curl_easy *data UNUSED_PARAM,
+ struct connectdata *conn UNUSED_PARAM,
+ int sockindex UNUSED_PARAM)
+{
+ infof(data, "rustls_connect: unimplemented\n");
+ return CURLE_SSL_CONNECT_ERROR;
+}
+
+/*
+ * 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
+ * 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.
+ */
+static ssize_t
+cr_recv(struct Curl_easy *data, int sockindex,
+ char *plainbuf, size_t plainlen, CURLcode *err)
+{
+ struct connectdata *conn = data->conn;
+ struct ssl_connect_data *const connssl = &conn->ssl[sockindex];
+ struct ssl_backend_data *const backend = connssl->backend;
+ struct rustls_client_session *const session = backend->session;
+ curl_socket_t sockfd = conn->sock[sockindex];
+ /* Per https://www.bearssl.org/api1.html, max TLS record size plus max
+ per-record overhead. */
+ uint8_t tlsbuf[16384 + 325];
+ size_t n = 0;
+ ssize_t tls_bytes_read = 0;
+ size_t tls_bytes_processed = 0;
+ size_t plain_bytes_copied = 0;
+ rustls_result rresult = 0;
+ char errorbuf[255];
+
+ tls_bytes_read = sread(sockfd, tlsbuf, sizeof(tlsbuf));
+ if(tls_bytes_read == 0) {
+ failf(data, "EOF in sread");
+ *err = CURLE_READ_ERROR;
+ return -1;
+ }
+ else if(tls_bytes_read < 0) {
+ if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
+ infof(data, "sread: EAGAIN or EWOULDBLOCK\n");
+ *err = CURLE_AGAIN;
+ return -1;
+ }
+ failf(data, "reading from socket: %s", strerror(SOCKERRNO));
+ *err = CURLE_READ_ERROR;
+ return -1;
+ }
+
+ /*
+ * Now pull those bytes from the buffer into ClientSession.
+ */
+ DEBUGASSERT(tls_bytes_read > 0);
+ while(tls_bytes_processed < (size_t)tls_bytes_read) {
+ rresult = rustls_client_session_read_tls(session,
+ (uint8_t *)tlsbuf + tls_bytes_processed,
+ tls_bytes_read - tls_bytes_processed,
+ &n);
+ if(rresult != RUSTLS_RESULT_OK) {
+ failf(data, "error in rustls_client_session_read_tls");
+ *err = CURLE_READ_ERROR;
+ return -1;
+ }
+ else if(n == 0) {
+ infof(data, "EOF from rustls_client_session_read_tls\n");
+ break;
+ }
+
+ rresult = rustls_client_session_process_new_packets(session);
+ if(rresult != RUSTLS_RESULT_OK) {
+ rustls_error(rresult, errorbuf, sizeof(errorbuf), &n);
+ failf(data, "%.*s", n, errorbuf);
+ *err = map_error(rresult);
+ return -1;
+ }
+
+ tls_bytes_processed += n;
+ backend->data_pending = TRUE;
+ }
+
+ while(plain_bytes_copied < plainlen) {
+ rresult = rustls_client_session_read(session,
+ (uint8_t *)plainbuf + plain_bytes_copied,
+ plainlen - plain_bytes_copied,
+ &n);
+ if(rresult != RUSTLS_RESULT_OK) {
+ failf(data, "error in rustls_client_session_read");
+ *err = CURLE_READ_ERROR;
+ return -1;
+ }
+ else if(n == 0) {
+ /* rustls returns 0 from client_session_read to mean "all currently
+ available data has been read." If we bring in more ciphertext with
+ read_tls, more plaintext will become available. So don't tell curl
+ this is an EOF. Instead, say "come back later." */
+ infof(data, "EOF from rustls_client_session_read\n");
+ backend->data_pending = FALSE;
+ break;
+ }
+ else {
+ plain_bytes_copied += n;
+ }
+ }
+
+ /* If we wrote out 0 plaintext bytes, it might just mean we haven't yet
+ read a full TLS record. Return CURLE_AGAIN so curl doesn't treat this
+ as EOF. */
+ if(plain_bytes_copied == 0) {
+ *err = CURLE_AGAIN;
+ return -1;
+ }
+
+ return plain_bytes_copied;
+}
+
+/*
+ * 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'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.
+ */
+static ssize_t
+cr_send(struct Curl_easy *data, int sockindex,
+ const void *plainbuf, size_t plainlen, CURLcode *err)
+{
+ struct connectdata *conn = data->conn;
+ struct ssl_connect_data *const connssl = &conn->ssl[sockindex];
+ struct ssl_backend_data *const backend = connssl->backend;
+ struct rustls_client_session *const session = backend->session;
+ curl_socket_t sockfd = conn->sock[sockindex];
+ ssize_t n = 0;
+ size_t plainwritten = 0;
+ size_t tlslen = 0;
+ size_t tlswritten = 0;
+ /* Max size of a TLS message, plus some space for TLS framing overhead. */
+ uint8_t tlsbuf[16384 + 325];
+ rustls_result rresult;
+
+ if(plainlen > 0) {
+ rresult = rustls_client_session_write(session,
+ plainbuf, plainlen, &plainwritten);
+ if(rresult != RUSTLS_RESULT_OK) {
+ failf(data, "error in rustls_client_session_write");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ else if(plainwritten == 0) {
+ failf(data, "EOF in rustls_client_session_write");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ }
+
+ while(rustls_client_session_wants_write(session)) {
+ rresult = rustls_client_session_write_tls(
+ session, tlsbuf, sizeof(tlsbuf), &tlslen);
+ if(rresult != RUSTLS_RESULT_OK) {
+ failf(data, "error in rustls_client_session_write_tls");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ else if(tlslen == 0) {
+ failf(data, "EOF in rustls_client_session_write_tls");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+
+ tlswritten = 0;
+
+ while(tlswritten < tlslen) {
+ n = swrite(sockfd, tlsbuf + tlswritten, tlslen - tlswritten);
+ if(n < 0) {
+ if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
+ /* Since recv is called from poll, there should be room to
+ write at least some bytes before hitting EAGAIN. */
+ infof(data, "swrite: EAGAIN after %ld bytes\n", tlswritten);
+ DEBUGASSERT(tlswritten > 0);
+ break;
+ }
+ failf(data, "error in swrite");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ if(n == 0) {
+ failf(data, "EOF in swrite");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ tlswritten += n;
+ }
+
+ DEBUGASSERT(tlswritten <= tlslen);
+ }
+
+ return plainwritten;
+}
+
+static CURLcode
+cr_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn,
+ int sockindex, bool *done)
+{
+ struct ssl_connect_data *const connssl = &conn->ssl[sockindex];
+ curl_socket_t sockfd = conn->sock[sockindex];
+ struct ssl_backend_data *const backend = connssl->backend;
+ struct rustls_client_session *session = backend->session;
+ struct rustls_client_config_builder *config_builder = NULL;
+ const char *const ssl_cafile = SSL_CONN_CONFIG(CAfile);
+ CURLcode tmperr = CURLE_OK;
+ int result;
+ int what;
+ bool wants_read;
+ bool wants_write;
+ curl_socket_t writefd;
+ curl_socket_t readfd;
+ char errorbuf[256];
+ size_t errorlen;
+
+ if(ssl_connection_none == connssl->state) {
+ config_builder = rustls_client_config_builder_new();
+ if(ssl_cafile) {
+ result = rustls_client_config_builder_load_roots_from_file(
+ config_builder, ssl_cafile);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "failed to load trusted certificates");
+ rustls_client_config_free(
+ rustls_client_config_builder_build(config_builder));
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+ else {
+ result = rustls_client_config_builder_load_native_roots(config_builder);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "failed to load trusted certificates");
+ rustls_client_config_free(
+ rustls_client_config_builder_build(config_builder));
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+
+ backend->config = rustls_client_config_builder_build(config_builder);
+ DEBUGASSERT(session == NULL);
+ result = rustls_client_session_new(
+ backend->config, conn->host.name, &session);
+ if(result != RUSTLS_RESULT_OK) {
+ rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen);
+ failf(data, "failed to create client session: %.*s", errorlen, errorbuf);
+ return CURLE_COULDNT_CONNECT;
+ }
+ backend->session = session;
+ connssl->state = ssl_connection_negotiating;
+ }
+
+ /* 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.
+ * This check has to come last because is_handshaking starts out false,
+ * then becomes true when we first write data, then becomes false again
+ * once the handshake is done.
+ */
+ if(!rustls_client_session_is_handshaking(session)) {
+ infof(data, "Done handshaking\n");
+ /* Done with the handshake. Set up callbacks to send/receive data. */
+ connssl->state = ssl_connection_complete;
+ conn->recv[sockindex] = cr_recv;
+ conn->send[sockindex] = cr_send;
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ wants_read = rustls_client_session_wants_read(session);
+ wants_write = rustls_client_session_wants_write(session);
+ DEBUGASSERT(wants_read || wants_write);
+ writefd = wants_write?sockfd:CURL_SOCKET_BAD;
+ readfd = wants_read?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, 0);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ if(0 == what) {
+ infof(data, "Curl_socket_check: %s would block\n",
+ wants_read&&wants_write ?
+ "writing and reading" :
+ wants_write ?
+ "writing" :
+ "reading");
+ *done = FALSE;
+ return CURLE_OK;
+ }
+ /* socket is readable or writable */
+
+ if(wants_write) {
+ infof(data, "ClientSession wants us to write_tls.\n");
+ cr_send(data, sockindex, NULL, 0, &tmperr);
+ if(tmperr == CURLE_AGAIN) {
+ infof(data, "writing would block\n");
+ /* fall through */
+ }
+ else if(tmperr != CURLE_OK) {
+ return tmperr;
+ }
+ }
+
+ if(wants_read) {
+ infof(data, "ClientSession wants us to read_tls.\n");
+
+ cr_recv(data, sockindex, NULL, 0, &tmperr);
+ if(tmperr == CURLE_AGAIN) {
+ infof(data, "reading would block\n");
+ /* fall through */
+ }
+ else if(tmperr != CURLE_OK) {
+ if(tmperr == CURLE_READ_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 can't read/write without blocking. */
+ DEBUGASSERT(false);
+}
+
+/* returns a bitmap of flags for this connection's first socket indicating
+ whether we want to read or write */
+static int
+cr_getsock(struct connectdata *conn, curl_socket_t *socks)
+{
+ struct ssl_connect_data *const connssl = &conn->ssl[FIRSTSOCKET];
+ curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
+ struct ssl_backend_data *const backend = connssl->backend;
+ struct rustls_client_session *session = backend->session;
+
+ if(rustls_client_session_wants_write(session)) {
+ socks[0] = sockfd;
+ return GETSOCK_WRITESOCK(0);
+ }
+ if(rustls_client_session_wants_read(session)) {
+ socks[0] = sockfd;
+ return GETSOCK_READSOCK(0);
+ }
+
+ return GETSOCK_BLANK;
+}
+
+static void *
+cr_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ return &backend->session;
+}
+
+static void
+cr_close(struct Curl_easy *data, struct connectdata *conn,
+ int sockindex)
+{
+ struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+ struct ssl_backend_data *backend = connssl->backend;
+ CURLcode tmperr = CURLE_OK;
+ ssize_t n = 0;
+
+ if(backend->session) {
+ rustls_client_session_send_close_notify(backend->session);
+ n = cr_send(data, sockindex, NULL, 0, &tmperr);
+ if(n < 0) {
+ failf(data, "error sending close notify: %d", tmperr);
+ }
+
+ rustls_client_session_free(backend->session);
+ backend->session = NULL;
+ }
+ if(backend->config) {
+ rustls_client_config_free(backend->config);
+ backend->config = NULL;
+ }
+}
+
+const struct Curl_ssl Curl_ssl_rustls = {
+ { CURLSSLBACKEND_RUSTLS, "rustls" },
+ SSLSUPP_TLS13_CIPHERSUITES, /* supports */
+ sizeof(struct ssl_backend_data),
+
+ Curl_none_init, /* init */
+ Curl_none_cleanup, /* cleanup */
+ rustls_version, /* version */
+ Curl_none_check_cxn, /* check_cxn */
+ Curl_none_shutdown, /* shutdown */
+ cr_data_pending, /* data_pending */
+ Curl_none_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ cr_connect, /* connect */
+ cr_connect_nonblocking, /* connect_nonblocking */
+ cr_getsock, /* cr_getsock */
+ 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 */
+ Curl_none_false_start, /* false_start */
+ NULL /* sha256sum */
+};
+
+#endif /* USE_RUSTLS */
diff --git a/lib/vtls/rustls.h b/lib/vtls/rustls.h
new file mode 100644
index 000000000..06effac4d
--- /dev/null
+++ b/lib/vtls/rustls.h
@@ -0,0 +1,32 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2020, Jacob Hoffman-Andrews, <github@hoffman-andrews.com>
+ *
+ * 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.
+ *
+ ***************************************************************************/
+#ifndef HEADER_CURL_RUSTLS_H
+#define HEADER_CURL_RUSTLS_H
+
+#include "curl_setup.h"
+
+#ifdef USE_RUSTLS
+
+extern const struct Curl_ssl Curl_ssl_rustls;
+
+#endif /* USE_RUSTLS */
+#endif /* HEADER_CURL_RUSTLS_H */
diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
index 5a1240938..6a0069237 100644
--- a/lib/vtls/vtls.c
+++ b/lib/vtls/vtls.c
@@ -1222,6 +1222,8 @@ const struct Curl_ssl *Curl_ssl =
&Curl_ssl_mbedtls;
#elif defined(USE_NSS)
&Curl_ssl_nss;
+#elif defined(USE_RUSTLS)
+ &Curl_ssl_rustls;
#elif defined(USE_OPENSSL)
&Curl_ssl_openssl;
#elif defined(USE_SCHANNEL)
@@ -1265,6 +1267,9 @@ static const struct Curl_ssl *available_backends[] = {
#if defined(USE_BEARSSL)
&Curl_ssl_bearssl,
#endif
+#if defined(USE_RUSTLS)
+ &Curl_ssl_rustls,
+#endif
NULL
};
diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h
index 250a8b99f..9de8a80c1 100644
--- a/lib/vtls/vtls.h
+++ b/lib/vtls/vtls.h
@@ -116,6 +116,7 @@ bool Curl_ssl_tls13_ciphersuites(void);
#include "mbedtls.h" /* mbedTLS versions */
#include "mesalink.h" /* MesaLink versions */
#include "bearssl.h" /* BearSSL versions */
+#include "rustls.h" /* rustls versions */
#ifndef MAX_PINNED_PUBKEY_SIZE
#define MAX_PINNED_PUBKEY_SIZE 1048576 /* 1MB */