Dear all, This changeset adds support for decoding and verifying the cryptographic validity of RPKI Signed Checklists (RSC) files. The draft [1] is in Working Group Last Call, the wire image is considered stable.
A RSC is a Cryptographic Message Syntax (CMS) profile for a general purpose listing of checksums (a 'checklist'), for use with the Resource Public Key Infrastructure (RPKI). The objective is to allow an attestation, in the form of a listing of one or more checksums of arbitrary digital objects (files), to be signed "with resources", and for validation to provide a means to confirm a specific Internet Resource Holder produced the Signed Checklist. The profile is intended to provide for the signing of an arbitrary checksum listing with a specific set of Internet Number Resources. https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-rpki-rsc Example: $ rpki-client -j -f checklist.sig { "file": "checklist.sig", "hash_id": "GcuTYA9a2aXFt19jA8uLKaA7WAnr6pTeg1GkMoo8Pcg=", "ski": "B7:D4:EF:59:EE:7C:0E:60:DA:55:97:E3:71:31:90:92:20:C2:8A:A1", "aki": "38:E1:4F:92:FD:C7:CC:FB:FC:18:23:61:52:3A:E2:7D:69:7E:95:2F", "cert_serial": "0", "aia": "rsync://rpki.ripe.net/repository/DEFAULT/OOFPkv3HzPv8GCNhUjrifWl-lS8.cer", "valid_until": 1653730307, "signed_with_resources": [ { "ip_prefix": "2001:67c:208c::/48" } ], "filenamesandhashes": [ { "filename": "b42_ipv6_loa.png", "hash_digest": "lRbdZL58FyW5/KEXEg5Y6NhCpSBoczmbPd/8kcS2rPA=" }, { "filename": "", "hash_digest": "CuE5RyIAXNkvTGqgJNXWs+LmfWKfEXINlHimM6EXocc=" } ], "validation": "OK" } The example checklist.sig file can be obtained at https://sobornost.net/~job/checklist.sig and validates via the RIPE NCC RPKI Trust Anchor. Feedback? OK? Kind regards, Job Index: usr.sbin/rpki-client/Makefile =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v retrieving revision 1.24 diff -u -p -r1.24 Makefile --- usr.sbin/rpki-client/Makefile 21 Apr 2022 09:53:07 -0000 1.24 +++ usr.sbin/rpki-client/Makefile 8 May 2022 16:00:41 -0000 @@ -5,7 +5,7 @@ SRCS= as.c cert.c cms.c crl.c encoding.c log.c main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \ output-csv.c output-json.c parser.c print.c repo.c roa.c rrdp.c \ rrdp_delta.c rrdp_notification.c rrdp_snapshot.c rrdp_util.c \ - rsync.c tal.c validate.c x509.c + rsc.c rsync.c tal.c validate.c x509.c MAN= rpki-client.8 LDADD+= -lexpat -ltls -lssl -lcrypto -lutil Index: usr.sbin/rpki-client/extern.h =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v retrieving revision 1.132 diff -u -p -r1.132 extern.h --- usr.sbin/rpki-client/extern.h 21 Apr 2022 12:59:03 -0000 1.132 +++ usr.sbin/rpki-client/extern.h 8 May 2022 16:00:41 -0000 @@ -24,6 +24,14 @@ #include <openssl/x509.h> #include <openssl/x509v3.h> +/* + * Enumeration for ASN.1 explicit tags in RSC eContent + */ +enum rsc_resourceblock_tag { + RSRCBLK_TYPE_ASID, + RSRCBLK_TYPE_IPADDRBLK, +}; + enum cert_as_type { CERT_AS_ID, /* single identifier */ CERT_AS_INHERIT, /* inherit from parent */ @@ -164,6 +172,7 @@ enum rtype { RTYPE_ASPA, RTYPE_REPO, RTYPE_FILE, + RTYPE_RSC, }; enum location { @@ -232,6 +241,29 @@ struct roa { time_t expires; /* do not use after */ }; +struct rscfile { + char *filename; /* an optional filename on the checklist */ + unsigned char hash[SHA256_DIGEST_LENGTH]; /* the digest */ +}; + +/* + * A Signed Checklist (RSC) + */ +struct rsc { + int talid; /* RSC covered by what TAL */ + int valid; /* eContent resources covered by EE's 3779? */ + struct cert_ip *ips; /* IP prefixes */ + size_t ipsz; /* number of IP prefixes */ + struct cert_as *as; /* AS resources */ + size_t asz; /* number of AS resources */ + struct rscfile *files; /* FileAndHashes in the RSC */ + size_t filesz; /* number of FileAndHashes */ + char *aia; /* AIA */ + char *aki; /* AKI */ + char *ski; /* SKI */ + time_t expires; /* Not After of the RSC EE */ +}; + /* * A single Ghostbuster record */ @@ -450,6 +482,12 @@ void gbr_free(struct gbr *); struct gbr *gbr_parse(X509 **, const char *, const unsigned char *, size_t); +void rsc_buffer(struct ibuf *, const struct rsc *); +void rsc_free(struct rsc *); +struct rsc *rsc_parse(X509 **, const char *, const unsigned char *, + size_t); +struct rsc *rsc_read(struct ibuf *); + /* crl.c */ struct crl *crl_parse(const char *, const unsigned char *, size_t); struct crl *crl_get(struct crl_tree *, const struct auth *); @@ -470,6 +508,7 @@ int valid_uri(const char *, size_t, co int valid_origin(const char *, const char *); int valid_x509(char *, X509_STORE_CTX *, X509 *, struct auth *, struct crl *, int); +int valid_rsc(const char *, struct auth *, struct rsc *); /* Working with CMS. */ unsigned char *cms_parse_validate(X509 **, const char *, @@ -608,6 +647,7 @@ void crl_print(const struct crl *); void mft_print(const X509 *, const struct mft *); void roa_print(const X509 *, const struct roa *); void gbr_print(const X509 *, const struct gbr *); +void rsc_print(const X509 *, const struct rsc *); /* Output! */ Index: usr.sbin/rpki-client/filemode.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/filemode.c,v retrieving revision 1.5 diff -u -p -r1.5 filemode.c --- usr.sbin/rpki-client/filemode.c 24 Apr 2022 22:26:44 -0000 1.5 +++ usr.sbin/rpki-client/filemode.c 8 May 2022 16:00:41 -0000 @@ -264,6 +264,7 @@ proc_parser_file(char *file, unsigned ch struct roa *roa = NULL; struct gbr *gbr = NULL; struct tal *tal = NULL; + struct rsc *rsc = NULL; char *aia = NULL, *aki = NULL; char filehash[SHA256_DIGEST_LENGTH]; char *hash; @@ -356,6 +357,14 @@ proc_parser_file(char *file, unsigned ch if (tal == NULL) break; tal_print(tal); + break; + case RTYPE_RSC: + rsc = rsc_parse(&x509, file, buf, len); + if (rsc == NULL) + break; + rsc_print(x509, rsc); + aia = rsc->aia; + aki = rsc->aki; break; default: printf("%s: unsupported file type\n", file); Index: usr.sbin/rpki-client/mft.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/mft.c,v retrieving revision 1.60 diff -u -p -r1.60 mft.c --- usr.sbin/rpki-client/mft.c 20 Apr 2022 10:46:20 -0000 1.60 +++ usr.sbin/rpki-client/mft.c 8 May 2022 16:00:42 -0000 @@ -120,6 +120,8 @@ rtype_from_file_extension(const char *fn return RTYPE_GBR; if (strcasecmp(fn + sz - 4, ".asa") == 0) return RTYPE_ASPA; + if (strcasecmp(fn + sz - 4, ".sig") == 0) + return RTYPE_RSC; return RTYPE_INVALID; } Index: usr.sbin/rpki-client/parser.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v retrieving revision 1.73 diff -u -p -r1.73 parser.c --- usr.sbin/rpki-client/parser.c 21 Apr 2022 12:59:03 -0000 1.73 +++ usr.sbin/rpki-client/parser.c 8 May 2022 16:00:42 -0000 @@ -492,6 +492,39 @@ proc_parser_gbr(char *file, const unsign } /* + * Parse a Signed Checklist (RSC) file. + * Returns rsc struct (which must not be freed) or NULL + */ +static struct rsc * +proc_parser_rsc(char *file, const unsigned char *der, size_t len) +{ + struct rsc *rsc = NULL; + struct auth *a; + struct crl *crl; + X509 *x509; + + if ((rsc = rsc_parse(&x509, file, der, len)) == NULL) + return NULL; + + a = valid_ski_aki(file, &auths, rsc->ski, rsc->aki); + crl = crl_get(&crlt, a); + + if (!valid_x509(file, ctx, x509, a, crl, 0)) { + X509_free(x509); + rsc_free(rsc); + return NULL; + } + X509_free(x509); + + rsc->talid = a->cert->talid; + + if (valid_rsc(file, a, rsc)) + rsc->valid = 1; + + return rsc; +} + +/* * Load the file specified by the entity information. */ static char * @@ -522,6 +555,7 @@ parse_entity(struct entityq *q, struct m struct cert *cert; struct mft *mft; struct roa *roa; + struct rsc *rsc; struct ibuf *b; unsigned char *f; size_t flen; @@ -606,6 +640,16 @@ parse_entity(struct entityq *q, struct m file = parse_load_file(entp, &f, &flen); io_str_buffer(b, file); proc_parser_gbr(file, f, flen); + break; + case RTYPE_RSC: + file = parse_load_file(entp, &f, &flen); + io_str_buffer(b, file); + rsc = proc_parser_rsc(file, f, flen); + c = (rsc != NULL); + io_simple_buffer(b, &c, sizeof(int)); + if (rsc != NULL) + rsc_buffer(b, rsc); + rsc_free(rsc); break; default: errx(1, "unhandled entity type %d", entp->type); Index: usr.sbin/rpki-client/print.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/print.c,v retrieving revision 1.10 diff -u -p -r1.10 print.c --- usr.sbin/rpki-client/print.c 24 Apr 2022 18:20:12 -0000 1.10 +++ usr.sbin/rpki-client/print.c 8 May 2022 16:00:42 -0000 @@ -455,3 +455,125 @@ gbr_print(const X509 *x, const struct gb printf("vcard:\n%s", p->vcard); } } + +void +rsc_print(const X509 *x, const struct rsc *p) +{ + char buf1[64], buf2[64], tbuf[21]; + char *hash; + int sockt; + size_t i, j; + + strftime(tbuf, sizeof(tbuf), "%FT%TZ", gmtime(&p->expires)); + + if (outformats & FORMAT_JSON) { + printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski)); + printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki)); + x509_print(x); + printf("\t\"aia\": \"%s\",\n", p->aia); + printf("\t\"valid_until\": %lld,\n", (long long)p->expires); + printf("\t\"signed_with_resources\": [\n"); + } else { + printf("Subject key identifier: %s\n", pretty_key_id(p->ski)); + printf("Authority key identifier: %s\n", pretty_key_id(p->aki)); + x509_print(x); + printf("Authority info access: %s\n", p->aia); + printf("Valid until: %s\n", tbuf); + printf("Signed with resources:\n"); + } + + for (i = 0; i < p->asz; i++) { + switch (p->as[i].type) { + case CERT_AS_ID: + if (outformats & FORMAT_JSON) + printf("\t\t{ \"asid\": %u }", p->as[i].id); + else + printf("%5zu: AS: %u", i + 1, p->as[i].id); + break; + case CERT_AS_INHERIT: + if (outformats & FORMAT_JSON) + printf("\t\t{ \"asid_inherit\": \"true\" }"); + else + printf("%5zu: AS: inherit", i + 1); + break; + case CERT_AS_RANGE: + if (outformats & FORMAT_JSON) + printf("\t\t{ \"asrange\": { \"min\": %u, " + "\"max\": %u }}", p->as[i].range.min, + p->as[i].range.max); + else + printf("%5zu: AS: %u -- %u", i + 1, + p->as[i].range.min, p->as[i].range.max); + break; + } + if (outformats & FORMAT_JSON && i + 1 < p->asz + p->ipsz) + printf(",\n"); + else + printf("\n"); + } + + for (j = 0; j < p->ipsz; j++) { + switch (p->ips[j].type) { + case CERT_IP_INHERIT: + if (outformats & FORMAT_JSON) + printf("\t\t{ \"ip_inherit\": \"true\" }"); + else + printf("%5zu: IP: inherit", i + j + 1); + break; + case CERT_IP_ADDR: + ip_addr_print(&p->ips[j].ip, + p->ips[j].afi, buf1, sizeof(buf1)); + if (outformats & FORMAT_JSON) + printf("\t\t{ \"ip_prefix\": \"%s\" }", buf1); + else + printf("%5zu: IP: %s", i + j + 1, buf1); + break; + case CERT_IP_RANGE: + sockt = (p->ips[j].afi == AFI_IPV4) ? + AF_INET : AF_INET6; + inet_ntop(sockt, p->ips[j].min, buf1, sizeof(buf1)); + inet_ntop(sockt, p->ips[j].max, buf2, sizeof(buf2)); + if (outformats & FORMAT_JSON) + printf("\t\t{ \"ip_range\": { \"min\": \"%s\"" + ", \"max\": \"%s\" }}", buf1, buf2); + else + printf("%5zu: IP: %s -- %s", i + j + 1, buf1, + buf2); + break; + } + if (outformats & FORMAT_JSON && i + j + 1 < p->asz + p->ipsz) + printf(",\n"); + else + printf("\n"); + } + + if (outformats & FORMAT_JSON) { + printf("\t],\n"); + printf("\t\"filenamesandhashes\": [\n"); + } else + printf("Filenames and hashes:\n"); + + for (i = 0; i < p->filesz; i++) { + if (base64_encode(p->files[i].hash, sizeof(p->files[i].hash), + &hash) == -1) + errx(1, "base64_encode failure"); + + if (outformats & FORMAT_JSON) { + printf("\t\t{ \"filename\": \"%s\",", + p->files[i].filename ? p->files[i].filename : ""); + printf(" \"hash_digest\": \"%s\" }", hash); + if (i + 1 < p->filesz) + printf(","); + printf("\n"); + } else { + printf("%5zu: %s\n", i + 1, p->files[i].filename + ? p->files[i].filename : "no filename"); + printf("\thash %s\n", hash); + } + + free(hash); + } + + if (outformats & FORMAT_JSON) + printf("\t],\n"); +} Index: usr.sbin/rpki-client/rpki-client.8 =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/rpki-client.8,v retrieving revision 1.61 diff -u -p -r1.61 rpki-client.8 --- usr.sbin/rpki-client/rpki-client.8 20 Apr 2022 20:26:22 -0000 1.61 +++ usr.sbin/rpki-client/rpki-client.8 8 May 2022 16:00:42 -0000 @@ -269,6 +269,8 @@ A Profile for BGPsec Router Certificates Certification Requests. .It RFC 8630 Resource Public Key Infrastructure (RPKI) Trust Anchor Locator. +.It draft-ietf-sidrops-rpki-rsc +A profile for Resource Public Key Infrastructure (RPKI) Signed Checklists (RSC). .El .Sh HISTORY .Nm Index: usr.sbin/rpki-client/rsc.c =================================================================== RCS file: usr.sbin/rpki-client/rsc.c diff -N usr.sbin/rpki-client/rsc.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ usr.sbin/rpki-client/rsc.c 8 May 2022 16:00:42 -0000 @@ -0,0 +1,802 @@ +/* $OpenBSD: rsc.c,v 1.0 2021/03/29 06:50:44 job Exp $ */ +/* + * Copyright (c) 2022 Job Snijders <j...@fastly.com> + * + * 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 <assert.h> +#include <err.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/asn1.h> +#include <openssl/x509.h> + +#include "extern.h" + +/* + * Parse results and data of the Signed Checklist file. + */ +struct parse { + const char *fn; /* Signed Checklist file name */ + struct rsc *res; /* results */ +}; + +extern ASN1_OBJECT *rsc_oid; + +/* + * Append an AS identifier structure to our list of results. + * Copied from cert.c. + * Return zero on failure. + */ +static int +append_as(struct parse *p, const struct cert_as *as) +{ + if (!as_check_overlap(as, p->fn, p->res->as, p->res->asz)) + return 0; + if (p->res->asz >= MAX_AS_SIZE) + return 0; + p->res->as = reallocarray(p->res->as, p->res->asz + 1, + sizeof(struct cert_as)); + if (p->res->as == NULL) + err(1, NULL); + p->res->as[p->res->asz++] = *as; + return 1; +} + +/* + * Append an IP address structure to our list of results. + * return zero on failure. + */ +static int +append_ip(struct parse *p, const struct cert_ip *ip) +{ + struct rsc *res = p->res; + + if (!ip_addr_check_overlap(ip, p->fn, p->res->ips, p->res->ipsz)) + return 0; + if (res->ipsz >= MAX_IP_SIZE) + return 0; + + res->ips = reallocarray(res->ips, res->ipsz + 1, + sizeof(struct cert_ip)); + if (res->ips == NULL) + err(1, NULL); + + res->ips[res->ipsz++] = *ip; + return 1; +} + +static int +rsc_check_digesttype(struct parse *p, const unsigned char *d, size_t dsz) +{ + ASN1_SEQUENCE_ANY *seq; + const ASN1_TYPE *t; + int rc = 0; + + if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: RSC failed ASN.1 sequence parse", p->fn); + goto out; + } + if (sk_ASN1_TYPE_num(seq) != 1) { + warnx("%s: unexpected parameters for SHA256: want 1 element," + " have %d", p->fn, sk_ASN1_TYPE_num(seq)); + goto out; + } + + t = sk_ASN1_TYPE_value(seq, 0); + if (t->type != V_ASN1_OBJECT) { + warnx("%s: RSC DigestAlgorithmIdentifier: want ASN.1 object, " + "have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); + goto out; + } else if (OBJ_obj2nid(t->value.object) != NID_sha256) { + warnx("%s: RSC DigestAlgorithmIdentifier: want SHA256, have %s" + " (NID %d)", p->fn, + ASN1_tag2str(OBJ_obj2nid(t->value.object)), + OBJ_obj2nid(t->value.object)); + goto out; + } + + rc = 1; + out: + sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); + return rc; +} + +/* + * Parse and individual "FileNameAndHash", draft-ietf-sidrops-rpki-rsc + * section 4.1. + * Return zero on failure, non-zero on success. + */ +static int +rsc_parse_filenamehash(struct parse *p, const ASN1_OCTET_STRING *os) +{ + ASN1_SEQUENCE_ANY *seq; + const ASN1_TYPE *file, *hash; + char *fn = NULL; + const unsigned char *d = os->data; + size_t dsz = os->length; + int i = 0, rc = 0, elemz; + struct rscfile *rent; + + if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: RSC FileNameAndHash: failed ASN.1 sequence " + "parse", p->fn); + goto out; + } else if ((elemz = sk_ASN1_TYPE_num(seq)) != 1 && elemz != 2) { + warnx("%s: RSC FileNameAndHash: want 1 or 2 elements, have %d", + p->fn, elemz); + goto out; + } + + if (elemz == 2) { + file = sk_ASN1_TYPE_value(seq, i++); + if (file->type != V_ASN1_IA5STRING) { + warnx("%s: RSC FileNameAndHash: want ASN.1 IA5 string," + " have %s (NID %d)", p->fn, + ASN1_tag2str(file->type), file->type); + goto out; + } + fn = strndup((const char *)file->value.ia5string->data, + file->value.ia5string->length); + if (fn == NULL) + err(1, NULL); + + /* filename must confirm to portable file name character set */ + if (strchr(fn, '/') != NULL) { + warnx("%s: path components disallowed in filename: %s", + p->fn, fn); + goto out; + } else if (strchr(fn, '\n') != NULL) { + warnx("%s: newline disallowed in filename: %s", + p->fn, fn); + goto out; + } + } + + /* Now hash value. */ + + hash = sk_ASN1_TYPE_value(seq, i); + if (hash->type != V_ASN1_OCTET_STRING) { + warnx("%s: RSC FileNameAndHash: want ASN.1 OCTET string, have " + "%s (NID %d)", p->fn, ASN1_tag2str(hash->type), hash->type); + goto out; + } + + if (hash->value.octet_string->length != SHA256_DIGEST_LENGTH) { + warnx("%s: RSC Digest: invalid SHA256 length, have %d", + p->fn, hash->value.octet_string->length); + goto out; + } + + p->res->files = recallocarray(p->res->files, p->res->filesz, + p->res->filesz + 1, sizeof(struct rscfile)); + if (p->res->files == NULL) + err(1, NULL); + + rent = &p->res->files[p->res->filesz++]; + rent->filename = fn; + fn = NULL; + memcpy(rent->hash, hash->value.octet_string->data, SHA256_DIGEST_LENGTH); + + rc = 1; + out: + free(fn); + sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); + return rc; +} + +/* + * Parse the FileNameAndHash sequence, draft-ietf-sidrops-rpki-rsc + * section 4.1 + * Return zero on failure, non-zero on success. + */ +static int +rsc_parse_checklist(struct parse *p, const ASN1_OCTET_STRING *os) +{ + ASN1_SEQUENCE_ANY *seq; + const ASN1_TYPE *t; + const unsigned char *d = os->data; + size_t dsz = os->length; + int i, rc = 0; + + if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: RSC checkList: failed ASN.1 sequence parse", + p->fn); + goto out; + } + + for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { + t = sk_ASN1_TYPE_value(seq, i); + if (t->type != V_ASN1_SEQUENCE) { + warnx("%s: RSC checkList: want ASN.1 sequence, have %s" + " (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); + goto out; + } else if (!rsc_parse_filenamehash(p, t->value.octet_string)) + goto out; + } + + rc = 1; + out: + sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); + return rc; +} + +/* + * Convert ASN1 INTEGER and add it to parse results + * Return zero on failure. + */ +static int +rsc_parse_asid(struct parse *p, const ASN1_INTEGER *i) +{ + struct cert_as as; + + memset(&as, 0, sizeof(struct cert_as)); + as.type = CERT_AS_ID; + + if (!as_id_parse(i, &as.id)) { + warnx("%s: RSC malformed AS identifier in %s", p->fn, __func__); + return 0; + } + if (as.id == 0) { + warnx("%s: RSC AS identifier zero is reserved", p->fn); + return 0; + } + + return append_as(p, &as); +} + +/* + * Parse AS Range and add it to parse result + * Return zero on failure. + */ +static int +rsc_parse_asrange(struct parse *p, const unsigned char *d, size_t dsz) +{ + struct cert_as as; + ASN1_SEQUENCE_ANY *seq; + const ASN1_TYPE *t; + int rc = 0; + + if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: ASRange failed ASN.1 seq parse", p->fn); + goto out; + } + + if (sk_ASN1_TYPE_num(seq) != 2) { + warnx("%s: expected 2 elements in RSC ASRange, have %d", + p->fn, sk_ASN1_TYPE_num(seq)); + goto out; + } + + memset(&as, 0, sizeof(struct cert_as)); + as.type = CERT_AS_RANGE; + + t = sk_ASN1_TYPE_value(seq, 0); + if (t->type != V_ASN1_INTEGER) { + warnx("%s: RSC ASRange: want ASN.1 integer, have %s (NID %d)", + p->fn, ASN1_tag2str(t->type), t->type); + goto out; + } + if (!as_id_parse(t->value.integer, &as.range.min)) { + warnx("%s: RSC malformed AS identifier in %s", p->fn, __func__); + goto out; + } + + t = sk_ASN1_TYPE_value(seq, 1); + if (t->type != V_ASN1_INTEGER) { + warnx("%s: RSC ASRange: want ASN.1 integer, have %s (NID %d)", + p->fn, ASN1_tag2str(t->type), t->type); + goto out; + } + if (!as_id_parse(t->value.integer, &as.range.max)) { + warnx("%s: RSC malformed AS identifier in %s", p->fn, __func__); + goto out; + } + + if (as.range.max == as.range.min) { + warnx("%s: RSC ASRange error: range is singular", p->fn); + goto out; + } + if (as.range.max < as.range.min) { + warnx("%s: RSC ASRange: range is out of order", p->fn); + goto out; + } + + if (!append_as(p, &as)) + goto out; + + rc = 1; + out: + sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); + return rc; +} + +/* + * parse AsList (inside ResourceBlock) + * Return 0 on failure. + */ +static int +rsc_parse_aslist(struct parse *p, const unsigned char *d, size_t dsz) +{ + ASN1_SEQUENCE_ANY *seq; + const ASN1_TYPE *t; + int i, rc = 0; + + if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: RSC AsList: failed ASN.1 sequence parse", + p->fn); + goto out; + } + + for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { + t = sk_ASN1_TYPE_value(seq, i); + switch (t->type) { + case V_ASN1_INTEGER: + if (!rsc_parse_asid(p, t->value.integer)) + goto out; + break; + case V_ASN1_SEQUENCE: + d = t->value.asn1_string->data; + dsz = t->value.asn1_string->length; + if (!rsc_parse_asrange(p, d, dsz)) + goto out; + break; + default: + warnx("%s: RSC AsList expected INTEGER or SEQUENCE, " + "have %s (NID %d)", p->fn, ASN1_tag2str(t->type), + t->type); + goto out; + } + } + + rc = 1; + out: + sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); + return rc; +} + +/* + * parse IPAddressFamilyItem (inside IPList, inside ResourceBlock) + * Return 0 on failure. + */ +static int +rsc_parse_ipaddrfamitem(struct parse *p, const ASN1_OCTET_STRING *os) +{ + ASN1_OCTET_STRING *aos = NULL; + IPAddressOrRange *aor = NULL; + int tag; + const unsigned char *cnt = os->data; + long cntsz; + const unsigned char *d; + size_t dsz = os->length; + struct cert_ip ip; + int rc = 0; + + memset(&ip, 0, sizeof(struct cert_ip)); + + /* + * IPAddressFamilyItem is a sequence containing an addressFamily and + * an IPAddressOrRange. + */ + if (!ASN1_frame(p->fn, dsz, &cnt, &cntsz, &tag)) { + cryptowarnx("%s: ASN1_frame failed in %s", p->fn, __func__); + goto out; + } + if (tag != V_ASN1_SEQUENCE) { + warnx("expected ASN.1 sequence, got %d in %s", tag, __func__); + goto out; + } + + d = cnt; + + if ((aos = d2i_ASN1_OCTET_STRING(NULL, &cnt, cntsz)) == NULL) { + cryptowarnx("%s: d2i_ASN1_OCTET_STRING failed", p->fn); + goto out; + } + + cntsz -= (cnt - d); + assert(cntsz >= 0); + + if (!ip_addr_afi_parse(p->fn, aos, &ip.afi)) { + warnx("%s: RSC invalid addressFamily in %s", p->fn, __func__); + goto out; + } + + d = cnt; + + if ((aor = d2i_IPAddressOrRange(NULL, &cnt, cntsz)) == NULL) { + warnx("%s: d2i_IPAddressOrRange failed", p->fn); + goto out; + } + + cntsz -= (cnt - d); + assert(cntsz >= 0); + + if (cntsz > 0) { + warnx("%s: trailing garbage in RSC IPAddressFamilyItem", p->fn); + goto out; + } + + switch (aor->type) { + case IPAddressOrRange_addressPrefix: + ip.type = CERT_IP_ADDR; + if (!ip_addr_parse(aor->u.addressPrefix, ip.afi, p->fn, &ip.ip)) + goto out; + break; + case IPAddressOrRange_addressRange: + ip.type = CERT_IP_RANGE; + if (!ip_addr_parse(aor->u.addressRange->min, ip.afi, p->fn, + &ip.range.min)) + goto out; + if (!ip_addr_parse(aor->u.addressRange->max, ip.afi, p->fn, + &ip.range.max)) + goto out; + break; + } + + if (!ip_cert_compose_ranges(&ip)) { + warnx("%s: RSC IP address range reversed", p->fn); + goto out; + } + + if (!append_ip(p, &ip)) + goto out; + + rc = 1; + out: + ASN1_OCTET_STRING_free(aos); + IPAddressOrRange_free(aor); + return rc; +} + +static int +rsc_parse_iplist(struct parse *p, const unsigned char *d, size_t dsz) +{ + ASN1_SEQUENCE_ANY *seq; + const ASN1_TYPE *t; + int i, rc = 0; + + if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: RSC IPList: failed ASN.1 sequence parse", + p->fn); + goto out; + } + + for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { + t = sk_ASN1_TYPE_value(seq, i); + if (t->type != V_ASN1_SEQUENCE) { + warnx("%s: RSC IPList: want ASN.1 sequence, have %s" + " (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); + goto out; + } + if (!rsc_parse_ipaddrfamitem(p, t->value.octet_string)) + goto out; + } + + rc = 1; + out: + sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); + return rc; +} + +/* + * Parse a ResourceBlock, draft-ietf-sidrops-rpki-rsc section 4 + * Returns zero on failure, non-zero on success. + */ +static int +rsc_parse_resourceblock(const ASN1_OCTET_STRING *os, struct parse *p) +{ + ASN1_SEQUENCE_ANY *seq; + const unsigned char *d = os->data; + size_t dsz = os->length; + int i, ptag, rc = 0; + const ASN1_TYPE *t; + long plen; + + if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: RSC: ResourceBlock: failed ASN.1 sequence " + "parse in %s", p->fn, __func__); + goto out; + } + + if (sk_ASN1_TYPE_num(seq) == 0) { + warnx("%s: ResourceBlock, there must be at least one of asID " + "or ipAddrBlocks", p->fn); + goto out; + } + + for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { + t = sk_ASN1_TYPE_value(seq, i); + + d = t->value.asn1_string->data; + dsz = t->value.asn1_string->length; + if (!ASN1_frame(p->fn, dsz, &d, &plen, &ptag)) + goto out; + switch (ptag) { + case RSRCBLK_TYPE_ASID: + if (!rsc_parse_aslist(p, d, plen)) + goto out; + break; + case RSRCBLK_TYPE_IPADDRBLK: + if (!rsc_parse_iplist(p, d, plen)) + goto out; + break; + default: + warnx("%s: want ASN.1 context specific id, have %s" + " (NID %d)", p->fn, ASN1_tag2str(ptag), ptag); + goto out; + } + } + + rc = 1; + out: + sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); + return rc; +} + +/* + * Parses the eContent segment of a RSC file + * draft-ietf-sidrops-rpki-rsc, section 4 + * Returns zero on failure, non-zero on success. + */ +static int +rsc_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) +{ + ASN1_SEQUENCE_ANY *seq; + const ASN1_TYPE *t; + int i = 0, rc = 0, sz; + long rsc_version; + + /* + * draft-ietf-sidrops-rpki-rsc section 4 + */ + + if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { + cryptowarnx("%s: RSC: RpkiSignedChecklist: failed ASN.1 " + "sequence parse", p->fn); + goto out; + } + + if ((sz = sk_ASN1_TYPE_num(seq)) != 3 && sz != 4) { + warnx("%s: RSC RpkiSignedChecklist: want 3 or 4 elements, have" + "%d", p->fn, sk_ASN1_TYPE_num(seq)); + goto out; + } + + /* + * if there are 4 elements, a version should be present: check it. + */ + if (sz == 4) { + t = sk_ASN1_TYPE_value(seq, i++); + d = t->value.asn1_string->data; + dsz = t->value.asn1_string->length; + + if (cms_econtent_version(p->fn, &d, dsz, &rsc_version) == -1) + goto out; + + switch (rsc_version) { + case 0: + warnx("%s: invalid encoding for version 0", p->fn); + goto out; + default: + warnx("%s: version %ld not supported (yet)", p->fn, + rsc_version); + goto out; + } + } + + /* + * The RSC's eContent ResourceBlock indicates which Internet Number + * Resources are associated with the signature over the checkList. + */ + t = sk_ASN1_TYPE_value(seq, i++); + if (t->type != V_ASN1_SEQUENCE) { + warnx("%s: RSC ResourceBlock: want ASN.1 sequence, have %s" + "(NID %d)", p->fn, ASN1_tag2str(t->type), t->type); + goto out; + } else if (!rsc_parse_resourceblock(t->value.octet_string, p)) + goto out; + + /* digestAlgorithm */ + t = sk_ASN1_TYPE_value(seq, i++); + if (t->type != V_ASN1_SEQUENCE) { + warnx("%s: RSC DigestAlgorithmIdentifier: want ASN.1 sequence," + " have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); + goto out; + } else if (!rsc_check_digesttype(p, t->value.asn1_string->data, + t->value.asn1_string->length)) + goto out; + + /* + * Now a sequence of FileNameAndHash + */ + t = sk_ASN1_TYPE_value(seq, i++); + if (t->type != V_ASN1_SEQUENCE) { + warnx("%s: RSC checkList: want ASN.1 sequence, have %s " + "(NID %d)", p->fn, ASN1_tag2str(t->type), t->type); + goto out; + } else if (!rsc_parse_checklist(p, t->value.octet_string)) + goto out; + + rc = 1; + out: + sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); + return rc; +} + +/* + * Parse a full draft-ietf-sidrops-rpki-rsc file. + * Returns the RSC or NULL if the object was malformed. + */ +struct rsc * +rsc_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len) +{ + struct parse p; + size_t cmsz; + unsigned char *cms; + int rc = 0; + const ASN1_TIME *at; + struct tm expires_tm; + time_t expires; + + memset(&p, 0, sizeof(struct parse)); + p.fn = fn; + + cms = cms_parse_validate(x509, fn, der, len, rsc_oid, &cmsz); + if (cms == NULL) + return NULL; + + if ((p.res = calloc(1, sizeof(struct rsc))) == NULL) + err(1, NULL); + + if (!x509_get_aia(*x509, fn, &p.res->aia)) + goto out; + if (!x509_get_aki(*x509, fn, &p.res->aki)) + goto out; + if (!x509_get_ski(*x509, fn, &p.res->ski)) + goto out; + if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) { + warnx("%s: RFC 6487 section 4.8: " + "missing AIA, AKI or SKI X509 extension", fn); + goto out; + } + + at = X509_get0_notAfter(*x509); + if (at == NULL) { + warnx("%s: X509_get0_notAfter failed in %s", fn, __func__); + goto out; + } + memset(&expires_tm, 0, sizeof(expires_tm)); + if (ASN1_time_parse(at->data, at->length, &expires_tm, 0) == -1) { + warnx("%s: ASN1_time_parse failed in %s", fn, __func__); + goto out; + } + if ((expires = mktime(&expires_tm)) == -1) + errx(1, "mktime failed in %s", __func__); + + p.res->expires = expires; + + if (!rsc_parse_econtent(cms, cmsz, &p)) + goto out; + + rc = 1; + out: + if (rc == 0) { + rsc_free(p.res); + p.res = NULL; + X509_free(*x509); + *x509 = NULL; + } + free(cms); + return p.res; +} + +/* + * Free an RSC pointer. + * Safe to call with NULL. + */ +void +rsc_free(struct rsc *p) +{ + if (p == NULL) + return; + + free(p->aia); + free(p->aki); + free(p->ski); + free(p->ips); + free(p->as); + free(p->files); + free(p); +} + +/* + * Serialise parsed RSC content. + * See rsc_read() for reader. + */ +void +rsc_buffer(struct ibuf *b, const struct rsc *p) +{ + size_t i; + + io_simple_buffer(b, &p->expires, sizeof(p->expires)); + io_simple_buffer(b, &p->talid, sizeof(p->talid)); + io_simple_buffer(b, &p->valid, sizeof(p->valid)); + + io_str_buffer(b, p->ski); + io_str_buffer(b, p->aki); + io_str_buffer(b, p->aia); + + io_simple_buffer(b, &p->asz, sizeof(p->asz)); + io_simple_buffer(b, &p->as, p->asz * sizeof(p->as[0])); + + io_simple_buffer(b, &p->ipsz, sizeof(p->ipsz)); + io_simple_buffer(b, &p->ips, p->ipsz * sizeof(p->ips[0])); + + io_simple_buffer(b, &p->filesz, sizeof(p->filesz)); + for (i = 0; i < p->filesz; i++) { + io_str_buffer(b, p->files[i].filename); + io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH); + } +} + +/* + * Read parsed RSC content from descriptor. + * See rsc_buffer() for writer. + * Result must be passed to rsc_free(). + */ +struct rsc * +rsc_read(struct ibuf *b) +{ + struct rsc *p; + size_t i; + + if ((p = calloc(1, sizeof(struct rsc))) == NULL) + err(1, NULL); + + io_read_buf(b, &p->expires, sizeof(p->expires)); + io_read_buf(b, &p->talid, sizeof(p->talid)); + io_read_buf(b, &p->valid, sizeof(p->valid)); + + io_read_str(b, &p->ski); + io_read_str(b, &p->aki); + io_read_str(b, &p->aia); + assert(p->ski && p->aia && p->aia); + + io_read_buf(b, &p->asz, sizeof(p->asz)); + p->as = calloc(p->asz, sizeof(struct cert_as)); + if (p->as == NULL) + err(1, NULL); + io_read_buf(b, p->as, p->asz * sizeof(p->as[0])); + + io_read_buf(b, &p->ipsz, sizeof(p->ipsz)); + p->ips = calloc(p->ipsz, sizeof(struct cert_ip)); + if (p->ips == NULL) + err(1, NULL); + io_read_buf(b, p->ips, p->ipsz * sizeof(p->ips[0])); + + io_read_buf(b, &p->filesz, sizeof(size_t)); + if ((p->files = calloc(p->filesz, sizeof(struct rscfile))) == NULL) + err(1, NULL); + for (i = 0; i < p->filesz; i++) { + io_read_str(b, &p->files[i].filename); + io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH); + } + + return p; +} Index: usr.sbin/rpki-client/validate.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/validate.c,v retrieving revision 1.31 diff -u -p -r1.31 validate.c --- usr.sbin/rpki-client/validate.c 21 Apr 2022 09:53:07 -0000 1.31 +++ usr.sbin/rpki-client/validate.c 8 May 2022 16:00:42 -0000 @@ -486,3 +486,65 @@ valid_x509(char *file, X509_STORE_CTX *s sk_X509_CRL_free(crls); return 1; } + +/* + * Validate our RSC: check that all items in the ResourceBlock are contained. + * Returns 1 if valid, 0 otherwise. + */ +int +valid_rsc(const char *fn, struct auth *a, struct rsc *rsc) +{ + size_t i; + uint32_t min, max; + char buf1[64], buf2[64]; + + for (i = 0; i < rsc->asz; i++) { + switch (rsc->as[i].type) { + case CERT_AS_INHERIT: + return 0; /* RSC doesn't permit inheriting */ + case CERT_AS_ID: + if (valid_as(a, rsc->as[i].id, rsc->as[i].id)) + continue; + warnx("%s: RSC resourceBlock: uncovered AS Identifier: " + "%u", fn, rsc->as[i].id); + return 0; + case CERT_AS_RANGE: + min = rsc->as[i].range.min; + max = rsc->as[i].range.max; + if (valid_as(a, min, max)) + continue; + warnx("%s: RSC resourceBlock: uncovered AS Range: " + "%u--%u", fn, min, max); + return 0; + } + } + + for (i = 0; i < rsc->ipsz; i++) { + if (valid_ip(a, rsc->ips[i].afi, rsc->ips[i].min, + rsc->ips[i].max)) + continue; + + switch (rsc->ips[i].type) { + case CERT_IP_RANGE: + ip_addr_print(&rsc->ips[i].range.min, + rsc->ips[i].afi, buf1, sizeof(buf1)); + ip_addr_print(&rsc->ips[i].range.max, + rsc->ips[i].afi, buf2, sizeof(buf2)); + warnx("%s: RSC ResourceBlock: uncovered IP Range: " + "%s--%s", fn, buf1, buf2); + break; + case CERT_IP_ADDR: + ip_addr_print(&rsc->ips[i].ip, + rsc->ips[i].afi, buf1, sizeof(buf1)); + warnx("%s: RSC ResourceBlock: uncovered IP: " + "%s", fn, buf1); + break; + case CERT_IP_INHERIT: + warnx("%s: RSC ResourceBlock: illegal inherit", fn); + break; + } + return 0; + } + + return 1; +} Index: usr.sbin/rpki-client/x509.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/x509.c,v retrieving revision 1.41 diff -u -p -r1.41 x509.c --- usr.sbin/rpki-client/x509.c 15 Apr 2022 12:59:44 -0000 1.41 +++ usr.sbin/rpki-client/x509.c 8 May 2022 16:00:43 -0000 @@ -42,6 +42,7 @@ ASN1_OBJECT *cnt_type_oid; /* pkcs-9 id- ASN1_OBJECT *msg_dgst_oid; /* pkcs-9 id-messageDigest */ ASN1_OBJECT *sign_time_oid; /* pkcs-9 id-signingTime */ ASN1_OBJECT *bin_sign_time_oid; /* pkcs-9 id-aa-binarySigningTime */ +ASN1_OBJECT *rsc_oid; /* id-ct-signedChecklist */ void x509_init_oid(void) @@ -76,6 +77,10 @@ x509_init_oid(void) OBJ_txt2obj("1.2.840.113549.1.9.16.2.46", 1)) == NULL) errx(1, "OBJ_txt2obj for %s failed", "1.2.840.113549.1.9.16.2.46"); + if ((rsc_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.48", 1)) == NULL) + errx(1, "OBJ_txt2obj for %s failed", + "1.2.840.113549.1.9.16.1.48"); + } /* Index: regress/usr.sbin/rpki-client/test-rsc.c =================================================================== RCS file: regress/usr.sbin/rpki-client/test-rsc.c diff -N regress/usr.sbin/rpki-client/test-rsc.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ regress/usr.sbin/rpki-client/test-rsc.c 8 May 2022 16:00:43 -0000 @@ -0,0 +1,104 @@ +/* $Id: test-rsc.c,v 1.18 2022/01/19 08:24:43 claudio Exp $ */ +/* + * Copyright (c) 2022 Job Snijders <j...@fastly.com> + * + * 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 <sys/types.h> +#include <netinet/in.h> +#include <assert.h> +#include <err.h> +#include <resolv.h> /* b64_ntop */ +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/x509v3.h> + +#include "extern.h" + +int outformats; +int verbose; + +int +main(int argc, char *argv[]) +{ + int c, i, ppem = 0, verb = 0; + struct rsc *p; + BIO *bio_out = NULL; + X509 *xp = NULL; + unsigned char *buf; + size_t len; + + ERR_load_crypto_strings(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + x509_init_oid(); + + while (-1 != (c = getopt(argc, argv, "pv"))) + switch (c) { + case 'p': + if (ppem) + break; + ppem = 1; + if ((bio_out = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) + errx(1, "BIO_new_fp"); + break; + case 'v': + verb++; + break; + default: + errx(1, "bad argument %c", c); + } + + argv += optind; + argc -= optind; + + if (argc == 0) + errx(1, "argument missing"); + + for (i = 0; i < argc; i++) { + buf = load_file(argv[i], &len); + if ((p = rsc_parse(&xp, argv[i], buf, len)) == NULL) { + free(buf); + continue; + } + if (verb) + rsc_print(xp, p); + if (ppem) { + if (!PEM_write_bio_X509(bio_out, xp)) + errx(1, + "PEM_write_bio_X509: unable to write cert"); + } + free(buf); + rsc_free(p); + X509_free(xp); + } + + BIO_free(bio_out); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_free_strings(); + + if (i < argc) + errx(1, "test failed for %s", argv[i]); + + printf("OK\n"); + return 0; +} Index: regress/usr.sbin/rpki-client/Makefile.inc =================================================================== RCS file: /cvs/src/regress/usr.sbin/rpki-client/Makefile.inc,v retrieving revision 1.23 diff -u -p -r1.23 Makefile.inc --- regress/usr.sbin/rpki-client/Makefile.inc 20 Apr 2022 17:37:53 -0000 1.23 +++ regress/usr.sbin/rpki-client/Makefile.inc 8 May 2022 16:00:43 -0000 @@ -7,6 +7,7 @@ PROGS += test-cert PROGS += test-gbr PROGS += test-mft PROGS += test-roa +PROGS += test-rsc PROGS += test-tal PROGS += test-rrdp @@ -42,6 +43,11 @@ SRCS_test-roa+= test-roa.c roa.c cms.c x encoding.c print.c validate.c cert.c mft.c run-regress-test-roa: test-roa ./test-roa -v ${.CURDIR}/../roa/*.roa + +SRCS_test-rsc+= test-rsc.c rsc.c cms.c x509.c ip.c as.c io.c log.c \ + encoding.c print.c validate.c cert.c mft.c +run-regress-test-rsc: test-rsc + ./test-rsc -v ${.CURDIR}/../rsc/*.sig SRCS_test-gbr+= test-gbr.c gbr.c cms.c x509.c ip.c io.c log.c \ encoding.c print.c validate.c as.c cert.c mft.c