diff options
Diffstat (limited to 'regress/lib')
-rw-r--r-- | regress/lib/libssl/unit/ssl_get_shared_ciphers.c | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/regress/lib/libssl/unit/ssl_get_shared_ciphers.c b/regress/lib/libssl/unit/ssl_get_shared_ciphers.c new file mode 100644 index 00000000000..f83da3e2fa1 --- /dev/null +++ b/regress/lib/libssl/unit/ssl_get_shared_ciphers.c @@ -0,0 +1,457 @@ +/* $OpenBSD: ssl_get_shared_ciphers.c,v 1.1 2021/01/10 07:30:00 tb Exp $ */ +/* + * Copyright (c) 2021 Theo Buehler <tb@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdint.h> +#include <openssl/err.h> +#include <openssl/ssl.h> + +struct peer_config { + const char *name; + int server; + uint16_t max_version; + uint16_t min_version; + const char *ciphers; +}; + +struct ssl_shared_ciphers_test_data { + const char *description; + struct peer_config client_config; + struct peer_config server_config; + const char *shared_ciphers; + const char *shared_ciphers_without_aesni; +}; + +char *server_cert; +char *server_key; + +static const struct ssl_shared_ciphers_test_data ssl_shared_ciphers_tests[] = { + { + .description = "TLSv1.3 defaults", + .client_config = { + .name = "client", + .server = 0, + .max_version = TLS1_3_VERSION, + .min_version = TLS1_3_VERSION, + .ciphers = + "AEAD-AES256-GCM-SHA384:" + "AEAD-CHACHA20-POLY1305-SHA256:" + "AEAD-AES128-GCM-SHA256", + }, + .server_config = { + .name = "server", + .server = 1, + .max_version = TLS1_3_VERSION, + .min_version = TLS1_3_VERSION, + .ciphers = + "AEAD-AES256-GCM-SHA384:" + "AEAD-CHACHA20-POLY1305-SHA256:" + "AEAD-AES128-GCM-SHA256", + }, + .shared_ciphers = + "AEAD-AES256-GCM-SHA384:" + "AEAD-CHACHA20-POLY1305-SHA256:" + "AEAD-AES128-GCM-SHA256", + }, + + { + .description = "TLSv1.3, client without ChaCha", + .client_config = { + .name = "client", + .server = 0, + .max_version = TLS1_3_VERSION, + .min_version = TLS1_3_VERSION, + .ciphers = + "AEAD-AES256-GCM-SHA384:" + "AEAD-AES128-GCM-SHA256", + }, + .server_config = { + .name = "server", + .server = 1, + .max_version = TLS1_3_VERSION, + .min_version = TLS1_3_VERSION, + .ciphers = + "AEAD-AES256-GCM-SHA384:" + "AEAD-CHACHA20-POLY1305-SHA256:" + "AEAD-AES128-GCM-SHA256", + }, + .shared_ciphers = + "AEAD-AES256-GCM-SHA384:" + "AEAD-AES128-GCM-SHA256", + }, + + { + .description = "TLSv1.2", + .client_config = { + .name = "client", + .server = 0, + .max_version = TLS1_2_VERSION, + .min_version = TLS1_2_VERSION, + .ciphers = + "ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "ECDHE-ECDSA-AES256-SHA384:" + "ECDHE-RSA-AES256-SHA:" + "ECDHE-ECDSA-AES256-SHA", + }, + .server_config = { + .name = "server", + .server = 1, + .max_version = TLS1_2_VERSION, + .min_version = TLS1_2_VERSION, + .ciphers = + "ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "ECDHE-ECDSA-AES256-SHA384:" + "ECDHE-RSA-AES256-SHA:" + "ECDHE-ECDSA-AES256-SHA", + }, + .shared_ciphers = + "ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "ECDHE-ECDSA-AES256-SHA384:" + "ECDHE-RSA-AES256-SHA:" + "ECDHE-ECDSA-AES256-SHA", + }, + + { + .description = "TLSv1.2, server without ECDSA", + .client_config = { + .name = "client", + .server = 0, + .max_version = TLS1_2_VERSION, + .min_version = TLS1_2_VERSION, + .ciphers = + "ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "ECDHE-ECDSA-AES256-SHA384:" + "ECDHE-RSA-AES256-SHA:" + "ECDHE-ECDSA-AES256-SHA", + }, + .server_config = { + .name = "server", + .server = 1, + .max_version = TLS1_2_VERSION, + .min_version = TLS1_2_VERSION, + .ciphers = + "ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "ECDHE-RSA-AES256-SHA", + }, + .shared_ciphers = + "ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "ECDHE-RSA-AES256-SHA", + }, + + { + .description = "TLSv1.3 ciphers are prepended", + .client_config = { + .name = "client", + .server = 0, + .max_version = TLS1_3_VERSION, + .min_version = TLS1_2_VERSION, + .ciphers = + "ECDHE-RSA-AES256-GCM-SHA384", + }, + .server_config = { + .name = "server", + .server = 1, + .max_version = TLS1_3_VERSION, + .min_version = TLS1_2_VERSION, + .ciphers = + "ECDHE-RSA-AES256-GCM-SHA384", + }, + .shared_ciphers = + "AEAD-AES256-GCM-SHA384:" + "AEAD-CHACHA20-POLY1305-SHA256:" + "AEAD-AES128-GCM-SHA256:" + "ECDHE-RSA-AES256-GCM-SHA384", + .shared_ciphers_without_aesni = + "AEAD-CHACHA20-POLY1305-SHA256:" + "AEAD-AES256-GCM-SHA384:" + "AEAD-AES128-GCM-SHA256:" + "ECDHE-RSA-AES256-GCM-SHA384", + }, +}; + +static const size_t N_SHARED_CIPHERS_TESTS = + sizeof(ssl_shared_ciphers_tests) / sizeof(ssl_shared_ciphers_tests[0]); + +static SSL_CTX * +peer_config_to_ssl_ctx(const struct peer_config *config) +{ + SSL_CTX *ctx; + + if ((ctx = SSL_CTX_new(TLS_method())) == NULL) { + fprintf(stderr, "SSL_CTX_new(%s) failed\n", config->name); + goto err; + } + if (!SSL_CTX_set_max_proto_version(ctx, config->max_version)) { + fprintf(stderr, "max_proto_version(%s) failed\n", config->name); + goto err; + } + if (!SSL_CTX_set_min_proto_version(ctx, config->min_version)) { + fprintf(stderr, "min_proto_version(%s) failed\n", config->name); + goto err; + } + if (!SSL_CTX_set_cipher_list(ctx, config->ciphers)) { + fprintf(stderr, "set_cipher_list(%s) failed\n", + config->name); + goto err; + } + + SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY); + + if (config->server) { + if (!SSL_CTX_use_certificate_file(ctx, server_cert, + SSL_FILETYPE_PEM)) { + fprintf(stderr, "use_certificate_file(%s) failed\n", + config->name); + goto err; + } + if (!SSL_CTX_use_PrivateKey_file(ctx, server_key, + SSL_FILETYPE_PEM)) { + fprintf(stderr, "use_PrivateKey_file(%s) failed\n", + config->name); + goto err; + } + } + + return ctx; + + err: + SSL_CTX_free(ctx); + return NULL; +} + +/* Connect client and server via a pair of "nonblocking" memory BIOs. */ +static int +connect_peers(SSL *client_ssl, SSL *server_ssl, const char *description) +{ + BIO *client_wbio = NULL, *server_wbio = NULL; + int ret = 0; + + if ((client_wbio = BIO_new(BIO_s_mem())) == NULL) { + fprintf(stderr, "%s: failed to create client BIO\n", + description); + goto err; + } + if ((server_wbio = BIO_new(BIO_s_mem())) == NULL) { + fprintf(stderr, "%s: failed to create server BIO\n", + description); + goto err; + } + if (BIO_set_mem_eof_return(client_wbio, -1) <= 0) { + fprintf(stderr, "%s: failed to set client eof return\n", + description); + goto err; + } + if (BIO_set_mem_eof_return(server_wbio, -1) <= 0) { + fprintf(stderr, "%s: failed to set server eof return\n", + description); + goto err; + } + + /* Avoid double free. SSL_set_bio() takes ownership of the BIOs. */ + BIO_up_ref(client_wbio); + BIO_up_ref(server_wbio); + + SSL_set_bio(client_ssl, server_wbio, client_wbio); + SSL_set_bio(server_ssl, client_wbio, server_wbio); + client_wbio = NULL; + server_wbio = NULL; + + ret = 1; + + err: + BIO_free(client_wbio); + BIO_free(server_wbio); + + return ret; +} + +static int +push_data_to_peer(SSL *ssl, int *ret, int (*func)(SSL *), const char *func_name, + const char *description) +{ + int ssl_err = 0; + + if (*ret == 1) + return 1; + + /* + * Do SSL_connect/SSL_accept once and loop while hitting WANT_WRITE. + * If done or on WANT_READ hand off to peer. + */ + + do { + if ((*ret = func(ssl)) <= 0) + ssl_err = SSL_get_error(ssl, *ret); + } while (*ret <= 0 && ssl_err == SSL_ERROR_WANT_WRITE); + + if (*ret <= 0 && ssl_err != SSL_ERROR_WANT_READ) { + fprintf(stderr, "%s: %s failed\n", description, func_name); + ERR_print_errors_fp(stderr); + return 0; + } + + return 1; +} + +static int +handshake_loop(SSL *client_ssl, int *client_ret, SSL *server_ssl, + int *server_ret, const char *description) +{ + if (!push_data_to_peer(client_ssl, client_ret, SSL_connect, + "SSL_connect", description)) + return 0; + + if (!push_data_to_peer(server_ssl, server_ret, SSL_accept, + "SSL_accept", description)) + return 0; + + return 1; +} + +/* + * Alternate between loops of SSL_connect() and SSL_accept() as long as only + * WANT_READ and WANT_WRITE situations are encountered. A function is repeated + * while WANT_WRITE is returned or it succeeds, then it's the other functions + * turn to make progress. Success: both functions returned 1. + */ +static int +handshake(SSL *client_ssl, SSL *server_ssl, const char *description) +{ + int loops = 0, client_ret = 0, server_ret = 0; + + while (loops++ < 10 && (client_ret <= 0 || server_ret <= 0)) { + if (!handshake_loop(client_ssl, &client_ret, server_ssl, + &server_ret, description)) + return 0; + } + + return client_ret == 1 && server_ret == 1; +} + +/* from ssl_ciph.c */ +static inline int +ssl_aes_is_accelerated(void) +{ +#if defined(__i386__) || defined(__x86_64__) + return ((OPENSSL_cpu_caps() & (1ULL << 57)) != 0); +#else + return (0); +#endif +} + +static int +check_shared_ciphers(const struct ssl_shared_ciphers_test_data *test, + const char *got) +{ + const char *want = test->shared_ciphers; + int failed; + + failed = strcmp(want, got); + + if (failed && !ssl_aes_is_accelerated() && + test->shared_ciphers_without_aesni != NULL) { + want = test->shared_ciphers_without_aesni; + failed = strcmp(want, got); + } + + if (failed) + fprintf(stderr, "%s: want \"%s\", got \"%s\"\n", + test->description, want, got); + + return failed; +} + +static int +test_get_shared_ciphers(const struct ssl_shared_ciphers_test_data *test) +{ + SSL_CTX *client_ctx = NULL, *server_ctx = NULL; + SSL *client_ssl = NULL, *server_ssl = NULL; + char buf[4096]; + int failed = 1; + + if ((client_ctx = peer_config_to_ssl_ctx(&test->client_config)) == NULL) + goto err; + if ((server_ctx = peer_config_to_ssl_ctx(&test->server_config)) == NULL) + goto err; + + if ((client_ssl = SSL_new(client_ctx)) == NULL) { + fprintf(stderr, "%s: failed to create client SSL\n", + test->description); + goto err; + } + if ((server_ssl = SSL_new(server_ctx)) == NULL) { + fprintf(stderr, "%s: failed to create server SSL\n", + test->description); + goto err; + } + + if (!connect_peers(client_ssl, server_ssl, test->description)) + goto err; + + if (!handshake(client_ssl, server_ssl, test->description)) + goto err; + + if (SSL_get_shared_ciphers(server_ssl, buf, sizeof(buf)) == NULL) { + fprintf(stderr, "%s: failed to get shared ciphers\n", + test->description); + goto err; + } + + failed = check_shared_ciphers(test, buf); + + err: + SSL_CTX_free(client_ctx); + SSL_CTX_free(server_ctx); + SSL_free(client_ssl); + SSL_free(server_ssl); + + return failed; +} + +int +main(int argc, char **argv) +{ + size_t i; + int failed = 0; + + if (asprintf(&server_cert, "%s/../certs/server.pem", CURDIR) == -1) { + fprintf(stderr, "asprintf server_cert failed\n"); + failed = 1; + goto err; + } + server_key = server_cert; + + for (i = 0; i < N_SHARED_CIPHERS_TESTS; i++) + failed |= test_get_shared_ciphers(&ssl_shared_ciphers_tests[i]); + + if (failed == 0) + printf("PASS %s\n", __FILE__); + + err: + free(server_cert); + + return failed; +} |