rehash.c 14.9 KB
Newer Older
T
Timo Teras 已提交
1
/*
M
Matt Caswell 已提交
2
 * Copyright 2015-2018 The OpenSSL Project Authors. All Rights Reserved.
R
Rich Salz 已提交
3
 * Copyright (c) 2013-2014 Timo Ters <timo.teras@gmail.com>
T
Timo Teras 已提交
4
 *
R
Rich Salz 已提交
5 6 7 8
 * 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 已提交
9
 */
R
Rich Salz 已提交
10

T
Timo Teras 已提交
11
#include "apps.h"
12
#include "progs.h"
T
Timo Teras 已提交
13

14
#if defined(OPENSSL_SYS_UNIX) || defined(__APPLE__) || \
15
    (defined(__VMS) && defined(__DECC) && __CRTL_VER >= 80300000)
T
Timo Teras 已提交
16 17 18 19 20 21 22 23
# include <unistd.h>
# include <stdio.h>
# include <limits.h>
# include <errno.h>
# include <string.h>
# include <ctype.h>
# include <sys/stat.h>

24 25 26 27 28 29 30 31 32 33 34
/*
 * Make sure that the processing of symbol names is treated the same as when
 * libcrypto is built.  This is done automatically for public headers (see
 * include/openssl/__DECC_INCLUDE_PROLOGUE.H and __DECC_INCLUDE_EPILOGUE.H),
 * but not for internal headers.
 */
# ifdef __VMS
#  pragma names save
#  pragma names as_is,shortened
# endif

T
Timo Teras 已提交
35
# include "internal/o_dir.h"
36 37 38 39 40

# ifdef __VMS
#  pragma names restore
# endif

T
Timo Teras 已提交
41 42 43 44 45
# include <openssl/evp.h>
# include <openssl/pem.h>
# include <openssl/x509.h>


46 47 48
# ifndef PATH_MAX
#  define PATH_MAX 4096
# endif
49 50 51
# ifndef NAME_MAX
#  define NAME_MAX 255
# endif
T
Timo Teras 已提交
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 89 90 91 92 93 94 95 96 97 98 99 100
# 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 已提交
101 102 103 104
/*
 * Process an entry; return number of errors.
 */
static int add_entry(enum Type type, unsigned int hash, const char *filename,
T
Timo Teras 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
                      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,
129 130
                       "%s: skipping duplicate %s in %s\n", opt_getprog(),
                       type == TYPE_CERT ? "certificate" : "CRL", filename);
R
Rich Salz 已提交
131
            return 1;
T
Timo Teras 已提交
132 133 134 135 136 137 138 139 140
        }
        if (strcmp(filename, ep->filename) == 0) {
            found = ep;
            if (digest == NULL)
                break;
        }
    }
    ep = found;
    if (ep == NULL) {
R
Rich Salz 已提交
141 142 143 144 145 146
        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 已提交
147 148 149
        ep = app_malloc(sizeof(*ep), "collision bucket");
        *ep = nilhentry;
        ep->old_id = ~0;
R
Rich Salz 已提交
150
        ep->filename = OPENSSL_strdup(filename);
T
Timo Teras 已提交
151 152 153 154 155 156 157 158 159 160 161 162 163 164
        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 已提交
165
    return 0;
T
Timo Teras 已提交
166 167
}

