rehash.c 13.8 KB
Newer Older
T
Timo Teras 已提交
1
/*
R
Rich Salz 已提交
2
 * Copyright 2015-2016 The OpenSSL Project Authors. All Rights Reserved.
T
Timo Teras 已提交
3
 *
R
Rich Salz 已提交
4 5 6 7
 * Licensed under the OpenSSL license (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
T
Timo Teras 已提交
8
 */
R
Rich Salz 已提交
9 10 11

/*
 * C implementation based on the original Perl and shell versions
T
Timo Teras 已提交
12
 *
R
Rich Salz 已提交
13
 * Copyright (c) 2013-2014 Timo Teräs <timo.teras@iki.fi>
T
Timo Teras 已提交
14 15 16 17
 */

#include "apps.h"

18 19
#if defined(OPENSSL_SYS_UNIX) || defined(__APPLE__) || \
    (defined(__VMS) && defined(__DECC) && __CTRL_VER >= 80300000)
T
Timo Teras 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33
# include <unistd.h>
# include <stdio.h>
# include <limits.h>
# include <errno.h>
# include <string.h>
# include <ctype.h>
# include <sys/stat.h>

# include "internal/o_dir.h"
# include <openssl/evp.h>
# include <openssl/pem.h>
# include <openssl/x509.h>


34 35 36
# ifndef PATH_MAX
#  define PATH_MAX 4096
# endif
37 38 39
# ifndef NAME_MAX
#  define NAME_MAX 255
# endif
T
Timo Teras 已提交
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
# define MAX_COLLISIONS  256

typedef struct hentry_st {
    struct hentry_st *next;
    char *filename;
    unsigned short old_id;
    unsigned char need_symlink;
    unsigned char digest[EVP_MAX_MD_SIZE];
} HENTRY;

typedef struct bucket_st {
    struct bucket_st *next;
    HENTRY *first_entry, *last_entry;
    unsigned int hash;
    unsigned short type;
    unsigned short num_needed;
} BUCKET;

enum Type {
    /* Keep in sync with |suffixes|, below. */
    TYPE_CERT=0, TYPE_CRL=1
};

enum Hash {
    HASH_OLD, HASH_NEW, HASH_BOTH
};


static int evpmdsize;
static const EVP_MD *evpmd;
static int remove_links = 1;
static int verbose = 0;
static BUCKET *hash_table[257];

static const char *suffixes[] = { "", "r" };
static const char *extensions[] = { "pem", "crt", "cer", "crl" };


static void bit_set(unsigned char *set, unsigned int bit)
{
    set[bit >> 3] |= 1 << (bit & 0x7);
}

static int bit_isset(unsigned char *set, unsigned int bit)
{
    return set[bit >> 3] & (1 << (bit & 0x7));
}


R
Rich Salz 已提交
89 90 91 92
/*
 * Process an entry; return number of errors.
 */
