1*6822f9c8Santon /* $OpenBSD: callback.c,v 1.5 2024/08/23 12:56:26 anton Exp $ */ 28657d445Sbeck /* 38657d445Sbeck * Copyright (c) 2020 Joel Sing <jsing@openbsd.org> 48657d445Sbeck * Copyright (c) 2020-2021 Bob Beck <beck@openbsd.org> 58657d445Sbeck * 68657d445Sbeck * Permission to use, copy, modify, and distribute this software for any 78657d445Sbeck * purpose with or without fee is hereby granted, provided that the above 88657d445Sbeck * copyright notice and this permission notice appear in all copies. 98657d445Sbeck * 108657d445Sbeck * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 118657d445Sbeck * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 128657d445Sbeck * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 138657d445Sbeck * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 148657d445Sbeck * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 158657d445Sbeck * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 168657d445Sbeck * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 178657d445Sbeck */ 188657d445Sbeck 198657d445Sbeck #include <err.h> 208657d445Sbeck #include <string.h> 218657d445Sbeck 228657d445Sbeck #include <openssl/bio.h> 238657d445Sbeck #include <openssl/err.h> 248657d445Sbeck #include <openssl/pem.h> 258657d445Sbeck #include <openssl/x509.h> 268657d445Sbeck #include <openssl/x509v3.h> 27f45080b3Stb 28f45080b3Stb #include "x509_verify.h" 298657d445Sbeck 308657d445Sbeck #define MODE_MODERN_VFY 0 318657d445Sbeck #define MODE_MODERN_VFY_DIR 1 328657d445Sbeck #define MODE_LEGACY_VFY 2 338657d445Sbeck #define MODE_VERIFY 3 348657d445Sbeck 358657d445Sbeck static int verbose = 1; 368657d445Sbeck FILE *output; 378657d445Sbeck 388657d445Sbeck static int 398657d445Sbeck passwd_cb(char *buf, int size, int rwflag, void *u) 408657d445Sbeck { 418657d445Sbeck memset(buf, 0, size); 428657d445Sbeck return (0); 438657d445Sbeck } 448657d445Sbeck 458657d445Sbeck static int 468657d445Sbeck certs_from_file(const char *filename, STACK_OF(X509) **certs) 478657d445Sbeck { 488657d445Sbeck STACK_OF(X509_INFO) *xis = NULL; 498657d445Sbeck STACK_OF(X509) *xs = NULL; 508657d445Sbeck BIO *bio = NULL; 518657d445Sbeck X509 *x; 528657d445Sbeck int i; 538657d445Sbeck 548657d445Sbeck if ((xs = sk_X509_new_null()) == NULL) 558657d445Sbeck errx(1, "failed to create X509 stack"); 568657d445Sbeck if ((bio = BIO_new_file(filename, "r")) == NULL) { 578657d445Sbeck ERR_print_errors_fp(stderr); 588657d445Sbeck errx(1, "failed to create bio"); 598657d445Sbeck } 608657d445Sbeck if ((xis = PEM_X509_INFO_read_bio(bio, NULL, passwd_cb, NULL)) == NULL) 618657d445Sbeck errx(1, "failed to read PEM"); 628657d445Sbeck 638657d445Sbeck for (i = 0; i < sk_X509_INFO_num(xis); i++) { 648657d445Sbeck if ((x = sk_X509_INFO_value(xis, i)->x509) == NULL) 658657d445Sbeck continue; 668657d445Sbeck if (!sk_X509_push(xs, x)) 678657d445Sbeck errx(1, "failed to push X509"); 688657d445Sbeck X509_up_ref(x); 698657d445Sbeck } 708657d445Sbeck 718657d445Sbeck *certs = xs; 728657d445Sbeck xs = NULL; 738657d445Sbeck 748657d445Sbeck sk_X509_INFO_pop_free(xis, X509_INFO_free); 758657d445Sbeck sk_X509_pop_free(xs, X509_free); 768657d445Sbeck BIO_free(bio); 778657d445Sbeck 788657d445Sbeck return 1; 798657d445Sbeck } 808657d445Sbeck 818657d445Sbeck static int 828657d445Sbeck verify_cert_cb(int ok, X509_STORE_CTX *xsc) 838657d445Sbeck { 848657d445Sbeck X509 *current_cert, *issuer_cert; 858657d445Sbeck int verify_err, verify_depth; 868657d445Sbeck 878657d445Sbeck current_cert = X509_STORE_CTX_get_current_cert(xsc); 888657d445Sbeck issuer_cert = X509_STORE_CTX_get0_current_issuer(xsc); 898657d445Sbeck verify_depth = X509_STORE_CTX_get_error_depth(xsc); 908657d445Sbeck verify_err = X509_STORE_CTX_get_error(xsc); 918657d445Sbeck fprintf(output, "depth %d error %d", verify_depth, verify_err); 928657d445Sbeck fprintf(output, " cert: "); 938657d445Sbeck if (current_cert != NULL) { 948657d445Sbeck X509_NAME_print_ex_fp(output, 958657d445Sbeck X509_get_subject_name(current_cert), 0, 968657d445Sbeck XN_FLAG_ONELINE); 978657d445Sbeck } else 988657d445Sbeck fprintf(output, "NULL"); 998657d445Sbeck fprintf(output, " issuer: "); 1008657d445Sbeck if (issuer_cert != NULL) { 1018657d445Sbeck X509_NAME_print_ex_fp(output, 1028657d445Sbeck X509_get_subject_name(issuer_cert), 0, 1038657d445Sbeck XN_FLAG_ONELINE); 1048657d445Sbeck } else 1058657d445Sbeck fprintf(output, "NULL"); 1068657d445Sbeck fprintf(output, "\n"); 1078657d445Sbeck 1088657d445Sbeck return ok; 1098657d445Sbeck } 1108657d445Sbeck 1118657d445Sbeck static void 1128657d445Sbeck verify_cert(const char *roots_dir, const char *roots_file, 1138657d445Sbeck const char *bundle_file, int *chains, int mode) 1148657d445Sbeck { 1158657d445Sbeck STACK_OF(X509) *roots = NULL, *bundle = NULL; 1168657d445Sbeck X509_STORE_CTX *xsc = NULL; 1178657d445Sbeck X509_STORE *store = NULL; 1188657d445Sbeck int verify_err, use_dir; 1198657d445Sbeck X509 *leaf = NULL; 1208657d445Sbeck 1218657d445Sbeck *chains = 0; 1228657d445Sbeck use_dir = (mode == MODE_MODERN_VFY_DIR); 1238657d445Sbeck 1248657d445Sbeck if (!use_dir && !certs_from_file(roots_file, &roots)) 1258657d445Sbeck errx(1, "failed to load roots from '%s'", roots_file); 1268657d445Sbeck if (!certs_from_file(bundle_file, &bundle)) 1278657d445Sbeck errx(1, "failed to load bundle from '%s'", bundle_file); 1288657d445Sbeck if (sk_X509_num(bundle) < 1) 1298657d445Sbeck errx(1, "not enough certs in bundle"); 1308657d445Sbeck leaf = sk_X509_shift(bundle); 1318657d445Sbeck 1328657d445Sbeck if ((xsc = X509_STORE_CTX_new()) == NULL) 1338657d445Sbeck errx(1, "X509_STORE_CTX"); 1348657d445Sbeck if (use_dir && (store = X509_STORE_new()) == NULL) 1358657d445Sbeck errx(1, "X509_STORE"); 1368657d445Sbeck if (!X509_STORE_CTX_init(xsc, store, leaf, bundle)) { 1378657d445Sbeck ERR_print_errors_fp(stderr); 1388657d445Sbeck errx(1, "failed to init store context"); 1398657d445Sbeck } 1408657d445Sbeck if (use_dir) { 1418657d445Sbeck if (!X509_STORE_load_locations(store, NULL, roots_dir)) 1428657d445Sbeck errx(1, "failed to set by_dir directory of %s", roots_dir); 1438657d445Sbeck } 144920585faStb if (mode == MODE_LEGACY_VFY) 145920585faStb X509_STORE_CTX_set_flags(xsc, X509_V_FLAG_LEGACY_VERIFY); 146920585faStb else 147920585faStb X509_VERIFY_PARAM_clear_flags(X509_STORE_CTX_get0_param(xsc), 148920585faStb X509_V_FLAG_LEGACY_VERIFY); 1498657d445Sbeck 1508657d445Sbeck if (verbose) 1518657d445Sbeck X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb); 1528657d445Sbeck if (!use_dir) 1538657d445Sbeck X509_STORE_CTX_set0_trusted_stack(xsc, roots); 1548657d445Sbeck if (X509_verify_cert(xsc) == 1) { 1558657d445Sbeck *chains = 1; /* XXX */ 1568657d445Sbeck goto done; 1578657d445Sbeck } 1588657d445Sbeck 1598657d445Sbeck verify_err = X509_STORE_CTX_get_error(xsc); 1608657d445Sbeck if (verify_err == 0) 161*6822f9c8Santon errx(1, "Error unset on failure!"); 1628657d445Sbeck 1638657d445Sbeck fprintf(stderr, "failed to verify at %d: %s\n", 1648657d445Sbeck X509_STORE_CTX_get_error_depth(xsc), 1658657d445Sbeck X509_verify_cert_error_string(verify_err)); 1668657d445Sbeck 1678657d445Sbeck done: 1688657d445Sbeck sk_X509_pop_free(roots, X509_free); 1698657d445Sbeck sk_X509_pop_free(bundle, X509_free); 1708657d445Sbeck X509_STORE_free(store); 1718657d445Sbeck X509_STORE_CTX_free(xsc); 1728657d445Sbeck X509_free(leaf); 1738657d445Sbeck } 1748657d445Sbeck 1758657d445Sbeck struct verify_cert_test { 1768657d445Sbeck const char *id; 1778657d445Sbeck int want_chains; 1788657d445Sbeck int failing; 1798657d445Sbeck }; 1808657d445Sbeck 1818657d445Sbeck struct verify_cert_test verify_cert_tests[] = { 1828657d445Sbeck { 1838657d445Sbeck .id = "1a", 1848657d445Sbeck .want_chains = 1, 1858657d445Sbeck }, 1868657d445Sbeck { 1878657d445Sbeck .id = "2a", 1888657d445Sbeck .want_chains = 1, 1898657d445Sbeck }, 1908657d445Sbeck { 1918657d445Sbeck .id = "2b", 1928657d445Sbeck .want_chains = 0, 1938657d445Sbeck }, 1948657d445Sbeck { 1958657d445Sbeck .id = "2c", 1968657d445Sbeck .want_chains = 1, 1978657d445Sbeck }, 1988657d445Sbeck { 1998657d445Sbeck .id = "3a", 2008657d445Sbeck .want_chains = 1, 2018657d445Sbeck }, 2028657d445Sbeck { 2038657d445Sbeck .id = "3b", 2048657d445Sbeck .want_chains = 0, 2058657d445Sbeck }, 2068657d445Sbeck { 2078657d445Sbeck .id = "3c", 2088657d445Sbeck .want_chains = 0, 2098657d445Sbeck }, 2108657d445Sbeck { 2118657d445Sbeck .id = "3d", 2128657d445Sbeck .want_chains = 0, 2138657d445Sbeck }, 2148657d445Sbeck { 2158657d445Sbeck .id = "3e", 2168657d445Sbeck .want_chains = 1, 2178657d445Sbeck }, 2188657d445Sbeck { 2198657d445Sbeck .id = "4a", 2208657d445Sbeck .want_chains = 2, 2218657d445Sbeck }, 2228657d445Sbeck { 2238657d445Sbeck .id = "4b", 2248657d445Sbeck .want_chains = 1, 2258657d445Sbeck }, 2268657d445Sbeck { 2278657d445Sbeck .id = "4c", 2288657d445Sbeck .want_chains = 1, 2298657d445Sbeck .failing = 1, 2308657d445Sbeck }, 2318657d445Sbeck { 2328657d445Sbeck .id = "4d", 2338657d445Sbeck .want_chains = 1, 2348657d445Sbeck }, 2358657d445Sbeck { 2368657d445Sbeck .id = "4e", 2378657d445Sbeck .want_chains = 1, 2388657d445Sbeck }, 2398657d445Sbeck { 2408657d445Sbeck .id = "4f", 2418657d445Sbeck .want_chains = 2, 2428657d445Sbeck }, 2438657d445Sbeck { 2448657d445Sbeck .id = "4g", 2458657d445Sbeck .want_chains = 1, 2468657d445Sbeck .failing = 1, 2478657d445Sbeck }, 2488657d445Sbeck { 2498657d445Sbeck .id = "4h", 2508657d445Sbeck .want_chains = 1, 2518657d445Sbeck }, 2528657d445Sbeck { 2538657d445Sbeck .id = "5a", 2548657d445Sbeck .want_chains = 2, 2558657d445Sbeck }, 2568657d445Sbeck { 2578657d445Sbeck .id = "5b", 2588657d445Sbeck .want_chains = 1, 2598657d445Sbeck .failing = 1, 2608657d445Sbeck }, 2618657d445Sbeck { 2628657d445Sbeck .id = "5c", 2638657d445Sbeck .want_chains = 1, 2648657d445Sbeck }, 2658657d445Sbeck { 2668657d445Sbeck .id = "5d", 2678657d445Sbeck .want_chains = 1, 2688657d445Sbeck }, 2698657d445Sbeck { 2708657d445Sbeck .id = "5e", 2718657d445Sbeck .want_chains = 1, 2728657d445Sbeck .failing = 1, 2738657d445Sbeck }, 2748657d445Sbeck { 2758657d445Sbeck .id = "5f", 2768657d445Sbeck .want_chains = 1, 2778657d445Sbeck }, 2788657d445Sbeck { 2798657d445Sbeck .id = "5g", 2808657d445Sbeck .want_chains = 2, 2818657d445Sbeck }, 2828657d445Sbeck { 2838657d445Sbeck .id = "5h", 2848657d445Sbeck .want_chains = 1, 2858657d445Sbeck }, 2868657d445Sbeck { 2878657d445Sbeck .id = "5i", 2888657d445Sbeck .want_chains = 1, 2898657d445Sbeck .failing = 1, 2908657d445Sbeck }, 2918657d445Sbeck { 2928657d445Sbeck .id = "6a", 2938657d445Sbeck .want_chains = 1, 2948657d445Sbeck }, 2958657d445Sbeck { 2968657d445Sbeck .id = "6b", 2978657d445Sbeck .want_chains = 1, 2988657d445Sbeck .failing = 1, 2998657d445Sbeck }, 3008657d445Sbeck { 3018657d445Sbeck .id = "7a", 3028657d445Sbeck .want_chains = 1, 3038657d445Sbeck .failing = 1, 3048657d445Sbeck }, 3058657d445Sbeck { 3068657d445Sbeck .id = "7b", 3078657d445Sbeck .want_chains = 1, 3088657d445Sbeck }, 3098657d445Sbeck { 3108657d445Sbeck .id = "8a", 3118657d445Sbeck .want_chains = 0, 3128657d445Sbeck }, 3138657d445Sbeck { 3148657d445Sbeck .id = "9a", 3158657d445Sbeck .want_chains = 0, 3168657d445Sbeck }, 3178657d445Sbeck { 3188657d445Sbeck .id = "10a", 3198657d445Sbeck .want_chains = 1, 3208657d445Sbeck }, 3218657d445Sbeck { 3228657d445Sbeck .id = "10b", 3238657d445Sbeck .want_chains = 1, 3248657d445Sbeck }, 3258657d445Sbeck { 3268657d445Sbeck .id = "11a", 3278657d445Sbeck .want_chains = 1, 3288657d445Sbeck .failing = 1, 3298657d445Sbeck }, 3308657d445Sbeck { 3318657d445Sbeck .id = "11b", 3328657d445Sbeck .want_chains = 1, 3338657d445Sbeck }, 3348657d445Sbeck { 3358657d445Sbeck .id = "12a", 3368657d445Sbeck .want_chains = 1, 3378657d445Sbeck }, 3388657d445Sbeck { 3398657d445Sbeck .id = "13a", 3408657d445Sbeck .want_chains = 1, 34107499e19Sjsing .failing = 1, 3428657d445Sbeck }, 3438657d445Sbeck }; 3448657d445Sbeck 3458657d445Sbeck #define N_VERIFY_CERT_TESTS \ 3468657d445Sbeck (sizeof(verify_cert_tests) / sizeof(*verify_cert_tests)) 3478657d445Sbeck 3488657d445Sbeck static int 3498657d445Sbeck verify_cert_test(const char *certs_path, int mode) 3508657d445Sbeck { 3518657d445Sbeck char *roots_file, *bundle_file, *roots_dir; 3528657d445Sbeck struct verify_cert_test *vct; 3538657d445Sbeck int failed = 0; 3548657d445Sbeck int chains; 3558657d445Sbeck size_t i; 3568657d445Sbeck 3578657d445Sbeck for (i = 0; i < N_VERIFY_CERT_TESTS; i++) { 3588657d445Sbeck vct = &verify_cert_tests[i]; 3598657d445Sbeck 3608657d445Sbeck if (asprintf(&roots_file, "%s/%s/roots.pem", certs_path, 3618657d445Sbeck vct->id) == -1) 3628657d445Sbeck errx(1, "asprintf"); 3638657d445Sbeck if (asprintf(&bundle_file, "%s/%s/bundle.pem", certs_path, 3648657d445Sbeck vct->id) == -1) 3658657d445Sbeck errx(1, "asprintf"); 3668657d445Sbeck if (asprintf(&roots_dir, "./%s/roots", vct->id) == -1) 3678657d445Sbeck errx(1, "asprintf"); 3688657d445Sbeck 3698657d445Sbeck fprintf(output, "== Test %zu (%s)\n", i, vct->id); 3708657d445Sbeck fprintf(output, "== Legacy:\n"); 3718657d445Sbeck mode = MODE_LEGACY_VFY; 3728657d445Sbeck verify_cert(roots_dir, roots_file, bundle_file, &chains, mode); 3738657d445Sbeck if ((mode == MODE_VERIFY && chains == vct->want_chains) || 3748657d445Sbeck (chains == 0 && vct->want_chains == 0) || 3758657d445Sbeck (chains == 1 && vct->want_chains > 0)) { 3768657d445Sbeck fprintf(output, "INFO: Succeeded with %d chains%s\n", 3778657d445Sbeck chains, vct->failing ? " (legacy failure)" : ""); 3788657d445Sbeck if (mode == MODE_LEGACY_VFY && vct->failing) 3798657d445Sbeck failed |= 1; 3808657d445Sbeck } else { 3818657d445Sbeck fprintf(output, "FAIL: Failed with %d chains%s\n", 3828657d445Sbeck chains, vct->failing ? " (legacy failure)" : ""); 3838657d445Sbeck if (!vct->failing) 3848657d445Sbeck failed |= 1; 3858657d445Sbeck } 3868657d445Sbeck fprintf(output, "\n"); 3878657d445Sbeck fprintf(output, "== Modern:\n"); 3888657d445Sbeck mode = MODE_MODERN_VFY; 3898657d445Sbeck verify_cert(roots_dir, roots_file, bundle_file, &chains, mode); 3908657d445Sbeck if ((mode == MODE_VERIFY && chains == vct->want_chains) || 3918657d445Sbeck (chains == 0 && vct->want_chains == 0) || 3928657d445Sbeck (chains == 1 && vct->want_chains > 0)) { 3938657d445Sbeck fprintf(output, "INFO: Succeeded with %d chains%s\n", 3948657d445Sbeck chains, vct->failing ? " (legacy failure)" : ""); 3958657d445Sbeck if (mode == MODE_LEGACY_VFY && vct->failing) 3968657d445Sbeck failed |= 1; 3978657d445Sbeck } else { 3988657d445Sbeck fprintf(output, "FAIL: Failed with %d chains%s\n", 3998657d445Sbeck chains, vct->failing ? " (legacy failure)" : ""); 4008657d445Sbeck if (!vct->failing) 4018657d445Sbeck failed |= 1; 4028657d445Sbeck } 4038657d445Sbeck fprintf(output, "\n"); 4048657d445Sbeck 4058657d445Sbeck free(roots_file); 4068657d445Sbeck free(bundle_file); 4078657d445Sbeck free(roots_dir); 4088657d445Sbeck } 4098657d445Sbeck 4108657d445Sbeck return failed; 4118657d445Sbeck } 4128657d445Sbeck 4138657d445Sbeck int 4148657d445Sbeck main(int argc, char **argv) 4158657d445Sbeck { 4168657d445Sbeck int failed = 0; 4178657d445Sbeck 4188657d445Sbeck if (argc != 2) { 4198657d445Sbeck fprintf(stderr, "usage: %s <certs_path>\n", argv[0]); 4208657d445Sbeck exit(1); 4218657d445Sbeck } 4228657d445Sbeck 4238657d445Sbeck output = fopen("callback.out", "w+"); 4248657d445Sbeck 4258657d445Sbeck fprintf(stderr, "\n\nTesting legacy and modern X509_vfy\n"); 4268657d445Sbeck failed |= verify_cert_test(argv[1], MODE_LEGACY_VFY); 4278657d445Sbeck return (failed); 4288657d445Sbeck } 429