1*6822f9c8Santon /* $OpenBSD: expirecallback.c,v 1.4 2024/08/23 12:56:26 anton Exp $ */ 2b8644936Sbeck /* 3b8644936Sbeck * Copyright (c) 2020 Joel Sing <jsing@openbsd.org> 4b8644936Sbeck * Copyright (c) 2020-2021 Bob Beck <beck@openbsd.org> 5b8644936Sbeck * 6b8644936Sbeck * Permission to use, copy, modify, and distribute this software for any 7b8644936Sbeck * purpose with or without fee is hereby granted, provided that the above 8b8644936Sbeck * copyright notice and this permission notice appear in all copies. 9b8644936Sbeck * 10b8644936Sbeck * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11b8644936Sbeck * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12b8644936Sbeck * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13b8644936Sbeck * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14b8644936Sbeck * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15b8644936Sbeck * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16b8644936Sbeck * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17b8644936Sbeck */ 18b8644936Sbeck 19b8644936Sbeck #include <err.h> 20b8644936Sbeck #include <string.h> 21b8644936Sbeck 22b8644936Sbeck #include <openssl/bio.h> 23b8644936Sbeck #include <openssl/err.h> 24b8644936Sbeck #include <openssl/pem.h> 25b8644936Sbeck #include <openssl/x509.h> 26b8644936Sbeck #include <openssl/x509v3.h> 27f45080b3Stb 28f45080b3Stb #include "x509_verify.h" 29b8644936Sbeck 30b8644936Sbeck #define MODE_MODERN_VFY 0 31b8644936Sbeck #define MODE_MODERN_VFY_DIR 1 32b8644936Sbeck #define MODE_LEGACY_VFY 2 33b8644936Sbeck #define MODE_VERIFY 3 34b8644936Sbeck 35b8644936Sbeck static int verbose = 1; 36b8644936Sbeck 37b8644936Sbeck static int 38b8644936Sbeck passwd_cb(char *buf, int size, int rwflag, void *u) 39b8644936Sbeck { 40b8644936Sbeck memset(buf, 0, size); 41b8644936Sbeck return (0); 42b8644936Sbeck } 43b8644936Sbeck 44b8644936Sbeck static int 45b8644936Sbeck certs_from_file(const char *filename, STACK_OF(X509) **certs) 46b8644936Sbeck { 47b8644936Sbeck STACK_OF(X509_INFO) *xis = NULL; 48b8644936Sbeck STACK_OF(X509) *xs = NULL; 49b8644936Sbeck BIO *bio = NULL; 50b8644936Sbeck X509 *x; 51b8644936Sbeck int i; 52b8644936Sbeck 53b8644936Sbeck if ((xs = sk_X509_new_null()) == NULL) 54b8644936Sbeck errx(1, "failed to create X509 stack"); 55b8644936Sbeck if ((bio = BIO_new_file(filename, "r")) == NULL) { 56b8644936Sbeck ERR_print_errors_fp(stderr); 57b8644936Sbeck errx(1, "failed to create bio"); 58b8644936Sbeck } 59b8644936Sbeck if ((xis = PEM_X509_INFO_read_bio(bio, NULL, passwd_cb, NULL)) == NULL) 60b8644936Sbeck errx(1, "failed to read PEM"); 61b8644936Sbeck 62b8644936Sbeck for (i = 0; i < sk_X509_INFO_num(xis); i++) { 63b8644936Sbeck if ((x = sk_X509_INFO_value(xis, i)->x509) == NULL) 64b8644936Sbeck continue; 65b8644936Sbeck if (!sk_X509_push(xs, x)) 66b8644936Sbeck errx(1, "failed to push X509"); 67b8644936Sbeck X509_up_ref(x); 68b8644936Sbeck } 69b8644936Sbeck 70b8644936Sbeck *certs = xs; 71b8644936Sbeck xs = NULL; 72b8644936Sbeck 73b8644936Sbeck sk_X509_INFO_pop_free(xis, X509_INFO_free); 74b8644936Sbeck sk_X509_pop_free(xs, X509_free); 75b8644936Sbeck BIO_free(bio); 76b8644936Sbeck 77b8644936Sbeck return 1; 78b8644936Sbeck } 79b8644936Sbeck 80b8644936Sbeck static int 81b8644936Sbeck verify_cert_cb(int ok, X509_STORE_CTX *xsc) 82b8644936Sbeck { 83b8644936Sbeck X509 *current_cert; 84b8644936Sbeck int verify_err; 85b8644936Sbeck 86b8644936Sbeck current_cert = X509_STORE_CTX_get_current_cert(xsc); 87b8644936Sbeck if (current_cert != NULL) { 88b8644936Sbeck X509_NAME_print_ex_fp(stderr, 89b8644936Sbeck X509_get_subject_name(current_cert), 0, 90b8644936Sbeck XN_FLAG_ONELINE); 91b8644936Sbeck fprintf(stderr, "\n"); 92b8644936Sbeck } 93b8644936Sbeck 94b8644936Sbeck verify_err = X509_STORE_CTX_get_error(xsc); 95b8644936Sbeck if (verify_err != X509_V_OK) { 96b8644936Sbeck if (verify_err == X509_V_ERR_CERT_HAS_EXPIRED) 97b8644936Sbeck fprintf(stderr, "IGNORING "); 98b8644936Sbeck fprintf(stderr, "verify error at depth %d: %s\n", 99b8644936Sbeck X509_STORE_CTX_get_error_depth(xsc), 100b8644936Sbeck X509_verify_cert_error_string(verify_err)); 101b8644936Sbeck } 102b8644936Sbeck 103b8644936Sbeck /* 104b8644936Sbeck * Ignore expired certs, in the way people are told to do it 105b8644936Sbeck * by OpenSSL 106b8644936Sbeck */ 107b8644936Sbeck 108b8644936Sbeck if (verify_err == X509_V_ERR_CERT_HAS_EXPIRED) 109b8644936Sbeck return 1; 110b8644936Sbeck 111b8644936Sbeck return ok; 112b8644936Sbeck } 113b8644936Sbeck 114b8644936Sbeck static void 115b8644936Sbeck verify_cert(const char *roots_dir, const char *roots_file, 116e71b8735Sjsing const char *bundle_file, int *chains, int *error, int *error_depth, 117e71b8735Sjsing int mode) 118b8644936Sbeck { 119b8644936Sbeck STACK_OF(X509) *roots = NULL, *bundle = NULL; 120b8644936Sbeck X509_STORE_CTX *xsc = NULL; 121b8644936Sbeck X509_STORE *store = NULL; 122b8644936Sbeck X509 *leaf = NULL; 123e71b8735Sjsing int use_dir; 124e71b8735Sjsing int ret; 125b8644936Sbeck 126b8644936Sbeck *chains = 0; 127e71b8735Sjsing *error = 0; 128e71b8735Sjsing *error_depth = 0; 129e71b8735Sjsing 130b8644936Sbeck use_dir = (mode == MODE_MODERN_VFY_DIR); 131b8644936Sbeck 132b8644936Sbeck if (!use_dir && !certs_from_file(roots_file, &roots)) 133b8644936Sbeck errx(1, "failed to load roots from '%s'", roots_file); 134b8644936Sbeck if (!certs_from_file(bundle_file, &bundle)) 135b8644936Sbeck errx(1, "failed to load bundle from '%s'", bundle_file); 136b8644936Sbeck if (sk_X509_num(bundle) < 1) 137b8644936Sbeck errx(1, "not enough certs in bundle"); 138b8644936Sbeck leaf = sk_X509_shift(bundle); 139b8644936Sbeck 140b8644936Sbeck if ((xsc = X509_STORE_CTX_new()) == NULL) 141b8644936Sbeck errx(1, "X509_STORE_CTX"); 142b8644936Sbeck if (use_dir && (store = X509_STORE_new()) == NULL) 143b8644936Sbeck errx(1, "X509_STORE"); 144b8644936Sbeck if (!X509_STORE_CTX_init(xsc, store, leaf, bundle)) { 145b8644936Sbeck ERR_print_errors_fp(stderr); 146b8644936Sbeck errx(1, "failed to init store context"); 147b8644936Sbeck } 148b8644936Sbeck 149b8644936Sbeck if (use_dir) { 150b8644936Sbeck if (!X509_STORE_load_locations(store, NULL, roots_dir)) 151b8644936Sbeck errx(1, "failed to set by_dir directory of %s", roots_dir); 152b8644936Sbeck } 153b8644936Sbeck if (mode == MODE_LEGACY_VFY) 154b8644936Sbeck X509_STORE_CTX_set_flags(xsc, X509_V_FLAG_LEGACY_VERIFY); 155b8644936Sbeck else 156b8644936Sbeck X509_VERIFY_PARAM_clear_flags(X509_STORE_CTX_get0_param(xsc), 157b8644936Sbeck X509_V_FLAG_LEGACY_VERIFY); 158b8644936Sbeck 159b8644936Sbeck if (verbose) 160b8644936Sbeck X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb); 161b8644936Sbeck if (!use_dir) 162b8644936Sbeck X509_STORE_CTX_set0_trusted_stack(xsc, roots); 163e71b8735Sjsing 164e71b8735Sjsing ret = X509_verify_cert(xsc); 165e71b8735Sjsing 166e71b8735Sjsing *error = X509_STORE_CTX_get_error(xsc); 167e71b8735Sjsing *error_depth = X509_STORE_CTX_get_error_depth(xsc); 168e71b8735Sjsing 169e71b8735Sjsing if (ret == 1) { 170b8644936Sbeck *chains = 1; /* XXX */ 171b8644936Sbeck goto done; 172b8644936Sbeck } 173b8644936Sbeck 174e71b8735Sjsing if (*error == 0) 175*6822f9c8Santon errx(1, "Error unset on failure!"); 176b8644936Sbeck 177b8644936Sbeck fprintf(stderr, "failed to verify at %d: %s\n", 178e71b8735Sjsing *error_depth, X509_verify_cert_error_string(*error)); 179b8644936Sbeck 180b8644936Sbeck done: 181b8644936Sbeck sk_X509_pop_free(roots, X509_free); 182b8644936Sbeck sk_X509_pop_free(bundle, X509_free); 183b8644936Sbeck X509_STORE_free(store); 184b8644936Sbeck X509_STORE_CTX_free(xsc); 185b8644936Sbeck X509_free(leaf); 186b8644936Sbeck } 187b8644936Sbeck 188b8644936Sbeck struct verify_cert_test { 189b8644936Sbeck const char *id; 190b8644936Sbeck int want_chains; 191e71b8735Sjsing int want_error; 192e71b8735Sjsing int want_error_depth; 193e71b8735Sjsing int want_legacy_error; 194e71b8735Sjsing int want_legacy_error_depth; 195b8644936Sbeck int failing; 196b8644936Sbeck }; 197b8644936Sbeck 198b8644936Sbeck struct verify_cert_test verify_cert_tests[] = { 199b8644936Sbeck { 200b8644936Sbeck .id = "2a", 201b8644936Sbeck .want_chains = 1, 202e71b8735Sjsing .want_error = 0, 203e71b8735Sjsing .want_error_depth = 0, 204e71b8735Sjsing .want_legacy_error = 0, 205e71b8735Sjsing .want_legacy_error_depth = 0, 206b8644936Sbeck }, 207b8644936Sbeck { 208e71b8735Sjsing .id = "8a", 209b8644936Sbeck .want_chains = 1, 210e71b8735Sjsing .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 211e71b8735Sjsing .want_error_depth = 0, 212e71b8735Sjsing .want_legacy_error = X509_V_ERR_CERT_HAS_EXPIRED, 213e71b8735Sjsing .want_legacy_error_depth = 0, 214e71b8735Sjsing }, 215e71b8735Sjsing { 216e71b8735Sjsing .id = "9a", 217e71b8735Sjsing .want_chains = 1, 218e71b8735Sjsing .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 219e71b8735Sjsing .want_error_depth = 0, 220e71b8735Sjsing .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 221e71b8735Sjsing .want_legacy_error_depth = 0, 222b8644936Sbeck .failing = 1, 223b8644936Sbeck }, 224b8644936Sbeck }; 225b8644936Sbeck 226b8644936Sbeck #define N_VERIFY_CERT_TESTS \ 227b8644936Sbeck (sizeof(verify_cert_tests) / sizeof(*verify_cert_tests)) 228b8644936Sbeck 229b8644936Sbeck static int 230b8644936Sbeck verify_cert_test(const char *certs_path, int mode) 231b8644936Sbeck { 232b8644936Sbeck char *roots_file, *bundle_file, *roots_dir; 233b8644936Sbeck struct verify_cert_test *vct; 234e71b8735Sjsing int chains, error, error_depth; 235b8644936Sbeck int failed = 0; 236b8644936Sbeck size_t i; 237b8644936Sbeck 238b8644936Sbeck for (i = 0; i < N_VERIFY_CERT_TESTS; i++) { 239b8644936Sbeck vct = &verify_cert_tests[i]; 240b8644936Sbeck 241b8644936Sbeck if (asprintf(&roots_file, "%s/%s/roots.pem", certs_path, 242b8644936Sbeck vct->id) == -1) 243b8644936Sbeck errx(1, "asprintf"); 244b8644936Sbeck if (asprintf(&bundle_file, "%s/%s/bundle.pem", certs_path, 245b8644936Sbeck vct->id) == -1) 246b8644936Sbeck errx(1, "asprintf"); 247b8644936Sbeck if (asprintf(&roots_dir, "./%s/roots", vct->id) == -1) 248b8644936Sbeck errx(1, "asprintf"); 249b8644936Sbeck 250b8644936Sbeck fprintf(stderr, "== Test %zu (%s)\n", i, vct->id); 251e71b8735Sjsing verify_cert(roots_dir, roots_file, bundle_file, &chains, &error, 252e71b8735Sjsing &error_depth, mode); 253e71b8735Sjsing 254b8644936Sbeck if ((mode == MODE_VERIFY && chains == vct->want_chains) || 255b8644936Sbeck (chains == 0 && vct->want_chains == 0) || 256b8644936Sbeck (chains == 1 && vct->want_chains > 0)) { 257b8644936Sbeck fprintf(stderr, "INFO: Succeeded with %d chains%s\n", 258b8644936Sbeck chains, vct->failing ? " (legacy failure)" : ""); 259b8644936Sbeck if (mode == MODE_LEGACY_VFY && vct->failing) 260b8644936Sbeck failed |= 1; 261b8644936Sbeck } else { 262b8644936Sbeck fprintf(stderr, "FAIL: Failed with %d chains%s\n", 263b8644936Sbeck chains, vct->failing ? " (legacy failure)" : ""); 264b8644936Sbeck if (!vct->failing) 265b8644936Sbeck failed |= 1; 266b8644936Sbeck } 267e71b8735Sjsing 268e71b8735Sjsing if (mode == MODE_LEGACY_VFY) { 269e71b8735Sjsing if (error != vct->want_legacy_error) { 270e71b8735Sjsing fprintf(stderr, "FAIL: Got legacy error %d, " 271e71b8735Sjsing "want %d\n", error, vct->want_legacy_error); 272e71b8735Sjsing failed |= 1; 273e71b8735Sjsing } 274e71b8735Sjsing if (error_depth != vct->want_legacy_error_depth) { 275e71b8735Sjsing fprintf(stderr, "FAIL: Got legacy error depth " 276e71b8735Sjsing "%d, want %d\n", error_depth, 277e71b8735Sjsing vct->want_legacy_error_depth); 278e71b8735Sjsing failed |= 1; 279e71b8735Sjsing } 280e71b8735Sjsing } else if (mode == MODE_MODERN_VFY || mode == MODE_MODERN_VFY_DIR) { 281e71b8735Sjsing if (error != vct->want_error) { 282e71b8735Sjsing fprintf(stderr, "FAIL: Got error %d, want %d\n", 283e71b8735Sjsing error, vct->want_error); 284e71b8735Sjsing failed |= 1; 285e71b8735Sjsing } 286e71b8735Sjsing if (error_depth != vct->want_error_depth) { 287e71b8735Sjsing fprintf(stderr, "FAIL: Got error depth %d, want" 288e71b8735Sjsing " %d\n", error_depth, vct->want_error_depth); 289e71b8735Sjsing failed |= 1; 290e71b8735Sjsing } 291e71b8735Sjsing } 292e71b8735Sjsing 293b8644936Sbeck fprintf(stderr, "\n"); 294b8644936Sbeck 295b8644936Sbeck free(roots_file); 296b8644936Sbeck free(bundle_file); 297b8644936Sbeck free(roots_dir); 298b8644936Sbeck } 299b8644936Sbeck 300b8644936Sbeck return failed; 301b8644936Sbeck } 302b8644936Sbeck 303b8644936Sbeck int 304b8644936Sbeck main(int argc, char **argv) 305b8644936Sbeck { 306b8644936Sbeck int failed = 0; 307b8644936Sbeck 308b8644936Sbeck if (argc != 2) { 309b8644936Sbeck fprintf(stderr, "usage: %s <certs_path>\n", argv[0]); 310b8644936Sbeck exit(1); 311b8644936Sbeck } 312b8644936Sbeck 313b8644936Sbeck fprintf(stderr, "\n\nTesting legacy x509_vfy\n"); 314b8644936Sbeck failed |= verify_cert_test(argv[1], MODE_LEGACY_VFY); 315b8644936Sbeck fprintf(stderr, "\n\nTesting modern x509_vfy\n"); 316b8644936Sbeck failed |= verify_cert_test(argv[1], MODE_MODERN_VFY); 317b8644936Sbeck fprintf(stderr, "\n\nTesting modern x509_vfy by_dir\n"); 318b8644936Sbeck failed |= verify_cert_test(argv[1], MODE_MODERN_VFY_DIR); 319b8644936Sbeck 320b8644936Sbeck return (failed); 321b8644936Sbeck } 322