GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/openssl/certhash.c Lines: 179 305 58.7 %
Date: 2017-11-07 Branches: 105 220 47.7 %

Line Branch Exec Source
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/types.h>
18
#include <sys/stat.h>
19
20
#include <errno.h>
21
#include <dirent.h>
22
#include <fcntl.h>
23
#include <limits.h>
24
#include <stdio.h>
25
#include <string.h>
26
#include <unistd.h>
27
28
#include <openssl/bio.h>
29
#include <openssl/evp.h>
30
#include <openssl/pem.h>
31
#include <openssl/x509.h>
32
33
#include "apps.h"
34
35
static struct {
36
	int dryrun;
37
	int verbose;
38
} certhash_config;
39
40
struct option certhash_options[] = {
41
	{
42
		.name = "n",
43
		.desc = "Perform a dry-run - do not make any changes",
44
		.type = OPTION_FLAG,
45
		.opt.flag = &certhash_config.dryrun,
46
	},
47
	{
48
		.name = "v",
49
		.desc = "Verbose",
50
		.type = OPTION_FLAG,
51
		.opt.flag = &certhash_config.verbose,
52
	},
53
	{ NULL },
54
};
55
56
struct hashinfo {
57
	char *filename;
58
	char *target;
59
	unsigned long hash;
60
	unsigned int index;
61
	unsigned char fingerprint[EVP_MAX_MD_SIZE];
62
	int is_crl;
63
	int is_dup;
64
	int exists;
65
	int changed;
66
	struct hashinfo *reference;
67
	struct hashinfo *next;
68
};
69
70
static struct hashinfo *
71
hashinfo(const char *filename, unsigned long hash, unsigned char *fingerprint)
72
{
73
	struct hashinfo *hi;
74
75
64
	if ((hi = calloc(1, sizeof(*hi))) == NULL)
76
		return (NULL);
77
32
	if (filename != NULL) {
78
16
		if ((hi->filename = strdup(filename)) == NULL) {
79
			free(hi);
80
			return (NULL);
81
		}
82
	}
83
32
	hi->hash = hash;
84
32
	if (fingerprint != NULL)
85
32
		memcpy(hi->fingerprint, fingerprint, sizeof(hi->fingerprint));
86
87
32
	return (hi);
88
32
}
89
90
static void
91
hashinfo_free(struct hashinfo *hi)
92
{
93
64
	if (hi == NULL)
94
		return;
95
96
32
	free(hi->filename);
97
32
	free(hi->target);
98
32
	free(hi);
99
64
}
100
101
#ifdef DEBUG
102
static void
103
hashinfo_print(struct hashinfo *hi)
104
{
105
	int i;
106
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' : ':');
112
	}
