On Mon, May 09, 2022 at 12:11:22PM +0200, Claudio Jeker wrote:
> why does the draft allow for optional filenames? What the heck is the
> digest then covering some random gunk?

Yes, that is entirely possible. Some folks in the working group
requested the filename to be optional, I abided. In inter-business
workflows that are less filesystem centric, I imagine it is possible
that one party challenges the other party to produce a RSC signing a
specific (random) SHA256 hash, and then validating the RSC to see if the
desired hash showed up.

> This will make it close to impossible to actually fully validate a RSC
> (which would include the validation of all SHA256 signatures). Because
> of this RSC validation in rpki-client will be close to impossible
> because how do you implement:
> 
>   To verify a set of digital objects with an RSC:
> 
>    *  The message digest of each referenced digital object, using the
>       digest algorithm specified in the the digestAlgorithm field, MUST
>       be calculated and MUST match the value given in the messageDigest
>       field of the associated FileNameAndHash, for the digital object to
>       be considered as verified with the RSC.
> 
> When there is no filename to reference an object?

I think we should print the hash(es) where the filename was left out,
and leave it up to the relying party how to proceed and what to compare
against.

> > +           case CERT_IP_INHERIT:
> > +                   warnx("%s: RSC ResourceBlock: illegal inherit", fn);
> > +                   break;
> 
> I'm not sure if this works the way you think it does. The switch case is
> only triggers when valid_ip() fails but that may not be the case if
> rsc->ips[i].type == CERT_IP_INHERIT.
> 
> I think the proper way to check this is above the valid_ip() call to make
> sure CERT_IP_INHERIT can't be used. The parse kind of makes sure of this
> (it does not handle NULL ipAddrOrRange objects so it will bork on that
> with an error).

done.

> Actually this function is not used anyway because the only place it was
> called was in the parse.c code but that code is no needed.

I'm not entirely sure I follow? valid_rsc() is called in
proc_parser_rsc(), which is called in parse_entity().

> Also this code should probably verify the fileandhash list if we are
> serious about full RSC validation. rpki-client -f currently skips some
> other validation steps so it mostly depends on proper x509 validation.

which other steps are skipped?

Below is the latest patch based on feedback from you and Theo.

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       9 May 2022 13:04:16 -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       9 May 2022 13:04:16 -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     9 May 2022 13:04:16 -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  9 May 2022 13:04:16 -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       9 May 2022 13:04:16 -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 *
@@ -606,6 +639,11 @@ 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);
+                       proc_parser_rsc(file, f, flen);
                        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        9 May 2022 13:04:17 -0000
