On Wed, Jan 19, 2022 at 11:06:06AM +0100, Claudio Jeker wrote:
> 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.

This generally looks good. I have a few nits and questions inline.

> 
> -- 
> :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);

We have so many of these file extension checks now. I wonder if it would
be worth adding a helper function that does this and returns the correct
RTYPE_* (perhaps renaming the unused RTYPE_EOF into RTYPE_INVALID).
This might simplify/streamline a few things.

Obviously not part of this diff. I could take a stab at this if you're
up for it.

> +
> +             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;

The initialization of f is not really needed.

> +     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://");
> +     

There's a tab on this otherwise blank line.

> +     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;

The initialization of f is not really needed.

> +     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);

I'm not super keen on recursion in such complicated contexts, but I
don't readily see a way to do this iteratively. Need to ponder this a
bit more.

> +             return;
> +     }
> +
> +     /* TA found play back the stack and add all certs */
> +     do {
> +             if (parser_cert_validate(nfile, cert) == NULL)
> +                     break;

I think this also needs a cert_free(cert).

> +             free(nfile);
> +             cert = stack[stacksz - 1];
> +             nfile = filestack[stacksz - 1];

I'm probably missing something, but I don't see why we don't hit this
in the last iteration where stacksz == 0 (which would be bad).

> +     } while (stacksz-- > 0);

It's probably related to my above confusion: We don't need to free cert
if parser_cert_validate() fails but do need to free it if we exit the
while loop because 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