113
}
114
#endif
115
116
static int
117
hashinfo_compare(const void *a, const void *b)
118
{
119
16
	struct hashinfo *hia = *(struct hashinfo **)a;
120
8
	struct hashinfo *hib = *(struct hashinfo **)b;
121
	int rv;
122
123
24
	rv = hia->hash < hib->hash ? -1 : hia->hash > hib->hash;
124
8
	if (rv != 0)
125
8
		return (rv);
126
	rv = memcmp(hia->fingerprint, hib->fingerprint,
127
	    sizeof(hia->fingerprint));
128
	if (rv != 0)
129
		return (rv);
130
	return strcmp(hia->filename, hib->filename);
131
8
}
132
133
static struct hashinfo *
134
hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
135
{
136
	struct hashinfo *hi = head;
137
138
64
	if (hi == NULL)
139
16
		return (entry);
140
16
	while (hi->next != NULL)
141
		hi = hi->next;
142
16
	hi->next = entry;
143
144
16
	return (head);
145
32
}
146
147
static void
148
hashinfo_chain_free(struct hashinfo *hi)
149
{
150
	struct hashinfo *next;
151
152
136
	while (hi != NULL) {
153
32
		next = hi->next;
154
32
		hashinfo_free(hi);
155
		hi = next;
156
	}
157
24
}
158
159
static size_t
160
hashinfo_chain_length(struct hashinfo *hi)
161
{
162
	int len = 0;
163
164
56
	while (hi != NULL) {
165
16
		len++;
166
16
		hi = hi->next;
167
	}
168
8
	return (len);
169
}
170
171
static int
172
hashinfo_chain_sort(struct hashinfo **head)
173
{
174
	struct hashinfo **list, *entry;
175
	size_t len;
176
	int i;
177
178
32
	if (*head == NULL)
179
8
		return (0);
180
181
8
	len = hashinfo_chain_length(*head);
182
8
	if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
183
		return (-1);
184
185
48
	for (entry = *head, i = 0; entry != NULL; entry = entry->next, i++)
186
16
		list[i] = entry;
187
8
	qsort(list, len, sizeof(struct hashinfo *), hashinfo_compare);
188
189
8
	*head = entry = list[0];
190
32
	for (i = 1; i < len; i++) {
191
8
		entry->next = list[i];
192
8
		entry = list[i];
193
	}
194
8
	entry->next = NULL;
195
196
8
	free(list);
197
8
	return (0);
198
16
}
199
200
static char *
201
hashinfo_linkname(struct hashinfo *hi)
202
{
203
32
	char *filename;
204
205
48
	if (asprintf(&filename, "%08lx.%s%u", hi->hash,
206
32
	    (hi->is_crl ? "r" : ""), hi->index) == -1)
207
		return (NULL);
208
209
16
	return (filename);
210
16
}
211
212
static int
213
filename_is_hash(const char *filename)
214
{
215
	const char *p = filename;
216
217


528
	while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
218
		p++;
219
80
	if (*p++ != '.')
220
64
		return (0);
221
16
	if (*p == 'r')		/* CRL format. */
222
		p++;
223

32
	while (*p >= '0' && *p <= '9')
224
		p++;
225
16
	if (*p != '\0')
226
8
		return (0);
227
228
8
	return (1);
229
80
}
230
231
static int
232
filename_is_pem(const char *filename)
233
{
234
	const char *q, *p = filename;
235
236
160
	if ((q = strchr(p, '\0')) == NULL)
237
		return (0);
238
80
	if ((q - p) < 4)
239
16
		return (0);
240
64
	if (strncmp((q - 4), ".pem", 4) != 0)
241
16
		return (0);
242
243
48
	return (1);
244
80
}
245
246
static struct hashinfo *
247
hashinfo_from_linkname(const char *linkname, const char *target)
248
{
249
	struct hashinfo *hi = NULL;
250
	const char *errstr;
251
	char *l, *p, *ep;
252
	long long val;
253
254
	if ((l = strdup(linkname)) == NULL)
255
		goto err;
256
	if ((p = strchr(l, '.')) == NULL)
257
		goto err;
258
	*p++ = '\0';
259
260
	if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
261
		goto err;
262
	if ((hi->target = strdup(target)) == NULL)
263
		goto err;
264
265
	errno = 0;
266
	val = strtoll(l, &ep, 16);
267
	if (l[0] == '\0' || *ep != '\0')
268
		goto err;
269
	if (errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN))
270
		goto err;
271
	if (val < 0 || val > ULONG_MAX)
272
		goto err;
273
	hi->hash = (unsigned long)val;
274
275
	if (*p == 'r') {
276
		hi->is_crl = 1;
277
		p++;
278
	}
279
280
	val = strtonum(p, 0, 0xffffffff, &errstr);
281
	if (errstr != NULL)
282
		goto err;
283
284
	hi->index = (unsigned int)val;
285
286
	goto done;
287
288
err:
289
	hashinfo_free(hi);
290
	hi = NULL;
291
292
done:
293
	free(l);
294
295
	return (hi);
