From 4cf26cd84b1aaa31a1916c57abc22a981ce35f79 Mon Sep 17 00:00:00 2001 From: tb Date: Mon, 30 Aug 2021 17:27:45 +0000 Subject: [PATCH] Reimplement part of the openssl/x509 regress tests in C Instead of using s_client and s_server and complicated shell scripts, we can reuse the framework from the ssl_get_shared_cipher() regress test and inspect the verify return value directly. Discussed with beck jan jsing --- regress/lib/libssl/verify/Makefile | 37 ++ .../verify/create-libressl-test-certs.pl | 111 ++++++ regress/lib/libssl/verify/verify.c | 373 ++++++++++++++++++ 3 files changed, 521 insertions(+) create mode 100644 regress/lib/libssl/verify/Makefile create mode 100644 regress/lib/libssl/verify/create-libressl-test-certs.pl create mode 100644 regress/lib/libssl/verify/verify.c diff --git a/regress/lib/libssl/verify/Makefile b/regress/lib/libssl/verify/Makefile new file mode 100644 index 00000000000..515b22e07a2 --- /dev/null +++ b/regress/lib/libssl/verify/Makefile @@ -0,0 +1,37 @@ +# $OpenBSD: Makefile,v 1.1.1.1 2021/08/30 17:27:45 tb Exp $ + +.if !(make(clean) || make(cleandir) || make(obj)) +. if !exists(/usr/local/libdata/perl5/site_perl/IO/Socket/SSL.pm) +regress: + @echo "missing package p5-IO-Socket-SSL" + @echo SKIPPED +. endif +.endif +PROGS += verify + +.for p in ${PROGS} +REGRESS_TARGETS += run-$p +.endfor + +LDADD = -lcrypto -lssl +DPADD = ${LIBCRYPTO} ${LIBSSL} +WARNINGS = Yes +CFLAGS += -DLIBRESSL_INTERNAL -Wundef -Werror + +PERL ?= perl + +REGRESS_SETUP_ONCE += create-libressl-test-certs +create-libressl-test-certs: create-libressl-test-certs.pl + ${PERL} ${.CURDIR}/$@.pl + + +CLEANFILES += *.pem *.key + +.for p in ${PROGS} +run-$p: $p + ./$p + +.PHONY: run-$p +.endfor + +.include diff --git a/regress/lib/libssl/verify/create-libressl-test-certs.pl b/regress/lib/libssl/verify/create-libressl-test-certs.pl new file mode 100644 index 00000000000..fdb718aadcd --- /dev/null +++ b/regress/lib/libssl/verify/create-libressl-test-certs.pl @@ -0,0 +1,111 @@ +#!/usr/bin/perl + +# Copyright (c) 2021 Steffen Ullrich +# Public Domain + +use strict; +use warnings; +use IO::Socket::SSL::Utils; + +# primitive CA - ROOT +my @ca = cert( + CA => 1, + subject => { CN => 'ROOT' } +); +out('caR.pem', pem(crt => $ca[0])); +out('caR.key', pem(key => $ca[1])); + +# server certificate where SAN contains in-label wildcards, which a +# client MAY choose to accept as per RFC 6125 section 6.4.3. +my @leafcert = cert( + issuer => \@ca, + purpose => 'server', + subject => { CN => 'server.local' }, + subjectAltNames => [ + [ DNS => 'bar.server.local' ], + [ DNS => 'www*.server.local'], + [ DNS => '*.www.server.local'], + [ DNS => 'foo.server.local' ], + [ DNS => 'server.local' ], + ] +); +out('server-unusual-wildcard.pem', pem(@leafcert)); + +@leafcert = cert( + issuer => \@ca, + purpose => 'server', + subject => { CN => 'server.local' }, + subjectAltNames => [ + [ DNS => 'bar.server.local' ], + [ DNS => '*.www.server.local'], + [ DNS => 'foo.server.local' ], + [ DNS => 'server.local' ], + ] +); +out('server-common-wildcard.pem', pem(@leafcert)); + +# alternative CA - OLD_ROOT +my @caO = cert( + CA => 1, + subject => { CN => 'OLD_ROOT' } +); +out('caO.pem', pem(crt => $caO[0])); +out('caO.key', pem(key => $caO[1])); + +# alternative ROOT CA, signed by OLD_ROOT, same key as other ROOT CA +my @caX = cert( + issuer => \@caO, + CA => 1, + subject => { CN => 'ROOT' }, + key => $ca[1], +); +out('caX.pem', pem(crt => $caX[0])); +out('caX.key', pem(key => $caX[1])); + +# subCA below ROOT +my @subcaR = cert( + issuer => \@ca, + CA => 1, + subject => { CN => 'SubCA.of.ROOT' } +); +out('subcaR.pem', pem(crt => $subcaR[0])); +out('subcaR.key', pem(key => $subcaR[1])); +out('chainSX.pem', pem($subcaR[0]), pem($caX[0])); + +@leafcert = cert( + issuer => \@subcaR, + purpose => 'server', + subject => { CN => 'server.subca.local' }, + subjectAltNames => [ + [ DNS => 'server.subca.local' ], + ] +); +out('server-subca.pem', pem(@leafcert)); +out('server-subca-chainSX.pem', pem(@leafcert, $subcaR[0], $caX[0])); +out('server-subca-chainS.pem', pem(@leafcert, $subcaR[0])); + + +sub cert { CERT_create(not_after => 10*365*86400+time(), @_) } +sub pem { + my @default = qw(crt key); + my %m = (key => \&PEM_key2string, crt => \&PEM_cert2string); + my $result = ''; + while (my $f = shift(@_)) { + my $v; + if ($f =~m{^(key|crt)$}) { + $v = shift(@_); + } else { + $v = $f; + $f = shift(@default) || 'crt'; + } + $f = $m{$f} || die "wrong key $f"; + $result .= $f->($v); + } + return $result; +} + +sub out { + my $file = shift; + open(my $fh,'>',"$file") or die "failed to create $file: $!"; + print $fh @_ +} diff --git a/regress/lib/libssl/verify/verify.c b/regress/lib/libssl/verify/verify.c new file mode 100644 index 00000000000..8784396a792 --- /dev/null +++ b/regress/lib/libssl/verify/verify.c @@ -0,0 +1,373 @@ +/* $OpenBSD: verify.c,v 1.1.1.1 2021/08/30 17:27:45 tb Exp $ */ +/* + * Copyright (c) 2021 Theo Buehler + * + * 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. + */ + +/* Based on https://github.com/noxxi/libressl-tests */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct peer_config { + const char *name; + int server; + const char *cert; + const char *key; + const char *ca_file; +}; + +struct ssl_wildcard_test_data { + const char *description; + struct peer_config client_config; + struct peer_config server_config; + long verify_result; +}; + +static const struct ssl_wildcard_test_data ssl_wildcard_tests[] = { + { + .description = "unusual wildcard cert, no CA given to client", + .client_config = { + .name = "client", + .server = 0, + .cert = NULL, + .ca_file = NULL, + }, + .server_config = { + .name = "server", + .server = 1, + .cert = "server-unusual-wildcard.pem", + .key = "server-unusual-wildcard.pem", + }, + /* OpenSSL returns X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE */ + .verify_result = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + }, + + { + .description = "unusual wildcard cert, CA given to client", + .client_config = { + .name = "client", + .server = 0, + .cert = NULL, + .ca_file = "caR.pem", + }, + .server_config = { + .name = "server", + .server = 1, + .cert = "server-unusual-wildcard.pem", + .key = "server-unusual-wildcard.pem", + }, + .verify_result = X509_V_OK, + }, + + { + .description = "common wildcard cert, no CA given to client", + .client_config = { + .name = "client", + .server = 0, + .cert = NULL, + .ca_file = NULL, + }, + .server_config = { + .name = "server", + .server = 1, + .cert = "server-common-wildcard.pem", + .key = "server-common-wildcard.pem", + }, + /* OpenSSL returns X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE */ + .verify_result = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + }, + + { + .description = "common wildcard cert, CA given to client", + .client_config = { + .name = "client", + .server = 0, + .cert = NULL, + .ca_file = "caR.pem", + }, + .server_config = { + .name = "server", + .server = 1, + .cert = "server-common-wildcard.pem", + .key = "server-common-wildcard.pem", + }, + .verify_result = X509_V_OK, + }, + + { + .description = "server sends all chain certificates", + .client_config = { + .name = "client", + .server = 0, + .cert = NULL, + .ca_file = "caR.pem", + }, + .server_config = { + .name = "server", + .server = 1, + .cert = "server-subca-chainS.pem", + .key = "server-subca-chainS.pem", + .ca_file = "subcaR.pem" + }, + .verify_result = X509_V_OK, + }, +}; + +static const size_t N_SSL_WILDCARD_TESTS = + sizeof(ssl_wildcard_tests) / sizeof(ssl_wildcard_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 (config->server) { + if (!SSL_CTX_use_certificate_file(ctx, config->cert, + SSL_FILETYPE_PEM)) { + fprintf(stderr, "use_certificate_file(%s) failed\n", + config->name); + goto err; + } + if (config->key != NULL && !SSL_CTX_use_PrivateKey_file(ctx, + config->key, SSL_FILETYPE_PEM)) { + fprintf(stderr, "use_PrivateKey_file(%s) failed\n", + config->name); + goto err; + } + } + + if (config->ca_file != NULL) { + if (!SSL_CTX_load_verify_locations(ctx, config->ca_file, NULL)) { + fprintf(stderr, "load_verify_locations(%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/SSL_shutdown 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); + + /* Ignore erroneous error - see SSL_shutdown(3)... */ + if (func == SSL_shutdown && ssl_err == SSL_ERROR_SYSCALL) + return 1; + + 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; +} + +/* + * 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 + * until WANT_READ is returned or it succeeds, then it's the other function's + * turn to make progress. Succeeds if SSL_connect() and SSL_accept() return 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 (!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; + } + + if (client_ret != 1 || server_ret != 1) { + fprintf(stderr, "%s: failed\n", __func__); + return 0; + } + + return 1; +} + +static int +shutdown_peers(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 (!push_data_to_peer(client_ssl, &client_ret, SSL_shutdown, + "client shutdown", description)) + return 0; + + if (!push_data_to_peer(server_ssl, &server_ret, SSL_shutdown, + "server shutdown", description)) + return 0; + } + + if (client_ret != 1 || server_ret != 1) { + fprintf(stderr, "%s: failed\n", __func__); + return 0; + } + + return 1; +} + +static int +test_ssl_wildcards(const struct ssl_wildcard_test_data *test) +{ + SSL_CTX *client_ctx = NULL, *server_ctx = NULL; + SSL *client_ssl = NULL, *server_ssl = NULL; + long verify_result; + 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; + + verify_result = SSL_get_verify_result(client_ssl); + + if (test->verify_result == verify_result) { + failed = 0; + fprintf(stderr, "%s: ok\n", test->description); + } else + fprintf(stderr, "%s: verify_result: want %ld, got %ld\n", + test->description, test->verify_result, verify_result); + + if (!shutdown_peers(client_ssl, server_ssl, test->description)) + goto err; + + 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; + + for (i = 0; i < N_SSL_WILDCARD_TESTS; i++) + failed |= test_ssl_wildcards(&ssl_wildcard_tests[i]); + + if (failed == 0) + printf("PASS %s\n", __FILE__); + + return failed; +} -- 2.20.1