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>
28 #include <openssl/bio.h>
29 #include <openssl/evp.h>
30 #include <openssl/pem.h>
31 #include <openssl/x509.h>
40 struct option certhash_options
[] = {
43 .desc
= "Perform a dry-run - do not make any changes",
45 .opt
.flag
= &certhash_config
.dryrun
,
51 .opt
.flag
= &certhash_config
.verbose
,
61 unsigned char fingerprint
[EVP_MAX_MD_SIZE
];
66 struct hashinfo
*reference
;
67 struct hashinfo
*next
;
70 static struct hashinfo
*
71 hashinfo(const char *filename
, unsigned long hash
, unsigned char *fingerprint
)
75 if ((hi
= calloc(1, sizeof(*hi
))) == NULL
)
77 if (filename
!= NULL
) {
78 if ((hi
->filename
= strdup(filename
)) == NULL
) {
84 if (fingerprint
!= NULL
)
85 memcpy(hi
->fingerprint
, fingerprint
, sizeof(hi
->fingerprint
));
91 hashinfo_free(struct hashinfo
*hi
)
103 hashinfo_print(struct hashinfo
*hi
)
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' : ':');
117 hashinfo_compare(const void *a
, const void *b
)
119 struct hashinfo
*hia
= *(struct hashinfo
**)a
;
120 struct hashinfo
*hib
= *(struct hashinfo
**)b
;
123 rv
= hia
->hash
< hib
->hash
? -1 : hia
->hash
> hib
->hash
;
126 rv
= memcmp(hia
->fingerprint
, hib
->fingerprint
,
127 sizeof(hia
->fingerprint
));
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
;
140 while (hi
->next
!= NULL
)
148 hashinfo_chain_free(struct hashinfo
*hi
)
150 struct hashinfo
*next
;
160 hashinfo_chain_length(struct hashinfo
*hi
)
172 hashinfo_chain_sort(struct hashinfo
**head
)
174 struct hashinfo
**list
, *entry
;
181 len
= hashinfo_chain_length(*head
);
182 if ((list
= reallocarray(NULL
, len
, sizeof(struct hashinfo
*))) == NULL
)
185 for (entry
= *head
, i
= 0; entry
!= NULL
; entry
= entry
->next
, i
++)
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
];
201 hashinfo_linkname(struct hashinfo
*hi
)
205 if (asprintf(&filename
, "%08lx.%s%u", hi
->hash
,
206 (hi
->is_crl
? "r" : ""), hi
->index
) == -1)
213 filename_is_hash(const char *filename
)
215 const char *p
= filename
;
217 while ((*p
>= '0' && *p
<= '9') || (*p
>= 'a' && *p
<= 'f'))
221 if (*p
== 'r') /* CRL format. */
223 while (*p
>= '0' && *p
<= '9')
232 filename_is_pem(const char *filename
)
234 const char *q
, *p
= filename
;
236 if ((q
= strchr(p
, '\0')) == NULL
)
240 if (strncmp((q
- 4), ".pem", 4) != 0)
246 static struct hashinfo
*
247 hashinfo_from_linkname(const char *linkname
, const char *target
)
249 struct hashinfo
*hi
= NULL
;
254 if ((l
= strdup(linkname
)) == NULL
)
256 if ((p
= strchr(l
, '.')) == NULL
)
260 if ((hi
= hashinfo(linkname
, 0, NULL
)) == NULL
)
262 if ((hi
->target
= strdup(target
)) == NULL
)
266 val
= strtoll(l
, &ep
, 16);
267 if (l
[0] == '\0' || *ep
!= '\0')
269 if (errno
== ERANGE
&& (val
== LLONG_MAX
|| val
== LLONG_MIN
))
271 if (val
< 0 || val
> ULONG_MAX
)
273 hi
->hash
= (unsigned long)val
;
280 val
= strtonum(p
, 0, 0xffffffff, &errstr
);
284 hi
->index
= (unsigned int)val
;
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
;
308 if ((cert
= PEM_read_bio_X509(bio
, NULL
, NULL
, NULL
)) == NULL
)
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");
319 hi
= hashinfo(filename
, hash
, fingerprint
);
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
;
337 if ((crl
= PEM_read_bio_X509_CRL(bio
, NULL
, NULL
, NULL
)) == 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");
348 hi
= hashinfo(filename
, hash
, fingerprint
);
357 certhash_addlink(struct hashinfo
**links
, struct hashinfo
*hi
)
359 struct hashinfo
*link
= NULL
;
361 if ((link
= hashinfo(NULL
, hi
->hash
, hi
->fingerprint
)) == NULL
)
364 if ((link
->filename
= hashinfo_linkname(hi
)) == NULL
)
367 link
->reference
= hi
;
369 *links
= hashinfo_chain(*links
, link
);
370 hi
->reference
= link
;
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)
393 hi
->reference
= link
;
400 certhash_index(struct hashinfo
*head
, const char *name
)
402 struct hashinfo
*last
, *entry
;
406 for (entry
= head
; entry
!= NULL
; entry
= entry
->next
) {
408 if (entry
->hash
== last
->hash
) {
409 if (memcmp(entry
->fingerprint
,
411 sizeof(entry
->fingerprint
)) == 0) {
412 fprintf(stderr
, "WARNING: duplicate %s "
413 "in %s (using %s), ignoring...\n",
414 name
, entry
->filename
,
424 entry
->index
= index
;
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)
438 if (hashinfo_chain_sort(crls
) == -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)
447 certhash_findlink(*links
, cert
);
449 for (crl
= *crls
; crl
!= NULL
; crl
= crl
->next
) {
450 if (crl
->is_dup
== 1)
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
)
459 if (certhash_addlink(links
, cert
) == -1)
462 for (crl
= *crls
; crl
!= NULL
; crl
= crl
->next
) {
463 if (crl
->is_dup
== 1 || crl
->reference
!= NULL
)
465 if (certhash_addlink(links
, crl
) == -1)
473 certhash_link(struct dirent
*dep
, struct hashinfo
**links
)
475 struct hashinfo
*hi
= NULL
;
476 char target
[PATH_MAX
];
480 if (lstat(dep
->d_name
, &sb
) == -1) {
481 fprintf(stderr
, "failed to stat %s\n", dep
->d_name
);
484 if (!S_ISLNK(sb
.st_mode
))
487 n
= readlink(dep
->d_name
, target
, sizeof(target
) - 1);
489 fprintf(stderr
, "failed to readlink %s\n", dep
->d_name
);
494 hi
= hashinfo_from_linkname(dep
->d_name
, target
);
496 fprintf(stderr
, "failed to get hash info %s\n", dep
->d_name
);
500 *links
= hashinfo_chain(*links
, hi
);
506 certhash_file(struct dirent
*dep
, struct hashinfo
**certs
,
507 struct hashinfo
**crls
)
509 struct hashinfo
*hi
= NULL
;
510 int has_cert
, has_crl
;
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
);
521 if ((bio
= BIO_new_fp(f
, BIO_CLOSE
)) == NULL
) {
522 fprintf(stderr
, "failed to create bio\n");
527 if ((hi
= certhash_cert(bio
, dep
->d_name
)) != NULL
) {
529 *certs
= hashinfo_chain(*certs
, hi
);
532 if (BIO_reset(bio
) != 0) {
533 fprintf(stderr
, "BIO_reset failed\n");
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
);
555 certhash_directory(const char *path
)
557 struct hashinfo
*links
= NULL
, *certs
= NULL
, *crls
= NULL
, *link
;
562 if ((dip
= opendir(".")) == NULL
) {
563 fprintf(stderr
, "failed to open directory %s\n", path
);
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)
576 if (filename_is_pem(dep
->d_name
)) {
577 if (certhash_file(dep
, &certs
, &crls
) == -1)
582 if (certhash_merge(&links
, &certs
, &crls
) == -1) {
583 fprintf(stderr
, "certhash merge failed\n");
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))
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
)
598 if (unlink(link
->filename
) == -1) {
599 fprintf(stderr
, "failed to remove link %s\n",
605 /* Create missing links. */
606 for (link
= links
; link
!= NULL
; link
= link
->next
) {
607 if (link
->exists
== 1 && link
->changed
== 0)
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
)
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
);
629 hashinfo_chain_free(certs
);
630 hashinfo_chain_free(crls
);
631 hashinfo_chain_free(links
);
641 fprintf(stderr
, "usage: certhash [-nv] dir ...\n");
642 options_usage(certhash_options
);
646 certhash_main(int argc
, char **argv
)
649 int i
, cwdfd
, ret
= 0;
651 if (single_execution
) {
652 if (pledge("stdio cpath wpath rpath", NULL
) == -1) {
658 memset(&certhash_config
, 0, sizeof(certhash_config
));
660 if (options_parse(argc
, argv
, certhash_options
, NULL
, &argsused
) != 0) {
665 if ((cwdfd
= open(".", O_RDONLY
)) == -1) {
666 perror("failed to open current directory");
670 for (i
= argsused
; i
< argc
; i
++) {
671 if (chdir(argv
[i
]) == -1) {
673 "failed to change to directory %s: %s\n",
674 argv
[i
], strerror(errno
));
678 ret
|= certhash_directory(argv
[i
]);
679 if (fchdir(cwdfd
) == -1) {
680 perror("failed to restore current directory");
682 break; /* can't continue safely */