1 /* 2 * Copyright 2015-2024 The OpenSSL Project Authors. All Rights Reserved. 3 * Copyright (c) 2013-2014 Timo Ters <timo.teras (at) gmail.com> 4 * 5 * Licensed under the Apache License 2.0 (the "License"). You may not use 6 * this file except in compliance with the License. You can obtain a copy 7 * in the file LICENSE in the source distribution or at 8 * https://www.openssl.org/source/license.html 9 */ 10 11 #include "internal/e_os.h" /* LIST_SEPARATOR_CHAR */ 12 #include "apps.h" 13 #include "progs.h" 14 15 #if defined(OPENSSL_SYS_UNIX) || defined(__APPLE__) || (defined(__VMS) && defined(__DECC) && __CRTL_VER >= 80300000) 16 #include <unistd.h> 17 #include <stdio.h> 18 #include <limits.h> 19 #include <errno.h> 20 #include <string.h> 21 #include <ctype.h> 22 #include <sys/stat.h> 23 24 /* 25 * Make sure that the processing of symbol names is treated the same as when 26 * libcrypto is built. This is done automatically for public headers (see 27 * include/openssl/__DECC_INCLUDE_PROLOGUE.H and __DECC_INCLUDE_EPILOGUE.H), 28 * but not for internal headers. 29 */ 30 #ifdef __VMS 31 #pragma names save 32 #pragma names as_is, shortened 33 #endif 34 35 #include "internal/o_dir.h" 36 37 #ifdef __VMS 38 #pragma names restore 39 #endif 40 41 #include <openssl/evp.h> 42 #include <openssl/pem.h> 43 #include <openssl/x509.h> 44 45 #ifndef PATH_MAX 46 #define PATH_MAX 4096 47 #endif 48 #define MAX_COLLISIONS 256 49 50 #if defined(OPENSSL_SYS_VXWORKS) 51 /* 52 * VxWorks has no symbolic links 53 */ 54 55 #define lstat(path, buf) stat(path, buf) 56 57 int symlink(const char *target, const char *linkpath) 58 { 59 errno = ENOSYS; 60 return -1; 61 } 62 63 ssize_t readlink(const char *pathname, char *buf, size_t bufsiz) 64 { 65 errno = ENOSYS; 66 return -1; 67 } 68 #endif 69 70 typedef struct hentry_st { 71 struct hentry_st *next; 72 char *filename; 73 unsigned short old_id; 74 unsigned char need_symlink; 75 unsigned char digest[EVP_MAX_MD_SIZE]; 76 } HENTRY; 77 78 typedef struct bucket_st { 79 struct bucket_st *next; 80 HENTRY *first_entry, *last_entry; 81 unsigned int hash; 82 unsigned short type; 83 unsigned short num_needed; 84 } BUCKET; 85 86 enum Type { 87 /* Keep in sync with |suffixes|, below. */ 88 TYPE_CERT = 0, 89 TYPE_CRL = 1 90 }; 91 92 enum Hash { 93 HASH_OLD, 94 HASH_NEW, 95 HASH_BOTH 96 }; 97 98 static int evpmdsize; 99 static const EVP_MD *evpmd; 100 static int remove_links = 1; 101 static int verbose = 0; 102 static BUCKET *hash_table[257]; 103 104 static const char *suffixes[] = { "", "r" }; 105 static const char *extensions[] = { "pem", "crt", "cer", "crl" }; 106 107 static void bit_set(unsigned char *set, unsigned int bit) 108 { 109 set[bit >> 3] |= 1 << (bit & 0x7); 110 } 111 112 static int bit_isset(unsigned char *set, unsigned int bit) 113 { 114 return set[bit >> 3] & (1 << (bit & 0x7)); 115 } 116 117 /* 118 * Process an entry; return number of errors. 119 */ 120 static int add_entry(enum Type type, unsigned int hash, const char *filename, 121 const unsigned char *digest, int need_symlink, 122 unsigned short old_id) 123 { 124 static BUCKET nilbucket; 125 static HENTRY nilhentry; 126 BUCKET *bp; 127 HENTRY *ep, *found = NULL; 128 unsigned int ndx = (type + hash) % OSSL_NELEM(hash_table); 129 130 for (bp = hash_table[ndx]; bp; bp = bp->next) 131 if (bp->type == type && bp->hash == hash) 132 break; 133 if (bp == NULL) { 134 bp = app_malloc(sizeof(*bp), "hash bucket"); 135 *bp = nilbucket; 136 bp->next = hash_table[ndx]; 137 bp->type = type; 138 bp->hash = hash; 139 hash_table[ndx] = bp; 140 } 141 142 for (ep = bp->first_entry; ep; ep = ep->next) { 143 if (digest && memcmp(digest, ep->digest, (size_t)evpmdsize) == 0) { 144 BIO_printf(bio_err, 145 "%s: warning: skipping duplicate %s in %s\n", 146 opt_getprog(), 147 type == TYPE_CERT ? "certificate" : "CRL", filename); 148 return 0; 149 } 150 if (strcmp(filename, ep->filename) == 0) { 151 found = ep; 152 if (digest == NULL) 153 break; 154 } 155 } 156 ep = found; 157 if (ep == NULL) { 158 if (bp->num_needed >= MAX_COLLISIONS) { 159 BIO_printf(bio_err, 160 "%s: error: hash table overflow for %s\n", 161 opt_getprog(), filename); 162 return 1; 163 } 164 ep = app_malloc(sizeof(*ep), "collision bucket"); 165 *ep = nilhentry; 166 ep->old_id = ~0; 167 ep->filename = OPENSSL_strdup(filename); 168 if (ep->filename == NULL) { 169 OPENSSL_free(ep); 170 ep = NULL; 171 BIO_printf(bio_err, "out of memory\n"); 172 return 1; 173 } 174 if (bp->last_entry) 175 bp->last_entry->next = ep; 176 if (bp->first_entry == NULL) 177 bp->first_entry = ep; 178 bp->last_entry = ep; 179 } 180 181 if (old_id < ep->old_id) 182 ep->old_id = old_id; 183 if (need_symlink && !ep->need_symlink) { 184 ep->need_symlink = 1; 185 bp->num_needed++; 186 memcpy(ep->digest, digest, (size_t)evpmdsize); 187 } 188 return 0; 189 } 190 191 /* 192 * Check if a symlink goes to the right spot; return 0 if okay. 193 * This can be -1 if bad filename, or an error count. 194 */ 195 static int handle_symlink(const char *filename, const char *fullpath) 196 { 197 unsigned int hash = 0; 198 int i, type, id; 199 unsigned char ch; 200 char linktarget[PATH_MAX], *endptr; 201 ossl_ssize_t n; 202 203 for (i = 0; i < 8; i++) { 204 ch = filename[i]; 205 if (!isxdigit(ch)) 206 return -1; 207 hash <<= 4; 208 hash += OPENSSL_hexchar2int(ch); 209 } 210 if (filename[i++] != '.') 211 return -1; 212 for (type = OSSL_NELEM(suffixes) - 1; type > 0; type--) 213 if (OPENSSL_strncasecmp(&filename[i], 214 suffixes[type], strlen(suffixes[type])) 215 == 0) 216 break; 217 218 i += strlen(suffixes[type]); 219 220 id = strtoul(&filename[i], &endptr, 10); 221 if (*endptr != '\0') 222 return -1; 223 224 n = readlink(fullpath, linktarget, sizeof(linktarget)); 225 if (n < 0 || n >= (int)sizeof(linktarget)) 226 return -1; 227 linktarget[n] = 0; 228 229 return add_entry(type, hash, linktarget, NULL, 0, id); 230 } 231 232 /* 233 * process a file, return number of errors. 234 */ 235 static int do_file(const char *filename, const char *fullpath, enum Hash h) 236 { 237 STACK_OF(X509_INFO) *inf = NULL; 238 X509_INFO *x; 239 const X509_NAME *name = NULL; 240 BIO *b; 241 const char *ext; 242 unsigned char digest[EVP_MAX_MD_SIZE]; 243 int type, errs = 0; 244 size_t i; 245 246 /* Does it end with a recognized extension? */ 247 if ((ext = strrchr(filename, '.')) == NULL) 248 goto end; 249 for (i = 0; i < OSSL_NELEM(extensions); i++) { 250 if (OPENSSL_strcasecmp(extensions[i], ext + 1) == 0) 251 break; 252 } 253 if (i >= OSSL_NELEM(extensions)) 254 goto end; 255 256 /* Does it have X.509 data in it? */ 257 if ((b = BIO_new_file(fullpath, "r")) == NULL) { 258 BIO_printf(bio_err, "%s: error: skipping %s, cannot open file\n", 259 opt_getprog(), filename); 260 errs++; 261 goto end; 262 } 263 inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL); 264 BIO_free(b); 265 if (inf == NULL) 266 goto end; 267 268 if (sk_X509_INFO_num(inf) != 1) { 269 BIO_printf(bio_err, 270 "%s: warning: skipping %s, " 271 "it does not contain exactly one certificate or CRL\n", 272 opt_getprog(), filename); 273 /* This is not an error. */ 274 goto end; 275 } 276 x = sk_X509_INFO_value(inf, 0); 277 if (x->x509 != NULL) { 278 type = TYPE_CERT; 279 name = X509_get_subject_name(x->x509); 280 if (!X509_digest(x->x509, evpmd, digest, NULL)) { 281 BIO_printf(bio_err, "out of memory\n"); 282 ++errs; 283 goto end; 284 } 285 } else if (x->crl != NULL) { 286 type = TYPE_CRL; 287 name = X509_CRL_get_issuer(x->crl); 288 if (!X509_CRL_digest(x->crl, evpmd, digest, NULL)) { 289 BIO_printf(bio_err, "out of memory\n"); 290 ++errs; 291 goto end; 292 } 293 } else { 294 ++errs; 295 goto end; 296 } 297 if (name != NULL) { 298 if (h == HASH_NEW || h == HASH_BOTH) { 299 int ok; 300 unsigned long hash_value = X509_NAME_hash_ex(name, 301 app_get0_libctx(), app_get0_propq(), &ok); 302 303 if (ok) { 304 errs += add_entry(type, hash_value, filename, digest, 1, ~0); 305 } else { 306 BIO_printf(bio_err, "%s: error calculating SHA1 hash value\n", 307 opt_getprog()); 308 errs++; 309 } 310 } 311 if ((h == HASH_OLD) || (h == HASH_BOTH)) 312 errs += add_entry(type, X509_NAME_hash_old(name), 313 filename, digest, 1, ~0); 314 } 315 316 end: 317 sk_X509_INFO_pop_free(inf, X509_INFO_free); 318 return errs; 319 } 320 321 static void str_free(char *s) 322 { 323 OPENSSL_free(s); 324 } 325 326 static int ends_with_dirsep(const char *path) 327 { 328 if (*path != '\0') 329 path += strlen(path) - 1; 330 #if defined __VMS 331 if (*path == ']' || *path == '>' || *path == ':') 332 return 1; 333 #elif defined _WIN32 334 if (*path == '\\') 335 return 1; 336 #endif 337 return *path == '/'; 338 } 339 340 static int sk_strcmp(const char *const *a, const char *const *b) 341 { 342 return strcmp(*a, *b); 343 } 344 345 /* 346 * Process a directory; return number of errors found. 347 */ 348 static int do_dir(const char *dirname, enum Hash h) 349 { 350 BUCKET *bp, *nextbp; 351 HENTRY *ep, *nextep; 352 OPENSSL_DIR_CTX *d = NULL; 353 struct stat st; 354 unsigned char idmask[MAX_COLLISIONS / 8]; 355 int n, numfiles, nextid, dirlen, buflen, errs = 0; 356 size_t i, fname_max_len = 20; /* maximum length of "%08x.r%d" */ 357 const char *pathsep = ""; 358 const char *filename; 359 char *buf = NULL, *copy = NULL; 360 STACK_OF(OPENSSL_STRING) *files = NULL; 361 362 if (app_access(dirname, W_OK) < 0) { 363 BIO_printf(bio_err, "Skipping %s, can't write\n", dirname); 364 return 1; 365 } 366 dirlen = strlen(dirname); 367 if (dirlen != 0 && !ends_with_dirsep(dirname)) { 368 pathsep = "/"; 369 dirlen++; 370 } 371 372 if (verbose) 373 BIO_printf(bio_out, "Doing %s\n", dirname); 374 375 if ((files = sk_OPENSSL_STRING_new(sk_strcmp)) == NULL) { 376 BIO_printf(bio_err, "Skipping %s, out of memory\n", dirname); 377 errs = 1; 378 goto err; 379 } 380 while ((filename = OPENSSL_DIR_read(&d, dirname)) != NULL) { 381 size_t fname_len = strlen(filename); 382 383 if ((copy = OPENSSL_strdup(filename)) == NULL 384 || sk_OPENSSL_STRING_push(files, copy) == 0) { 385 OPENSSL_free(copy); 386 OPENSSL_DIR_end(&d); 387 BIO_puts(bio_err, "out of memory\n"); 388 errs = 1; 389 goto err; 390 } 391 if (fname_len > fname_max_len) 392 fname_max_len = fname_len; 393 } 394 OPENSSL_DIR_end(&d); 395 sk_OPENSSL_STRING_sort(files); 396 397 buflen = dirlen + fname_max_len + 1; 398 buf = app_malloc(buflen, "filename buffer"); 399 400 numfiles = sk_OPENSSL_STRING_num(files); 401 for (n = 0; n < numfiles; ++n) { 402 filename = sk_OPENSSL_STRING_value(files, n); 403 if (BIO_snprintf(buf, buflen, "%s%s%s", 404 dirname, pathsep, filename) 405 >= buflen) 406 continue; 407 if (lstat(buf, &st) < 0) 408 continue; 409 if (S_ISLNK(st.st_mode) && handle_symlink(filename, buf) == 0) 410 continue; 411 errs += do_file(filename, buf, h); 412 } 413 414 for (i = 0; i < OSSL_NELEM(hash_table); i++) { 415 for (bp = hash_table[i]; bp; bp = nextbp) { 416 nextbp = bp->next; 417 nextid = 0; 418 memset(idmask, 0, (bp->num_needed + 7) / 8); 419 for (ep = bp->first_entry; ep; ep = ep->next) 420 if (ep->old_id < bp->num_needed) 421 bit_set(idmask, ep->old_id); 422 423 for (ep = bp->first_entry; ep; ep = nextep) { 424 nextep = ep->next; 425 if (ep->old_id < bp->num_needed) { 426 /* Link exists, and is used as-is */ 427 BIO_snprintf(buf, buflen, "%08x.%s%d", bp->hash, 428 suffixes[bp->type], ep->old_id); 429 if (verbose) 430 BIO_printf(bio_out, "link %s -> %s\n", 431 ep->filename, buf); 432 } else if (ep->need_symlink) { 433 /* New link needed (it may replace something) */ 434 while (bit_isset(idmask, nextid)) 435 nextid++; 436 437 BIO_snprintf(buf, buflen, "%s%s%08x.%s%d", 438 dirname, pathsep, bp->hash, 439 suffixes[bp->type], nextid); 440 if (verbose) 441 BIO_printf(bio_out, "link %s -> %s\n", 442 ep->filename, &buf[dirlen]); 443 if (unlink(buf) < 0 && errno != ENOENT) { 444 BIO_printf(bio_err, 445 "%s: Can't unlink %s, %s\n", 446 opt_getprog(), buf, strerror(errno)); 447 errs++; 448 } 449 if (symlink(ep->filename, buf) < 0) { 450 BIO_printf(bio_err, 451 "%s: Can't symlink %s, %s\n", 452 opt_getprog(), ep->filename, 453 strerror(errno)); 454 errs++; 455 } 456 bit_set(idmask, nextid); 457 } else if (remove_links) { 458 /* Link to be deleted */ 459 BIO_snprintf(buf, buflen, "%s%s%08x.%s%d", 460 dirname, pathsep, bp->hash, 461 suffixes[bp->type], ep->old_id); 462 if (verbose) 463 BIO_printf(bio_out, "unlink %s\n", 464 &buf[dirlen]); 465 if (unlink(buf) < 0 && errno != ENOENT) { 466 BIO_printf(bio_err, 467 "%s: Can't unlink %s, %s\n", 468 opt_getprog(), buf, strerror(errno)); 469 errs++; 470 } 471 } 472 OPENSSL_free(ep->filename); 473 OPENSSL_free(ep); 474 } 475 OPENSSL_free(bp); 476 } 477 hash_table[i] = NULL; 478 } 479 480 err: 481 sk_OPENSSL_STRING_pop_free(files, str_free); 482 OPENSSL_free(buf); 483 return errs; 484 } 485 486 typedef enum OPTION_choice { 487 OPT_COMMON, 488 OPT_COMPAT, 489 OPT_OLD, 490 OPT_N, 491 OPT_VERBOSE, 492 OPT_PROV_ENUM 493 } OPTION_CHOICE; 494 495 const OPTIONS rehash_options[] = { 496 { OPT_HELP_STR, 1, '-', "Usage: %s [options] [directory...]\n" }, 497 498 OPT_SECTION("General"), 499 { "help", OPT_HELP, '-', "Display this summary" }, 500 { "h", OPT_HELP, '-', "Display this summary" }, 501 { "compat", OPT_COMPAT, '-', "Create both new- and old-style hash links" }, 502 { "old", OPT_OLD, '-', "Use old-style hash to generate links" }, 503 { "n", OPT_N, '-', "Do not remove existing links" }, 504 505 OPT_SECTION("Output"), 506 { "v", OPT_VERBOSE, '-', "Verbose output" }, 507 508 OPT_PROV_OPTIONS, 509 510 OPT_PARAMETERS(), 511 { "directory", 0, 0, "One or more directories to process (optional)" }, 512 { NULL } 513 }; 514 515 int rehash_main(int argc, char **argv) 516 { 517 const char *env, *prog; 518 char *e, *m; 519 int errs = 0; 520 OPTION_CHOICE o; 521 enum Hash h = HASH_NEW; 522 523 prog = opt_init(argc, argv, rehash_options); 524 while ((o = opt_next()) != OPT_EOF) { 525 switch (o) { 526 case OPT_EOF: 527 case OPT_ERR: 528 BIO_printf(bio_err, "%s: Use -help for summary.\n", prog); 529 goto end; 530 case OPT_HELP: 531 opt_help(rehash_options); 532 goto end; 533 case OPT_COMPAT: 534 h = HASH_BOTH; 535 break; 536 case OPT_OLD: 537 h = HASH_OLD; 538 break; 539 case OPT_N: 540 remove_links = 0; 541 break; 542 case OPT_VERBOSE: 543 verbose = 1; 544 break; 545 case OPT_PROV_CASES: 546 if (!opt_provider(o)) 547 goto end; 548 break; 549 } 550 } 551 552 /* Optional arguments are directories to scan. */ 553 argc = opt_num_rest(); 554 argv = opt_rest(); 555 556 evpmd = EVP_sha1(); 557 evpmdsize = EVP_MD_get_size(evpmd); 558 559 if (evpmdsize <= 0 || evpmdsize > EVP_MAX_MD_SIZE) 560 goto end; 561 562 if (*argv != NULL) { 563 while (*argv != NULL) 564 errs += do_dir(*argv++, h); 565 } else if ((env = getenv(X509_get_default_cert_dir_env())) != NULL) { 566 char lsc[2] = { LIST_SEPARATOR_CHAR, '\0' }; 567 m = OPENSSL_strdup(env); 568 if (m == NULL) { 569 BIO_puts(bio_err, "out of memory\n"); 570 errs = 1; 571 goto end; 572 } 573 for (e = strtok(m, lsc); e != NULL; e = strtok(NULL, lsc)) 574 errs += do_dir(e, h); 575 OPENSSL_free(m); 576 } else { 577 errs += do_dir(X509_get_default_cert_dir(), h); 578 } 579 580 end: 581 return errs; 582 } 583 584 #else 585 const OPTIONS rehash_options[] = { 586 { NULL } 587 }; 588 589 int rehash_main(int argc, char **argv) 590 { 591 BIO_printf(bio_err, "Not available; use c_rehash script\n"); 592 return 1; 593 } 594 595 #endif /* defined(OPENSSL_SYS_UNIX) || defined(__APPLE__) */ 596