296
}
297
298
static struct hashinfo *
299
certhash_cert(BIO *bio, const char *filename)
300
{
301
96
	unsigned char fingerprint[EVP_MAX_MD_SIZE];
302
	struct hashinfo *hi = NULL;
303
	const EVP_MD *digest;
304
	X509 *cert = NULL;
305
	unsigned long hash;
306
48
	unsigned int len;
307
308
48
	if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
309
		goto err;
310
311
16
	hash = X509_subject_name_hash(cert);
312
313
16
	digest = EVP_sha256();
314
16
	if (X509_digest(cert, digest, fingerprint, &len) != 1) {
315
		fprintf(stderr, "out of memory\n");
316
		goto err;
317
	}
318
319
16
	hi = hashinfo(filename, hash, fingerprint);
320
321
err:
322
48
	X509_free(cert);
323
324
48
	return (hi);
325
48
}
326
327
static struct hashinfo *
328
certhash_crl(BIO *bio, const char *filename)
329
{
330
96
	unsigned char fingerprint[EVP_MAX_MD_SIZE];
331
	struct hashinfo *hi = NULL;
332
	const EVP_MD *digest;
333
	X509_CRL *crl = NULL;
334
	unsigned long hash;
335
48
	unsigned int len;
336
337
48
	if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
338
48
		return (NULL);
339
340
	hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
341
342
	digest = EVP_sha256();
343
	if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
344
		fprintf(stderr, "out of memory\n");
345
		goto err;
346
	}
347
348
	hi = hashinfo(filename, hash, fingerprint);
349
350
err:
351
	X509_CRL_free(crl);
352
353
	return (hi);
354
48
}
355
356
static int
357
certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
358
{
359
	struct hashinfo *link = NULL;
360
361
32
	if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
362
		goto err;
363
364
16
	if ((link->filename = hashinfo_linkname(hi)) == NULL)
365
		goto err;
366
367
16
	link->reference = hi;
368
16
	link->changed = 1;
369
16
	*links = hashinfo_chain(*links, link);
370
16
	hi->reference = link;
371
372
16
	return (0);
373
374
err:
375
	hashinfo_free(link);
376
	return (-1);
377
16
}
378
379
static void
380
certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
381
{
382
	struct hashinfo *link;
383
384
48
	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)
392
				link->changed = 1;
393
			hi->reference = link;
394
			break;
395
		}
396
	}
397
16
}
398
399
static void
400
certhash_index(struct hashinfo *head, const char *name)
401
{
402
	struct hashinfo *last, *entry;
403
	int index = 0;
404
405
	last = NULL;
406
80
	for (entry = head; entry != NULL; entry = entry->next) {
407
16
		if (last != NULL) {
408
8
			if (entry->hash == last->hash) {
409
				if (memcmp(entry->fingerprint,
410
				    last->fingerprint,
411
				    sizeof(entry->fingerprint)) == 0) {
412
					fprintf(stderr, "WARNING: duplicate %s "
413
					    "in %s (using %s), ignoring...\n",
414
					    name, entry->filename,
415
					    last->filename);
416
					entry->is_dup = 1;
417
					continue;
418
				}
419
				index++;
420
			} else {
421
				index = 0;
422
			}
423
		}
424
16
		entry->index = index;
425
		last = entry;
426
16
	}
427
16
}
428
429
static int
430
certhash_merge(struct hashinfo **links, struct hashinfo **certs,
431
    struct hashinfo **crls)
