Annotation of src/usr.bin/openssl/certhash.c, Revision 1.2
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: {
94: free(hi->filename);
95: free(hi->target);
96: free(hi);
97: }
98:
99: #ifdef DEBUG
100: static void
101: hashinfo_print(struct hashinfo *hi)
102: {
103: int i;
104:
105: printf("hashinfo %s %08lx %u %i\n", hi->filename, hi->hash,
106: hi->index, hi->is_crl);
107: for (i = 0; i < (int)EVP_MAX_MD_SIZE; i++) {
108: printf("%02X%c", hi->fingerprint[i],
109: (i + 1 == (int)EVP_MAX_MD_SIZE) ? '\n' : ':');
110: }
111: }
112: #endif
113:
114: static int
115: hashinfo_compare(const void *a, const void *b)
116: {
117: struct hashinfo *hia = *(struct hashinfo **)a;
118: struct hashinfo *hib = *(struct hashinfo **)b;
119: int rv;
120:
121: rv = hia->hash - hib->hash;
122: if (rv != 0)
123: return (rv);
124: rv = bcmp(hia->fingerprint, hib->fingerprint, sizeof(hia->fingerprint));
125: if (rv != 0)
126: return (rv);
127: return strcmp(hia->filename, hib->filename);
128: }
129:
130: static struct hashinfo *
131: hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
132: {
133: struct hashinfo *hi = head;
134:
135: if (hi == NULL)
136: return (entry);
137: while (hi->next != NULL)
138: hi = hi->next;
139: hi->next = entry;
140:
141: return (head);
142: }
143:
144: static void
145: hashinfo_chain_free(struct hashinfo *hi)
146: {
147: struct hashinfo *next;
148:
149: while (hi != NULL) {
150: next = hi->next;
151: hashinfo_free(hi);
152: hi = next;
153: }
154: }
155:
156: static size_t
157: hashinfo_chain_length(struct hashinfo *hi)
158: {
159: int len = 0;
160:
161: while (hi != NULL) {
162: len++;
163: hi = hi->next;
164: }
165: return (len);
166: }
167:
168: static int
169: hashinfo_chain_sort(struct hashinfo **head)
170: {
171: struct hashinfo **list, *entry;
172: size_t len;
173: int i;
174:
175: if (*head == NULL)
176: return (0);
177:
178: len = hashinfo_chain_length(*head);
179: if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
180: return (-1);
181:
182: for (entry = *head, i = 0; entry != NULL; entry = entry->next, i++)
183: list[i] = entry;
184: qsort(list, len, sizeof(struct hashinfo *), hashinfo_compare);
185:
186: *head = entry = list[0];
187: for (i = 1; i < len; i++) {
188: entry->next = list[i];
189: entry = list[i];
190: }
191: entry->next = NULL;
192:
193: return (0);
194: }
195:
196: static char *
197: hashinfo_linkname(struct hashinfo *hi)
198: {
199: char *filename;
200:
201: if (asprintf(&filename, "%08lx.%s%u", hi->hash,
202: (hi->is_crl ? "r" : ""), hi->index) == -1)
203: return (NULL);
204:
205: return (filename);
206: }
207:
208: static int
209: filename_is_hash(const char *filename)
210: {
211: const char *p = filename;
212:
213: while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
214: p++;
215: if (*p++ != '.')
216: return (0);
217: if (*p == 'r') /* CRL format. */
218: p++;
219: while (*p >= '0' && *p <= '9')
220: p++;
221: if (*p != '\0')
222: return (0);
223:
224: return (1);
225: }
226:
227: static int
228: filename_is_pem(const char *filename)
229: {
230: const char *q, *p = filename;
231:
232: if ((q = strchr(p, '\0')) == NULL)
233: return (0);
234: if ((q - p) < 4)
235: return (0);
236: if (strncmp((q - 4), ".pem", 4) != 0)
237: return (0);
238:
239: return (1);
240: }
241:
242: static struct hashinfo *
243: hashinfo_from_linkname(const char *linkname, const char *target)
244: {
245: struct hashinfo *hi = NULL;
246: const char *errstr;
247: char *l, *p, *ep;
248: long long val;
249:
250: if ((l = strdup(linkname)) == NULL)
251: goto err;
252: if ((p = strchr(l, '.')) == NULL)
253: goto err;
254: *p++ = '\0';
255:
256: if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
257: goto err;
258: if ((hi->target = strdup(target)) == NULL)
259: goto err;
260:
261: errno = 0;
262: val = strtoll(l, &ep, 16);
263: if (l[0] == '\0' || *ep != '\0')
264: goto err;
265: if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
266: goto err;
267: if (val < 0 || val > ULONG_MAX)
268: goto err;
269: hi->hash = (unsigned long)val;
270:
271: if (*p == 'r') {
272: hi->is_crl = 1;
273: p++;
274: }
275:
276: val = strtonum(p, 0, 0xffffffff, &errstr);
277: if (errstr != NULL)
278: goto err;
279:
280: hi->index = (unsigned int)val;
281:
282: goto done;
283:
284: err:
285: hashinfo_free(hi);
286: hi = NULL;
287:
288: done:
289: free(l);
290:
291: return (hi);
292: }
293:
294: static struct hashinfo *
295: certhash_cert(BIO *bio, const char *filename)
296: {
297: unsigned char fingerprint[EVP_MAX_MD_SIZE];
298: struct hashinfo *hi = NULL;
299: const EVP_MD *digest;
300: X509 *cert = NULL;
301: unsigned long hash;
302: unsigned int len;
303:
304: if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
305: goto err;
306:
307: hash = X509_subject_name_hash(cert);
308:
309: digest = EVP_sha256();
310: if (X509_digest(cert, digest, fingerprint, &len) != 1) {
311: fprintf(stderr, "out of memory\n");
312: goto err;
313: }
314:
315: hi = hashinfo(filename, hash, fingerprint);
316:
317: err:
318: X509_free(cert);
319:
320: return (hi);
321: }
322:
323: static struct hashinfo *
324: certhash_crl(BIO *bio, const char *filename)
325: {
326: unsigned char fingerprint[EVP_MAX_MD_SIZE];
327: struct hashinfo *hi = NULL;
328: const EVP_MD *digest;
329: X509_CRL *crl = NULL;
330: unsigned long hash;
331: unsigned int len;
332:
333: if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
334: return (NULL);
335:
336: hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
337:
338: digest = EVP_sha256();
339: if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
340: fprintf(stderr, "out of memory\n");
341: goto err;
342: }
343:
344: hi = hashinfo(filename, hash, fingerprint);
345:
346: err:
347: X509_CRL_free(crl);
348:
349: return (hi);
350: }
351:
352: static int
353: certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
354: {
355: struct hashinfo *link = NULL;
356:
357: if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
358: goto err;
359:
360: printf("hi->is_crl = %i\n", hi->is_crl);
361: if ((link->filename = hashinfo_linkname(hi)) == NULL)
362: goto err;
363: printf("filename = %s\n", link->filename);
364:
365: link->reference = hi;
366: link->changed = 1;
367: *links = hashinfo_chain(*links, link);
368: hi->reference = link;
369:
370: return (0);
371:
372: err:
373: hashinfo_free(link);
374: return (-1);
375: }
376:
377: static void
378: certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
379: {
380: struct hashinfo *link;
381:
382: for (link = links; link != NULL; link = link->next) {
383: if (link->is_crl == hi->is_crl &&
384: link->hash == hi->hash &&
385: link->index == hi->index &&
386: link->reference == NULL) {
387: link->reference = hi;
388: if (link->target == NULL ||
389: strcmp(link->target, hi->filename) != 0)
390: link->changed = 1;
391: hi->reference = link;
392: break;
393: }
394: }
395: }
396:
397: static void
398: certhash_index(struct hashinfo *head, const char *name)
399: {
400: struct hashinfo *last, *entry;
401: int index = 0;
402:
403: last = NULL;
404: for (entry = head; entry != NULL; entry = entry->next) {
405: if (last != NULL) {
406: if (entry->hash == last->hash) {
407: if (bcmp(entry->fingerprint, last->fingerprint,
408: sizeof(entry->fingerprint)) == 0) {
409: fprintf(stderr, "WARNING: duplicate %s "
410: "in %s (using %s), ignoring...\n",
411: name, entry->filename,
412: last->filename);
413: entry->is_dup = 1;
414: continue;
415: }
416: index++;
417: } else {
418: index = 0;
419: }
420: }
421: entry->index = index;
422: last = entry;
423: }
424: }
425:
426: static int
427: certhash_merge(struct hashinfo **links, struct hashinfo **certs,
428: struct hashinfo **crls)
429: {
430: struct hashinfo *cert, *crl;
431:
432: /* Pass 1 - sort and index entries. */
433: if (hashinfo_chain_sort(certs) == -1)
434: return (-1);
435: if (hashinfo_chain_sort(crls) == -1)
436: return (-1);
437: certhash_index(*certs, "certificate");
438: certhash_index(*crls, "CRL");
439:
440: /* Pass 2 - map to existing links. */
441: for (cert = *certs; cert != NULL; cert = cert->next) {
442: if (cert->is_dup == 1)
443: continue;
444: certhash_findlink(*links, cert);
445: }
446: for (crl = *crls; crl != NULL; crl = crl->next) {
447: if (crl->is_dup == 1)
448: continue;
449: certhash_findlink(*links, crl);
450: }
451:
452: /* Pass 3 - determine missing links. */
453: for (cert = *certs; cert != NULL; cert = cert->next) {
454: if (cert->is_dup == 1 || cert->reference != NULL)
455: continue;
456: if (certhash_addlink(links, cert) == -1)
457: return (-1);
458: }
459: for (crl = *crls; crl != NULL; crl = crl->next) {
460: if (crl->is_dup == 1 || crl->reference != NULL)
461: continue;
462: if (certhash_addlink(links, crl) == -1)
463: return (-1);
464: }
465:
466: return (0);
467: }
468:
469: static int
470: certhash_link(int dfd, struct dirent *dep, struct hashinfo **links)
471: {
472: struct hashinfo *hi = NULL;
473: char target[MAXPATHLEN];
474: struct stat sb;
475: int n;
476:
477: if (fstatat(dfd, dep->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
478: fprintf(stderr, "failed to stat %s\n", dep->d_name);
479: return (-1);
480: }
481: if (!S_ISLNK(sb.st_mode))
482: return (0);
483:
484: n = readlinkat(dfd, dep->d_name, target, sizeof(target));
485: if (n == -1) {
486: fprintf(stderr, "failed to readlink %s\n", dep->d_name);
487: return (-1);
488: }
489: target[n] = '\0';
490:
491: hi = hashinfo_from_linkname(dep->d_name, target);
492: if (hi == NULL) {
493: fprintf(stderr, "failed to get hash info %s\n", dep->d_name);
494: return (-1);
495: }
496: hi->exists = 1;
497: *links = hashinfo_chain(*links, hi);
498:
499: return (0);
500: }
501:
502: static int
503: certhash_file(int dfd, struct dirent *dep, struct hashinfo **certs,
504: struct hashinfo **crls)
505: {
506: struct hashinfo *hi = NULL;
507: int has_cert, has_crl;
508: int ffd, ret = -1;
509: BIO *bio = NULL;
510: FILE *f;
511:
512: has_cert = has_crl = 0;
513:
514: if ((ffd = openat(dfd, dep->d_name, O_RDONLY)) == -1) {
515: fprintf(stderr, "failed to open %s\n", dep->d_name);
516: goto err;
517: }
518: if ((f = fdopen(ffd, "r")) == NULL) {
519: fprintf(stderr, "failed to fdopen %s\n", dep->d_name);
520: goto err;
521: }
522: if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
523: fprintf(stderr, "failed to create bio\n");
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: if (ffd != -1)
551: close(ffd);
552:
553: return (ret);
554: }
555:
556: static int
557: certhash_directory(const char *path)
558: {
559: struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
560: int dfd = -1, ret = 0;
561: struct dirent *dep;
562: DIR *dip = NULL;
563:
564: if ((dfd = open(path, O_DIRECTORY)) == -1) {
565: fprintf(stderr, "failed to open directory %s\n", path);
566: goto err;
567: }
568: if ((dip = fdopendir(dfd)) == NULL) {
569: fprintf(stderr, "failed to open directory %s\n", path);
570: goto err;
571: }
572:
573: if (certhash_config.verbose)
574: fprintf(stdout, "scanning directory %s\n", path);
575:
576: /* Create lists of existing hash links, certs and CRLs. */
577: while ((dep = readdir(dip)) != NULL) {
578: if (filename_is_hash(dep->d_name)) {
579: if (certhash_link(dfd, dep, &links) == -1)
580: goto err;
581: }
582: if (filename_is_pem(dep->d_name)) {
583: if (certhash_file(dfd, dep, &certs, &crls) == -1)
584: goto err;
585: }
586: }
587:
588: if (certhash_merge(&links, &certs, &crls) == -1) {
589: fprintf(stderr, "certhash merge failed\n");
590: goto err;
591: }
592:
593: /* Remove spurious links. */
594: for (link = links; link != NULL; link = link->next) {
595: if (link->exists == 0 ||
596: (link->reference != NULL && link->changed == 0))
597: continue;
598: if (certhash_config.verbose)
599: fprintf(stdout, "%s link %s -> %s\n",
600: (certhash_config.dryrun ? "would remove" :
601: "removing"), link->filename, link->target);
602: if (certhash_config.dryrun)
603: continue;
604: if (unlinkat(dfd, link->filename, 0) == -1) {
605: fprintf(stderr, "failed to remove link %s\n",
606: link->filename);
607: goto err;
608: }
609: }
610:
611: /* Create missing links. */
612: for (link = links; link != NULL; link = link->next) {
613: if (link->exists == 1 && link->changed == 0)
614: continue;
615: if (certhash_config.verbose)
616: fprintf(stdout, "%s link %s -> %s\n",
617: (certhash_config.dryrun ? "would create" :
618: "creating"), link->filename,
619: link->reference->filename);
620: if (certhash_config.dryrun)
621: continue;
622: if (symlinkat(link->reference->filename, dfd,
623: link->filename) == -1) {
624: fprintf(stderr, "failed to create link %s -> %s\n",
625: link->filename, link->reference->filename);
626: goto err;
627: }
628: }
629:
630: goto done;
631:
632: err:
633: ret = 1;
634:
635: done:
636: hashinfo_chain_free(certs);
637: hashinfo_chain_free(crls);
638: hashinfo_chain_free(links);
639:
640: if (dip != NULL)
641: closedir(dip);
642: else if (dfd != -1)
643: close(dfd);
644:
645: return (ret);
646: }
647:
648: static void
649: certhash_usage(void)
650: {
651: fprintf(stderr, "usage: certhash [-nv] dir ...\n");
652: options_usage(certhash_options);
653: }
654:
655: int certhash_main(int argc, char **argv);
656:
657: int
658: certhash_main(int argc, char **argv)
659: {
660: int argsused;
661: int i, ret = 0;
662:
663: memset(&certhash_config, 0, sizeof(certhash_config));
664:
665: if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
666: certhash_usage();
667: return (1);
668: }
669:
670: for (i = argsused; i < argc; i++)
671: ret |= certhash_directory(argv[i]);
672:
673: return (ret);
674: }