/* $OpenBSD: cert.c,v 1.28 2021/03/05 17:15:19 claudio Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "extern.h" /* * Type of ASIdentifier (RFC 3779, 3.2.3). */ #define ASID_TYPE_ASNUM 0x00 #define ASID_TYPE_RDI 0x01 #define ASID_TYPE_MAX ASID_TYPE_RDI /* * A parsing sequence of a file (which may just be ). */ struct parse { struct cert *res; /* result */ const char *fn; /* currently-parsed file */ }; /* * Wrapper around ASN1_get_object() that preserves the current start * state and returns a more meaningful value. * Return zero on failure, non-zero on success. */ static int ASN1_frame(struct parse *p, size_t sz, const unsigned char **cnt, long *cntsz, int *tag) { int ret, pcls; assert(cnt != NULL && *cnt != NULL); assert(sz > 0); ret = ASN1_get_object(cnt, cntsz, tag, &pcls, sz); if ((ret & 0x80)) { cryptowarnx("%s: ASN1_get_object", p->fn); return 0; } return ASN1_object_size((ret & 0x01) ? 2 : 0, *cntsz, *tag); } /* * Append an IP address structure to our list of results. * This will also constrain us to having at most one inheritence * statement per AFI and also not have overlapping rages (as prohibited * in section 2.2.3.6). * It does not make sure that ranges can't coalesce, that is, that any * two ranges abut each other. * This is warned against in section 2.2.3.6, but doesn't change the * semantics of the system. * Return zero on failure (IP overlap) non-zero on success. */ static int append_ip(struct parse *p, const struct cert_ip *ip) { struct cert *res = p->res; if (!ip_addr_check_overlap(ip, p->fn, p->res->ips, p->res->ipsz)) 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; } /* * Append an AS identifier structure to our list of results. * Makes sure that the identifiers do not overlap or improperly inherit * as defined by RFC 3779 section 3.3. */ 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; 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; } /* * Construct a RFC 3779 2.2.3.8 range by its bit string. * Return zero on failure, non-zero on success. */ static int sbgp_addr(struct parse *p, struct cert_ip *ip, const ASN1_BIT_STRING *bs) { if (!ip_addr_parse(bs, ip->afi, p->fn, &ip->ip)) { warnx("%s: RFC 3779 section 2.2.3.8: IPAddress: " "invalid IP address", p->fn); return 0; } if (!ip_cert_compose_ranges(ip)) { warnx("%s: RFC 3779 section 2.2.3.8: IPAddress: " "IP address range reversed", p->fn); return 0; } return append_ip(p, ip); } /* * Parse the SIA notify URL, 4.8.8.1. * Returns zero on failure, non-zero on success. */ static int sbgp_sia_resource_notify(struct parse *p, const char *d, size_t dsz) { if (p->res->notify != NULL) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "Notify location already specified", p->fn); return 0; } /* Make sure it's a https:// address. */ if (!valid_uri(d, dsz, "https://")) { warnx("%s: RFC 8182 section 3.2: bad Notify URI", p->fn); return 0; } if ((p->res->notify = strndup(d, dsz)) == NULL) err(1, NULL); return 1; } /* * Parse the SIA manifest, 4.8.8.1. * Returns zero on failure, non-zero on success. */ static int sbgp_sia_resource_mft(struct parse *p, const char *d, size_t dsz) { if (p->res->mft != NULL) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "MFT location already specified", p->fn); return 0; } /* Make sure it's an MFT rsync address. */ if (!valid_uri(d, dsz, "rsync://")) { warnx("%s: RFC 6487 section 4.8.8: bad MFT location", p->fn); return 0; } if (dsz < 4 || strcasecmp(d + dsz - 4, ".mft") != 0) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "not an MFT file", p->fn); return 0; } if ((p->res->mft = strndup(d, dsz)) == NULL) err(1, NULL); return 1; } /* * Parse the SIA manifest, 4.8.8.1. * Returns zero on failure, non-zero on success. */ static int sbgp_sia_resource_carepo(struct parse *p, const char *d, size_t dsz) { if (p->res->repo != NULL) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "CA repository already specified", p->fn); return 0; } /* Make sure it's an rsync:// address. */ if (!valid_uri(d, dsz, "rsync://")) { warnx("%s: RFC 6487 section 4.8.8: bad CA repository URI", p->fn); return 0; } if ((p->res->repo = strndup(d, dsz)) == NULL) err(1, NULL); return 1; } /* * Parse the SIA entries, 4.8.8.1. * There may be multiple different resources at this location, so throw * out all but the matching resource type. Currently only two entries * are of interest: rpkiManifest and rpkiNotify. * Returns zero on failure, non-zero on success. */ static int sbgp_sia_resource_entry(struct parse *p, const unsigned char *d, size_t dsz) { ASN1_SEQUENCE_ANY *seq; const ASN1_TYPE *t; int rc = 0, ptag; char buf[128]; long plen; if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 6487 section 4.8.8: SIA: " "failed ASN.1 sequence parse", p->fn); goto out; } if (sk_ASN1_TYPE_num(seq) != 2) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "want 2 elements, have %d", p->fn, sk_ASN1_TYPE_num(seq)); goto out; } /* Composed of an OID and its continuation. */ t = sk_ASN1_TYPE_value(seq, 0); if (t->type != V_ASN1_OBJECT) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "want ASN.1 object, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } OBJ_obj2txt(buf, sizeof(buf), t->value.object, 1); t = sk_ASN1_TYPE_value(seq, 1); if (t->type != V_ASN1_OTHER) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "want ASN.1 external, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } /* FIXME: there must be a way to do this without ASN1_frame. */ d = t->value.asn1_string->data; dsz = t->value.asn1_string->length; if (!ASN1_frame(p, dsz, &d, &plen, &ptag)) goto out; /* * Ignore all but manifest and RRDP notify URL. * Things we may see: * - 1.3.6.1.5.5.7.48.5 (caRepository) * - 1.3.6.1.5.5.7.48.10 (rpkiManifest) * - 1.3.6.1.5.5.7.48.13 (rpkiNotify) */ if (strcmp(buf, "1.3.6.1.5.5.7.48.5") == 0) rc = sbgp_sia_resource_carepo(p, d, plen); else if (strcmp(buf, "1.3.6.1.5.5.7.48.10") == 0) rc = sbgp_sia_resource_mft(p, d, plen); else if (strcmp(buf, "1.3.6.1.5.5.7.48.13") == 0) rc = sbgp_sia_resource_notify(p, d, plen); else rc = 1; /* silently ignore */ out: sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); return rc; } /* * Multiple locations as defined in RFC 6487, 4.8.8.1. * Returns zero on failure, non-zero on success. */ static int sbgp_sia_resource(struct parse *p, const unsigned char *d, size_t dsz) { ASN1_SEQUENCE_ANY *seq; const ASN1_TYPE *t; int rc = 0, i; if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 6487 section 4.8.8: SIA: " "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: RFC 6487 section 4.8.8: SIA: " "want ASN.1 sequence, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } d = t->value.asn1_string->data; dsz = t->value.asn1_string->length; if (!sbgp_sia_resource_entry(p, d, dsz)) goto out; } if (strstr(p->res->mft, p->res->repo) != p->res->mft) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "conflicting URIs for caRepository and rpkiManifest", p->fn); goto out; } rc = 1; out: sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); return rc; } /* * Parse "Subject Information Access" extension, RFC 6487 4.8.8. * Returns zero on failure, non-zero on success. */ static int sbgp_sia(struct parse *p, X509_EXTENSION *ext) { unsigned char *sv = NULL; const unsigned char *d; ASN1_SEQUENCE_ANY *seq = NULL; const ASN1_TYPE *t; int dsz, rc = 0; if ((dsz = i2d_X509_EXTENSION(ext, &sv)) < 0) { cryptowarnx("%s: RFC 6487 section 4.8.8: SIA: " "failed extension parse", p->fn); goto out; } d = sv; if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 6487 section 4.8.8: SIA: " "failed ASN.1 sequence parse", p->fn); goto out; } if (sk_ASN1_TYPE_num(seq) != 2) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "want 2 elements, 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: RFC 6487 section 4.8.8: SIA: " "want ASN.1 object, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } if (OBJ_obj2nid(t->value.object) != NID_sinfo_access) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "incorrect OID, have %s (NID %d)", p->fn, ASN1_tag2str(OBJ_obj2nid(t->value.object)), OBJ_obj2nid(t->value.object)); goto out; } t = sk_ASN1_TYPE_value(seq, 1); if (t->type != V_ASN1_OCTET_STRING) { warnx("%s: RFC 6487 section 4.8.8: SIA: " "want ASN.1 octet string, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } d = t->value.octet_string->data; dsz = t->value.octet_string->length; if (!sbgp_sia_resource(p, d, dsz)) goto out; rc = 1; out: sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); free(sv); return rc; } /* * Parse a range of addresses as in 3.2.3.8. * Returns zero on failure, non-zero on success. */ static int sbgp_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: RFC 3779 section 3.2.3.8: ASRange: " "failed ASN.1 sequence parse", p->fn); goto out; } if (sk_ASN1_TYPE_num(seq) != 2) { warnx("%s: RFC 3779 section 3.2.3.8: ASRange: " "want 2 elements, 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: RFC 3779 section 3.2.3.8: 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: RFC 3770 section 3.2.3.8 (via RFC 1930): " "malformed AS identifier", p->fn); return 0; } t = sk_ASN1_TYPE_value(seq, 1); if (t->type != V_ASN1_INTEGER) { warnx("%s: RFC 3779 section 3.2.3.8: 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: RFC 3770 section 3.2.3.8 (via RFC 1930): " "malformed AS identifier", p->fn); return 0; } if (as.range.max == as.range.min) { warnx("%s: RFC 3379 section 3.2.3.8: ASRange: " "range is singular", p->fn); goto out; } else if (as.range.max < as.range.min) { warnx("%s: RFC 3379 section 3.2.3.8: 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 an entire 3.2.3.10 integer type. */ static int sbgp_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: RFC 3770 section 3.2.3.10 (via RFC 1930): " "malformed AS identifier", p->fn); return 0; } if (as.id == 0) { warnx("%s: RFC 3770 section 3.2.3.10 (via RFC 1930): " "AS identifier zero is reserved", p->fn); return 0; } return append_as(p, &as); } /* * Parse one of RFC 3779 3.2.3.2. * Returns zero on failure, non-zero on success. */ static int sbgp_asnum(struct parse *p, const unsigned char *d, size_t dsz) { struct cert_as as; ASN1_TYPE *t, *tt; ASN1_SEQUENCE_ANY *seq = NULL; int i, rc = 0; const unsigned char *sv = d; /* We can either be a null (inherit) or sequence. */ if ((t = d2i_ASN1_TYPE(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 3779 section 3.2.3.2: ASIdentifierChoice: " "failed ASN.1 type parse", p->fn); goto out; } /* * Section 3779 3.2.3.3 is to inherit with an ASN.1 NULL type, * which is the easy case. */ switch (t->type) { case V_ASN1_NULL: memset(&as, 0, sizeof(struct cert_as)); as.type = CERT_AS_INHERIT; if (!append_as(p, &as)) goto out; rc = 1; goto out; case V_ASN1_SEQUENCE: break; default: warnx("%s: RFC 3779 section 3.2.3.2: ASIdentifierChoice: " "want ASN.1 sequence or null, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } /* This is RFC 3779 3.2.3.4. */ if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &sv, dsz)) == NULL) { cryptowarnx("%s: RFC 3779 section 3.2.3.2: ASIdentifierChoice: " "failed ASN.1 sequence parse", p->fn); goto out; } /* Accepts RFC 3779 3.2.3.6 or 3.2.3.7 (sequence). */ for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { tt = sk_ASN1_TYPE_value(seq, i); switch (tt->type) { case V_ASN1_INTEGER: if (!sbgp_asid(p, tt->value.integer)) goto out; break; case V_ASN1_SEQUENCE: d = tt->value.asn1_string->data; dsz = tt->value.asn1_string->length; if (!sbgp_asrange(p, d, dsz)) goto out; break; default: warnx("%s: RFC 3779 section 3.2.3.4: IPAddressOrRange: " "want ASN.1 sequence or integer, have %s (NID %d)", p->fn, ASN1_tag2str(tt->type), tt->type); goto out; } } rc = 1; out: ASN1_TYPE_free(t); sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); return rc; } /* * Parse RFC 6487 4.8.11 X509v3 extension, with syntax documented in RFC * 3779 starting in section 3.2. * Returns zero on failure, non-zero on success. */ static int sbgp_assysnum(struct parse *p, X509_EXTENSION *ext) { unsigned char *sv = NULL; const unsigned char *d; ASN1_SEQUENCE_ANY *seq = NULL, *sseq = NULL; const ASN1_TYPE *t; int dsz, rc = 0, i, ptag; long plen; if ((dsz = i2d_X509_EXTENSION(ext, &sv)) < 0) { cryptowarnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " "failed extension parse", p->fn); goto out; } /* Start with RFC 3779, section 3.2 top-level. */ d = sv; if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " "failed ASN.1 sequence parse", p->fn); goto out; } if (sk_ASN1_TYPE_num(seq) != 3) { warnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " "want 3 elements, 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: RFC 6487 section 4.8.11: autonomousSysNum: " "want ASN.1 object, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } /* FIXME: verify OID. */ t = sk_ASN1_TYPE_value(seq, 1); if (t->type != V_ASN1_BOOLEAN) { warnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " "want ASN.1 boolean, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } t = sk_ASN1_TYPE_value(seq, 2); if (t->type != V_ASN1_OCTET_STRING) { warnx("%s: RFC 6487 section 4.8.11: autonomousSysNum: " "want ASN.1 octet string, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } /* Within RFC 3779 3.2.3, check 3.2.3.1. */ d = t->value.octet_string->data; dsz = t->value.octet_string->length; if ((sseq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 3779 section 3.2.3.1: ASIdentifiers: " "failed ASN.1 sequence parse", p->fn); goto out; } /* Scan through for private 3.2.3.2 classes. */ for (i = 0; i < sk_ASN1_TYPE_num(sseq); i++) { t = sk_ASN1_TYPE_value(sseq, i); if (t->type != V_ASN1_OTHER) { warnx("%s: RFC 3779 section 3.2.3.1: ASIdentifiers: " "want ASN.1 explicit, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } /* Use the low-level ASN1_frame. */ d = t->value.asn1_string->data; dsz = t->value.asn1_string->length; if (!ASN1_frame(p, dsz, &d, &plen, &ptag)) goto out; /* Ignore bad AS identifiers and RDI entries. */ if (ptag > ASID_TYPE_MAX) { warnx("%s: RFC 3779 section 3.2.3.1: ASIdentifiers: " "unknown explicit tag 0x%02x", p->fn, ptag); goto out; } else if (ptag == ASID_TYPE_RDI) continue; if (!sbgp_asnum(p, d, plen)) goto out; } rc = 1; out: sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); sk_ASN1_TYPE_pop_free(sseq, ASN1_TYPE_free); free(sv); return rc; } /* * Parse RFC 3779 2.2.3.9 range of addresses. * Return zero on failure, non-zero on success. */ static int sbgp_addr_range(struct parse *p, struct cert_ip *ip, 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: RFC 3779 section 2.2.3.9: IPAddressRange: " "failed ASN.1 sequence parse", p->fn); goto out; } if (sk_ASN1_TYPE_num(seq) != 2) { warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " "want 2 elements, have %d", p->fn, sk_ASN1_TYPE_num(seq)); goto out; } t = sk_ASN1_TYPE_value(seq, 0); if (t->type != V_ASN1_BIT_STRING) { warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " "want ASN.1 bit string, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } if (!ip_addr_parse(t->value.bit_string, ip->afi, p->fn, &ip->range.min)) { warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " "invalid IP address", p->fn); goto out; } t = sk_ASN1_TYPE_value(seq, 1); if (t->type != V_ASN1_BIT_STRING) { warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " "want ASN.1 bit string, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } if (!ip_addr_parse(t->value.bit_string, ip->afi, p->fn, &ip->range.max)) { warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " "invalid IP address", p->fn); goto out; } if (!ip_cert_compose_ranges(ip)) { warnx("%s: RFC 3779 section 2.2.3.9: IPAddressRange: " "IP address range reversed", p->fn); return 0; } rc = append_ip(p, ip); out: sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); return rc; } /* * Parse an IP address or range, RFC 3779 2.2.3.7. * We don't constrain this parse (as specified in section 2.2.3.6) to * having any kind of order. * Returns zero on failure, non-zero on success. */ static int sbgp_addr_or_range(struct parse *p, struct cert_ip *ip, const unsigned char *d, size_t dsz) { struct cert_ip nip; ASN1_SEQUENCE_ANY *seq; const ASN1_TYPE *t; int i, rc = 0; if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 3779 section 2.2.3.7: IPAddressOrRange: " "failed ASN.1 sequence parse", p->fn); goto out; } /* Either RFC 3779 2.2.3.8 or 2.2.3.9. */ for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { nip = *ip; t = sk_ASN1_TYPE_value(seq, i); switch (t->type) { case V_ASN1_BIT_STRING: nip.type = CERT_IP_ADDR; if (!sbgp_addr(p, &nip, t->value.bit_string)) goto out; break; case V_ASN1_SEQUENCE: nip.type = CERT_IP_RANGE; d = t->value.asn1_string->data; dsz = t->value.asn1_string->length; if (!sbgp_addr_range(p, &nip, d, dsz)) goto out; break; default: warnx("%s: RFC 3779 section 2.2.3.7: IPAddressOrRange: " "want ASN.1 sequence or bit string, 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 a sequence of address families as in RFC 3779 sec. 2.2.3.2. * Ignore several stipulations of the RFC (2.2.3.3). * Namely, we don't require entries to be ordered in any way (type, AFI * or SAFI group, etc.). * This is because it doesn't matter for our purposes: we're going to * validate in the same way regardless. * Returns zero no failure, non-zero on success. */ static int sbgp_ipaddrfam(struct parse *p, const unsigned char *d, size_t dsz) { struct cert_ip ip; ASN1_SEQUENCE_ANY *seq; const ASN1_TYPE *t; int rc = 0; memset(&ip, 0, sizeof(struct cert_ip)); if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 3779 section 2.2.3.2: IPAddressFamily: " "failed ASN.1 sequence parse", p->fn); goto out; } if (sk_ASN1_TYPE_num(seq) != 2) { warnx("%s: RFC 3779 section 2.2.3.2: IPAddressFamily: " "want 2 elements, have %d", p->fn, sk_ASN1_TYPE_num(seq)); goto out; } /* Get address family, RFC 3779, 2.2.3.3. */ t = sk_ASN1_TYPE_value(seq, 0); if (t->type != V_ASN1_OCTET_STRING) { warnx("%s: RFC 3779 section 2.2.3.2: addressFamily: " "want ASN.1 octet string, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } if (!ip_addr_afi_parse(p->fn, t->value.octet_string, &ip.afi)) { warnx("%s: RFC 3779 section 2.2.3.2: addressFamily: " "invalid AFI", p->fn); goto out; } /* Either sequence or null (inherit), RFC 3779 sec. 2.2.3.4. */ t = sk_ASN1_TYPE_value(seq, 1); switch (t->type) { case V_ASN1_SEQUENCE: d = t->value.asn1_string->data; dsz = t->value.asn1_string->length; if (!sbgp_addr_or_range(p, &ip, d, dsz)) goto out; break; case V_ASN1_NULL: ip.type = CERT_IP_INHERIT; if (!append_ip(p, &ip)) goto out; break; default: warnx("%s: RFC 3779 section 2.2.3.2: IPAddressChoice: " "want ASN.1 sequence or null, 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 an sbgp-ipAddrBlock X509 extension, RFC 6487 4.8.10, with * syntax documented in RFC 3779 starting in section 2.2. * Returns zero on failure, non-zero on success. */ static int sbgp_ipaddrblk(struct parse *p, X509_EXTENSION *ext) { int dsz, rc = 0; unsigned char *sv = NULL; const unsigned char *d; ASN1_SEQUENCE_ANY *seq = NULL, *sseq = NULL; const ASN1_TYPE *t = NULL; int i; if ((dsz = i2d_X509_EXTENSION(ext, &sv)) < 0) { cryptowarnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " "failed extension parse", p->fn); goto out; } d = sv; if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " "failed ASN.1 sequence parse", p->fn); goto out; } if (sk_ASN1_TYPE_num(seq) != 3) { warnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " "want 3 elements, 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: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " "want ASN.1 object, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } /* FIXME: verify OID. */ t = sk_ASN1_TYPE_value(seq, 1); if (t->type != V_ASN1_BOOLEAN) { warnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " "want ASN.1 boolean, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } t = sk_ASN1_TYPE_value(seq, 2); if (t->type != V_ASN1_OCTET_STRING) { warnx("%s: RFC 6487 section 4.8.10: sbgp-ipAddrBlock: " "want ASN.1 octet string, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } /* The blocks sequence, RFC 3779 2.2.3.1. */ d = t->value.octet_string->data; dsz = t->value.octet_string->length; if ((sseq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { cryptowarnx("%s: RFC 3779 section 2.2.3.1: IPAddrBlocks: " "failed ASN.1 sequence parse", p->fn); goto out; } /* Each sequence element contains RFC 3779 sec. 2.2.3.2. */ for (i = 0; i < sk_ASN1_TYPE_num(sseq); i++) { t = sk_ASN1_TYPE_value(sseq, i); if (t->type != V_ASN1_SEQUENCE) { warnx("%s: RFC 3779 section 2.2.3.2: IPAddressFamily: " "want ASN.1 sequence, have %s (NID %d)", p->fn, ASN1_tag2str(t->type), t->type); goto out; } d = t->value.asn1_string->data; dsz = t->value.asn1_string->length; if (!sbgp_ipaddrfam(p, d, dsz)) goto out; } rc = 1; out: sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); sk_ASN1_TYPE_pop_free(sseq, ASN1_TYPE_free); free(sv); return rc; } /* * Parse and partially validate an RPKI X509 certificate (either a trust * anchor or a certificate) as defined in RFC 6487. * If "ta" is set, this is a trust anchor and must be self-signed. * Returns the parse results or NULL on failure ("xp" will be NULL too). * On success, free the pointer with cert_free() and make sure that "xp" * is also dereferenced. */ static struct cert * cert_parse_inner(X509 **xp, const char *fn, int ta) { int rc = 0, extsz, c; size_t i; X509 *x = NULL; X509_EXTENSION *ext = NULL; ASN1_OBJECT *obj; struct parse p; BIO *bio = NULL; FILE *f; *xp = NULL; if ((f = fopen(fn, "rb")) == NULL) { warn("%s", fn); return NULL; } if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) { if (verbose > 0) cryptowarnx("%s: BIO_new_file", fn); return NULL; } memset(&p, 0, sizeof(struct parse)); p.fn = fn; if ((p.res = calloc(1, sizeof(struct cert))) == NULL) err(1, NULL); if ((x = *xp = d2i_X509_bio(bio, NULL)) == NULL) { cryptowarnx("%s: d2i_X509_bio", p.fn); goto out; } /* Look for X509v3 extensions. */ if ((extsz = X509_get_ext_count(x)) < 0) cryptoerrx("X509_get_ext_count"); for (i = 0; i < (size_t)extsz; i++) { ext = X509_get_ext(x, i); assert(ext != NULL); obj = X509_EXTENSION_get_object(ext); assert(obj != NULL); c = 1; switch (OBJ_obj2nid(obj)) { case NID_sbgp_ipAddrBlock: c = sbgp_ipaddrblk(&p, ext); break; case NID_sbgp_autonomousSysNum: c = sbgp_assysnum(&p, ext); break; case NID_sinfo_access: c = sbgp_sia(&p, ext); break; case NID_crl_distribution_points: /* ignored here, handled later */ break; case NID_info_access: break; case NID_authority_key_identifier: break; case NID_subject_key_identifier: break; default: /* { char objn[64]; OBJ_obj2txt(objn, sizeof(objn), obj, 0); warnx("%s: ignoring %s (NID %d)", p.fn, objn, OBJ_obj2nid(obj)); } */ break; } if (c == 0) goto out; } p.res->aki = x509_get_aki(x, ta, p.fn); p.res->ski = x509_get_ski(x, p.fn); if (!ta) { p.res->aia = x509_get_aia(x, p.fn); p.res->crl = x509_get_crl(x, p.fn); } /* Validation on required fields. */ if (p.res->ski == NULL) { warnx("%s: RFC 6487 section 8.4.2: " "missing SKI", p.fn); goto out; } if (ta && p.res->aki != NULL && strcmp(p.res->aki, p.res->ski)) { warnx("%s: RFC 6487 section 8.4.2: " "trust anchor AKI, if specified, must match SKI", p.fn); goto out; } if (!ta && p.res->aki == NULL) { warnx("%s: RFC 6487 section 8.4.2: " "non-trust anchor missing AKI", p.fn); goto out; } else if (!ta && strcmp(p.res->aki, p.res->ski) == 0) { warnx("%s: RFC 6487 section 8.4.2: " "non-trust anchor AKI may not match SKI", p.fn); goto out; } if (!ta && p.res->aia == NULL) { warnx("%s: RFC 6487 section 8.4.7: " "non-trust anchor missing AIA", p.fn); goto out; } else if (ta && p.res->aia != NULL) { warnx("%s: RFC 6487 section 8.4.7: " "trust anchor must not have AIA", p.fn); goto out; } if (ta && p.res->crl != NULL) { warnx("%s: RFC 6487 section 8.4.2: " "trust anchor may not specify CRL resource", p.fn); goto out; } if (p.res->asz == 0 && p.res->ipsz == 0) { warnx("%s: RFC 6487 section 4.8.10 and 4.8.11: " "missing IP or AS resources", p.fn); goto out; } if (p.res->mft == NULL) { warnx("%s: RFC 6487 section 4.8.8: " "missing SIA", p.fn); goto out; } if (X509_up_ref(x) == 0) errx(1, "king bula"); p.res->x509 = x; rc = 1; out: BIO_free_all(bio); if (rc == 0) { cert_free(p.res); X509_free(x); *xp = NULL; } return (rc == 0) ? NULL : p.res; } struct cert * cert_parse(X509 **xp, const char *fn) { return cert_parse_inner(xp, fn, 0); } struct cert * ta_parse(X509 **xp, const char *fn, const unsigned char *pkey, size_t pkeysz) { EVP_PKEY *pk = NULL, *opk = NULL; struct cert *p; int rc = 0; if ((p = cert_parse_inner(xp, fn, 1)) == NULL) return NULL; if (pkey != NULL) { assert(*xp != NULL); pk = d2i_PUBKEY(NULL, &pkey, pkeysz); assert(pk != NULL); if ((opk = X509_get_pubkey(*xp)) == NULL) cryptowarnx("%s: RFC 6487 (trust anchor): " "missing pubkey", fn); else if (EVP_PKEY_cmp(pk, opk) != 1) cryptowarnx("%s: RFC 6487 (trust anchor): " "pubkey does not match TAL pubkey", fn); else rc = 1; EVP_PKEY_free(pk); EVP_PKEY_free(opk); } if (rc == 0) { cert_free(p); p = NULL; X509_free(*xp); *xp = NULL; } return p; } /* * Free parsed certificate contents. * Passing NULL is a noop. */ void cert_free(struct cert *p) { if (p == NULL) return; free(p->crl); free(p->repo); free(p->mft); free(p->notify); free(p->ips); free(p->as); free(p->aia); free(p->aki); free(p->ski); X509_free(p->x509); free(p); } static void cert_ip_buffer(struct ibuf *b, const struct cert_ip *p) { io_simple_buffer(b, &p->afi, sizeof(enum afi)); io_simple_buffer(b, &p->type, sizeof(enum cert_ip_type)); if (p->type != CERT_IP_INHERIT) { io_simple_buffer(b, &p->min, sizeof(p->min)); io_simple_buffer(b, &p->max, sizeof(p->max)); } if (p->type == CERT_IP_RANGE) ip_addr_range_buffer(b, &p->range); else if (p->type == CERT_IP_ADDR) ip_addr_buffer(b, &p->ip); } static void cert_as_buffer(struct ibuf *b, const struct cert_as *p) { io_simple_buffer(b, &p->type, sizeof(enum cert_as_type)); if (p->type == CERT_AS_RANGE) { io_simple_buffer(b, &p->range.min, sizeof(uint32_t)); io_simple_buffer(b, &p->range.max, sizeof(uint32_t)); } else if (p->type == CERT_AS_ID) io_simple_buffer(b, &p->id, sizeof(uint32_t)); } /* * Write certificate parsed content into buffer. * See cert_read() for the other side of the pipe. */ void cert_buffer(struct ibuf *b, const struct cert *p) { size_t i; io_simple_buffer(b, &p->valid, sizeof(int)); io_simple_buffer(b, &p->ipsz, sizeof(size_t)); for (i = 0; i < p->ipsz; i++) cert_ip_buffer(b, &p->ips[i]); io_simple_buffer(b, &p->asz, sizeof(size_t)); for (i = 0; i < p->asz; i++) cert_as_buffer(b, &p->as[i]); io_str_buffer(b, p->mft); io_str_buffer(b, p->notify); io_str_buffer(b, p->repo); io_str_buffer(b, p->crl); io_str_buffer(b, p->aia); io_str_buffer(b, p->aki); io_str_buffer(b, p->ski); } static void cert_ip_read(int fd, struct cert_ip *p) { io_simple_read(fd, &p->afi, sizeof(enum afi)); io_simple_read(fd, &p->type, sizeof(enum cert_ip_type)); if (p->type != CERT_IP_INHERIT) { io_simple_read(fd, &p->min, sizeof(p->min)); io_simple_read(fd, &p->max, sizeof(p->max)); } if (p->type == CERT_IP_RANGE) ip_addr_range_read(fd, &p->range); else if (p->type == CERT_IP_ADDR) ip_addr_read(fd, &p->ip); } static void cert_as_read(int fd, struct cert_as *p) { io_simple_read(fd, &p->type, sizeof(enum cert_as_type)); if (p->type == CERT_AS_RANGE) { io_simple_read(fd, &p->range.min, sizeof(uint32_t)); io_simple_read(fd, &p->range.max, sizeof(uint32_t)); } else if (p->type == CERT_AS_ID) io_simple_read(fd, &p->id, sizeof(uint32_t)); } /* * Allocate and read parsed certificate content from descriptor. * The pointer must be freed with cert_free(). * Always returns a valid pointer. */ struct cert * cert_read(int fd) { struct cert *p; size_t i; if ((p = calloc(1, sizeof(struct cert))) == NULL) err(1, NULL); io_simple_read(fd, &p->valid, sizeof(int)); io_simple_read(fd, &p->ipsz, sizeof(size_t)); p->ips = calloc(p->ipsz, sizeof(struct cert_ip)); if (p->ips == NULL) err(1, NULL); for (i = 0; i < p->ipsz; i++) cert_ip_read(fd, &p->ips[i]); io_simple_read(fd, &p->asz, sizeof(size_t)); p->as = calloc(p->asz, sizeof(struct cert_as)); if (p->as == NULL) err(1, NULL); for (i = 0; i < p->asz; i++) cert_as_read(fd, &p->as[i]); io_str_read(fd, &p->mft); assert(p->mft); io_str_read(fd, &p->notify); io_str_read(fd, &p->repo); io_str_read(fd, &p->crl); io_str_read(fd, &p->aia); io_str_read(fd, &p->aki); io_str_read(fd, &p->ski); assert(p->ski); return p; } struct auth * auth_find(struct auth_tree *auths, const char *aki) { struct auth a; struct cert c; /* we look up the cert where the ski == aki */ c.ski = (char *)aki; a.cert = &c; return RB_FIND(auth_tree, auths, &a); } static inline int authcmp(struct auth *a, struct auth *b) { return strcmp(a->cert->ski, b->cert->ski); } RB_GENERATE(auth_tree, auth, entry, authcmp);