src/usr.sbin/rpki-client/spl.c

488 lines
12 KiB
C

/* $OpenBSD: spl.c,v 1.2 2024/02/22 19:29:55 tb Exp $ */
/*
* Copyright (c) 2024 Job Snijders <job@fastly.com>
* Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
* 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/asn1.h>
#include <openssl/asn1t.h>
#include <openssl/stack.h>
#include <openssl/safestack.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#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 afpsz, prefixesz;
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;
}
afpsz = sk_AddressFamilyPrefixes_num(spl_asn1->prefixBlocks);
if (afpsz < 0 || afpsz > 2) {
warnx("%s: unexpected number of AddressFamilyAddressPrefixes"
"(got %d, expected 0, 1, or 2)", fn, afpsz);
goto out;
}
for (i = 0; i < afpsz; i++) {
struct ip_addr *prev_ip_addr = NULL;
afp = sk_AddressFamilyPrefixes_value(spl_asn1->prefixBlocks, i);
prefixes = afp->addressPrefixes;
prefixesz = sk_ASN1_BIT_STRING_num(afp->addressPrefixes);
if (prefixesz == 0) {
warnx("%s: empty AddressFamilyAddressPrefixes", fn);
goto out;
}
if (spl->pfxsz + prefixesz >= 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->pfxs = recallocarray(spl->pfxs, spl->pfxsz,
spl->pfxsz + prefixesz, sizeof(struct spl_pfx));
if (spl->pfxs == NULL)
err(1, NULL);
for (j = 0; j < prefixesz; 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->pfxs[spl->pfxsz++];
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->asz == 0) {
warnx("%s: AS Resources extension missing", fn);
goto out;
}
if (cert->ipsz > 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->pfxs);
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->pfxsz, sizeof(s->pfxsz));
io_simple_buffer(b, &s->expires, sizeof(s->expires));
io_simple_buffer(b, s->pfxs, s->pfxsz * sizeof(s->pfxs[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->pfxsz, sizeof(s->pfxsz));
io_read_buf(b, &s->expires, sizeof(s->expires));
if ((s->pfxs = calloc(s->pfxsz, sizeof(struct spl_pfx))) == NULL)
err(1, NULL);
io_read_buf(b, s->pfxs, s->pfxsz * sizeof(s->pfxs[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->prefixesz)
memmove(vsp->prefixes + idx + 1, vsp->prefixes + idx,
(vsp->prefixesz - idx) * sizeof(*vsp->prefixes));
vsp->prefixes[idx] = *pfx;
vsp->prefixesz++;
}
/*
* 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->prefixesz + spl->pfxsz, sizeof(struct spl_pfx));
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->pfxsz; ) {
cmp = -1;
if (j == vsp->prefixesz ||
(cmp = spl_pfx_cmp(&spl->pfxs[i], &vsp->prefixes[j])) < 0) {
insert_vsp(vsp, j, &spl->pfxs[i]);
i++;
} else if (cmp == 0)
i++;
if (j < vsp->prefixesz)
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);