R
Rich Salz 已提交
168 169 170 171
/*
 * 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 已提交
172 173 174 175 176
static int handle_symlink(const char *filename, const char *fullpath)
{
    unsigned int hash = 0;
    int i, type, id;
    unsigned char ch;
V
Vladimir Kotal 已提交
177
    char linktarget[PATH_MAX], *endptr;
178
    ossl_ssize_t n;
T
Timo Teras 已提交
179 180 181 182 183 184

    for (i = 0; i < 8; i++) {
        ch = filename[i];
        if (!isxdigit(ch))
            return -1;
        hash <<= 4;
185
        hash += OPENSSL_hexchar2int(ch);
T
Timo Teras 已提交
186 187 188
    }
    if (filename[i++] != '.')
        return -1;
D
Dr. Stephen Henson 已提交
189 190 191
    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 已提交
192
            break;
D
Dr. Stephen Henson 已提交
193
    }
T
Timo Teras 已提交
194 195 196 197 198 199 200 201 202 203 204
    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 已提交
205
    return add_entry(type, hash, linktarget, NULL, 0, id);
T
Timo Teras 已提交
206 207
}

R
Rich Salz 已提交
208 209 210
/*
 * process a file, return number of errors.
 */
T
Timo Teras 已提交
211 212
static int do_file(const char *filename, const char *fullpath, enum Hash h)
{
R
Rich Salz 已提交
213
    STACK_OF (X509_INFO) *inf = NULL;
T
Timo Teras 已提交
214 215 216 217 218
    X509_INFO *x;
    X509_NAME *name = NULL;
    BIO *b;
    const char *ext;
    unsigned char digest[EVP_MAX_MD_SIZE];
219 220
    int type, errs = 0;
    size_t i;
T
Timo Teras 已提交
221

R
Rich Salz 已提交
222
    /* Does it end with a recognized extension? */
T
Timo Teras 已提交
223
    if ((ext = strrchr(filename, '.')) == NULL)
R
Rich Salz 已提交
224
        goto end;
225
    for (i = 0; i < OSSL_NELEM(extensions); i++) {
T
Timo Teras 已提交
226 227 228
        if (strcasecmp(extensions[i], ext + 1) == 0)
            break;
    }
229
    if (i >= OSSL_NELEM(extensions))
R
Rich Salz 已提交
230
        goto end;
T
Timo Teras 已提交
231

R
Rich Salz 已提交
232 233 234 235 236 237 238
    /* 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 已提交
239 240 241
    inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL);
    BIO_free(b);
    if (inf == NULL)
R
Rich Salz 已提交
242
        goto end;
T
Timo Teras 已提交
243 244 245 246 247 248

    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 已提交
249
        /* This is not an error. */
T
Timo Teras 已提交
250 251 252
        goto end;
    }
    x = sk_X509_INFO_value(inf, 0);
253
    if (x->x509 != NULL) {
T
Timo Teras 已提交
254 255 256
        type = TYPE_CERT;
        name = X509_get_subject_name(x->x509);
        X509_digest(x->x509, evpmd, digest, NULL);
257
    } else if (x->crl != NULL) {
T
Timo Teras 已提交
258 259 260
        type = TYPE_CRL;
        name = X509_CRL_get_issuer(x->crl);
        X509_CRL_digest(x->crl, evpmd, digest, NULL);
261 262 263
    } else {
        ++errs;
        goto end;
T
Timo Teras 已提交
264
    }
265
    if (name != NULL) {
T
Timo Teras 已提交
266
        if ((h == HASH_NEW) || (h == HASH_BOTH))
R
Rich Salz 已提交
267
            errs += add_entry(type, X509_NAME_hash(name), filename, digest, 1, ~0);
T
Timo Teras 已提交
268
        if ((h == HASH_OLD) || (h == HASH_BOTH))
R
Rich Salz 已提交
269
            errs += add_entry(type, X509_NAME_hash_old(name), filename, digest, 1, ~0);
T
Timo Teras 已提交
270 271 272 273
    }

end:
    sk_X509_INFO_pop_free(inf, X509_INFO_free);
R
Rich Salz 已提交
274
    return errs;
T
Timo Teras 已提交
275 276
}

R
Rich Salz 已提交
277 278 279 280 281
static void str_free(char *s)
{
    OPENSSL_free(s);
}

