diff options
author | Jacob Hoffman-Andrews <github@hoffman-andrews.com> | 2021-05-14 18:45:49 -0700 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2021-05-24 16:40:59 +0200 |
commit | a62e6435f4c110d14644bc8298d83c2e7b18dee5 (patch) | |
tree | dc8ba45c997b195d49ee57fd6d607da07ae6a6de | |
parent | 98770344b2d6527c5b504fa740d7bbddbee1728e (diff) | |
download | curl-a62e6435f4c110d14644bc8298d83c2e7b18dee5.tar.gz |
rustls: switch read_tls and write_tls to callbacks
And update to 0.6.0, including a rename from session to connection for
many fields.
Closes #7071
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | docs/RUSTLS.md | 4 | ||||
-rw-r--r-- | lib/vtls/rustls.c | 233 | ||||
-rw-r--r-- | m4/curl-rustls.m4 | 2 | ||||
-rw-r--r-- | tests/data/Makefile.inc | 2 | ||||
-rw-r--r-- | tests/data/test364 | 51 |
6 files changed, 158 insertions, 136 deletions
diff --git a/.travis.yml b/.travis.yml index 3cf563bc3..3fdc761c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -125,7 +125,7 @@ jobs: - libbrotli-dev - libzstd-dev - env: - - T=debug-rustls RUSTLS_VERSION="v0.5.0" C="--with-rustls=$HOME/crust" + - T=debug-rustls RUSTLS_VERSION="v0.6.0" C="--with-rustls=$HOME/crust" addons: apt: <<: *common_apt diff --git a/docs/RUSTLS.md b/docs/RUSTLS.md index 569002c06..c12b70a60 100644 --- a/docs/RUSTLS.md +++ b/docs/RUSTLS.md @@ -3,7 +3,7 @@ [Rustls is a TLS backend written in Rust.](https://docs.rs/rustls/). Curl can be built to use it as an alternative to OpenSSL or other TLS backends. We use the [crustls C bindings](https://github.com/abetterinternet/crustls/). This -version of curl depends on version v0.5.0 of crustls. +version of curl depends on version v0.6.0 of crustls. # Building with rustls @@ -12,7 +12,7 @@ First, [install Rust](https://rustup.rs/). Next, check out, build, and install the appropriate version of crustls: % cargo install cbindgen - % git clone https://github.com/abetterinternet/crustls/ -b v0.5.0 + % git clone https://github.com/abetterinternet/crustls/ -b v0.6.0 % cd crustls % make % make DESTDIR=${HOME}/crustls-built/ install diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 161f3bf51..d5247f936 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -37,16 +37,11 @@ #include "multiif.h" -/* Per https://www.bearssl.org/api1.html, max TLS record size plus max - per-record overhead. */ -#define TLSBUF_SIZE (16384 + 325) - struct ssl_backend_data { const struct rustls_client_config *config; - struct rustls_client_session *session; + struct rustls_connection *conn; bool data_pending; - uint8_t *tlsbuf; }; /* For a given rustls_result error code, return the best-matching CURLcode. */ @@ -82,6 +77,28 @@ cr_connect(struct Curl_easy *data UNUSED_PARAM, return CURLE_SSL_CONNECT_ERROR; } +static int +read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) +{ + ssize_t n = sread(*(int *)userdata, buf, len); + if(n < 0) { + return SOCKERRNO; + } + *out_n = n; + return 0; +} + +static int +write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) +{ + ssize_t n = swrite(*(int *)userdata, buf, len); + if(n < 0) { + return SOCKERRNO; + } + *out_n = n; + return 0; +} + /* * On each run: * - Read a chunk of bytes from the socket into rustls' TLS input buffer. @@ -101,68 +118,44 @@ cr_recv(struct Curl_easy *data, int sockindex, 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]; + struct rustls_connection *const rconn = backend->conn; size_t n = 0; - ssize_t tls_bytes_read = 0; - size_t tls_bytes_processed = 0; + size_t tls_bytes_read = 0; size_t plain_bytes_copied = 0; rustls_result rresult = 0; char errorbuf[255]; + rustls_io_result io_error; - tls_bytes_read = sread(sockfd, backend->tlsbuf, TLSBUF_SIZE); - if(tls_bytes_read == 0) { - failf(data, "connection closed without TLS close_notify alert"); + io_error = rustls_connection_read_tls(rconn, read_cb, + &conn->sock[sockindex], &tls_bytes_read); + if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + infof(data, "sread: EAGAIN or EWOULDBLOCK\n"); + } + else if(io_error) { + failf(data, "reading from socket: %s", strerror(io_error)); *err = CURLE_READ_ERROR; return -1; } - else if(tls_bytes_read < 0) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { - infof(data, "sread: EAGAIN or EWOULDBLOCK\n"); - /* There is no data in the socket right now, but there could still be - some data in the rustls session, so we need to read from it below. */ - tls_bytes_read = 0; - } - else { - failf(data, "reading from socket: %s", strerror(SOCKERRNO)); - *err = CURLE_READ_ERROR; - return -1; - } + else if(tls_bytes_read == 0) { + failf(data, "connection closed without TLS close_notify alert"); + *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, - backend->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; - } + infof(data, "cr_recv read %ld bytes from the network\n", tls_bytes_read); - tls_bytes_processed += n; - backend->data_pending = TRUE; + rresult = rustls_connection_process_new_packets(rconn); + if(rresult != RUSTLS_RESULT_OK) { + rustls_error(rresult, errorbuf, sizeof(errorbuf), &n); + failf(data, "%.*s", n, errorbuf); + *err = map_error(rresult); + return -1; } + backend->data_pending = TRUE; + while(plain_bytes_copied < plainlen) { - rresult = rustls_client_session_read(session, + rresult = rustls_connection_read(rconn, (uint8_t *)plainbuf + plain_bytes_copied, plainlen - plain_bytes_copied, &n); @@ -171,20 +164,21 @@ cr_recv(struct Curl_easy *data, int sockindex, return 0; } else if(rresult != RUSTLS_RESULT_OK) { - failf(data, "error in rustls_client_session_read"); + failf(data, "error in rustls_connection_read"); *err = CURLE_READ_ERROR; return -1; } else if(n == 0) { - /* rustls returns 0 from client_session_read to mean "all currently + /* rustls returns 0 from connection_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"); + infof(data, "cr_recv got 0 bytes of plaintext\n"); backend->data_pending = FALSE; break; } else { + infof(data, "cr_recv copied out %ld bytes of plaintext\n", n); plain_bytes_copied += n; } } @@ -217,68 +211,50 @@ cr_send(struct Curl_easy *data, int sockindex, 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; + struct rustls_connection *const rconn = backend->conn; size_t plainwritten = 0; - size_t tlslen = 0; size_t tlswritten = 0; + size_t tlswritten_total = 0; rustls_result rresult; + rustls_io_result io_error; + + infof(data, "cr_send %ld bytes of plaintext\n", plainlen); if(plainlen > 0) { - rresult = rustls_client_session_write(session, - plainbuf, plainlen, &plainwritten); + rresult = rustls_connection_write(rconn, plainbuf, plainlen, + &plainwritten); if(rresult != RUSTLS_RESULT_OK) { - failf(data, "error in rustls_client_session_write"); + failf(data, "error in rustls_connection_write"); *err = CURLE_WRITE_ERROR; return -1; } else if(plainwritten == 0) { - failf(data, "EOF in rustls_client_session_write"); + failf(data, "EOF in rustls_connection_write"); *err = CURLE_WRITE_ERROR; return -1; } } - while(rustls_client_session_wants_write(session)) { - rresult = rustls_client_session_write_tls( - session, backend->tlsbuf, TLSBUF_SIZE, &tlslen); - if(rresult != RUSTLS_RESULT_OK) { - failf(data, "error in rustls_client_session_write_tls"); - *err = CURLE_WRITE_ERROR; + while(rustls_connection_wants_write(rconn)) { + io_error = rustls_connection_write_tls(rconn, write_cb, + &conn->sock[sockindex], &tlswritten); + if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + infof(data, "swrite: EAGAIN after %ld bytes\n", tlswritten_total); + *err = CURLE_AGAIN; return -1; } - else if(tlslen == 0) { - failf(data, "EOF in rustls_client_session_write_tls"); + else if(io_error) { + failf(data, "writing to socket: %s", strerror(io_error)); *err = CURLE_WRITE_ERROR; return -1; } - - tlswritten = 0; - - while(tlswritten < tlslen) { - n = swrite(sockfd, backend->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; + if(tlswritten == 0) { + failf(data, "EOF in swrite"); + *err = CURLE_WRITE_ERROR; + return -1; } - - DEBUGASSERT(tlswritten <= tlslen); + infof(data, "cr_send wrote %ld bytes to network\n", tlswritten); + tlswritten_total += tlswritten; } return plainwritten; @@ -313,7 +289,7 @@ static CURLcode cr_init_backend(struct Curl_easy *data, struct connectdata *conn, struct ssl_backend_data *const backend) { - struct rustls_client_session *session = backend->session; + struct rustls_connection *rconn = backend->conn; struct rustls_client_config_builder *config_builder = NULL; const char *const ssl_cafile = SSL_CONN_CONFIG(CAfile); const bool verifypeer = SSL_CONN_CONFIG(verifypeer); @@ -326,11 +302,6 @@ cr_init_backend(struct Curl_easy *data, struct connectdata *conn, { (const uint8_t *)ALPN_H2, ALPN_H2_LENGTH }, }; - backend->tlsbuf = calloc(TLSBUF_SIZE, 1); - if(!backend->tlsbuf) { - return CURLE_OUT_OF_MEMORY; - } - config_builder = rustls_client_config_builder_new(); #ifdef USE_HTTP2 infof(data, "offering ALPN for HTTP/1.1 and HTTP/2\n"); @@ -341,10 +312,11 @@ cr_init_backend(struct Curl_easy *data, struct connectdata *conn, #endif if(!verifypeer) { rustls_client_config_builder_dangerous_set_certificate_verifier( - config_builder, cr_verify_none, NULL); + config_builder, cr_verify_none); /* rustls doesn't support IP addresses (as of 0.19.0), and will reject - * sessions created with an IP address, even when certificate verification - * is turned off. Set a placeholder hostname and disable SNI. */ + * 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"; @@ -371,26 +343,26 @@ cr_init_backend(struct Curl_easy *data, struct connectdata *conn, } backend->config = rustls_client_config_builder_build(config_builder); - DEBUGASSERT(session == NULL); - result = rustls_client_session_new( - backend->config, hostname, &session); + DEBUGASSERT(rconn == NULL); + result = rustls_client_connection_new(backend->config, hostname, &rconn); if(result != RUSTLS_RESULT_OK) { rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "failed to create client session: %.*s", errorlen, errorbuf); + failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf); return CURLE_COULDNT_CONNECT; } - backend->session = session; + rustls_connection_set_userdata(rconn, backend); + backend->conn = rconn; return CURLE_OK; } static void cr_set_negotiated_alpn(struct Curl_easy *data, struct connectdata *conn, - const struct rustls_client_session *session) + const struct rustls_connection *rconn) { const uint8_t *protocol = NULL; size_t len = 0; - rustls_client_session_get_alpn_protocol(session, &protocol, &len); + rustls_connection_get_alpn_protocol(rconn, &protocol, &len); if(NULL == protocol) { infof(data, "ALPN, server did not agree to a protocol\n"); return; @@ -423,7 +395,7 @@ cr_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, 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 = NULL; + struct rustls_connection *rconn = NULL; CURLcode tmperr = CURLE_OK; int result; int what; @@ -440,7 +412,7 @@ cr_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, connssl->state = ssl_connection_negotiating; } - session = backend->session; + rconn = backend->conn; /* Read/write data until the handshake is done or the socket would block. */ for(;;) { @@ -451,12 +423,12 @@ cr_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, * then becomes true when we first write data, then becomes false again * once the handshake is done. */ - if(!rustls_client_session_is_handshaking(session)) { + if(!rustls_connection_is_handshaking(rconn)) { infof(data, "Done handshaking\n"); /* Done with the handshake. Set up callbacks to send/receive data. */ connssl->state = ssl_connection_complete; - cr_set_negotiated_alpn(data, conn, session); + cr_set_negotiated_alpn(data, conn, rconn); conn->recv[sockindex] = cr_recv; conn->send[sockindex] = cr_send; @@ -464,8 +436,8 @@ cr_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, return CURLE_OK; } - wants_read = rustls_client_session_wants_read(session); - wants_write = rustls_client_session_wants_write(session); + wants_read = rustls_connection_wants_read(rconn); + wants_write = rustls_connection_wants_write(rconn); DEBUGASSERT(wants_read || wants_write); writefd = wants_write?sockfd:CURL_SOCKET_BAD; readfd = wants_read?sockfd:CURL_SOCKET_BAD; @@ -489,7 +461,7 @@ cr_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, /* socket is readable or writable */ if(wants_write) { - infof(data, "ClientSession wants us to write_tls.\n"); + infof(data, "rustls_connection wants us to write_tls.\n"); cr_send(data, sockindex, NULL, 0, &tmperr); if(tmperr == CURLE_AGAIN) { infof(data, "writing would block\n"); @@ -501,7 +473,7 @@ cr_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, } if(wants_read) { - infof(data, "ClientSession wants us to read_tls.\n"); + infof(data, "rustls_connection wants us to read_tls.\n"); cr_recv(data, sockindex, NULL, 0, &tmperr); if(tmperr == CURLE_AGAIN) { @@ -532,13 +504,13 @@ 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; + struct rustls_connection *rconn = backend->conn; - if(rustls_client_session_wants_write(session)) { + if(rustls_connection_wants_write(rconn)) { socks[0] = sockfd; return GETSOCK_WRITESOCK(0); } - if(rustls_client_session_wants_read(session)) { + if(rustls_connection_wants_read(rconn)) { socks[0] = sockfd; return GETSOCK_READSOCK(0); } @@ -551,7 +523,7 @@ cr_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) { struct ssl_backend_data *backend = connssl->backend; - return &backend->session; + return &backend->conn; } static void @@ -563,21 +535,20 @@ cr_close(struct Curl_easy *data, struct connectdata *conn, CURLcode tmperr = CURLE_OK; ssize_t n = 0; - if(backend->session) { - rustls_client_session_send_close_notify(backend->session); + if(backend->conn) { + rustls_connection_send_close_notify(backend->conn); 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; + rustls_connection_free(backend->conn); + backend->conn = NULL; } if(backend->config) { rustls_client_config_free(backend->config); backend->config = NULL; } - free(backend->tlsbuf); } const struct Curl_ssl Curl_ssl_rustls = { diff --git a/m4/curl-rustls.m4 b/m4/curl-rustls.m4 index 12bc8f59e..6f8ea4ff8 100644 --- a/m4/curl-rustls.m4 +++ b/m4/curl-rustls.m4 @@ -70,7 +70,7 @@ if test "x$OPT_RUSTLS" != xno; then CPPFLAGS="$CPPFLAGS $addcflags" fi - AC_CHECK_LIB(crustls, rustls_client_session_read, + AC_CHECK_LIB(crustls, rustls_connection_read, [ AC_DEFINE(USE_RUSTLS, 1, [if rustls is enabled]) AC_SUBST(USE_RUSTLS, [1]) diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index b0fef53c8..77feeccee 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -60,7 +60,7 @@ test325 test326 test327 test328 test329 test330 test331 test332 test333 \ test334 test335 test336 test337 test338 test339 test340 test341 test342 \ test343 test344 test345 test346 test347 test348 test349 test350 test351 \ test352 test353 test354 test355 test356 test357 test358 test359 test360 \ -test361 test362 test363 \ +test361 test362 test363 test364 \ \ test393 test394 test395 test396 test397 \ \ diff --git a/tests/data/test364 b/tests/data/test364 new file mode 100644 index 000000000..5afb8c96d --- /dev/null +++ b/tests/data/test364 @@ -0,0 +1,51 @@ +<testcase> +<info> +<keywords> +HTTPS +HTTPS PUT +</keywords> +</info> +# Server-side +<reply> +<data> +HTTP/1.0 200 OK swsclose +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake + +blablabla +</data> +</reply> + +# Client-side +<client> +<features> +SSL +</features> +<server> +https +</server> + <name> +HTTPS PUT of small file + </name> + <command> +-k https://%HOSTIP:%HTTPSPORT/we/want/%TESTNUMBER -T log/test%TESTNUMBER.txt +</command> +<file name="log/test%TESTNUMBER.txt"> +%repeat[200 x banana]% +</file> +</client> + +# Verify data after the test has been "shot" +<verify> +<protocol> +PUT /we/want/%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPSPORT
+User-Agent: curl/%VERSION
+Accept: */*
+Content-Length: 1201
+Expect: 100-continue
+
+%repeat[200 x banana]% +</protocol> +</verify> +</testcase> |