Blame c_rehash.c

William Pitcock c17d73
/* c_rehash.c - Create hash symlinks for certificates
William Pitcock c17d73
 * C implementation based on the original Perl and shell versions
William Pitcock c17d73
 *
William Pitcock c17d73
 * Copyright (c) 2013-2014 Timo Teräs <timo.teras@iki.fi>
William Pitcock c17d73
 * All rights reserved.
William Pitcock c17d73
 *
William Pitcock c17d73
 * This software is licensed under the MIT License.
William Pitcock c17d73
 * Full license available at: http://opensource.org/licenses/MIT
William Pitcock c17d73
 */
William Pitcock c17d73
William Pitcock c17d73
#include <stdio.h>
William Pitcock c17d73
#include <limits.h>
William Pitcock c17d73
#include <string.h>
William Pitcock c17d73
#include <unistd.h>
William Pitcock c17d73
#include <dirent.h>
William Pitcock c17d73
#include <sys/stat.h>
William Pitcock c17d73
William Pitcock c17d73
#include <openssl/evp.h>
William Pitcock c17d73
#include <openssl/pem.h>
William Pitcock c17d73
#include <openssl/x509.h>
William Pitcock c17d73
William Pitcock c17d73
#define MAX_COLLISIONS	256
William Pitcock c17d73
#define countof(x) 	(sizeof(x) / sizeof(x[0]))
William Pitcock c17d73
William Pitcock c17d73
#if 0
William Pitcock c17d73
#define DEBUG(args...) fprintf(stderr, args)
William Pitcock c17d73
#else
William Pitcock c17d73
#define DEBUG(args...)
William Pitcock c17d73
#endif
William Pitcock c17d73
William Pitcock c17d73
struct entry_info {
William Pitcock c17d73
	struct entry_info *next;
William Pitcock c17d73
	char *filename;
William Pitcock c17d73
	unsigned short old_id;
William Pitcock c17d73
	unsigned char need_symlink;
William Pitcock c17d73
	unsigned char digest[EVP_MAX_MD_SIZE];
William Pitcock c17d73
};
William Pitcock c17d73
William Pitcock c17d73
struct bucket_info {
William Pitcock c17d73
	struct bucket_info *next;
William Pitcock c17d73
	struct entry_info *first_entry, *last_entry;
William Pitcock c17d73
	unsigned int hash;
William Pitcock c17d73
	unsigned short type;
William Pitcock c17d73
	unsigned short num_needed;
William Pitcock c17d73
};
William Pitcock c17d73
William Pitcock c17d73
enum Type {
William Pitcock c17d73
	TYPE_CERT = 0,
William Pitcock c17d73
	TYPE_CRL
William Pitcock c17d73
};
William Pitcock c17d73
William Pitcock c17d73
static const char *symlink_extensions[] = { "", "r" };
William Pitcock c17d73
static const char *file_extensions[] = { "pem", "crt", "cer", "crl" };
William Pitcock c17d73
William Pitcock c17d73
static int evpmdsize;
William Pitcock c17d73
static const EVP_MD *evpmd;
William Pitcock c17d73
William Pitcock c17d73
static int do_hash_new = 1;
William Pitcock c17d73
static int do_hash_old = 0;
William Pitcock c17d73
static int do_remove_links = 1;
William Pitcock c17d73
static int do_verbose = 0;
William Pitcock c17d73
William Pitcock c17d73
static struct bucket_info *hash_table[257];
William Pitcock c17d73
William Pitcock c17d73
static void bit_set(unsigned char *set, unsigned bit)
William Pitcock c17d73
{
William Pitcock c17d73
	set[bit / 8] |= 1 << (bit % 8);
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static int bit_isset(unsigned char *set, unsigned bit)
William Pitcock c17d73
{
William Pitcock c17d73
	return set[bit / 8] & (1 << (bit % 8));
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static void add_entry(
William Pitcock c17d73
	int type, unsigned int hash,
William Pitcock c17d73
	const char *filename, const unsigned char *digest,
William Pitcock c17d73
	int need_symlink, unsigned short old_id)
William Pitcock c17d73
{
William Pitcock c17d73
	struct bucket_info *bi;
William Pitcock c17d73
	struct entry_info *ei, *found = NULL;
William Pitcock c17d73
	unsigned int ndx = (type + hash) % countof(hash_table);
William Pitcock c17d73
William Pitcock c17d73
	for (bi = hash_table[ndx]; bi; bi = bi->next)
William Pitcock c17d73
		if (bi->type == type && bi->hash == hash)
William Pitcock c17d73
			break;
William Pitcock c17d73
	if (!bi) {
William Pitcock c17d73
		bi = calloc(1, sizeof(*bi));
William Pitcock c17d73
		if (!bi) return;
William Pitcock c17d73
		bi->next = hash_table[ndx];
William Pitcock c17d73
		bi->type = type;
William Pitcock c17d73
		bi->hash = hash;
William Pitcock c17d73
		hash_table[ndx] = bi;
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	for (ei = bi->first_entry; ei; ei = ei->next) {
William Pitcock c17d73
		if (digest && memcmp(digest, ei->digest, evpmdsize) == 0) {
William Pitcock c17d73
			fprintf(stderr,
William Pitcock c17d73
				"WARNING: Skipping duplicate certificate in file %s\n",
William Pitcock c17d73
				filename);
William Pitcock c17d73
			return;
William Pitcock c17d73
		}
William Pitcock c17d73
		if (!strcmp(filename, ei->filename)) {
William Pitcock c17d73
			found = ei;
William Pitcock c17d73
			if (!digest) break;
William Pitcock c17d73
		}
William Pitcock c17d73
	}
William Pitcock c17d73
	ei = found;
William Pitcock c17d73
	if (!ei) {
William Pitcock c17d73
		if (bi->num_needed >= MAX_COLLISIONS) return;
William Pitcock c17d73
		ei = calloc(1, sizeof(*ei));
William Pitcock c17d73
		if (!ei) return;
William Pitcock c17d73
William Pitcock c17d73
		ei->old_id = ~0;
William Pitcock c17d73
		ei->filename = strdup(filename);
William Pitcock c17d73
		if (bi->last_entry) bi->last_entry->next = ei;
William Pitcock c17d73
		if (!bi->first_entry) bi->first_entry = ei;
William Pitcock c17d73
		bi->last_entry = ei;
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	if (old_id < ei->old_id) ei->old_id = old_id;
William Pitcock c17d73
	if (need_symlink && !ei->need_symlink) {
William Pitcock c17d73
		ei->need_symlink = 1;
William Pitcock c17d73
		bi->num_needed++;
William Pitcock c17d73
		memcpy(ei->digest, digest, evpmdsize);
William Pitcock c17d73
	}
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static int handle_symlink(const char *filename, const char *fullpath)
William Pitcock c17d73
{
William Pitcock c17d73
	static char xdigit[] = {
William Pitcock c17d73
		 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
William Pitcock c17d73
		-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
William Pitcock c17d73
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
William Pitcock c17d73
		-1,10,11,12,13,14,15
William Pitcock c17d73
	};
William Pitcock c17d73
	char linktarget[NAME_MAX], *endptr;
William Pitcock c17d73
	unsigned int hash = 0;
William Pitcock c17d73
	unsigned char ch;
William Pitcock c17d73
	int i, type, id;
William Pitcock c17d73
	ssize_t n;
William Pitcock c17d73
William Pitcock c17d73
	for (i = 0; i < 8; i++) {
William Pitcock c17d73
		ch = filename[i] - '0';
William Pitcock c17d73
		if (ch >= countof(xdigit) || xdigit[ch] < 0)
William Pitcock c17d73
			return -1;
William Pitcock c17d73
		hash <<= 4;
William Pitcock c17d73
		hash += xdigit[ch];
William Pitcock c17d73
	}
William Pitcock c17d73
	if (filename[i++] != '.') return -1;
William Pitcock c17d73
	for (type = countof(symlink_extensions) - 1; type > 0; type--)
William Pitcock c17d73
		if (strcasecmp(symlink_extensions[type], &filename[i]) == 0)
William Pitcock c17d73
			break;
William Pitcock c17d73
	i += strlen(symlink_extensions[type]);
William Pitcock c17d73
William Pitcock c17d73
	id = strtoul(&filename[i], &endptr, 10);
William Pitcock c17d73
	if (*endptr != 0) return -1;
William Pitcock c17d73
William Pitcock c17d73
	n = readlink(fullpath, linktarget, sizeof(linktarget));
William Pitcock c17d73
	if (n >= sizeof(linktarget) || n < 0) return -1;
William Pitcock c17d73
	linktarget[n] = 0;
William Pitcock c17d73
William Pitcock c17d73
	DEBUG("Found existing symlink %s for %08x (%d), certname %s\n",
William Pitcock c17d73
		filename, hash, type, linktarget);
William Pitcock c17d73
	add_entry(type, hash, linktarget, NULL, 0, id);
William Pitcock c17d73
	return 0;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static int handle_certificate(const char *filename, const char *fullpath)
William Pitcock c17d73
{
William Pitcock c17d73
	STACK_OF(X509_INFO) *inf;
William Pitcock c17d73
	X509_INFO *x;
William Pitcock c17d73
	BIO *b;
William Pitcock c17d73
	const char *ext;
William Pitcock c17d73
	unsigned char digest[EVP_MAX_MD_SIZE];
William Pitcock c17d73
	X509_NAME *name = NULL;
James Larrowe b007d0
	int type, ret = -1;
James Larrowe b007d0
	size_t i;
William Pitcock c17d73
William Pitcock c17d73
	ext = strrchr(filename, '.');
William Pitcock c17d73
	if (ext == NULL) return 0;
William Pitcock c17d73
	for (i = 0; i < countof(file_extensions); i++) {
William Pitcock c17d73
		if (strcasecmp(file_extensions[i], ext+1) == 0)
William Pitcock c17d73
			break;
William Pitcock c17d73
	}
William Pitcock c17d73
	if (i >= countof(file_extensions)) return -1;
William Pitcock c17d73
William Pitcock c17d73
	b = BIO_new_file(fullpath, "r");
William Pitcock c17d73
	if (!b) return -1;
William Pitcock c17d73
	inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL);
William Pitcock c17d73
	BIO_free(b);
William Pitcock c17d73
	if (!inf) return -1;
William Pitcock c17d73
William Pitcock c17d73
	if (sk_X509_INFO_num(inf) == 1) {
William Pitcock c17d73
		x = sk_X509_INFO_value(inf, 0);
William Pitcock c17d73
		if (x->x509) {
William Pitcock c17d73
			type = TYPE_CERT;
William Pitcock c17d73
			name = X509_get_subject_name(x->x509);
William Pitcock c17d73
			X509_digest(x->x509, evpmd, digest, NULL);
William Pitcock c17d73
		} else if (x->crl) {
William Pitcock c17d73
			type = TYPE_CRL;
William Pitcock c17d73
			name = X509_CRL_get_issuer(x->crl);
William Pitcock c17d73
			X509_CRL_digest(x->crl, evpmd, digest, NULL);
William Pitcock c17d73
		}
William Pitcock c17d73
		if (name && do_hash_new)
William Pitcock c17d73
			add_entry(type, X509_NAME_hash(name), filename, digest, 1, ~0);
William Pitcock c17d73
		if (name && do_hash_old)
William Pitcock c17d73
			add_entry(type, X509_NAME_hash_old(name), filename, digest, 1, ~0);
William Pitcock c17d73
	} else {
William Pitcock c17d73
		fprintf(stderr,
William Pitcock c17d73
			"WARNING: %s does not contain exactly one certificate or CRL: skipping\n",
William Pitcock c17d73
			filename);
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	sk_X509_INFO_pop_free(inf, X509_INFO_free);
William Pitcock c17d73
William Pitcock c17d73
	return ret;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static int hash_dir(const char *dirname)
William Pitcock c17d73
{
William Pitcock c17d73
	struct bucket_info *bi, *nextbi;
William Pitcock c17d73
	struct entry_info *ei, *nextei;
William Pitcock c17d73
	struct dirent *de;
William Pitcock c17d73
	struct stat st;
William Pitcock c17d73
	unsigned char idmask[MAX_COLLISIONS / 8];
James Larrowe b007d0
	int n, nextid, buflen, ret = -1;
William Pitcock c17d73
	const char *pathsep;
William Pitcock c17d73
	char *buf;
James Larrowe b007d0
	size_t i;
William Pitcock c17d73
	DIR *d;
William Pitcock c17d73
William Pitcock c17d73
	if (access(dirname, R_OK|W_OK|X_OK) != 0) {
William Pitcock c17d73
		fprintf(stderr,
William Pitcock c17d73
			"ERROR: Access denied '%s'\n",
William Pitcock c17d73
			dirname);
William Pitcock c17d73
		return -1;
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	buflen = strlen(dirname);
William Pitcock c17d73
	pathsep = (buflen && dirname[buflen-1] == '/') ? "" : "/";
William Pitcock c17d73
	buflen += NAME_MAX + 2;
William Pitcock c17d73
	buf = malloc(buflen);
William Pitcock c17d73
	if (buf == NULL)
William Pitcock c17d73
		goto err;
William Pitcock c17d73
William Pitcock c17d73
	if (do_verbose) printf("Doing %s\n", dirname);
William Pitcock c17d73
	d = opendir(dirname);
William Pitcock c17d73
	if (!d) goto err;
William Pitcock c17d73
William Pitcock c17d73
	while ((de = readdir(d)) != NULL) {
William Pitcock c17d73
		if (snprintf(buf, buflen, "%s%s%s", dirname, pathsep, de->d_name) >= buflen)
William Pitcock c17d73
			continue;
William Pitcock c17d73
		if (lstat(buf, &st) < 0)
William Pitcock c17d73
			continue;
William Pitcock c17d73
		if (S_ISLNK(st.st_mode) && handle_symlink(de->d_name, buf) == 0)
William Pitcock c17d73
			continue;
William Pitcock c17d73
		handle_certificate(de->d_name, buf);
William Pitcock c17d73
	}
William Pitcock c17d73
	closedir(d);
William Pitcock c17d73
William Pitcock c17d73
	for (i = 0; i < countof(hash_table); i++) {
William Pitcock c17d73
		for (bi = hash_table[i]; bi; bi = nextbi) {
William Pitcock c17d73
			nextbi = bi->next;
William Pitcock c17d73
			DEBUG("Type %d, hash %08x, num entries %d:\n", bi->type, bi->hash, bi->num_needed);
William Pitcock c17d73
William Pitcock c17d73
			nextid = 0;
William Pitcock c17d73
			memset(idmask, 0, (bi->num_needed+7)/8);
William Pitcock c17d73
			for (ei = bi->first_entry; ei; ei = ei->next)
William Pitcock c17d73
				if (ei->old_id < bi->num_needed)
William Pitcock c17d73
					bit_set(idmask, ei->old_id);
William Pitcock c17d73
William Pitcock c17d73
			for (ei = bi->first_entry; ei; ei = nextei) {
William Pitcock c17d73
				nextei = ei->next;
William Pitcock c17d73
				DEBUG("\t(old_id %d, need_symlink %d) Cert %s\n",
William Pitcock c17d73
					ei->old_id, ei->need_symlink,
William Pitcock c17d73
					ei->filename);
William Pitcock c17d73
William Pitcock c17d73
				if (ei->old_id < bi->num_needed) {
William Pitcock c17d73
					/* Link exists, and is used as-is */
William Pitcock c17d73
					snprintf(buf, buflen, "%08x.%s%d", bi->hash, symlink_extensions[bi->type], ei->old_id);
William Pitcock c17d73
					if (do_verbose) printf("link %s -> %s\n", ei->filename, buf);
William Pitcock c17d73
				} else if (ei->need_symlink) {
William Pitcock c17d73
					/* New link needed (it may replace something) */
William Pitcock c17d73
					while (bit_isset(idmask, nextid))
William Pitcock c17d73
						nextid++;
William Pitcock c17d73
William Pitcock c17d73
					snprintf(buf, buflen, "%s%s%n%08x.%s%d",
William Pitcock c17d73
						 dirname, pathsep, &n, bi->hash,
William Pitcock c17d73
						 symlink_extensions[bi->type],
William Pitcock c17d73
						 nextid);
William Pitcock c17d73
					if (do_verbose) printf("link %s -> %s\n", ei->filename, &buf[n]);
William Pitcock c17d73
					unlink(buf);
William Pitcock c17d73
					symlink(ei->filename, buf);
William Pitcock c17d73
				} else if (do_remove_links) {
William Pitcock c17d73
					/* Link to be deleted */
William Pitcock c17d73
					snprintf(buf, buflen, "%s%s%n%08x.%s%d",
William Pitcock c17d73
						 dirname, pathsep, &n, bi->hash,
William Pitcock c17d73
						 symlink_extensions[bi->type],
William Pitcock c17d73
						 ei->old_id);
William Pitcock c17d73
					if (do_verbose) printf("unlink %s\n", &buf[n]);
William Pitcock c17d73
					unlink(buf);
William Pitcock c17d73
				}
William Pitcock c17d73
				free(ei->filename);
William Pitcock c17d73
				free(ei);
William Pitcock c17d73
			}
William Pitcock c17d73
			free(bi);
William Pitcock c17d73
		}
William Pitcock c17d73
		hash_table[i] = NULL;
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	ret = 0;
William Pitcock c17d73
err:
William Pitcock c17d73
	free(buf);
William Pitcock c17d73
	return ret;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static void c_rehash_usage(void)
William Pitcock c17d73
{
William Pitcock c17d73
	printf("\
William Pitcock c17d73
usage: c_rehash <args> <dirs>\n\
William Pitcock c17d73
\n\
William Pitcock c17d73
-compat       - create new- and old-style hashed links\n\
William Pitcock c17d73
-old          - use old-style hashing for generating links\n\
William Pitcock c17d73
-h            - display this help\n\
William Pitcock c17d73
-n            - do not remove existing links\n\
William Pitcock c17d73
-v            - be more verbose\n\
William Pitcock c17d73
\n");
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
int main(int argc, char **argv)
William Pitcock c17d73
{
William Pitcock c17d73
	const char *env, *opt;
William Pitcock c17d73
	int i, numargs, r = 0;
William Pitcock c17d73
William Pitcock c17d73
	evpmd = EVP_sha1();
William Pitcock c17d73
	evpmdsize = EVP_MD_size(evpmd);
William Pitcock c17d73
William Pitcock c17d73
	numargs = argc;
William Pitcock c17d73
	for (i = 1; i < argc; i++) {
William Pitcock c17d73
		if (argv[i][0] != '-') continue;
William Pitcock c17d73
		if (strcmp(argv[i], "--") == 0) { argv[i] = 0; numargs--; break; }
William Pitcock c17d73
		opt = &argv[i][1];
William Pitcock c17d73
		if (strcmp(opt, "compat") == 0) {
William Pitcock c17d73
			do_hash_new = do_hash_old = 1;
William Pitcock c17d73
		} else if (strcmp(opt, "old") == 0) {
William Pitcock c17d73
			do_hash_new = 0;
William Pitcock c17d73
			do_hash_old = 1;
William Pitcock c17d73
		} else if (strcmp(opt, "n") == 0) {
William Pitcock c17d73
			do_remove_links = 0;
William Pitcock c17d73
		} else if (strcmp(opt, "v") == 0) {
William Pitcock c17d73
			do_verbose++;
William Pitcock c17d73
		} else {
William Pitcock c17d73
			if (strcmp(opt, "h") != 0)
William Pitcock c17d73
				fprintf(stderr, "unknown option %s\n", argv[i]);
William Pitcock c17d73
			c_rehash_usage();
William Pitcock c17d73
			return 1;
William Pitcock c17d73
		}
William Pitcock c17d73
		argv[i] = 0;
William Pitcock c17d73
		numargs--;
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	if (numargs > 1) {
William Pitcock c17d73
		for (i = 1; i < argc; i++)
William Pitcock c17d73
			if (argv[i]) r |= hash_dir(argv[i]);
William Pitcock c17d73
	} else if ((env = getenv("SSL_CERT_DIR")) != NULL) {
William Pitcock c17d73
		char *e, *m;
William Pitcock c17d73
		m = strdup(env);
William Pitcock c17d73
		for (e = strtok(m, ":"); e != NULL; e = strtok(NULL, ":"))
William Pitcock c17d73
			r |= hash_dir(e);
William Pitcock c17d73
		free(m);
William Pitcock c17d73
	} else {
William Pitcock c17d73
		r |= hash_dir("/etc/ssl/certs");
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	return r ? 2 : 0;
William Pitcock c17d73
}