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 }