IMPORT openssh-9.8p1
[dragonfly.git] / crypto / libressl / apps / openssl / certhash.c
bloba4417a2b267da02100570a1bc7d439efc33cd938
1 /* $OpenBSD: certhash.c,v 1.19 2021/10/23 08:13:48 tb Exp $ */
2 /*
3 * Copyright (c) 2014, 2015 Joel Sing <jsing@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/types.h>
19 #include <sys/stat.h>
21 #include <errno.h>
22 #include <dirent.h>
23 #include <fcntl.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <unistd.h>
29 #include <openssl/bio.h>
30 #include <openssl/evp.h>
31 #include <openssl/pem.h>
32 #include <openssl/x509.h>
34 #include "apps.h"
36 static struct {
37 int dryrun;
38 int verbose;
39 } certhash_config;
41 static const struct option certhash_options[] = {
43 .name = "n",
44 .desc = "Perform a dry-run - do not make any changes",
45 .type = OPTION_FLAG,
46 .opt.flag = &certhash_config.dryrun,
49 .name = "v",
50 .desc = "Verbose",
51 .type = OPTION_FLAG,
52 .opt.flag = &certhash_config.verbose,
54 { NULL },
57 struct hashinfo {
58 char *filename;
59 char *target;
60 unsigned long hash;
61 unsigned int index;
62 unsigned char fingerprint[EVP_MAX_MD_SIZE];
63 int is_crl;
64 int is_dup;
65 int exists;
66 int changed;
67 struct hashinfo *reference;
68 struct hashinfo *next;
71 static struct hashinfo *
72 hashinfo(const char *filename, unsigned long hash, unsigned char *fingerprint)
74 struct hashinfo *hi;
76 if ((hi = calloc(1, sizeof(*hi))) == NULL)
77 return (NULL);
78 if (filename != NULL) {
79 if ((hi->filename = strdup(filename)) == NULL) {
80 free(hi);
81 return (NULL);
84 hi->hash = hash;
85 if (fingerprint != NULL)
86 memcpy(hi->fingerprint, fingerprint, sizeof(hi->fingerprint));
88 return (hi);
91 static void
92 hashinfo_free(struct hashinfo *hi)
94 if (hi == NULL)
95 return;
97 free(hi->filename);
98 free(hi->target);
99 free(hi);
102 #ifdef DEBUG
103 static void
104 hashinfo_print(struct hashinfo *hi)
106 int i;
108 printf("hashinfo %s %08lx %u %i\n", hi->filename, hi->hash,
109 hi->index, hi->is_crl);
110 for (i = 0; i < (int)EVP_MAX_MD_SIZE; i++) {
111 printf("%02X%c", hi->fingerprint[i],
112 (i + 1 == (int)EVP_MAX_MD_SIZE) ? '\n' : ':');
115 #endif
117 static int
118 hashinfo_compare(const void *a, const void *b)
120 struct hashinfo *hia = *(struct hashinfo **)a;
121 struct hashinfo *hib = *(struct hashinfo **)b;
122 int rv;
124 rv = hia->hash < hib->hash ? -1 : hia->hash > hib->hash;
125 if (rv != 0)
126 return (rv);
127 rv = memcmp(hia->fingerprint, hib->fingerprint,
128 sizeof(hia->fingerprint));
129 if (rv != 0)
130 return (rv);
131 return strcmp(hia->filename, hib->filename);
134 static struct hashinfo *
135 hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
137 struct hashinfo *hi = head;
139 if (hi == NULL)
140 return (entry);
141 while (hi->next != NULL)
142 hi = hi->next;
143 hi->next = entry;
145 return (head);
148 static void
149 hashinfo_chain_free(struct hashinfo *hi)
151 struct hashinfo *next;
153 while (hi != NULL) {
154 next = hi->next;
155 hashinfo_free(hi);
156 hi = next;
160 static size_t
161 hashinfo_chain_length(struct hashinfo *hi)
163 int len = 0;
165 while (hi != NULL) {
166 len++;
167 hi = hi->next;
169 return (len);
172 static int
173 hashinfo_chain_sort(struct hashinfo **head)
175 struct hashinfo **list, *entry;
176 size_t len;
177 int i;
179 if (*head == NULL)
180 return (0);
182 len = hashinfo_chain_length(*head);
183 if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
184 return (-1);
186 for (entry = *head, i = 0; entry != NULL; entry = entry->next, i++)
187 list[i] = entry;
188 qsort(list, len, sizeof(struct hashinfo *), hashinfo_compare);
190 *head = entry = list[0];
191 for (i = 1; i < len; i++) {
192 entry->next = list[i];
193 entry = list[i];
195 entry->next = NULL;
197 free(list);
198 return (0);
201 static char *
202 hashinfo_linkname(struct hashinfo *hi)
204 char *filename;
206 if (asprintf(&filename, "%08lx.%s%u", hi->hash,
207 (hi->is_crl ? "r" : ""), hi->index) == -1)
208 return (NULL);
210 return (filename);
213 static int
214 filename_is_hash(const char *filename)
216 const char *p = filename;
218 while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
219 p++;
220 if (*p++ != '.')
221 return (0);
222 if (*p == 'r') /* CRL format. */
223 p++;
224 while (*p >= '0' && *p <= '9')
225 p++;
226 if (*p != '\0')
227 return (0);
229 return (1);
232 static int
233 filename_is_pem(const char *filename)
235 const char *q, *p = filename;
237 if ((q = strchr(p, '\0')) == NULL)
238 return (0);
239 if ((q - p) < 4)
240 return (0);
241 if (strncmp((q - 4), ".pem", 4) != 0)
242 return (0);
244 return (1);
247 static struct hashinfo *
248 hashinfo_from_linkname(const char *linkname, const char *target)
250 struct hashinfo *hi = NULL;
251 const char *errstr;
252 char *l, *p, *ep;
253 long long val;
255 if ((l = strdup(linkname)) == NULL)
256 goto err;
257 if ((p = strchr(l, '.')) == NULL)
258 goto err;
259 *p++ = '\0';
261 if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
262 goto err;
263 if ((hi->target = strdup(target)) == NULL)
264 goto err;
266 errno = 0;
267 val = strtoll(l, &ep, 16);
268 if (l[0] == '\0' || *ep != '\0')
269 goto err;
270 if (errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN))
271 goto err;
272 if (val < 0 || val > ULONG_MAX)
273 goto err;
274 hi->hash = (unsigned long)val;
276 if (*p == 'r') {
277 hi->is_crl = 1;
278 p++;
281 val = strtonum(p, 0, 0xffffffff, &errstr);
282 if (errstr != NULL)
283 goto err;
285 hi->index = (unsigned int)val;
287 goto done;
289 err:
290 hashinfo_free(hi);
291 hi = NULL;
293 done:
294 free(l);
296 return (hi);
299 static struct hashinfo *
300 certhash_cert(BIO *bio, const char *filename)
302 unsigned char fingerprint[EVP_MAX_MD_SIZE];
303 struct hashinfo *hi = NULL;
304 const EVP_MD *digest;
305 X509 *cert = NULL;
306 unsigned long hash;
307 unsigned int len;
309 if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
310 goto err;
312 hash = X509_subject_name_hash(cert);
314 digest = EVP_sha256();
315 if (X509_digest(cert, digest, fingerprint, &len) != 1) {
316 fprintf(stderr, "out of memory\n");
317 goto err;
320 hi = hashinfo(filename, hash, fingerprint);
322 err:
323 X509_free(cert);
325 return (hi);
328 static struct hashinfo *
329 certhash_crl(BIO *bio, const char *filename)
331 unsigned char fingerprint[EVP_MAX_MD_SIZE];
332 struct hashinfo *hi = NULL;
333 const EVP_MD *digest;
334 X509_CRL *crl = NULL;
335 unsigned long hash;
336 unsigned int len;
338 if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
339 return (NULL);
341 hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
343 digest = EVP_sha256();
344 if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
345 fprintf(stderr, "out of memory\n");
346 goto err;
349 hi = hashinfo(filename, hash, fingerprint);
351 err:
352 X509_CRL_free(crl);
354 return (hi);
357 static int
358 certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
360 struct hashinfo *link = NULL;
362 if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
363 goto err;
365 if ((link->filename = hashinfo_linkname(hi)) == NULL)
366 goto err;
368 link->reference = hi;
369 link->changed = 1;
370 *links = hashinfo_chain(*links, link);
371 hi->reference = link;
373 return (0);
375 err:
376 hashinfo_free(link);
377 return (-1);
380 static void
381 certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
383 struct hashinfo *link;
385 for (link = links; link != NULL; link = link->next) {
386 if (link->is_crl == hi->is_crl &&
387 link->hash == hi->hash &&
388 link->index == hi->index &&
389 link->reference == NULL) {
390 link->reference = hi;
391 if (link->target == NULL ||
392 strcmp(link->target, hi->filename) != 0)
393 link->changed = 1;
394 hi->reference = link;
395 break;
400 static void
401 certhash_index(struct hashinfo *head, const char *name)
403 struct hashinfo *last, *entry;
404 int index = 0;
406 last = NULL;
407 for (entry = head; entry != NULL; entry = entry->next) {
408 if (last != NULL) {
409 if (entry->hash == last->hash) {
410 if (memcmp(entry->fingerprint,
411 last->fingerprint,
412 sizeof(entry->fingerprint)) == 0) {
413 fprintf(stderr, "WARNING: duplicate %s "
414 "in %s (using %s), ignoring...\n",
415 name, entry->filename,
416 last->filename);
417 entry->is_dup = 1;
418 continue;
420 index++;
421 } else {
422 index = 0;
425 entry->index = index;
426 last = entry;
430 static int
431 certhash_merge(struct hashinfo **links, struct hashinfo **certs,
432 struct hashinfo **crls)
434 struct hashinfo *cert, *crl;
436 /* Pass 1 - sort and index entries. */
437 if (hashinfo_chain_sort(certs) == -1)
438 return (-1);
439 if (hashinfo_chain_sort(crls) == -1)
440 return (-1);
441 certhash_index(*certs, "certificate");
442 certhash_index(*crls, "CRL");
444 /* Pass 2 - map to existing links. */
445 for (cert = *certs; cert != NULL; cert = cert->next) {
446 if (cert->is_dup == 1)
447 continue;
448 certhash_findlink(*links, cert);
450 for (crl = *crls; crl != NULL; crl = crl->next) {
451 if (crl->is_dup == 1)
452 continue;
453 certhash_findlink(*links, crl);
456 /* Pass 3 - determine missing links. */
457 for (cert = *certs; cert != NULL; cert = cert->next) {
458 if (cert->is_dup == 1 || cert->reference != NULL)
459 continue;
460 if (certhash_addlink(links, cert) == -1)
461 return (-1);
463 for (crl = *crls; crl != NULL; crl = crl->next) {
464 if (crl->is_dup == 1 || crl->reference != NULL)
465 continue;
466 if (certhash_addlink(links, crl) == -1)
467 return (-1);
470 return (0);
473 static int
474 certhash_link(struct dirent *dep, struct hashinfo **links)
476 struct hashinfo *hi = NULL;
477 char target[PATH_MAX];
478 struct stat sb;
479 int n;
481 if (lstat(dep->d_name, &sb) == -1) {
482 fprintf(stderr, "failed to stat %s\n", dep->d_name);
483 return (-1);
485 if (!S_ISLNK(sb.st_mode))
486 return (0);
488 n = readlink(dep->d_name, target, sizeof(target) - 1);
489 if (n == -1) {
490 fprintf(stderr, "failed to readlink %s\n", dep->d_name);
491 return (-1);
493 if (n >= sizeof(target) - 1) {
494 fprintf(stderr, "symbolic link is too long %s\n", dep->d_name);
495 return (-1);
497 target[n] = '\0';
499 hi = hashinfo_from_linkname(dep->d_name, target);
500 if (hi == NULL) {
501 fprintf(stderr, "failed to get hash info %s\n", dep->d_name);
502 return (-1);
504 hi->exists = 1;
505 *links = hashinfo_chain(*links, hi);
507 return (0);
510 static int
511 certhash_file(struct dirent *dep, struct hashinfo **certs,
512 struct hashinfo **crls)
514 struct hashinfo *hi = NULL;
515 int has_cert, has_crl;
516 int ret = -1;
517 BIO *bio = NULL;
518 FILE *f;
520 has_cert = has_crl = 0;
522 if ((f = fopen(dep->d_name, "r")) == NULL) {
523 fprintf(stderr, "failed to fopen %s\n", dep->d_name);
524 goto err;
526 if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
527 fprintf(stderr, "failed to create bio\n");
528 fclose(f);
529 goto err;
532 if ((hi = certhash_cert(bio, dep->d_name)) != NULL) {
533 has_cert = 1;
534 *certs = hashinfo_chain(*certs, hi);
537 if (BIO_reset(bio) != 0) {
538 fprintf(stderr, "BIO_reset failed\n");
539 goto err;
542 if ((hi = certhash_crl(bio, dep->d_name)) != NULL) {
543 has_crl = hi->is_crl = 1;
544 *crls = hashinfo_chain(*crls, hi);
547 if (!has_cert && !has_crl)
548 fprintf(stderr, "PEM file %s does not contain a certificate "
549 "or CRL, ignoring...\n", dep->d_name);
551 ret = 0;
553 err:
554 BIO_free(bio);
556 return (ret);
559 static int
560 certhash_directory(const char *path)
562 struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
563 int ret = 0;
564 struct dirent *dep;
565 DIR *dip = NULL;
567 if ((dip = opendir(".")) == NULL) {
568 fprintf(stderr, "failed to open directory %s\n", path);
569 goto err;
572 if (certhash_config.verbose)
573 fprintf(stdout, "scanning directory %s\n", path);
575 /* Create lists of existing hash links, certs and CRLs. */
576 while ((dep = readdir(dip)) != NULL) {
577 if (filename_is_hash(dep->d_name)) {
578 if (certhash_link(dep, &links) == -1)
579 goto err;
581 if (filename_is_pem(dep->d_name)) {
582 if (certhash_file(dep, &certs, &crls) == -1)
583 goto err;
587 if (certhash_merge(&links, &certs, &crls) == -1) {
588 fprintf(stderr, "certhash merge failed\n");
589 goto err;
592 /* Remove spurious links. */
593 for (link = links; link != NULL; link = link->next) {
594 if (link->exists == 0 ||
595 (link->reference != NULL && link->changed == 0))
596 continue;
597 if (certhash_config.verbose)
598 fprintf(stdout, "%s link %s -> %s\n",
599 (certhash_config.dryrun ? "would remove" :
600 "removing"), link->filename, link->target);
601 if (certhash_config.dryrun)
602 continue;
603 if (unlink(link->filename) == -1) {
604 fprintf(stderr, "failed to remove link %s\n",
605 link->filename);
606 goto err;
610 /* Create missing links. */
611 for (link = links; link != NULL; link = link->next) {
612 if (link->exists == 1 && link->changed == 0)
613 continue;
614 if (certhash_config.verbose)
615 fprintf(stdout, "%s link %s -> %s\n",
616 (certhash_config.dryrun ? "would create" :
617 "creating"), link->filename,
618 link->reference->filename);
619 if (certhash_config.dryrun)
620 continue;
621 if (symlink(link->reference->filename, link->filename) == -1) {
622 fprintf(stderr, "failed to create link %s -> %s\n",
623 link->filename, link->reference->filename);
624 goto err;
628 goto done;
630 err:
631 ret = 1;
633 done:
634 hashinfo_chain_free(certs);
635 hashinfo_chain_free(crls);
636 hashinfo_chain_free(links);
638 if (dip != NULL)
639 closedir(dip);
640 return (ret);
643 static void
644 certhash_usage(void)
646 fprintf(stderr, "usage: certhash [-nv] dir ...\n");
647 options_usage(certhash_options);
651 certhash_main(int argc, char **argv)
653 int argsused;
654 int i, cwdfd, ret = 0;
656 if (single_execution) {
657 if (pledge("stdio cpath wpath rpath", NULL) == -1) {
658 perror("pledge");
659 exit(1);
663 memset(&certhash_config, 0, sizeof(certhash_config));
665 if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
666 certhash_usage();
667 return (1);
670 if ((cwdfd = open(".", O_RDONLY)) == -1) {
671 perror("failed to open current directory");
672 return (1);
675 for (i = argsused; i < argc; i++) {
676 if (chdir(argv[i]) == -1) {
677 fprintf(stderr,
678 "failed to change to directory %s: %s\n",
679 argv[i], strerror(errno));
680 ret = 1;
681 continue;
683 ret |= certhash_directory(argv[i]);
684 if (fchdir(cwdfd) == -1) {
685 perror("failed to restore current directory");
686 ret = 1;
687 break; /* can't continue safely */
690 close(cwdfd);
692 return (ret);