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) { >