282 283 284 285
static int ends_with_dirsep(const char *path)
{
    if (*path != '\0')
        path += strlen(path) - 1;
286
# if defined __VMS
287 288
    if (*path == ']' || *path == '>' || *path == ':')
        return 1;
289
# elif defined _WIN32
290 291 292 293 294 295
    if (*path == '\\')
        return 1;
# endif
    return *path == '/';
}

296 297 298 299 300 301 302 303
static int massage_filename(char *name)
{
# ifdef __VMS
    char *p = strchr(name, ';');
    char *q = p;

    if (q != NULL) {
        for (q++; *q != '\0'; q++) {
P
Pauli 已提交
304
            if (!isdigit((unsigned char)*q))
305 306 307 308 309 310 311 312 313
                return 1;
        }
    }

    *p = '\0';
# endif
    return 1;
}

R
Rich Salz 已提交
314 315 316
/*
 * Process a directory; return number of errors found.
 */
T
Timo Teras 已提交
317 318 319 320 321 322 323
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 已提交
324
    int n, numfiles, nextid, buflen, errs = 0;
325
    size_t i;
T
Timo Teras 已提交
326 327
    const char *pathsep;
    const char *filename;
R
Rich Salz 已提交
328 329
    char *buf, *copy;
    STACK_OF(OPENSSL_STRING) *files = NULL;
T
Timo Teras 已提交
330

331 332
    if (app_access(dirname, W_OK) < 0) {
        BIO_printf(bio_err, "Skipping %s, can't write\n", dirname);
R
Rich Salz 已提交
333
        return 1;
334
    }
T
Timo Teras 已提交
335
    buflen = strlen(dirname);
336
    pathsep = (buflen && !ends_with_dirsep(dirname)) ? "/": "";
337 338
    buflen += NAME_MAX + 1 + 1;
    buf = app_malloc(buflen, "filename buffer");
T
Timo Teras 已提交
339 340 341 342

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

R
Rich Salz 已提交
343 344 345 346
    if ((files = sk_OPENSSL_STRING_new_null()) == NULL) {
        BIO_printf(bio_err, "Skipping %s, out of memory\n", dirname);
        exit(1);
    }
T
Timo Teras 已提交
347
    while ((filename = OPENSSL_DIR_read(&d, dirname)) != NULL) {
R
Rich Salz 已提交
348
        if ((copy = strdup(filename)) == NULL
349
                || !massage_filename(copy)
R
Rich Salz 已提交
350 351 352 353 354 355 356 357 358 359 360
                || 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);
361 362
        if (BIO_snprintf(buf, buflen, "%s%s%s",
                         dirname, pathsep, filename) >= buflen)
T
Timo Teras 已提交
363 364 365 366 367
            continue;
        if (lstat(buf, &st) < 0)
            continue;
        if (S_ISLNK(st.st_mode) && handle_symlink(filename, buf) == 0)
            continue;
R
Rich Salz 已提交
368
        errs += do_file(filename, buf, h);
T
Timo Teras 已提交
369
    }
R
Rich Salz 已提交
370
    sk_OPENSSL_STRING_pop_free(files, str_free);
T
Timo Teras 已提交
371