432
{
433
	struct hashinfo *cert, *crl;
434
435
	/* Pass 1 - sort and index entries. */
436
16
	if (hashinfo_chain_sort(certs) == -1)
437
		return (-1);
438
8
	if (hashinfo_chain_sort(crls) == -1)
439
		return (-1);
440
8
	certhash_index(*certs, "certificate");
441
8
	certhash_index(*crls, "CRL");
442
443
	/* Pass 2 - map to existing links. */
444
48
	for (cert = *certs; cert != NULL; cert = cert->next) {
445
16
		if (cert->is_dup == 1)
446
			continue;
447
16
		certhash_findlink(*links, cert);
448
16
	}
449
16
	for (crl = *crls; crl != NULL; crl = crl->next) {
450
		if (crl->is_dup == 1)
451
			continue;
452
		certhash_findlink(*links, crl);
453
	}
454
455
	/* Pass 3 - determine missing links. */
456
48
	for (cert = *certs; cert != NULL; cert = cert->next) {
457

32
		if (cert->is_dup == 1 || cert->reference != NULL)
458
			continue;
459
16
		if (certhash_addlink(links, cert) == -1)
460
			return (-1);
461
	}
462
16
	for (crl = *crls; crl != NULL; crl = crl->next) {
463
		if (crl->is_dup == 1 || crl->reference != NULL)
464
			continue;
465
		if (certhash_addlink(links, crl) == -1)
466
			return (-1);
467
	}
468
469
8
	return (0);
470
8
}
471
472
static int
473
certhash_link(struct dirent *dep, struct hashinfo **links)
474
{
475
	struct hashinfo *hi = NULL;
476
16
	char target[PATH_MAX];
477
8
	struct stat sb;
478
	int n;
479
480
8
	if (lstat(dep->d_name, &sb) == -1) {
481
		fprintf(stderr, "failed to stat %s\n", dep->d_name);
482
		return (-1);
483
	}
484
8
	if (!S_ISLNK(sb.st_mode))
485
8
		return (0);
486
487
	n = readlink(dep->d_name, target, sizeof(target) - 1);
488
	if (n == -1) {
489
		fprintf(stderr, "failed to readlink %s\n", dep->d_name);
490
		return (-1);
491
	}
492
	target[n] = '\0';
493
494
	hi = hashinfo_from_linkname(dep->d_name, target);
495
	if (hi == NULL) {
496
		fprintf(stderr, "failed to get hash info %s\n", dep->d_name);
497
		return (-1);
498
	}
499
	hi->exists = 1;
500
	*links = hashinfo_chain(*links, hi);
501
502
	return (0);
503
8
}
504
505
static int
506
certhash_file(struct dirent *dep, struct hashinfo **certs,
507
    struct hashinfo **crls)
508
{
509
	struct hashinfo *hi = NULL;
510
	int has_cert, has_crl;
511
	int ret = -1;
512
	BIO *bio = NULL;
513
	FILE *f;
514
515
	has_cert = has_crl = 0;
516
517
96
	if ((f = fopen(dep->d_name, "r")) == NULL) {
518
		fprintf(stderr, "failed to fopen %s\n", dep->d_name);
519
		goto err;
520
	}
521
48
	if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
522
		fprintf(stderr, "failed to create bio\n");
523
		fclose(f);
524
		goto err;
525
	}
526
527
48
	if ((hi = certhash_cert(bio, dep->d_name)) != NULL) {
528
		has_cert = 1;
529
16
		*certs = hashinfo_chain(*certs, hi);
530
16
	}
531
532
48
	if (BIO_reset(bio) != 0) {
533
		fprintf(stderr, "BIO_reset failed\n");
534
		goto err;
535
	}
536
537
48
	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
48
	if (!has_cert && !has_crl)
543
32
		fprintf(stderr, "PEM file %s does not contain a certificate "
544
		    "or CRL, ignoring...\n", dep->d_name);
545
546
48
	ret = 0;
547
548
err:
549
48
	BIO_free(bio);
550
551
48
	return (ret);
