From 10a57ce79e2c6ffe3a02ba86272a447a8183eb8a Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Thu, 29 Jun 2023 11:23:27 -0400 Subject: [PATCH 1/4] tests: common read_file and load_cert_and_key. Previously only the `tests/server.c` code needed to load a `rustls_certified_key` (for the server cert/keypair). In a subsequent commit the `tests/client.c` code will need to do the same for optionally providing a `rustls_certified_key` for client certificate authentication. In preparation, this commit lifts the `read_file` and `load_cert_and_key` helper functions from `tests/server.c` into `tests/common.c` (updating `tests/common.h` to match) where both client and server test programs can use the shared code. --- tests/common.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++- tests/common.h | 6 ++++++ tests/server.c | 51 +------------------------------------------------- 3 files changed, 56 insertions(+), 51 deletions(-) diff --git a/tests/common.c b/tests/common.c index d2797bae..ab412422 100644 --- a/tests/common.c +++ b/tests/common.c @@ -16,7 +16,6 @@ #include #include #include -#include #include "rustls.h" #include "common.h" @@ -327,3 +326,52 @@ log_cb(void *userdata, const struct rustls_log_params *params) fprintf(stderr, "%s[fd %d][%.*s]: %.*s\n", conn->program_name, conn->fd, (int)level_str.len, level_str.data, (int)params->message.len, params->message.data); } + +enum demo_result +read_file(const char *progname, const char *filename, char *buf, size_t buflen, size_t *n) +{ + FILE *f = fopen(filename, "r"); + if(f == NULL) { + fprintf(stderr, "%s: opening %s: %s\n", progname, filename, strerror(errno)); + return DEMO_ERROR; + } + *n = fread(buf, 1, buflen, f); + if(!feof(f)) { + fprintf(stderr, "%s: reading %s: %s\n", progname, filename, strerror(errno)); + fclose(f); + return DEMO_ERROR; + } + fclose(f); + return DEMO_OK; +} + +const struct rustls_certified_key * +load_cert_and_key(const char *progname, const char *certfile, const char *keyfile) +{ + char certbuf[10000]; + size_t certbuf_len; + char keybuf[10000]; + size_t keybuf_len; + + unsigned int result = read_file(progname, certfile, certbuf, sizeof(certbuf), &certbuf_len); + if(result != DEMO_OK) { + return NULL; + } + + result = read_file(progname, keyfile, keybuf, sizeof(keybuf), &keybuf_len); + if(result != DEMO_OK) { + return NULL; + } + + const struct 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(progname, "parsing certificate and key", result); + return NULL; + } + return certified_key; +} diff --git a/tests/common.h b/tests/common.h index 6efdffc3..37a4a567 100644 --- a/tests/common.h +++ b/tests/common.h @@ -125,4 +125,10 @@ get_first_header_value(const char *headers, size_t headers_len, void log_cb(void *userdata, const struct rustls_log_params *params); +enum demo_result +read_file(const char *progname, const char *filename, char *buf, size_t buflen, size_t *n); + +const struct rustls_certified_key * +load_cert_and_key(const char *progname, const char *certfile, const char *keyfile); + #endif /* COMMON_H */ diff --git a/tests/server.c b/tests/server.c index a394cb34..a66ca73c 100644 --- a/tests/server.c +++ b/tests/server.c @@ -23,24 +23,6 @@ #include "rustls.h" #include "common.h" -enum demo_result -read_file(const char *filename, char *buf, size_t buflen, size_t *n) -{ - FILE *f = fopen(filename, "r"); - if(f == NULL) { - fprintf(stderr, "server: opening %s: %s\n", filename, strerror(errno)); - return DEMO_ERROR; - } - *n = fread(buf, 1, buflen, f); - if(!feof(f)) { - fprintf(stderr, "server: reading %s: %s\n", filename, strerror(errno)); - fclose(f); - return DEMO_ERROR; - } - fclose(f); - return DEMO_OK; -} - typedef enum exchange_state { READING_REQUEST, @@ -242,37 +224,6 @@ handle_conn(struct conndata *conn) free(conn); } -const struct rustls_certified_key * -load_cert_and_key(const char *certfile, const char *keyfile) -{ - char certbuf[10000]; - size_t certbuf_len; - char keybuf[10000]; - size_t keybuf_len; - - unsigned int result = read_file(certfile, certbuf, sizeof(certbuf), &certbuf_len); - if(result != DEMO_OK) { - return NULL; - } - - result = read_file(keyfile, keybuf, sizeof(keybuf), &keybuf_len); - if(result != DEMO_OK) { - return NULL; - } - - const struct 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("server", "parsing certificate and key", result); - return NULL; - } - return certified_key; -} - bool shutting_down = false; void handle_signal(int signo) { @@ -315,7 +266,7 @@ main(int argc, const char **argv) goto cleanup; } - certified_key = load_cert_and_key(argv[1], argv[2]); + certified_key = load_cert_and_key(argv[0], argv[1], argv[2]); if(certified_key == NULL) { goto cleanup; } From db1d9b77f3bace3f35ac632c7c4b37017c7ae4e5 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Thu, 29 Jun 2023 11:26:32 -0400 Subject: [PATCH 2/4] tests/client: add client certificate support. This commit updates `tests/client.c` to allow setting two new env vars, `AUTH_CERT` and `AUTH_KEY`. If neither are set, the program works as it did before: no client certificate is sent for mTLS. If one but not both of these env vars are set, the program will error: they must both be provided. If both are set, the `AUTH_CERT` and `AUTH_KEY` files are loaded into a `rustls_certified_key` and the built `rustls_client_config` will be configured to offer client certificate authentication with the server using the cert/key pair. --- tests/client.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/client.c b/tests/client.c index 84b78bf4..562ccc63 100644 --- a/tests/client.c +++ b/tests/client.c @@ -409,6 +409,7 @@ main(int argc, const char **argv) rustls_client_config_builder_new(); const struct rustls_client_config *client_config = NULL; struct rustls_slice_bytes alpn_http11; + const struct rustls_certified_key *certified_key = NULL; alpn_http11.data = (unsigned char*)"http/1.1"; alpn_http11.len = 8; @@ -434,6 +435,19 @@ main(int argc, const char **argv) goto cleanup; } + 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"); + goto cleanup; + } else if (auth_cert && auth_key) { + certified_key = load_cert_and_key(argv[0], auth_cert, auth_key); + if(certified_key == NULL) { + goto cleanup; + } + rustls_client_config_builder_set_certified_key(config_builder, &certified_key, 1); + } + rustls_client_config_builder_set_alpn_protocols(config_builder, &alpn_http11, 1); client_config = rustls_client_config_builder_build(config_builder); @@ -450,6 +464,7 @@ main(int argc, const char **argv) ret = 0; cleanup: + rustls_certified_key_free(certified_key); rustls_client_config_free(client_config); #ifdef _WIN32 From 518f34c9ce2811f840a7ee2e4d0d4d5436f5d7e8 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Thu, 29 Jun 2023 11:31:01 -0400 Subject: [PATCH 3/4] tests/server: add optional required client cert auth. This commit updates the `tests/server.c` program so that if an `AUTH_CERT` env var is provided the server will be configured to require clients provide a client certificate issued that chains to the `AUTH_CERT` certificate authority. If no `AUTH_CERT` env var is set the server works as it did before, ignoring client certificate authentication. --- tests/server.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/server.c b/tests/server.c index a66ca73c..43c31eeb 100644 --- a/tests/server.c +++ b/tests/server.c @@ -245,6 +245,8 @@ main(int argc, const char **argv) struct rustls_connection *rconn = NULL; const struct rustls_certified_key *certified_key = NULL; struct rustls_slice_bytes alpn_http11; + const struct rustls_client_cert_verifier *client_cert_verifier = NULL; + struct rustls_root_cert_store *client_cert_root_store = NULL; alpn_http11.data = (unsigned char*)"http/1.1"; alpn_http11.len = 8; @@ -275,6 +277,22 @@ main(int argc, const char **argv) config_builder, &certified_key, 1); rustls_server_config_builder_set_alpn_protocols(config_builder, &alpn_http11, 1); + char* auth_cert = getenv("AUTH_CERT"); + if(auth_cert) { + char certbuf[10000]; + size_t certbuf_len; + int result = read_file(argv[0], auth_cert, certbuf, sizeof(certbuf), &certbuf_len); + if(result != DEMO_OK) { + goto cleanup; + } + + client_cert_root_store = rustls_root_cert_store_new(); + rustls_root_cert_store_add_pem(client_cert_root_store, (uint8_t *)certbuf, certbuf_len, true); + + client_cert_verifier = rustls_client_cert_verifier_new(client_cert_root_store); + rustls_server_config_builder_set_client_verifier(config_builder, client_cert_verifier); + } + server_config = rustls_server_config_builder_build(config_builder); #ifdef _WIN32 @@ -350,6 +368,8 @@ main(int argc, const char **argv) cleanup: rustls_certified_key_free(certified_key); + rustls_root_cert_store_free(client_cert_root_store); + rustls_client_cert_verifier_free(client_cert_verifier); rustls_server_config_free(server_config); rustls_connection_free(rconn); if(sockfd>0) { From 1538cb67bd0a611f4748c0df300880ea7c12ab4d Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 30 Jun 2023 13:11:48 -0400 Subject: [PATCH 4/4] tests: include mTLS tests. Tests that: * A client w/ AUTH_KEY + AUTH_CERT can connect to a server that doesn't require mTLS without error. * A client w/ AUTH_KEY + AUTH_CERT can connect to a server that requires mTLS without error. * A client w/o AUTH_KEY + AUTH_CERT errors when connecting to a server that requires mTLS. --- tests/client-server.py | 64 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/tests/client-server.py b/tests/client-server.py index 8d3beb1c..4f0d10e4 100755 --- a/tests/client-server.py +++ b/tests/client-server.py @@ -35,12 +35,16 @@ def wait_tcp_port(host, port): print("Connected to {}:{}".format(host, port)) -def run_with_maybe_valgrind(args, env, valgrind): +def run_with_maybe_valgrind(args, env, valgrind, expect_error=False): if valgrind is not None: args = [valgrind] + args process_env = os.environ.copy() process_env.update(env) - subprocess.check_call(args, env=process_env, stdout=subprocess.DEVNULL) + try: + subprocess.check_call(args, env=process_env, stdout=subprocess.DEVNULL) + except subprocess.CalledProcessError as e: + if not expect_error: + raise e def run_client_tests(client, valgrind): @@ -81,6 +85,50 @@ def run_client_tests(client, valgrind): }, valgrind ) + run_with_maybe_valgrind( + [ + client, + HOST, + str(PORT), + "/" + ], + { + "CA_FILE": "testdata/minica.pem", + "AUTH_CERT": "testdata/localhost/cert.pem", + "AUTH_KEY": "testdata/localhost/key.pem", + }, + valgrind + ) + + +def run_mtls_client_tests(client, valgrind): + run_with_maybe_valgrind( + [ + client, + HOST, + str(PORT), + "/" + ], + { + "CA_FILE": "testdata/minica.pem", + }, + valgrind, + expect_error=True # Client connecting w/o AUTH_CERT/AUTH_KEY should err. + ) + run_with_maybe_valgrind( + [ + client, + HOST, + str(PORT), + "/" + ], + { + "CA_FILE": "testdata/minica.pem", + "AUTH_CERT": "testdata/localhost/cert.pem", + "AUTH_KEY": "testdata/localhost/key.pem", + }, + valgrind + ) def run_server(server, valgrind, env): @@ -116,17 +164,27 @@ def main(): .format(PORT)) sys.exit(1) + # Standard client/server tests. server_popen = run_server(server, valgrind, {}) wait_tcp_port(HOST, PORT) run_client_tests(client, valgrind) server_popen.kill() server_popen.wait() - run_server(server, valgrind, { + # Client/server tests w/ vectored I/O. + server_popen = run_server(server, valgrind, { "VECTORED_IO": "" }) wait_tcp_port(HOST, PORT) run_client_tests(client, valgrind) + server_popen.kill() + server_popen.wait() + + # Client/server tests w/ mandatory client authentication. + run_server(server, valgrind, { + "AUTH_CERT": "testdata/minica.pem", + }) + run_mtls_client_tests(client, valgrind) if __name__ == "__main__":