1 /* 2 * Copyright 2015-2024 The OpenSSL Project Authors. All Rights Reserved. 3 * Copyright (c) 2013-2014 Timo Teräs <timo.teras@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__) || \ 16 (defined(__VMS) && defined(__DECC) && __CRTL_VER >= 80300000) 17 # include <unistd.h> 18 # include <stdio.h> 19 # include <limits.h> 20 # include <errno.h> 21 # include <string.h> 22 # include <ctype.h> 23 # include <sys/stat.h> 24 25 /* 26 * Make sure that the processing of symbol names is treated the same as when 27 * libcrypto is built. This is done automatically for public headers (see 28 * include/openssl/__DECC_INCLUDE_PROLOGUE.H and __DECC_INCLUDE_EPILOGUE.H), 29 * but not for internal headers. 30 */ 31 # ifdef __VMS 32 # pragma names save 33 # pragma names as_is,shortened 34 # endif 35 36 # include "internal/o_dir.h" 37 38 # ifdef __VMS 39 # pragma names restore 40 # endif 41 42 # include <openssl/evp.h> 43 # include <openssl/pem.h> 44 # include <openssl/x509.h> 45 46 # ifndef PATH_MAX 47 # define PATH_MAX 4096 48 # endif 49 # define MAX_COLLISIONS 256 50 51 # if defined(OPENSSL_SYS_VXWORKS) 52 /* 53 * VxWorks has no symbolic links 54 */ 55 56 # define lstat(path, buf) stat(path, buf) 57 58 int symlink(const char *target, const char *linkpath) 59 { 60 errno = ENOSYS; 61 return -1; 62 } 63 64 ssize_t readlink(const char *pathname, char *buf, size_t bufsiz) 65 { 66 errno = ENOSYS; 67 return -1; 68 } 69 # endif 70 71 typedef struct hentry_st { 72 struct hentry_st *next; 73 char *filename; 74 unsigned short old_id; 75 unsigned char need_symlink; 76 unsigned char digest[EVP_MAX_MD_SIZE]; 77 } HENTRY; 78 79 typedef struct bucket_st { 80 struct bucket_st *next; 81 HENTRY *first_entry, *last_entry; 82 unsigned int hash; 83 unsigned short type; 84 unsigned short num_needed; 85 } BUCKET; 86 87 enum Type { 88 /* Keep in sync with |suffixes|, below. */ 89 TYPE_CERT=0, TYPE_CRL=1 90 }; 91 92 enum Hash { 93 HASH_OLD, HASH_NEW, HASH_BOTH 94 }; 95 96 97 static int evpmdsize; 98 static const EVP_MD *evpmd; 99 static int remove_links = 1; 100 static int verbose = 0; 101 static BUCKET *hash_table[257]; 102 103 static const char *suffixes[] = { "", "r" }; 104 static const char *extensions[] = { "pem", "crt", "cer", "crl" }; 105 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 /* 119 * Process an entry; return number of errors. 120 */ 121 static int add_entry(enum Type type, unsigned int hash, const char *filename, 122 const unsigned char *digest, int need_symlink, 123 unsigned short old_id) 124 { 125 static BUCKET nilbucket; 126 static HENTRY nilhentry; 127 BUCKET *bp; 128 HENTRY *ep, *found = NULL; 129 unsigned int ndx = (type + hash) % OSSL_NELEM(hash_table); 130 131 for (bp = hash_table[ndx]; bp; bp = bp->next) 132 if (bp->type == type && bp->hash == hash) 133 break; 134 if (bp == NULL) { 135 bp = app_malloc(sizeof(*bp), "hash bucket"); 136 *bp = nilbucket; 137 bp->next = hash_table[ndx]; 138 bp->type = type; 139 bp->hash = hash; 140 hash_table[ndx] = bp; 141 } 142 143 for (ep = bp->first_entry; ep; ep = ep->next) { 144 if (digest && memcmp(digest, ep->digest, (size_t)evpmdsize) == 0) { 145 BIO_printf(bio_err, 146 "%s: warning: skipping duplicate %s in %s\n", 147 opt_getprog(), 148 type == TYPE_CERT ? "certificate" : "CRL", filename); 149 return 0; 150 } 151 if (strcmp(filename, ep->filename) == 0) { 152 found = ep; 153 if (digest == NULL) 154 break; 155 } 156 } 157 ep = found; 158 if (ep == NULL) { 159 if (bp->num_needed >= MAX_COLLISIONS) { 160 BIO_printf(bio_err, 161 "%s: error: hash table overflow for %s\n", 162 opt_getprog(), filename); 163 return 1; 164 } 165 ep = app_malloc(sizeof(*ep), "collision bucket"); 166 *ep = nilhentry; 167 ep->old_id = ~0; 168 ep->filename = OPENSSL_strdup(filename); 169 if (ep->filename == NULL) { 170 OPENSSL_free(ep); 171 ep = NULL; 172 BIO_printf(bio_err, "out of memory\n"); 173 return 1; 174 } 175 if (bp->last_entry) 176 bp->last_entry->next = ep; 177 if (bp->first_entry == NULL) 178 bp->first_entry = ep; 179 bp->last_entry = ep; 180 } 181 182 if (old_id < ep->old_id) 183 ep->old_id = old_id; 184 if (need_symlink && !ep->need_symlink) { 185 ep->need_symlink = 1; 186 bp->num_needed++; 187 memcpy(ep->digest, digest, (size_t)evpmdsize); 188 } 189 return 0; 190 } 191 192 /* 193 * Check if a symlink goes to the right spot; return 0 if okay. 194 * This can be -1 if bad filename, or an error count. 195 */ 196 static int handle_symlink(const char *filename, const char *fullpath) 197 { 198 unsigned int hash = 0; 199 int i, type, id; 200 unsigned char ch; 201 char linktarget[PATH_MAX], *endptr; 202 ossl_ssize_t n; 203 204 for (i = 0; i < 8; i++) { 205 ch = filename[i]; 206 if (!isxdigit(ch)) 207 return -1; 208 hash <<= 4; 209 hash += OPENSSL_hexchar2int(ch); 210 } 211 if (filename[i++] != '.') 212 return -1; 213 for (type = OSSL_NELEM(suffixes) - 1; type > 0; type--) 214 if (OPENSSL_strncasecmp(&filename[i], 215 suffixes[type], strlen(suffixes[type])) == 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 = 301 X509_NAME_hash_ex(name, 302 app_get0_libctx(), app_get0_propq(), &ok); 303 304 if (ok) { 305 errs += add_entry(type, hash_value, filename, digest, 1, ~0); 306 } else { 307 BIO_printf(bio_err, "%s: error calculating SHA1 hash value\n", 308 opt_getprog()); 309 errs++; 310 } 311 } 312 if ((h == HASH_OLD) || (h == HASH_BOTH)) 313 errs += add_entry(type, X509_NAME_hash_old(name), 314 filename, digest, 1, ~0); 315 } 316 317 end: 318 sk_X509_INFO_pop_free(inf, X509_INFO_free); 319 return errs; 320 } 321 322 static void str_free(char *s) 323 { 324 OPENSSL_free(s); 325 } 326 327 static int ends_with_dirsep(const char *path) 328 { 329 if (*path != '\0') 330 path += strlen(path) - 1; 331 # if defined __VMS 332 if (*path == ']' || *path == '>' || *path == ':') 333 return 1; 334 # elif defined _WIN32 335 if (*path == '\\') 336 return 1; 337 # endif 338 return *path == '/'; 339 } 340 341 static int sk_strcmp(const char * const *a, const char * const *b) 342 { 343 return strcmp(*a, *b); 344 } 345 346 /* 347 * Process a directory; return number of errors found. 348 */ 349 static int do_dir(const char *dirname, enum Hash h) 350 { 351 BUCKET *bp, *nextbp; 352 HENTRY *ep, *nextep; 353 OPENSSL_DIR_CTX *d = NULL; 354 struct stat st; 355 unsigned char idmask[MAX_COLLISIONS / 8]; 356 int n, numfiles, nextid, dirlen, buflen, errs = 0; 357 size_t i, fname_max_len = 20; /* maximum length of "%08x.r%d" */ 358 const char *pathsep = ""; 359 const char *filename; 360 char *buf = NULL, *copy = NULL; 361 STACK_OF(OPENSSL_STRING) *files = NULL; 362 363 if (app_access(dirname, W_OK) < 0) { 364 BIO_printf(bio_err, "Skipping %s, can't write\n", dirname); 365 return 1; 366 } 367 dirlen = strlen(dirname); 368 if (dirlen != 0 && !ends_with_dirsep(dirname)) { 369 pathsep = "/"; 370 dirlen++; 371 } 372 373 if (verbose) 374 BIO_printf(bio_out, "Doing %s\n", dirname); 375 376 if ((files = sk_OPENSSL_STRING_new(sk_strcmp)) == NULL) { 377 BIO_printf(bio_err, "Skipping %s, out of memory\n", dirname); 378 errs = 1; 379 goto err; 380 } 381 while ((filename = OPENSSL_DIR_read(&d, dirname)) != NULL) { 382 size_t fname_len = strlen(filename); 383 384 if ((copy = OPENSSL_strdup(filename)) == NULL 385 || sk_OPENSSL_STRING_push(files, copy) == 0) { 386 OPENSSL_free(copy); 387 OPENSSL_DIR_end(&d); 388 BIO_puts(bio_err, "out of memory\n"); 389 errs = 1; 390 goto err; 391 } 392 if (fname_len > fname_max_len) 393 fname_max_len = fname_len; 394 } 395 OPENSSL_DIR_end(&d); 396 sk_OPENSSL_STRING_sort(files); 397 398 buflen = dirlen + fname_max_len + 1; 399 buf = app_malloc(buflen, "filename buffer"); 400 401 numfiles = sk_OPENSSL_STRING_num(files); 402 for (n = 0; n < numfiles; ++n) { 403 filename = sk_OPENSSL_STRING_value(files, n); 404 if (BIO_snprintf(buf, buflen, "%s%s%s", 405 dirname, pathsep, filename) >= 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, OPT_OLD, OPT_N, OPT_VERBOSE, 489 OPT_PROV_ENUM 490 } OPTION_CHOICE; 491 492 const OPTIONS rehash_options[] = { 493 {OPT_HELP_STR, 1, '-', "Usage: %s [options] [directory...]\n"}, 494 495 OPT_SECTION("General"), 496 {"help", OPT_HELP, '-', "Display this summary"}, 497 {"h", OPT_HELP, '-', "Display this summary"}, 498 {"compat", OPT_COMPAT, '-', "Create both new- and old-style hash links"}, 499 {"old", OPT_OLD, '-', "Use old-style hash to generate links"}, 500 {"n", OPT_N, '-', "Do not remove existing links"}, 501 502 OPT_SECTION("Output"), 503 {"v", OPT_VERBOSE, '-', "Verbose output"}, 504 505 OPT_PROV_OPTIONS, 506 507 OPT_PARAMETERS(), 508 {"directory", 0, 0, "One or more directories to process (optional)"}, 509 {NULL} 510 }; 511 512 513 int rehash_main(int argc, char **argv) 514 { 515 const char *env, *prog; 516 char *e, *m; 517 int errs = 0; 518 OPTION_CHOICE o; 519 enum Hash h = HASH_NEW; 520 521 prog = opt_init(argc, argv, rehash_options); 522 while ((o = opt_next()) != OPT_EOF) { 523 switch (o) { 524 case OPT_EOF: 525 case OPT_ERR: 526 BIO_printf(bio_err, "%s: Use -help for summary.\n", prog); 527 goto end; 528 case OPT_HELP: 529 opt_help(rehash_options); 530 goto end; 531 case OPT_COMPAT: 532 h = HASH_BOTH; 533 break; 534 case OPT_OLD: 535 h = HASH_OLD; 536 break; 537 case OPT_N: 538 remove_links = 0; 539 break; 540 case OPT_VERBOSE: 541 verbose = 1; 542 break; 543 case OPT_PROV_CASES: 544 if (!opt_provider(o)) 545 goto end; 546 break; 547 } 548 } 549 550 /* Optional arguments are directories to scan. */ 551 argc = opt_num_rest(); 552 argv = opt_rest(); 553 554 evpmd = EVP_sha1(); 555 evpmdsize = EVP_MD_get_size(evpmd); 556 557 if (evpmdsize <= 0 || evpmdsize > EVP_MAX_MD_SIZE) 558 goto end; 559 560 if (*argv != NULL) { 561 while (*argv != NULL) 562 errs += do_dir(*argv++, h); 563 } else if ((env = getenv(X509_get_default_cert_dir_env())) != NULL) { 564 char lsc[2] = { LIST_SEPARATOR_CHAR, '\0' }; 565 m = OPENSSL_strdup(env); 566 if (m == NULL) { 567 BIO_puts(bio_err, "out of memory\n"); 568 errs = 1; 569 goto end; 570 } 571 for (e = strtok(m, lsc); e != NULL; e = strtok(NULL, lsc)) 572 errs += do_dir(e, h); 573 OPENSSL_free(m); 574 } else { 575 errs += do_dir(X509_get_default_cert_dir(), h); 576 } 577 578 end: 579 return errs; 580 } 581 582 #else 583 const OPTIONS rehash_options[] = { 584 {NULL} 585 }; 586 587 int rehash_main(int argc, char **argv) 588 { 589 BIO_printf(bio_err, "Not available; use c_rehash script\n"); 590 return 1; 591 } 592 593 #endif /* defined(OPENSSL_SYS_UNIX) || defined(__APPLE__) */ 594