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