[BACK]Return to certhash.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / openssl

Annotation of src/usr.bin/openssl/certhash.c, Revision 1.2

1.1       jsing       1: /*
                      2:  * Copyright (c) 2014, 2015 Joel Sing <jsing@openbsd.org>
                      3:  *
                      4:  * Permission to use, copy, modify, and distribute this software for any
                      5:  * purpose with or without fee is hereby granted, provided that the above
                      6:  * copyright notice and this permission notice appear in all copies.
                      7:  *
                      8:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
                      9:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
                     10:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
                     11:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     12:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     13:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     14:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
                     15:  */
                     16:
                     17: #include <sys/param.h>
                     18: #include <sys/types.h>
                     19: #include <sys/stat.h>
                     20:
                     21: #include <errno.h>
                     22: #include <dirent.h>
                     23: #include <fcntl.h>
1.2     ! bcook      24: #include <limits.h>
1.1       jsing      25: #include <stdio.h>
                     26: #include <string.h>
                     27: #include <unistd.h>
                     28:
                     29: #include <openssl/bio.h>
                     30: #include <openssl/evp.h>
                     31: #include <openssl/pem.h>
                     32: #include <openssl/x509.h>
                     33:
                     34: #include "apps.h"
                     35:
                     36: static struct {
                     37:        int dryrun;
                     38:        int verbose;
                     39: } certhash_config;
                     40:
                     41: struct option certhash_options[] = {
                     42:        {
                     43:                .name = "n",
                     44:                .desc = "Perform a dry-run - do not make any changes",
                     45:                .type = OPTION_FLAG,
                     46:                .opt.flag = &certhash_config.dryrun,
                     47:        },
                     48:        {
                     49:                .name = "v",
                     50:                .desc = "Verbose",
                     51:                .type = OPTION_FLAG,
                     52:                .opt.flag = &certhash_config.verbose,
                     53:        },
                     54:        { NULL },
                     55: };
                     56:
                     57: struct hashinfo {
                     58:        char *filename;
                     59:        char *target;
                     60:        unsigned long hash;
                     61:        unsigned int index;
                     62:        unsigned char fingerprint[EVP_MAX_MD_SIZE];
                     63:        int is_crl;
                     64:        int is_dup;
                     65:        int exists;
                     66:        int changed;
                     67:        struct hashinfo *reference;
                     68:        struct hashinfo *next;
                     69: };
                     70:
                     71: static struct hashinfo *
                     72: hashinfo(const char *filename, unsigned long hash, unsigned char *fingerprint)
                     73: {
                     74:        struct hashinfo *hi;
                     75:
                     76:        if ((hi = calloc(1, sizeof(*hi))) == NULL)
                     77:                return (NULL);
                     78:        if (filename != NULL) {
                     79:                if ((hi->filename = strdup(filename)) == NULL) {
                     80:                        free(hi);
                     81:                        return (NULL);
                     82:                }
                     83:        }
                     84:        hi->hash = hash;
                     85:        if (fingerprint != NULL)
                     86:                memcpy(hi->fingerprint, fingerprint, sizeof(hi->fingerprint));
                     87:
                     88:        return (hi);
                     89: }
                     90:
                     91: static void
                     92: hashinfo_free(struct hashinfo *hi)
                     93: {
                     94:        free(hi->filename);
                     95:        free(hi->target);
                     96:        free(hi);
                     97: }
                     98:
                     99: #ifdef DEBUG
                    100: static void
                    101: hashinfo_print(struct hashinfo *hi)
                    102: {
                    103:        int i;
                    104:
                    105:        printf("hashinfo %s %08lx %u %i\n", hi->filename, hi->hash,
                    106:            hi->index, hi->is_crl);
                    107:        for (i = 0; i < (int)EVP_MAX_MD_SIZE; i++) {
                    108:                printf("%02X%c", hi->fingerprint[i],
                    109:                    (i + 1 == (int)EVP_MAX_MD_SIZE) ? '\n' : ':');
                    110:        }
                    111: }
                    112: #endif
                    113:
                    114: static int
                    115: hashinfo_compare(const void *a, const void *b)
                    116: {
                    117:        struct hashinfo *hia = *(struct hashinfo **)a;
                    118:        struct hashinfo *hib = *(struct hashinfo **)b;
                    119:        int rv;
                    120:
                    121:        rv = hia->hash - hib->hash;
                    122:        if (rv != 0)
                    123:                return (rv);
                    124:        rv = bcmp(hia->fingerprint, hib->fingerprint, sizeof(hia->fingerprint));
                    125:        if (rv != 0)
                    126:                return (rv);
                    127:        return strcmp(hia->filename, hib->filename);
                    128: }
                    129:
                    130: static struct hashinfo *
                    131: hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
                    132: {
                    133:        struct hashinfo *hi = head;
                    134:
                    135:        if (hi == NULL)
                    136:                return (entry);
                    137:        while (hi->next != NULL)
                    138:                hi = hi->next;
                    139:        hi->next = entry;
                    140:
                    141:        return (head);
                    142: }
                    143:
                    144: static void
                    145: hashinfo_chain_free(struct hashinfo *hi)
                    146: {
                    147:        struct hashinfo *next;
                    148:
                    149:        while (hi != NULL) {
                    150:                next = hi->next;
                    151:                hashinfo_free(hi);
                    152:                hi = next;
                    153:        }
                    154: }
                    155:
                    156: static size_t
                    157: hashinfo_chain_length(struct hashinfo *hi)
                    158: {
                    159:        int len = 0;
                    160:
                    161:        while (hi != NULL) {
                    162:                len++;
                    163:                hi = hi->next;
                    164:        }
                    165:        return (len);
                    166: }
                    167:
                    168: static int
                    169: hashinfo_chain_sort(struct hashinfo **head)
                    170: {
                    171:        struct hashinfo **list, *entry;
                    172:        size_t len;
                    173:        int i;
                    174:
                    175:        if (*head == NULL)
                    176:                return (0);
                    177:
                    178:        len = hashinfo_chain_length(*head);
                    179:        if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
                    180:                return (-1);
                    181:
                    182:        for (entry = *head, i = 0; entry != NULL; entry = entry->next, i++)
                    183:                list[i] = entry;
                    184:        qsort(list, len, sizeof(struct hashinfo *), hashinfo_compare);
                    185:
                    186:        *head = entry = list[0];
                    187:        for (i = 1; i < len; i++) {
                    188:                entry->next = list[i];
                    189:                entry = list[i];
                    190:        }
                    191:        entry->next = NULL;
                    192:
                    193:        return (0);
                    194: }
                    195:
                    196: static char *
                    197: hashinfo_linkname(struct hashinfo *hi)
                    198: {
                    199:        char *filename;
                    200:
                    201:        if (asprintf(&filename, "%08lx.%s%u", hi->hash,
                    202:            (hi->is_crl ? "r" : ""), hi->index) == -1)
                    203:                return (NULL);
                    204:
                    205:        return (filename);
                    206: }
                    207:
                    208: static int
                    209: filename_is_hash(const char *filename)
                    210: {
                    211:        const char *p = filename;
                    212:
                    213:        while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
                    214:                p++;
                    215:        if (*p++ != '.')
                    216:                return (0);
                    217:        if (*p == 'r')          /* CRL format. */
                    218:                p++;
                    219:        while (*p >= '0' && *p <= '9')
                    220:                p++;
                    221:        if (*p != '\0')
                    222:                return (0);
                    223:
                    224:        return (1);
                    225: }
                    226:
                    227: static int
                    228: filename_is_pem(const char *filename)
                    229: {
                    230:        const char *q, *p = filename;
                    231:
                    232:        if ((q = strchr(p, '\0')) == NULL)
                    233:                return (0);
                    234:        if ((q - p) < 4)
                    235:                return (0);
                    236:        if (strncmp((q - 4), ".pem", 4) != 0)
                    237:                return (0);
                    238:
                    239:        return (1);
                    240: }
                    241:
                    242: static struct hashinfo *
                    243: hashinfo_from_linkname(const char *linkname, const char *target)
                    244: {
                    245:        struct hashinfo *hi = NULL;
                    246:        const char *errstr;
                    247:        char *l, *p, *ep;
                    248:        long long val;
                    249:
                    250:        if ((l = strdup(linkname)) == NULL)
                    251:                goto err;
                    252:        if ((p = strchr(l, '.')) == NULL)
                    253:                goto err;
                    254:        *p++ = '\0';
                    255:
                    256:        if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
                    257:                goto err;
                    258:        if ((hi->target = strdup(target)) == NULL)
                    259:                goto err;
                    260:
                    261:        errno = 0;
                    262:        val = strtoll(l, &ep, 16);
                    263:        if (l[0] == '\0' || *ep != '\0')
                    264:                goto err;
                    265:        if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
                    266:                goto err;
                    267:        if (val < 0 || val > ULONG_MAX)
                    268:                goto err;
                    269:        hi->hash = (unsigned long)val;
                    270:
                    271:        if (*p == 'r') {
                    272:                hi->is_crl = 1;
                    273:                p++;
                    274:        }
                    275:
                    276:        val = strtonum(p, 0, 0xffffffff, &errstr);
                    277:        if (errstr != NULL)
                    278:                goto err;
                    279:
                    280:        hi->index = (unsigned int)val;
                    281:
                    282:        goto done;
                    283:
                    284: err:
                    285:        hashinfo_free(hi);
                    286:        hi = NULL;
                    287:
                    288: done:
                    289:        free(l);
                    290:
                    291:        return (hi);
                    292: }
                    293:
                    294: static struct hashinfo *
                    295: certhash_cert(BIO *bio, const char *filename)
                    296: {
                    297:        unsigned char fingerprint[EVP_MAX_MD_SIZE];
                    298:        struct hashinfo *hi = NULL;
                    299:        const EVP_MD *digest;
                    300:        X509 *cert = NULL;
                    301:        unsigned long hash;
                    302:        unsigned int len;
                    303:
                    304:        if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
                    305:                goto err;
                    306:
                    307:        hash = X509_subject_name_hash(cert);
                    308:
                    309:        digest = EVP_sha256();
                    310:        if (X509_digest(cert, digest, fingerprint, &len) != 1) {
                    311:                fprintf(stderr, "out of memory\n");
                    312:                goto err;
                    313:        }
                    314:
                    315:        hi = hashinfo(filename, hash, fingerprint);
                    316:
                    317: err:
                    318:        X509_free(cert);
                    319:
                    320:        return (hi);
                    321: }
                    322:
                    323: static struct hashinfo *
                    324: certhash_crl(BIO *bio, const char *filename)
                    325: {
                    326:        unsigned char fingerprint[EVP_MAX_MD_SIZE];
                    327:        struct hashinfo *hi = NULL;
                    328:        const EVP_MD *digest;
                    329:        X509_CRL *crl = NULL;
                    330:        unsigned long hash;
                    331:        unsigned int len;
                    332:
                    333:        if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
                    334:                return (NULL);
                    335:
                    336:        hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
                    337:
                    338:        digest = EVP_sha256();
                    339:        if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
                    340:                fprintf(stderr, "out of memory\n");
                    341:                goto err;
                    342:        }
                    343:
                    344:        hi = hashinfo(filename, hash, fingerprint);
                    345:
                    346: err:
                    347:        X509_CRL_free(crl);
                    348:
                    349:        return (hi);
                    350: }
                    351:
                    352: static int
                    353: certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
                    354: {
                    355:        struct hashinfo *link = NULL;
                    356:
                    357:        if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
                    358:                goto err;
                    359:
                    360: printf("hi->is_crl = %i\n", hi->is_crl);
                    361:        if ((link->filename = hashinfo_linkname(hi)) == NULL)
                    362:                goto err;
                    363: printf("filename = %s\n", link->filename);
                    364:
                    365:        link->reference = hi;
                    366:        link->changed = 1;
                    367:        *links = hashinfo_chain(*links, link);
                    368:        hi->reference = link;
                    369:
                    370:        return (0);
                    371:
                    372: err:
                    373:        hashinfo_free(link);
                    374:        return (-1);
                    375: }
                    376:
                    377: static void
                    378: certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
                    379: {
                    380:        struct hashinfo *link;
                    381:
                    382:        for (link = links; link != NULL; link = link->next) {
                    383:                if (link->is_crl == hi->is_crl &&
                    384:                    link->hash == hi->hash &&
                    385:                    link->index == hi->index &&
                    386:                    link->reference == NULL) {
                    387:                        link->reference = hi;
                    388:                        if (link->target == NULL ||
                    389:                            strcmp(link->target, hi->filename) != 0)
                    390:                                link->changed = 1;
                    391:                        hi->reference = link;
                    392:                        break;
                    393:                }
                    394:        }
                    395: }
                    396:
                    397: static void
                    398: certhash_index(struct hashinfo *head, const char *name)
                    399: {
                    400:        struct hashinfo *last, *entry;
                    401:        int index = 0;
                    402:
                    403:        last = NULL;
                    404:        for (entry = head; entry != NULL; entry = entry->next) {
                    405:                if (last != NULL) {
                    406:                        if (entry->hash == last->hash) {
                    407:                                if (bcmp(entry->fingerprint, last->fingerprint,
                    408:                                    sizeof(entry->fingerprint)) == 0) {
                    409:                                        fprintf(stderr, "WARNING: duplicate %s "
                    410:                                            "in %s (using %s), ignoring...\n",
                    411:                                            name, entry->filename,
                    412:                                            last->filename);
                    413:                                        entry->is_dup = 1;
                    414:                                        continue;
                    415:                                }
                    416:                                index++;
                    417:                        } else {
                    418:                                index = 0;
                    419:                        }
                    420:                }
                    421:                entry->index = index;
                    422:                last = entry;
                    423:        }
                    424: }
                    425:
                    426: static int
                    427: certhash_merge(struct hashinfo **links, struct hashinfo **certs,
                    428:     struct hashinfo **crls)
                    429: {
                    430:        struct hashinfo *cert, *crl;
                    431:
                    432:        /* Pass 1 - sort and index entries. */
                    433:        if (hashinfo_chain_sort(certs) == -1)
                    434:                return (-1);
                    435:        if (hashinfo_chain_sort(crls) == -1)
                    436:                return (-1);
                    437:        certhash_index(*certs, "certificate");
                    438:        certhash_index(*crls, "CRL");
                    439:
                    440:        /* Pass 2 - map to existing links. */
                    441:        for (cert = *certs; cert != NULL; cert = cert->next) {
                    442:                if (cert->is_dup == 1)
                    443:                        continue;
                    444:                certhash_findlink(*links, cert);
                    445:        }
                    446:        for (crl = *crls; crl != NULL; crl = crl->next) {
                    447:                if (crl->is_dup == 1)
                    448:                        continue;
                    449:                certhash_findlink(*links, crl);
                    450:        }
                    451:
                    452:        /* Pass 3 - determine missing links. */
                    453:        for (cert = *certs; cert != NULL; cert = cert->next) {
                    454:                if (cert->is_dup == 1 || cert->reference != NULL)
                    455:                        continue;
                    456:                if (certhash_addlink(links, cert) == -1)
                    457:                        return (-1);
                    458:        }
                    459:        for (crl = *crls; crl != NULL; crl = crl->next) {
                    460:                if (crl->is_dup == 1 || crl->reference != NULL)
                    461:                        continue;
                    462:                if (certhash_addlink(links, crl) == -1)
                    463:                        return (-1);
                    464:        }
                    465:
                    466:        return (0);
                    467: }
                    468:
                    469: static int
                    470: certhash_link(int dfd, struct dirent *dep, struct hashinfo **links)
                    471: {
                    472:        struct hashinfo *hi = NULL;
                    473:        char target[MAXPATHLEN];
                    474:        struct stat sb;
                    475:        int n;
                    476:
                    477:        if (fstatat(dfd, dep->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
                    478:                fprintf(stderr, "failed to stat %s\n", dep->d_name);
                    479:                return (-1);
                    480:        }
                    481:        if (!S_ISLNK(sb.st_mode))
                    482:                return (0);
                    483:
                    484:        n = readlinkat(dfd, dep->d_name, target, sizeof(target));
                    485:        if (n == -1) {
                    486:                fprintf(stderr, "failed to readlink %s\n", dep->d_name);
                    487:                return (-1);
                    488:        }
                    489:        target[n] = '\0';
                    490:
                    491:        hi = hashinfo_from_linkname(dep->d_name, target);
                    492:        if (hi == NULL) {
                    493:                fprintf(stderr, "failed to get hash info %s\n", dep->d_name);
                    494:                return (-1);
                    495:        }
                    496:        hi->exists = 1;
                    497:        *links = hashinfo_chain(*links, hi);
                    498:
                    499:        return (0);
                    500: }
                    501:
                    502: static int
                    503: certhash_file(int dfd, struct dirent *dep, struct hashinfo **certs,
                    504:     struct hashinfo **crls)
                    505: {
                    506:        struct hashinfo *hi = NULL;
                    507:        int has_cert, has_crl;
                    508:        int ffd, ret = -1;
                    509:        BIO *bio = NULL;
                    510:        FILE *f;
                    511:
                    512:        has_cert = has_crl = 0;
                    513:
                    514:        if ((ffd = openat(dfd, dep->d_name, O_RDONLY)) == -1) {
                    515:                fprintf(stderr, "failed to open %s\n", dep->d_name);
                    516:                goto err;
                    517:        }
                    518:        if ((f = fdopen(ffd, "r")) == NULL) {
                    519:                fprintf(stderr, "failed to fdopen %s\n", dep->d_name);
                    520:                goto err;
                    521:        }
                    522:        if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
                    523:                fprintf(stderr, "failed to create bio\n");
                    524:                goto err;
                    525:        }
                    526:
                    527:        if ((hi = certhash_cert(bio, dep->d_name)) != NULL) {
                    528:                has_cert = 1;
                    529:                *certs = hashinfo_chain(*certs, hi);
                    530:        }
                    531:
                    532:        if (BIO_reset(bio) != 0) {
                    533:                fprintf(stderr, "BIO_reset failed\n");
                    534:                goto err;
                    535:        }
                    536:
                    537:        if ((hi = certhash_crl(bio, dep->d_name)) != NULL) {
                    538:                has_crl = hi->is_crl = 1;
                    539:                *crls = hashinfo_chain(*crls, hi);
                    540:        }
                    541:
                    542:        if (!has_cert && !has_crl)
                    543:                fprintf(stderr, "PEM file %s does not contain a certificate "
                    544:                    "or CRL, ignoring...\n", dep->d_name);
                    545:
                    546:        ret = 0;
                    547:
                    548: err:
                    549:        BIO_free(bio);
                    550:        if (ffd != -1)
                    551:                close(ffd);
                    552:
                    553:        return (ret);
                    554: }
                    555:
                    556: static int
                    557: certhash_directory(const char *path)
                    558: {
                    559:        struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
                    560:        int dfd = -1, ret = 0;
                    561:        struct dirent *dep;
                    562:        DIR *dip = NULL;
                    563:
                    564:        if ((dfd = open(path, O_DIRECTORY)) == -1) {
                    565:                fprintf(stderr, "failed to open directory %s\n", path);
                    566:                goto err;
                    567:        }
                    568:        if ((dip = fdopendir(dfd)) == NULL) {
                    569:                fprintf(stderr, "failed to open directory %s\n", path);
                    570:                goto err;
                    571:        }
                    572:
                    573:        if (certhash_config.verbose)
                    574:                fprintf(stdout, "scanning directory %s\n", path);
                    575:
                    576:        /* Create lists of existing hash links, certs and CRLs. */
                    577:        while ((dep = readdir(dip)) != NULL) {
                    578:                if (filename_is_hash(dep->d_name)) {
                    579:                        if (certhash_link(dfd, dep, &links) == -1)
                    580:                                goto err;
                    581:                }
                    582:                if (filename_is_pem(dep->d_name)) {
                    583:                        if (certhash_file(dfd, dep, &certs, &crls) == -1)
                    584:                                goto err;
                    585:                }
                    586:        }
                    587:
                    588:        if (certhash_merge(&links, &certs, &crls) == -1) {
                    589:                fprintf(stderr, "certhash merge failed\n");
                    590:                goto err;
                    591:        }
                    592:
                    593:        /* Remove spurious links. */
                    594:        for (link = links; link != NULL; link = link->next) {
                    595:                if (link->exists == 0 ||
                    596:                    (link->reference != NULL && link->changed == 0))
                    597:                        continue;
                    598:                if (certhash_config.verbose)
                    599:                        fprintf(stdout, "%s link %s -> %s\n",
                    600:                            (certhash_config.dryrun ? "would remove" :
                    601:                                "removing"), link->filename, link->target);
                    602:                if (certhash_config.dryrun)
                    603:                        continue;
                    604:                if (unlinkat(dfd, link->filename, 0) == -1) {
                    605:                        fprintf(stderr, "failed to remove link %s\n",
                    606:                            link->filename);
                    607:                        goto err;
                    608:                }
                    609:        }
                    610:
                    611:        /* Create missing links. */
                    612:        for (link = links; link != NULL; link = link->next) {
                    613:                if (link->exists == 1 && link->changed == 0)
                    614:                        continue;
                    615:                if (certhash_config.verbose)
                    616:                        fprintf(stdout, "%s link %s -> %s\n",
                    617:                            (certhash_config.dryrun ? "would create" :
                    618:                                "creating"), link->filename,
                    619:                            link->reference->filename);
                    620:                if (certhash_config.dryrun)
                    621:                        continue;
                    622:                if (symlinkat(link->reference->filename, dfd,
                    623:                    link->filename) == -1) {
                    624:                        fprintf(stderr, "failed to create link %s -> %s\n",
                    625:                            link->filename, link->reference->filename);
                    626:                        goto err;
                    627:                }
                    628:        }
                    629:
                    630:        goto done;
                    631:
                    632: err:
                    633:        ret = 1;
                    634:
                    635: done:
                    636:        hashinfo_chain_free(certs);
                    637:        hashinfo_chain_free(crls);
                    638:        hashinfo_chain_free(links);
                    639:
                    640:        if (dip != NULL)
                    641:                closedir(dip);
                    642:        else if (dfd != -1)
                    643:                close(dfd);
                    644:
                    645:        return (ret);
                    646: }
                    647:
                    648: static void
                    649: certhash_usage(void)
                    650: {
                    651:        fprintf(stderr, "usage: certhash [-nv] dir ...\n");
                    652:        options_usage(certhash_options);
                    653: }
                    654:
                    655: int certhash_main(int argc, char **argv);
                    656:
                    657: int
                    658: certhash_main(int argc, char **argv)
                    659: {
                    660:        int argsused;
                    661:        int i, ret = 0;
                    662:
                    663:        memset(&certhash_config, 0, sizeof(certhash_config));
                    664:
                    665:        if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
                    666:                 certhash_usage();
                    667:                 return (1);
                    668:         }
                    669:
                    670:        for (i = argsused; i < argc; i++)
                    671:                ret |= certhash_directory(argv[i]);
                    672:
                    673:        return (ret);
                    674: }