static int add_entry(enum Type type, unsigned int hash, const char *filename,
T
Timo Teras 已提交
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
                      const unsigned char *digest, int need_symlink,
                      unsigned short old_id)
{
    static BUCKET nilbucket;
    static HENTRY nilhentry;
    BUCKET *bp;
    HENTRY *ep, *found = NULL;
    unsigned int ndx = (type + hash) % OSSL_NELEM(hash_table);

    for (bp = hash_table[ndx]; bp; bp = bp->next)
        if (bp->type == type && bp->hash == hash)
            break;
    if (bp == NULL) {
        bp = app_malloc(sizeof(*bp), "hash bucket");
        *bp = nilbucket;
        bp->next = hash_table[ndx];
        bp->type = type;
        bp->hash = hash;
        hash_table[ndx] = bp;
    }

    for (ep = bp->first_entry; ep; ep = ep->next) {
        if (digest && memcmp(digest, ep->digest, evpmdsize) == 0) {
            BIO_printf(bio_err,
117 118
                       "%s: skipping duplicate %s in %s\n", opt_getprog(),
                       type == TYPE_CERT ? "certificate" : "CRL", filename);
R
Rich Salz 已提交
119
            return 1;
T
Timo Teras 已提交
120 121 122 123 124 125 126 127 128
        }
        if (strcmp(filename, ep->filename) == 0) {
            found = ep;
            if (digest == NULL)
                break;
        }
    }
    ep = found;
    if (ep == NULL) {
R
Rich Salz 已提交
129 130 131 132 133 134
        if (bp->num_needed >= MAX_COLLISIONS) {
            BIO_printf(bio_err,
                       "%s: hash table overflow for %s\n",
                       opt_getprog(), filename);
            return 1;
        }
T
Timo Teras 已提交
135 136 137
        ep = app_malloc(sizeof(*ep), "collision bucket");
        *ep = nilhentry;
        ep->old_id = ~0;
R
Rich Salz 已提交
138
        ep->filename = OPENSSL_strdup(filename);
T
Timo Teras 已提交
139 140 141 142 143 144 145 146 147 148 149 150 151 152
        if (bp->last_entry)
            bp->last_entry->next = ep;
        if (bp->first_entry == NULL)
            bp->first_entry = ep;
        bp->last_entry = ep;
    }

    if (old_id < ep->old_id)
        ep->old_id = old_id;
    if (need_symlink && !ep->need_symlink) {
        ep->need_symlink = 1;
        bp->num_needed++;
        memcpy(ep->digest, digest, evpmdsize);
    }
R
Rich Salz 已提交
153
    return 0;
T
Timo Teras 已提交
154 155
}

R
Rich Salz 已提交
156 157 158 159
/*
 * Check if a symlink goes to the right spot; return 0 if okay.
 * This can be -1 if bad filename, or an error count.
 */
T
Timo Teras 已提交
160 161 162 163 164
static int handle_symlink(const char *filename, const char *fullpath)
{
    unsigned int hash = 0;
    int i, type, id;
    unsigned char ch;
V
Vladimir Kotal 已提交
165
    char linktarget[PATH_MAX], *endptr;
166
    ossl_ssize_t n;
T
Timo Teras 已提交
167 168 169 170 171 172

    for (i = 0; i < 8; i++) {
        ch = filename[i];
        if (!isxdigit(ch))
            return -1;
        hash <<= 4;
173
        hash += OPENSSL_hexchar2int(ch);
T
Timo Teras 已提交
174 175 176
    }
    if (filename[i++] != '.')
        return -1;
D
Dr. Stephen Henson 已提交
177 178 179
    for (type = OSSL_NELEM(suffixes) - 1; type > 0; type--) {
        const char *suffix = suffixes[type];
        if (strncasecmp(suffix, &filename[i], strlen(suffix)) == 0)
T
Timo Teras 已提交
180
            break;
D
Dr. Stephen Henson 已提交
181
    }
T
Timo Teras 已提交
182 183 184 185 186 187 188 189 190 191 192
    i += strlen(suffixes[type]);

    id = strtoul(&filename[i], &endptr, 10);
    if (*endptr != '\0')
        return -1;

    n = readlink(fullpath, linktarget, sizeof(linktarget));
    if (n < 0 || n >= (int)sizeof(linktarget))
        return -1;
    linktarget[n] = 0;

R
Rich Salz 已提交
193
    return add_entry(type, hash, linktarget, NULL, 0, id);
T
Timo Teras 已提交
194 195
}

R
Rich Salz 已提交
196 197 198
/*
 * process a file, return number of errors.
 */
