Annotation of src/usr.bin/openssl/certhash.c, Revision 1.15
1.1 jsing 1: /*
2: * Copyright (c) 2014, 2015 Joel Sing <jsing@openbsd.org>
3: *
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.
7: *
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.
15: */
16:
17: #include <sys/types.h>
18: #include <sys/stat.h>
19:
20: #include <errno.h>
21: #include <dirent.h>
22: #include <fcntl.h>
1.2 bcook 23: #include <limits.h>
1.1 jsing 24: #include <stdio.h>
25: #include <string.h>
26: #include <unistd.h>
27:
28: #include <openssl/bio.h>
29: #include <openssl/evp.h>
30: #include <openssl/pem.h>
31: #include <openssl/x509.h>
32:
33: #include "apps.h"
34:
35: static struct {
36: int dryrun;
37: int verbose;
38: } certhash_config;
39:
40: struct option certhash_options[] = {
41: {
42: .name = "n",
43: .desc = "Perform a dry-run - do not make any changes",
44: .type = OPTION_FLAG,
45: .opt.flag = &certhash_config.dryrun,
46: },
47: {
48: .name = "v",
49: .desc = "Verbose",
50: .type = OPTION_FLAG,
51: .opt.flag = &certhash_config.verbose,
52: },
53: { NULL },
54: };
55:
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;
68: };
69:
70: static struct hashinfo *
71: hashinfo(const char *filename, unsigned long hash, unsigned char *fingerprint)
72: {
73: struct hashinfo *hi;
74:
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);
81: }
82: }
83: hi->hash = hash;
84: if (fingerprint != NULL)
85: memcpy(hi->fingerprint, fingerprint, sizeof(hi->fingerprint));
86:
87: return (hi);
88: }
89:
90: static void
91: hashinfo_free(struct hashinfo *hi)
92: {
1.6 doug 93: if (hi == NULL)
94: return;
95:
1.1 jsing 96: free(hi->filename);
97: free(hi->target);
98: free(hi);
99: }
100:
101: #ifdef DEBUG
102: static void
103: hashinfo_print(struct hashinfo *hi)
104: {
105: int i;
106:
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' : ':');
112: }
113: }
114: #endif
115:
116: static int
117: hashinfo_compare(const void *a, const void *b)
118: {
119: struct hashinfo *hia = *(struct hashinfo **)a;
120: struct hashinfo *hib = *(struct hashinfo **)b;
121: int rv;
122:
1.8 tedu 123: rv = hia->hash < hib->hash ? -1 : hia->hash > hib->hash;
1.1 jsing 124: if (rv != 0)
125: return (rv);
1.3 guenther 126: rv = memcmp(hia->fingerprint, hib->fingerprint,
127: sizeof(hia->fingerprint));
1.1 jsing 128: if (rv != 0)
129: return (rv);
130: return strcmp(hia->filename, hib->filename);
131: }
132:
133: static struct hashinfo *
134: hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
135: {
136: struct hashinfo *hi = head;
137:
138: if (hi == NULL)
139: return (entry);
140: while (hi->next != NULL)
141: hi = hi->next;
142: hi->next = entry;
143:
144: return (head);
145: }
146:
147: static void
148: hashinfo_chain_free(struct hashinfo *hi)
149: {
150: struct hashinfo *next;
151:
152: while (hi != NULL) {
153: next = hi->next;
154: hashinfo_free(hi);
155: hi = next;
156: }
157: }
158:
159: static size_t
160: hashinfo_chain_length(struct hashinfo *hi)
161: {
162: int len = 0;
163:
164: while (hi != NULL) {
165: len++;
166: hi = hi->next;
167: }
168: return (len);
169: }
170:
171: static int
172: hashinfo_chain_sort(struct hashinfo **head)
173: {
174: struct hashinfo **list, *entry;
175: size_t len;
176: int i;
177:
178: if (*head == NULL)
179: return (0);
180:
181: len = hashinfo_chain_length(*head);
182: if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
183: return (-1);
184:
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);
188:
189: *head = entry = list[0];
190: for (i = 1; i < len; i++) {
191: entry->next = list[i];
192: entry = list[i];
193: }
194: entry->next = NULL;
195:
1.4 beck 196: free(list);
1.1 jsing 197: return (0);
198: }
199:
200: static char *
201: hashinfo_linkname(struct hashinfo *hi)
202: {
203: char *filename;
204:
205: if (asprintf(&filename, "%08lx.%s%u", hi->hash,
206: (hi->is_crl ? "r" : ""), hi->index) == -1)
207: return (NULL);
208:
209: return (filename);
210: }
211:
212: static int
213: filename_is_hash(const char *filename)
214: {
215: const char *p = filename;
216:
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);
227:
228: return (1);
229: }
230:
231: static int
232: filename_is_pem(const char *filename)
233: {
234: const char *q, *p = filename;
235:
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);
242:
243: return (1);
244: }
245:
246: static struct hashinfo *
247: hashinfo_from_linkname(const char *linkname, const char *target)
248: {
249: struct hashinfo *hi = NULL;
250: const char *errstr;
251: char *l, *p, *ep;
252: long long val;
253:
254: if ((l = strdup(linkname)) == NULL)
255: goto err;
256: if ((p = strchr(l, '.')) == NULL)
257: goto err;
258: *p++ = '\0';
259:
260: if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
261: goto err;
262: if ((hi->target = strdup(target)) == NULL)
263: goto err;
264:
265: errno = 0;
266: val = strtoll(l, &ep, 16);
267: if (l[0] == '\0' || *ep != '\0')
268: goto err;
1.9 beck 269: if (errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN))
1.1 jsing 270: goto err;
271: if (val < 0 || val > ULONG_MAX)
272: goto err;
273: hi->hash = (unsigned long)val;
274:
275: if (*p == 'r') {
276: hi->is_crl = 1;
277: p++;
278: }
279:
280: val = strtonum(p, 0, 0xffffffff, &errstr);
281: if (errstr != NULL)
282: goto err;
283:
284: hi->index = (unsigned int)val;
285:
286: goto done;
287:
288: err:
289: hashinfo_free(hi);
290: hi = NULL;
291:
292: done:
293: free(l);
294:
295: return (hi);
296: }
297:
298: static struct hashinfo *
299: certhash_cert(BIO *bio, const char *filename)
300: {
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;
307:
308: if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
309: goto err;
310:
311: hash = X509_subject_name_hash(cert);
312:
313: digest = EVP_sha256();
314: if (X509_digest(cert, digest, fingerprint, &len) != 1) {
315: fprintf(stderr, "out of memory\n");
316: goto err;
317: }
318:
319: hi = hashinfo(filename, hash, fingerprint);
320:
321: err:
322: X509_free(cert);
323:
324: return (hi);
325: }
326:
327: static struct hashinfo *
328: certhash_crl(BIO *bio, const char *filename)
329: {
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;
336:
337: if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
338: return (NULL);
339:
340: hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
341:
342: digest = EVP_sha256();
343: if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
344: fprintf(stderr, "out of memory\n");
345: goto err;
346: }
347:
348: hi = hashinfo(filename, hash, fingerprint);
349:
350: err:
351: X509_CRL_free(crl);
352:
353: return (hi);
354: }
355:
356: static int
357: certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
358: {
359: struct hashinfo *link = NULL;
360:
361: if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
362: goto err;
363:
364: if ((link->filename = hashinfo_linkname(hi)) == NULL)
365: goto err;
366:
367: link->reference = hi;
368: link->changed = 1;
369: *links = hashinfo_chain(*links, link);
370: hi->reference = link;
371:
372: return (0);
373:
374: err:
375: hashinfo_free(link);
376: return (-1);
377: }
378:
379: static void
380: certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
381: {
382: struct hashinfo *link;
383:
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;
395: }
396: }
397: }
398:
399: static void
400: certhash_index(struct hashinfo *head, const char *name)
401: {
402: struct hashinfo *last, *entry;
403: int index = 0;
404:
405: last = NULL;
406: for (entry = head; entry != NULL; entry = entry->next) {
407: if (last != NULL) {
408: if (entry->hash == last->hash) {
1.3 guenther 409: if (memcmp(entry->fingerprint,
410: last->fingerprint,
1.1 jsing 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;
418: }
419: index++;
420: } else {
421: index = 0;
422: }
423: }
424: entry->index = index;
425: last = entry;
426: }
427: }
428:
429: static int
430: certhash_merge(struct hashinfo **links, struct hashinfo **certs,
431: struct hashinfo **crls)
432: {
433: struct hashinfo *cert, *crl;
434:
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");
442:
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);
448: }
449: for (crl = *crls; crl != NULL; crl = crl->next) {
450: if (crl->is_dup == 1)
451: continue;
452: certhash_findlink(*links, crl);
453: }
454:
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);
461: }
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);
467: }
468:
469: return (0);
470: }
471:
472: static int
1.5 guenther 473: certhash_link(struct dirent *dep, struct hashinfo **links)
1.1 jsing 474: {
475: struct hashinfo *hi = NULL;
1.11 deraadt 476: char target[PATH_MAX];
1.1 jsing 477: struct stat sb;
478: int n;
479:
1.5 guenther 480: if (lstat(dep->d_name, &sb) == -1) {
1.1 jsing 481: fprintf(stderr, "failed to stat %s\n", dep->d_name);
482: return (-1);
483: }
484: if (!S_ISLNK(sb.st_mode))
485: return (0);
486:
1.5 guenther 487: n = readlink(dep->d_name, target, sizeof(target) - 1);
1.1 jsing 488: if (n == -1) {
489: fprintf(stderr, "failed to readlink %s\n", dep->d_name);
490: return (-1);
491: }
492: target[n] = '\0';
493:
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);
498: }
499: hi->exists = 1;
500: *links = hashinfo_chain(*links, hi);
501:
502: return (0);
503: }
504:
505: static int
1.5 guenther 506: certhash_file(struct dirent *dep, struct hashinfo **certs,
1.1 jsing 507: struct hashinfo **crls)
508: {
509: struct hashinfo *hi = NULL;
510: int has_cert, has_crl;
1.5 guenther 511: int ret = -1;
1.1 jsing 512: BIO *bio = NULL;
513: FILE *f;
514:
515: has_cert = has_crl = 0;
516:
1.5 guenther 517: if ((f = fopen(dep->d_name, "r")) == NULL) {
518: fprintf(stderr, "failed to fopen %s\n", dep->d_name);
1.1 jsing 519: goto err;
520: }
521: if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
522: fprintf(stderr, "failed to create bio\n");
1.5 guenther 523: fclose(f);
1.1 jsing 524: goto err;
525: }
526:
527: if ((hi = certhash_cert(bio, dep->d_name)) != NULL) {
528: has_cert = 1;
529: *certs = hashinfo_chain(*certs, hi);
530: }
531:
532: if (BIO_reset(bio) != 0) {
533: fprintf(stderr, "BIO_reset failed\n");
534: goto err;
535: }
536:
537: if ((hi = certhash_crl(bio, dep->d_name)) != NULL) {
538: has_crl = hi->is_crl = 1;
539: *crls = hashinfo_chain(*crls, hi);
540: }
541:
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);
545:
546: ret = 0;
547:
548: err:
549: BIO_free(bio);
550:
551: return (ret);
552: }
553:
554: static int
555: certhash_directory(const char *path)
556: {
557: struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
1.5 guenther 558: int ret = 0;
1.1 jsing 559: struct dirent *dep;
560: DIR *dip = NULL;
561:
1.5 guenther 562: if ((dip = opendir(".")) == NULL) {
1.1 jsing 563: fprintf(stderr, "failed to open directory %s\n", path);
564: goto err;
565: }
566:
567: if (certhash_config.verbose)
568: fprintf(stdout, "scanning directory %s\n", path);
569:
570: /* Create lists of existing hash links, certs and CRLs. */
571: while ((dep = readdir(dip)) != NULL) {
572: if (filename_is_hash(dep->d_name)) {
1.5 guenther 573: if (certhash_link(dep, &links) == -1)
1.1 jsing 574: goto err;
575: }
576: if (filename_is_pem(dep->d_name)) {
1.5 guenther 577: if (certhash_file(dep, &certs, &crls) == -1)
1.1 jsing 578: goto err;
579: }
580: }
581:
582: if (certhash_merge(&links, &certs, &crls) == -1) {
583: fprintf(stderr, "certhash merge failed\n");
584: goto err;
585: }
586:
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;
1.5 guenther 598: if (unlink(link->filename) == -1) {
1.1 jsing 599: fprintf(stderr, "failed to remove link %s\n",
600: link->filename);
601: goto err;
602: }
603: }
604:
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;
1.5 guenther 616: if (symlink(link->reference->filename, link->filename) == -1) {
1.1 jsing 617: fprintf(stderr, "failed to create link %s -> %s\n",
618: link->filename, link->reference->filename);
619: goto err;
620: }
621: }
622:
623: goto done;
624:
625: err:
626: ret = 1;
627:
628: done:
629: hashinfo_chain_free(certs);
630: hashinfo_chain_free(crls);
631: hashinfo_chain_free(links);
632:
633: if (dip != NULL)
634: closedir(dip);
635: return (ret);
636: }
637:
638: static void
639: certhash_usage(void)
640: {
641: fprintf(stderr, "usage: certhash [-nv] dir ...\n");
642: options_usage(certhash_options);
643: }
644:
645: int
646: certhash_main(int argc, char **argv)
647: {
648: int argsused;
1.5 guenther 649: int i, cwdfd, ret = 0;
1.1 jsing 650:
1.12 doug 651: if (single_execution) {
1.15 ! deraadt 652: if (pledge("stdio cpath wpath rpath", NULL) == -1) {
1.12 doug 653: perror("pledge");
1.13 doug 654: exit(1);
655: }
1.12 doug 656: }
657:
1.1 jsing 658: memset(&certhash_config, 0, sizeof(certhash_config));
659:
660: if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
661: certhash_usage();
662: return (1);
663: }
664:
1.7 millert 665: if ((cwdfd = open(".", O_RDONLY)) == -1) {
1.5 guenther 666: perror("failed to open current directory");
667: return (1);
668: }
669:
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;
677: }
1.1 jsing 678: ret |= certhash_directory(argv[i]);
1.5 guenther 679: if (fchdir(cwdfd) == -1) {
680: perror("failed to restore current directory");
681: ret = 1;
682: break; /* can't continue safely */
683: }
684: }
685: close(cwdfd);
1.1 jsing 686:
687: return (ret);
688: }