From 9394d47233d8ad61fb2f7c3b028c48daedab05a6 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 14:45:05 -0500 Subject: [PATCH 01/23] tests: use typedef consistently * Where there is already a `typedef struct foo { ... } foo;` prefer writing `foo` over `struct foo` throughout. * Where there isn't already a `typedef`, (e.g. `enum demo_result`) add one and use it consistently throughout. --- tests/client.c | 47 +++++++++++++++++++++++------------------------ tests/common.c | 48 ++++++++++++++++++++++++------------------------ tests/common.h | 49 ++++++++++++++++++++++++------------------------- tests/server.c | 42 +++++++++++++++++++++--------------------- 4 files changed, 92 insertions(+), 94 deletions(-) diff --git a/tests/client.c b/tests/client.c index c93ae196..90602ece 100644 --- a/tests/client.c +++ b/tests/client.c @@ -71,7 +71,7 @@ make_conn(const char *hostname, const char *port) perror("client: connecting"); goto cleanup; } - enum demo_result result = nonblock(sockfd); + demo_result result = nonblock(sockfd); if(result != DEMO_OK) { return 1; } @@ -99,8 +99,8 @@ make_conn(const char *hostname, const char *port) * - DEMO_EOF if we got EOF * - DEMO_ERROR for other errors. */ -enum demo_result -do_read(struct conndata *conn, struct rustls_connection *rconn) +demo_result +do_read(conndata *conn, rustls_connection *rconn) { int err = 1; unsigned result = 1; @@ -152,8 +152,7 @@ static const char *CONTENT_LENGTH = "Content-Length"; * the message and return 1. */ int -send_request_and_read_response(struct conndata *conn, - struct rustls_connection *rconn, +send_request_and_read_response(conndata *conn, rustls_connection *rconn, const char *hostname, const char *path) { int sockfd = conn->fd; @@ -169,10 +168,10 @@ send_request_and_read_response(struct conndata *conn, const char *content_length_end; unsigned long content_length = 0; size_t headers_len = 0; - struct rustls_str version; + rustls_str version; rustls_handshake_kind hs_kind; int ciphersuite_id, kex_id; - struct rustls_str ciphersuite_name, kex_name, hs_kind_name; + rustls_str ciphersuite_name, kex_name, hs_kind_name; version = rustls_version(); memset(buf, '\0', sizeof(buf)); @@ -329,12 +328,12 @@ send_request_and_read_response(struct conndata *conn, } int -do_request(const struct rustls_client_config *client_config, - const char *hostname, const char *port, +do_request(const rustls_client_config *client_config, const char *hostname, + const char *port, const char *path) // NOLINT(bugprone-easily-swappable-parameters) { - struct rustls_connection *rconn = NULL; - struct conndata *conn = NULL; + rustls_connection *rconn = NULL; + conndata *conn = NULL; int ret = 1; int sockfd = make_conn(hostname, port); if(sockfd < 0) { @@ -349,7 +348,7 @@ do_request(const struct rustls_client_config *client_config, goto cleanup; } - conn = calloc(1, sizeof(struct conndata)); + conn = calloc(1, sizeof(conndata)); if(conn == NULL) { goto cleanup; } @@ -387,9 +386,9 @@ verify(void *userdata, const rustls_verify_server_cert_params *params) size_t i = 0; const rustls_slice_slice_bytes *intermediates = params->intermediate_certs_der; - struct rustls_slice_bytes bytes; + rustls_slice_bytes bytes; const size_t intermediates_len = rustls_slice_slice_bytes_len(intermediates); - struct conndata *conn = (struct conndata *)userdata; + conndata *conn = (struct conndata *)userdata; LOG("custom certificate verifier called for %.*s", (int)params->server_name.len, @@ -432,16 +431,16 @@ main(int argc, const char **argv) /* Set this global variable for logging purposes. */ programname = "client"; - const struct rustls_crypto_provider *custom_provider = NULL; - struct rustls_client_config_builder *config_builder = NULL; - struct rustls_root_cert_store_builder *server_cert_root_store_builder = NULL; - const struct rustls_root_cert_store *server_cert_root_store = NULL; - const struct rustls_client_config *client_config = NULL; - struct rustls_web_pki_server_cert_verifier_builder - *server_cert_verifier_builder = NULL; - struct rustls_server_cert_verifier *server_cert_verifier = NULL; - struct rustls_slice_bytes alpn_http11; - const struct rustls_certified_key *certified_key = NULL; + const rustls_crypto_provider *custom_provider = NULL; + rustls_client_config_builder *config_builder = NULL; + rustls_root_cert_store_builder *server_cert_root_store_builder = NULL; + const rustls_root_cert_store *server_cert_root_store = NULL; + const rustls_client_config *client_config = NULL; + rustls_web_pki_server_cert_verifier_builder *server_cert_verifier_builder = + NULL; + rustls_server_cert_verifier *server_cert_verifier = NULL; + rustls_slice_bytes alpn_http11; + const rustls_certified_key *certified_key = NULL; alpn_http11.data = (unsigned char *)"http/1.1"; alpn_http11.len = 8; diff --git a/tests/common.c b/tests/common.c index dbebd51c..11a9e9f9 100644 --- a/tests/common.c +++ b/tests/common.c @@ -53,7 +53,7 @@ ws_strerror(int err) * * Returns DEMO_OK on success, DEMO_ERROR on error. */ -enum demo_result +demo_result nonblock(int sockfd) { #ifdef _WIN32 @@ -82,7 +82,7 @@ nonblock(int sockfd) int read_cb(void *userdata, unsigned char *buf, size_t len, size_t *out_n) { - struct conndata *conn = (struct conndata *)userdata; + conndata *conn = (struct conndata *)userdata; ssize_t n = recv(conn->fd, buf, len, 0); if(n < 0) { return errno; @@ -96,7 +96,7 @@ read_cb(void *userdata, unsigned char *buf, size_t len, size_t *out_n) int write_cb(void *userdata, const unsigned char *buf, size_t len, size_t *out_n) { - struct conndata *conn = (struct conndata *)userdata; + conndata *conn = (struct conndata *)userdata; ssize_t n = send(conn->fd, buf, len, 0); if(n < 0) { @@ -109,7 +109,7 @@ write_cb(void *userdata, const unsigned char *buf, size_t len, size_t *out_n) } rustls_io_result -write_tls(struct rustls_connection *rconn, struct conndata *conn, size_t *n) +write_tls(rustls_connection *rconn, conndata *conn, size_t *n) { #ifdef _WIN32 return rustls_connection_write_tls(rconn, write_cb, conn, n); @@ -126,10 +126,10 @@ write_tls(struct rustls_connection *rconn, struct conndata *conn, size_t *n) #ifndef _WIN32 rustls_io_result -write_vectored_cb(void *userdata, const struct rustls_iovec *iov, size_t count, +write_vectored_cb(void *userdata, const rustls_iovec *iov, size_t count, size_t *out_n) { - struct conndata *conn = (struct conndata *)userdata; + conndata *conn = (struct conndata *)userdata; // safety: narrowing conversion from `size_t count` to `int` is safe because // writev return -1 and sets errno to EINVAL on out of range input (<0 || > @@ -144,19 +144,19 @@ write_vectored_cb(void *userdata, const struct rustls_iovec *iov, size_t count, #endif /* _WIN32 */ size_t -bytevec_available(struct bytevec *vec) +bytevec_available(bytevec *vec) { return vec->capacity - vec->len; } char * -bytevec_writeable(struct bytevec *vec) +bytevec_writeable(bytevec *vec) { return vec->data + vec->len; } void -bytevec_consume(struct bytevec *vec, size_t n) +bytevec_consume(bytevec *vec, size_t n) { vec->len += n; } @@ -164,8 +164,8 @@ bytevec_consume(struct bytevec *vec, size_t n) // Ensure there are at least n bytes available between vec->len and // vec->capacity. If this requires reallocating, this may return // DEMO_ERROR. -enum demo_result -bytevec_ensure_available(struct bytevec *vec, size_t n) +demo_result +bytevec_ensure_available(bytevec *vec, size_t n) { size_t available = vec->capacity - vec->len; size_t newsize; @@ -191,11 +191,11 @@ bytevec_ensure_available(struct bytevec *vec, size_t n) * our buffer as much as needed. */ int -copy_plaintext_to_buffer(struct conndata *conn) +copy_plaintext_to_buffer(conndata *conn) { unsigned int result; size_t n; - struct rustls_connection *rconn = conn->rconn; + rustls_connection *rconn = conn->rconn; if(bytevec_ensure_available(&conn->data, 1024) != DEMO_OK) { return DEMO_ERROR; @@ -271,7 +271,7 @@ memmem(const void *haystack, size_t haystacklen, const void *needle, } char * -body_beginning(struct bytevec *vec) +body_beginning(bytevec *vec) { const void *result = memmem(vec->data, vec->len, "\r\n\r\n", 4); if(result == NULL) { @@ -324,10 +324,10 @@ get_first_header_value(const char *headers, size_t headers_len, } void -log_cb(void *userdata, const struct rustls_log_params *params) +log_cb(void *userdata, const rustls_log_params *params) { - struct conndata *conn = (struct conndata *)userdata; - struct rustls_str level_str = rustls_log_level_str(params->level); + conndata *conn = (struct conndata *)userdata; + rustls_str level_str = rustls_log_level_str(params->level); LOG("[fd %d][%.*s]: %.*s", conn->fd, (int)level_str.len, @@ -336,7 +336,7 @@ log_cb(void *userdata, const struct rustls_log_params *params) params->message.data); } -enum demo_result +demo_result read_file(const char *filename, char *buf, size_t buflen, size_t *n) { FILE *f = fopen(filename, "r"); @@ -354,7 +354,7 @@ read_file(const char *filename, char *buf, size_t buflen, size_t *n) return DEMO_OK; } -const struct rustls_certified_key * +const rustls_certified_key * load_cert_and_key(const char *certfile, const char *keyfile) { char certbuf[10000]; @@ -373,7 +373,7 @@ load_cert_and_key(const char *certfile, const char *keyfile) return NULL; } - const struct rustls_certified_key *certified_key; + const rustls_certified_key *certified_key; result = rustls_certified_key_build((uint8_t *)certbuf, certbuf_len, (uint8_t *)keybuf, @@ -396,16 +396,16 @@ load_cert_and_key(const char *certfile, const char *keyfile) return certified_key; } -const struct rustls_crypto_provider * +const rustls_crypto_provider * default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) { - const struct rustls_supported_ciphersuite *custom_ciphersuite = NULL; + const rustls_supported_ciphersuite *custom_ciphersuite = NULL; rustls_crypto_provider_builder *provider_builder = NULL; - const struct rustls_crypto_provider *custom_provider = NULL; + const rustls_crypto_provider *custom_provider = NULL; size_t num_supported = rustls_default_crypto_provider_ciphersuites_len(); for(size_t i = 0; i < num_supported; i++) { - const struct rustls_supported_ciphersuite *suite = + const rustls_supported_ciphersuite *suite = rustls_default_crypto_provider_ciphersuites_get(i); if(suite == NULL) { fprintf(stderr, "failed to get ciphersuite %zu\n", i); diff --git a/tests/common.h b/tests/common.h index 075af3f3..8d600005 100644 --- a/tests/common.h +++ b/tests/common.h @@ -29,29 +29,29 @@ const char *ws_strerror(int err); #define STRTOK_R strtok_r #endif -enum demo_result +typedef enum demo_result { DEMO_OK, DEMO_ERROR, DEMO_AGAIN, DEMO_EOF, -}; +} demo_result; /* A growable vector of bytes. */ -struct bytevec +typedef struct bytevec { char *data; size_t len; size_t capacity; -}; +} bytevec; -struct conndata +typedef struct conndata { int fd; const char *verify_arg; - struct bytevec data; - struct rustls_connection *rconn; -}; + bytevec data; + rustls_connection *rconn; +} conndata; extern const char *programname; @@ -68,39 +68,38 @@ void print_error(const char *prefix, rustls_result result); int write_all(int fd, const char *buf, int n); /* Make a socket nonblocking. */ -enum demo_result nonblock(int sockfd); +demo_result nonblock(int sockfd); /* A callback that reads bytes from the network. */ int read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n); /* Invoke rustls_connection_write_tls with either a vectored or unvectored callback, depending on environment variable. */ -rustls_io_result write_tls(struct rustls_connection *rconn, - struct conndata *conn, size_t *n); +rustls_io_result write_tls(rustls_connection *rconn, conndata *conn, + size_t *n); /* A callback that writes bytes to the network. */ int write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n); #ifndef _WIN32 -rustls_io_result write_vectored_cb(void *userdata, - const struct rustls_iovec *iov, +rustls_io_result write_vectored_cb(void *userdata, const rustls_iovec *iov, size_t count, size_t *out_n); #endif /* _WIN32 */ /* Number of bytes available for writing. */ -size_t bytevec_available(struct bytevec *vec); +size_t bytevec_available(bytevec *vec); /* Pointer to the writeable region. */ -char *bytevec_writeable(struct bytevec *vec); +char *bytevec_writeable(bytevec *vec); /* Indicate that n bytes have been written, increasing len. */ -void bytevec_consume(struct bytevec *vec, size_t n); +void bytevec_consume(bytevec *vec, size_t n); /* Ensure there are at least n bytes available between vec->len and * vec->capacity. If this requires reallocating, this may return * DEMO_ERROR. */ -enum demo_result bytevec_ensure_available(struct bytevec *vec, size_t n); +demo_result bytevec_ensure_available(bytevec *vec, size_t n); /* Read all available bytes from the rustls_connection until EOF. * Note that EOF here indicates "no more bytes until @@ -110,7 +109,7 @@ enum demo_result bytevec_ensure_available(struct bytevec *vec, size_t n); * DEMO_ERROR for error, * DEMO_EOF for "connection cleanly terminated by peer" */ -int copy_plaintext_to_buffer(struct conndata *conn); +int copy_plaintext_to_buffer(conndata *conn); /* Polyfill */ void *memmem(const void *haystack, size_t haystacklen, const void *needle, @@ -119,7 +118,7 @@ void *memmem(const void *haystack, size_t haystacklen, const void *needle, /* If headers are done (received \r\n\r\n), return a pointer to the beginning * of the body. Otherwise return NULL. */ -char *body_beginning(struct bytevec *vec); +char *body_beginning(bytevec *vec); /* If any header matching the provided name (NUL-terminated) exists, return * a pointer to the beginning of the value for the first such occurrence @@ -131,15 +130,15 @@ const char *get_first_header_value(const char *headers, size_t headers_len, const char *name, size_t name_len, size_t *n); -void log_cb(void *userdata, const struct rustls_log_params *params); +void log_cb(void *userdata, const rustls_log_params *params); -enum demo_result read_file(const char *filename, char *buf, size_t buflen, - size_t *n); +demo_result read_file(const char *filename, char *buf, size_t buflen, + size_t *n); -const struct rustls_certified_key *load_cert_and_key(const char *certfile, - const char *keyfile); +const rustls_certified_key *load_cert_and_key(const char *certfile, + const char *keyfile); -const struct rustls_crypto_provider *default_provider_with_custom_ciphersuite( +const rustls_crypto_provider *default_provider_with_custom_ciphersuite( const char *custom_ciphersuite_name); void stderr_key_log_cb(rustls_str label, const unsigned char *client_random, diff --git a/tests/server.c b/tests/server.c index 316bee77..4f5b3814 100644 --- a/tests/server.c +++ b/tests/server.c @@ -39,8 +39,8 @@ typedef enum exchange_state * - DEMO_EOF if we got EOF * - DEMO_ERROR for other errors. */ -enum demo_result -do_read(struct conndata *conn, struct rustls_connection *rconn) +demo_result +do_read(conndata *conn, rustls_connection *rconn) { size_t n = 0; char buf[1]; @@ -86,10 +86,10 @@ do_read(struct conndata *conn, struct rustls_connection *rconn) return DEMO_EOF; } -enum demo_result -send_response(struct conndata *conn) +demo_result +send_response(conndata *conn) { - struct rustls_connection *rconn = conn->rconn; + rustls_connection *rconn = conn->rconn; const char *prefix = "HTTP/1.1 200 OK\r\nContent-Length:"; const int body_size = 10000; size_t response_size = strlen(prefix) + 15 + body_size; @@ -118,18 +118,18 @@ send_response(struct conndata *conn) } void -handle_conn(struct conndata *conn) +handle_conn(conndata *conn) { fd_set read_fds; fd_set write_fds; size_t n = 0; - struct rustls_connection *rconn = conn->rconn; + rustls_connection *rconn = conn->rconn; int sockfd = conn->fd; struct timeval tv; - enum exchange_state state = READING_REQUEST; + exchange_state state = READING_REQUEST; rustls_handshake_kind hs_kind; int ciphersuite_id, kex_id; - struct rustls_str ciphersuite_name, kex_name, hs_kind_name; + rustls_str ciphersuite_name, kex_name, hs_kind_name; LOG("acccepted conn on fd %d", conn->fd); @@ -254,17 +254,17 @@ main(int argc, const char **argv) int ret = 1; int sockfd = 0; - const struct rustls_crypto_provider *custom_provider = NULL; - struct rustls_server_config_builder *config_builder = NULL; - const struct rustls_server_config *server_config = NULL; - struct rustls_connection *rconn = NULL; - const struct rustls_certified_key *certified_key = NULL; - struct rustls_slice_bytes alpn_http11; - struct rustls_root_cert_store_builder *client_cert_root_store_builder = NULL; - const struct rustls_root_cert_store *client_cert_root_store = NULL; - struct rustls_web_pki_client_cert_verifier_builder - *client_cert_verifier_builder = NULL; - struct rustls_client_cert_verifier *client_cert_verifier = NULL; + const rustls_crypto_provider *custom_provider = NULL; + rustls_server_config_builder *config_builder = NULL; + const rustls_server_config *server_config = NULL; + rustls_connection *rconn = NULL; + const rustls_certified_key *certified_key = NULL; + rustls_slice_bytes alpn_http11; + rustls_root_cert_store_builder *client_cert_root_store_builder = NULL; + const rustls_root_cert_store *client_cert_root_store = NULL; + rustls_web_pki_client_cert_verifier_builder *client_cert_verifier_builder = + NULL; + rustls_client_cert_verifier *client_cert_verifier = NULL; rustls_result result = RUSTLS_RESULT_OK; /* Set this global variable for logging purposes. */ @@ -454,7 +454,7 @@ main(int argc, const char **argv) goto cleanup; } - struct conndata *conndata; + conndata *conndata; conndata = calloc(1, sizeof(struct conndata)); conndata->fd = clientfd; conndata->rconn = rconn; From a6eadbae77ca3019212a216f639cba5c8708f903 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 14:47:44 -0500 Subject: [PATCH 02/23] tests: fix handling of socket errors Previously we were checking the result of `socket(2)` for the error result (-1) but weren't jumping to the clean up label. This meant we'd fall through to calling `bind(2)` with `sockfd == -1`. Instead, jump directly to jail^H^H^H^Hcleanup. --- tests/server.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/server.c b/tests/server.c index 4f5b3814..0335bfe0 100644 --- a/tests/server.c +++ b/tests/server.c @@ -401,6 +401,7 @@ main(int argc, const char **argv) sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { LOG("making socket: %s", strerror(errno)); + goto cleanup; } int enable = 1; From 6ff168fbc428e7ecd658d91fc3b1d0e25af88fb6 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 14:51:24 -0500 Subject: [PATCH 03/23] tests: remove unused bzero win32 macro --- tests/common.h | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/common.h b/tests/common.h index 8d600005..83aaad5a 100644 --- a/tests/common.h +++ b/tests/common.h @@ -5,7 +5,6 @@ #define sleep(s) Sleep(1000 * (s)) #define read(s, buf, n) recv(s, buf, n, 0) #define close(s) closesocket(s) -#define bzero(buf, n) memset(buf, '\0', n) /* Hacks for 'errno' stuff */ From 0a492a9373b13516a33bbee91191802cd4a0ce4e Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 14:51:35 -0500 Subject: [PATCH 04/23] tests: prefer C99 empty struct initialization Instead of using `memset(3)` we can lean on C99 empty structure initialization syntax, `struct foo = { 0 };` In C23+ we could use the even more simple `struct foo = {};` syntax, but C99 seems like the best compatibility goal for now. --- tests/client.c | 4 +--- tests/server.c | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/client.c b/tests/client.c index 90602ece..87f6db3f 100644 --- a/tests/client.c +++ b/tests/client.c @@ -30,9 +30,7 @@ int make_conn(const char *hostname, const char *port) { int sockfd = 0; - struct addrinfo *getaddrinfo_output = NULL, hints; - - memset(&hints, 0, sizeof(hints)); + struct addrinfo *getaddrinfo_output = NULL, hints = { 0 }; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; /* looking for TCP */ diff --git a/tests/server.c b/tests/server.c index 0335bfe0..82432dca 100644 --- a/tests/server.c +++ b/tests/server.c @@ -411,8 +411,7 @@ main(int argc, const char **argv) print_error("setsockopt(SO_REUSEADDR) failed", 7001); } - struct sockaddr_in my_addr, peer_addr; - memset(&my_addr, 0, sizeof(struct sockaddr_in)); + struct sockaddr_in my_addr = { 0 }, peer_addr; /* Clear structure */ my_addr.sin_family = AF_INET; my_addr.sin_addr.s_addr = INADDR_ANY; From cbdb72a23365a31c3cbee88d68d806009c6ce180 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 15:15:07 -0500 Subject: [PATCH 05/23] tests: clearly separate rustls_result from demo_result * Always use `rustls_result` or `demo_result`, not `unsigned int` or `int`. * Always name `rustls_result` vars `rr` and `demo_result` vars `dr`. * Don't check `rustls_result` instances against `DEMO_XXX` defines and don't check `demo_result` instances against `RUSTLS_RESULT_XXX` defines. * Use `const` and smaller scopes where possible. --- tests/client.c | 115 +++++++++++++++++++++++++------------------------ tests/common.c | 51 +++++++++++----------- tests/common.h | 2 +- tests/server.c | 25 +++++------ 4 files changed, 98 insertions(+), 95 deletions(-) diff --git a/tests/client.c b/tests/client.c index 87f6db3f..5df22b93 100644 --- a/tests/client.c +++ b/tests/client.c @@ -69,8 +69,8 @@ make_conn(const char *hostname, const char *port) perror("client: connecting"); goto cleanup; } - demo_result result = nonblock(sockfd); - if(result != DEMO_OK) { + const demo_result dr = nonblock(sockfd); + if(dr != DEMO_OK) { return 1; } @@ -101,7 +101,6 @@ demo_result do_read(conndata *conn, rustls_connection *rconn) { int err = 1; - unsigned result = 1; size_t n = 0; ssize_t signed_n = 0; char buf[1]; @@ -117,15 +116,15 @@ do_read(conndata *conn, rustls_connection *rconn) return DEMO_ERROR; } - result = rustls_connection_process_new_packets(rconn); - if(result != RUSTLS_RESULT_OK) { - print_error("in process_new_packets", result); + const rustls_result rr = rustls_connection_process_new_packets(rconn); + if(rr != RUSTLS_RESULT_OK) { + print_error("in process_new_packets", rr); return DEMO_ERROR; } - result = copy_plaintext_to_buffer(conn); - if(result != DEMO_EOF) { - return result; + const demo_result dr = copy_plaintext_to_buffer(conn); + if(dr != DEMO_EOF) { + return dr; } /* If we got an EOF on the plaintext stream (peer closed connection cleanly), @@ -156,7 +155,6 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, int sockfd = conn->fd; int ret = 1; int err = 1; - unsigned result = 1; char buf[2048]; fd_set read_fds; fd_set write_fds; @@ -187,8 +185,9 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, version.data); /* First we write the plaintext - the data that we want rustls to encrypt for * us- to the rustls connection. */ - result = rustls_connection_write(rconn, (uint8_t *)buf, strlen(buf), &n); - if(result != RUSTLS_RESULT_OK) { + const rustls_result rr = + rustls_connection_write(rconn, (uint8_t *)buf, strlen(buf), &n); + if(rr != RUSTLS_RESULT_OK) { LOG_SIMPLE("error writing plaintext bytes to rustls_connection"); goto cleanup; } @@ -229,18 +228,19 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, goto cleanup; } + demo_result dr = DEMO_ERROR; if(FD_ISSET(sockfd, &read_fds)) { /* Read all bytes until we get EAGAIN. Then loop again to wind up in select awaiting the next bit of data. */ for(;;) { - result = do_read(conn, rconn); - if(result == DEMO_AGAIN) { + dr = do_read(conn, rconn); + if(dr == DEMO_AGAIN) { break; } - else if(result == DEMO_EOF) { + else if(dr == DEMO_EOF) { goto drain_plaintext; } - else if(result != DEMO_OK) { + else if(dr != DEMO_OK) { goto cleanup; } if(headers_len == 0) { @@ -282,7 +282,7 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, LOG("error in rustls_connection_write_tls: errno %d", err); goto cleanup; } - if(result == DEMO_AGAIN) { + if(dr == DEMO_AGAIN) { break; } else if(n == 0) { @@ -307,8 +307,8 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, kex_name.data, kex_id); - result = copy_plaintext_to_buffer(conn); - if(result != DEMO_OK && result != DEMO_EOF) { + const demo_result dr = copy_plaintext_to_buffer(conn); + if(dr != DEMO_OK && dr != DEMO_EOF) { goto cleanup; } LOG("writing %zu bytes to stdout", conn->data.len); @@ -325,24 +325,24 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, return ret; } -int +demo_result do_request(const rustls_client_config *client_config, const char *hostname, const char *port, const char *path) // NOLINT(bugprone-easily-swappable-parameters) { rustls_connection *rconn = NULL; conndata *conn = NULL; - int ret = 1; + demo_result dr = DEMO_ERROR; int sockfd = make_conn(hostname, port); if(sockfd < 0) { // No perror because make_conn printed error already. goto cleanup; } - rustls_result result = + const rustls_result rr = rustls_client_connection_new(client_config, hostname, &rconn); - if(result != RUSTLS_RESULT_OK) { - print_error("client_connection_new", result); + if(rr != RUSTLS_RESULT_OK) { + print_error("client_connection_new", rr); goto cleanup; } @@ -357,12 +357,12 @@ do_request(const rustls_client_config *client_config, const char *hostname, rustls_connection_set_userdata(rconn, conn); rustls_connection_set_log_callback(rconn, log_cb); - ret = send_request_and_read_response(conn, rconn, hostname, path); - if(ret != RUSTLS_RESULT_OK) { + dr = send_request_and_read_response(conn, rconn, hostname, path); + if(dr != DEMO_OK) { goto cleanup; } - ret = 0; + dr = DEMO_OK; cleanup: rustls_connection_free(rconn); @@ -375,7 +375,7 @@ do_request(const rustls_client_config *client_config, const char *hostname, } free(conn); } - return ret; + return dr; } uint32_t @@ -411,7 +411,6 @@ int main(int argc, const char **argv) { int ret = 1; - unsigned result = 1; if(argc <= 2) { fprintf(stderr, @@ -458,12 +457,13 @@ main(int argc, const char **argv) } printf("customized to use ciphersuite: %s\n", custom_ciphersuite_name); - result = rustls_client_config_builder_new_custom(custom_provider, - default_tls_versions, - default_tls_versions_len, - &config_builder); - if(result != RUSTLS_RESULT_OK) { - print_error("creating client config builder", result); + const rustls_result rr = + rustls_client_config_builder_new_custom(custom_provider, + default_tls_versions, + default_tls_versions_len, + &config_builder); + if(rr != RUSTLS_RESULT_OK) { + print_error("creating client config builder", rr); goto cleanup; } } @@ -567,8 +567,9 @@ main(int argc, const char **argv) } if(getenv("RUSTLS_PLATFORM_VERIFIER")) { - result = rustls_platform_server_cert_verifier(&server_cert_verifier); - if(result != RUSTLS_RESULT_OK) { + const rustls_result rr = + rustls_platform_server_cert_verifier(&server_cert_verifier); + if(rr != RUSTLS_RESULT_OK) { fprintf(stderr, "client: failed to construct platform verifier\n"); goto cleanup; } @@ -577,23 +578,23 @@ main(int argc, const char **argv) } else if(getenv("CA_FILE")) { server_cert_root_store_builder = rustls_root_cert_store_builder_new(); - result = rustls_root_cert_store_builder_load_roots_from_file( + rustls_result rr = rustls_root_cert_store_builder_load_roots_from_file( server_cert_root_store_builder, getenv("CA_FILE"), true); - if(result != RUSTLS_RESULT_OK) { - print_error("loading trusted certificates", result); + if(rr != RUSTLS_RESULT_OK) { + print_error("loading trusted certificates", rr); goto cleanup; } - result = rustls_root_cert_store_builder_build( - server_cert_root_store_builder, &server_cert_root_store); - if(result != RUSTLS_RESULT_OK) { + rr = rustls_root_cert_store_builder_build(server_cert_root_store_builder, + &server_cert_root_store); + if(rr != RUSTLS_RESULT_OK) { goto cleanup; } server_cert_verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(server_cert_root_store); - result = rustls_web_pki_server_cert_verifier_builder_build( + rr = rustls_web_pki_server_cert_verifier_builder_build( server_cert_verifier_builder, &server_cert_verifier); - if(result != RUSTLS_RESULT_OK) { + if(rr != RUSTLS_RESULT_OK) { goto cleanup; } rustls_client_config_builder_set_server_verifier(config_builder, @@ -611,17 +612,18 @@ main(int argc, const char **argv) } if(getenv("SSLKEYLOGFILE")) { - result = rustls_client_config_builder_set_key_log_file(config_builder); - if(result != RUSTLS_RESULT_OK) { - print_error("enabling keylog", result); + const rustls_result rr = + rustls_client_config_builder_set_key_log_file(config_builder); + if(rr != RUSTLS_RESULT_OK) { + print_error("enabling keylog", rr); goto cleanup; } } else if(getenv("STDERRKEYLOG")) { - result = rustls_client_config_builder_set_key_log( + const rustls_result rr = rustls_client_config_builder_set_key_log( config_builder, stderr_key_log_cb, NULL); - if(result != RUSTLS_RESULT_OK) { - print_error("enabling keylog", result); + if(rr != RUSTLS_RESULT_OK) { + print_error("enabling keylog", rr); goto cleanup; } } @@ -646,16 +648,17 @@ main(int argc, const char **argv) rustls_client_config_builder_set_alpn_protocols( config_builder, &alpn_http11, 1); - result = rustls_client_config_builder_build(config_builder, &client_config); - if(result != RUSTLS_RESULT_OK) { - print_error("building client config", result); + const rustls_result rr = + rustls_client_config_builder_build(config_builder, &client_config); + if(rr != RUSTLS_RESULT_OK) { + print_error("building client config", rr); goto cleanup; } int i; for(i = 0; i < 3; i++) { - result = do_request(client_config, hostname, port, path); - if(result != 0) { + const demo_result dr = do_request(client_config, hostname, port, path); + if(dr != DEMO_OK) { goto cleanup; } } diff --git a/tests/common.c b/tests/common.c index 11a9e9f9..8711e369 100644 --- a/tests/common.c +++ b/tests/common.c @@ -25,11 +25,11 @@ const char *programname; void -print_error(const char *prefix, rustls_result result) +print_error(const char *prefix, rustls_result rr) { char buf[256]; size_t n; - rustls_error(result, buf, sizeof(buf), &n); + rustls_error(rr, buf, sizeof(buf), &n); LOG("%s: %.*s", prefix, (int)n, buf); } @@ -190,10 +190,9 @@ bytevec_ensure_available(bytevec *vec, size_t n) * Copy all available plaintext from rustls into our own buffer, growing * our buffer as much as needed. */ -int +demo_result copy_plaintext_to_buffer(conndata *conn) { - unsigned int result; size_t n; rustls_connection *rconn = conn->rconn; @@ -204,13 +203,14 @@ copy_plaintext_to_buffer(conndata *conn) for(;;) { char *buf = bytevec_writeable(&conn->data); size_t avail = bytevec_available(&conn->data); - result = rustls_connection_read(rconn, (uint8_t *)buf, avail, &n); - if(result == RUSTLS_RESULT_PLAINTEXT_EMPTY) { + const rustls_result rr = + rustls_connection_read(rconn, (uint8_t *)buf, avail, &n); + if(rr == RUSTLS_RESULT_PLAINTEXT_EMPTY) { /* This is expected. It just means "no more bytes for now." */ return DEMO_OK; } - if(result != RUSTLS_RESULT_OK) { - print_error("error in rustls_connection_read", result); + if(rr != RUSTLS_RESULT_OK) { + print_error("error in rustls_connection_read", rr); return DEMO_ERROR; } if(n == 0) { @@ -362,25 +362,24 @@ load_cert_and_key(const char *certfile, const char *keyfile) char keybuf[10000]; size_t keybuf_len; - unsigned int result = - read_file(certfile, certbuf, sizeof(certbuf), &certbuf_len); - if(result != DEMO_OK) { + demo_result dr = read_file(certfile, certbuf, sizeof(certbuf), &certbuf_len); + if(dr != DEMO_OK) { return NULL; } - result = read_file(keyfile, keybuf, sizeof(keybuf), &keybuf_len); - if(result != DEMO_OK) { + dr = read_file(keyfile, keybuf, sizeof(keybuf), &keybuf_len); + if(dr != DEMO_OK) { return NULL; } const rustls_certified_key *certified_key; - result = rustls_certified_key_build((uint8_t *)certbuf, - certbuf_len, - (uint8_t *)keybuf, - keybuf_len, - &certified_key); - if(result != RUSTLS_RESULT_OK) { - print_error("parsing certificate and key", result); + const rustls_result rr = rustls_certified_key_build((uint8_t *)certbuf, + certbuf_len, + (uint8_t *)keybuf, + keybuf_len, + &certified_key); + if(rr != RUSTLS_RESULT_OK) { + print_error("parsing certificate and key", rr); return NULL; } @@ -427,23 +426,23 @@ default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) goto cleanup; } - rustls_result result = + rustls_result rr = rustls_crypto_provider_builder_new_from_default(&provider_builder); - if(result != RUSTLS_RESULT_OK) { + if(rr != RUSTLS_RESULT_OK) { fprintf(stderr, "failed to create provider builder\n"); goto cleanup; } - result = rustls_crypto_provider_builder_set_cipher_suites( + rr = rustls_crypto_provider_builder_set_cipher_suites( provider_builder, &custom_ciphersuite, 1); - if(result != RUSTLS_RESULT_OK) { + if(rr != RUSTLS_RESULT_OK) { fprintf(stderr, "failed to set custom ciphersuite\n"); goto cleanup; } - result = + rr = rustls_crypto_provider_builder_build(provider_builder, &custom_provider); - if(result != RUSTLS_RESULT_OK) { + if(rr != RUSTLS_RESULT_OK) { fprintf(stderr, "failed to build custom provider\n"); goto cleanup; } diff --git a/tests/common.h b/tests/common.h index 83aaad5a..64a363fd 100644 --- a/tests/common.h +++ b/tests/common.h @@ -108,7 +108,7 @@ demo_result bytevec_ensure_available(bytevec *vec, size_t n); * DEMO_ERROR for error, * DEMO_EOF for "connection cleanly terminated by peer" */ -int copy_plaintext_to_buffer(conndata *conn); +demo_result copy_plaintext_to_buffer(conndata *conn); /* Polyfill */ void *memmem(const void *haystack, size_t haystacklen, const void *needle, diff --git a/tests/server.c b/tests/server.c index 82432dca..96a2189a 100644 --- a/tests/server.c +++ b/tests/server.c @@ -60,16 +60,16 @@ do_read(conndata *conn, rustls_connection *rconn) } LOG("read %zu bytes from socket", n); - unsigned int result = rustls_connection_process_new_packets(rconn); - if(result != RUSTLS_RESULT_OK) { - print_error("in process_new_packets", result); + const rustls_result rr = rustls_connection_process_new_packets(rconn); + if(rr != RUSTLS_RESULT_OK) { + print_error("in process_new_packets", rr); return DEMO_ERROR; } - result = copy_plaintext_to_buffer(conn); - if(result != DEMO_EOF) { - LOG("do_read returning %d", result); - return result; + const demo_result dr = copy_plaintext_to_buffer(conn); + if(dr != DEMO_EOF) { + LOG("do_read returning %d", dr); + return dr; } /* If we got an EOF on the plaintext stream (peer closed connection cleanly), @@ -327,9 +327,9 @@ main(int argc, const char **argv) if(auth_cert) { char certbuf[10000]; size_t certbuf_len; - unsigned result = + const demo_result dr = read_file(auth_cert, certbuf, sizeof(certbuf), &certbuf_len); - if(result != DEMO_OK) { + if(dr != DEMO_OK) { goto cleanup; } @@ -448,9 +448,10 @@ main(int argc, const char **argv) nonblock(clientfd); - unsigned int result = rustls_server_connection_new(server_config, &rconn); - if(result != RUSTLS_RESULT_OK) { - print_error("making session", result); + const rustls_result rr = + rustls_server_connection_new(server_config, &rconn); + if(rr != RUSTLS_RESULT_OK) { + print_error("making session", rr); goto cleanup; } From e5dd6904e5ef7a2c9fa53c8000ce568f31b555bc Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 15:26:13 -0500 Subject: [PATCH 06/23] tests: use LOG, LOG_SIMPLE, and print_error more * Avoid direct `fprintf()` to `stderr` - we have `LOG` and `LOG_SIMPLE` for that. They handle adding the program name and a `\n`. * Use `print_error()` when printing an error about a `rustls_result`. It handles making a friendly textual description for the `rustls_result` numeric code. --- tests/client.c | 11 ++++------- tests/common.c | 48 +++++++++++++++++++++++------------------------- tests/common.h | 2 +- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/tests/client.c b/tests/client.c index 5df22b93..a7aeaf1c 100644 --- a/tests/client.c +++ b/tests/client.c @@ -570,7 +570,7 @@ main(int argc, const char **argv) const rustls_result rr = rustls_platform_server_cert_verifier(&server_cert_verifier); if(rr != RUSTLS_RESULT_OK) { - fprintf(stderr, "client: failed to construct platform verifier\n"); + print_error("client: failed to construct platform verifier", rr); goto cleanup; } rustls_client_config_builder_set_server_verifier(config_builder, @@ -605,9 +605,8 @@ main(int argc, const char **argv) config_builder, verify); } else { - fprintf(stderr, - "client: must set either RUSTLS_PLATFORM_VERIFIER or CA_FILE or " - "NO_CHECK_CERTIFICATE env var\n"); + LOG_SIMPLE("must set either RUSTLS_PLATFORM_VERIFIER or CA_FILE or " + "NO_CHECK_CERTIFICATE env var"); goto cleanup; } @@ -631,9 +630,7 @@ main(int argc, const char **argv) char *auth_cert = getenv("AUTH_CERT"); char *auth_key = getenv("AUTH_KEY"); if((auth_cert && !auth_key) || (!auth_cert && auth_key)) { - fprintf( - stderr, - "client: must set both AUTH_CERT and AUTH_KEY env vars, or neither\n"); + LOG_SIMPLE("must set both AUTH_CERT and AUTH_KEY env vars, or neither"); goto cleanup; } else if(auth_cert && auth_key) { diff --git a/tests/common.c b/tests/common.c index 8711e369..db9aac59 100644 --- a/tests/common.c +++ b/tests/common.c @@ -177,7 +177,7 @@ bytevec_ensure_available(bytevec *vec, size_t n) } newdata = realloc(vec->data, newsize); if(newdata == NULL) { - fprintf(stderr, "out of memory trying to get %zu bytes\n", newsize); + LOG("out of memory trying to get %zu bytes", newsize); return DEMO_ERROR; } vec->data = newdata; @@ -214,7 +214,7 @@ copy_plaintext_to_buffer(conndata *conn) return DEMO_ERROR; } if(n == 0) { - fprintf(stderr, "got 0-byte read, cleanly ending connection\n"); + LOG_SIMPLE("got 0-byte read, cleanly ending connection"); return DEMO_EOF; } bytevec_consume(&conn->data, n); @@ -373,21 +373,22 @@ load_cert_and_key(const char *certfile, const char *keyfile) } const rustls_certified_key *certified_key; - const rustls_result rr = rustls_certified_key_build((uint8_t *)certbuf, - certbuf_len, - (uint8_t *)keybuf, - keybuf_len, - &certified_key); + rustls_result rr = rustls_certified_key_build((uint8_t *)certbuf, + certbuf_len, + (uint8_t *)keybuf, + keybuf_len, + &certified_key); if(rr != RUSTLS_RESULT_OK) { print_error("parsing certificate and key", rr); return NULL; } - if(rustls_certified_key_keys_match(certified_key) != RUSTLS_RESULT_OK) { - fprintf(stderr, - "private key %s does not match certificate %s public key\n", - keyfile, - certfile); + rr = rustls_certified_key_keys_match(certified_key); + if(rr != RUSTLS_RESULT_OK) { + LOG("private key %s does not match certificate %s public key", + keyfile, + certfile); + print_error("certified key mismatch", rr); rustls_certified_key_free(certified_key); return NULL; } @@ -407,7 +408,7 @@ default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) const rustls_supported_ciphersuite *suite = rustls_default_crypto_provider_ciphersuites_get(i); if(suite == NULL) { - fprintf(stderr, "failed to get ciphersuite %zu\n", i); + LOG("failed to get ciphersuite %zu", i); goto cleanup; } @@ -420,30 +421,28 @@ default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) } if(custom_ciphersuite == NULL) { - fprintf(stderr, - "failed to select custom ciphersuite: %s\n", - custom_ciphersuite_name); + LOG("failed to select custom ciphersuite: %s", custom_ciphersuite_name); goto cleanup; } rustls_result rr = rustls_crypto_provider_builder_new_from_default(&provider_builder); if(rr != RUSTLS_RESULT_OK) { - fprintf(stderr, "failed to create provider builder\n"); + print_error("failed to create provider builder", rr); goto cleanup; } rr = rustls_crypto_provider_builder_set_cipher_suites( provider_builder, &custom_ciphersuite, 1); if(rr != RUSTLS_RESULT_OK) { - fprintf(stderr, "failed to set custom ciphersuite\n"); + print_error("failed to set custom ciphersuite", rr); goto cleanup; } rr = rustls_crypto_provider_builder_build(provider_builder, &custom_provider); if(rr != RUSTLS_RESULT_OK) { - fprintf(stderr, "failed to build custom provider\n"); + print_error("failed to build custom provider", rr); goto cleanup; } @@ -491,12 +490,11 @@ stderr_key_log_cb(rustls_str label, const unsigned char *client_random, goto cleanup; } - fprintf(stderr, - "SSLKEYLOG: label=%.*s client_random=%s secret=%s\n", - (int)label.len, - label.data, - client_random_str, - secret_str); + LOG("SSLKEYLOG: label=%.*s client_random=%s secret=%s", + (int)label.len, + label.data, + client_random_str, + secret_str); cleanup: if(client_random_str != NULL) { diff --git a/tests/common.h b/tests/common.h index 64a363fd..9c7fa936 100644 --- a/tests/common.h +++ b/tests/common.h @@ -62,7 +62,7 @@ extern const char *programname; * we have a special case for when there are no formatting parameters. */ #define LOG_SIMPLE(s) LOG("%s", s) -void print_error(const char *prefix, rustls_result result); +void print_error(const char *prefix, rustls_result rr); int write_all(int fd, const char *buf, int n); From 340e60aff585c7a9e2ae631c64bf00922d5df554 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 15:37:10 -0500 Subject: [PATCH 07/23] tests: misc tidying/constifying of common * Try to consolidate declarations and assignments where possible. * Condense scopes where possible. * Use `const` where possible. --- tests/common.c | 88 +++++++++++++++++++++++--------------------------- tests/common.h | 6 ++-- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/tests/common.c b/tests/common.c index db9aac59..855f0ad5 100644 --- a/tests/common.c +++ b/tests/common.c @@ -25,7 +25,7 @@ const char *programname; void -print_error(const char *prefix, rustls_result rr) +print_error(const char *prefix, const rustls_result rr) { char buf[256]; size_t n; @@ -64,8 +64,7 @@ nonblock(int sockfd) return DEMO_ERROR; } #else - int flags; - flags = fcntl(sockfd, F_GETFL, 0); + int flags = fcntl(sockfd, F_GETFL, 0); if(flags < 0) { perror("getting socket flags"); return DEMO_ERROR; @@ -80,10 +79,10 @@ nonblock(int sockfd) } int -read_cb(void *userdata, unsigned char *buf, size_t len, size_t *out_n) +read_cb(void *userdata, unsigned char *buf, const size_t len, size_t *out_n) { - conndata *conn = (struct conndata *)userdata; - ssize_t n = recv(conn->fd, buf, len, 0); + const conndata *conn = (struct conndata *)userdata; + const ssize_t n = recv(conn->fd, buf, len, 0); if(n < 0) { return errno; } @@ -94,11 +93,12 @@ read_cb(void *userdata, unsigned char *buf, size_t len, size_t *out_n) } int -write_cb(void *userdata, const unsigned char *buf, size_t len, size_t *out_n) +write_cb(void *userdata, const unsigned char *buf, const size_t len, + size_t *out_n) { - conndata *conn = (struct conndata *)userdata; + const conndata *conn = (struct conndata *)userdata; - ssize_t n = send(conn->fd, buf, len, 0); + const ssize_t n = send(conn->fd, buf, len, 0); if(n < 0) { return errno; } @@ -118,9 +118,8 @@ write_tls(rustls_connection *rconn, conndata *conn, size_t *n) return rustls_connection_write_tls_vectored( rconn, write_vectored_cb, conn, n); } - else { - return rustls_connection_write_tls(rconn, write_cb, conn, n); - } + + return rustls_connection_write_tls(rconn, write_cb, conn, n); #endif /* _WIN32 */ } @@ -129,12 +128,12 @@ rustls_io_result write_vectored_cb(void *userdata, const rustls_iovec *iov, size_t count, size_t *out_n) { - conndata *conn = (struct conndata *)userdata; + const conndata *conn = (struct conndata *)userdata; // safety: narrowing conversion from `size_t count` to `int` is safe because // writev return -1 and sets errno to EINVAL on out of range input (<0 || > // IOV_MAX). - ssize_t n = writev(conn->fd, (const struct iovec *)iov, (int)count); + const ssize_t n = writev(conn->fd, (const struct iovec *)iov, (int)count); if(n < 0) { return errno; } @@ -144,19 +143,19 @@ write_vectored_cb(void *userdata, const rustls_iovec *iov, size_t count, #endif /* _WIN32 */ size_t -bytevec_available(bytevec *vec) +bytevec_available(const bytevec *vec) { return vec->capacity - vec->len; } char * -bytevec_writeable(bytevec *vec) +bytevec_writeable(const bytevec *vec) { return vec->data + vec->len; } void -bytevec_consume(bytevec *vec, size_t n) +bytevec_consume(bytevec *vec, const size_t n) { vec->len += n; } @@ -165,17 +164,15 @@ bytevec_consume(bytevec *vec, size_t n) // vec->capacity. If this requires reallocating, this may return // DEMO_ERROR. demo_result -bytevec_ensure_available(bytevec *vec, size_t n) +bytevec_ensure_available(bytevec *vec, const size_t n) { - size_t available = vec->capacity - vec->len; - size_t newsize; - void *newdata; + const size_t available = vec->capacity - vec->len; if(available < n) { - newsize = vec->len + n; + size_t newsize = vec->len + n; if(newsize < vec->capacity * 2) { newsize = vec->capacity * 2; } - newdata = realloc(vec->data, newsize); + void *newdata = realloc(vec->data, newsize); if(newdata == NULL) { LOG("out of memory trying to get %zu bytes", newsize); return DEMO_ERROR; @@ -202,7 +199,7 @@ copy_plaintext_to_buffer(conndata *conn) for(;;) { char *buf = bytevec_writeable(&conn->data); - size_t avail = bytevec_available(&conn->data); + const size_t avail = bytevec_available(&conn->data); const rustls_result rr = rustls_connection_read(rconn, (uint8_t *)buf, avail, &n); if(rr == RUSTLS_RESULT_PLAINTEXT_EMPTY) { @@ -254,13 +251,12 @@ memmem(const void *haystack, size_t haystacklen, const void *needle, const char *p = bf; while(needlelen <= (haystacklen - (p - bf))) { - if(NULL != (p = memchr(p, (int)(*pt), haystacklen - (p - bf)))) { + p = memchr(p, (int)(*pt), haystacklen - (p - bf)); + if(NULL != p) { if(0 == memcmp(p, needle, needlelen)) { return (void *)p; } - else { - ++p; - } + ++p; } else { break; @@ -271,34 +267,31 @@ memmem(const void *haystack, size_t haystacklen, const void *needle, } char * -body_beginning(bytevec *vec) +body_beginning(const bytevec *vec) { const void *result = memmem(vec->data, vec->len, "\r\n\r\n", 4); if(result == NULL) { return NULL; } - else { - return (char *)result + 4; - } + + return (char *)result + 4; } const char * -get_first_header_value(const char *headers, size_t headers_len, - const char *name, size_t name_len, size_t *n) +get_first_header_value(const char *headers, const size_t headers_len, + const char *name, const size_t name_len, size_t *n) { - const void *result; const char *current = headers; size_t len = headers_len; - size_t skipped; // We use + 3 because there needs to be room for `:` and `\r\n` after the // header name while(len > name_len + 3) { - result = memmem(current, len, "\r\n", 2); + const void *result = memmem(current, len, "\r\n", 2); if(result == NULL) { return NULL; } - skipped = (char *)result - current + 2; + const size_t skipped = (char *)result - current + 2; len -= skipped; current += skipped; /* Make sure there's enough room to conceivably contain the header name, @@ -326,8 +319,8 @@ get_first_header_value(const char *headers, size_t headers_len, void log_cb(void *userdata, const rustls_log_params *params) { - conndata *conn = (struct conndata *)userdata; - rustls_str level_str = rustls_log_level_str(params->level); + const conndata *conn = (struct conndata *)userdata; + const rustls_str level_str = rustls_log_level_str(params->level); LOG("[fd %d][%.*s]: %.*s", conn->fd, (int)level_str.len, @@ -337,7 +330,7 @@ log_cb(void *userdata, const rustls_log_params *params) } demo_result -read_file(const char *filename, char *buf, size_t buflen, size_t *n) +read_file(const char *filename, char *buf, const size_t buflen, size_t *n) { FILE *f = fopen(filename, "r"); if(f == NULL) { @@ -403,7 +396,8 @@ default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) rustls_crypto_provider_builder *provider_builder = NULL; const rustls_crypto_provider *custom_provider = NULL; - size_t num_supported = rustls_default_crypto_provider_ciphersuites_len(); + const size_t num_supported = + rustls_default_crypto_provider_ciphersuites_len(); for(size_t i = 0; i < num_supported; i++) { const rustls_supported_ciphersuite *suite = rustls_default_crypto_provider_ciphersuites_get(i); @@ -456,10 +450,10 @@ default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) // // Caller owns the returned buffer and must free it. static char * -hex_encode(const unsigned char *data, size_t len) +hex_encode(const unsigned char *data, const size_t len) { // Two output chars per input char, plus the NULL terminator. - char *hex_str = (char *)malloc((len * 2) + 1); + char *hex_str = malloc((len * 2) + 1); if(!hex_str) { return NULL; } @@ -473,9 +467,9 @@ hex_encode(const unsigned char *data, size_t len) } void -stderr_key_log_cb(rustls_str label, const unsigned char *client_random, - size_t client_random_len, const unsigned char *secret, - size_t secret_len) +stderr_key_log_cb(const rustls_str label, const unsigned char *client_random, + const size_t client_random_len, const unsigned char *secret, + const size_t secret_len) { char *client_random_str = NULL; char *secret_str = NULL; diff --git a/tests/common.h b/tests/common.h index 9c7fa936..c5e0cb9a 100644 --- a/tests/common.h +++ b/tests/common.h @@ -87,10 +87,10 @@ rustls_io_result write_vectored_cb(void *userdata, const rustls_iovec *iov, #endif /* _WIN32 */ /* Number of bytes available for writing. */ -size_t bytevec_available(bytevec *vec); +size_t bytevec_available(const bytevec *vec); /* Pointer to the writeable region. */ -char *bytevec_writeable(bytevec *vec); +char *bytevec_writeable(const bytevec *vec); /* Indicate that n bytes have been written, increasing len. */ void bytevec_consume(bytevec *vec, size_t n); @@ -117,7 +117,7 @@ void *memmem(const void *haystack, size_t haystacklen, const void *needle, /* If headers are done (received \r\n\r\n), return a pointer to the beginning * of the body. Otherwise return NULL. */ -char *body_beginning(bytevec *vec); +char *body_beginning(const bytevec *vec); /* If any header matching the provided name (NUL-terminated) exists, return * a pointer to the beginning of the value for the first such occurrence From 534d5e231f33b7aa53f1d93c7b32464ffac70472 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 15:48:30 -0500 Subject: [PATCH 08/23] tests: misc tidying/constifying of client * Try to consolidate declarations and assignments where possible. * Condense scopes where possible. * Use `const` where possible. --- tests/client.c | 67 ++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/tests/client.c b/tests/client.c index a7aeaf1c..686ce737 100644 --- a/tests/client.c +++ b/tests/client.c @@ -35,7 +35,7 @@ make_conn(const char *hostname, const char *port) hints.ai_socktype = SOCK_STREAM; /* looking for TCP */ LOG("connecting to %s:%s", hostname, port); - int getaddrinfo_result = + const int getaddrinfo_result = getaddrinfo(hostname, port, &hints, &getaddrinfo_output); if(getaddrinfo_result != 0) { LOG("getaddrinfo: %s", gai_strerror(getaddrinfo_result)); @@ -100,13 +100,11 @@ make_conn(const char *hostname, const char *port) demo_result do_read(conndata *conn, rustls_connection *rconn) { - int err = 1; size_t n = 0; ssize_t signed_n = 0; char buf[1]; - err = rustls_connection_read_tls(rconn, read_cb, conn, &n); - + const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); if(err == EAGAIN || err == EWOULDBLOCK) { LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); return DEMO_AGAIN; @@ -152,24 +150,17 @@ int send_request_and_read_response(conndata *conn, rustls_connection *rconn, const char *hostname, const char *path) { - int sockfd = conn->fd; + const int sockfd = conn->fd; int ret = 1; - int err = 1; char buf[2048]; fd_set read_fds; fd_set write_fds; size_t n = 0; - const char *body; - const char *content_length_str; const char *content_length_end; unsigned long content_length = 0; size_t headers_len = 0; - rustls_str version; - rustls_handshake_kind hs_kind; - int ciphersuite_id, kex_id; - rustls_str ciphersuite_name, kex_name, hs_kind_name; - version = rustls_version(); + const rustls_str version = rustls_version(); memset(buf, '\0', sizeof(buf)); snprintf(buf, sizeof(buf), @@ -196,8 +187,10 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, goto cleanup; } - ciphersuite_id = rustls_connection_get_negotiated_ciphersuite(rconn); - ciphersuite_name = rustls_connection_get_negotiated_ciphersuite_name(rconn); + const int ciphersuite_id = + rustls_connection_get_negotiated_ciphersuite(rconn); + const rustls_str ciphersuite_name = + rustls_connection_get_negotiated_ciphersuite_name(rconn); LOG("negotiated ciphersuite: %.*s (%#x)", (int)ciphersuite_name.len, ciphersuite_name.data, @@ -222,7 +215,8 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, goto drain_plaintext; } - int select_result = select(sockfd + 1, &read_fds, &write_fds, NULL, NULL); + const int select_result = + select(sockfd + 1, &read_fds, &write_fds, NULL, NULL); if(select_result == -1) { perror("client: select"); goto cleanup; @@ -244,15 +238,16 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, goto cleanup; } if(headers_len == 0) { - body = body_beginning(&conn->data); + const char *body = body_beginning(&conn->data); if(body != NULL) { headers_len = body - conn->data.data; LOG("body began at %zu", headers_len); - content_length_str = get_first_header_value(conn->data.data, - headers_len, - CONTENT_LENGTH, - strlen(CONTENT_LENGTH), - &n); + const char *content_length_str = + get_first_header_value(conn->data.data, + headers_len, + CONTENT_LENGTH, + strlen(CONTENT_LENGTH), + &n); if(content_length_str == NULL) { LOG_SIMPLE("content length header not found"); goto cleanup; @@ -277,7 +272,7 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, /* This invokes rustls_connection_write_tls. We pass a callback to * that function. Rustls will pass a buffer to that callback with * encrypted bytes, that we will write to `conn`. */ - err = write_tls(rconn, conn, &n); + const int err = write_tls(rconn, conn, &n); if(err != 0) { LOG("error in rustls_connection_write_tls: errno %d", err); goto cleanup; @@ -295,13 +290,16 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, LOG_SIMPLE("send_request_and_read_response: loop fell through"); -drain_plaintext: - hs_kind = rustls_connection_handshake_kind(rconn); - hs_kind_name = rustls_handshake_kind_str(hs_kind); +drain_plaintext:; // NOTE: empty statement after label to allow var decls. + const rustls_handshake_kind hs_kind = + rustls_connection_handshake_kind(rconn); + const rustls_str hs_kind_name = rustls_handshake_kind_str(hs_kind); LOG("handshake kind: %.*s", (int)hs_kind_name.len, hs_kind_name.data); - kex_id = rustls_connection_get_negotiated_key_exchange_group(rconn); - kex_name = rustls_connection_get_negotiated_key_exchange_group_name(rconn); + const int kex_id = + rustls_connection_get_negotiated_key_exchange_group(rconn); + const rustls_str kex_name = + rustls_connection_get_negotiated_key_exchange_group_name(rconn); LOG("negotiated key exchange: %.*s (%#x)", (int)kex_name.len, kex_name.data, @@ -333,7 +331,7 @@ do_request(const rustls_client_config *client_config, const char *hostname, rustls_connection *rconn = NULL; conndata *conn = NULL; demo_result dr = DEMO_ERROR; - int sockfd = make_conn(hostname, port); + const int sockfd = make_conn(hostname, port); if(sockfd < 0) { // No perror because make_conn printed error already. goto cleanup; @@ -384,9 +382,8 @@ verify(void *userdata, const rustls_verify_server_cert_params *params) size_t i = 0; const rustls_slice_slice_bytes *intermediates = params->intermediate_certs_der; - rustls_slice_bytes bytes; const size_t intermediates_len = rustls_slice_slice_bytes_len(intermediates); - conndata *conn = (struct conndata *)userdata; + const conndata *conn = (struct conndata *)userdata; LOG("custom certificate verifier called for %.*s", (int)params->server_name.len, @@ -394,7 +391,8 @@ verify(void *userdata, const rustls_verify_server_cert_params *params) LOG("end entity len: %zu", params->end_entity_cert_der.len); LOG_SIMPLE("intermediates:"); for(i = 0; i < intermediates_len; i++) { - bytes = rustls_slice_slice_bytes_get(intermediates, i); + const rustls_slice_bytes bytes = + rustls_slice_slice_bytes_get(intermediates, i); if(bytes.data != NULL) { LOG(" intermediate, len = %zu", bytes.len); } @@ -408,7 +406,7 @@ verify(void *userdata, const rustls_verify_server_cert_params *params) } int -main(int argc, const char **argv) +main(const int argc, const char **argv) { int ret = 1; @@ -652,8 +650,7 @@ main(int argc, const char **argv) goto cleanup; } - int i; - for(i = 0; i < 3; i++) { + for(int i = 0; i < 3; i++) { const demo_result dr = do_request(client_config, hostname, port, path); if(dr != DEMO_OK) { goto cleanup; From 203af197fdb0002fab53ba50c72c976a60e8cafd Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 15:50:14 -0500 Subject: [PATCH 09/23] tests: simplify EWOULDBLOCK check For Win32, `common.h` redefines `EAGAIN` to `WSAEWOULDBLOCK`, and `EWOULDBLOCK` to `WSAEWOULDBLOCK`. On Unix, `errno.h` defines `EWOULDBLOCK` as `EAGAIN`. Thus, there's no situation where `errno` is either `EAGAIN` _or_ `EWOULDBLOCK` - both are the same value and checking just `EWOULDBLOCK` is sufficient in both client/server. --- tests/client.c | 2 +- tests/server.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/client.c b/tests/client.c index 686ce737..df9d2ec0 100644 --- a/tests/client.c +++ b/tests/client.c @@ -105,7 +105,7 @@ do_read(conndata *conn, rustls_connection *rconn) char buf[1]; const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); - if(err == EAGAIN || err == EWOULDBLOCK) { + if(err == EWOULDBLOCK) { LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); return DEMO_AGAIN; } diff --git a/tests/server.c b/tests/server.c index 96a2189a..f8efdee7 100644 --- a/tests/server.c +++ b/tests/server.c @@ -45,8 +45,8 @@ do_read(conndata *conn, rustls_connection *rconn) size_t n = 0; char buf[1]; - int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); - if(err == EAGAIN || err == EWOULDBLOCK) { + const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); + if(err == EWOULDBLOCK) { LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); return DEMO_AGAIN; } From 4eff70e5196bbb20a19a6b98e1513a9964368a6c Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 15:59:13 -0500 Subject: [PATCH 10/23] tests: misc tidying/constifying of server * Try to consolidate declarations and assignments where possible. * Condense scopes where possible. * Use `const` where possible. --- tests/server.c | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/tests/server.c b/tests/server.c index f8efdee7..85493b66 100644 --- a/tests/server.c +++ b/tests/server.c @@ -44,7 +44,6 @@ do_read(conndata *conn, rustls_connection *rconn) { size_t n = 0; char buf[1]; - const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); if(err == EWOULDBLOCK) { LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); @@ -54,8 +53,7 @@ do_read(conndata *conn, rustls_connection *rconn) LOG("reading from socket: errno %d", err); return DEMO_ERROR; } - - if(n == 0) { + else if(n == 0) { return DEMO_EOF; } LOG("read %zu bytes from socket", n); @@ -74,7 +72,7 @@ do_read(conndata *conn, rustls_connection *rconn) /* If we got an EOF on the plaintext stream (peer closed connection cleanly), * verify that the sender then closed the TCP connection. */ - ssize_t signed_n = read(conn->fd, buf, sizeof(buf)); + const ssize_t signed_n = read(conn->fd, buf, sizeof(buf)); if(signed_n > 0) { LOG("error: read returned %zu bytes after receiving close_notify", n); return DEMO_ERROR; @@ -87,7 +85,7 @@ do_read(conndata *conn, rustls_connection *rconn) } demo_result -send_response(conndata *conn) +send_response(const conndata *conn) { rustls_connection *rconn = conn->rconn; const char *prefix = "HTTP/1.1 200 OK\r\nContent-Length:"; @@ -124,12 +122,9 @@ handle_conn(conndata *conn) fd_set write_fds; size_t n = 0; rustls_connection *rconn = conn->rconn; - int sockfd = conn->fd; + const int sockfd = conn->fd; struct timeval tv; exchange_state state = READING_REQUEST; - rustls_handshake_kind hs_kind; - int ciphersuite_id, kex_id; - rustls_str ciphersuite_name, kex_name, hs_kind_name; LOG("acccepted conn on fd %d", conn->fd); @@ -176,7 +171,7 @@ handle_conn(conndata *conn) } } if(FD_ISSET(sockfd, &write_fds)) { - int err = write_tls(rconn, conn, &n); + const int err = write_tls(rconn, conn, &n); if(err != 0) { LOG("error in write_tls: errno %d", err); goto cleanup; @@ -192,18 +187,21 @@ handle_conn(conndata *conn) if(state == READING_REQUEST && body_beginning(&conn->data) != NULL) { state = SENT_RESPONSE; LOG_SIMPLE("writing response"); - hs_kind = rustls_connection_handshake_kind(rconn); - hs_kind_name = rustls_handshake_kind_str(hs_kind); + const rustls_handshake_kind hs_kind = + rustls_connection_handshake_kind(rconn); + const rustls_str hs_kind_name = rustls_handshake_kind_str(hs_kind); LOG("handshake kind: %.*s", (int)hs_kind_name.len, hs_kind_name.data); - ciphersuite_id = rustls_connection_get_negotiated_ciphersuite(rconn); - ciphersuite_name = + const int ciphersuite_id = + rustls_connection_get_negotiated_ciphersuite(rconn); + const rustls_str ciphersuite_name = rustls_connection_get_negotiated_ciphersuite_name(rconn); LOG("negotiated ciphersuite: %.*s (%#x)", (int)ciphersuite_name.len, ciphersuite_name.data, ciphersuite_id); - kex_id = rustls_connection_get_negotiated_key_exchange_group(rconn); - kex_name = + const int kex_id = + rustls_connection_get_negotiated_key_exchange_group(rconn); + const rustls_str kex_name = rustls_connection_get_negotiated_key_exchange_group_name(rconn); LOG("negotiated key exchange: %.*s (%#x)", (int)kex_name.len, @@ -240,7 +238,7 @@ handle_conn(conndata *conn) bool shutting_down = false; void -handle_signal(int signo) +handle_signal(const int signo) { if(signo == SIGTERM) { LOG_SIMPLE("received SIGTERM, shutting down"); @@ -249,7 +247,7 @@ handle_signal(int signo) } int -main(int argc, const char **argv) +main(const int argc, const char **argv) { int ret = 1; int sockfd = 0; @@ -347,9 +345,9 @@ main(int argc, const char **argv) client_cert_verifier_builder = rustls_web_pki_client_cert_verifier_builder_new(client_cert_root_store); - char crlbuf[10000]; - size_t crlbuf_len; if(auth_crl) { + size_t crlbuf_len; + char crlbuf[10000]; result = read_file(auth_crl, crlbuf, sizeof(crlbuf), &crlbuf_len); if(result != DEMO_OK) { goto cleanup; @@ -404,7 +402,7 @@ main(int argc, const char **argv) goto cleanup; } - int enable = 1; + const int enable = 1; if(setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&enable, sizeof(int)) < 0) { @@ -436,7 +434,7 @@ main(int argc, const char **argv) while(!shutting_down) { socklen_t peer_addr_size; peer_addr_size = sizeof(struct sockaddr_in); - int clientfd = + const int clientfd = accept(sockfd, (struct sockaddr *)&peer_addr, &peer_addr_size); if(shutting_down) { break; @@ -455,8 +453,7 @@ main(int argc, const char **argv) goto cleanup; } - conndata *conndata; - conndata = calloc(1, sizeof(struct conndata)); + conndata *conndata = calloc(1, sizeof(struct conndata)); conndata->fd = clientfd; conndata->rconn = rconn; rustls_connection_set_userdata(rconn, conndata); From 29fe32a036e5dc226ad8081eb47810fa7d1ad419 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:01:52 -0500 Subject: [PATCH 11/23] tests: fixup server rustls_results/demo_result Missed a couple with the previous round. --- tests/server.c | 61 +++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/tests/server.c b/tests/server.c index 85493b66..131ad61a 100644 --- a/tests/server.c +++ b/tests/server.c @@ -263,7 +263,6 @@ main(const int argc, const char **argv) rustls_web_pki_client_cert_verifier_builder *client_cert_verifier_builder = NULL; rustls_client_cert_verifier *client_cert_verifier = NULL; - rustls_result result = RUSTLS_RESULT_OK; /* Set this global variable for logging purposes. */ programname = "server"; @@ -297,12 +296,13 @@ main(const int argc, const char **argv) } printf("customized to use ciphersuite: %s\n", custom_ciphersuite_name); - result = rustls_server_config_builder_new_custom(custom_provider, - default_tls_versions, - default_tls_versions_len, - &config_builder); - if(result != RUSTLS_RESULT_OK) { - print_error("creating client config builder", result); + const rustls_result rr = + rustls_server_config_builder_new_custom(custom_provider, + default_tls_versions, + default_tls_versions_len, + &config_builder); + if(rr != RUSTLS_RESULT_OK) { + print_error("creating client config builder", rr); goto cleanup; } } @@ -325,21 +325,21 @@ main(const int argc, const char **argv) if(auth_cert) { char certbuf[10000]; size_t certbuf_len; - const demo_result dr = + demo_result dr = read_file(auth_cert, certbuf, sizeof(certbuf), &certbuf_len); if(dr != DEMO_OK) { goto cleanup; } client_cert_root_store_builder = rustls_root_cert_store_builder_new(); - result = rustls_root_cert_store_builder_add_pem( + rustls_result rr = rustls_root_cert_store_builder_add_pem( client_cert_root_store_builder, (uint8_t *)certbuf, certbuf_len, true); - if(result != RUSTLS_RESULT_OK) { + if(rr != RUSTLS_RESULT_OK) { goto cleanup; } - result = rustls_root_cert_store_builder_build( - client_cert_root_store_builder, &client_cert_root_store); - if(result != RUSTLS_RESULT_OK) { + rr = rustls_root_cert_store_builder_build(client_cert_root_store_builder, + &client_cert_root_store); + if(rr != RUSTLS_RESULT_OK) { goto cleanup; } client_cert_verifier_builder = @@ -348,21 +348,21 @@ main(const int argc, const char **argv) if(auth_crl) { size_t crlbuf_len; char crlbuf[10000]; - result = read_file(auth_crl, crlbuf, sizeof(crlbuf), &crlbuf_len); - if(result != DEMO_OK) { + dr = read_file(auth_crl, crlbuf, sizeof(crlbuf), &crlbuf_len); + if(dr != DEMO_OK) { goto cleanup; } - result = rustls_web_pki_client_cert_verifier_builder_add_crl( + rr = rustls_web_pki_client_cert_verifier_builder_add_crl( client_cert_verifier_builder, (uint8_t *)crlbuf, crlbuf_len); - if(result != RUSTLS_RESULT_OK) { + if(rr != RUSTLS_RESULT_OK) { goto cleanup; } } - result = rustls_web_pki_client_cert_verifier_builder_build( + rr = rustls_web_pki_client_cert_verifier_builder_build( client_cert_verifier_builder, &client_cert_verifier); - if(result != RUSTLS_RESULT_OK) { + if(rr != RUSTLS_RESULT_OK) { goto cleanup; } rustls_server_config_builder_set_client_verifier(config_builder, @@ -370,24 +370,26 @@ main(const int argc, const char **argv) } if(getenv("SSLKEYLOGFILE")) { - result = rustls_server_config_builder_set_key_log_file(config_builder); - if(result != RUSTLS_RESULT_OK) { - print_error("enabling keylog", result); + const rustls_result rr = + rustls_server_config_builder_set_key_log_file(config_builder); + if(rr != RUSTLS_RESULT_OK) { + print_error("enabling keylog", rr); goto cleanup; } } else if(getenv("STDERRKEYLOG")) { - result = rustls_server_config_builder_set_key_log( + const rustls_result rr = rustls_server_config_builder_set_key_log( config_builder, stderr_key_log_cb, NULL); - if(result != RUSTLS_RESULT_OK) { - print_error("enabling keylog", result); + if(rr != RUSTLS_RESULT_OK) { + print_error("enabling keylog", rr); goto cleanup; } } - result = rustls_server_config_builder_build(config_builder, &server_config); - if(result != RUSTLS_RESULT_OK) { - print_error("building server config", result); + rustls_result rr = + rustls_server_config_builder_build(config_builder, &server_config); + if(rr != RUSTLS_RESULT_OK) { + print_error("building server config", rr); goto cleanup; } @@ -446,8 +448,7 @@ main(const int argc, const char **argv) nonblock(clientfd); - const rustls_result rr = - rustls_server_connection_new(server_config, &rconn); + rr = rustls_server_connection_new(server_config, &rconn); if(rr != RUSTLS_RESULT_OK) { print_error("making session", rr); goto cleanup; From c18c7761bd809b68160c70282d3d6277bb4fc22e Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:04:10 -0500 Subject: [PATCH 12/23] tests: fix cast for ALPN data print The `rustls_connection_get_alpn_protocol()` function takes an out param of type `const uint8_t*` for the ALPN data and an out param of type `size_t` for the length. We were using these with `printf()` for a `%.*s` specifier. This requires that we cast the len to `int` (already done), as well as casting the `const uint8_t*` to `const char*` (done as of this commit). Along the way, move the declarations of the two out vars closer to the only code that uses them. --- tests/server.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/server.c b/tests/server.c index 131ad61a..c83809af 100644 --- a/tests/server.c +++ b/tests/server.c @@ -182,8 +182,7 @@ handle_conn(conndata *conn) } } - const uint8_t *negotiated_alpn; - size_t negotiated_alpn_len; + if(state == READING_REQUEST && body_beginning(&conn->data) != NULL) { state = SENT_RESPONSE; LOG_SIMPLE("writing response"); @@ -208,12 +207,14 @@ handle_conn(conndata *conn) kex_name.data, kex_id); + const uint8_t *negotiated_alpn = NULL; + size_t negotiated_alpn_len; rustls_connection_get_alpn_protocol( rconn, &negotiated_alpn, &negotiated_alpn_len); if(negotiated_alpn != NULL) { LOG("negotiated ALPN protocol: '%.*s'", (int)negotiated_alpn_len, - negotiated_alpn); + (const char *)negotiated_alpn); } else { LOG_SIMPLE("no ALPN protocol was negotiated"); From 2e61e2f0cd822e143e46a578ed459332cb95f7a9 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:15:04 -0500 Subject: [PATCH 13/23] tests: share do_read() between client/server Both `client.c` and `server.c` defined a `do_read()` function that was almost line-for-line identical. The only meaningful difference was the server version returned `DEMO_EOF` earlier for a `n == 0` read. I can't see any reason why this shouldn't be done for the client, so let's hoist that version into `common.c` and use it in both binaries. --- tests/client.c | 52 ---------------------------------------------- tests/common.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/common.h | 12 +++++++++++ tests/server.c | 56 -------------------------------------------------- 4 files changed, 67 insertions(+), 108 deletions(-) diff --git a/tests/client.c b/tests/client.c index df9d2ec0..76c86dbb 100644 --- a/tests/client.c +++ b/tests/client.c @@ -87,58 +87,6 @@ make_conn(const char *hostname, const char *port) return -1; } -/* - * Do one read from the socket, and process all resulting bytes into the - * rustls_connection, then copy all plaintext bytes from the session to stdout. - * Returns: - * - DEMO_OK for success - * - DEMO_AGAIN if we got an EAGAIN or EWOULDBLOCK reading from the - * socket - * - DEMO_EOF if we got EOF - * - DEMO_ERROR for other errors. - */ -demo_result -do_read(conndata *conn, rustls_connection *rconn) -{ - size_t n = 0; - ssize_t signed_n = 0; - char buf[1]; - - const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); - if(err == EWOULDBLOCK) { - LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); - return DEMO_AGAIN; - } - else if(err != 0) { - LOG("reading from socket: errno %d", err); - return DEMO_ERROR; - } - - const rustls_result rr = rustls_connection_process_new_packets(rconn); - if(rr != RUSTLS_RESULT_OK) { - print_error("in process_new_packets", rr); - return DEMO_ERROR; - } - - const demo_result dr = copy_plaintext_to_buffer(conn); - if(dr != DEMO_EOF) { - return dr; - } - - /* If we got an EOF on the plaintext stream (peer closed connection cleanly), - * verify that the sender then closed the TCP connection. */ - signed_n = read(conn->fd, buf, sizeof(buf)); - if(signed_n > 0) { - LOG("error: read returned %zu bytes after receiving close_notify", n); - return DEMO_ERROR; - } - else if(signed_n < 0 && errno != EWOULDBLOCK) { - LOG("wrong error after receiving close_notify: %s", strerror(errno)); - return DEMO_ERROR; - } - return DEMO_EOF; -} - static const char *CONTENT_LENGTH = "Content-Length"; /* diff --git a/tests/common.c b/tests/common.c index 855f0ad5..a6866c35 100644 --- a/tests/common.c +++ b/tests/common.c @@ -183,6 +183,61 @@ bytevec_ensure_available(bytevec *vec, const size_t n) return DEMO_OK; } +/* + * Do one read from the socket, and process all resulting bytes into the + * rustls_connection. + * Returns: + * - DEMO_OK for success + * - DEMO_AGAIN if we got an EAGAIN or EWOULDBLOCK reading from the + * socket + * - DEMO_EOF if we got EOF + * - DEMO_ERROR for other errors. + */ +demo_result +do_read(conndata *conn, rustls_connection *rconn) +{ + size_t n = 0; + char buf[1]; + const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); + if(err == EWOULDBLOCK) { + LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); + return DEMO_AGAIN; + } + else if(err != 0) { + LOG("reading from socket: errno %d", err); + return DEMO_ERROR; + } + else if(n == 0) { + return DEMO_EOF; + } + LOG("read %zu bytes from socket", n); + + const rustls_result rr = rustls_connection_process_new_packets(rconn); + if(rr != RUSTLS_RESULT_OK) { + print_error("in process_new_packets", rr); + return DEMO_ERROR; + } + + const demo_result dr = copy_plaintext_to_buffer(conn); + if(dr != DEMO_EOF) { + LOG("do_read returning %d", dr); + return dr; + } + + /* If we got an EOF on the plaintext stream (peer closed connection cleanly), + * verify that the sender then closed the TCP connection. */ + const ssize_t signed_n = read(conn->fd, buf, sizeof(buf)); + if(signed_n > 0) { + LOG("error: read returned %zu bytes after receiving close_notify", n); + return DEMO_ERROR; + } + else if(signed_n < 0 && errno != EWOULDBLOCK) { + LOG("wrong error after receiving close_notify: %s", strerror(errno)); + return DEMO_ERROR; + } + return DEMO_EOF; +} + /** * Copy all available plaintext from rustls into our own buffer, growing * our buffer as much as needed. diff --git a/tests/common.h b/tests/common.h index c5e0cb9a..2a7db8e1 100644 --- a/tests/common.h +++ b/tests/common.h @@ -100,6 +100,18 @@ void bytevec_consume(bytevec *vec, size_t n); * DEMO_ERROR. */ demo_result bytevec_ensure_available(bytevec *vec, size_t n); +/* + * Do one read from the socket, and process all resulting bytes into the + * rustls_connection. + * Returns: + * - DEMO_OK for success + * - DEMO_AGAIN if we got an EAGAIN or EWOULDBLOCK reading from the + * socket + * - DEMO_EOF if we got EOF + * - DEMO_ERROR for other errors. + */ +demo_result do_read(conndata *conn, rustls_connection *rconn); + /* Read all available bytes from the rustls_connection until EOF. * Note that EOF here indicates "no more bytes until * process_new_packets", not "stream is closed". diff --git a/tests/server.c b/tests/server.c index c83809af..1644c436 100644 --- a/tests/server.c +++ b/tests/server.c @@ -29,61 +29,6 @@ typedef enum exchange_state SENT_RESPONSE } exchange_state; -/* - * Do one read from the socket, and process all resulting bytes into the - * rustls_connection, then copy all plaintext bytes from the session to stdout. - * Returns: - * - DEMO_OK for success - * - DEMO_AGAIN if we got an EAGAIN or EWOULDBLOCK reading from the - * socket - * - DEMO_EOF if we got EOF - * - DEMO_ERROR for other errors. - */ -demo_result -do_read(conndata *conn, rustls_connection *rconn) -{ - size_t n = 0; - char buf[1]; - const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); - if(err == EWOULDBLOCK) { - LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); - return DEMO_AGAIN; - } - else if(err != 0) { - LOG("reading from socket: errno %d", err); - return DEMO_ERROR; - } - else if(n == 0) { - return DEMO_EOF; - } - LOG("read %zu bytes from socket", n); - - const rustls_result rr = rustls_connection_process_new_packets(rconn); - if(rr != RUSTLS_RESULT_OK) { - print_error("in process_new_packets", rr); - return DEMO_ERROR; - } - - const demo_result dr = copy_plaintext_to_buffer(conn); - if(dr != DEMO_EOF) { - LOG("do_read returning %d", dr); - return dr; - } - - /* If we got an EOF on the plaintext stream (peer closed connection cleanly), - * verify that the sender then closed the TCP connection. */ - const ssize_t signed_n = read(conn->fd, buf, sizeof(buf)); - if(signed_n > 0) { - LOG("error: read returned %zu bytes after receiving close_notify", n); - return DEMO_ERROR; - } - else if(signed_n < 0 && errno != EWOULDBLOCK) { - LOG("wrong error after receiving close_notify: %s", strerror(errno)); - return DEMO_ERROR; - } - return DEMO_EOF; -} - demo_result send_response(const conndata *conn) { @@ -182,7 +127,6 @@ handle_conn(conndata *conn) } } - if(state == READING_REQUEST && body_beginning(&conn->data) != NULL) { state = SENT_RESPONSE; LOG_SIMPLE("writing response"); From 945668b844f2e057292ea073282aa8ae69127433 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:30:28 -0500 Subject: [PATCH 14/23] tests: lift up helper for logging conn info Rather than duplicating the same logic in client/server, let's do it in once in `common.c`. Also consistently log the protocol version and ALPN status for both client/server. --- tests/client.c | 23 +-------------------- tests/common.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/common.h | 8 ++++++++ tests/server.c | 34 +------------------------------ 4 files changed, 64 insertions(+), 55 deletions(-) diff --git a/tests/client.c b/tests/client.c index 76c86dbb..0a5a8afe 100644 --- a/tests/client.c +++ b/tests/client.c @@ -135,15 +135,6 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, goto cleanup; } - const int ciphersuite_id = - rustls_connection_get_negotiated_ciphersuite(rconn); - const rustls_str ciphersuite_name = - rustls_connection_get_negotiated_ciphersuite_name(rconn); - LOG("negotiated ciphersuite: %.*s (%#x)", - (int)ciphersuite_name.len, - ciphersuite_name.data, - ciphersuite_id); - for(;;) { FD_ZERO(&read_fds); /* These two calls just inspect the state of the connection - if it's time @@ -239,19 +230,7 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, LOG_SIMPLE("send_request_and_read_response: loop fell through"); drain_plaintext:; // NOTE: empty statement after label to allow var decls. - const rustls_handshake_kind hs_kind = - rustls_connection_handshake_kind(rconn); - const rustls_str hs_kind_name = rustls_handshake_kind_str(hs_kind); - LOG("handshake kind: %.*s", (int)hs_kind_name.len, hs_kind_name.data); - - const int kex_id = - rustls_connection_get_negotiated_key_exchange_group(rconn); - const rustls_str kex_name = - rustls_connection_get_negotiated_key_exchange_group_name(rconn); - LOG("negotiated key exchange: %.*s (%#x)", - (int)kex_name.len, - kex_name.data, - kex_id); + log_connection_info(rconn); const demo_result dr = copy_plaintext_to_buffer(conn); if(dr != DEMO_OK && dr != DEMO_EOF) { diff --git a/tests/common.c b/tests/common.c index a6866c35..d86d27d0 100644 --- a/tests/common.c +++ b/tests/common.c @@ -554,6 +554,60 @@ stderr_key_log_cb(const rustls_str label, const unsigned char *client_random, } } +void +log_connection_info(const rustls_connection *rconn) +{ + const rustls_handshake_kind hs_kind = + rustls_connection_handshake_kind(rconn); + const rustls_str hs_kind_name = rustls_handshake_kind_str(hs_kind); + LOG("handshake kind: %.*s", (int)hs_kind_name.len, hs_kind_name.data); + + const int protocol = rustls_connection_get_protocol_version(rconn); + const char *protocol_name; + switch(protocol) { + case RUSTLS_TLS_VERSION_TLSV1_2: + protocol_name = "TLSv1.2"; + break; + case RUSTLS_TLS_VERSION_TLSV1_3: + protocol_name = "TLSv1.3"; + break; + default: + protocol_name = "Unknown"; + } + LOG("negotiated protocol version: %s (%#x)", protocol_name, protocol); + + const int ciphersuite_id = + rustls_connection_get_negotiated_ciphersuite(rconn); + const rustls_str ciphersuite_name = + rustls_connection_get_negotiated_ciphersuite_name(rconn); + LOG("negotiated ciphersuite: %.*s (%#x)", + (int)ciphersuite_name.len, + ciphersuite_name.data, + ciphersuite_id); + + const int kex_id = + rustls_connection_get_negotiated_key_exchange_group(rconn); + const rustls_str kex_name = + rustls_connection_get_negotiated_key_exchange_group_name(rconn); + LOG("negotiated key exchange: %.*s (%#x)", + (int)kex_name.len, + kex_name.data, + kex_id); + + const uint8_t *negotiated_alpn = NULL; + size_t negotiated_alpn_len; + rustls_connection_get_alpn_protocol( + rconn, &negotiated_alpn, &negotiated_alpn_len); + if(negotiated_alpn != NULL) { + LOG("negotiated ALPN protocol: '%.*s'", + (int)negotiated_alpn_len, + (const char *)negotiated_alpn); + } + else { + LOG_SIMPLE("negotiated ALPN protocol: none"); + } +} + // TLS 1.2 and TLS 1.3, matching Rustls default. const uint16_t default_tls_versions[] = { 0x0303, 0x0304 }; diff --git a/tests/common.h b/tests/common.h index 2a7db8e1..53e493a1 100644 --- a/tests/common.h +++ b/tests/common.h @@ -156,6 +156,14 @@ void stderr_key_log_cb(rustls_str label, const unsigned char *client_random, size_t client_random_len, const unsigned char *secret, size_t secret_len); +/* + * Log information about the rustls_connection to stderr. + * + * This includes the handshake type (full vs resumed), the negotiated + * ciphersuite, and the key exchange algorithm. + */ +void log_connection_info(const rustls_connection *rconn); + extern const uint16_t default_tls_versions[]; extern const size_t default_tls_versions_len; diff --git a/tests/server.c b/tests/server.c index 1644c436..f1ed12a4 100644 --- a/tests/server.c +++ b/tests/server.c @@ -130,39 +130,7 @@ handle_conn(conndata *conn) if(state == READING_REQUEST && body_beginning(&conn->data) != NULL) { state = SENT_RESPONSE; LOG_SIMPLE("writing response"); - const rustls_handshake_kind hs_kind = - rustls_connection_handshake_kind(rconn); - const rustls_str hs_kind_name = rustls_handshake_kind_str(hs_kind); - LOG("handshake kind: %.*s", (int)hs_kind_name.len, hs_kind_name.data); - const int ciphersuite_id = - rustls_connection_get_negotiated_ciphersuite(rconn); - const rustls_str ciphersuite_name = - rustls_connection_get_negotiated_ciphersuite_name(rconn); - LOG("negotiated ciphersuite: %.*s (%#x)", - (int)ciphersuite_name.len, - ciphersuite_name.data, - ciphersuite_id); - const int kex_id = - rustls_connection_get_negotiated_key_exchange_group(rconn); - const rustls_str kex_name = - rustls_connection_get_negotiated_key_exchange_group_name(rconn); - LOG("negotiated key exchange: %.*s (%#x)", - (int)kex_name.len, - kex_name.data, - kex_id); - - const uint8_t *negotiated_alpn = NULL; - size_t negotiated_alpn_len; - rustls_connection_get_alpn_protocol( - rconn, &negotiated_alpn, &negotiated_alpn_len); - if(negotiated_alpn != NULL) { - LOG("negotiated ALPN protocol: '%.*s'", - (int)negotiated_alpn_len, - (const char *)negotiated_alpn); - } - else { - LOG_SIMPLE("no ALPN protocol was negotiated"); - } + log_connection_info(rconn); if(send_response(conn) != DEMO_OK) { goto cleanup; From d744662fe17f33854e07676feda0f17a59aa57b5 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:37:25 -0500 Subject: [PATCH 15/23] tests: collapse peer_addr_size decl/init --- tests/server.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/server.c b/tests/server.c index f1ed12a4..0727ed11 100644 --- a/tests/server.c +++ b/tests/server.c @@ -347,8 +347,7 @@ main(const int argc, const char **argv) getenv("VECTORED_IO")); while(!shutting_down) { - socklen_t peer_addr_size; - peer_addr_size = sizeof(struct sockaddr_in); + socklen_t peer_addr_size = sizeof(struct sockaddr_in); const int clientfd = accept(sockfd, (struct sockaddr *)&peer_addr, &peer_addr_size); if(shutting_down) { From 9bc64db6c0eef705c0c4bc71fc659951f62545aa Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:45:59 -0500 Subject: [PATCH 16/23] tests: tidy arg handling, let client make n reqs * Fix off-by-one in client argc checking. * Move server argv processing up front. * Add a new optional arg for the client for the number of requests to do (defaulting to 3, matching existing behaviour). --- tests/client.c | 28 ++++++++++++++++++++-------- tests/server.c | 4 +++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/tests/client.c b/tests/client.c index 0a5a8afe..54310220 100644 --- a/tests/client.c +++ b/tests/client.c @@ -337,18 +337,30 @@ main(const int argc, const char **argv) { int ret = 1; - if(argc <= 2) { - fprintf(stderr, - "usage: %s hostname port path\n\n" - "Connect to a host via HTTPS on the provided port, make a request " - "for the\n" - "given path, and emit response to stdout (three times).\n", - argv[0]); + if(argc <= 3) { + fprintf( + stderr, + "usage: %s hostname port path [numreqs]\n\n" + "Connect to a host via HTTPS on the provided port, make [numreqs] \n" + "requests for the given path, and emit each response to stdout.\n", + argv[0]); return 1; } const char *hostname = argv[1]; const char *port = argv[2]; const char *path = argv[3]; + const char *numreqs = argc > 4 ? argv[4] : "3"; + + char *end; + const long int numreqs_int = strtol(numreqs, &end, 10); + if(end == numreqs || *end != '\0') { + fprintf(stderr, "numreqs must be a positive integer\n"); + return 1; + } + if(numreqs_int <= 0) { + fprintf(stderr, "numreqs must be a positive integer\n"); + return 1; + } /* Set this global variable for logging purposes. */ programname = "client"; @@ -577,7 +589,7 @@ main(const int argc, const char **argv) goto cleanup; } - for(int i = 0; i < 3; i++) { + for(int i = 0; i < numreqs_int; i++) { const demo_result dr = do_request(client_config, hostname, port, path); if(dr != DEMO_OK) { goto cleanup; diff --git a/tests/server.c b/tests/server.c index 0727ed11..f763b575 100644 --- a/tests/server.c +++ b/tests/server.c @@ -199,6 +199,8 @@ main(const int argc, const char **argv) argv[0]); goto cleanup; } + const char *certfile = argv[1]; + const char *keyfile = argv[2]; const char *custom_ciphersuite_name = getenv("RUSTLS_CIPHERSUITE"); if(custom_ciphersuite_name != NULL) { @@ -223,7 +225,7 @@ main(const int argc, const char **argv) config_builder = rustls_server_config_builder_new(); } - certified_key = load_cert_and_key(argv[1], argv[2]); + certified_key = load_cert_and_key(certfile, keyfile); if(certified_key == NULL) { goto cleanup; } From 7084c06c53720f0a39ff5cc5f9f1017e60668ac9 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:49:01 -0500 Subject: [PATCH 17/23] tests: no log for copy_plaintext_to_buffer ret. 0 The `copy_plaintext_to_buffer()` helper is called in `do_read()`. It may return `DEMO_OK` (0) when there's no more bytes available to copy. In this case it's not very interesting to log from `do_read()`. --- tests/common.c | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/common.c b/tests/common.c index d86d27d0..20b7159e 100644 --- a/tests/common.c +++ b/tests/common.c @@ -220,7 +220,6 @@ do_read(conndata *conn, rustls_connection *rconn) const demo_result dr = copy_plaintext_to_buffer(conn); if(dr != DEMO_EOF) { - LOG("do_read returning %d", dr); return dr; } From 49e989eca9166bfe5df6f85b2378cff088ed9c72 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:52:55 -0500 Subject: [PATCH 18/23] tests: uses rustls_io_result for write_tls return --- tests/client.c | 2 +- tests/server.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/client.c b/tests/client.c index 54310220..091a130d 100644 --- a/tests/client.c +++ b/tests/client.c @@ -211,7 +211,7 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, /* This invokes rustls_connection_write_tls. We pass a callback to * that function. Rustls will pass a buffer to that callback with * encrypted bytes, that we will write to `conn`. */ - const int err = write_tls(rconn, conn, &n); + const rustls_io_result err = write_tls(rconn, conn, &n); if(err != 0) { LOG("error in rustls_connection_write_tls: errno %d", err); goto cleanup; diff --git a/tests/server.c b/tests/server.c index f763b575..6137db75 100644 --- a/tests/server.c +++ b/tests/server.c @@ -116,7 +116,7 @@ handle_conn(conndata *conn) } } if(FD_ISSET(sockfd, &write_fds)) { - const int err = write_tls(rconn, conn, &n); + const rustls_io_result err = write_tls(rconn, conn, &n); if(err != 0) { LOG("error in write_tls: errno %d", err); goto cleanup; From 270aee60c7ae93993a3293e23c6df46c4d35f9bb Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 16:57:21 -0500 Subject: [PATCH 19/23] tests: adjust logging for tls writes Rather than log when we're done writing, log when we've written some bytes. This is more interesting and we already have logging that will kick in when we break the loop. --- tests/client.c | 5 +++-- tests/server.c | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/client.c b/tests/client.c index 091a130d..73b225dc 100644 --- a/tests/client.c +++ b/tests/client.c @@ -219,10 +219,11 @@ send_request_and_read_response(conndata *conn, rustls_connection *rconn, if(dr == DEMO_AGAIN) { break; } - else if(n == 0) { - LOG_SIMPLE("write returned 0 from rustls_connection_write_tls"); + if(n == 0) { + // Writing was successful, but we wrote 0 bytes. break; } + LOG("wrote %zu bytes of data to socket", n); } } } diff --git a/tests/server.c b/tests/server.c index 6137db75..cd058329 100644 --- a/tests/server.c +++ b/tests/server.c @@ -121,10 +121,11 @@ handle_conn(conndata *conn) LOG("error in write_tls: errno %d", err); goto cleanup; } - else if(n == 0) { - LOG_SIMPLE("write returned 0 from write_tls"); + if(n == 0) { + // Writing was successful, but we wrote 0 bytes. goto cleanup; } + LOG("wrote %zu bytes of data to socket", n); } if(state == READING_REQUEST && body_beginning(&conn->data) != NULL) { From d0af1a6109b8b589771dffe78d874dd72e255a05 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 29 Nov 2024 17:03:28 -0500 Subject: [PATCH 20/23] tests: client/server rewrites For `server.c` the changes are fairly minor since it was already a relatively straight-forward and self-contained example: * Handle a potential `EAGAIN` `demo_result` from `write_tls()`. * Add a `server.h` that is presently unused, but allows keeping the compilation rules simple by treating server/client symmetrically. * Break the connection handling loop when we've both sent a response and the rustls connection requires no more writes. This effectively closes the connection after a response has been written, without waiting on the peer to do so. We want to do this since we don't process the HTTP request to learn if the client wanted `Connection: keep-alive` or `Connection: close`. For `client.c`, the changes are more extensive: * Add a `client.h` so we can forward declare everything interesting. This allows `client.c` to match our preferred Rust standard of "top down ordering" * Extract out a `demo_client_options` struct and a `options_from_env()` function for handling options based on the environment. * Extract out a `new_tls_config()` function that takes a pointer to `demo_client_options` and returns a `rustls_client_config`. * Extract out a `demo_client_request_options` struct for per-request options (hostname, port, path, whether to use vectored I/O). * Pull out a `demo_client_connection` struct for managing the state associated with a connection (socket fd, rustls_connection, conndata, closing stae, etc). * Rework existing logic around the new types. * Simplify the request handling to better match tls-client-mio.rs in the Rustls examples. Notably we _do not_ process the HTTP response, instead we just read whatever data we get and blast it to stdout. A new timeout on `select()` ensures that if the server doesn't close the connection after writing a response we will time out waiting for more data and do it ourselves. With the update to server.c to close the connection after writing a response this won't kick in, but is helpful for testing against servers that may let the conn linger even though we send `Connection: close`. * Care is taken to still treat unclean closure as an error condition. * Various other small improvements are made where possible. --- tests/client.c | 1093 ++++++++++++++++++++++++++++++------------------ tests/client.h | 132 ++++++ tests/server.c | 11 + tests/server.h | 7 + 4 files changed, 835 insertions(+), 408 deletions(-) create mode 100644 tests/client.h create mode 100644 tests/server.h diff --git a/tests/client.c b/tests/client.c index 73b225dc..56407811 100644 --- a/tests/client.c +++ b/tests/client.c @@ -1,3 +1,12 @@ +/* + * A simple demonstration client for rustls-ffi. + * + * This client connects to an HTTPS server, sends an HTTP GET request, and + * prints the response to stdout. + * + * Notably it _does not_ attempt to implement the semantics of HTTP 1.1 by + * parsing the response and processing content-length or chunked encoding. + */ #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -12,388 +21,220 @@ #endif #include +#include #include #include #include #include -/* rustls.h is autogenerated in the Makefile using cbindgen. */ #include "rustls.h" #include "common.h" +#include "client.h" -/* - * Connect to the given hostname on the given port and return the file - * descriptor of the socket. Tries to connect up to 10 times. On error, - * print the error and return 1. Caller is responsible for closing socket. - */ int -make_conn(const char *hostname, const char *port) +main(const int argc, const char **argv) { - int sockfd = 0; - struct addrinfo *getaddrinfo_output = NULL, hints = { 0 }; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; /* looking for TCP */ +#ifdef _WIN32 + WSADATA wsa; + WSAStartup(MAKEWORD(1, 1), &wsa); + setmode(STDOUT_FILENO, O_BINARY); +#endif + const rustls_client_config *tls_config = NULL; - LOG("connecting to %s:%s", hostname, port); - const int getaddrinfo_result = - getaddrinfo(hostname, port, &hints, &getaddrinfo_output); - if(getaddrinfo_result != 0) { - LOG("getaddrinfo: %s", gai_strerror(getaddrinfo_result)); - goto cleanup; - } + // Set the program name global variable for logging purposes. + programname = "client"; - int connect_result = -1; - for(int attempts = 0; attempts < 10; attempts++) { - LOG("connect attempt %d", attempts); - sockfd = socket(getaddrinfo_output->ai_family, - getaddrinfo_output->ai_socktype, - getaddrinfo_output->ai_protocol); - if(sockfd < 0) { - perror("client: making socket"); - sleep(1); - continue; - } - connect_result = connect( - sockfd, getaddrinfo_output->ai_addr, getaddrinfo_output->ai_addrlen); - if(connect_result < 0) { - if(sockfd > 0) { - close(sockfd); - } - perror("client: connecting"); - sleep(1); - continue; - } - break; - } - if(connect_result < 0) { - perror("client: connecting"); + // Process command line arguments. + int ret = 1; + if(argc <= 3) { + fprintf( + stderr, + "usage: %s hostname port path [numreqs]\n\n" + "Connect to a host via HTTPS on the provided port, make [numreqs] \n" + "requests for the given path, and emit each response to stdout.\n", + argv[0]); goto cleanup; } - const demo_result dr = nonblock(sockfd); - if(dr != DEMO_OK) { - return 1; - } - - freeaddrinfo(getaddrinfo_output); - return sockfd; + const char *hostname = argv[1]; + const char *port = argv[2]; + const char *path = argv[3]; + const char *numreqs = argc > 4 ? argv[4] : "3"; -cleanup: - if(getaddrinfo_output != NULL) { - freeaddrinfo(getaddrinfo_output); + char *end; + const long int numreqs_int = strtol(numreqs, &end, 10); + if(end == numreqs || *end != '\0') { + fprintf(stderr, "numreqs must be a positive integer\n"); + goto cleanup; } - if(sockfd > 0) { - close(sockfd); + if(numreqs_int <= 0) { + fprintf(stderr, "numreqs must be a positive integer\n"); + goto cleanup; } - return -1; -} - -static const char *CONTENT_LENGTH = "Content-Length"; - -/* - * Given an established TCP connection, and a rustls_connection, send an - * HTTP request and read the response. On success, return 0. On error, print - * the message and return 1. - */ -int -send_request_and_read_response(conndata *conn, rustls_connection *rconn, - const char *hostname, const char *path) -{ - const int sockfd = conn->fd; - int ret = 1; - char buf[2048]; - fd_set read_fds; - fd_set write_fds; - size_t n = 0; - const char *content_length_end; - unsigned long content_length = 0; - size_t headers_len = 0; - const rustls_str version = rustls_version(); - memset(buf, '\0', sizeof(buf)); - snprintf(buf, - sizeof(buf), - "GET %s HTTP/1.1\r\n" - "Host: %s\r\n" - "User-Agent: %.*s\r\n" - "Accept: carcinization/inevitable, text/html\r\n" - "Connection: close\r\n" - "\r\n", - path, - hostname, - (int)version.len, - version.data); - /* First we write the plaintext - the data that we want rustls to encrypt for - * us- to the rustls connection. */ - const rustls_result rr = - rustls_connection_write(rconn, (uint8_t *)buf, strlen(buf), &n); - if(rr != RUSTLS_RESULT_OK) { - LOG_SIMPLE("error writing plaintext bytes to rustls_connection"); + // Build a demo client options struct based on the environment. + demo_client_options opts = { 0 }; + if(options_from_env(&opts)) { goto cleanup; } - if(n != strlen(buf)) { - LOG_SIMPLE("short write writing plaintext bytes to rustls_connection"); + + // Build a rustls TLS client config with our client options. + tls_config = new_tls_config(&opts); + if(tls_config == NULL) { goto cleanup; } - for(;;) { - FD_ZERO(&read_fds); - /* These two calls just inspect the state of the connection - if it's time - for us to write more, or to read more. */ - if(rustls_connection_wants_read(rconn)) { - FD_SET(sockfd, &read_fds); - } - FD_ZERO(&write_fds); - if(rustls_connection_wants_write(rconn)) { - FD_SET(sockfd, &write_fds); - } + // Describe the connection we're about to make. + const demo_client_request_options req_opts = { + .tls_config = tls_config, + .hostname = hostname, + .port = port, + .path = path, + .use_vectored_io = opts.use_vectored_io, + }; - if(!rustls_connection_wants_read(rconn) && - !rustls_connection_wants_write(rconn)) { - LOG_SIMPLE( - "rustls wants neither read nor write. drain plaintext and exit"); - goto drain_plaintext; - } + // Make GET requests with the rustls client config. + for(int i = 0; i < numreqs_int; i++) { + LOG("request %d of %ld", i + 1, numreqs_int); - const int select_result = - select(sockfd + 1, &read_fds, &write_fds, NULL, NULL); - if(select_result == -1) { - perror("client: select"); + if(do_get_request(&req_opts)) { + LOG("request %d of %ld FAILED", i + 1, numreqs_int); goto cleanup; } - demo_result dr = DEMO_ERROR; - if(FD_ISSET(sockfd, &read_fds)) { - /* Read all bytes until we get EAGAIN. Then loop again to wind up in - select awaiting the next bit of data. */ - for(;;) { - dr = do_read(conn, rconn); - if(dr == DEMO_AGAIN) { - break; - } - else if(dr == DEMO_EOF) { - goto drain_plaintext; - } - else if(dr != DEMO_OK) { - goto cleanup; - } - if(headers_len == 0) { - const char *body = body_beginning(&conn->data); - if(body != NULL) { - headers_len = body - conn->data.data; - LOG("body began at %zu", headers_len); - const char *content_length_str = - get_first_header_value(conn->data.data, - headers_len, - CONTENT_LENGTH, - strlen(CONTENT_LENGTH), - &n); - if(content_length_str == NULL) { - LOG_SIMPLE("content length header not found"); - goto cleanup; - } - content_length = - strtoul(content_length_str, (char **)&content_length_end, 10); - if(content_length_end == content_length_str) { - LOG("invalid Content-Length '%.*s'", (int)n, content_length_str); - goto cleanup; - } - LOG("content length %lu", content_length); - } - } - if(headers_len != 0 && - conn->data.len >= headers_len + content_length) { - goto drain_plaintext; - } - } - } - if(FD_ISSET(sockfd, &write_fds)) { - for(;;) { - /* This invokes rustls_connection_write_tls. We pass a callback to - * that function. Rustls will pass a buffer to that callback with - * encrypted bytes, that we will write to `conn`. */ - const rustls_io_result err = write_tls(rconn, conn, &n); - if(err != 0) { - LOG("error in rustls_connection_write_tls: errno %d", err); - goto cleanup; - } - if(dr == DEMO_AGAIN) { - break; - } - if(n == 0) { - // Writing was successful, but we wrote 0 bytes. - break; - } - LOG("wrote %zu bytes of data to socket", n); - } - } + LOG("request %d of %ld successful", i + 1, numreqs_int); } + ret = 0; // Success. - LOG_SIMPLE("send_request_and_read_response: loop fell through"); - -drain_plaintext:; // NOTE: empty statement after label to allow var decls. - log_connection_info(rconn); +cleanup: + // Free the rustls TLS config. + rustls_client_config_free(tls_config); - const demo_result dr = copy_plaintext_to_buffer(conn); - if(dr != DEMO_OK && dr != DEMO_EOF) { - goto cleanup; - } - LOG("writing %zu bytes to stdout", conn->data.len); - if(write(STDOUT_FILENO, conn->data.data, conn->data.len) < 0) { - LOG_SIMPLE("error writing to stderr"); - goto cleanup; - } - ret = 0; +#ifdef _WIN32 + WSACleanup(); +#endif -cleanup: - if(sockfd > 0) { - close(sockfd); - } return ret; } -demo_result -do_request(const rustls_client_config *client_config, const char *hostname, - const char *port, - const char *path) // NOLINT(bugprone-easily-swappable-parameters) +int +options_from_env(demo_client_options *opts) { - rustls_connection *rconn = NULL; - conndata *conn = NULL; - demo_result dr = DEMO_ERROR; - const int sockfd = make_conn(hostname, port); - if(sockfd < 0) { - // No perror because make_conn printed error already. - goto cleanup; - } + // Consider verifier options. + const char *use_platform_verifier = getenv("RUSTLS_PLATFORM_VERIFIER"); + const char *use_ca_certificate_verifier = getenv("CA_FILE"); + const char *use_no_verifier = getenv("NO_CHECK_CERTIFICATE"); - const rustls_result rr = - rustls_client_connection_new(client_config, hostname, &rconn); - if(rr != RUSTLS_RESULT_OK) { - print_error("client_connection_new", rr); - goto cleanup; + if(use_platform_verifier) { + LOG_SIMPLE("using the platform verifier for certificate verification."); + opts->use_platform_verifier = true; } - - conn = calloc(1, sizeof(conndata)); - if(conn == NULL) { - goto cleanup; + else if(use_ca_certificate_verifier) { + LOG("using the CA file '%s' for certificate verification.", + use_ca_certificate_verifier); + opts->use_ca_certificate_verifier = use_ca_certificate_verifier; } - conn->rconn = rconn; - conn->fd = sockfd; - conn->verify_arg = "verify_arg"; - - rustls_connection_set_userdata(rconn, conn); - rustls_connection_set_log_callback(rconn, log_cb); - - dr = send_request_and_read_response(conn, rconn, hostname, path); - if(dr != DEMO_OK) { - goto cleanup; + else if(use_no_verifier) { + LOG_SIMPLE("skipping certificate verification (DANGER!)."); + opts->use_no_verifier = true; + } + else { + LOG_SIMPLE("must set RUSTLS_PLATFORM_VERIFIER, CA_FILE or " + "NO_CHECK_CERTIFICATE env var"); + return 1; } - dr = DEMO_OK; - -cleanup: - rustls_connection_free(rconn); - if(sockfd > 0) { - close(sockfd); + // Consider client auth options. + const char *auth_cert = getenv("AUTH_CERT"); + const char *auth_key = getenv("AUTH_KEY"); + if(auth_cert && auth_key) { + LOG("using client auth with cert '%s' and key '%s'", auth_cert, auth_key); + opts->use_auth_cert_file = auth_cert; + opts->use_auth_cert_key_file = auth_key; } - if(conn != NULL) { - if(conn->data.data != NULL) { - free(conn->data.data); - } - free(conn); + else if(auth_cert || auth_key) { + LOG_SIMPLE("must set both or neither of AUTH_CERT and AUTH_KEY env vars"); + return 1; } - return dr; -} - -uint32_t -verify(void *userdata, const rustls_verify_server_cert_params *params) -{ - size_t i = 0; - const rustls_slice_slice_bytes *intermediates = - params->intermediate_certs_der; - const size_t intermediates_len = rustls_slice_slice_bytes_len(intermediates); - const conndata *conn = (struct conndata *)userdata; - LOG("custom certificate verifier called for %.*s", - (int)params->server_name.len, - params->server_name.data); - LOG("end entity len: %zu", params->end_entity_cert_der.len); - LOG_SIMPLE("intermediates:"); - for(i = 0; i < intermediates_len; i++) { - const rustls_slice_bytes bytes = - rustls_slice_slice_bytes_get(intermediates, i); - if(bytes.data != NULL) { - LOG(" intermediate, len = %zu", bytes.len); - } + // Consider ECH options. + const char *ech_grease = getenv("ECH_GREASE"); + const char *ech_config_lists = getenv("ECH_CONFIG_LIST"); + if(ech_grease && ech_config_lists) { + LOG_SIMPLE( + "must set at most one of ECH_GREASE or ECH_CONFIG_LIST env vars"); + return 1; } - LOG("ocsp response len: %zu", params->ocsp_response.len); - if(0 != strcmp(conn->verify_arg, "verify_arg")) { - LOG("invalid argument to verify: %p", userdata); - return RUSTLS_RESULT_GENERAL; + else if(ech_grease) { + LOG_SIMPLE("using ECH grease"); + opts->use_ech_grease = true; + } + else if(ech_config_lists) { + LOG("using ECH config lists '%s'", ech_config_lists); + opts->use_ech_config_list_files = ech_config_lists; } - return RUSTLS_RESULT_OK; -} - -int -main(const int argc, const char **argv) -{ - int ret = 1; - if(argc <= 3) { - fprintf( - stderr, - "usage: %s hostname port path [numreqs]\n\n" - "Connect to a host via HTTPS on the provided port, make [numreqs] \n" - "requests for the given path, and emit each response to stdout.\n", - argv[0]); + // Consider SSLKEYLOGFILE options. + const char *sslkeylogfile = getenv("SSLKEYLOGFILE"); + const char *stderrkeylog = getenv("STDERRKEYLOG"); + if(sslkeylogfile && stderrkeylog) { + LOG_SIMPLE( + "must set at most one of SSLKEYLOGFILE or STDERRKEYLOG env vars"); return 1; } - const char *hostname = argv[1]; - const char *port = argv[2]; - const char *path = argv[3]; - const char *numreqs = argc > 4 ? argv[4] : "3"; + if(sslkeylogfile) { + opts->use_ssl_keylog_file = sslkeylogfile; + LOG("using SSLKEYLOGFILE '%s'", opts->use_ssl_keylog_file); + } + else if(stderrkeylog) { + opts->use_stderr_keylog = true; + LOG_SIMPLE("using stderr for keylog output"); + } - char *end; - const long int numreqs_int = strtol(numreqs, &end, 10); - if(end == numreqs || *end != '\0') { - fprintf(stderr, "numreqs must be a positive integer\n"); - return 1; + // Consider custom ciphersuite name option. + const char *custom_ciphersuite_name = getenv("RUSTLS_CIPHERSUITE"); + if(custom_ciphersuite_name) { + opts->custom_ciphersuite_name = custom_ciphersuite_name; + LOG("using custom ciphersuite '%s'", opts->custom_ciphersuite_name); } - if(numreqs_int <= 0) { - fprintf(stderr, "numreqs must be a positive integer\n"); - return 1; + + // Consider vectored I/O (if supported) +#if !defined(_WIN32) + if(getenv("USE_VECTORED_IO")) { + LOG_SIMPLE("using vectored I/O"); + opts->use_vectored_io = true; } +#endif - /* Set this global variable for logging purposes. */ - programname = "client"; + return 0; +} + +const rustls_client_config * +new_tls_config(const demo_client_options *opts) +{ + const rustls_client_config *result = NULL; + if(opts == NULL) { + return result; + } + // Initialize things we may need to clean up. const rustls_crypto_provider *custom_provider = NULL; rustls_client_config_builder *config_builder = NULL; - rustls_root_cert_store_builder *server_cert_root_store_builder = NULL; - const rustls_root_cert_store *server_cert_root_store = NULL; - const rustls_client_config *client_config = NULL; rustls_web_pki_server_cert_verifier_builder *server_cert_verifier_builder = NULL; rustls_server_cert_verifier *server_cert_verifier = NULL; - rustls_slice_bytes alpn_http11; - const rustls_certified_key *certified_key = NULL; - - alpn_http11.data = (unsigned char *)"http/1.1"; - alpn_http11.len = 8; - -#ifdef _WIN32 - WSADATA wsa; - WSAStartup(MAKEWORD(1, 1), &wsa); - setmode(STDOUT_FILENO, O_BINARY); -#endif + rustls_root_cert_store_builder *server_cert_root_store_builder = NULL; + const rustls_root_cert_store *server_cert_root_store = NULL; - const char *custom_ciphersuite_name = getenv("RUSTLS_CIPHERSUITE"); - if(custom_ciphersuite_name != NULL) { + // First, construct the client config builder. If the user has requested + // a custom ciphersuite, we first build a custom crypto provider that + // has only that suite, and then build the config builder with that. + if(opts->custom_ciphersuite_name != NULL) { custom_provider = - default_provider_with_custom_ciphersuite(custom_ciphersuite_name); + default_provider_with_custom_ciphersuite(opts->custom_ciphersuite_name); if(custom_provider == NULL) { goto cleanup; } - printf("customized to use ciphersuite: %s\n", custom_ciphersuite_name); + LOG("customized to use ciphersuite: %s", opts->custom_ciphersuite_name); const rustls_result rr = rustls_client_config_builder_new_custom(custom_provider, @@ -401,7 +242,7 @@ main(const int argc, const char **argv) default_tls_versions_len, &config_builder); if(rr != RUSTLS_RESULT_OK) { - print_error("creating client config builder", rr); + print_error("creating custom client config builder", rr); goto cleanup; } } @@ -409,33 +250,70 @@ main(const int argc, const char **argv) config_builder = rustls_client_config_builder_new(); } - const char *rustls_ech_grease = getenv("RUSTLS_ECH_GREASE"); - const char *rustls_ech_config_list = getenv("RUSTLS_ECH_CONFIG_LIST"); - if(rustls_ech_grease) { + // Then configure a verifier for the client config builder. + if(opts->use_platform_verifier) { + const rustls_result rr = + rustls_platform_server_cert_verifier(&server_cert_verifier); + if(rr != RUSTLS_RESULT_OK) { + print_error("failed to construct platform verifier", rr); + goto cleanup; + } + rustls_client_config_builder_set_server_verifier(config_builder, + server_cert_verifier); + } + else if(opts->use_ca_certificate_verifier != NULL) { + server_cert_root_store_builder = rustls_root_cert_store_builder_new(); + rustls_result rr = rustls_root_cert_store_builder_load_roots_from_file( + server_cert_root_store_builder, opts->use_ca_certificate_verifier, true); + if(rr != RUSTLS_RESULT_OK) { + print_error("loading trusted certificates", rr); + goto cleanup; + } + rr = rustls_root_cert_store_builder_build(server_cert_root_store_builder, + &server_cert_root_store); + if(rr != RUSTLS_RESULT_OK) { + goto cleanup; + } + server_cert_verifier_builder = + rustls_web_pki_server_cert_verifier_builder_new(server_cert_root_store); + + rr = rustls_web_pki_server_cert_verifier_builder_build( + server_cert_verifier_builder, &server_cert_verifier); + if(rr != RUSTLS_RESULT_OK) { + goto cleanup; + } + rustls_client_config_builder_set_server_verifier(config_builder, + server_cert_verifier); + } + else if(opts->use_no_verifier) { + rustls_client_config_builder_dangerous_set_certificate_verifier( + config_builder, unsafe_skip_verify); + } + + // Then configure ECH if required. + if(opts->use_ech_grease) { const rustls_hpke *hpke = rustls_supported_hpke(); if(hpke == NULL) { - fprintf(stderr, "client: no HPKE suites for ECH available\n"); + LOG_SIMPLE("client: no HPKE suites for ECH available"); goto cleanup; } - - result = + const rustls_result rr = rustls_client_config_builder_enable_ech_grease(config_builder, hpke); - if(result != RUSTLS_RESULT_OK) { - fprintf(stderr, "client: failed to configure ECH GREASE\n"); + if(rr != RUSTLS_RESULT_OK) { + print_error("enabling ECH GREASE", rr); goto cleanup; } - fprintf(stderr, "configured for ECH GREASE\n"); } - else if(rustls_ech_config_list) { + else if(opts->use_ech_config_list_files) { const rustls_hpke *hpke = rustls_supported_hpke(); if(hpke == NULL) { - fprintf(stderr, "client: no HPKE suites for ECH available\n"); + LOG_SIMPLE("client: no HPKE suites for ECH available"); goto cleanup; } - // Duplicate the ENV var value - calling STRTOK_R will modify the string - // to add null terminators between tokens. - char *ech_config_list_copy = strdup(rustls_ech_config_list); + // Duplicate the config lists var value - calling STRTOK_R will modify the + // string to add null terminators between tokens. + char *ech_config_list_copy = strdup(opts->use_ech_config_list_files); if(!ech_config_list_copy) { LOG_SIMPLE("failed to allocate memory for ECH config list"); goto cleanup; @@ -476,14 +354,14 @@ main(const int argc, const char **argv) // Try to enable ECH with the config list. This may error if none // of the ECH configs are valid/compatible. - result = + const rustls_result rr = rustls_client_config_builder_enable_ech(config_builder, (uint8_t *)ech_config_list_buf, ech_config_list_len, hpke); // If we successfully configured ECH with the config list then break. - if(result == RUSTLS_RESULT_OK) { + if(rr == RUSTLS_RESULT_OK) { LOG("using ECH with config list from '%s'", ech_config_list_path); ech_configured = true; break; @@ -504,51 +382,23 @@ main(const int argc, const char **argv) } } - if(getenv("RUSTLS_PLATFORM_VERIFIER")) { - const rustls_result rr = - rustls_platform_server_cert_verifier(&server_cert_verifier); - if(rr != RUSTLS_RESULT_OK) { - print_error("client: failed to construct platform verifier", rr); + // Then configure client authentication if required. + if(opts->use_auth_cert_file != NULL && + opts->use_auth_cert_key_file != NULL) { + const rustls_certified_key *certified_key = load_cert_and_key( + opts->use_auth_cert_file, opts->use_auth_cert_key_file); + if(certified_key == NULL) { goto cleanup; } - rustls_client_config_builder_set_server_verifier(config_builder, - server_cert_verifier); - } - else if(getenv("CA_FILE")) { - server_cert_root_store_builder = rustls_root_cert_store_builder_new(); - rustls_result rr = rustls_root_cert_store_builder_load_roots_from_file( - server_cert_root_store_builder, getenv("CA_FILE"), true); - if(rr != RUSTLS_RESULT_OK) { - print_error("loading trusted certificates", rr); - goto cleanup; - } - rr = rustls_root_cert_store_builder_build(server_cert_root_store_builder, - &server_cert_root_store); - if(rr != RUSTLS_RESULT_OK) { - goto cleanup; - } - server_cert_verifier_builder = - rustls_web_pki_server_cert_verifier_builder_new(server_cert_root_store); - - rr = rustls_web_pki_server_cert_verifier_builder_build( - server_cert_verifier_builder, &server_cert_verifier); - if(rr != RUSTLS_RESULT_OK) { - goto cleanup; - } - rustls_client_config_builder_set_server_verifier(config_builder, - server_cert_verifier); - } - else if(getenv("NO_CHECK_CERTIFICATE")) { - rustls_client_config_builder_dangerous_set_certificate_verifier( - config_builder, verify); - } - else { - LOG_SIMPLE("must set either RUSTLS_PLATFORM_VERIFIER or CA_FILE or " - "NO_CHECK_CERTIFICATE env var"); - goto cleanup; + rustls_client_config_builder_set_certified_key( + config_builder, &certified_key, 1); + // Per docs we are allowed to free the certified key after giving it to the + // builder. + rustls_certified_key_free(certified_key); } - if(getenv("SSLKEYLOGFILE")) { + // Then configure SSLKEYLOG as required + if(opts->use_ssl_keylog_file != NULL) { const rustls_result rr = rustls_client_config_builder_set_key_log_file(config_builder); if(rr != RUSTLS_RESULT_OK) { @@ -556,7 +406,7 @@ main(const int argc, const char **argv) goto cleanup; } } - else if(getenv("STDERRKEYLOG")) { + else if(opts->use_stderr_keylog) { const rustls_result rr = rustls_client_config_builder_set_key_log( config_builder, stderr_key_log_cb, NULL); if(rr != RUSTLS_RESULT_OK) { @@ -565,54 +415,481 @@ main(const int argc, const char **argv) } } - char *auth_cert = getenv("AUTH_CERT"); - char *auth_key = getenv("AUTH_KEY"); - if((auth_cert && !auth_key) || (!auth_cert && auth_key)) { - LOG_SIMPLE("must set both AUTH_CERT and AUTH_KEY env vars, or neither"); + // Then configure ALPN. + rustls_slice_bytes alpn_http11 = { .data = (unsigned char *)"http/1.1", + .len = 8 }; + rustls_result rr = rustls_client_config_builder_set_alpn_protocols( + config_builder, &alpn_http11, 1); + if(rr != RUSTLS_RESULT_OK) { + print_error("setting ALPN", rr); goto cleanup; } - else if(auth_cert && auth_key) { - certified_key = load_cert_and_key(auth_cert, auth_key); - if(certified_key == NULL) { + + // Finally consume the config_builder by trying to build it into a client + // config. We can't use the config_builder (even to free it!) after this + // point. + rr = rustls_client_config_builder_build(config_builder, &result); + config_builder = NULL; + if(rr != RUSTLS_RESULT_OK) { + print_error("building client config builder", rr); + goto cleanup; + } + +cleanup: + rustls_root_cert_store_builder_free(server_cert_root_store_builder); + rustls_root_cert_store_free(server_cert_root_store); + rustls_web_pki_server_cert_verifier_builder_free( + server_cert_verifier_builder); + rustls_server_cert_verifier_free(server_cert_verifier); + rustls_crypto_provider_free(custom_provider); + rustls_client_config_builder_free(config_builder); + return result; +} + +int +do_get_request(const demo_client_request_options *options) +{ + if(options == NULL || options->tls_config == NULL || + options->hostname == NULL || options->port == NULL || + options->path == NULL) { + return 1; + } + + int ret = 1; + LOG("making GET request to https://%s:%s%s", + options->hostname, + options->port, + options->path); + + // Construct a new connection to the server. + demo_client_connection *demo_conn = demo_client_connect(options); + + // Write a plaintext HTTP GET request. + if(demo_client_connection_write_get(demo_conn)) { + goto cleanup; + } + + // Process I/O with select(). + struct timeval timeout; + timeout.tv_sec = 4; // Picked arbitrarily. + timeout.tv_usec = 0; + fd_set read_fds; + fd_set write_fds; + for(;;) { + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + + if(rustls_connection_wants_read(demo_conn->rconn)) { + FD_SET(demo_conn->sockfd, &read_fds); + } + if(rustls_connection_wants_write(demo_conn->rconn)) { + FD_SET(demo_conn->sockfd, &write_fds); + } + + if(!rustls_connection_wants_read(demo_conn->rconn) && + !rustls_connection_wants_write(demo_conn->rconn)) { + LOG_SIMPLE("rustls wants neither read nor write. Breaking i/o loop."); + break; + } + + const int select_result = + select(demo_conn->sockfd + 1, &read_fds, &write_fds, NULL, &timeout); + if(select_result == -1) { + perror("client: select"); goto cleanup; } - rustls_client_config_builder_set_certified_key( - config_builder, &certified_key, 1); + else if(select_result == 0) { + LOG_SIMPLE("select timed out"); + break; + } + + // If we can read data from the socket, read it and pass it to rustls. + if(FD_ISSET(demo_conn->sockfd, &read_fds)) { + LOG_SIMPLE("doing TLS reads"); + const demo_result dr = demo_client_connection_read_tls(demo_conn); + if(dr == DEMO_ERROR || dr == DEMO_EOF) { + demo_conn->closing = true; + } + if(dr == DEMO_AGAIN) { + LOG_SIMPLE("reading from socket: EAGAIN or EWOULDBLOCK"); + continue; + } + } + + // If we can write data to the socket, write whatever rustls has queued. + if(FD_ISSET(demo_conn->sockfd, &write_fds)) { + LOG_SIMPLE("doing TLS writes"); + const demo_result dr = demo_client_connection_write_tls(demo_conn); + if(dr == DEMO_ERROR) { + demo_conn->closing = true; + } + if(dr == DEMO_AGAIN) { + LOG_SIMPLE("writing to socket: EAGAIN or EWOULDBLOCK"); + continue; + } + } + + // Handle closure. + if(demo_conn->closing) { + LOG("Connection closed. Clean? %s", + demo_conn->clean_closure ? "yes" : "no"); + // fail result if it wasn't a clean closure. + ret = !demo_conn->clean_closure; + break; + } + } + LOG_SIMPLE("I/O loop fell through"); + log_connection_info(demo_conn->rconn); + + // Print whatever is in the user data buffer. + // TODO(@cpu): refactor conndata struct to avoid "data data data" naming + // when digging in to the conndata's bytevec's data. + const char *data = demo_conn->data->data.data; + const size_t data_len = demo_conn->data->data.len; + if(data_len > 0) { + LOG("writing %zu plaintext response bytes to stdout", data_len); + if(write(STDOUT_FILENO, data, data_len) < 0) { + LOG_SIMPLE("error writing to stderr"); + goto cleanup; + } + } + else if(ret == 0) { + LOG_SIMPLE("no plaintext response data was read"); + ret = 1; } - rustls_client_config_builder_set_alpn_protocols( - config_builder, &alpn_http11, 1); +cleanup: + // Free connection resources and return. + demo_client_connection_free(demo_conn); + return ret; +} + +demo_client_connection * +demo_client_connect(const demo_client_request_options *options) +{ + if(options == NULL) { + return NULL; + } + + conndata *data = NULL; + demo_client_connection *demo_conn = NULL; + + demo_conn = calloc(1, sizeof(demo_client_connection)); + if(demo_conn == NULL) { + perror("demo_client_connection calloc"); + goto cleanup; + } + demo_conn->options = options; + + // Connect the TCP socket. + const int sockfd = connect_socket(demo_conn->options); + if(sockfd <= 0) { + perror("client: connect_socket"); + goto cleanup; + } + LOG_SIMPLE("socket connected"); + demo_conn->sockfd = sockfd; - const rustls_result rr = - rustls_client_config_builder_build(config_builder, &client_config); + // Construct the rustls request with the client config. + const rustls_result rr = rustls_client_connection_new( + options->tls_config, options->hostname, &demo_conn->rconn); if(rr != RUSTLS_RESULT_OK) { - print_error("building client config", rr); + print_error("client_connection_new", rr); goto cleanup; } - for(int i = 0; i < numreqs_int; i++) { - const demo_result dr = do_request(client_config, hostname, port, path); - if(dr != DEMO_OK) { - goto cleanup; + data = calloc(1, sizeof(conndata)); + if(data == NULL) { + perror("client: conndata calloc"); + goto cleanup; + } + data->rconn = demo_conn->rconn; + data->fd = demo_conn->sockfd; + data->verify_arg = "verify_arg"; + demo_conn->data = data; + + rustls_connection_set_userdata(demo_conn->rconn, data); + rustls_connection_set_log_callback(demo_conn->rconn, log_cb); + + return demo_conn; + +cleanup: + if(demo_conn != NULL) { + demo_client_connection_free(demo_conn); + } + + return NULL; +} + +int +connect_socket(const demo_client_request_options *options) +{ + if(options == NULL) { + return -1; + } + + int sockfd = 0; + struct addrinfo *getaddrinfo_output = NULL, hints = { 0 }; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; /* looking for TCP */ + + const int getaddrinfo_result = + getaddrinfo(options->hostname, options->port, &hints, &getaddrinfo_output); + if(getaddrinfo_result != 0) { + LOG("getaddrinfo: %s", gai_strerror(getaddrinfo_result)); + goto cleanup; + } + + int connect_result = -1; + for(int attempts = 0; attempts < MAX_CONNECT_ATTEMPTS; attempts++) { + LOG("connect attempt %d of %d", attempts + 1, MAX_CONNECT_ATTEMPTS); + sockfd = socket(getaddrinfo_output->ai_family, + getaddrinfo_output->ai_socktype, + getaddrinfo_output->ai_protocol); + if(sockfd < 0) { + perror("client: making socket"); + sleep(1); + continue; } + connect_result = connect( + sockfd, getaddrinfo_output->ai_addr, getaddrinfo_output->ai_addrlen); + if(connect_result < 0) { + if(sockfd > 0) { + close(sockfd); + } + perror("client: connecting"); + sleep(1); + continue; + } + break; + } + if(connect_result < 0) { + perror("client: connecting"); + goto cleanup; + } + const demo_result dr = nonblock(sockfd); + if(dr != DEMO_OK) { + // no need to perror() - nonblock() already did. + return -1; } - // Success! - ret = 0; + freeaddrinfo(getaddrinfo_output); + return sockfd; // Success cleanup: - rustls_root_cert_store_builder_free(server_cert_root_store_builder); - rustls_root_cert_store_free(server_cert_root_store); - rustls_web_pki_server_cert_verifier_builder_free( - server_cert_verifier_builder); - rustls_server_cert_verifier_free(server_cert_verifier); - rustls_certified_key_free(certified_key); - rustls_client_config_free(client_config); - rustls_crypto_provider_free(custom_provider); + if(getaddrinfo_output != NULL) { + freeaddrinfo(getaddrinfo_output); + } + if(sockfd > 0) { + close(sockfd); + } + return -1; +} -#ifdef _WIN32 - WSACleanup(); +int +demo_client_connection_write_get(const demo_client_connection *demo_conn) +{ + if(demo_conn == NULL || demo_conn->options == NULL) { + return 1; + } + + // Construct a plaintext HTTP request buffer. + const rustls_str version = rustls_version(); + char get_request_buf[2048]; + int get_request_size = + snprintf(get_request_buf, + sizeof(get_request_buf), + "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: %.*s\r\n" + "Accept: carcinization/inevitable, text/html\r\n" + "Connection: close\r\n" + "\r\n", + demo_conn->options->path, + demo_conn->options->hostname, + (int)version.len, + version.data); + + // Write the plaintext to the rustls connection. + size_t n = 0; + const rustls_result rr = rustls_connection_write( + demo_conn->rconn, (uint8_t *)get_request_buf, get_request_size, &n); + if(rr != RUSTLS_RESULT_OK) { + LOG_SIMPLE( + "error writing plaintext GET request bytes to rustls_connection"); + return 1; + } + if(n != (size_t)get_request_size) { + LOG_SIMPLE( + "short write writing plaintext GET request bytes to rustls_connection"); + return 1; + } + + return 0; // Success. +} + +demo_result +demo_client_connection_read_tls(demo_client_connection *demo_conn) +{ + size_t n = 0; + + const rustls_io_result io_res = + rustls_connection_read_tls(demo_conn->rconn, read_cb, demo_conn->data, &n); + + if(io_res == EAGAIN) { + return DEMO_AGAIN; + } + else if(io_res != 0) { + LOG("reading from socket failed: errno %d", io_res); + return DEMO_ERROR; + } + + if(n == 0) { + LOG_SIMPLE("read 0 bytes from socket, connection closed"); + demo_conn->closing = true; + demo_conn->clean_closure = true; + return DEMO_EOF; + } + + rustls_result rr = rustls_connection_process_new_packets(demo_conn->rconn); + if(rr != RUSTLS_RESULT_OK) { + print_error("processing new TLS packets", rr); + return DEMO_ERROR; + } + LOG("read %zu TLS bytes from socket", n); + + bytevec *bv = &demo_conn->data->data; + if(bytevec_ensure_available(bv, 1024) != DEMO_OK) { + return DEMO_ERROR; + } + + for(;;) { + char *buf = bytevec_writeable(bv); + const size_t avail = bytevec_available(bv); + + rr = rustls_connection_read(demo_conn->rconn, (uint8_t *)buf, avail, &n); + if(rr == RUSTLS_RESULT_PLAINTEXT_EMPTY) { + /* This is expected. It just means "no more bytes for now." */ + return DEMO_OK; + } + if(rr != RUSTLS_RESULT_OK) { + print_error("error in rustls_connection_read", rr); + return DEMO_ERROR; + } + if(n == 0) { + break; + } + bytevec_consume(bv, n); + if(bytevec_ensure_available(bv, 1024) != DEMO_OK) { + return DEMO_ERROR; + } + } + + /* If we got an EOF on the plaintext stream (peer closed connection cleanly), + * verify that the sender then closed the TCP connection. */ + char buf[1]; + const ssize_t signed_n = read(demo_conn->sockfd, buf, sizeof(buf)); + if(signed_n > 0) { + LOG("error: read returned %zu bytes after receiving close_notify", n); + return DEMO_ERROR; + } + else if(signed_n < 0 && errno != EWOULDBLOCK) { + LOG("wrong error after receiving close_notify: %s", strerror(errno)); + return DEMO_ERROR; + } + demo_conn->closing = true; + demo_conn->clean_closure = true; + return DEMO_EOF; +} + +demo_result +demo_client_connection_write_tls(const demo_client_connection *demo_conn) +{ + if(demo_conn == NULL || demo_conn->options == NULL) { + return DEMO_ERROR; + } + + size_t n; + rustls_io_result io_res; + +#if !defined(_WIN32) + if(demo_conn->options->use_vectored_io) { + io_res = rustls_connection_write_tls_vectored( + demo_conn->rconn, write_vectored_cb, demo_conn->data, &n); + } + else { + io_res = rustls_connection_write_tls( + demo_conn->rconn, write_cb, demo_conn->data, &n); + } +#else + io_res = rustls_connection_write_tls( + demo_conn->rconn, write_cb, demo_conn->data, &n); #endif - return ret; + if(io_res == EAGAIN) { + return DEMO_AGAIN; + } + else if(io_res != 0) { + LOG("writing to socket failed: errno %d", io_res); + return DEMO_ERROR; + } + + LOG("wrote %zu bytes of data to socket", n); + return DEMO_OK; +} + +void +demo_client_connection_free(demo_client_connection *conn) +{ + if(conn == NULL) { + return; + } + + if(conn->rconn != NULL) { + rustls_connection_free(conn->rconn); + } + + if(conn->data != NULL) { + conndata *data = conn->data; + if(data->data.data != NULL) { + free(data->data.data); + } + free(data); + } + + if(conn->sockfd != 0) { + close(conn->sockfd); + } + + free(conn); +} + +uint32_t +unsafe_skip_verify(void *userdata, + const rustls_verify_server_cert_params *params) +{ + size_t i = 0; + const rustls_slice_slice_bytes *intermediates = + params->intermediate_certs_der; + const size_t intermediates_len = rustls_slice_slice_bytes_len(intermediates); + const conndata *conn = (struct conndata *)userdata; + + LOG("custom certificate verifier called for %.*s", + (int)params->server_name.len, + params->server_name.data); + LOG("end entity len: %zu", params->end_entity_cert_der.len); + LOG_SIMPLE("intermediates:"); + for(i = 0; i < intermediates_len; i++) { + const rustls_slice_bytes bytes = + rustls_slice_slice_bytes_get(intermediates, i); + if(bytes.data != NULL) { + LOG(" intermediate, len = %zu", bytes.len); + } + } + LOG("ocsp response len: %zu", params->ocsp_response.len); + if(0 != strcmp(conn->verify_arg, "verify_arg")) { + LOG("invalid argument to verify: %p", userdata); + return RUSTLS_RESULT_GENERAL; + } + return RUSTLS_RESULT_OK; } diff --git a/tests/client.h b/tests/client.h new file mode 100644 index 00000000..259b4dfe --- /dev/null +++ b/tests/client.h @@ -0,0 +1,132 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include + +#include "rustls.h" +#include "common.h" + +// A structure to hold client demo option state. +typedef struct demo_client_options +{ + // Options for certificate verification. Only one should be set. + bool use_no_verifier; + bool use_platform_verifier; + const char *use_ca_certificate_verifier; + + // Optional client authentication using a certificate/key. None, or both + // must be set. + const char *use_auth_cert_file; + const char *use_auth_cert_key_file; + + // Optional encrypted client hello (ECH) settings. Only one should be set. + bool use_ech_grease; + const char *use_ech_config_list_files; + + // Optional SSL keylog support. At most one should be set. + const char *use_ssl_keylog_file; + bool use_stderr_keylog; + + // Optional custom ciphersuite name. If set _only_ this ciphersuite + // will be used. + const char *custom_ciphersuite_name; + + // Optional vectored IO support. + bool use_vectored_io; +} demo_client_options; + +// Populate the provided options with values from the environment. +// Returns 0 on success, or non-zero on failure after printing error +// messages to stderr. +int options_from_env(demo_client_options *opts); + +// Construct a rustls_client_config based on the provided demo_client_options. +// +// Caller owns the returned rustls_client_config and must free it with +// rustls_client_config_free. The rustls_client_config must out-live any +// demo_client_request_options made referencing it. +// +// Returns NULL on failure after printing error messages to stderr. +const rustls_client_config *new_tls_config(const demo_client_options *opts); + +// Options for an HTTPS GET request. +typedef struct demo_client_request_options +{ + const rustls_client_config *tls_config; + const char *hostname; + const char *port; + const char *path; + bool use_vectored_io; +} demo_client_request_options; + +// Make an HTTP request based on the provided options. The resulting +// plaintext is printed to STDOUT. +// +// Returns 0 on success, or non-zero on failure after printing error +// messages to stderr. +int do_get_request(const demo_client_request_options *options); + +// State related to a demo HTTPS client connection. +typedef struct demo_client_connection +{ + // the options used to create the connection. + const demo_client_request_options *options; + // the socket file descriptor for the connection. + int sockfd; + // the rustls_connection for TLS. + rustls_connection *rconn; + // the connection data for the rustls_connection. + conndata *data; + // whether the connection is closing + bool closing; + // whether the connection was closed cleanly. + bool clean_closure; +} demo_client_connection; + +// Free a demo_client_connection. +void demo_client_connection_free(demo_client_connection *conn); + +// Create a new demo_client_connection by connecting to the server +// specified by the options. +// +// The caller owns the resulting demo_client_connection and must free it +// with demo_client_connection_free. The provided +// options must outlive the connection. +// +// Returns NULL on failure after printing error messages to stderr. +demo_client_connection *demo_client_connect( + const demo_client_request_options *options); + +// Number of attempts to make in connect_socket. +#define MAX_CONNECT_ATTEMPTS 10 + +// Connect a socket to the hostname/port specified in the +// demo_client_request_options. Tries up to MAX_CONNECT_ATTEMPTS times before +// giving up. +// +// Returns a non-zero FD for the connected socket if successful, or 0 on +// failure after printing error messages to stderr. +int connect_socket(const demo_client_request_options *options); + +// Write a GET request to the provided demo_client_connection. +int demo_client_connection_write_get(const demo_client_connection *demo_conn); + +// Read TLS data from the provided demo_client_connection's socket, updating +// the rustls connection and putting any available plaintext in the demo +// connection's data. +// +// Returns a demo_result indicating the result of the read. +demo_result demo_client_connection_read_tls(demo_client_connection *demo_conn); + +// Write queued TLS data to the provided demo_client_connection's socket. +// +// Returns a demo_result indicating the result of the write. +demo_result demo_client_connection_write_tls( + const demo_client_connection *demo_conn); + +// A callback for rustls certificate validation that *unsafely* allows all +// presented certificate chains, printing their contents to stderr. +uint32_t unsafe_skip_verify(void *userdata, + const rustls_verify_server_cert_params *params); + +#endif // CLIENT_H diff --git a/tests/server.c b/tests/server.c index cd058329..28b5ae27 100644 --- a/tests/server.c +++ b/tests/server.c @@ -22,6 +22,7 @@ /* rustls.h is autogenerated in the Makefile using cbindgen. */ #include "rustls.h" #include "common.h" +#include "server.h" typedef enum exchange_state { @@ -83,6 +84,12 @@ handle_conn(conndata *conn) FD_SET(sockfd, &write_fds); } + if(state == SENT_RESPONSE && !rustls_connection_wants_write(rconn)) { + LOG_SIMPLE( + "sent response and rustls doesn't want write. closing connection"); + goto cleanup; + } + if(!rustls_connection_wants_read(rconn) && !rustls_connection_wants_write(rconn)) { LOG_SIMPLE("rustls wants neither read nor write. closing connection"); @@ -117,6 +124,10 @@ handle_conn(conndata *conn) } if(FD_ISSET(sockfd, &write_fds)) { const rustls_io_result err = write_tls(rconn, conn, &n); + if(err == EAGAIN) { + LOG_SIMPLE("writing to socket: EAGAIN or EWOULDBLOCK"); + continue; + } if(err != 0) { LOG("error in write_tls: errno %d", err); goto cleanup; diff --git a/tests/server.h b/tests/server.h new file mode 100644 index 00000000..44c4a867 --- /dev/null +++ b/tests/server.h @@ -0,0 +1,7 @@ +#ifndef SERVER_H +#define SERVER_H + +// Placeholder so that Makefile compilation rule for client/server +// can remain symmetric. + +#endif // SERVER_H From 414acde3dbae62b74a8912a272ff06f58f997658 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 9 Dec 2024 16:49:16 -0500 Subject: [PATCH 21/23] tests: remove unused get_first_header_value --- tests/common.c | 39 --------------------------------------- tests/common.h | 10 ---------- 2 files changed, 49 deletions(-) diff --git a/tests/common.c b/tests/common.c index 20b7159e..b1cc264f 100644 --- a/tests/common.c +++ b/tests/common.c @@ -331,45 +331,6 @@ body_beginning(const bytevec *vec) return (char *)result + 4; } -const char * -get_first_header_value(const char *headers, const size_t headers_len, - const char *name, const size_t name_len, size_t *n) -{ - const char *current = headers; - size_t len = headers_len; - - // We use + 3 because there needs to be room for `:` and `\r\n` after the - // header name - while(len > name_len + 3) { - const void *result = memmem(current, len, "\r\n", 2); - if(result == NULL) { - return NULL; - } - const size_t skipped = (char *)result - current + 2; - len -= skipped; - current += skipped; - /* Make sure there's enough room to conceivably contain the header name, - * a colon (:), and something after that. - */ - if(len < name_len + 2) { - return NULL; - } - if(strncasecmp(name, current, name_len) == 0 && current[name_len] == ':') { - /* Found it! */ - len -= name_len + 1; - current += name_len + 1; - result = memmem(current, len, "\r\n", 2); - if(result == NULL) { - *n = len; - return current; - } - *n = (char *)result - current; - return current; - } - } - return NULL; -} - void log_cb(void *userdata, const rustls_log_params *params) { diff --git a/tests/common.h b/tests/common.h index 53e493a1..1a8cc76b 100644 --- a/tests/common.h +++ b/tests/common.h @@ -131,16 +131,6 @@ void *memmem(const void *haystack, size_t haystacklen, const void *needle, */ char *body_beginning(const bytevec *vec); -/* If any header matching the provided name (NUL-terminated) exists, return - * a pointer to the beginning of the value for the first such occurrence - * and store the length of the header in n. - * If no such header exists, return NULL and don't modify n. - * The returned pointer will be borrowed from `headers`. - */ -const char *get_first_header_value(const char *headers, size_t headers_len, - const char *name, size_t name_len, - size_t *n); - void log_cb(void *userdata, const rustls_log_params *params); demo_result read_file(const char *filename, char *buf, size_t buflen, From a3279bc1091bfa3ad46fedbb80fcabe5a654b3f4 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 9 Dec 2024 16:51:16 -0500 Subject: [PATCH 22/23] tests: remove unused write_all header def There was no `write_all()` defined in `common.c`, so the `common.h` prototype wasn't worth keeping. --- tests/common.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/common.h b/tests/common.h index 1a8cc76b..e613ac63 100644 --- a/tests/common.h +++ b/tests/common.h @@ -64,8 +64,6 @@ extern const char *programname; void print_error(const char *prefix, rustls_result rr); -int write_all(int fd, const char *buf, int n); - /* Make a socket nonblocking. */ demo_result nonblock(int sockfd); From d3c8797819a88f364a9cf26bcc1f4686fc33d8a6 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 9 Dec 2024 16:56:38 -0500 Subject: [PATCH 23/23] tests: move server specific code out of common After the client rewrite some of the pieces in common are now server specific. Move these bits into `server.c`. For now I've put them into `server.c` in the order required for compilation pending a larger top-down order rewrite. --- tests/common.c | 80 ----------------------------------------------- tests/common.h | 22 ------------- tests/server.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 102 deletions(-) diff --git a/tests/common.c b/tests/common.c index b1cc264f..7755a7dc 100644 --- a/tests/common.c +++ b/tests/common.c @@ -108,21 +108,6 @@ write_cb(void *userdata, const unsigned char *buf, const size_t len, return 0; } -rustls_io_result -write_tls(rustls_connection *rconn, conndata *conn, size_t *n) -{ -#ifdef _WIN32 - return rustls_connection_write_tls(rconn, write_cb, conn, n); -#else - if(getenv("VECTORED_IO")) { - return rustls_connection_write_tls_vectored( - rconn, write_vectored_cb, conn, n); - } - - return rustls_connection_write_tls(rconn, write_cb, conn, n); -#endif /* _WIN32 */ -} - #ifndef _WIN32 rustls_io_result write_vectored_cb(void *userdata, const rustls_iovec *iov, size_t count, @@ -183,60 +168,6 @@ bytevec_ensure_available(bytevec *vec, const size_t n) return DEMO_OK; } -/* - * Do one read from the socket, and process all resulting bytes into the - * rustls_connection. - * Returns: - * - DEMO_OK for success - * - DEMO_AGAIN if we got an EAGAIN or EWOULDBLOCK reading from the - * socket - * - DEMO_EOF if we got EOF - * - DEMO_ERROR for other errors. - */ -demo_result -do_read(conndata *conn, rustls_connection *rconn) -{ - size_t n = 0; - char buf[1]; - const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); - if(err == EWOULDBLOCK) { - LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); - return DEMO_AGAIN; - } - else if(err != 0) { - LOG("reading from socket: errno %d", err); - return DEMO_ERROR; - } - else if(n == 0) { - return DEMO_EOF; - } - LOG("read %zu bytes from socket", n); - - const rustls_result rr = rustls_connection_process_new_packets(rconn); - if(rr != RUSTLS_RESULT_OK) { - print_error("in process_new_packets", rr); - return DEMO_ERROR; - } - - const demo_result dr = copy_plaintext_to_buffer(conn); - if(dr != DEMO_EOF) { - return dr; - } - - /* If we got an EOF on the plaintext stream (peer closed connection cleanly), - * verify that the sender then closed the TCP connection. */ - const ssize_t signed_n = read(conn->fd, buf, sizeof(buf)); - if(signed_n > 0) { - LOG("error: read returned %zu bytes after receiving close_notify", n); - return DEMO_ERROR; - } - else if(signed_n < 0 && errno != EWOULDBLOCK) { - LOG("wrong error after receiving close_notify: %s", strerror(errno)); - return DEMO_ERROR; - } - return DEMO_EOF; -} - /** * Copy all available plaintext from rustls into our own buffer, growing * our buffer as much as needed. @@ -320,17 +251,6 @@ memmem(const void *haystack, size_t haystacklen, const void *needle, return NULL; } -char * -body_beginning(const bytevec *vec) -{ - const void *result = memmem(vec->data, vec->len, "\r\n\r\n", 4); - if(result == NULL) { - return NULL; - } - - return (char *)result + 4; -} - void log_cb(void *userdata, const rustls_log_params *params) { diff --git a/tests/common.h b/tests/common.h index e613ac63..8707a72e 100644 --- a/tests/common.h +++ b/tests/common.h @@ -70,11 +70,6 @@ demo_result nonblock(int sockfd); /* A callback that reads bytes from the network. */ int read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n); -/* Invoke rustls_connection_write_tls with either a vectored or unvectored - callback, depending on environment variable. */ -rustls_io_result write_tls(rustls_connection *rconn, conndata *conn, - size_t *n); - /* A callback that writes bytes to the network. */ int write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n); @@ -98,18 +93,6 @@ void bytevec_consume(bytevec *vec, size_t n); * DEMO_ERROR. */ demo_result bytevec_ensure_available(bytevec *vec, size_t n); -/* - * Do one read from the socket, and process all resulting bytes into the - * rustls_connection. - * Returns: - * - DEMO_OK for success - * - DEMO_AGAIN if we got an EAGAIN or EWOULDBLOCK reading from the - * socket - * - DEMO_EOF if we got EOF - * - DEMO_ERROR for other errors. - */ -demo_result do_read(conndata *conn, rustls_connection *rconn); - /* Read all available bytes from the rustls_connection until EOF. * Note that EOF here indicates "no more bytes until * process_new_packets", not "stream is closed". @@ -124,11 +107,6 @@ demo_result copy_plaintext_to_buffer(conndata *conn); void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen); -/* If headers are done (received \r\n\r\n), return a pointer to the beginning - * of the body. Otherwise return NULL. - */ -char *body_beginning(const bytevec *vec); - void log_cb(void *userdata, const rustls_log_params *params); demo_result read_file(const char *filename, char *buf, size_t buflen, diff --git a/tests/server.c b/tests/server.c index 28b5ae27..ea7d1db0 100644 --- a/tests/server.c +++ b/tests/server.c @@ -61,6 +61,91 @@ send_response(const conndata *conn) return DEMO_OK; } +/* Invoke rustls_connection_write_tls with either a vectored or unvectored + callback, depending on environment variable. */ +rustls_io_result +write_tls(rustls_connection *rconn, conndata *conn, size_t *n) +{ +#ifdef _WIN32 + return rustls_connection_write_tls(rconn, write_cb, conn, n); +#else + if(getenv("VECTORED_IO")) { + return rustls_connection_write_tls_vectored( + rconn, write_vectored_cb, conn, n); + } + + return rustls_connection_write_tls(rconn, write_cb, conn, n); +#endif /* _WIN32 */ +} + +/* + * Do one read from the socket, and process all resulting bytes into the + * rustls_connection. + * Returns: + * - DEMO_OK for success + * - DEMO_AGAIN if we got an EAGAIN or EWOULDBLOCK reading from the + * socket + * - DEMO_EOF if we got EOF + * - DEMO_ERROR for other errors. + */ +demo_result +do_read(conndata *conn, rustls_connection *rconn) +{ + size_t n = 0; + char buf[1]; + const int err = rustls_connection_read_tls(rconn, read_cb, conn, &n); + if(err == EWOULDBLOCK) { + LOG("reading from socket: EAGAIN or EWOULDBLOCK: %s", strerror(errno)); + return DEMO_AGAIN; + } + else if(err != 0) { + LOG("reading from socket: errno %d", err); + return DEMO_ERROR; + } + else if(n == 0) { + return DEMO_EOF; + } + LOG("read %zu bytes from socket", n); + + const rustls_result rr = rustls_connection_process_new_packets(rconn); + if(rr != RUSTLS_RESULT_OK) { + print_error("in process_new_packets", rr); + return DEMO_ERROR; + } + + const demo_result dr = copy_plaintext_to_buffer(conn); + if(dr != DEMO_EOF) { + return dr; + } + + /* If we got an EOF on the plaintext stream (peer closed connection cleanly), + * verify that the sender then closed the TCP connection. */ + const ssize_t signed_n = read(conn->fd, buf, sizeof(buf)); + if(signed_n > 0) { + LOG("error: read returned %zu bytes after receiving close_notify", n); + return DEMO_ERROR; + } + else if(signed_n < 0 && errno != EWOULDBLOCK) { + LOG("wrong error after receiving close_notify: %s", strerror(errno)); + return DEMO_ERROR; + } + return DEMO_EOF; +} + +/* If headers are done (received \r\n\r\n), return a pointer to the beginning + * of the body. Otherwise return NULL. + */ +char * +body_beginning(const bytevec *vec) +{ + const void *result = memmem(vec->data, vec->len, "\r\n\r\n", 4); + if(result == NULL) { + return NULL; + } + + return (char *)result + 4; +} + void handle_conn(conndata *conn) {