xref: /openbsd-src/usr.bin/openssl/certhash.c (revision e7718adaf9bdf5a32db0f291795197a85ccbb6ed)
1*e7718adaStb /*	$OpenBSD: certhash.c,v 1.21 2023/03/06 14:32:05 tb Exp $ */
233596611Sjsing /*
333596611Sjsing  * Copyright (c) 2014, 2015 Joel Sing <jsing@openbsd.org>
433596611Sjsing  *
533596611Sjsing  * Permission to use, copy, modify, and distribute this software for any
633596611Sjsing  * purpose with or without fee is hereby granted, provided that the above
733596611Sjsing  * copyright notice and this permission notice appear in all copies.
833596611Sjsing  *
933596611Sjsing  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1033596611Sjsing  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1133596611Sjsing  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1233596611Sjsing  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1333596611Sjsing  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1433596611Sjsing  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1533596611Sjsing  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1633596611Sjsing  */
1733596611Sjsing 
1833596611Sjsing #include <sys/types.h>
1933596611Sjsing #include <sys/stat.h>
2033596611Sjsing 
2133596611Sjsing #include <errno.h>
2233596611Sjsing #include <dirent.h>
2333596611Sjsing #include <fcntl.h>
2438ec878bSbcook #include <limits.h>
2533596611Sjsing #include <stdio.h>
2633596611Sjsing #include <string.h>
2733596611Sjsing #include <unistd.h>
2833596611Sjsing 
2933596611Sjsing #include <openssl/bio.h>
3033596611Sjsing #include <openssl/evp.h>
3133596611Sjsing #include <openssl/pem.h>
3233596611Sjsing #include <openssl/x509.h>
3333596611Sjsing 
3433596611Sjsing #include "apps.h"
3533596611Sjsing 
3633596611Sjsing static struct {
3733596611Sjsing 	int dryrun;
3833596611Sjsing 	int verbose;
39*e7718adaStb } cfg;
4033596611Sjsing 
41ea149709Sguenther static const struct option certhash_options[] = {
4233596611Sjsing 	{
4333596611Sjsing 		.name = "n",
4433596611Sjsing 		.desc = "Perform a dry-run - do not make any changes",
4533596611Sjsing 		.type = OPTION_FLAG,
46*e7718adaStb 		.opt.flag = &cfg.dryrun,
4733596611Sjsing 	},
4833596611Sjsing 	{
4933596611Sjsing 		.name = "v",
5033596611Sjsing 		.desc = "Verbose",
5133596611Sjsing 		.type = OPTION_FLAG,
52*e7718adaStb 		.opt.flag = &cfg.verbose,
5333596611Sjsing 	},
5433596611Sjsing 	{ NULL },
5533596611Sjsing };
5633596611Sjsing 
5733596611Sjsing struct hashinfo {
5833596611Sjsing 	char *filename;
5933596611Sjsing 	char *target;
6033596611Sjsing 	unsigned long hash;
6133596611Sjsing 	unsigned int index;
6233596611Sjsing 	unsigned char fingerprint[EVP_MAX_MD_SIZE];
6333596611Sjsing 	int is_crl;
6433596611Sjsing 	int is_dup;
6533596611Sjsing 	int exists;
6633596611Sjsing 	int changed;
6733596611Sjsing 	struct hashinfo *reference;
6833596611Sjsing 	struct hashinfo *next;
6933596611Sjsing };
7033596611Sjsing 
7133596611Sjsing static struct hashinfo *
hashinfo(const char * filename,unsigned long hash,unsigned char * fingerprint)7233596611Sjsing hashinfo(const char *filename, unsigned long hash, unsigned char *fingerprint)
7333596611Sjsing {
7433596611Sjsing 	struct hashinfo *hi;
7533596611Sjsing 
7633596611Sjsing 	if ((hi = calloc(1, sizeof(*hi))) == NULL)
7733596611Sjsing 		return (NULL);
7833596611Sjsing 	if (filename != NULL) {
7933596611Sjsing 		if ((hi->filename = strdup(filename)) == NULL) {
8033596611Sjsing 			free(hi);
8133596611Sjsing 			return (NULL);
8233596611Sjsing 		}
8333596611Sjsing 	}
8433596611Sjsing 	hi->hash = hash;
8533596611Sjsing 	if (fingerprint != NULL)
8633596611Sjsing 		memcpy(hi->fingerprint, fingerprint, sizeof(hi->fingerprint));
8733596611Sjsing 
8833596611Sjsing 	return (hi);
8933596611Sjsing }
9033596611Sjsing 
9133596611Sjsing static void
hashinfo_free(struct hashinfo * hi)9233596611Sjsing hashinfo_free(struct hashinfo *hi)
9333596611Sjsing {
9407628f06Sdoug 	if (hi == NULL)
9507628f06Sdoug 		return;
9607628f06Sdoug 
9733596611Sjsing 	free(hi->filename);
9833596611Sjsing 	free(hi->target);
9933596611Sjsing 	free(hi);
10033596611Sjsing }
10133596611Sjsing 
10233596611Sjsing #ifdef DEBUG
10333596611Sjsing static void
hashinfo_print(struct hashinfo * hi)10433596611Sjsing hashinfo_print(struct hashinfo *hi)
10533596611Sjsing {
10633596611Sjsing 	int i;
10733596611Sjsing 
10833596611Sjsing 	printf("hashinfo %s %08lx %u %i\n", hi->filename, hi->hash,
10933596611Sjsing 	    hi->index, hi->is_crl);
11033596611Sjsing 	for (i = 0; i < (int)EVP_MAX_MD_SIZE; i++) {
11133596611Sjsing 		printf("%02X%c", hi->fingerprint[i],
11233596611Sjsing 		    (i + 1 == (int)EVP_MAX_MD_SIZE) ? '\n' : ':');
11333596611Sjsing 	}
11433596611Sjsing }
11533596611Sjsing #endif
11633596611Sjsing 
11733596611Sjsing static int
hashinfo_compare(const void * a,const void * b)11833596611Sjsing hashinfo_compare(const void *a, const void *b)
11933596611Sjsing {
12033596611Sjsing 	struct hashinfo *hia = *(struct hashinfo **)a;
12133596611Sjsing 	struct hashinfo *hib = *(struct hashinfo **)b;
12233596611Sjsing 	int rv;
12333596611Sjsing 
12409a3af8fStedu 	rv = hia->hash < hib->hash ? -1 : hia->hash > hib->hash;
12533596611Sjsing 	if (rv != 0)
12633596611Sjsing 		return (rv);
12713a74dccSguenther 	rv = memcmp(hia->fingerprint, hib->fingerprint,
12813a74dccSguenther 	    sizeof(hia->fingerprint));
12933596611Sjsing 	if (rv != 0)
13033596611Sjsing 		return (rv);
13133596611Sjsing 	return strcmp(hia->filename, hib->filename);
13233596611Sjsing }
13333596611Sjsing 
13433596611Sjsing static struct hashinfo *
hashinfo_chain(struct hashinfo * head,struct hashinfo * entry)13533596611Sjsing hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
13633596611Sjsing {
13733596611Sjsing 	struct hashinfo *hi = head;
13833596611Sjsing 
13933596611Sjsing 	if (hi == NULL)
14033596611Sjsing 		return (entry);
14133596611Sjsing 	while (hi->next != NULL)
14233596611Sjsing 		hi = hi->next;
14333596611Sjsing 	hi->next = entry;
14433596611Sjsing 
14533596611Sjsing 	return (head);
14633596611Sjsing }
14733596611Sjsing 
14833596611Sjsing static void
hashinfo_chain_free(struct hashinfo * hi)14933596611Sjsing hashinfo_chain_free(struct hashinfo *hi)
15033596611Sjsing {
15133596611Sjsing 	struct hashinfo *next;
15233596611Sjsing 
15333596611Sjsing 	while (hi != NULL) {
15433596611Sjsing 		next = hi->next;
15533596611Sjsing 		hashinfo_free(hi);
15633596611Sjsing 		hi = next;
15733596611Sjsing 	}
15833596611Sjsing }
15933596611Sjsing 
16033596611Sjsing static size_t
hashinfo_chain_length(struct hashinfo * hi)16133596611Sjsing hashinfo_chain_length(struct hashinfo *hi)
16233596611Sjsing {
16333596611Sjsing 	int len = 0;
16433596611Sjsing 
16533596611Sjsing 	while (hi != NULL) {
16633596611Sjsing 		len++;
16733596611Sjsing 		hi = hi->next;
16833596611Sjsing 	}
16933596611Sjsing 	return (len);
17033596611Sjsing }
17133596611Sjsing 
17233596611Sjsing static int
hashinfo_chain_sort(struct hashinfo ** head)17333596611Sjsing hashinfo_chain_sort(struct hashinfo **head)
17433596611Sjsing {
17533596611Sjsing 	struct hashinfo **list, *entry;
17633596611Sjsing 	size_t len;
17733596611Sjsing 	int i;
17833596611Sjsing 
17933596611Sjsing 	if (*head == NULL)
18033596611Sjsing 		return (0);
18133596611Sjsing 
18233596611Sjsing 	len = hashinfo_chain_length(*head);
18333596611Sjsing 	if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
18433596611Sjsing 		return (-1);
18533596611Sjsing 
18633596611Sjsing 	for (entry = *head, i = 0; entry != NULL; entry = entry->next, i++)
18733596611Sjsing 		list[i] = entry;
18833596611Sjsing 	qsort(list, len, sizeof(struct hashinfo *), hashinfo_compare);
18933596611Sjsing 
19033596611Sjsing 	*head = entry = list[0];
19133596611Sjsing 	for (i = 1; i < len; i++) {
19233596611Sjsing 		entry->next = list[i];
19333596611Sjsing 		entry = list[i];
19433596611Sjsing 	}
19533596611Sjsing 	entry->next = NULL;
19633596611Sjsing 
1975558f497Sbeck 	free(list);
19833596611Sjsing 	return (0);
19933596611Sjsing }
20033596611Sjsing 
20133596611Sjsing static char *
hashinfo_linkname(struct hashinfo * hi)20233596611Sjsing hashinfo_linkname(struct hashinfo *hi)
20333596611Sjsing {
20433596611Sjsing 	char *filename;
20533596611Sjsing 
20633596611Sjsing 	if (asprintf(&filename, "%08lx.%s%u", hi->hash,
20733596611Sjsing 	    (hi->is_crl ? "r" : ""), hi->index) == -1)
20833596611Sjsing 		return (NULL);
20933596611Sjsing 
21033596611Sjsing 	return (filename);
21133596611Sjsing }
21233596611Sjsing 
21333596611Sjsing static int
filename_is_hash(const char * filename)21433596611Sjsing filename_is_hash(const char *filename)
21533596611Sjsing {
21633596611Sjsing 	const char *p = filename;
21733596611Sjsing 
21833596611Sjsing 	while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
21933596611Sjsing 		p++;
22033596611Sjsing 	if (*p++ != '.')
22133596611Sjsing 		return (0);
22233596611Sjsing 	if (*p == 'r')		/* CRL format. */
22333596611Sjsing 		p++;
22433596611Sjsing 	while (*p >= '0' && *p <= '9')
22533596611Sjsing 		p++;
22633596611Sjsing 	if (*p != '\0')
22733596611Sjsing 		return (0);
22833596611Sjsing 
22933596611Sjsing 	return (1);
23033596611Sjsing }
23133596611Sjsing 
23233596611Sjsing static int
filename_is_pem(const char * filename)23333596611Sjsing filename_is_pem(const char *filename)
23433596611Sjsing {
23533596611Sjsing 	const char *q, *p = filename;
23633596611Sjsing 
23733596611Sjsing 	if ((q = strchr(p, '\0')) == NULL)
23833596611Sjsing 		return (0);
23933596611Sjsing 	if ((q - p) < 4)
24033596611Sjsing 		return (0);
24133596611Sjsing 	if (strncmp((q - 4), ".pem", 4) != 0)
24233596611Sjsing 		return (0);
24333596611Sjsing 
24433596611Sjsing 	return (1);
24533596611Sjsing }
24633596611Sjsing 
24733596611Sjsing static struct hashinfo *
hashinfo_from_linkname(const char * linkname,const char * target)24833596611Sjsing hashinfo_from_linkname(const char *linkname, const char *target)
24933596611Sjsing {
25033596611Sjsing 	struct hashinfo *hi = NULL;
25133596611Sjsing 	const char *errstr;
25233596611Sjsing 	char *l, *p, *ep;
25333596611Sjsing 	long long val;
25433596611Sjsing 
25533596611Sjsing 	if ((l = strdup(linkname)) == NULL)
25633596611Sjsing 		goto err;
25733596611Sjsing 	if ((p = strchr(l, '.')) == NULL)
25833596611Sjsing 		goto err;
25933596611Sjsing 	*p++ = '\0';
26033596611Sjsing 
26133596611Sjsing 	if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
26233596611Sjsing 		goto err;
26333596611Sjsing 	if ((hi->target = strdup(target)) == NULL)
26433596611Sjsing 		goto err;
26533596611Sjsing 
26633596611Sjsing 	errno = 0;
26733596611Sjsing 	val = strtoll(l, &ep, 16);
26833596611Sjsing 	if (l[0] == '\0' || *ep != '\0')
26933596611Sjsing 		goto err;
2703b51259bSbeck 	if (errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN))
27133596611Sjsing 		goto err;
27233596611Sjsing 	if (val < 0 || val > ULONG_MAX)
27333596611Sjsing 		goto err;
27433596611Sjsing 	hi->hash = (unsigned long)val;
27533596611Sjsing 
27633596611Sjsing 	if (*p == 'r') {
27733596611Sjsing 		hi->is_crl = 1;
27833596611Sjsing 		p++;
27933596611Sjsing 	}
28033596611Sjsing 
28133596611Sjsing 	val = strtonum(p, 0, 0xffffffff, &errstr);
28233596611Sjsing 	if (errstr != NULL)
28333596611Sjsing 		goto err;
28433596611Sjsing 
28533596611Sjsing 	hi->index = (unsigned int)val;
28633596611Sjsing 
28733596611Sjsing 	goto done;
28833596611Sjsing 
28933596611Sjsing  err:
29033596611Sjsing 	hashinfo_free(hi);
29133596611Sjsing 	hi = NULL;
29233596611Sjsing 
29333596611Sjsing  done:
29433596611Sjsing 	free(l);
29533596611Sjsing 
29633596611Sjsing 	return (hi);
29733596611Sjsing }
29833596611Sjsing 
29933596611Sjsing static struct hashinfo *
certhash_cert(BIO * bio,const char * filename)30033596611Sjsing certhash_cert(BIO *bio, const char *filename)
30133596611Sjsing {
30233596611Sjsing 	unsigned char fingerprint[EVP_MAX_MD_SIZE];
30333596611Sjsing 	struct hashinfo *hi = NULL;
30433596611Sjsing 	const EVP_MD *digest;
30533596611Sjsing 	X509 *cert = NULL;
30633596611Sjsing 	unsigned long hash;
30733596611Sjsing 	unsigned int len;
30833596611Sjsing 
30933596611Sjsing 	if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
31033596611Sjsing 		goto err;
31133596611Sjsing 
31233596611Sjsing 	hash = X509_subject_name_hash(cert);
31333596611Sjsing 
31433596611Sjsing 	digest = EVP_sha256();
31533596611Sjsing 	if (X509_digest(cert, digest, fingerprint, &len) != 1) {
31633596611Sjsing 		fprintf(stderr, "out of memory\n");
31733596611Sjsing 		goto err;
31833596611Sjsing 	}
31933596611Sjsing 
32033596611Sjsing 	hi = hashinfo(filename, hash, fingerprint);
32133596611Sjsing 
32233596611Sjsing  err:
32333596611Sjsing 	X509_free(cert);
32433596611Sjsing 
32533596611Sjsing 	return (hi);
32633596611Sjsing }
32733596611Sjsing 
32833596611Sjsing static struct hashinfo *
certhash_crl(BIO * bio,const char * filename)32933596611Sjsing certhash_crl(BIO *bio, const char *filename)
33033596611Sjsing {
33133596611Sjsing 	unsigned char fingerprint[EVP_MAX_MD_SIZE];
33233596611Sjsing 	struct hashinfo *hi = NULL;
33333596611Sjsing 	const EVP_MD *digest;
33433596611Sjsing 	X509_CRL *crl = NULL;
33533596611Sjsing 	unsigned long hash;
33633596611Sjsing 	unsigned int len;
33733596611Sjsing 
33833596611Sjsing 	if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
33933596611Sjsing 		return (NULL);
34033596611Sjsing 
34133596611Sjsing 	hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
34233596611Sjsing 
34333596611Sjsing 	digest = EVP_sha256();
34433596611Sjsing 	if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
34533596611Sjsing 		fprintf(stderr, "out of memory\n");
34633596611Sjsing 		goto err;
34733596611Sjsing 	}
34833596611Sjsing 
34933596611Sjsing 	hi = hashinfo(filename, hash, fingerprint);
35033596611Sjsing 
35133596611Sjsing  err:
35233596611Sjsing 	X509_CRL_free(crl);
35333596611Sjsing 
35433596611Sjsing 	return (hi);
35533596611Sjsing }
35633596611Sjsing 
35733596611Sjsing static int
certhash_addlink(struct hashinfo ** links,struct hashinfo * hi)35833596611Sjsing certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
35933596611Sjsing {
36033596611Sjsing 	struct hashinfo *link = NULL;
36133596611Sjsing 
36233596611Sjsing 	if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
36333596611Sjsing 		goto err;
36433596611Sjsing 
36533596611Sjsing 	if ((link->filename = hashinfo_linkname(hi)) == NULL)
36633596611Sjsing 		goto err;
36733596611Sjsing 
36833596611Sjsing 	link->reference = hi;
36933596611Sjsing 	link->changed = 1;
37033596611Sjsing 	*links = hashinfo_chain(*links, link);
37133596611Sjsing 	hi->reference = link;
37233596611Sjsing 
37333596611Sjsing 	return (0);
37433596611Sjsing 
37533596611Sjsing  err:
37633596611Sjsing 	hashinfo_free(link);
37733596611Sjsing 	return (-1);
37833596611Sjsing }
37933596611Sjsing 
38033596611Sjsing static void
certhash_findlink(struct hashinfo * links,struct hashinfo * hi)38133596611Sjsing certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
38233596611Sjsing {
38333596611Sjsing 	struct hashinfo *link;
38433596611Sjsing 
38533596611Sjsing 	for (link = links; link != NULL; link = link->next) {
38633596611Sjsing 		if (link->is_crl == hi->is_crl &&
38733596611Sjsing 		    link->hash == hi->hash &&
38833596611Sjsing 		    link->index == hi->index &&
38933596611Sjsing 		    link->reference == NULL) {
39033596611Sjsing 			link->reference = hi;
39133596611Sjsing 			if (link->target == NULL ||
39233596611Sjsing 			    strcmp(link->target, hi->filename) != 0)
39333596611Sjsing 				link->changed = 1;
39433596611Sjsing 			hi->reference = link;
39533596611Sjsing 			break;
39633596611Sjsing 		}
39733596611Sjsing 	}
39833596611Sjsing }
39933596611Sjsing 
40033596611Sjsing static void
certhash_index(struct hashinfo * head,const char * name)40133596611Sjsing certhash_index(struct hashinfo *head, const char *name)
40233596611Sjsing {
40333596611Sjsing 	struct hashinfo *last, *entry;
40433596611Sjsing 	int index = 0;
40533596611Sjsing 
40633596611Sjsing 	last = NULL;
40733596611Sjsing 	for (entry = head; entry != NULL; entry = entry->next) {
40833596611Sjsing 		if (last != NULL) {
40933596611Sjsing 			if (entry->hash == last->hash) {
41013a74dccSguenther 				if (memcmp(entry->fingerprint,
41113a74dccSguenther 				    last->fingerprint,
41233596611Sjsing 				    sizeof(entry->fingerprint)) == 0) {
41333596611Sjsing 					fprintf(stderr, "WARNING: duplicate %s "
41433596611Sjsing 					    "in %s (using %s), ignoring...\n",
41533596611Sjsing 					    name, entry->filename,
41633596611Sjsing 					    last->filename);
41733596611Sjsing 					entry->is_dup = 1;
41833596611Sjsing 					continue;
41933596611Sjsing 				}
42033596611Sjsing 				index++;
42133596611Sjsing 			} else {
42233596611Sjsing 				index = 0;
42333596611Sjsing 			}
42433596611Sjsing 		}
42533596611Sjsing 		entry->index = index;
42633596611Sjsing 		last = entry;
42733596611Sjsing 	}
42833596611Sjsing }
42933596611Sjsing 
43033596611Sjsing static int
certhash_merge(struct hashinfo ** links,struct hashinfo ** certs,struct hashinfo ** crls)43133596611Sjsing certhash_merge(struct hashinfo **links, struct hashinfo **certs,
43233596611Sjsing     struct hashinfo **crls)
43333596611Sjsing {
43433596611Sjsing 	struct hashinfo *cert, *crl;
43533596611Sjsing 
43633596611Sjsing 	/* Pass 1 - sort and index entries. */
43733596611Sjsing 	if (hashinfo_chain_sort(certs) == -1)
43833596611Sjsing 		return (-1);
43933596611Sjsing 	if (hashinfo_chain_sort(crls) == -1)
44033596611Sjsing 		return (-1);
44133596611Sjsing 	certhash_index(*certs, "certificate");
44233596611Sjsing 	certhash_index(*crls, "CRL");
44333596611Sjsing 
44433596611Sjsing 	/* Pass 2 - map to existing links. */
44533596611Sjsing 	for (cert = *certs; cert != NULL; cert = cert->next) {
44633596611Sjsing 		if (cert->is_dup == 1)
44733596611Sjsing 			continue;
44833596611Sjsing 		certhash_findlink(*links, cert);
44933596611Sjsing 	}
45033596611Sjsing 	for (crl = *crls; crl != NULL; crl = crl->next) {
45133596611Sjsing 		if (crl->is_dup == 1)
45233596611Sjsing 			continue;
45333596611Sjsing 		certhash_findlink(*links, crl);
45433596611Sjsing 	}
45533596611Sjsing 
45633596611Sjsing 	/* Pass 3 - determine missing links. */
45733596611Sjsing 	for (cert = *certs; cert != NULL; cert = cert->next) {
45833596611Sjsing 		if (cert->is_dup == 1 || cert->reference != NULL)
45933596611Sjsing 			continue;
46033596611Sjsing 		if (certhash_addlink(links, cert) == -1)
46133596611Sjsing 			return (-1);
46233596611Sjsing 	}
46333596611Sjsing 	for (crl = *crls; crl != NULL; crl = crl->next) {
46433596611Sjsing 		if (crl->is_dup == 1 || crl->reference != NULL)
46533596611Sjsing 			continue;
46633596611Sjsing 		if (certhash_addlink(links, crl) == -1)
46733596611Sjsing 			return (-1);
46833596611Sjsing 	}
46933596611Sjsing 
47033596611Sjsing 	return (0);
47133596611Sjsing }
47233596611Sjsing 
47333596611Sjsing static int
certhash_link(struct dirent * dep,struct hashinfo ** links)474c339397cSguenther certhash_link(struct dirent *dep, struct hashinfo **links)
47533596611Sjsing {
47633596611Sjsing 	struct hashinfo *hi = NULL;
477b19fe0adSderaadt 	char target[PATH_MAX];
47833596611Sjsing 	struct stat sb;
47933596611Sjsing 	int n;
48033596611Sjsing 
481c339397cSguenther 	if (lstat(dep->d_name, &sb) == -1) {
48233596611Sjsing 		fprintf(stderr, "failed to stat %s\n", dep->d_name);
48333596611Sjsing 		return (-1);
48433596611Sjsing 	}
48533596611Sjsing 	if (!S_ISLNK(sb.st_mode))
48633596611Sjsing 		return (0);
48733596611Sjsing 
488c339397cSguenther 	n = readlink(dep->d_name, target, sizeof(target) - 1);
48933596611Sjsing 	if (n == -1) {
49033596611Sjsing 		fprintf(stderr, "failed to readlink %s\n", dep->d_name);
49133596611Sjsing 		return (-1);
49233596611Sjsing 	}
4933b269dc8Stb 	if (n >= sizeof(target) - 1) {
4943b269dc8Stb 		fprintf(stderr, "symbolic link is too long %s\n", dep->d_name);
4953b269dc8Stb 		return (-1);
4963b269dc8Stb 	}
49733596611Sjsing 	target[n] = '\0';
49833596611Sjsing 
49933596611Sjsing 	hi = hashinfo_from_linkname(dep->d_name, target);
50033596611Sjsing 	if (hi == NULL) {
50133596611Sjsing 		fprintf(stderr, "failed to get hash info %s\n", dep->d_name);
50233596611Sjsing 		return (-1);
50333596611Sjsing 	}
50433596611Sjsing 	hi->exists = 1;
50533596611Sjsing 	*links = hashinfo_chain(*links, hi);
50633596611Sjsing 
50733596611Sjsing 	return (0);
50833596611Sjsing }
50933596611Sjsing 
51033596611Sjsing static int
certhash_file(struct dirent * dep,struct hashinfo ** certs,struct hashinfo ** crls)511c339397cSguenther certhash_file(struct dirent *dep, struct hashinfo **certs,
51233596611Sjsing     struct hashinfo **crls)
51333596611Sjsing {
51433596611Sjsing 	struct hashinfo *hi = NULL;
51533596611Sjsing 	int has_cert, has_crl;
516c339397cSguenther 	int ret = -1;
51733596611Sjsing 	BIO *bio = NULL;
51833596611Sjsing 	FILE *f;
51933596611Sjsing 
52033596611Sjsing 	has_cert = has_crl = 0;
52133596611Sjsing 
522c339397cSguenther 	if ((f = fopen(dep->d_name, "r")) == NULL) {
523c339397cSguenther 		fprintf(stderr, "failed to fopen %s\n", dep->d_name);
52433596611Sjsing 		goto err;
52533596611Sjsing 	}
52633596611Sjsing 	if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
52733596611Sjsing 		fprintf(stderr, "failed to create bio\n");
528c339397cSguenther 		fclose(f);
52933596611Sjsing 		goto err;
53033596611Sjsing 	}
53133596611Sjsing 
53233596611Sjsing 	if ((hi = certhash_cert(bio, dep->d_name)) != NULL) {
53333596611Sjsing 		has_cert = 1;
53433596611Sjsing 		*certs = hashinfo_chain(*certs, hi);
53533596611Sjsing 	}
53633596611Sjsing 
53733596611Sjsing 	if (BIO_reset(bio) != 0) {
53833596611Sjsing 		fprintf(stderr, "BIO_reset failed\n");
53933596611Sjsing 		goto err;
54033596611Sjsing 	}
54133596611Sjsing 
54233596611Sjsing 	if ((hi = certhash_crl(bio, dep->d_name)) != NULL) {
54333596611Sjsing 		has_crl = hi->is_crl = 1;
54433596611Sjsing 		*crls = hashinfo_chain(*crls, hi);
54533596611Sjsing 	}
54633596611Sjsing 
54733596611Sjsing 	if (!has_cert && !has_crl)
54833596611Sjsing 		fprintf(stderr, "PEM file %s does not contain a certificate "
54933596611Sjsing 		    "or CRL, ignoring...\n", dep->d_name);
55033596611Sjsing 
55133596611Sjsing 	ret = 0;
55233596611Sjsing 
55333596611Sjsing  err:
55433596611Sjsing 	BIO_free(bio);
55533596611Sjsing 
55633596611Sjsing 	return (ret);
55733596611Sjsing }
55833596611Sjsing 
55933596611Sjsing static int
certhash_directory(const char * path)56033596611Sjsing certhash_directory(const char *path)
56133596611Sjsing {
56233596611Sjsing 	struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
563c339397cSguenther 	int ret = 0;
56433596611Sjsing 	struct dirent *dep;
56533596611Sjsing 	DIR *dip = NULL;
56633596611Sjsing 
567c339397cSguenther 	if ((dip = opendir(".")) == NULL) {
56833596611Sjsing 		fprintf(stderr, "failed to open directory %s\n", path);
56933596611Sjsing 		goto err;
57033596611Sjsing 	}
57133596611Sjsing 
572*e7718adaStb 	if (cfg.verbose)
57333596611Sjsing 		fprintf(stdout, "scanning directory %s\n", path);
57433596611Sjsing 
57533596611Sjsing 	/* Create lists of existing hash links, certs and CRLs. */
57633596611Sjsing 	while ((dep = readdir(dip)) != NULL) {
57733596611Sjsing 		if (filename_is_hash(dep->d_name)) {
578c339397cSguenther 			if (certhash_link(dep, &links) == -1)
57933596611Sjsing 				goto err;
58033596611Sjsing 		}
58133596611Sjsing 		if (filename_is_pem(dep->d_name)) {
582c339397cSguenther 			if (certhash_file(dep, &certs, &crls) == -1)
58333596611Sjsing 				goto err;
58433596611Sjsing 		}
58533596611Sjsing 	}
58633596611Sjsing 
58733596611Sjsing 	if (certhash_merge(&links, &certs, &crls) == -1) {
58833596611Sjsing 		fprintf(stderr, "certhash merge failed\n");
58933596611Sjsing 		goto err;
59033596611Sjsing 	}
59133596611Sjsing 
59233596611Sjsing 	/* Remove spurious links. */
59333596611Sjsing 	for (link = links; link != NULL; link = link->next) {
59433596611Sjsing 		if (link->exists == 0 ||
59533596611Sjsing 		    (link->reference != NULL && link->changed == 0))
59633596611Sjsing 			continue;
597*e7718adaStb 		if (cfg.verbose)
59833596611Sjsing 			fprintf(stdout, "%s link %s -> %s\n",
599*e7718adaStb 			    (cfg.dryrun ? "would remove" :
60033596611Sjsing 				"removing"), link->filename, link->target);
601*e7718adaStb 		if (cfg.dryrun)
60233596611Sjsing 			continue;
603c339397cSguenther 		if (unlink(link->filename) == -1) {
60433596611Sjsing 			fprintf(stderr, "failed to remove link %s\n",
60533596611Sjsing 			    link->filename);
60633596611Sjsing 			goto err;
60733596611Sjsing 		}
60833596611Sjsing 	}
60933596611Sjsing 
61033596611Sjsing 	/* Create missing links. */
61133596611Sjsing 	for (link = links; link != NULL; link = link->next) {
61233596611Sjsing 		if (link->exists == 1 && link->changed == 0)
61333596611Sjsing 			continue;
614*e7718adaStb 		if (cfg.verbose)
61533596611Sjsing 			fprintf(stdout, "%s link %s -> %s\n",
616*e7718adaStb 			    (cfg.dryrun ? "would create" :
61733596611Sjsing 				"creating"), link->filename,
61833596611Sjsing 			    link->reference->filename);
619*e7718adaStb 		if (cfg.dryrun)
62033596611Sjsing 			continue;
621c339397cSguenther 		if (symlink(link->reference->filename, link->filename) == -1) {
62233596611Sjsing 			fprintf(stderr, "failed to create link %s -> %s\n",
62333596611Sjsing 			    link->filename, link->reference->filename);
62433596611Sjsing 			goto err;
62533596611Sjsing 		}
62633596611Sjsing 	}
62733596611Sjsing 
62833596611Sjsing 	goto done;
62933596611Sjsing 
63033596611Sjsing  err:
63133596611Sjsing 	ret = 1;
63233596611Sjsing 
63333596611Sjsing  done:
63433596611Sjsing 	hashinfo_chain_free(certs);
63533596611Sjsing 	hashinfo_chain_free(crls);
63633596611Sjsing 	hashinfo_chain_free(links);
63733596611Sjsing 
63833596611Sjsing 	if (dip != NULL)
63933596611Sjsing 		closedir(dip);
64033596611Sjsing 	return (ret);
64133596611Sjsing }
64233596611Sjsing 
64333596611Sjsing static void
certhash_usage(void)64433596611Sjsing certhash_usage(void)
64533596611Sjsing {
64633596611Sjsing 	fprintf(stderr, "usage: certhash [-nv] dir ...\n");
64733596611Sjsing 	options_usage(certhash_options);
64833596611Sjsing }
64933596611Sjsing 
65033596611Sjsing int
certhash_main(int argc,char ** argv)65133596611Sjsing certhash_main(int argc, char **argv)
65233596611Sjsing {
65333596611Sjsing 	int argsused;
654c339397cSguenther 	int i, cwdfd, ret = 0;
65533596611Sjsing 
65651811eadSderaadt 	if (pledge("stdio cpath wpath rpath", NULL) == -1) {
6579bc487adSdoug 		perror("pledge");
658e370f0eeSdoug 		exit(1);
659e370f0eeSdoug 	}
6609bc487adSdoug 
661*e7718adaStb 	memset(&cfg, 0, sizeof(cfg));
66233596611Sjsing 
66333596611Sjsing 	if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
66433596611Sjsing                 certhash_usage();
66533596611Sjsing                 return (1);
66633596611Sjsing         }
66733596611Sjsing 
66810ff8fbeSmillert 	if ((cwdfd = open(".", O_RDONLY)) == -1) {
669c339397cSguenther 		perror("failed to open current directory");
670c339397cSguenther 		return (1);
671c339397cSguenther 	}
672c339397cSguenther 
673c339397cSguenther 	for (i = argsused; i < argc; i++) {
674c339397cSguenther 		if (chdir(argv[i]) == -1) {
675c339397cSguenther 			fprintf(stderr,
676c339397cSguenther 			    "failed to change to directory %s: %s\n",
677c339397cSguenther 			    argv[i], strerror(errno));
678c339397cSguenther 			ret = 1;
679c339397cSguenther 			continue;
680c339397cSguenther 		}
68133596611Sjsing 		ret |= certhash_directory(argv[i]);
682c339397cSguenther 		if (fchdir(cwdfd) == -1) {
683c339397cSguenther 			perror("failed to restore current directory");
684c339397cSguenther 			ret = 1;
685c339397cSguenther 			break;		/* can't continue safely */
686c339397cSguenther 		}
687c339397cSguenther 	}
688c339397cSguenther 	close(cwdfd);
68933596611Sjsing 
69033596611Sjsing 	return (ret);
69133596611Sjsing }
692