The idea is that rpki-client -f file will show a human readable output for
file. It will also verify that file is valid (or show an error if not).

This implements this as a first version. Especially the output needs
improvement but parsing and validation works.
For validation rpki-client needs to follow up the chain of trust by
traversing the Authority Information Access and the X509v3 CRL
Distribution Points fields of the certificate and follow them up until a
TA is found. Once a TA is found the stack can be walked back validating
each intermediate cert and finally the file in question. While doing that
all CRLs referenced by any cert are loaded into rpki-client as well.
Now the TA are loaded at the start by loading the TAL files and then using
them to validate the TAs.

Since all of this is done in offline mode only the valid/ and ta/
directory are used for these lookups. This also simplifies the lookup.
Just chop of rsync:// from the URI and prepend valid/ to get the path of
the file. Also do not run the cleanup routines at the end because that
would remove all of the repo.

-- 
:wq Claudio

Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
retrieving revision 1.108
diff -u -p -r1.108 extern.h
--- extern.h    18 Jan 2022 16:36:49 -0000      1.108
+++ extern.h    18 Jan 2022 16:52:29 -0000
@@ -294,6 +294,7 @@ enum rtype {
        RTYPE_CRL,
        RTYPE_GBR,
        RTYPE_REPO,
+       RTYPE_FILE,
 };
 
 enum http_result {
@@ -393,6 +394,7 @@ struct msgbuf;
 
 /* global variables */
 extern int verbose;
+extern int filemode;
 extern const char *tals[];
 extern const char *taldescs[];
 extern unsigned int talrepocnt[];
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
retrieving revision 1.176
diff -u -p -r1.176 main.c
--- main.c      14 Jan 2022 15:00:23 -0000      1.176
+++ main.c      18 Jan 2022 08:50:21 -0000
@@ -67,6 +67,7 @@ const char    *bird_tablename = "ROAS";
 
 int    verbose;
 int    noop;
+int    filemode;
 int    rrdpon = 1;
 int    repo_timeout;
 
@@ -385,25 +386,23 @@ queue_add_from_mft_set(const struct mft 
 }
 
 /*
- * Add a local TAL file (RFC 7730) to the queue of files to fetch.
+ * Add a local file to the queue of files to fetch.
  */
 static void
-queue_add_tal(const char *file, int talid)
+queue_add_file(const char *file, enum rtype type, int talid)
 {
        unsigned char   *buf;
        char            *nfile;
        size_t           len;
 
        buf = load_file(file, &len);
-       if (buf == NULL) {
-               warn("%s", file);
-               return;
-       }
+       if (buf == NULL)
+               err(1, "%s", file);
 
        if ((nfile = strdup(file)) == NULL)
                err(1, NULL);
        /* Not in a repository, so directly add to queue. */
-       entityq_add(NULL, nfile, RTYPE_TAL, NULL, buf, len, talid);
+       entityq_add(NULL, nfile, type, NULL, buf, len, talid);
 }
 
 /*
@@ -508,11 +507,13 @@ entity_process(struct ibuf *b, struct st
        io_read_buf(b, &type, sizeof(type));
        io_read_str(b, &file);
 
+       /* in filemode messages can be ignored, only the accounting matters */
+       if (filemode)
+               goto done;
+
        if (filepath_add(&fpt, file) == 0) {
                warnx("%s: File already visited", file);
-               free(file);
-               entity_queue--;
-               return;
+               goto done;
        }
 
        switch (type) {
@@ -580,10 +581,13 @@ entity_process(struct ibuf *b, struct st
        case RTYPE_GBR:
                st->gbrs++;
                break;
+       case RTYPE_FILE:
+               break;
        default:
                errx(1, "unknown entity type %d", type);
        }
 
+done:
        free(file);
        entity_queue--;
 }
@@ -771,6 +775,7 @@ main(int argc, char *argv[])
                        break;
                case 'f':
                        file = optarg;
+                       filemode = 1;
                        noop = 1;
                        break;
                case 'j':
@@ -831,20 +836,35 @@ main(int argc, char *argv[])
                warnx("cache directory required");
                goto usage;
        }
