summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJob Snijders <job@cvs.openbsd.org>2022-05-09 17:02:35 +0000
committerJob Snijders <job@cvs.openbsd.org>2022-05-09 17:02:35 +0000
commitf514202260d936fe427fa60a803b2f30406110a2 (patch)
tree8b1a238f9fe585f36c8ab969c31a795336f30f7b
parent77a93d74b640065f545dc5370f6044262f031208 (diff)
Add preliminary support for decoding RSC objects in filemode
This implements decoding support for draft-ietf-sidrops-rpki-rsc-06 There are three major outstanding issues: * The wire image might still change to conform to the more widely deployed 3779 API in libressl/openssl. IETF discussion ongoing. * Whether the resources listed in the ResourceBlock are contained within the EE's RFC 3779 extension is not hooked up yet. * There is a fair bit of duplicity between rsc.c and cert.c, look for XXX OK tb@
-rw-r--r--usr.sbin/rpki-client/Makefile4
-rw-r--r--usr.sbin/rpki-client/extern.h40
-rw-r--r--usr.sbin/rpki-client/filemode.c11
-rw-r--r--usr.sbin/rpki-client/mft.c4
-rw-r--r--usr.sbin/rpki-client/print.c118
-rw-r--r--usr.sbin/rpki-client/rpki-client.86
-rw-r--r--usr.sbin/rpki-client/rsc.c739
7 files changed, 914 insertions, 8 deletions
diff --git a/usr.sbin/rpki-client/Makefile b/usr.sbin/rpki-client/Makefile
index 6f53821e674..a27dcba8bb0 100644
--- a/usr.sbin/rpki-client/Makefile
+++ b/usr.sbin/rpki-client/Makefile
@@ -1,11 +1,11 @@
-# $OpenBSD: Makefile,v 1.24 2022/04/21 09:53:07 claudio Exp $
+# $OpenBSD: Makefile,v 1.25 2022/05/09 17:02:34 job Exp $
PROG= rpki-client
SRCS= as.c cert.c cms.c crl.c encoding.c filemode.c gbr.c http.c io.c ip.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
diff --git a/usr.sbin/rpki-client/extern.h b/usr.sbin/rpki-client/extern.h
index 79101aeb9bd..415eb51b6be 100644
--- a/usr.sbin/rpki-client/extern.h
+++ b/usr.sbin/rpki-client/extern.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: extern.h,v 1.132 2022/04/21 12:59:03 claudio Exp $ */
+/* $OpenBSD: extern.h,v 1.133 2022/05/09 17:02:34 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
@@ -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,10 @@ void gbr_free(struct gbr *);
struct gbr *gbr_parse(X509 **, const char *, const unsigned char *,
size_t);
+void rsc_free(struct rsc *);
+struct rsc *rsc_parse(X509 **, const char *, const unsigned char *,
+ size_t);
+
/* 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 +506,7 @@ int valid_uri(const char *, size_t, const char *);
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 +645,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! */
diff --git a/usr.sbin/rpki-client/filemode.c b/usr.sbin/rpki-client/filemode.c
index ec70d62f622..5ab2906d44c 100644
--- a/usr.sbin/rpki-client/filemode.c
+++ b/usr.sbin/rpki-client/filemode.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: filemode.c,v 1.5 2022/04/24 22:26:44 tb Exp $ */
+/* $OpenBSD: filemode.c,v 1.6 2022/05/09 17:02:34 job Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -264,6 +264,7 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
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;
@@ -357,6 +358,14 @@ proc_parser_file(char *file, unsigned char *buf, size_t len)
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);
break;
diff --git a/usr.sbin/rpki-client/mft.c b/usr.sbin/rpki-client/mft.c
index 565a441b0bb..ca94f1005e5 100644
--- a/usr.sbin/rpki-client/mft.c
+++ b/usr.sbin/rpki-client/mft.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mft.c,v 1.60 2022/04/20 10:46:20 job Exp $ */
+/* $OpenBSD: mft.c,v 1.61 2022/05/09 17:02:34 job Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
@@ -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;
}
diff --git a/usr.sbin/rpki-client/print.c b/usr.sbin/rpki-client/print.c
index c38ae1c238a..2db36b220b7 100644
--- a/usr.sbin/rpki-client/print.c
+++ b/usr.sbin/rpki-client/print.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: print.c,v 1.10 2022/04/24 18:20:12 tb Exp $ */
+/* $OpenBSD: print.c,v 1.11 2022/05/09 17:02:34 job Exp $ */
/*
* Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -455,3 +455,119 @@ gbr_print(const X509 *x, const struct gbr *p)
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");
+}
diff --git a/usr.sbin/rpki-client/rpki-client.8 b/usr.sbin/rpki-client/rpki-client.8
index 537f7b0293a..b307e462cac 100644
--- a/usr.sbin/rpki-client/rpki-client.8
+++ b/usr.sbin/rpki-client/rpki-client.8
@@ -1,4 +1,4 @@
-.\" $OpenBSD: rpki-client.8,v 1.61 2022/04/20 20:26:22 tb Exp $
+.\" $OpenBSD: rpki-client.8,v 1.62 2022/05/09 17:02:34 job Exp $
.\"
.\" Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
.\"
@@ -14,7 +14,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: April 20 2022 $
+.Dd $Mdocdate: May 9 2022 $
.Dt RPKI-CLIENT 8
.Os
.Sh NAME
@@ -269,6 +269,8 @@ A Profile for BGPsec Router Certificates, Certificate Revocation Lists, and
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
diff --git a/usr.sbin/rpki-client/rsc.c b/usr.sbin/rpki-client/rsc.c
new file mode 100644
index 00000000000..3e702d02233
--- /dev/null
+++ b/usr.sbin/rpki-client/rsc.c
@@ -0,0 +1,739 @@
+/* $OpenBSD: rsc.c,v 1.1 2022/05/09 17:02:34 job Exp $ */
+/*
+ * Copyright (c) 2022 Job Snijders <job@fastly.com>
+ * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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;
+
+ 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);
+}