Unleashed v1.4
[unleashed.git] / bin / openssl / certhash.c
blob5838f0209b2b5fd13fcaad5e0a1cdbf8bb75ce31
1 /*
2 * Copyright (c) 2014, 2015 Joel Sing <jsing@openbsd.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include <sys/types.h>
18 #include <sys/stat.h>
20 #include <errno.h>
21 #include <dirent.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <unistd.h>
28 #include <openssl/bio.h>
29 #include <openssl/evp.h>
30 #include <openssl/pem.h>
31 #include <openssl/x509.h>
33 #include "apps.h"
35 static struct {
36 int dryrun;
37 int verbose;
38 } certhash_config;
40 struct option certhash_options[] = {
42 .name = "n",
43 .desc = "Perform a dry-run - do not make any changes",
44 .type = OPTION_FLAG,
45 .opt.flag = &certhash_config.dryrun,
48 .name = "v",
49 .desc = "Verbose",
50 .type = OPTION_FLAG,
51 .opt.flag = &certhash_config.verbose,
53 { NULL },
56 struct hashinfo {
57 char *filename;
58 char *target;
59 unsigned long hash;
60 unsigned int index;
61 unsigned char fingerprint[EVP_MAX_MD_SIZE];
62 int is_crl;
63 int is_dup;
64 int exists;
65 int changed;
66 struct hashinfo *reference;
67 struct hashinfo *next;
70 static struct hashinfo *
71 hashinfo(const char *filename, unsigned long hash, unsigned char *fingerprint)
73 struct hashinfo *hi;
75 if ((hi = calloc(1, sizeof(*hi))) == NULL)
76 return (NULL);
77 if (filename != NULL) {
78 if ((hi->filename = strdup(filename)) == NULL) {
79 free(hi);
80 return (NULL);
83 hi->hash = hash;
84 if (fingerprint != NULL)
85 memcpy(hi->fingerprint, fingerprint, sizeof(hi->fingerprint));
87 return (hi);
90 static void
91 hashinfo_free(struct hashinfo *hi)
93 if (hi == NULL)
94 return;
96 free(hi->filename);
97 free(hi->target);
98 free(hi);
101 #ifdef DEBUG
102 static void
103 hashinfo_print(struct hashinfo *hi)
105 int i;
107 printf("hashinfo %s %08lx %u %i\n", hi->filename, hi->hash,
108 hi->index, hi->is_crl);
109 for (i = 0; i < (int)EVP_MAX_MD_SIZE; i++) {
110 printf("%02X%c", hi->fingerprint[i],
111 (i + 1 == (int)EVP_MAX_MD_SIZE) ? '\n' : ':');
114 #endif
116 static int
117 hashinfo_compare(const void *a, const void *b)
119 struct hashinfo *hia = *(struct hashinfo **)a;
120 struct hashinfo *hib = *(struct hashinfo **)b;
121 int rv;
123 rv = hia->hash < hib->hash ? -1 : hia->hash > hib->hash;
124 if (rv != 0)
125 return (rv);
126 rv = memcmp(hia->fingerprint, hib->fingerprint,
127 sizeof(hia->fingerprint));
128 if (rv != 0)
129 return (rv);
130 return strcmp(hia->filename, hib->filename);
133 static struct hashinfo *
134 hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
136 struct hashinfo *hi = head;
138 if (hi == NULL)
139 return (entry);
140 while (hi->next != NULL)
141 hi = hi->next;
142 hi->next = entry;
144 return (head);
147 static void
148 hashinfo_chain_free(struct hashinfo *hi)
150 struct hashinfo *next;
152 while (hi != NULL) {
153 next = hi->next;
154 hashinfo_free(hi);
155 hi = next;
159 static size_t
160 hashinfo_chain_length(struct hashinfo *hi)
162 int len = 0;
164 while (hi != NULL) {
165 len++;
166 hi = hi->next;
168 return (len);
171 static int
172 hashinfo_chain_sort(struct hashinfo **head)
174 struct hashinfo **list, *entry;
175 size_t len;
176 int i;
178 if (*head == NULL)
179 return (0);
181 len = hashinfo_chain_length(*head);
182 if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
183 return (-1);
185 for (entry = *head, i = 0; entry != NULL; entry = entry->next, i++)
186 list[i] = entry;
187 qsort(list, len, sizeof(struct hashinfo *), hashinfo_compare);
189 *head = entry = list[0];
190 for (i = 1; i < len; i++) {
191 entry->next = list[i];
192 entry = list[i];
194 entry->next = NULL;
196 free(list);
197 return (0);
200 static char *
201 hashinfo_linkname(struct hashinfo *hi)
203 char *filename;
205 if (asprintf(&filename, "%08lx.%s%u", hi->hash,
206 (hi->is_crl ? "r" : ""), hi->index) == -1)
207 return (NULL);
209 return (filename);
212 static int
213 filename_is_hash(const char *filename)
215 const char *p = filename;
217 while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
218 p++;
219 if (*p++ != '.')
220 return (0);
221 if (*p == 'r') /* CRL format. */
222 p++;
223 while (*p >= '0' && *p <= '9')
224 p++;
225 if (*p != '\0')
226 return (0);
228 return (1);
231 static int
232 filename_is_pem(const char *filename)
234 const char *q, *p = filename;
236 if ((q = strchr(p, '\0')) == NULL)
237 return (0);
238 if ((q - p) < 4)
239 return (0);
240 if (strncmp((q - 4), ".pem", 4) != 0)
241 return (0);
243 return (1);
246 static struct hashinfo *
247 hashinfo_from_linkname(const char *linkname, const char *target)
249 struct hashinfo *hi = NULL;
250 const char *errstr;
251 char *l, *p, *ep;
252 long long val;
254 if ((l = strdup(linkname)) == NULL)
255 goto err;
256 if ((p = strchr(l, '.')) == NULL)
257 goto err;
258 *p++ = '\0';
260 if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
261 goto err;
262 if ((hi->target = strdup(target)) == NULL)
263 goto err;
265 errno = 0;
266 val = strtoll(l, &ep, 16);
267 if (l[0] == '\0' || *ep != '\0')
268 goto err;
269 if (errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN))
270 goto err;
271 if (val < 0 || val > ULONG_MAX)
272 goto err;
273 hi->hash = (unsigned long)val;
275 if (*p == 'r') {
276 hi->is_crl = 1;
277 p++;
280 val = strtonum(p, 0, 0xffffffff, &errstr);
281 if (errstr != NULL)
282 goto err;
284 hi->index = (unsigned int)val;
286 goto done;
288 err:
289 hashinfo_free(hi);
290 hi = NULL;
292 done:
293 free(l);
295 return (hi);
298 static struct hashinfo *
299 certhash_cert(BIO *bio, const char *filename)
301 unsigned char fingerprint[EVP_MAX_MD_SIZE];
302 struct hashinfo *hi = NULL;
303 const EVP_MD *digest;
304 X509 *cert = NULL;
305 unsigned long hash;
306 unsigned int len;
308 if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
309 goto err;
311 hash = X509_subject_name_hash(cert);
313 digest = EVP_sha256();
314 if (X509_digest(cert, digest, fingerprint, &len) != 1) {
315 fprintf(stderr, "out of memory\n");
316 goto err;
319 hi = hashinfo(filename, hash, fingerprint);
321 err:
322 X509_free(cert);
324 return (hi);
327 static struct hashinfo *
328 certhash_crl(BIO *bio, const char *filename)
330 unsigned char fingerprint[EVP_MAX_MD_SIZE];
331 struct hashinfo *hi = NULL;
332 const EVP_MD *digest;
333 X509_CRL *crl = NULL;
334 unsigned long hash;
335 unsigned int len;
337 if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
338 return (NULL);
340 hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
342 digest = EVP_sha256();
343 if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
344 fprintf(stderr, "out of memory\n");
345 goto err;
348 hi = hashinfo(filename, hash, fingerprint);
350 err:
351 X509_CRL_free(crl);
353 return (hi);
356 static int
357 certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
359 struct hashinfo *link = NULL;
361 if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
362 goto err;
364 if ((link->filename = hashinfo_linkname(hi)) == NULL)
365 goto err;
367 link->reference = hi;
368 link->changed = 1;
369 *links = hashinfo_chain(*links, link);
370 hi->reference = link;
372 return (0);
374 err:
375 hashinfo_free(link);
376 return (-1);
379 static void
380 certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
382 struct hashinfo *link;
384 for (link = links; link != NULL; link = link->next) {
385 if (link->is_crl == hi->is_crl &&
386 link->hash == hi->hash &&
387 link->index == hi->index &&
388 link->reference == NULL) {
389 link->reference = hi;
390 if (link->target == NULL ||
391 strcmp(link->target, hi->filename) != 0)
392 link->changed = 1;
393 hi->reference = link;
394 break;
399 static void
400 certhash_index(struct hashinfo *head, const char *name)
402 struct hashinfo *last, *entry;
403 int index = 0;
405 last = NULL;
406 for (entry = head; entry != NULL; entry = entry->next) {
407 if (last != NULL) {
408 if (entry->hash == last->hash) {
409 if (memcmp(entry->fingerprint,
410 last->fingerprint,
411 sizeof(entry->fingerprint)) == 0) {
412 fprintf(stderr, "WARNING: duplicate %s "
413 "in %s (using %s), ignoring...\n",
414 name, entry->filename,
415 last->filename);
416 entry->is_dup = 1;
417 continue;
419 index++;
420 } else {
421 index = 0;
424 entry->index = index;
425 last = entry;
429 static int
430 certhash_merge(struct hashinfo **links, struct hashinfo **certs,
431 struct hashinfo **crls)
433 struct hashinfo *cert, *crl;
435 /* Pass 1 - sort and index entries. */
436 if (hashinfo_chain_sort(certs) == -1)
437 return (-1);
438 if (hashinfo_chain_sort(crls) == -1)
439 return (-1);
440 certhash_index(*certs, "certificate");
441 certhash_index(*crls, "CRL");
443 /* Pass 2 - map to existing links. */
444 for (cert = *certs; cert != NULL; cert = cert->next) {
445 if (cert->is_dup == 1)
446 continue;
447 certhash_findlink(*links, cert);
449 for (crl = *crls; crl != NULL; crl = crl->next) {
450 if (crl->is_dup == 1)
451 continue;
452 certhash_findlink(*links, crl);
455 /* Pass 3 - determine missing links. */
456 for (cert = *certs; cert != NULL; cert = cert->next) {
457 if (cert->is_dup == 1 || cert->reference != NULL)
458 continue;
459 if (certhash_addlink(links, cert) == -1)
460 return (-1);
462 for (crl = *crls; crl != NULL; crl = crl->next) {
463 if (crl->is_dup == 1 || crl->reference != NULL)
464 continue;
465 if (certhash_addlink(links, crl) == -1)
466 return (-1);
469 return (0);
472 static int
473 certhash_link(struct dirent *dep, struct hashinfo **links)
475 struct hashinfo *hi = NULL;
476 char target[PATH_MAX];
477 struct stat sb;
478 int n;
480 if (lstat(dep->d_name, &sb) == -1) {
481 fprintf(stderr, "failed to stat %s\n", dep->d_name);
482 return (-1);
484 if (!S_ISLNK(sb.st_mode))
485 return (0);
487 n = readlink(dep->d_name, target, sizeof(target) - 1);
488 if (n == -1) {
489 fprintf(stderr, "failed to readlink %s\n", dep->d_name);
490 return (-1);
492 target[n] = '\0';
494 hi = hashinfo_from_linkname(dep->d_name, target);
495 if (hi == NULL) {
496 fprintf(stderr, "failed to get hash info %s\n", dep->d_name);
497 return (-1);
499 hi->exists = 1;
500 *links = hashinfo_chain(*links, hi);
502 return (0);
505 static int
506 certhash_file(struct dirent *dep, struct hashinfo **certs,
507 struct hashinfo **crls)
509 struct hashinfo *hi = NULL;
510 int has_cert, has_crl;
511 int ret = -1;
512 BIO *bio = NULL;
513 FILE *f;
515 has_cert = has_crl = 0;
517 if ((f = fopen(dep->d_name, "r")) == NULL) {
518 fprintf(stderr, "failed to fopen %s\n", dep->d_name);
519 goto err;
521 if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
522 fprintf(stderr, "failed to create bio\n");
523 fclose(f);
524 goto err;
527 if ((hi = certhash_cert(bio, dep->d_name)) != NULL) {
528 has_cert = 1;
529 *certs = hashinfo_chain(*certs, hi);
532 if (BIO_reset(bio) != 0) {
533 fprintf(stderr, "BIO_reset failed\n");
534 goto err;
537 if ((hi = certhash_crl(bio, dep->d_name)) != NULL) {
538 has_crl = hi->is_crl = 1;
539 *crls = hashinfo_chain(*crls, hi);
542 if (!has_cert && !has_crl)
543 fprintf(stderr, "PEM file %s does not contain a certificate "
544 "or CRL, ignoring...\n", dep->d_name);
546 ret = 0;
548 err:
549 BIO_free(bio);
551 return (ret);
554 static int
555 certhash_directory(const char *path)
557 struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
558 int ret = 0;
559 struct dirent *dep;
560 DIR *dip = NULL;
562 if ((dip = opendir(".")) == NULL) {
563 fprintf(stderr, "failed to open directory %s\n", path);
564 goto err;
567 if (certhash_config.verbose)
568 fprintf(stdout, "scanning directory %s\n", path);
570 /* Create lists of existing hash links, certs and CRLs. */
571 while ((dep = readdir(dip)) != NULL) {
572 if (filename_is_hash(dep->d_name)) {
573 if (certhash_link(dep, &links) == -1)
574 goto err;
576 if (filename_is_pem(dep->d_name)) {
577 if (certhash_file(dep, &certs, &crls) == -1)
578 goto err;
582 if (certhash_merge(&links, &certs, &crls) == -1) {
583 fprintf(stderr, "certhash merge failed\n");
584 goto err;
587 /* Remove spurious links. */
588 for (link = links; link != NULL; link = link->next) {
589 if (link->exists == 0 ||
590 (link->reference != NULL && link->changed == 0))
591 continue;
592 if (certhash_config.verbose)
593 fprintf(stdout, "%s link %s -> %s\n",
594 (certhash_config.dryrun ? "would remove" :
595 "removing"), link->filename, link->target);
596 if (certhash_config.dryrun)
597 continue;
598 if (unlink(link->filename) == -1) {
599 fprintf(stderr, "failed to remove link %s\n",
600 link->filename);
601 goto err;
605 /* Create missing links. */
606 for (link = links; link != NULL; link = link->next) {
607 if (link->exists == 1 && link->changed == 0)
608 continue;
609 if (certhash_config.verbose)
610 fprintf(stdout, "%s link %s -> %s\n",
611 (certhash_config.dryrun ? "would create" :
612 "creating"), link->filename,
613 link->reference->filename);
614 if (certhash_config.dryrun)
615 continue;
616 if (symlink(link->reference->filename, link->filename) == -1) {
617 fprintf(stderr, "failed to create link %s -> %s\n",
618 link->filename, link->reference->filename);
619 goto err;
623 goto done;
625 err:
626 ret = 1;
628 done:
629 hashinfo_chain_free(certs);
630 hashinfo_chain_free(crls);
631 hashinfo_chain_free(links);
633 if (dip != NULL)
634 closedir(dip);
635 return (ret);
638 static void
639 certhash_usage(void)
641 fprintf(stderr, "usage: certhash [-nv] dir ...\n");
642 options_usage(certhash_options);
646 certhash_main(int argc, char **argv)
648 int argsused;
649 int i, cwdfd, ret = 0;
651 if (single_execution) {
652 if (pledge("stdio cpath wpath rpath", NULL) == -1) {
653 perror("pledge");
654 exit(1);
658 memset(&certhash_config, 0, sizeof(certhash_config));
660 if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
661 certhash_usage();
662 return (1);
665 if ((cwdfd = open(".", O_RDONLY)) == -1) {
666 perror("failed to open current directory");
667 return (1);
670 for (i = argsused; i < argc; i++) {
671 if (chdir(argv[i]) == -1) {
672 fprintf(stderr,
673 "failed to change to directory %s: %s\n",
674 argv[i], strerror(errno));
675 ret = 1;
676 continue;
678 ret |= certhash_directory(argv[i]);
679 if (fchdir(cwdfd) == -1) {
680 perror("failed to restore current directory");
681 ret = 1;
682 break; /* can't continue safely */
685 close(cwdfd);
687 return (ret);