T
Timo Teras 已提交
199 200
static int do_file(const char *filename, const char *fullpath, enum Hash h)
{
R
Rich Salz 已提交
201
    STACK_OF (X509_INFO) *inf = NULL;
T
Timo Teras 已提交
202 203 204 205 206
    X509_INFO *x;
    X509_NAME *name = NULL;
    BIO *b;
    const char *ext;
    unsigned char digest[EVP_MAX_MD_SIZE];
207 208
    int type, errs = 0;
    size_t i;
T
Timo Teras 已提交
209

R
Rich Salz 已提交
210
    /* Does it end with a recognized extension? */
T
Timo Teras 已提交
211
    if ((ext = strrchr(filename, '.')) == NULL)
R
Rich Salz 已提交
212
        goto end;
213
    for (i = 0; i < OSSL_NELEM(extensions); i++) {
T
Timo Teras 已提交
214 215 216
        if (strcasecmp(extensions[i], ext + 1) == 0)
            break;
    }
217
    if (i >= OSSL_NELEM(extensions))
R
Rich Salz 已提交
218
        goto end;
T
Timo Teras 已提交
219

R
Rich Salz 已提交
220 221 222 223 224 225 226
    /* Does it have X.509 data in it? */
    if ((b = BIO_new_file(fullpath, "r")) == NULL) {
        BIO_printf(bio_err, "%s: skipping %s, cannot open file\n",
                   opt_getprog(), filename);
        errs++;
        goto end;
    }
T
Timo Teras 已提交
227 228 229
    inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL);
    BIO_free(b);
    if (inf == NULL)
R
Rich Salz 已提交
230
        goto end;
T
Timo Teras 已提交
231 232 233 234 235 236

    if (sk_X509_INFO_num(inf) != 1) {
        BIO_printf(bio_err,
                   "%s: skipping %s,"
                   "it does not contain exactly one certificate or CRL\n",
                   opt_getprog(), filename);
R
Rich Salz 已提交
237
        /* This is not an error. */
T
Timo Teras 已提交
238 239 240 241 242 243 244 245 246 247 248
        goto end;
    }
    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);
249 250 251
    } else {
        ++errs;
        goto end;
T
Timo Teras 已提交
252 253 254
    }
    if (name) {
        if ((h == HASH_NEW) || (h == HASH_BOTH))
R
Rich Salz 已提交
255
            errs += add_entry(type, X509_NAME_hash(name), filename, digest, 1, ~0);
T
Timo Teras 已提交
256
        if ((h == HASH_OLD) || (h == HASH_BOTH))
R
Rich Salz 已提交
257
            errs += add_entry(type, X509_NAME_hash_old(name), filename, digest, 1, ~0);
T
Timo Teras 已提交
258 259 260 261
    }

end:
    sk_X509_INFO_pop_free(inf, X509_INFO_free);
R
Rich Salz 已提交
262
    return errs;
T
Timo Teras 已提交
263 264
}

R
Rich Salz 已提交
265 266 267 268 269
static void str_free(char *s)
{
    OPENSSL_free(s);
}

R
Rich Salz 已提交
270 271 272
/*
 * Process a directory; return number of errors found.
 */
