March 2021 update: This work has been superseded by 'RPKI Signed Checklists' https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-rpki-rsc Original: Date: December 26th, 2020 Author: Job Snijders Quick (entirely untested!) implementation of RTA in rpki-client Some implementation notes: 0) Section 8 suggests RTAs can also be used in a 'standalone' mode, however, this patch only supports acquiring RTAs 'through publication'. perhaps in the future rpki-client can have a '-r' command line option to specify a file on the local file system which is not listed on any manifest. 1) rpki-client expects RTA objects to have .rta as file name extension 3) Canonicalization is not verified, RTA contents are considered a single octect string. Canonicalization is left to the signer and the ultimate content consumer. 4) Whether multiple signers are correctly handled needs to be tested 5) RTA eContent is not decoded Index: Makefile =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v retrieving revision 1.15 diff -u -p -r1.15 Makefile --- Makefile 9 Dec 2020 11:29:04 -0000 1.15 +++ Makefile 26 Dec 2020 18:06:35 -0000 @@ -3,7 +3,7 @@ PROG= rpki-client SRCS= as.c cert.c cms.c crl.c gbr.c io.c ip.c log.c main.c mft.c output.c \ output-bgpd.c output-bird.c output-csv.c output-json.c \ - roa.c rsync.c tal.c validate.c x509.c + roa.c rsync.c rta.c tal.c validate.c x509.c MAN= rpki-client.8 LDADD+= -lcrypto Index: extern.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v retrieving revision 1.36 diff -u -p -r1.36 extern.h --- extern.h 9 Dec 2020 11:29:04 -0000 1.36 +++ extern.h 26 Dec 2020 18:06:35 -0000 @@ -194,6 +194,16 @@ struct gbr { }; /* + * A single Resource Tagged Attestation + */ +struct rta { + char *rtaf; + char *ski; /* SKI */ + char *aki; /* AKI */ +}; + + +/* * A single VRP element (including ASID) */ struct vrp { @@ -256,6 +266,7 @@ enum rtype { RTYPE_CER, RTYPE_CRL, RTYPE_GBR, + RTYPE_RTA, }; /* @@ -275,6 +286,7 @@ struct stats { size_t repos; /* repositories */ size_t crls; /* revocation lists */ size_t gbrs; /* ghostbuster records */ + size_t rtas; /* RTAs */ size_t vrps; /* total number of vrps */ size_t uniqs; /* number of unique vrps */ size_t del_files; /* number of files removed in cleanup */ @@ -316,6 +328,9 @@ void roa_insert_vrps(struct vrp_tree * void gbr_free(struct gbr *); struct gbr *gbr_parse(X509 **, const char *); + +void rta_free(struct rta *); +struct rta *rta_parse(X509 **, const char *); /* crl.c */ X509_CRL *crl_parse(const char *, const unsigned char *); Index: main.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v retrieving revision 1.88 diff -u -p -r1.88 main.c --- main.c 21 Dec 2020 11:35:55 -0000 1.88 +++ main.c 26 Dec 2020 18:06:35 -0000 @@ -519,10 +519,20 @@ queue_add_from_mft_set(int fd, struct en f = &mft->files[i]; sz = strlen(f->file); assert(sz > 4); + if (strcasecmp(f->file + sz - 4, ".rta")) + continue; + queue_add_from_mft(fd, q, mft->file, f, RTYPE_RTA, eid); + } + + for (i = 0; i < mft->filesz; i++) { + f = &mft->files[i]; + sz = strlen(f->file); + assert(sz > 4); if (strcasecmp(f->file + sz - 4, ".crl") == 0 || strcasecmp(f->file + sz - 4, ".cer") == 0 || strcasecmp(f->file + sz - 4, ".roa") == 0 || - strcasecmp(f->file + sz - 4, ".gbr") == 0) + strcasecmp(f->file + sz - 4, ".gbr") == 0 || + strcasecmp(f->file + sz - 4, ".rta") == 0) continue; logx("%s: unsupported file type: %s", mft->file, f->file); } @@ -988,6 +998,50 @@ proc_parser_gbr(struct entity *entp, X50 gbr_free(gbr); } +/* + * Parse a single? multiple? signer Resource Tagged Attestation + */ +static void +proc_parser_rta(struct entity *entp, X509_STORE *store, + X509_STORE_CTX *ctx, struct auth_tree *auths, struct crl_tree *crlt) +{ + struct rta *rta; + X509 *x509; + int c; + struct auth *a; + STACK_OF(X509) *chain; + STACK_OF(X509_CRL) *crls; + + if ((rta = rta_parse(&x509, entp->uri)) == NULL) + return; + + a = valid_ski_aki(entp->uri, auths, rta->ski, rta->aki); + + build_chain(a, &chain); + build_crls(a, crlt, &crls); + + assert(x509 != NULL); + if (!X509_STORE_CTX_init(ctx, store, x509, chain)) + cryptoerrx("X509_STORE_CTX_init"); + X509_STORE_CTX_set_flags(ctx, + X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK); + X509_STORE_CTX_set0_crls(ctx, crls); + + if (X509_verify_cert(ctx) <= 0) { + c = X509_STORE_CTX_get_error(ctx); + if (verbose > 0 || c != X509_V_ERR_UNABLE_TO_GET_CRL) + warnx("%s: %s", entp->uri, + X509_verify_cert_error_string(c)); + } + + X509_STORE_CTX_cleanup(ctx); + sk_X509_free(chain); + sk_X509_CRL_free(crls); + X509_free(x509); + rta_free(rta); +} + + /* use the parent (id) to walk the tree to the root and build a certificate chain from cert->x509 */ static void @@ -1183,6 +1237,9 @@ proc_parser(int fd) case RTYPE_GBR: proc_parser_gbr(entp, store, ctx, &auths, &crlt); break; + case RTYPE_RTA: + proc_parser_rta(entp, store, ctx, &auths, &crlt); + break; default: abort(); } @@ -1293,6 +1350,9 @@ entity_process(int proc, int rsync, stru case RTYPE_GBR: st->gbrs++; break; + case RTYPE_RTA: + st->rtas++; + break; default: abort(); } @@ -1754,6 +1814,7 @@ main(int argc, char *argv[]) stats.mfts, stats.mfts_fail, stats.mfts_stale); logx("Certificate revocation lists: %zu", stats.crls); logx("Ghostbuster records: %zu", stats.gbrs); + logx("Resource Tagged Attestations: %zu", stats.rtas); logx("Repositories: %zu", stats.repos); logx("Files removed: %zu", stats.del_files); logx("VRP Entries: %zu (%zu unique)", stats.vrps, stats.uniqs); Index: rpki-client.8 =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/rpki-client.8,v retrieving revision 1.33 diff -u -p -r1.33 rpki-client.8 --- rpki-client.8 9 Dec 2020 11:33:10 -0000 1.33 +++ rpki-client.8 26 Dec 2020 18:06:35 -0000 @@ -208,6 +208,8 @@ Signed Object Template for the Resource The Resource Public Key Infrastructure (RPKI) Ghostbusters Record. .It RFC 7730 Resource Public Key Infrastructure (RPKI) Trust Anchor Locator. +.It draft-michaelson-rpki-rta-02 +A profile for Resource Tagged Attestations (RTAs). .El .\" .Sh HISTORY .Sh AUTHORS Index: rta.c =================================================================== RCS file: rta.c diff -N rta.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ rta.c 26 Dec 2020 18:06:35 -0000 @@ -0,0 +1,90 @@ +/* $OpenBSD: rta.c,v 1.1 2020/12/09 11:29:04 claudio Exp $ */ +/* + * Copyright (c) 2020 Job Snijders + * Copyright (c) 2020 Claudio Jeker + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +/* + * Parse results and data of the rta file. + */ +struct parse { + const char *fn; /* rta file name */ + struct rta *res; /* results */ +}; + +/* + * Parse a draft-michaelson-rpki-rta-02 file and signed by the + * certificate "cacert" (the latter is optional and may be passed as + * NULL to disable). Returns the payload or NULL if the document was + * malformed. + */ +struct rta * +rta_parse(X509 **x509, const char *fn) +{ + struct parse p; + size_t cmsz; + unsigned char *cms; + + memset(&p, 0, sizeof(struct parse)); + p.fn = fn; + + /* OID from section 4 draft-michaelson-rpki-rta-02 */ + + cms = cms_parse_validate(x509, fn, + "1.2.840.113549.1.9.16.1.36", NULL, &cmsz); + if (cms == NULL) + return NULL; + + if ((p.res = calloc(1, sizeof(*p.res))) == NULL) + err(1, NULL); + if ((p.res->rtaf = strndup(cms, cmsz)) == NULL) + err(1, NULL); + if (!x509_get_ski_aki(*x509, fn, &p.res->ski, &p.res->aki)) { + rta_free(p.res); + X509_free(*x509); + *x509 = NULL; + return NULL; + } + + free(cms); + return p.res; +} + +/* + * Free an RTA pointer. + * Safe to call with NULL. + */ +void +rta_free(struct rta *p) +{ + + if (p == NULL) + return; + free(p->aki); + free(p->ski); + free(p->rtaf); + free(p); +}