Hi, another co-worker of mine has found an other regress in the LibreSSL legacy verifier. I took his diff and made a test for our regression framework.
The legacy verifier seems not to check the certificate if no root CA was given. The following test creates an expired certificate and tries to verify it. In one case it found the expected error in another, it does not. OK? bye, Jan Index: lib/libcrypto/Makefile =================================================================== RCS file: /cvs/src/regress/lib/libcrypto/Makefile,v retrieving revision 1.41 diff -u -p -r1.41 Makefile --- lib/libcrypto/Makefile 26 Dec 2020 00:48:56 -0000 1.41 +++ lib/libcrypto/Makefile 24 Feb 2021 05:29:51 -0000 @@ -23,6 +23,7 @@ SUBDIR += ecdsa SUBDIR += engine SUBDIR += evp SUBDIR += exp +SUBDIR += expcert SUBDIR += free SUBDIR += gcm128 SUBDIR += gost Index: lib/libcrypto/expcert/Makefile =================================================================== RCS file: lib/libcrypto/expcert/Makefile diff -N lib/libcrypto/expcert/Makefile --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lib/libcrypto/expcert/Makefile 24 Feb 2021 05:39:38 -0000 @@ -0,0 +1,29 @@ +# $OpenBSD$ + +LDFLAGS += -lcrypto + +PROG = expcrt + +PKG_REQUIRE != pkg_info -e 'p5-IO-Socket-SSL-*' +.if empty (PKG_REQUIRE) +regress: + @echo "missing package p5-IO-Socket-SSL" + @echo SKIPPED +.endif + +REGRESS_TARGETS = test-chain-with-root-CA +REGRESS_TARGETS += test-chain-without-root-CA +REGRESS_SETUP_ONCE = create-certs + +REGRESS_EXPECTED_FAILURES = test-chain-without-root-CA + +create-certs: create-certs.pl ${PROG} + perl ${.CURDIR}/create-certs.pl + +test-chain-with-root-CA: + ./expcrt -e 10 -r + +test-chain-without-root-CA: + ./expcrt -e 10 + +.include <bsd.regress.mk> Index: lib/libcrypto/expcert/create-certs.pl =================================================================== RCS file: lib/libcrypto/expcert/create-certs.pl diff -N lib/libcrypto/expcert/create-certs.pl --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lib/libcrypto/expcert/create-certs.pl 24 Feb 2021 05:27:46 -0000 @@ -0,0 +1,46 @@ +#!/usr/bin/perl + +# Copyright (c) 2021 Anton Borowka <ab...@genua.de> +# +# 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. + +use strict; +use warnings; + +use IO::Socket::SSL::Utils; + +my %certs; + +@{$certs{root}}{qw/cert key/} = CERT_create( + CA => 1, + not_after => time() + 315360000, + subject => { commonName => 'Root CA' }, +); + +@{$certs{intermediate}}{qw/cert key/} = CERT_create( + CA => 1, + issuer => [@{$certs{root}}{qw/cert key/}], + not_after => time() + 315360000, + subject => { commonName => 'Intermediate CA' }, +); + +@{$certs{expired}}{qw/cert key/} = CERT_create( + issuer => [@{$certs{intermediate}}{qw/cert key/}], + not_before => time() - 7200, + not_after => time() - 3600, + subject => { commonName => 'Expired' }, +); + +for (sort keys %certs) { + PEM_cert2file($certs{$_}{cert}, "$_.crt"); +} Index: lib/libcrypto/expcert/expcrt.c =================================================================== RCS file: lib/libcrypto/expcert/expcrt.c diff -N lib/libcrypto/expcert/expcrt.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lib/libcrypto/expcert/expcrt.c 24 Feb 2021 05:27:46 -0000 @@ -0,0 +1,218 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2021 Jan Klemkow <j.klem...@wemelug.de> + * Copyright (c) 2021 Anton Borowka <ab...@genua.de> + * + * 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 <err.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/err.h> +#include <openssl/pem.h> +#include <openssl/x509_vfy.h> + +int error_found = 0; +int expected_error = 0; + +static int +x509verify_error_handler(int ok, X509_STORE_CTX *ctx) +{ + char name[BUFSIZ] = { 0 }; + + if (ctx->current_cert) { + X509_NAME_oneline(X509_get_subject_name(ctx->current_cert), + name, sizeof name); + } + + if (ok == 0) + warnx("cert: %s error: %d %s", name, ctx->error, + X509_verify_cert_error_string(ctx->error)); + + if (ctx->error == expected_error) + error_found = 1; + + return 1; +} + +X509_STORE * +store_init(const char *path) +{ + X509_STORE *store; + X509_LOOKUP *lookup; + + /* init store object */ + if ((store = X509_STORE_new()) == NULL) + return NULL; + + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if (X509_LOOKUP_load_file(lookup, path, X509_FILETYPE_PEM) == 0) { + X509_STORE_free(store); + return NULL; + } + + X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN); + X509_STORE_set_verify_cb(store, x509verify_error_handler); + + ERR_clear_error(); + return store; +} + +int +store_verify(X509_STORE *store, X509 *cert, STACK_OF(X509) *chain) +{ + X509_STORE_CTX *csc; + int status = 0; + + /* prepare ctx: */ + if ((csc = X509_STORE_CTX_new()) == NULL) { + warnx("X509_STORE_CTX_new failed"); + return 1; + } + + if (X509_STORE_CTX_init(csc, store, cert, chain) == 0) { + X509_STORE_CTX_free(csc); + warnx("X509_STORE_CTX_init failed"); + return 1; + } + + X509_STORE_CTX_set_flags(csc, X509_V_FLAG_TRUSTED_FIRST); +#ifdef X509_V_FLAG_LEGACY_VERIFY + X509_STORE_CTX_set_flags(csc, X509_V_FLAG_LEGACY_VERIFY); +#endif + /* verify */ + status = X509_verify_cert(csc); + + if (error_found != 1) { + warnx("expected error %d not found", expected_error); + return 1; + } + + return 0; +} + +X509 * +cert_init(const char *path) +{ + FILE *fh; + X509 *x509; + + if ((fh = fopen(path, "r")) == NULL) + err(1, "fopen: %s", path); + + x509 = PEM_read_X509(fh, NULL, NULL, NULL); + + if (fclose(fh) == EOF) + err(1, "fclose: %s", path); + + return x509; +} + +STACK_OF(X509) * +chain_init(const char *path) +{ + FILE *fh; + STACK_OF(X509_INFO) *sk; + STACK_OF(X509) *st; + X509_INFO *xi; + + if (path == NULL) + return NULL; + + if ((fh = fopen(path, "r")) == NULL) + err(1, "fopen: %s", path); + + if ((sk = PEM_X509_INFO_read(fh, NULL, NULL, NULL)) == NULL) + err(1, "error reading chain info"); + + if (fclose(fh) == EOF) + err(1, "fclose: %s", path); + + if ((st = sk_X509_new_null()) == NULL) { + sk_X509_INFO_pop_free(sk, X509_INFO_free); + err(1, "reading chain memory error"); + } + + while (sk_X509_INFO_num(sk)) { + xi = sk_X509_INFO_shift(sk); + if (xi->x509 != NULL) { + sk_X509_push(st, xi->x509); + xi->x509=NULL; + } + X509_INFO_free(xi); + } + + sk_X509_INFO_free(sk); + + if (sk_X509_num(st) == 0) + printf("no chain certificates loaded\n"); + + return st; +} + +void +usage(void) +{ + err(1, "usage: expcrt [-r] [-e error]"); +} + +int +main(int argc, char *argv[]) +{ + X509_STORE *store; + X509 *cert; + STACK_OF(X509) *chain; + char *path_store = NULL; + char *path_chain = NULL; + const char *errstr; + int root_flag = 0; + int ch; + + while ((ch = getopt(argc, argv, "e:r")) != -1) { + switch (ch) { + case 'e': + expected_error = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + errx(1, "%s: %s", errstr, optarg); + break; + case 'r': + root_flag = 1; + break; + default: + usage(); + } + } + + if (root_flag == 1) { + /* + * with root CA + */ + path_store = strdup("root.crt"); + path_chain = strdup("intermediate.crt"); + } else { + /* + * without root CA + */ + path_store = strdup("intermediate.crt"); + } + + store = store_init(path_store); + chain = chain_init(path_chain); + cert = cert_init("expired.crt"); + + return store_verify(store, cert, chain); +}