@@ -455,3 +455,119 @@ 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_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;
+               case CERT_AS_INHERIT:
+                       /* inheritance isn't possible in RSC */
+                       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_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;
+               case CERT_IP_INHERIT:
+                       /* inheritance isn't possible in RSC */
+                       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  9 May 2022 13:04:17 -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  9 May 2022 13:04:17 -0000
@@ -0,0 +1,740 @@
+/*     $OpenBSD: rsc.c,v 1.0 2021/03/29 06:50:44 job Exp $ */
+/*
+ * Copyright (c) 2022 Job Snijders <[email protected]>
+ * Copyright (c) 2019 Kristaps Dzonsons <[email protected]>
+ *
+ * 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.
+ * Return zero on failure.
+ * XXX: merge with append_as() in cert.c
+ */
+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.
+ * XXX: merge with append_ip() in cert.c
+ */
+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)
+{
+       X509_ALGOR              *alg;
+       const ASN1_OBJECT       *obj;
+       int                      type, nid;
+       int                      rc = 0;
+
+       if ((alg = d2i_X509_ALGOR(NULL, &d, dsz)) == NULL) {
+               cryptowarnx("%s: RSC DigestAlgorithmIdentifier faild to parse",
+                   p->fn);
+               goto out;
+       }
+
+       X509_ALGOR_get0(&obj, &type, NULL, alg);
+
+       if (type != V_ASN1_UNDEF) {
+               warnx("%s: RSC DigestAlgorithmIdentifier unexpected parameters:"
+                   " %d", p->fn, type);
+               goto out;
+       }
+
+       if ((nid = OBJ_obj2nid(obj)) != NID_sha256) {
+               warnx("%s: RSC DigestAlgorithmIdentifier: want SHA256, have %s"
+                   " (NID %d)", p->fn, ASN1_tag2str(nid), nid);
+               goto out;
+       }
+
+       rc = 1;
+ out:
+       X509_ALGOR_free(alg);
+       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, elemsz;
+       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;
+       }
+
+       elemsz = sk_ASN1_TYPE_num(seq);
+       if (elemsz != 1 && elemsz != 2) {
+               warnx("%s: RSC FileNameAndHash: want 1 or 2 elements, have %d",
+                   p->fn, elemsz);
+               goto out;
+       }
+
+       if (elemsz == 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
+                * XXX: use valid_filename() instead
+                */
+               if (strchr(fn, '/') != NULL) {
+                       warnx("%s: path components disallowed in filename: %s",
+                           p->fn, fn);
+                       goto out;
+               }
+               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;
+               }
+               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.
+ * XXX: merge with sbgp_asid() in cert.c
+ */
+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", p->fn);
+               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.
+ * XXX: merge with sbgp_asrange() in cert.c
+ */
+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", p->fn);
+               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", p->fn);
+               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;
+       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, os->length, &cnt, &cntsz, &tag)) {
+               cryptowarnx("%s: ASN1_frame failed", p->fn);
+               goto out;
+       }
+       if (tag != V_ASN1_SEQUENCE) {
+               warnx("expected ASN.1 sequence, got %d", tag);
+               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", p->fn);
+               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;
+       default:
+               warnx("%s: unknown addressOrRange type %d\n", p->fn, aor->type);
+               goto out;
+       }
+
+       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", p->fn);
+               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;
+       }
+       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;
+       }
+       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;
+       }
+       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;
+
+       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", fn);
+               goto out;
+       }
+       if (x509_get_time(at, &p.res->expires) == -1) {
+               warnx("%s: ASN1_time_parse failed", fn);
+               goto out;
+       }
+
+       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)
+{
+       size_t  i;
+
+       if (p == NULL)
+               return;
+
+       if (p->files != NULL)
+               for (i = 0; i < p->filesz; i++)
+                       free(p->files[i].filename);
+
+       free(p->aia);
+       free(p->aki);
+       free(p->ski);
+       free(p->ips);
+       free(p->as);
+       free(p->files);
+       free(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     9 May 2022 13:04:17 -0000
@@ -486,3 +486,66 @@ 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++) {
+               if (rsc->as[i].type == CERT_AS_INHERIT)
+                       return 0; /* RSC doesn't permit inheriting */
+
+               if (rsc->as[i].type == 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);
+               }
+
+               if (rsc->as[i].type == 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 (rsc->ips[i].type == CERT_IP_INHERIT)
+                       return 0;
+
+               if (valid_ip(a, rsc->ips[i].afi, rsc->ips[i].min,
+                   rsc->ips[i].max))
+                       continue;
+
+               if (rsc->ips[i].type == 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);
+               }
+
+               if (rsc->ips[i].type == 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);
+               }
+
+               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 9 May 2022 13:04:17 -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     9 May 2022 13:04:17 -0000
@@ -0,0 +1,104 @@
+/*     $Id: test-rsc.c,v 1.18 2022/01/19 08:24:43 claudio Exp $ */
+/*
+ * Copyright (c) 2019 Kristaps Dzonsons <[email protected]>
+ *
+ * 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   9 May 2022 13:04:17 -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
Index: regress/usr.sbin/rpki-client/openssl11/Makefile
===================================================================
RCS file: /cvs/src/regress/usr.sbin/rpki-client/openssl11/Makefile,v
retrieving revision 1.7
diff -u -p -r1.7 Makefile
--- regress/usr.sbin/rpki-client/openssl11/Makefile     20 Apr 2022 17:37:53 
-0000      1.7
+++ regress/usr.sbin/rpki-client/openssl11/Makefile     9 May 2022 13:04:17 
-0000
@@ -26,6 +26,7 @@ SRCS_test-gbr =               a_time_tm_gen.c o_time.
 SRCS_test-tal =                a_time_tm_gen.c o_time.c
 SRCS_test-bgpsec =     a_time_tm_gen.c o_time.c
 SRCS_test-rrdp =       a_time_tm_gen.c o_time.c
+SRCS_test-rsc =                a_time_tm_gen.c o_time.c
 CFLAGS +=      -I${.CURDIR}/../../../../lib/libcrypto/
 
 .PATH:         ${.CURDIR}/..

Reply via email to