T
Timo Teras 已提交
273 274 275 276 277 278 279
static int do_dir(const char *dirname, enum Hash h)
{
    BUCKET *bp, *nextbp;
    HENTRY *ep, *nextep;
    OPENSSL_DIR_CTX *d = NULL;
    struct stat st;
    unsigned char idmask[MAX_COLLISIONS / 8];
R
Rich Salz 已提交
280
    int n, numfiles, nextid, buflen, errs = 0;
281
    size_t i;
T
Timo Teras 已提交
282 283
    const char *pathsep;
    const char *filename;
R
Rich Salz 已提交
284 285
    char *buf, *copy;
    STACK_OF(OPENSSL_STRING) *files = NULL;
T
Timo Teras 已提交
286

287 288
    if (app_access(dirname, W_OK) < 0) {
        BIO_printf(bio_err, "Skipping %s, can't write\n", dirname);
R
Rich Salz 已提交
289
        return 1;
290
    }
T
Timo Teras 已提交
291 292
    buflen = strlen(dirname);
    pathsep = (buflen && dirname[buflen - 1] == '/') ? "" : "/";
293 294
    buflen += NAME_MAX + 1 + 1;
    buf = app_malloc(buflen, "filename buffer");
T
Timo Teras 已提交
295 296 297 298

    if (verbose)
        BIO_printf(bio_out, "Doing %s\n", dirname);

R
Rich Salz 已提交
299 300 301 302
    if ((files = sk_OPENSSL_STRING_new_null()) == NULL) {
        BIO_printf(bio_err, "Skipping %s, out of memory\n", dirname);
        exit(1);
    }
T
Timo Teras 已提交
303
    while ((filename = OPENSSL_DIR_read(&d, dirname)) != NULL) {
R
Rich Salz 已提交
304 305 306 307 308 309 310 311 312 313 314 315
        if ((copy = strdup(filename)) == NULL
                || sk_OPENSSL_STRING_push(files, copy) == 0) {
            BIO_puts(bio_err, "out of memory\n");
            exit(1);
        }
    }
    OPENSSL_DIR_end(&d);
    sk_OPENSSL_STRING_sort(files);

    numfiles = sk_OPENSSL_STRING_num(files);
    for (n = 0; n < numfiles; ++n) {
        filename = sk_OPENSSL_STRING_value(files, n);
T
Timo Teras 已提交
316 317 318 319 320 321 322
        if (snprintf(buf, buflen, "%s%s%s",
                    dirname, pathsep, filename) >= buflen)
            continue;
        if (lstat(buf, &st) < 0)
            continue;
        if (S_ISLNK(st.st_mode) && handle_symlink(filename, buf) == 0)
            continue;
R
Rich Salz 已提交
323
        errs += do_file(filename, buf, h);
T
Timo Teras 已提交
324
    }
R
Rich Salz 已提交
325
    sk_OPENSSL_STRING_pop_free(files, str_free);
T
Timo Teras 已提交
326

327
    for (i = 0; i < OSSL_NELEM(hash_table); i++) {
T
Timo Teras 已提交
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
        for (bp = hash_table[i]; bp; bp = nextbp) {
            nextbp = bp->next;
            nextid = 0;
            memset(idmask, 0, (bp->num_needed + 7) / 8);
            for (ep = bp->first_entry; ep; ep = ep->next)
                if (ep->old_id < bp->num_needed)
                    bit_set(idmask, ep->old_id);

            for (ep = bp->first_entry; ep; ep = nextep) {
                nextep = ep->next;
                if (ep->old_id < bp->num_needed) {
                    /* Link exists, and is used as-is */
                    snprintf(buf, buflen, "%08x.%s%d", bp->hash,
                             suffixes[bp->type], ep->old_id);
                    if (verbose)
                        BIO_printf(bio_out, "link %s -> %s\n",
                                   ep->filename, buf);
                } else if (ep->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, bp->hash,
                             suffixes[bp->type], nextid);
                    if (verbose)
                        BIO_printf(bio_out, "link %s -> %s\n",
                                   ep->filename, &buf[n]);
R
Rich Salz 已提交
356
                    if (unlink(buf) < 0 && errno != ENOENT) {
T
Timo Teras 已提交
357 358 359
                        BIO_printf(bio_err,
                                   "%s: Can't unlink %s, %s\n",
                                   opt_getprog(), buf, strerror(errno));
R
Rich Salz 已提交
360 361 362
                        errs++;
                    }
                    if (symlink(ep->filename, buf) < 0) {
T
Timo Teras 已提交
363 364 365 366
                        BIO_printf(bio_err,
                                   "%s: Can't symlink %s, %s\n",
                                   opt_getprog(), ep->filename,
                                   strerror(errno));
R
Rich Salz 已提交
367 368
                        errs++;
                    }
369
                    bit_set(idmask, nextid);
T
Timo Teras 已提交
370 371 372 373 374 375 376 377
                } else if (remove_links) {
                    /* Link to be deleted */
                    snprintf(buf, buflen, "%s%s%n%08x.%s%d",
                             dirname, pathsep, &n, bp->hash,
                             suffixes[bp->type], ep->old_id);
                    if (verbose)
                        BIO_printf(bio_out, "unlink %s\n",
                                   &buf[n]);
R
Rich Salz 已提交
378
                    if (unlink(buf) < 0 && errno != ENOENT) {
T
Timo Teras 已提交
379 380 381
                        BIO_printf(bio_err,
                                   "%s: Can't unlink %s, %s\n",
                                   opt_getprog(), buf, strerror(errno));
R
Rich Salz 已提交
382 383
                        errs++;
                    }
T
Timo Teras 已提交
384 385 386 387 388 389 390 391 392 393
                }
                OPENSSL_free(ep->filename);
                OPENSSL_free(ep);
            }
            OPENSSL_free(bp);
        }
        hash_table[i] = NULL;
    }

    OPENSSL_free(buf);
