1 /* $OpenBSD: expirecallback.c,v 1.2 2022/10/17 18:44:36 jsing 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 *error, int *error_depth, 116 int mode) 117 { 118 STACK_OF(X509) *roots = NULL, *bundle = NULL; 119 X509_STORE_CTX *xsc = NULL; 120 X509_STORE *store = NULL; 121 X509 *leaf = NULL; 122 int use_dir; 123 int ret; 124 125 *chains = 0; 126 *error = 0; 127 *error_depth = 0; 128 129 use_dir = (mode == MODE_MODERN_VFY_DIR); 130 131 if (!use_dir && !certs_from_file(roots_file, &roots)) 132 errx(1, "failed to load roots from '%s'", roots_file); 133 if (!certs_from_file(bundle_file, &bundle)) 134 errx(1, "failed to load bundle from '%s'", bundle_file); 135 if (sk_X509_num(bundle) < 1) 136 errx(1, "not enough certs in bundle"); 137 leaf = sk_X509_shift(bundle); 138 139 if ((xsc = X509_STORE_CTX_new()) == NULL) 140 errx(1, "X509_STORE_CTX"); 141 if (use_dir && (store = X509_STORE_new()) == NULL) 142 errx(1, "X509_STORE"); 143 if (!X509_STORE_CTX_init(xsc, store, leaf, bundle)) { 144 ERR_print_errors_fp(stderr); 145 errx(1, "failed to init store context"); 146 } 147 148 if (use_dir) { 149 if (!X509_STORE_load_locations(store, NULL, roots_dir)) 150 errx(1, "failed to set by_dir directory of %s", roots_dir); 151 } 152 if (mode == MODE_LEGACY_VFY) 153 X509_STORE_CTX_set_flags(xsc, X509_V_FLAG_LEGACY_VERIFY); 154 else 155 X509_VERIFY_PARAM_clear_flags(X509_STORE_CTX_get0_param(xsc), 156 X509_V_FLAG_LEGACY_VERIFY); 157 158 if (verbose) 159 X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb); 160 if (!use_dir) 161 X509_STORE_CTX_set0_trusted_stack(xsc, roots); 162 163 ret = X509_verify_cert(xsc); 164 165 *error = X509_STORE_CTX_get_error(xsc); 166 *error_depth = X509_STORE_CTX_get_error_depth(xsc); 167 168 if (ret == 1) { 169 *chains = 1; /* XXX */ 170 goto done; 171 } 172 173 if (*error == 0) 174 errx(1, "Error unset on failure!\n"); 175 176 fprintf(stderr, "failed to verify at %d: %s\n", 177 *error_depth, X509_verify_cert_error_string(*error)); 178 179 done: 180 sk_X509_pop_free(roots, X509_free); 181 sk_X509_pop_free(bundle, X509_free); 182 X509_STORE_free(store); 183 X509_STORE_CTX_free(xsc); 184 X509_free(leaf); 185 } 186 187 struct verify_cert_test { 188 const char *id; 189 int want_chains; 190 int want_error; 191 int want_error_depth; 192 int want_legacy_error; 193 int want_legacy_error_depth; 194 int failing; 195 }; 196 197 struct verify_cert_test verify_cert_tests[] = { 198 { 199 .id = "2a", 200 .want_chains = 1, 201 .want_error = 0, 202 .want_error_depth = 0, 203 .want_legacy_error = 0, 204 .want_legacy_error_depth = 0, 205 }, 206 { 207 .id = "8a", 208 .want_chains = 1, 209 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 210 .want_error_depth = 0, 211 .want_legacy_error = X509_V_ERR_CERT_HAS_EXPIRED, 212 .want_legacy_error_depth = 0, 213 }, 214 { 215 .id = "9a", 216 .want_chains = 1, 217 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 218 .want_error_depth = 0, 219 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 220 .want_legacy_error_depth = 0, 221 .failing = 1, 222 }, 223 }; 224 225 #define N_VERIFY_CERT_TESTS \ 226 (sizeof(verify_cert_tests) / sizeof(*verify_cert_tests)) 227 228 static int 229 verify_cert_test(const char *certs_path, int mode) 230 { 231 char *roots_file, *bundle_file, *roots_dir; 232 struct verify_cert_test *vct; 233 int chains, error, error_depth; 234 int failed = 0; 235 size_t i; 236 237 for (i = 0; i < N_VERIFY_CERT_TESTS; i++) { 238 vct = &verify_cert_tests[i]; 239 240 if (asprintf(&roots_file, "%s/%s/roots.pem", certs_path, 241 vct->id) == -1) 242 errx(1, "asprintf"); 243 if (asprintf(&bundle_file, "%s/%s/bundle.pem", certs_path, 244 vct->id) == -1) 245 errx(1, "asprintf"); 246 if (asprintf(&roots_dir, "./%s/roots", vct->id) == -1) 247 errx(1, "asprintf"); 248 249 fprintf(stderr, "== Test %zu (%s)\n", i, vct->id); 250 verify_cert(roots_dir, roots_file, bundle_file, &chains, &error, 251 &error_depth, mode); 252 253 if ((mode == MODE_VERIFY && chains == vct->want_chains) || 254 (chains == 0 && vct->want_chains == 0) || 255 (chains == 1 && vct->want_chains > 0)) { 256 fprintf(stderr, "INFO: Succeeded with %d chains%s\n", 257 chains, vct->failing ? " (legacy failure)" : ""); 258 if (mode == MODE_LEGACY_VFY && vct->failing) 259 failed |= 1; 260 } else { 261 fprintf(stderr, "FAIL: Failed with %d chains%s\n", 262 chains, vct->failing ? " (legacy failure)" : ""); 263 if (!vct->failing) 264 failed |= 1; 265 } 266 267 if (mode == MODE_LEGACY_VFY) { 268 if (error != vct->want_legacy_error) { 269 fprintf(stderr, "FAIL: Got legacy error %d, " 270 "want %d\n", error, vct->want_legacy_error); 271 failed |= 1; 272 } 273 if (error_depth != vct->want_legacy_error_depth) { 274 fprintf(stderr, "FAIL: Got legacy error depth " 275 "%d, want %d\n", error_depth, 276 vct->want_legacy_error_depth); 277 failed |= 1; 278 } 279 } else if (mode == MODE_MODERN_VFY || mode == MODE_MODERN_VFY_DIR) { 280 if (error != vct->want_error) { 281 fprintf(stderr, "FAIL: Got error %d, want %d\n", 282 error, vct->want_error); 283 failed |= 1; 284 } 285 if (error_depth != vct->want_error_depth) { 286 fprintf(stderr, "FAIL: Got error depth %d, want" 287 " %d\n", error_depth, vct->want_error_depth); 288 failed |= 1; 289 } 290 } 291 292 fprintf(stderr, "\n"); 293 294 free(roots_file); 295 free(bundle_file); 296 free(roots_dir); 297 } 298 299 return failed; 300 } 301 302 int 303 main(int argc, char **argv) 304 { 305 int failed = 0; 306 307 if (argc != 2) { 308 fprintf(stderr, "usage: %s <certs_path>\n", argv[0]); 309 exit(1); 310 } 311 312 fprintf(stderr, "\n\nTesting legacy x509_vfy\n"); 313 failed |= verify_cert_test(argv[1], MODE_LEGACY_VFY); 314 fprintf(stderr, "\n\nTesting modern x509_vfy\n"); 315 failed |= verify_cert_test(argv[1], MODE_MODERN_VFY); 316 fprintf(stderr, "\n\nTesting modern x509_vfy by_dir\n"); 317 failed |= verify_cert_test(argv[1], MODE_MODERN_VFY_DIR); 318 319 return (failed); 320 } 321