552
}
553
554
static int
555
certhash_directory(const char *path)
556
{
557
16
	struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
558
	int ret = 0;
559
	struct dirent *dep;
560
	DIR *dip = NULL;
561
562
8
	if ((dip = opendir(".")) == NULL) {
563
		fprintf(stderr, "failed to open directory %s\n", path);
564
		goto err;
565
	}
566
567
8
	if (certhash_config.verbose)
568
8
		fprintf(stdout, "scanning directory %s\n", path);
569
570
	/* Create lists of existing hash links, certs and CRLs. */
571
88
	while ((dep = readdir(dip)) != NULL) {
572
80
		if (filename_is_hash(dep->d_name)) {
573
8
			if (certhash_link(dep, &links) == -1)
574
				goto err;
575
		}
576
80
		if (filename_is_pem(dep->d_name)) {
577
48
			if (certhash_file(dep, &certs, &crls) == -1)
578
				goto err;
579
		}
580
	}
581
582
8
	if (certhash_merge(&links, &certs, &crls) == -1) {
583
		fprintf(stderr, "certhash merge failed\n");
584
		goto err;
585
	}
586
587
	/* Remove spurious links. */
588
48
	for (link = links; link != NULL; link = link->next) {
589

16
		if (link->exists == 0 ||
590
		    (link->reference != NULL && link->changed == 0))
591
			continue;
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)
597
			continue;
598
		if (unlink(link->filename) == -1) {
599
			fprintf(stderr, "failed to remove link %s\n",
600
			    link->filename);
601
			goto err;
602
		}
603
	}
604
605
	/* Create missing links. */
606
48
	for (link = links; link != NULL; link = link->next) {
607

16
		if (link->exists == 1 && link->changed == 0)
608
			continue;
609
16
		if (certhash_config.verbose)
610
16
			fprintf(stdout, "%s link %s -> %s\n",
611
16
			    (certhash_config.dryrun ? "would create" :
612
16
				"creating"), link->filename,
613
16
			    link->reference->filename);
614
16
		if (certhash_config.dryrun)
615
			continue;
616
16
		if (symlink(link->reference->filename, link->filename) == -1) {
617
			fprintf(stderr, "failed to create link %s -> %s\n",
618
			    link->filename, link->reference->filename);
619
			goto err;
620
		}
621
	}
622
623
	goto done;
624
625
err:
626
	ret = 1;
627
628
done:
629
8
	hashinfo_chain_free(certs);
630
8
	hashinfo_chain_free(crls);
631
8
	hashinfo_chain_free(links);
632
633
8
	if (dip != NULL)
634
8
		closedir(dip);
635
8
	return (ret);
636
8
}
637
638
static void
639
certhash_usage(void)
640
{
641
16
	fprintf(stderr, "usage: certhash [-nv] dir ...\n");
642
8
	options_usage(certhash_options);
643
8
}
644
645
int
646
certhash_main(int argc, char **argv)
647
{
648
32
	int argsused;
649
	int i, cwdfd, ret = 0;
650
651
16
	if (single_execution) {
652
16
		if (pledge("stdio cpath wpath rpath flock", NULL) == -1) {
653
			perror("pledge");
654
			exit(1);
655
		}
656
	}
657
658
16
	memset(&certhash_config, 0, sizeof(certhash_config));
659
660
16
	if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
661
8
                certhash_usage();
662
8
                return (1);
663
        }
664
665
8
	if ((cwdfd = open(".", O_RDONLY)) == -1) {
666
		perror("failed to open current directory");
667
		return (1);
668
	}
669
670
32
	for (i = argsused; i < argc; i++) {
671
8
		if (chdir(argv[i]) == -1) {
672
			fprintf(stderr,
673
			    "failed to change to directory %s: %s\n",
674
			    argv[i], strerror(errno));
675
			ret = 1;
676
			continue;
677
		}
678
8
		ret |= certhash_directory(argv[i]);
679
8
		if (fchdir(cwdfd) == -1) {
680
			perror("failed to restore current directory");
681
			ret = 1;
682
			break;		/* can't continue safely */
683
		}
684
	}
685
8
	close(cwdfd);
686
687
8
	return (ret);
688
16
}