R
Rich Salz 已提交
394
    return errs;
T
Timo Teras 已提交
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
}

typedef enum OPTION_choice {
    OPT_ERR = -1, OPT_EOF = 0, OPT_HELP,
    OPT_COMPAT, OPT_OLD, OPT_N, OPT_VERBOSE
} OPTION_CHOICE;

OPTIONS rehash_options[] = {
    {OPT_HELP_STR, 1, '-', "Usage: %s [options] [cert-directory...]\n"},
    {OPT_HELP_STR, 1, '-', "Valid options are:\n"},
    {"help", OPT_HELP, '-', "Display this summary"},
    {"compat", OPT_COMPAT, '-', "Create both new- and old-style hash links"},
    {"old", OPT_OLD, '-', "Use old-style hash to generate links"},
    {"n", OPT_N, '-', "Do not remove existing links"},
    {"v", OPT_VERBOSE, '-', "Verbose output"},
    {NULL}
};


int rehash_main(int argc, char **argv)
{
    const char *env, *prog;
    char *e, *m;
R
Rich Salz 已提交
418
    int errs = 0;
T
Timo Teras 已提交
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
    OPTION_CHOICE o;
    enum Hash h = HASH_NEW;

    prog = opt_init(argc, argv, rehash_options);
    while ((o = opt_next()) != OPT_EOF) {
        switch (o) {
        case OPT_EOF:
        case OPT_ERR:
            BIO_printf(bio_err, "%s: Use -help for summary.\n", prog);
            goto end;
        case OPT_HELP:
            opt_help(rehash_options);
            goto end;
        case OPT_COMPAT:
            h = HASH_BOTH;
            break;
        case OPT_OLD:
            h = HASH_OLD;
            break;
        case OPT_N:
            remove_links = 0;
            break;
        case OPT_VERBOSE:
            verbose = 1;
            break;
        }
    }
    argc = opt_num_rest();
    argv = opt_rest();

    evpmd = EVP_sha1();
    evpmdsize = EVP_MD_size(evpmd);

    if (*argv) {
        while (*argv)
R
Rich Salz 已提交
454
            errs += do_dir(*argv++, h);
T
Timo Teras 已提交
455
    } else if ((env = getenv("SSL_CERT_DIR")) != NULL) {
R
Rich Salz 已提交
456
        m = OPENSSL_strdup(env);
T
Timo Teras 已提交
457
        for (e = strtok(m, ":"); e != NULL; e = strtok(NULL, ":"))
R
Rich Salz 已提交
458
            errs += do_dir(e, h);
T
Timo Teras 已提交
459 460
        OPENSSL_free(m);
    } else {
R
Rich Salz 已提交
461
        errs += do_dir("/etc/ssl/certs", h);
T
Timo Teras 已提交
462 463 464
    }

 end:
R
Rich Salz 已提交
465
    return errs;
T
Timo Teras 已提交
466 467 468
}

#else
R
Rich Salz 已提交
469 470 471
OPTIONS rehash_options[] = {
    {NULL}
};
T
Timo Teras 已提交
472 473 474

int rehash_main(int argc, char **argv)
{
475
    BIO_printf(bio_err, "Not available; use c_rehash script\n");
T
Timo Teras 已提交
476 477 478
    return (1);
}

479
#endif /* defined(OPENSSL_SYS_UNIX) || defined(__APPLE__) */