mrrl

Minimal Reliable Reproducible Linux
git clone https://ccx.te2000.cz/git/mrrl
Log | Files | Refs | Submodules | README

c_rehash.c (9533B)


      1 /* c_rehash.c - Create hash symlinks for certificates
      2  * C implementation based on the original Perl and shell versions
      3  *
      4  * Copyright (c) 2013-2014 Timo Teräs <timo.teras@iki.fi>
      5  * All rights reserved.
      6  *
      7  * This software is licensed under the MIT License.
      8  * Full license available at: http://opensource.org/licenses/MIT
      9  */
     10 
     11 #include <stdio.h>
     12 #include <limits.h>
     13 #include <string.h>
     14 #include <unistd.h>
     15 #include <dirent.h>
     16 #include <sys/stat.h>
     17 
     18 #include <openssl/evp.h>
     19 #include <openssl/pem.h>
     20 #include <openssl/x509.h>
     21 
     22 #define MAX_COLLISIONS	256
     23 #define countof(x) 	(sizeof(x) / sizeof(x[0]))
     24 
     25 #if 0
     26 #define DEBUG(args...) fprintf(stderr, args)
     27 #else
     28 #define DEBUG(args...)
     29 #endif
     30 
     31 struct entry_info {
     32 	struct entry_info *next;
     33 	char *filename;
     34 	unsigned short old_id;
     35 	unsigned char need_symlink;
     36 	unsigned char digest[EVP_MAX_MD_SIZE];
     37 };
     38 
     39 struct bucket_info {
     40 	struct bucket_info *next;
     41 	struct entry_info *first_entry, *last_entry;
     42 	unsigned int hash;
     43 	unsigned short type;
     44 	unsigned short num_needed;
     45 };
     46 
     47 enum Type {
     48 	TYPE_CERT = 0,
     49 	TYPE_CRL
     50 };
     51 
     52 static const char *symlink_extensions[] = { "", "r" };
     53 static const char *file_extensions[] = { "pem", "crt", "cer", "crl" };
     54 
     55 static int evpmdsize;
     56 static const EVP_MD *evpmd;
     57 
     58 static int do_hash_new = 1;
     59 static int do_hash_old = 0;
     60 static int do_remove_links = 1;
     61 static int do_verbose = 0;
     62 
     63 static struct bucket_info *hash_table[257];
     64 
     65 static void bit_set(unsigned char *set, unsigned bit)
     66 {
     67 	set[bit / 8] |= 1 << (bit % 8);
     68 }
     69 
     70 static int bit_isset(unsigned char *set, unsigned bit)
     71 {
     72 	return set[bit / 8] & (1 << (bit % 8));
     73 }
     74 
     75 static void add_entry(
     76 	int type, unsigned int hash,
     77 	const char *filename, const unsigned char *digest,
     78 	int need_symlink, unsigned short old_id)
     79 {
     80 	struct bucket_info *bi;
     81 	struct entry_info *ei, *found = NULL;
     82 	unsigned int ndx = (type + hash) % countof(hash_table);
     83 
     84 	for (bi = hash_table[ndx]; bi; bi = bi->next)
     85 		if (bi->type == type && bi->hash == hash)
     86 			break;
     87 	if (!bi) {
     88 		bi = calloc(1, sizeof(*bi));
     89 		if (!bi) return;
     90 		bi->next = hash_table[ndx];
     91 		bi->type = type;
     92 		bi->hash = hash;
     93 		hash_table[ndx] = bi;
     94 	}
     95 
     96 	for (ei = bi->first_entry; ei; ei = ei->next) {
     97 		if (digest && memcmp(digest, ei->digest, evpmdsize) == 0) {
     98 			fprintf(stderr,
     99 				"WARNING: Skipping duplicate certificate in file %s\n",
    100 				filename);
    101 			return;
    102 		}
    103 		if (!strcmp(filename, ei->filename)) {
    104 			found = ei;
    105 			if (!digest) break;
    106 		}
    107 	}
    108 	ei = found;
    109 	if (!ei) {
    110 		if (bi->num_needed >= MAX_COLLISIONS) return;
    111 		ei = calloc(1, sizeof(*ei));
    112 		if (!ei) return;
    113 
    114 		ei->old_id = ~0;
    115 		ei->filename = strdup(filename);
    116 		if (bi->last_entry) bi->last_entry->next = ei;
    117 		if (!bi->first_entry) bi->first_entry = ei;
    118 		bi->last_entry = ei;
    119 	}
    120 
    121 	if (old_id < ei->old_id) ei->old_id = old_id;
    122 	if (need_symlink && !ei->need_symlink) {
    123 		ei->need_symlink = 1;
    124 		bi->num_needed++;
    125 		memcpy(ei->digest, digest, evpmdsize);
    126 	}
    127 }
    128 
    129 static int handle_symlink(const char *filename, const char *fullpath)
    130 {
    131 	static char xdigit[] = {
    132 		 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
    133 		-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    134 		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    135 		-1,10,11,12,13,14,15
    136 	};
    137 	char linktarget[NAME_MAX], *endptr;
    138 	unsigned int hash = 0;
    139 	unsigned char ch;
    140 	int i, type, id;
    141 	ssize_t n;
    142 
    143 	for (i = 0; i < 8; i++) {
    144 		ch = filename[i] - '0';
    145 		if (ch >= countof(xdigit) || xdigit[ch] < 0)
    146 			return -1;
    147 		hash <<= 4;
    148 		hash += xdigit[ch];
    149 	}
    150 	if (filename[i++] != '.') return -1;
    151 	for (type = countof(symlink_extensions) - 1; type > 0; type--)
    152 		if (strcasecmp(symlink_extensions[type], &filename[i]) == 0)
    153 			break;
    154 	i += strlen(symlink_extensions[type]);
    155 
    156 	id = strtoul(&filename[i], &endptr, 10);
    157 	if (*endptr != 0) return -1;
    158 
    159 	n = readlink(fullpath, linktarget, sizeof(linktarget));
    160 	if (n >= sizeof(linktarget) || n < 0) return -1;
    161 	linktarget[n] = 0;
    162 
    163 	DEBUG("Found existing symlink %s for %08x (%d), certname %s\n",
    164 		filename, hash, type, linktarget);
    165 	add_entry(type, hash, linktarget, NULL, 0, id);
    166 	return 0;
    167 }
    168 
    169 static int handle_certificate(const char *filename, const char *fullpath)
    170 {
    171 	STACK_OF(X509_INFO) *inf;
    172 	X509_INFO *x;
    173 	BIO *b;
    174 	const char *ext;
    175 	unsigned char digest[EVP_MAX_MD_SIZE];
    176 	X509_NAME *name = NULL;
    177 	int i, type, ret = -1;
    178 
    179 	ext = strrchr(filename, '.');
    180 	if (ext == NULL) return 0;
    181 	for (i = 0; i < countof(file_extensions); i++) {
    182 		if (strcasecmp(file_extensions[i], ext+1) == 0)
    183 			break;
    184 	}
    185 	if (i >= countof(file_extensions)) return -1;
    186 
    187 	b = BIO_new_file(fullpath, "r");
    188 	if (!b) return -1;
    189 	inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL);
    190 	BIO_free(b);
    191 	if (!inf) return -1;
    192 
    193 	if (sk_X509_INFO_num(inf) == 1) {
    194 		x = sk_X509_INFO_value(inf, 0);
    195 		if (x->x509) {
    196 			type = TYPE_CERT;
    197 			name = X509_get_subject_name(x->x509);
    198 			X509_digest(x->x509, evpmd, digest, NULL);
    199 		} else if (x->crl) {
    200 			type = TYPE_CRL;
    201 			name = X509_CRL_get_issuer(x->crl);
    202 			X509_CRL_digest(x->crl, evpmd, digest, NULL);
    203 		}
    204 		if (name && do_hash_new)
    205 			add_entry(type, X509_NAME_hash(name), filename, digest, 1, ~0);
    206 		if (name && do_hash_old)
    207 			add_entry(type, X509_NAME_hash_old(name), filename, digest, 1, ~0);
    208 	} else {
    209 		fprintf(stderr,
    210 			"WARNING: %s does not contain exactly one certificate or CRL: skipping\n",
    211 			filename);
    212 	}
    213 
    214 	sk_X509_INFO_pop_free(inf, X509_INFO_free);
    215 
    216 	return ret;
    217 }
    218 
    219 static int hash_dir(const char *dirname)
    220 {
    221 	struct bucket_info *bi, *nextbi;
    222 	struct entry_info *ei, *nextei;
    223 	struct dirent *de;
    224 	struct stat st;
    225 	unsigned char idmask[MAX_COLLISIONS / 8];
    226 	int i, n, nextid, buflen, ret = -1;
    227 	const char *pathsep;
    228 	char *buf;
    229 	DIR *d;
    230 
    231 	if (access(dirname, R_OK|W_OK|X_OK) != 0) {
    232 		fprintf(stderr,
    233 			"ERROR: Access denied '%s'\n",
    234 			dirname);
    235 		return -1;
    236 	}
    237 
    238 	buflen = strlen(dirname);
    239 	pathsep = (buflen && dirname[buflen-1] == '/') ? "" : "/";
    240 	buflen += NAME_MAX + 2;
    241 	buf = malloc(buflen);
    242 	if (buf == NULL)
    243 		goto err;
    244 
    245 	if (do_verbose) printf("Doing %s\n", dirname);
    246 	d = opendir(dirname);
    247 	if (!d) goto err;
    248 
    249 	while ((de = readdir(d)) != NULL) {
    250 		if (snprintf(buf, buflen, "%s%s%s", dirname, pathsep, de->d_name) >= buflen)
    251 			continue;
    252 		if (lstat(buf, &st) < 0)
    253 			continue;
    254 		if (S_ISLNK(st.st_mode) && handle_symlink(de->d_name, buf) == 0)
    255 			continue;
    256 		if (strcmp(buf, "/etc/ssl/certs/ca-certificates.crt") == 0) {
    257 			/* Ignore the /etc/ssl/certs/ca-certificates.crt file */
    258 			if (do_verbose) printf("Skipping %s file\n", buf);
    259 			continue;
    260 		}
    261 		handle_certificate(de->d_name, buf);
    262 	}
    263 	closedir(d);
    264 
    265 	for (i = 0; i < countof(hash_table); i++) {
    266 		for (bi = hash_table[i]; bi; bi = nextbi) {
    267 			nextbi = bi->next;
    268 			DEBUG("Type %d, hash %08x, num entries %d:\n", bi->type, bi->hash, bi->num_needed);
    269 
    270 			nextid = 0;
    271 			memset(idmask, 0, (bi->num_needed+7)/8);
    272 			for (ei = bi->first_entry; ei; ei = ei->next)
    273 				if (ei->old_id < bi->num_needed)
    274 					bit_set(idmask, ei->old_id);
    275 
    276 			for (ei = bi->first_entry; ei; ei = nextei) {
    277 				nextei = ei->next;
    278 				DEBUG("\t(old_id %d, need_symlink %d) Cert %s\n",
    279 					ei->old_id, ei->need_symlink,
    280 					ei->filename);
    281 
    282 				if (ei->old_id < bi->num_needed) {
    283 					/* Link exists, and is used as-is */
    284 					snprintf(buf, buflen, "%08x.%s%d", bi->hash, symlink_extensions[bi->type], ei->old_id);
    285 					if (do_verbose) printf("link %s -> %s\n", ei->filename, buf);
    286 				} else if (ei->need_symlink) {
    287 					/* New link needed (it may replace something) */
    288 					while (bit_isset(idmask, nextid))
    289 						nextid++;
    290 
    291 					snprintf(buf, buflen, "%s%s%n%08x.%s%d",
    292 						 dirname, pathsep, &n, bi->hash,
    293 						 symlink_extensions[bi->type],
    294 						 nextid);
    295 					if (do_verbose) printf("link %s -> %s\n", ei->filename, &buf[n]);
    296 					unlink(buf);
    297 					symlink(ei->filename, buf);
    298 				} else if (do_remove_links) {
    299 					/* Link to be deleted */
    300 					snprintf(buf, buflen, "%s%s%n%08x.%s%d",
    301 						 dirname, pathsep, &n, bi->hash,
    302 						 symlink_extensions[bi->type],
    303 						 ei->old_id);
    304 					if (do_verbose) printf("unlink %s\n", &buf[n]);
    305 					unlink(buf);
    306 				}
    307 				free(ei->filename);
    308 				free(ei);
    309 			}
    310 			free(bi);
    311 		}
    312 		hash_table[i] = NULL;
    313 	}
    314 
    315 	ret = 0;
    316 err:
    317 	free(buf);
    318 	return ret;
    319 }
    320 
    321 static void c_rehash_usage(void)
    322 {
    323 	printf("\
    324 usage: c_rehash <args> <dirs>\n\
    325 \n\
    326 -compat       - create new- and old-style hashed links\n\
    327 -old          - use old-style hashing for generating links\n\
    328 -h            - display this help\n\
    329 -n            - do not remove existing links\n\
    330 -v            - be more verbose\n\
    331 \n");
    332 }
    333 
    334 int main(int argc, char **argv)
    335 {
    336 	const char *env, *opt;
    337 	int i, numargs, r = 0;
    338 
    339 	evpmd = EVP_sha1();
    340 	evpmdsize = EVP_MD_size(evpmd);
    341 
    342 	numargs = argc;
    343 	for (i = 1; i < argc; i++) {
    344 		if (argv[i][0] != '-') continue;
    345 		if (strcmp(argv[i], "--") == 0) { argv[i] = 0; numargs--; break; }
    346 		opt = &argv[i][1];
    347 		if (strcmp(opt, "compat") == 0) {
    348 			do_hash_new = do_hash_old = 1;
    349 		} else if (strcmp(opt, "old") == 0) {
    350 			do_hash_new = 0;
    351 			do_hash_old = 1;
    352 		} else if (strcmp(opt, "n") == 0) {
    353 			do_remove_links = 0;
    354 		} else if (strcmp(opt, "v") == 0) {
    355 			do_verbose++;
    356 		} else {
    357 			if (strcmp(opt, "h") != 0)
    358 				fprintf(stderr, "unknown option %s\n", argv[i]);
    359 			c_rehash_usage();
    360 			return 1;
    361 		}
    362 		argv[i] = 0;
    363 		numargs--;
    364 	}
    365 
    366 	if (numargs > 1) {
    367 		for (i = 1; i < argc; i++)
    368 			if (argv[i]) r |= hash_dir(argv[i]);
    369 	} else if ((env = getenv("SSL_CERT_DIR")) != NULL) {
    370 		char *e, *m;
    371 		m = strdup(env);
    372 		for (e = strtok(m, ":"); e != NULL; e = strtok(NULL, ":"))
    373 			r |= hash_dir(e);
    374 		free(m);
    375 	} else {
    376 		r |= hash_dir("/etc/ssl/certs");
    377 	}
    378 
    379 	return r ? 2 : 0;
    380 }