1 /* $OpenBSD: verify.c,v 1.10 2022/10/17 18:36:52 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/crypto.h> 24 #include <openssl/err.h> 25 #include <openssl/pem.h> 26 #include <openssl/x509.h> 27 #include <openssl/x509v3.h> 28 #include <openssl/x509_verify.h> 29 30 #define MODE_MODERN_VFY 0 31 #define MODE_MODERN_VFY_DIR 1 32 #define MODE_LEGACY_VFY 2 33 #define MODE_VERIFY 3 34 35 static int verbose = 1; 36 37 static int 38 passwd_cb(char *buf, int size, int rwflag, void *u) 39 { 40 memset(buf, 0, size); 41 return (0); 42 } 43 44 static int 45 certs_from_file(const char *filename, STACK_OF(X509) **certs) 46 { 47 STACK_OF(X509_INFO) *xis = NULL; 48 STACK_OF(X509) *xs = NULL; 49 BIO *bio = NULL; 50 X509 *x; 51 int i; 52 53 if ((xs = sk_X509_new_null()) == NULL) 54 errx(1, "failed to create X509 stack"); 55 if ((bio = BIO_new_file(filename, "r")) == NULL) { 56 ERR_print_errors_fp(stderr); 57 errx(1, "failed to create bio"); 58 } 59 if ((xis = PEM_X509_INFO_read_bio(bio, NULL, passwd_cb, NULL)) == NULL) 60 errx(1, "failed to read PEM"); 61 62 for (i = 0; i < sk_X509_INFO_num(xis); i++) { 63 if ((x = sk_X509_INFO_value(xis, i)->x509) == NULL) 64 continue; 65 if (!sk_X509_push(xs, x)) 66 errx(1, "failed to push X509"); 67 X509_up_ref(x); 68 } 69 70 *certs = xs; 71 xs = NULL; 72 73 sk_X509_INFO_pop_free(xis, X509_INFO_free); 74 sk_X509_pop_free(xs, X509_free); 75 BIO_free(bio); 76 77 return 1; 78 } 79 80 static int 81 verify_cert_cb(int ok, X509_STORE_CTX *xsc) 82 { 83 X509 *current_cert; 84 int verify_err; 85 86 current_cert = X509_STORE_CTX_get_current_cert(xsc); 87 if (current_cert != NULL) { 88 X509_NAME_print_ex_fp(stderr, 89 X509_get_subject_name(current_cert), 0, 90 XN_FLAG_ONELINE); 91 fprintf(stderr, "\n"); 92 } 93 94 verify_err = X509_STORE_CTX_get_error(xsc); 95 if (verify_err != X509_V_OK) { 96 fprintf(stderr, "verify error at depth %d: %s\n", 97 X509_STORE_CTX_get_error_depth(xsc), 98 X509_verify_cert_error_string(verify_err)); 99 } 100 101 return ok; 102 } 103 104 static void 105 verify_cert(const char *roots_dir, const char *roots_file, 106 const char *bundle_file, int *chains, int *error, int *error_depth, 107 int mode) 108 { 109 STACK_OF(X509) *roots = NULL, *bundle = NULL; 110 X509_STORE_CTX *xsc = NULL; 111 X509_STORE *store = NULL; 112 X509 *leaf = NULL; 113 int use_dir; 114 int ret; 115 116 *chains = 0; 117 *error = 0; 118 *error_depth = 0; 119 120 use_dir = (mode == MODE_MODERN_VFY_DIR); 121 122 if (!use_dir && !certs_from_file(roots_file, &roots)) 123 errx(1, "failed to load roots from '%s'", roots_file); 124 if (!certs_from_file(bundle_file, &bundle)) 125 errx(1, "failed to load bundle from '%s'", bundle_file); 126 if (sk_X509_num(bundle) < 1) 127 errx(1, "not enough certs in bundle"); 128 leaf = sk_X509_shift(bundle); 129 130 if ((xsc = X509_STORE_CTX_new()) == NULL) 131 errx(1, "X509_STORE_CTX"); 132 if (use_dir && (store = X509_STORE_new()) == NULL) 133 errx(1, "X509_STORE"); 134 if (!X509_STORE_CTX_init(xsc, store, leaf, bundle)) { 135 ERR_print_errors_fp(stderr); 136 errx(1, "failed to init store context"); 137 } 138 if (use_dir) { 139 if (!X509_STORE_load_locations(store, NULL, roots_dir)) 140 errx(1, "failed to set by_dir directory of %s", roots_dir); 141 } 142 if (mode == MODE_LEGACY_VFY) 143 X509_STORE_CTX_set_flags(xsc, X509_V_FLAG_LEGACY_VERIFY); 144 else 145 X509_VERIFY_PARAM_clear_flags(X509_STORE_CTX_get0_param(xsc), 146 X509_V_FLAG_LEGACY_VERIFY); 147 148 if (verbose) 149 X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb); 150 if (!use_dir) 151 X509_STORE_CTX_set0_trusted_stack(xsc, roots); 152 153 ret = X509_verify_cert(xsc); 154 155 *error = X509_STORE_CTX_get_error(xsc); 156 *error_depth = X509_STORE_CTX_get_error_depth(xsc); 157 158 if (ret == 1) { 159 *chains = 1; /* XXX */ 160 goto done; 161 } 162 163 if (*error == 0) 164 errx(1, "Error unset on failure!\n"); 165 166 fprintf(stderr, "failed to verify at %d: %s\n", 167 *error_depth, X509_verify_cert_error_string(*error)); 168 169 done: 170 sk_X509_pop_free(roots, X509_free); 171 sk_X509_pop_free(bundle, X509_free); 172 X509_STORE_free(store); 173 X509_STORE_CTX_free(xsc); 174 X509_free(leaf); 175 } 176 177 static void 178 verify_cert_new(const char *roots_file, const char *bundle_file, int *chains) 179 { 180 STACK_OF(X509) *roots = NULL, *bundle = NULL; 181 X509_STORE_CTX *xsc = NULL; 182 X509 *leaf = NULL; 183 struct x509_verify_ctx *ctx; 184 185 *chains = 0; 186 187 if (!certs_from_file(roots_file, &roots)) 188 errx(1, "failed to load roots from '%s'", roots_file); 189 if (!certs_from_file(bundle_file, &bundle)) 190 errx(1, "failed to load bundle from '%s'", bundle_file); 191 if (sk_X509_num(bundle) < 1) 192 errx(1, "not enough certs in bundle"); 193 leaf = sk_X509_shift(bundle); 194 195 if ((xsc = X509_STORE_CTX_new()) == NULL) 196 errx(1, "X509_STORE_CTX"); 197 if (!X509_STORE_CTX_init(xsc, NULL, leaf, bundle)) { 198 ERR_print_errors_fp(stderr); 199 errx(1, "failed to init store context"); 200 } 201 if (verbose) 202 X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb); 203 204 if ((ctx = x509_verify_ctx_new(roots)) == NULL) 205 errx(1, "failed to create ctx"); 206 if (!x509_verify_ctx_set_intermediates(ctx, bundle)) 207 errx(1, "failed to set intermediates"); 208 209 if ((*chains = x509_verify(ctx, leaf, NULL)) == 0) { 210 fprintf(stderr, "failed to verify at %lu: %s\n", 211 x509_verify_ctx_error_depth(ctx), 212 x509_verify_ctx_error_string(ctx)); 213 } else { 214 int c; 215 216 for (c = 0; verbose && c < *chains; c++) { 217 STACK_OF(X509) *chain; 218 int i; 219 220 fprintf(stderr, "Chain %d\n--------\n", c); 221 chain = x509_verify_ctx_chain(ctx, c); 222 for (i = 0; i < sk_X509_num(chain); i++) { 223 X509 *cert = sk_X509_value(chain, i); 224 X509_NAME_print_ex_fp(stderr, 225 X509_get_subject_name(cert), 0, 226 XN_FLAG_ONELINE); 227 fprintf(stderr, "\n"); 228 } 229 } 230 } 231 sk_X509_pop_free(roots, X509_free); 232 sk_X509_pop_free(bundle, X509_free); 233 X509_free(leaf); 234 X509_STORE_CTX_free(xsc); 235 x509_verify_ctx_free(ctx); 236 } 237 238 struct verify_cert_test { 239 const char *id; 240 int want_chains; 241 int want_error; 242 int want_error_depth; 243 int want_legacy_error; 244 int want_legacy_error_depth; 245 int failing; 246 }; 247 248 struct verify_cert_test verify_cert_tests[] = { 249 { 250 .id = "1a", 251 .want_chains = 1, 252 }, 253 { 254 .id = "2a", 255 .want_chains = 1, 256 }, 257 { 258 .id = "2b", 259 .want_chains = 0, 260 .want_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 261 .want_error_depth = 0, 262 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 263 .want_legacy_error_depth = 0, 264 }, 265 { 266 .id = "2c", 267 .want_chains = 1, 268 }, 269 { 270 .id = "3a", 271 .want_chains = 1, 272 }, 273 { 274 .id = "3b", 275 .want_chains = 0, 276 .want_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 277 .want_error_depth = 2, 278 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 279 .want_legacy_error_depth = 2, 280 }, 281 { 282 .id = "3c", 283 .want_chains = 0, 284 .want_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 285 .want_error_depth = 1, 286 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 287 .want_legacy_error_depth = 1, 288 }, 289 { 290 .id = "3d", 291 .want_chains = 0, 292 .want_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 293 .want_error_depth = 0, 294 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 295 .want_legacy_error_depth = 0, 296 }, 297 { 298 .id = "3e", 299 .want_chains = 1, 300 }, 301 { 302 .id = "4a", 303 .want_chains = 2, 304 }, 305 { 306 .id = "4b", 307 .want_chains = 1, 308 }, 309 { 310 .id = "4c", 311 .want_chains = 1, 312 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 313 .want_legacy_error_depth = 1, 314 .failing = 1, 315 }, 316 { 317 .id = "4d", 318 .want_chains = 1, 319 }, 320 { 321 .id = "4e", 322 .want_chains = 1, 323 }, 324 { 325 .id = "4f", 326 .want_chains = 2, 327 }, 328 { 329 .id = "4g", 330 .want_chains = 1, 331 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 332 .want_legacy_error_depth = 1, 333 .failing = 1, 334 }, 335 { 336 .id = "4h", 337 .want_chains = 1, 338 }, 339 { 340 .id = "5a", 341 .want_chains = 2, 342 }, 343 { 344 .id = "5b", 345 .want_chains = 1, 346 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 347 .want_legacy_error_depth = 2, 348 .failing = 1, 349 }, 350 { 351 .id = "5c", 352 .want_chains = 1, 353 }, 354 { 355 .id = "5d", 356 .want_chains = 1, 357 }, 358 { 359 .id = "5e", 360 .want_chains = 1, 361 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 362 .want_legacy_error_depth = 1, 363 .failing = 1, 364 }, 365 { 366 .id = "5f", 367 .want_chains = 1, 368 }, 369 { 370 .id = "5g", 371 .want_chains = 2, 372 }, 373 { 374 .id = "5h", 375 .want_chains = 1, 376 }, 377 { 378 .id = "5i", 379 .want_chains = 1, 380 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 381 .want_legacy_error_depth = 1, 382 .failing = 1, 383 }, 384 { 385 .id = "6a", 386 .want_chains = 1, 387 }, 388 { 389 .id = "6b", 390 .want_chains = 1, 391 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 392 .want_error_depth = 0, 393 .want_legacy_error = X509_V_ERR_CERT_HAS_EXPIRED, 394 .want_legacy_error_depth = 2, 395 .failing = 1, 396 }, 397 { 398 .id = "7a", 399 .want_chains = 1, 400 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 401 .want_error_depth = 0, 402 .want_legacy_error = X509_V_ERR_CERT_HAS_EXPIRED, 403 .want_legacy_error_depth = 3, 404 .failing = 1, 405 }, 406 { 407 .id = "7b", 408 .want_chains = 1, 409 }, 410 { 411 .id = "8a", 412 .want_chains = 0, 413 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 414 .want_error_depth = 0, 415 .want_legacy_error = X509_V_ERR_CERT_HAS_EXPIRED, 416 .want_legacy_error_depth = 0, 417 }, 418 { 419 .id = "9a", 420 .want_chains = 0, 421 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 422 .want_error_depth = 1, 423 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 424 .want_legacy_error_depth = 0, 425 }, 426 { 427 .id = "10a", 428 .want_chains = 1, 429 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 430 .want_error_depth = 0, 431 }, 432 { 433 .id = "10b", 434 .want_chains = 1, 435 }, 436 { 437 .id = "11a", 438 .want_chains = 1, 439 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 440 .want_error_depth = 0, 441 .want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, 442 .want_legacy_error_depth = 1, 443 .failing = 1, 444 }, 445 { 446 .id = "11b", 447 .want_chains = 1, 448 }, 449 { 450 .id = "12a", 451 .want_chains = 1, 452 }, 453 { 454 .id = "13a", 455 .want_chains = 1, 456 .want_error = X509_V_ERR_CERT_HAS_EXPIRED, 457 .want_error_depth = 0, 458 .want_legacy_error = X509_V_ERR_CERT_HAS_EXPIRED, 459 .want_legacy_error_depth = 2, 460 .failing = 1, 461 }, 462 }; 463 464 #define N_VERIFY_CERT_TESTS \ 465 (sizeof(verify_cert_tests) / sizeof(*verify_cert_tests)) 466 467 static int 468 verify_cert_test(const char *certs_path, int mode) 469 { 470 char *roots_file, *bundle_file, *roots_dir; 471 struct verify_cert_test *vct; 472 int chains, error, error_depth; 473 int failed = 0; 474 size_t i; 475 476 for (i = 0; i < N_VERIFY_CERT_TESTS; i++) { 477 vct = &verify_cert_tests[i]; 478 479 if (asprintf(&roots_file, "%s/%s/roots.pem", certs_path, 480 vct->id) == -1) 481 errx(1, "asprintf"); 482 if (asprintf(&bundle_file, "%s/%s/bundle.pem", certs_path, 483 vct->id) == -1) 484 errx(1, "asprintf"); 485 if (asprintf(&roots_dir, "./%s/roots", vct->id) == -1) 486 errx(1, "asprintf"); 487 488 error = 0; 489 error_depth = 0; 490 491 fprintf(stderr, "== Test %zu (%s)\n", i, vct->id); 492 if (mode == MODE_VERIFY) 493 verify_cert_new(roots_file, bundle_file, &chains); 494 else 495 verify_cert(roots_dir, roots_file, bundle_file, &chains, 496 &error, &error_depth, mode); 497 498 if ((mode == MODE_VERIFY && chains == vct->want_chains) || 499 (chains == 0 && vct->want_chains == 0) || 500 (chains == 1 && vct->want_chains > 0)) { 501 fprintf(stderr, "INFO: Succeeded with %d chains%s\n", 502 chains, vct->failing ? " (legacy failure)" : ""); 503 if (mode == MODE_LEGACY_VFY && vct->failing) 504 failed |= 1; 505 } else { 506 fprintf(stderr, "FAIL: Failed with %d chains%s\n", 507 chains, vct->failing ? " (legacy failure)" : ""); 508 if (!vct->failing) 509 failed |= 1; 510 } 511 512 if (mode == MODE_LEGACY_VFY) { 513 if (error != vct->want_legacy_error) { 514 fprintf(stderr, "FAIL: Got legacy error %d, " 515 "want %d\n", error, vct->want_legacy_error); 516 failed |= 1; 517 } 518 if (error_depth != vct->want_legacy_error_depth) { 519 fprintf(stderr, "FAIL: Got legacy error depth " 520 "%d, want %d\n", error_depth, 521 vct->want_legacy_error_depth); 522 failed |= 1; 523 } 524 } else if (mode == MODE_MODERN_VFY || mode == MODE_MODERN_VFY_DIR) { 525 if (error != vct->want_error) { 526 fprintf(stderr, "FAIL: Got error %d, want %d\n", 527 error, vct->want_error); 528 failed |= 1; 529 } 530 if (error_depth != vct->want_error_depth) { 531 fprintf(stderr, "FAIL: Got error depth %d, want" 532 " %d\n", error_depth, vct->want_error_depth); 533 failed |= 1; 534 } 535 } 536 537 fprintf(stderr, "\n"); 538 539 free(roots_file); 540 free(bundle_file); 541 free(roots_dir); 542 } 543 544 return failed; 545 } 546 547 int 548 main(int argc, char **argv) 549 { 550 int failed = 0; 551 552 if (argc != 2) { 553 fprintf(stderr, "usage: %s <certs_path>\n", argv[0]); 554 exit(1); 555 } 556 557 fprintf(stderr, "\n\nTesting legacy x509_vfy\n"); 558 failed |= verify_cert_test(argv[1], MODE_LEGACY_VFY); 559 fprintf(stderr, "\n\nTesting modern x509_vfy\n"); 560 failed |= verify_cert_test(argv[1], MODE_MODERN_VFY); 561 fprintf(stderr, "\n\nTesting modern x509_vfy by_dir\n"); 562 failed |= verify_cert_test(argv[1], MODE_MODERN_VFY_DIR); 563 fprintf(stderr, "\n\nTesting x509_verify\n"); 564 failed |= verify_cert_test(argv[1], MODE_VERIFY); 565 566 return (failed); 567 } 568