1 /* $OpenBSD: certhash.c,v 1.19 2021/10/23 08:13:48 tb Exp $ */
3 * Copyright (c) 2014, 2015 Joel Sing <jsing@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/types.h>
29 #include <openssl/bio.h>
30 #include <openssl/evp.h>
31 #include <openssl/pem.h>
32 #include <openssl/x509.h>
41 static const struct option certhash_options
[] = {
44 .desc
= "Perform a dry-run - do not make any changes",
46 .opt
.flag
= &certhash_config
.dryrun
,
52 .opt
.flag
= &certhash_config
.verbose
,
62 unsigned char fingerprint
[EVP_MAX_MD_SIZE
];
67 struct hashinfo
*reference
;
68 struct hashinfo
*next
;
71 static struct hashinfo
*
72 hashinfo(const char *filename
, unsigned long hash
, unsigned char *fingerprint
)
76 if ((hi
= calloc(1, sizeof(*hi
))) == NULL
)
78 if (filename
!= NULL
) {
79 if ((hi
->filename
= strdup(filename
)) == NULL
) {
85 if (fingerprint
!= NULL
)
86 memcpy(hi
->fingerprint
, fingerprint
, sizeof(hi
->fingerprint
));
92 hashinfo_free(struct hashinfo
*hi
)
104 hashinfo_print(struct hashinfo
*hi
)
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' : ':');
118 hashinfo_compare(const void *a
, const void *b
)
120 struct hashinfo
*hia
= *(struct hashinfo
**)a
;
121 struct hashinfo
*hib
= *(struct hashinfo
**)b
;
124 rv
= hia
->hash
< hib
->hash
? -1 : hia
->hash
> hib
->hash
;
127 rv
= memcmp(hia
->fingerprint
, hib
->fingerprint
,
128 sizeof(hia
->fingerprint
));
131 return strcmp(hia
->filename
, hib
->filename
);
134 static struct hashinfo
*
135 hashinfo_chain(struct hashinfo
*head
, struct hashinfo
*entry
)
137 struct hashinfo
*hi
= head
;
141 while (hi
->next
!= NULL
)
149 hashinfo_chain_free(struct hashinfo
*hi
)
151 struct hashinfo
*next
;
161 hashinfo_chain_length(struct hashinfo
*hi
)
173 hashinfo_chain_sort(struct hashinfo
**head
)
175 struct hashinfo
**list
, *entry
;
182 len
= hashinfo_chain_length(*head
);
183 if ((list
= reallocarray(NULL
, len
, sizeof(struct hashinfo
*))) == NULL
)
186 for (entry
= *head
, i
= 0; entry
!= NULL
; entry
= entry
->next
, i
++)
188 qsort(list
, len
, sizeof(struct hashinfo
*), hashinfo_compare
);
190 *head
= entry
= list
[0];
191 for (i
= 1; i
< len
; i
++) {
192 entry
->next
= list
[i
];
202 hashinfo_linkname(struct hashinfo
*hi
)
206 if (asprintf(&filename
, "%08lx.%s%u", hi
->hash
,
207 (hi
->is_crl
? "r" : ""), hi
->index
) == -1)
214 filename_is_hash(const char *filename
)
216 const char *p
= filename
;
218 while ((*p
>= '0' && *p
<= '9') || (*p
>= 'a' && *p
<= 'f'))
222 if (*p
== 'r') /* CRL format. */
224 while (*p
>= '0' && *p
<= '9')
233 filename_is_pem(const char *filename
)
235 const char *q
, *p
= filename
;
237 if ((q
= strchr(p
, '\0')) == NULL
)
241 if (strncmp((q
- 4), ".pem", 4) != 0)
247 static struct hashinfo
*
248 hashinfo_from_linkname(const char *linkname
, const char *target
)
250 struct hashinfo
*hi
= NULL
;
255 if ((l
= strdup(linkname
)) == NULL
)
257 if ((p
= strchr(l
, '.')) == NULL
)
261 if ((hi
= hashinfo(linkname
, 0, NULL
)) == NULL
)
263 if ((hi
->target
= strdup(target
)) == NULL
)
267 val
= strtoll(l
, &ep
, 16);
268 if (l
[0] == '\0' || *ep
!= '\0')
270 if (errno
== ERANGE
&& (val
== LLONG_MAX
|| val
== LLONG_MIN
))
272 if (val
< 0 || val
> ULONG_MAX
)
274 hi
->hash
= (unsigned long)val
;
281 val
= strtonum(p
, 0, 0xffffffff, &errstr
);
285 hi
->index
= (unsigned int)val
;
299 static struct hashinfo
*
300 certhash_cert(BIO
*bio
, const char *filename
)
302 unsigned char fingerprint
[EVP_MAX_MD_SIZE
];
303 struct hashinfo
*hi
= NULL
;
304 const EVP_MD
*digest
;
309 if ((cert
= PEM_read_bio_X509(bio
, NULL
, NULL
, NULL
)) == NULL
)
312 hash
= X509_subject_name_hash(cert
);
314 digest
= EVP_sha256();
315 if (X509_digest(cert
, digest
, fingerprint
, &len
) != 1) {
316 fprintf(stderr
, "out of memory\n");
320 hi
= hashinfo(filename
, hash
, fingerprint
);
328 static struct hashinfo
*
329 certhash_crl(BIO
*bio
, const char *filename
)
331 unsigned char fingerprint
[EVP_MAX_MD_SIZE
];
332 struct hashinfo
*hi
= NULL
;
333 const EVP_MD
*digest
;
334 X509_CRL
*crl
= NULL
;
338 if ((crl
= PEM_read_bio_X509_CRL(bio
, NULL
, NULL
, NULL
)) == NULL
)
341 hash
= X509_NAME_hash(X509_CRL_get_issuer(crl
));
343 digest
= EVP_sha256();
344 if (X509_CRL_digest(crl
, digest
, fingerprint
, &len
) != 1) {
345 fprintf(stderr
, "out of memory\n");
349 hi
= hashinfo(filename
, hash
, fingerprint
);
358 certhash_addlink(struct hashinfo
**links
, struct hashinfo
*hi
)
360 struct hashinfo
*link
= NULL
;
362 if ((link
= hashinfo(NULL
, hi
->hash
, hi
->fingerprint
)) == NULL
)
365 if ((link
->filename
= hashinfo_linkname(hi
)) == NULL
)
368 link
->reference
= hi
;
370 *links
= hashinfo_chain(*links
, link
);
371 hi
->reference
= link
;
381 certhash_findlink(struct hashinfo
*links
, struct hashinfo
*hi
)
383 struct hashinfo
*link
;
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)
394 hi
->reference
= link
;
401 certhash_index(struct hashinfo
*head
, const char *name
)
403 struct hashinfo
*last
, *entry
;
407 for (entry
= head
; entry
!= NULL
; entry
= entry
->next
) {
409 if (entry
->hash
== last
->hash
) {
410 if (memcmp(entry
->fingerprint
,
412 sizeof(entry
->fingerprint
)) == 0) {
413 fprintf(stderr
, "WARNING: duplicate %s "
414 "in %s (using %s), ignoring...\n",
415 name
, entry
->filename
,
425 entry
->index
= index
;
431 certhash_merge(struct hashinfo
**links
, struct hashinfo
**certs
,
432 struct hashinfo
**crls
)
434 struct hashinfo
*cert
, *crl
;
436 /* Pass 1 - sort and index entries. */
437 if (hashinfo_chain_sort(certs
) == -1)
439 if (hashinfo_chain_sort(crls
) == -1)
441 certhash_index(*certs
, "certificate");
442 certhash_index(*crls
, "CRL");
444 /* Pass 2 - map to existing links. */
445 for (cert
= *certs
; cert
!= NULL
; cert
= cert
->next
) {
446 if (cert
->is_dup
== 1)
448 certhash_findlink(*links
, cert
);
450 for (crl
= *crls
; crl
!= NULL
; crl
= crl
->next
) {
451 if (crl
->is_dup
== 1)
453 certhash_findlink(*links
, crl
);
456 /* Pass 3 - determine missing links. */
457 for (cert
= *certs
; cert
!= NULL
; cert
= cert
->next
) {
458 if (cert
->is_dup
== 1 || cert
->reference
!= NULL
)
460 if (certhash_addlink(links
, cert
) == -1)
463 for (crl
= *crls
; crl
!= NULL
; crl
= crl
->next
) {
464 if (crl
->is_dup
== 1 || crl
->reference
!= NULL
)
466 if (certhash_addlink(links
, crl
) == -1)
474 certhash_link(struct dirent
*dep
, struct hashinfo
**links
)
476 struct hashinfo
*hi
= NULL
;
477 char target
[PATH_MAX
];
481 if (lstat(dep
->d_name
, &sb
) == -1) {
482 fprintf(stderr
, "failed to stat %s\n", dep
->d_name
);
485 if (!S_ISLNK(sb
.st_mode
))
488 n
= readlink(dep
->d_name
, target
, sizeof(target
) - 1);
490 fprintf(stderr
, "failed to readlink %s\n", dep
->d_name
);
493 if (n
>= sizeof(target
) - 1) {
494 fprintf(stderr
, "symbolic link is too long %s\n", dep
->d_name
);
499 hi
= hashinfo_from_linkname(dep
->d_name
, target
);
501 fprintf(stderr
, "failed to get hash info %s\n", dep
->d_name
);
505 *links
= hashinfo_chain(*links
, hi
);
511 certhash_file(struct dirent
*dep
, struct hashinfo
**certs
,
512 struct hashinfo
**crls
)
514 struct hashinfo
*hi
= NULL
;
515 int has_cert
, has_crl
;
520 has_cert
= has_crl
= 0;
522 if ((f
= fopen(dep
->d_name
, "r")) == NULL
) {
523 fprintf(stderr
, "failed to fopen %s\n", dep
->d_name
);
526 if ((bio
= BIO_new_fp(f
, BIO_CLOSE
)) == NULL
) {
527 fprintf(stderr
, "failed to create bio\n");
532 if ((hi
= certhash_cert(bio
, dep
->d_name
)) != NULL
) {
534 *certs
= hashinfo_chain(*certs
, hi
);
537 if (BIO_reset(bio
) != 0) {
538 fprintf(stderr
, "BIO_reset failed\n");
542 if ((hi
= certhash_crl(bio
, dep
->d_name
)) != NULL
) {
543 has_crl
= hi
->is_crl
= 1;
544 *crls
= hashinfo_chain(*crls
, hi
);
547 if (!has_cert
&& !has_crl
)
548 fprintf(stderr
, "PEM file %s does not contain a certificate "
549 "or CRL, ignoring...\n", dep
->d_name
);
560 certhash_directory(const char *path
)
562 struct hashinfo
*links
= NULL
, *certs
= NULL
, *crls
= NULL
, *link
;
567 if ((dip
= opendir(".")) == NULL
) {
568 fprintf(stderr
, "failed to open directory %s\n", path
);
572 if (certhash_config
.verbose
)
573 fprintf(stdout
, "scanning directory %s\n", path
);
575 /* Create lists of existing hash links, certs and CRLs. */
576 while ((dep
= readdir(dip
)) != NULL
) {
577 if (filename_is_hash(dep
->d_name
)) {
578 if (certhash_link(dep
, &links
) == -1)
581 if (filename_is_pem(dep
->d_name
)) {
582 if (certhash_file(dep
, &certs
, &crls
) == -1)
587 if (certhash_merge(&links
, &certs
, &crls
) == -1) {
588 fprintf(stderr
, "certhash merge failed\n");
592 /* Remove spurious links. */
593 for (link
= links
; link
!= NULL
; link
= link
->next
) {
594 if (link
->exists
== 0 ||
595 (link
->reference
!= NULL
&& link
->changed
== 0))
597 if (certhash_config
.verbose
)
598 fprintf(stdout
, "%s link %s -> %s\n",
599 (certhash_config
.dryrun
? "would remove" :
600 "removing"), link
->filename
, link
->target
);
601 if (certhash_config
.dryrun
)
603 if (unlink(link
->filename
) == -1) {
604 fprintf(stderr
, "failed to remove link %s\n",
610 /* Create missing links. */
611 for (link
= links
; link
!= NULL
; link
= link
->next
) {
612 if (link
->exists
== 1 && link
->changed
== 0)
614 if (certhash_config
.verbose
)
615 fprintf(stdout
, "%s link %s -> %s\n",
616 (certhash_config
.dryrun
? "would create" :
617 "creating"), link
->filename
,
618 link
->reference
->filename
);
619 if (certhash_config
.dryrun
)
621 if (symlink(link
->reference
->filename
, link
->filename
) == -1) {
622 fprintf(stderr
, "failed to create link %s -> %s\n",
623 link
->filename
, link
->reference
->filename
);
634 hashinfo_chain_free(certs
);
635 hashinfo_chain_free(crls
);
636 hashinfo_chain_free(links
);
646 fprintf(stderr
, "usage: certhash [-nv] dir ...\n");
647 options_usage(certhash_options
);
651 certhash_main(int argc
, char **argv
)
654 int i
, cwdfd
, ret
= 0;
656 if (single_execution
) {
657 if (pledge("stdio cpath wpath rpath", NULL
) == -1) {
663 memset(&certhash_config
, 0, sizeof(certhash_config
));
665 if (options_parse(argc
, argv
, certhash_options
, NULL
, &argsused
) != 0) {
670 if ((cwdfd
= open(".", O_RDONLY
)) == -1) {
671 perror("failed to open current directory");
675 for (i
= argsused
; i
< argc
; i
++) {
676 if (chdir(argv
[i
]) == -1) {
678 "failed to change to directory %s: %s\n",
679 argv
[i
], strerror(errno
));
683 ret
|= certhash_directory(argv
[i
]);
684 if (fchdir(cwdfd
) == -1) {
685 perror("failed to restore current directory");
687 break; /* can't continue safely */