372
    for (i = 0; i < OSSL_NELEM(hash_table); i++) {
T
Timo Teras 已提交
373 374 375 376 377 378 379 380 381 382 383 384
        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 */
385 386
                    BIO_snprintf(buf, buflen, "%08x.%s%d", bp->hash,
                                 suffixes[bp->type], ep->old_id);
T
Timo Teras 已提交
387 388 389 390 391 392 393 394
                    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++;

395 396 397
                    BIO_snprintf(buf, buflen, "%s%s%n%08x.%s%d",
                                 dirname, pathsep, &n, bp->hash,
                                 suffixes[bp->type], nextid);
T
Timo Teras 已提交
398 399 400
                    if (verbose)
                        BIO_printf(bio_out, "link %s -> %s\n",
                                   ep->filename, &buf[n]);
R
Rich Salz 已提交
401
                    if (unlink(buf) < 0 && errno != ENOENT) {
T
Timo Teras 已提交
402 403 404
                        BIO_printf(bio_err,
                                   "%s: Can't unlink %s, %s\n",
                                   opt_getprog(), buf, strerror(errno));
R
Rich Salz 已提交
405 406 407
                        errs++;
                    }
                    if (symlink(ep->filename, buf) < 0) {
T
Timo Teras 已提交
408 409 410 411
                        BIO_printf(bio_err,
                                   "%s: Can't symlink %s, %s\n",
                                   opt_getprog(), ep->filename,
                                   strerror(errno));
R
Rich Salz 已提交
412 413
                        errs++;
                    }
414
                    bit_set(idmask, nextid);
T
Timo Teras 已提交
415 416
                } else if (remove_links) {
                    /* Link to be deleted */
417 418 419
                    BIO_snprintf(buf, buflen, "%s%s%n%08x.%s%d",
                                 dirname, pathsep, &n, bp->hash,
                                 suffixes[bp->type], ep->old_id);
T
Timo Teras 已提交
420 421 422
                    if (verbose)
                        BIO_printf(bio_out, "unlink %s\n",
                                   &buf[n]);
R
Rich Salz 已提交
423
                    if (unlink(buf) < 0 && errno != ENOENT) {
T
Timo Teras 已提交
424 425 426
                        BIO_printf(bio_err,
                                   "%s: Can't unlink %s, %s\n",
                                   opt_getprog(), buf, strerror(errno));
R
Rich Salz 已提交
427 428
                        errs++;
                    }
T
Timo Teras 已提交
429 430 431 432 433 434 435 436 437 438
                }
                OPENSSL_free(ep->filename);
                OPENSSL_free(ep);
            }
            OPENSSL_free(bp);
        }
        hash_table[i] = NULL;
    }

    OPENSSL_free(buf);
R
Rich Salz 已提交
439
    return errs;
T
Timo Teras 已提交
440 441 442 443 444 445 446
}

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

F
FdaSilvaYY 已提交
447
const OPTIONS rehash_options[] = {
T
Timo Teras 已提交
448 449 450
    {OPT_HELP_STR, 1, '-', "Usage: %s [options] [cert-directory...]\n"},
    {OPT_HELP_STR, 1, '-', "Valid options are:\n"},
    {"help", OPT_HELP, '-', "Display this summary"},
451
    {"h", OPT_HELP, '-', "Display this summary"},
T
Timo Teras 已提交
452 453 454 455 456 457 458 459 460 461 462 463
    {"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 已提交
464
    int errs = 0;
T
Timo Teras 已提交
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    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);

498 499
    if (*argv != NULL) {
        while (*argv != NULL)
R
Rich Salz 已提交
500
            errs += do_dir(*argv++, h);
T
Timo Teras 已提交
501
    } else if ((env = getenv("SSL_CERT_DIR")) != NULL) {
R
Rich Salz 已提交
502
        m = OPENSSL_strdup(env);
T
Timo Teras 已提交
503
        for (e = strtok(m, ":"); e != NULL; e = strtok(NULL, ":"))
R
Rich Salz 已提交
504
            errs += do_dir(e, h);
T
Timo Teras 已提交
505 506
        OPENSSL_free(m);
    } else {
R
Rich Salz 已提交
507
        errs += do_dir("/etc/ssl/certs", h);
T
Timo Teras 已提交
508 509 510
    }

 end:
R
Rich Salz 已提交
511
    return errs;
T
Timo Teras 已提交
512 513 514
}

#else
F
FdaSilvaYY 已提交
515
const OPTIONS rehash_options[] = {
R
Rich Salz 已提交
516 517
    {NULL}
};
T
Timo Teras 已提交
518 519 520

int rehash_main(int argc, char **argv)
{
521
    BIO_printf(bio_err, "Not available; use c_rehash script\n");
522
    return 1;
T
Timo Teras 已提交
523 524
}

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