-       if (outputdir == NULL) {
+       if (file != NULL) {
+               size_t sz;
+
+               sz = strlen(file);
+               if (strcasecmp(file + sz - 4, ".tal") != 0 &&
+                   strcasecmp(file + sz - 4, ".cer") != 0 &&
+                   strcasecmp(file + sz - 4, ".crl") != 0 &&
+                   strcasecmp(file + sz - 4, ".mft") != 0 &&
+                   strcasecmp(file + sz - 4, ".roa") != 0 &&
+                   strcasecmp(file + sz - 4, ".gbr") != 0)
+                       errx(1, "unsupported or invalid file: %s", file);
+
+               outputdir = NULL;
+       } else if (outputdir == NULL) {
                warnx("output directory required");
                goto usage;
        }
 
        if ((cachefd = open(cachedir, O_RDONLY | O_DIRECTORY)) == -1)
                err(1, "cache directory %s", cachedir);
-       if ((outdirfd = open(outputdir, O_RDONLY | O_DIRECTORY)) == -1)
-               err(1, "output directory %s", outputdir);
+       if (outputdir != NULL) {
+               if ((outdirfd = open(outputdir, O_RDONLY | O_DIRECTORY)) == -1)
+                       err(1, "output directory %s", outputdir);
+               if (outformats == 0)
+                       outformats = FORMAT_OPENBGPD;
+       }
 
        check_fs_size(cachefd, cachedir);
 
-       if (outformats == 0)
-               outformats = FORMAT_OPENBGPD;
 
        if (talsz == 0)
                talsz = tal_load_default();
@@ -1047,7 +1067,10 @@ main(int argc, char *argv[])
         */
 
        for (i = 0; i < talsz; i++)
-               queue_add_tal(tals[i], i);
+               queue_add_file(tals[i], RTYPE_TAL, i);
+
+       if (file != NULL)
+               queue_add_file(file, RTYPE_FILE, 0);
 
        /* change working directory to the cache directory */
        if (fchdir(cachefd) == -1)
@@ -1209,6 +1232,10 @@ main(int argc, char *argv[])
        /* processing did not finish because of error */
        if (entity_queue != 0)
                errx(1, "not all files processed, giving up");
+
+       /* if processing in filemode the process is done, no cleanup */
+       if (filemode)
+               return rc;
 
        logx("all files parsed: generating output");
 
Index: parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
retrieving revision 1.44
diff -u -p -r1.44 parser.c
--- parser.c    18 Jan 2022 18:19:47 -0000      1.44
+++ parser.c    19 Jan 2022 09:18:21 -0000
@@ -383,24 +383,13 @@ proc_parser_mft(char *file, const unsign
 }
 
 /*
- * Certificates are from manifests (has a digest and is signed with
- * another certificate) Parse the certificate, make sure its
- * signatures are valid (with CRLs), then validate the RPKI content.
- * This returns a certificate (which must not be freed) or NULL on
- * parse failure.
+ *
  */
 static struct cert *
-proc_parser_cert(char *file, const unsigned char *der, size_t len)
+parser_cert_validate(char *file, struct cert *cert)
 {
-       struct cert             *cert;
-       struct auth             *a;
-       struct crl              *crl;
-
-       /* Extract certificate data and X509. */
-
-       cert = cert_parse(file, der, len);
-       if (cert == NULL)
-               return NULL;
+       struct auth     *a;
+       struct crl      *crl;
 
        a = valid_ski_aki(file, &auths, cert->ski, cert->aki);
        crl = get_crl(a);
@@ -428,6 +417,28 @@ proc_parser_cert(char *file, const unsig
 }
 
 /*
+ * Certificates are from manifests (has a digest and is signed with
+ * another certificate) Parse the certificate, make sure its
+ * signatures are valid (with CRLs), then validate the RPKI content.
+ * This returns a certificate (which must not be freed) or NULL on
+ * parse failure.
+ */
+static struct cert *
+proc_parser_cert(char *file, const unsigned char *der, size_t len)
+{
+       struct cert     *cert;
+
+       /* Extract certificate data. */
+
+       cert = cert_parse(file, der, len);
+       if (cert == NULL)
+               return NULL;
+
+       cert = parser_cert_validate(file, cert);
+       return cert;
+}
+
+/*
  * Root certificates come from TALs (has a pkey and is self-signed).
  * Parse the certificate, ensure that it's public key matches the
  * known public key from the TAL, and then validate the RPKI
@@ -446,7 +457,7 @@ proc_parser_root_cert(char *file, const 
        struct cert             *cert;
        X509                    *x509;
 
-       /* Extract certificate data and X509. */
+       /* Extract certificate data. */
 
        cert = ta_parse(file, der, len, pkey, pkeysz);
        if (cert == NULL)
@@ -669,6 +680,9 @@ fail:
        return file;
 }
 
+/*
+ * Process an entity and responing to parent process.
+ */
 static void
 parse_entity(struct entityq *q, struct msgbuf *msgq)
 {
@@ -762,6 +776,294 @@ parse_entity(struct entityq *q, struct m
 }
 
 /*
+ * Use the X509 CRL Distribution Points to locate the CRL needed for
+ * verification.
+ */
+static void
+parse_load_crl(const char *uri)
+{
+       char *nfile, *f = NULL;
+       size_t flen;
+
+       if (uri == NULL)
+               return;
+       if (strncmp(uri, "rsync://", strlen("rsync://")) != 0) {
+               warnx("bad CRL distribution point URI %s", uri);
+               return;
+       }
+       uri += strlen("rsync://");
+       
+       if (asprintf(&nfile, "valid/%s", uri) == -1)
+               err(1, NULL);
+
+       f = load_file(nfile, &flen);
+       if (f == NULL) {
+               warn("parse file %s", nfile);
+               goto done;
+       }
+
+       proc_parser_crl(nfile, f, flen);
+
+done:
+       free(nfile);
+       free(f);
+}
+
+/*
+ * Parse the cert pointed at by the AIA URI. Since the validation stack
+ * is traversed backwards the cert can not be validated until the TA
+ * is found.
+ */
+static void
+parse_load_certchain(const char *uri)
+{
+       static struct cert *stack[MAX_CERT_DEPTH];
+       static char *filestack[MAX_CERT_DEPTH];
+       static size_t stacksz;
+       struct cert *cert = NULL;
+       char *nfile, *f = NULL;
+       size_t flen;
+
+       if (uri == NULL) {
+               warnx("no TA in authority chain");
+               return;
+       }
+
+       if (strncmp(uri, "rsync://", strlen("rsync://")) != 0) {
+               warnx("bad authority information access URI %s", uri);
+               return;
+       }
+       uri += strlen("rsync://");
+
+       if (asprintf(&nfile, "valid/%s", uri) == -1)
+               err(1, NULL);
+
+       f = load_file(nfile, &flen);
+       if (f == NULL) {
+               warn("parse file %s", nfile);
+               goto done;
+       }
+
+       cert = cert_parse(nfile, f, flen);
+       free(f);
+
+       if (cert == NULL)
+               goto done;
+       if (cert->purpose != CERT_PURPOSE_CA) {
+               warnx("AIA reference to bgpsec cert %s", nfile);
+               goto done;
+       }
+       /* try to load the CRL of this cert */
+       parse_load_crl(cert->crl);
+
+       /* Look for the TA which is hopefully in the auths tree */
+       if (auth_find(&auths, cert->aki) == NULL) {
+               if (stacksz >= MAX_CERT_DEPTH) {
+                       warnx("authority chain exceeds max depth of %i",
+                           MAX_CERT_DEPTH);
+                       goto done;
+               }
+               stack[stacksz] = cert;
+               filestack[stacksz] = nfile;
+               stacksz++;
+               /* recurse on next certificate */
+               parse_load_certchain(cert->aia);
+               return;
+       }
+
+       /* TA found play back the stack and add all certs */
+       do {
+               if (parser_cert_validate(nfile, cert) == NULL)
+                       break;
+               free(nfile);
+               cert = stack[stacksz - 1];
+               nfile = filestack[stacksz - 1];
+       } while (stacksz-- > 0);
+       free(nfile);
+       return;
+
+done:
+       cert_free(cert);
+       free(nfile);
+}
+
+static void
+parse_load_ta(struct tal *tal)
+{
+       const char *file;
+       char *nfile, *f;
+       size_t flen;
+
+       /* does not matter which URI, all end with same filename */
+       file = strrchr(tal->uri[0], '/');
+       assert(file);
+
+       if (asprintf(&nfile, "ta/%s%s", tal->descr, file) == -1)
+               err(1, NULL);
+
+       f = load_file(nfile, &flen);
+       if (f == NULL) {
+               warn("parse file %s", nfile);
+               free(nfile);
+               return;
+       }
+
+       /* if TA is valid it was added as a root which is all we need */
+       proc_parser_root_cert(nfile, f, flen, tal->pkey, tal->pkeysz, tal->id);
+       free(nfile);
+       free(f);
+}
+
+/*
+ * Parse file passed with -f option.
+ */
+static void
+proc_parser_file(char *file, unsigned char *buf, size_t len)
+{
+       X509 *x509 = NULL;
+       struct cert *cert = NULL;
+       struct mft *mft = NULL;
+       struct roa *roa = NULL;
+       struct gbr *gbr = NULL;
+       struct tal *tal = NULL;
+       enum rtype type;
+       char *aia = NULL, *aki = NULL, *ski = NULL;
+       size_t sz;
+       unsigned long verify_flags = X509_V_FLAG_CRL_CHECK;
+
+       sz = strlen(file);
+       if (strcasecmp(file + sz - 4, ".tal") == 0)
+               type = RTYPE_TAL;
+       else if (strcasecmp(file + sz - 4, ".cer") == 0)
+               type = RTYPE_CER;
+       else if (strcasecmp(file + sz - 4, ".crl") == 0)
+               type = RTYPE_CRL;
+       else if (strcasecmp(file + sz - 4, ".mft") == 0)
+               type = RTYPE_MFT;
+       else if (strcasecmp(file + sz - 4, ".roa") == 0)
+               type = RTYPE_ROA;
+       else if (strcasecmp(file + sz - 4, ".gbr") == 0)
+               type = RTYPE_GBR;
+       else
+               errx(1, "%s: unsupported file type", file);
+
+       switch (type) {
+       case RTYPE_CER:
+               cert = cert_parse(file, buf, len);
+               if (cert == NULL)
+                       break;
+               cert_print(cert);
+               aia = cert->aia;
+               aki = cert->aki;
+               ski = cert->ski;
+               x509 = cert->x509;
+               if (X509_up_ref(x509) == 0)
+                       errx(1, "%s: X509_up_ref failed", __func__);
+               break;
+       case RTYPE_MFT:
+               mft = mft_parse(&x509, file, buf, len);
+               if (mft == NULL)
+                       break;
+               mft_print(mft);
+               aia = mft->aia;
+               aki = mft->aki;
+               ski = mft->ski;
+               verify_flags = 0;
+               break;
+       case RTYPE_ROA:
+               roa = roa_parse(&x509, file, buf, len);
+               if (roa == NULL)
+                       break;
+               roa_print(roa);
+               aia = roa->aia;
+               aki = roa->aki;
+               ski = roa->ski;
+               break;
+       case RTYPE_GBR:
+               gbr = gbr_parse(&x509, file, buf, len);
+               if (gbr == NULL)
+                       break;
+               gbr_print(gbr);
+               aia = gbr->aia;
+               aki = gbr->aki;
+               ski = gbr->ski;
+               break;
+       case RTYPE_TAL:
+               tal = tal_parse(file, buf, len);
+               if (tal == NULL)
+                       break;
+               tal_print(tal);
+               break;
+       case RTYPE_CRL: /* XXX no printer yet */
+       default:
+               break;
+       }
+
+       if (aia != NULL) {
+               struct auth *a;
+               struct crl *crl;
+               char *c;
+
+               c = x509_get_crl(x509, file);
+               parse_load_crl(c);
+               free(c);
+               parse_load_certchain(aia);
+               a = valid_ski_aki(file, &auths, ski, aki);
+               crl = get_crl(a);
+
+               if (valid_x509(file, x509, a, crl, verify_flags))
+                       printf("Validation: OK\n");
+               else
+                       printf("Validation: Failed\n");
+       }
+
+       X509_free(x509);
+       cert_free(cert);
+       mft_free(mft);
+       roa_free(roa);
+       gbr_free(gbr);
+       tal_free(tal);
+}
+
+/*
+ * Process a file request, in general don't send anything back.
+ */
+static void
+parse_file(struct entityq *q, struct msgbuf *msgq)
+{
+       struct entity   *entp;
+       struct ibuf     *b;
+       struct tal      *tal;
+
+       while ((entp = TAILQ_FIRST(q)) != NULL) {
+               TAILQ_REMOVE(q, entp, entries);
+
+               switch (entp->type) {
+               case RTYPE_FILE:
+                       proc_parser_file(entp->file, entp->data, entp->datasz);
+                       break;
+               case RTYPE_TAL:
+                       if ((tal = tal_parse(entp->file, entp->data,
+                           entp->datasz)) == NULL)
+                               errx(1, "%s: could not parse tal file",
+                                   entp->file);
+                       tal->id = entp->talid;
+                       parse_load_ta(tal);
+                       tal_free(tal);
+                       break;
+               default:
+                       errx(1, "unhandled entity type %d", entp->type);
+               }
+
+               b = io_new_buffer();
+               io_simple_buffer(b, &entp->type, sizeof(entp->type));
+               io_str_buffer(b, entp->file);
+               io_close_buffer(msgq, b);
+               entity_free(entp);
+       }
+}
+
+/*
  * Process responsible for parsing and validating content.
  * All this process does is wait to be told about a file to parse, then
  * it parses it and makes sure that the data being returned is fully
@@ -828,7 +1130,10 @@ proc_parser(int fd)
                        }
                }
 
-               parse_entity(&q, &msgq);
+               if (!filemode)
+                       parse_entity(&q, &msgq);
+               else
+                       parse_file(&q, &msgq);
        }
 
        while ((entp = TAILQ_FIRST(&q)) != NULL) {

Reply via email to