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

Reply via email to