]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
rustls: switch read_tls and write_tls to callbacks
authorJacob Hoffman-Andrews <github@hoffman-andrews.com>
Sat, 15 May 2021 01:45:49 +0000 (18:45 -0700)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 24 May 2021 14:40:59 +0000 (16:40 +0200)
And update to 0.6.0, including a rename from session to connection for
many fields.

Closes #7071

.travis.yml
docs/RUSTLS.md
lib/vtls/rustls.c
m4/curl-rustls.m4
tests/data/Makefile.inc
tests/data/test364 [new file with mode: 0644]

index 3cf563bc3c3fa2a703d0b274ee9888411d1d5145..3fdc761c7a16f2072c455204b3c322b18092ffdc 100644 (file)
@@ -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
index 569002c06aa6b85e65a5310db6df75ff229d059a..c12b70a6010b7d52154f64657ecfccf9aa3ef530 100644 (file)
@@ -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
index 161f3bf51d759c3be34fa4a22760d37d3f7a866e..d5247f936ade604313c902107e519b3eb330a6bf 100644 (file)
 
 #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 = {
index 12bc8f59e6349a4520cf4c30b14719956ea5a229..6f8ea4ff820042381d97632d970778f623a1d231 100644 (file)
@@ -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])
index b0fef53c8d0daa46f14c7730664b3ebf70a89e91..77feeccee86ca49136eb667ab26f189de0461c64 100644 (file)
@@ -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 (file)
index 0000000..5afb8c9
--- /dev/null
@@ -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\r
+Host: %HOSTIP:%HTTPSPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+Content-Length: 1201\r
+Expect: 100-continue\r
+\r
+%repeat[200 x banana]%
+</protocol>
+</verify>
+</testcase>