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