/* $OpenBSD: spl.c,v 1.7 2024/11/13 12:51:04 tb Exp $ */ /* * Copyright (c) 2024 Job Snijders * Copyright (c) 2022 Theo Buehler * 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 #include "extern.h" extern ASN1_OBJECT *spl_oid; /* * Types and templates for the SPL eContent. */ ASN1_ITEM_EXP AddressFamilyPrefixes_it; ASN1_ITEM_EXP SignedPrefixList_it; DECLARE_STACK_OF(ASN1_BIT_STRING); typedef struct { ASN1_OCTET_STRING *addressFamily; STACK_OF(ASN1_BIT_STRING) *addressPrefixes; } AddressFamilyPrefixes; DECLARE_STACK_OF(AddressFamilyPrefixes); ASN1_SEQUENCE(AddressFamilyPrefixes) = { ASN1_SIMPLE(AddressFamilyPrefixes, addressFamily, ASN1_OCTET_STRING), ASN1_SEQUENCE_OF(AddressFamilyPrefixes, addressPrefixes, ASN1_BIT_STRING), } ASN1_SEQUENCE_END(AddressFamilyPrefixes); #ifndef DEFINE_STACK_OF #define sk_ASN1_BIT_STRING_num(st) SKM_sk_num(ASN1_BIT_STRING, (st)) #define sk_ASN1_BIT_STRING_value(st, i) SKM_sk_value(ASN1_BIT_STRING, (st), (i)) #define sk_AddressFamilyPrefixes_num(st) \ SKM_sk_num(AddressFamilyPrefixes, (st)) #define sk_AddressFamilyPrefixes_value(st, i) \ SKM_sk_value(AddressFamilyPrefixes, (st), (i)) #endif typedef struct { ASN1_INTEGER *version; ASN1_INTEGER *asid; STACK_OF(AddressFamilyPrefixes) *prefixBlocks; } SignedPrefixList; ASN1_SEQUENCE(SignedPrefixList) = { ASN1_EXP_OPT(SignedPrefixList, version, ASN1_INTEGER, 0), ASN1_SIMPLE(SignedPrefixList, asid, ASN1_INTEGER), ASN1_SEQUENCE_OF(SignedPrefixList, prefixBlocks, AddressFamilyPrefixes) } ASN1_SEQUENCE_END(SignedPrefixList); DECLARE_ASN1_FUNCTIONS(SignedPrefixList); IMPLEMENT_ASN1_FUNCTIONS(SignedPrefixList); /* * Comparator to help sorting elements in SPL prefixBlocks and VSPs. * Returns -1 if 'a' should precede 'b', 1 if 'b' should precede 'a', * or '0' if a and b are equal. */ static int prefix_cmp(enum afi afi, const struct ip_addr *a, const struct ip_addr *b) { int cmp; switch (afi) { case AFI_IPV4: cmp = memcmp(&a->addr, &b->addr, 4); if (cmp < 0) return -1; if (cmp > 0) return 1; break; case AFI_IPV6: cmp = memcmp(&a->addr, &b->addr, 16); if (cmp < 0) return -1; if (cmp > 0) return 1; break; default: break; } if (a->prefixlen < b->prefixlen) return -1; if (a->prefixlen > b->prefixlen) return 1; return 0; } /* * Parses the eContent section of a SPL file, * draft-ietf-sidrops-rpki-prefixlist-02 section 3. * Returns zero on failure, non-zero on success. */ static int spl_parse_econtent(const char *fn, struct spl *spl, const unsigned char *d, size_t dsz) { const unsigned char *oder; SignedPrefixList *spl_asn1; const AddressFamilyPrefixes *afp; const STACK_OF(ASN1_BIT_STRING) *prefixes; const ASN1_BIT_STRING *prefix_asn1; int num_afps, num_prefixes; enum afi afi; struct ip_addr ip_addr; struct spl_pfx *prefix; int ipv4_seen = 0, ipv6_seen = 0; int i, j, rc = 0; oder = d; if ((spl_asn1 = d2i_SignedPrefixList(NULL, &d, dsz)) == NULL) { warnx("%s: RFC 6482 section 3: failed to parse " "SignedPrefixList", fn); goto out; } if (d != oder + dsz) { warnx("%s: %td bytes trailing garbage in eContent", fn, oder + dsz - d); goto out; } if (!valid_econtent_version(fn, spl_asn1->version, 0)) goto out; if (!as_id_parse(spl_asn1->asid, &spl->asid)) { warnx("%s: asid: malformed AS identifier", fn); goto out; } num_afps = sk_AddressFamilyPrefixes_num(spl_asn1->prefixBlocks); if (num_afps < 0 || num_afps > 2) { warnx("%s: unexpected number of AddressFamilyAddressPrefixes" "(got %d, expected 0, 1, or 2)", fn, num_afps); goto out; } for (i = 0; i < num_afps; i++) { struct ip_addr *prev_ip_addr = NULL; afp = sk_AddressFamilyPrefixes_value(spl_asn1->prefixBlocks, i); prefixes = afp->addressPrefixes; num_prefixes = sk_ASN1_BIT_STRING_num(afp->addressPrefixes); if (num_prefixes == 0) { warnx("%s: empty AddressFamilyAddressPrefixes", fn); goto out; } if (spl->num_prefixes + num_prefixes >= MAX_IP_SIZE) { warnx("%s: too many addressPrefixes entries", fn); goto out; } if (!ip_addr_afi_parse(fn, afp->addressFamily, &afi)) goto out; switch (afi) { case AFI_IPV4: if (ipv4_seen++ > 0) { warnx("%s: addressFamilyIPv4 appeared twice", fn); goto out; } if (ipv6_seen > 0) { warnx("%s: invalid sorting, IPv6 before IPv4", fn); goto out; } break; case AFI_IPV6: if (ipv6_seen++ > 0) { warnx("%s: addressFamilyIPv6 appeared twice", fn); goto out; } } spl->prefixes = recallocarray(spl->prefixes, spl->num_prefixes, spl->num_prefixes + num_prefixes, sizeof(spl->prefixes[0])); if (spl->prefixes == NULL) err(1, NULL); for (j = 0; j < num_prefixes; j++) { prefix_asn1 = sk_ASN1_BIT_STRING_value(prefixes, j); if (!ip_addr_parse(prefix_asn1, afi, fn, &ip_addr)) goto out; if (j > 0 && prefix_cmp(afi, prev_ip_addr, &ip_addr) != -1) { warnx("%s: invalid addressPrefixes sorting", fn); goto out; } prefix = &spl->prefixes[spl->num_prefixes++]; prefix->prefix = ip_addr; prefix->afi = afi; prev_ip_addr = &prefix->prefix; } } rc = 1; out: SignedPrefixList_free(spl_asn1); return rc; } /* * Parse a full Signed Prefix List file. * Returns the SPL, or NULL if the object was malformed. */ struct spl * spl_parse(X509 **x509, const char *fn, int talid, const unsigned char *der, size_t len) { struct spl *spl; size_t cmsz; unsigned char *cms; struct cert *cert = NULL; time_t signtime = 0; int rc = 0; cms = cms_parse_validate(x509, fn, der, len, spl_oid, &cmsz, &signtime); if (cms == NULL) return NULL; if ((spl = calloc(1, sizeof(*spl))) == NULL) err(1, NULL); spl->signtime = signtime; if (!x509_get_aia(*x509, fn, &spl->aia)) goto out; if (!x509_get_aki(*x509, fn, &spl->aki)) goto out; if (!x509_get_sia(*x509, fn, &spl->sia)) goto out; if (!x509_get_ski(*x509, fn, &spl->ski)) goto out; if (spl->aia == NULL || spl->aki == NULL || spl->sia == NULL || spl->ski == NULL) { warnx("%s: RFC 6487 section 4.8: " "missing AIA, AKI, SIA, or SKI X509 extension", fn); goto out; } if (!x509_get_notbefore(*x509, fn, &spl->notbefore)) goto out; if (!x509_get_notafter(*x509, fn, &spl->notafter)) goto out; if (!spl_parse_econtent(fn, spl, cms, cmsz)) goto out; if (x509_any_inherits(*x509)) { warnx("%s: inherit elements not allowed in EE cert", fn); goto out; } if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL) goto out; if (cert->num_ases == 0) { warnx("%s: AS Resources extension missing", fn); goto out; } if (cert->num_ips > 0) { warnx("%s: superfluous IP Resources extension present", fn); goto out; } /* * If the SPL isn't valid, we accept it anyway and depend upon * the code around spl_read() to check the "valid" field itself. */ spl->valid = valid_spl(fn, cert, spl); rc = 1; out: if (rc == 0) { spl_free(spl); spl = NULL; X509_free(*x509); *x509 = NULL; } cert_free(cert); free(cms); return spl; } void spl_free(struct spl *s) { if (s == NULL) return; free(s->aia); free(s->aki); free(s->sia); free(s->ski); free(s->prefixes); free(s); } /* * Serialize parsed SPL content. * See spl_read() for reader. */ void spl_buffer(struct ibuf *b, const struct spl *s) { io_simple_buffer(b, &s->valid, sizeof(s->valid)); io_simple_buffer(b, &s->asid, sizeof(s->asid)); io_simple_buffer(b, &s->talid, sizeof(s->talid)); io_simple_buffer(b, &s->num_prefixes, sizeof(s->num_prefixes)); io_simple_buffer(b, &s->expires, sizeof(s->expires)); io_simple_buffer(b, s->prefixes, s->num_prefixes * sizeof(s->prefixes[0])); io_str_buffer(b, s->aia); io_str_buffer(b, s->aki); io_str_buffer(b, s->ski); } /* * Read parsed SPL content from descriptor. * See spl_buffer() for writer. * Result must be passed to spl_free(). */ struct spl * spl_read(struct ibuf *b) { struct spl *s; if ((s = calloc(1, sizeof(struct spl))) == NULL) err(1, NULL); io_read_buf(b, &s->valid, sizeof(s->valid)); io_read_buf(b, &s->asid, sizeof(s->asid)); io_read_buf(b, &s->talid, sizeof(s->talid)); io_read_buf(b, &s->num_prefixes, sizeof(s->num_prefixes)); io_read_buf(b, &s->expires, sizeof(s->expires)); if (s->num_prefixes > 0) { if ((s->prefixes = calloc(s->num_prefixes, sizeof(s->prefixes[0]))) == NULL) err(1, NULL); io_read_buf(b, s->prefixes, s->num_prefixes * sizeof(s->prefixes[0])); } io_read_str(b, &s->aia); io_read_str(b, &s->aki); io_read_str(b, &s->ski); assert(s->aia && s->aki && s->ski); return s; } static int spl_pfx_cmp(const struct spl_pfx *a, const struct spl_pfx *b) { if (a->afi > b->afi) return 1; if (a->afi < b->afi) return -1; return prefix_cmp(a->afi, &a->prefix, &b->prefix); } static void insert_vsp(struct vsp *vsp, size_t idx, struct spl_pfx *pfx) { if (idx < vsp->num_prefixes) memmove(vsp->prefixes + idx + 1, vsp->prefixes + idx, (vsp->num_prefixes - idx) * sizeof(vsp->prefixes[0])); vsp->prefixes[idx] = *pfx; vsp->num_prefixes++; } /* * Add each prefix in the SPL into the VSP tree. * Updates "vsps" to be the number of VSPs and "uniqs" to be the unique * number of prefixes. */ void spl_insert_vsps(struct vsp_tree *tree, struct spl *spl, struct repo *rp) { struct vsp *vsp, *found; size_t i, j; int cmp; if ((vsp = calloc(1, sizeof(*vsp))) == NULL) err(1, NULL); vsp->asid = spl->asid; vsp->talid = spl->talid; vsp->expires = spl->expires; if (rp != NULL) vsp->repoid = repo_id(rp); if ((found = RB_INSERT(vsp_tree, tree, vsp)) != NULL) { /* already exists */ if (found->expires < vsp->expires) { /* adjust unique count */ repo_stat_inc(repo_byid(found->repoid), found->talid, RTYPE_SPL, STYPE_DEC_UNIQUE); found->expires = vsp->expires; found->talid = vsp->talid; found->repoid = vsp->repoid; repo_stat_inc(rp, vsp->talid, RTYPE_SPL, STYPE_UNIQUE); } free(vsp); vsp = found; } else repo_stat_inc(rp, vsp->talid, RTYPE_SPL, STYPE_UNIQUE); repo_stat_inc(rp, spl->talid, RTYPE_SPL, STYPE_TOTAL); /* merge content of multiple SPLs */ vsp->prefixes = reallocarray(vsp->prefixes, vsp->num_prefixes + spl->num_prefixes, sizeof(vsp->prefixes[0])); if (vsp->prefixes == NULL) err(1, NULL); /* * Merge all data from the new SPL at hand into 'vsp': loop over * all SPL->pfxs, and insert them in the right place in * vsp->prefixes while keeping the order of the array. */ for (i = 0, j = 0; i < spl->num_prefixes; ) { cmp = -1; if (j == vsp->num_prefixes || (cmp = spl_pfx_cmp(&spl->prefixes[i], &vsp->prefixes[j])) < 0) { insert_vsp(vsp, j, &spl->prefixes[i]); i++; } else if (cmp == 0) i++; if (j < vsp->num_prefixes) j++; } } /* * Comparison function for the RB tree */ static inline int vspcmp(const struct vsp *a, const struct vsp *b) { if (a->asid > b->asid) return 1; if (a->asid < b->asid) return -1; return 0; } RB_GENERATE(vsp_tree, vsp, entry, vspcmp);