From f9044ad2a216f984481645671ccc30a043849fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Ter=C3=A4s?= Date: Thu, 5 Feb 2015 09:11:27 +0200 Subject: [PATCH] reimplement c_rehash in C --- apps/Makefile | 4 +- apps/c_rehash.c | 383 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/Makefile | 3 +- 3 files changed, 386 insertions(+), 4 deletions(-) create mode 100644 apps/c_rehash.c diff --git a/apps/Makefile b/apps/Makefile index 72657ea..3aa03e0 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -36,7 +36,7 @@ SCRIPTS=CA.sh CA.pl tsget EXE= $(PROGRAM)$(EXE_EXT) E_EXE= verify asn1pars req dgst dh dhparam enc passwd gendh errstr \ - ca crl rsa rsautl dsa dsaparam ec ecparam \ + ca crl c_rehash rsa rsautl dsa dsaparam ec ecparam \ x509 genrsa gendsa genpkey s_server s_client speed \ s_time version pkcs7 cms crl2pkcs7 sess_id ciphers nseq pkcs12 \ pkcs8 pkey pkeyparam pkeyutl spkac smime rand engine ocsp prime ts srp @@ -51,7 +51,7 @@ RAND_OBJ=app_rand.o RAND_SRC=app_rand.c E_OBJ= verify.o asn1pars.o req.o dgst.o dh.o dhparam.o enc.o passwd.o gendh.o errstr.o \ - ca.o pkcs7.o crl2p7.o crl.o \ + ca.o pkcs7.o crl2p7.o crl.o c_rehash.o \ rsa.o rsautl.o dsa.o dsaparam.o ec.o ecparam.o \ x509.o genrsa.o gendsa.o genpkey.o s_server.o s_client.o speed.o \ s_time.o $(A_OBJ) $(S_OBJ) $(RAND_OBJ) version.o sess_id.o \ diff --git a/apps/c_rehash.c b/apps/c_rehash.c new file mode 100644 index 0000000..e8d0b86 --- /dev/null +++ b/apps/c_rehash.c @@ -0,0 +1,383 @@ +/* c_rehash.c - Create hash symlinks for certificates + * C implementation based on the original Perl and shell versions + * + * Copyright (c) 2013-2014 Timo Teräs + * All rights reserved. + * + * This software is licensed under the MIT License. + * Full license available at: http://opensource.org/licenses/MIT + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "apps.h" + +#undef PROG +#define PROG c_rehash_main + + +#define MAX_COLLISIONS 256 +#define countof(x) (sizeof(x) / sizeof(x[0])) + +#if 0 +#define DEBUG(args...) fprintf(stderr, args) +#else +#define DEBUG(args...) +#endif + +struct entry_info { + struct entry_info *next; + char *filename; + unsigned short old_id; + unsigned char need_symlink; + unsigned char digest[EVP_MAX_MD_SIZE]; +}; + +struct bucket_info { + struct bucket_info *next; + struct entry_info *first_entry, *last_entry; + unsigned int hash; + unsigned short type; + unsigned short num_needed; +}; + +enum Type { + TYPE_CERT = 0, + TYPE_CRL +}; + +static const char *symlink_extensions[] = { "", "r" }; +static const char *file_extensions[] = { "pem", "crt", "cer", "crl" }; + +static int evpmdsize; +static const EVP_MD *evpmd; + +static int do_hash_new = 1; +static int do_hash_old = 0; +static int do_remove_links = 1; +static int do_verbose = 0; + +static struct bucket_info *hash_table[257]; + +static void bit_set(unsigned char *set, unsigned bit) +{ + set[bit / 8] |= 1 << (bit % 8); +} + +static int bit_isset(unsigned char *set, unsigned bit) +{ + return set[bit / 8] & (1 << (bit % 8)); +} + +static void add_entry( + int type, unsigned int hash, + const char *filename, const unsigned char *digest, + int need_symlink, unsigned short old_id) +{ + struct bucket_info *bi; + struct entry_info *ei, *found = NULL; + unsigned int ndx = (type + hash) % countof(hash_table); + + for (bi = hash_table[ndx]; bi; bi = bi->next) + if (bi->type == type && bi->hash == hash) + break; + if (!bi) { + bi = calloc(1, sizeof(*bi)); + if (!bi) return; + bi->next = hash_table[ndx]; + bi->type = type; + bi->hash = hash; + hash_table[ndx] = bi; + } + + for (ei = bi->first_entry; ei; ei = ei->next) { + if (digest && memcmp(digest, ei->digest, evpmdsize) == 0) { + BIO_printf(bio_err, + "WARNING: Skipping duplicate certificate in file %s\n", + filename); + return; + } + if (!strcmp(filename, ei->filename)) { + found = ei; + if (!digest) break; + } + } + ei = found; + if (!ei) { + if (bi->num_needed >= MAX_COLLISIONS) return; + ei = calloc(1, sizeof(*ei)); + if (!ei) return; + + ei->old_id = ~0; + ei->filename = strdup(filename); + if (bi->last_entry) bi->last_entry->next = ei; + if (!bi->first_entry) bi->first_entry = ei; + bi->last_entry = ei; + } + + if (old_id < ei->old_id) ei->old_id = old_id; + if (need_symlink && !ei->need_symlink) { + ei->need_symlink = 1; + bi->num_needed++; + memcpy(ei->digest, digest, evpmdsize); + } +} + +static int handle_symlink(const char *filename, const char *fullpath) +{ + static char xdigit[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,10,11,12,13,14,15 + }; + char linktarget[NAME_MAX], *endptr; + unsigned int hash = 0; + unsigned char ch; + int i, type, id; + ssize_t n; + + for (i = 0; i < 8; i++) { + ch = filename[i] - '0'; + if (ch >= countof(xdigit) || xdigit[ch] < 0) + return -1; + hash <<= 4; + hash += xdigit[ch]; + } + if (filename[i++] != '.') return -1; + for (type = countof(symlink_extensions) - 1; type > 0; type--) + if (strcasecmp(symlink_extensions[type], &filename[i]) == 0) + break; + i += strlen(symlink_extensions[type]); + + id = strtoul(&filename[i], &endptr, 10); + if (*endptr != 0) return -1; + + n = readlink(fullpath, linktarget, sizeof(linktarget)); + if (n >= sizeof(linktarget) || n < 0) return -1; + linktarget[n] = 0; + + DEBUG("Found existing symlink %s for %08x (%d), certname %s\n", + filename, hash, type, linktarget); + add_entry(type, hash, linktarget, NULL, 0, id); + return 0; +} + +static int handle_certificate(const char *filename, const char *fullpath) +{ + STACK_OF(X509_INFO) *inf; + X509_INFO *x; + BIO *b; + const char *ext; + unsigned char digest[EVP_MAX_MD_SIZE]; + X509_NAME *name = NULL; + int i, type, ret = -1; + + ext = strrchr(filename, '.'); + if (ext == NULL) return 0; + for (i = 0; i < countof(file_extensions); i++) { + if (strcasecmp(file_extensions[i], ext+1) == 0) + break; + } + if (i >= countof(file_extensions)) return -1; + + b = BIO_new_file(fullpath, "r"); + if (!b) return -1; + inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL); + BIO_free(b); + if (!inf) return -1; + + if (sk_X509_INFO_num(inf) == 1) { + x = sk_X509_INFO_value(inf, 0); + if (x->x509) { + type = TYPE_CERT; + name = X509_get_subject_name(x->x509); + X509_digest(x->x509, evpmd, digest, NULL); + } else if (x->crl) { + type = TYPE_CRL; + name = X509_CRL_get_issuer(x->crl); + X509_CRL_digest(x->crl, evpmd, digest, NULL); + } + if (name && do_hash_new) + add_entry(type, X509_NAME_hash(name), filename, digest, 1, ~0); + if (name && do_hash_old) + add_entry(type, X509_NAME_hash_old(name), filename, digest, 1, ~0); + } else { + BIO_printf(bio_err, + "WARNING: %s does not contain exactly one certificate or CRL: skipping\n", + filename); + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + + return ret; +} + +static int hash_dir(const char *dirname) +{ + struct bucket_info *bi, *nextbi; + struct entry_info *ei, *nextei; + struct dirent *de; + struct stat st; + unsigned char idmask[MAX_COLLISIONS / 8]; + int i, n, nextid, buflen, ret = -1; + const char *pathsep; + char *buf; + DIR *d; + + if (access(dirname, R_OK|W_OK|X_OK) != 0) { + BIO_printf(bio_err, + "ERROR: Access denied '%s'\n", + dirname); + return -1; + } + + buflen = strlen(dirname); + pathsep = (buflen && dirname[buflen-1] == '/') ? "" : "/"; + buflen += NAME_MAX + 2; + buf = malloc(buflen); + if (buf == NULL) + goto err; + + if (do_verbose) printf("Doing %s\n", dirname); + d = opendir(dirname); + if (!d) goto err; + + while ((de = readdir(d)) != NULL) { + if (snprintf(buf, buflen, "%s%s%s", dirname, pathsep, de->d_name) >= buflen) + continue; + if (lstat(buf, &st) < 0) + continue; + if (S_ISLNK(st.st_mode) && handle_symlink(de->d_name, buf) == 0) + continue; + handle_certificate(de->d_name, buf); + } + closedir(d); + + for (i = 0; i < countof(hash_table); i++) { + for (bi = hash_table[i]; bi; bi = nextbi) { + nextbi = bi->next; + DEBUG("Type %d, hash %08x, num entries %d:\n", bi->type, bi->hash, bi->num_needed); + + nextid = 0; + memset(idmask, 0, (bi->num_needed+7)/8); + for (ei = bi->first_entry; ei; ei = ei->next) + if (ei->old_id < bi->num_needed) + bit_set(idmask, ei->old_id); + + for (ei = bi->first_entry; ei; ei = nextei) { + nextei = ei->next; + DEBUG("\t(old_id %d, need_symlink %d) Cert %s\n", + ei->old_id, ei->need_symlink, + ei->filename); + + if (ei->old_id < bi->num_needed) { + /* Link exists, and is used as-is */ + snprintf(buf, buflen, "%08x.%s%d", bi->hash, symlink_extensions[bi->type], ei->old_id); + if (do_verbose) printf("link %s -> %s\n", ei->filename, buf); + } else if (ei->need_symlink) { + /* New link needed (it may replace something) */ + while (bit_isset(idmask, nextid)) + nextid++; + + snprintf(buf, buflen, "%s%s%n%08x.%s%d", + dirname, pathsep, &n, bi->hash, + symlink_extensions[bi->type], + nextid); + if (do_verbose) printf("link %s -> %s\n", ei->filename, &buf[n]); + unlink(buf); + symlink(ei->filename, buf); + } else if (do_remove_links) { + /* Link to be deleted */ + snprintf(buf, buflen, "%s%s%n%08x.%s%d", + dirname, pathsep, &n, bi->hash, + symlink_extensions[bi->type], + ei->old_id); + if (do_verbose) printf("unlink %s\n", &buf[n]); + unlink(buf); + } + free(ei->filename); + free(ei); + } + free(bi); + } + hash_table[i] = NULL; + } + + ret = 0; +err: + free(buf); + return ret; +} + +static void c_rehash_usage(void) +{ + printf("\ +usage: c_rehash \n\ +\n\ +-compat - create new- and old-style hashed links\n\ +-old - use old-style hashing for generating links\n\ +-h - display this help\n\ +-n - do not remove existing links\n\ +-v - be more verbose\n\ +\n"); +} + +int MAIN(int argc, char **argv) +{ + const char *env, *opt; + int i, numargs, r = 0; + + evpmd = EVP_sha1(); + evpmdsize = EVP_MD_size(evpmd); + if (bio_err == NULL) + bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); + + numargs = argc; + for (i = 1; i < argc; i++) { + if (argv[i][0] != '-') continue; + if (strcmp(argv[i], "--") == 0) { argv[i] = 0; numargs--; break; } + opt = &argv[i][1]; + if (strcmp(opt, "compat") == 0) { + do_hash_new = do_hash_old = 1; + } else if (strcmp(opt, "old") == 0) { + do_hash_new = 0; + do_hash_old = 1; + } else if (strcmp(opt, "n") == 0) { + do_remove_links = 0; + } else if (strcmp(opt, "v") == 0) { + do_verbose++; + } else { + if (strcmp(opt, "h") != 0) + BIO_printf(bio_err,"unknown option %s\n", argv[i]); + c_rehash_usage(); + return 1; + } + argv[i] = 0; + numargs--; + } + + if (numargs > 1) { + for (i = 1; i < argc; i++) + if (argv[i]) r |= hash_dir(argv[i]); + } else if ((env = getenv("SSL_CERT_DIR")) != NULL) { + char *e, *m; + m = strdup(env); + for (e = strtok(m, ":"); e != NULL; e = strtok(NULL, ":")) + r |= hash_dir(e); + free(m); + } else { + r |= hash_dir("/etc/ssl/certs"); + } + + return r ? 2 : 0; +} diff --git a/tools/Makefile b/tools/Makefile index bb6fb71..51190fc 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -13,7 +13,7 @@ CFLAGS= $(INCLUDES) $(CFLAG) GENERAL=Makefile TEST= -APPS= c_rehash +APPS= MISC_APPS= c_hash c_info c_issuer c_name all: @@ -49,7 +49,6 @@ depend: dclean: $(PERL) -pe 'if (/^# DO NOT DELETE THIS LINE/) {print; exit(0);}' $(MAKEFILE) >Makefile.new mv -f Makefile.new $(MAKEFILE) - rm -f c_rehash clean: rm -f *.o *.obj lib tags core .pure .nfs* *.old *.bak fluff -- 2.2.2