[BACK]Return to roa.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / rpki-client

File: [local] / src / usr.sbin / rpki-client / roa.c (download)

Revision 1.77, Wed Feb 21 09:17:06 2024 UTC (3 months, 2 weeks ago) by tb
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5
Changes since 1.76: +43 -53 lines

rpki-client: remove the remaining struct parse

With the exception of mft.c where there is an additional boolean, this
struct carries a file name and a result. This means functions having
struct parse in the signature can't be shared between files, which has
been annoying. Simply pass file name and necessary info directly as a
function parameter and add a small dance to handle the boolean in mft.c.

ok job

/*	$OpenBSD: roa.c,v 1.77 2024/02/21 09:17:06 tb Exp $ */
/*
 * 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 "extern.h"

extern ASN1_OBJECT	*roa_oid;

/*
 * Types and templates for the ROA eContent, RFC 6482, section 3.
 */

ASN1_ITEM_EXP ROAIPAddress_it;
ASN1_ITEM_EXP ROAIPAddressFamily_it;
ASN1_ITEM_EXP RouteOriginAttestation_it;

typedef struct {
	ASN1_BIT_STRING		*address;
	ASN1_INTEGER		*maxLength;
} ROAIPAddress;

DECLARE_STACK_OF(ROAIPAddress);

typedef struct {
	ASN1_OCTET_STRING	*addressFamily;
	STACK_OF(ROAIPAddress)	*addresses;
} ROAIPAddressFamily;

DECLARE_STACK_OF(ROAIPAddressFamily);

#ifndef DEFINE_STACK_OF
#define sk_ROAIPAddress_num(st)		SKM_sk_num(ROAIPAddress, (st))
#define sk_ROAIPAddress_value(st, i)	SKM_sk_value(ROAIPAddress, (st), (i))

#define sk_ROAIPAddressFamily_num(st)	SKM_sk_num(ROAIPAddressFamily, (st))
#define sk_ROAIPAddressFamily_value(st, i) \
    SKM_sk_value(ROAIPAddressFamily, (st), (i))
#endif

typedef struct {
	ASN1_INTEGER			*version;
	ASN1_INTEGER			*asid;
	STACK_OF(ROAIPAddressFamily)	*ipAddrBlocks;
} RouteOriginAttestation;

ASN1_SEQUENCE(ROAIPAddress) = {
	ASN1_SIMPLE(ROAIPAddress, address, ASN1_BIT_STRING),
	ASN1_OPT(ROAIPAddress, maxLength, ASN1_INTEGER),
} ASN1_SEQUENCE_END(ROAIPAddress);

ASN1_SEQUENCE(ROAIPAddressFamily) = {
	ASN1_SIMPLE(ROAIPAddressFamily, addressFamily, ASN1_OCTET_STRING),
	ASN1_SEQUENCE_OF(ROAIPAddressFamily, addresses, ROAIPAddress),
} ASN1_SEQUENCE_END(ROAIPAddressFamily);

ASN1_SEQUENCE(RouteOriginAttestation) = {
	ASN1_EXP_OPT(RouteOriginAttestation, version, ASN1_INTEGER, 0),
	ASN1_SIMPLE(RouteOriginAttestation, asid, ASN1_INTEGER),
	ASN1_SEQUENCE_OF(RouteOriginAttestation, ipAddrBlocks,
	    ROAIPAddressFamily),
} ASN1_SEQUENCE_END(RouteOriginAttestation);

DECLARE_ASN1_FUNCTIONS(RouteOriginAttestation);
IMPLEMENT_ASN1_FUNCTIONS(RouteOriginAttestation);

/*
 * Parses the eContent section of an ROA file, RFC 6482, section 3.
 * Returns zero on failure, non-zero on success.
 */
static int
roa_parse_econtent(const char *fn, struct roa *roa, const unsigned char *d,
    size_t dsz)
{
	const unsigned char		*oder;
	RouteOriginAttestation		*roa_asn1;
	const ROAIPAddressFamily	*addrfam;
	const STACK_OF(ROAIPAddress)	*addrs;
	int				 addrsz, ipv4_seen = 0, ipv6_seen = 0;
	enum afi			 afi;
	const ROAIPAddress		*addr;
	uint64_t			 maxlen;
	struct ip_addr			 ipaddr;
	struct roa_ip			*res;
	int				 ipaddrblocksz;
	int				 i, j, rc = 0;

	oder = d;
	if ((roa_asn1 = d2i_RouteOriginAttestation(NULL, &d, dsz)) == NULL) {
		warnx("%s: RFC 6482 section 3: failed to parse "
		    "RouteOriginAttestation", 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, roa_asn1->version, 0))
		goto out;

	if (!as_id_parse(roa_asn1->asid, &roa->asid)) {
		warnx("%s: RFC 6482 section 3.2: asID: "
		    "malformed AS identifier", fn);
		goto out;
	}

	ipaddrblocksz = sk_ROAIPAddressFamily_num(roa_asn1->ipAddrBlocks);
	if (ipaddrblocksz != 1 && ipaddrblocksz != 2) {
		warnx("%s: draft-rfc6482bis: unexpected number of ipAddrBlocks "
		    "(got %d, expected 1 or 2)", fn, ipaddrblocksz);
		goto out;
	}

	for (i = 0; i < ipaddrblocksz; i++) {
		addrfam = sk_ROAIPAddressFamily_value(roa_asn1->ipAddrBlocks,
		    i);
		addrs = addrfam->addresses;
		addrsz = sk_ROAIPAddress_num(addrs);

		if (!ip_addr_afi_parse(fn, addrfam->addressFamily, &afi)) {
			warnx("%s: RFC 6482 section 3.3: addressFamily: "
			    "invalid", fn);
			goto out;
		}

		switch (afi) {
		case AFI_IPV4:
			if (ipv4_seen++ > 0) {
				warnx("%s: RFC 6482bis section 4.3.2: "
				    "IPv4 appears twice", fn);
				goto out;
			}
			break;
		case AFI_IPV6:
			if (ipv6_seen++ > 0) {
				warnx("%s: RFC 6482bis section 4.3.2: "
				    "IPv6 appears twice", fn);
				goto out;
			}
			break;
		}

		if (addrsz == 0) {
			warnx("%s: RFC 6482bis, section 4.3.2: "
			    "empty ROAIPAddressFamily", fn);
			goto out;
		}

		if (roa->ipsz + addrsz >= MAX_IP_SIZE) {
			warnx("%s: too many ROAIPAddress entries: limit %d",
			    fn, MAX_IP_SIZE);
			goto out;
		}
		roa->ips = recallocarray(roa->ips, roa->ipsz,
		    roa->ipsz + addrsz, sizeof(struct roa_ip));
		if (roa->ips == NULL)
			err(1, NULL);

		for (j = 0; j < addrsz; j++) {
			addr = sk_ROAIPAddress_value(addrs, j);

			if (!ip_addr_parse(addr->address, afi, fn, &ipaddr)) {
				warnx("%s: RFC 6482 section 3.3: address: "
				    "invalid IP address", fn);
				goto out;
			}
			maxlen = ipaddr.prefixlen;

			if (addr->maxLength != NULL) {
				if (!ASN1_INTEGER_get_uint64(&maxlen,
				    addr->maxLength)) {
					warnx("%s: RFC 6482 section 3.2: "
					    "ASN1_INTEGER_get_uint64 failed",
					    fn);
					goto out;
				}
				if (ipaddr.prefixlen > maxlen) {
					warnx("%s: prefixlen (%d) larger than "
					    "maxLength (%llu)", fn,
					    ipaddr.prefixlen,
					    (unsigned long long)maxlen);
					goto out;
				}
				if (maxlen > ((afi == AFI_IPV4) ? 32 : 128)) {
					warnx("%s: maxLength (%llu) too large",
					    fn, (unsigned long long)maxlen);
					goto out;
				}
			}

			res = &roa->ips[roa->ipsz++];
			res->addr = ipaddr;
			res->afi = afi;
			res->maxlength = maxlen;
			ip_roa_compose_ranges(res);
		}
	}

	rc = 1;
 out:
	RouteOriginAttestation_free(roa_asn1);
	return rc;
}

/*
 * Parse a full RFC 6482 file.
 * Returns the ROA or NULL if the document was malformed.
 */
struct roa *
roa_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
    size_t len)
{
	struct roa	*roa;
	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, roa_oid, &cmsz, &signtime);
	if (cms == NULL)
		return NULL;

	if ((roa = calloc(1, sizeof(struct roa))) == NULL)
		err(1, NULL);
	roa->signtime = signtime;

	if (!x509_get_aia(*x509, fn, &roa->aia))
		goto out;
	if (!x509_get_aki(*x509, fn, &roa->aki))
		goto out;
	if (!x509_get_sia(*x509, fn, &roa->sia))
		goto out;
	if (!x509_get_ski(*x509, fn, &roa->ski))
		goto out;
	if (roa->aia == NULL || roa->aki == NULL || roa->sia == NULL ||
	    roa->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, &roa->notbefore))
		goto out;
	if (!x509_get_notafter(*x509, fn, &roa->notafter))
		goto out;

	if (!roa_parse_econtent(fn, roa, 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: superfluous AS Resources extension present", fn);
		goto out;
	}

	/*
	 * If the ROA isn't valid, we accept it anyway and depend upon
	 * the code around roa_read() to check the "valid" field itself.
	 */
	roa->valid = valid_roa(fn, cert, roa);

	rc = 1;
out:
	if (rc == 0) {
		roa_free(roa);
		roa = NULL;
		X509_free(*x509);
		*x509 = NULL;
	}
	cert_free(cert);
	free(cms);
	return roa;
}

/*
 * Free an ROA pointer.
 * Safe to call with NULL.
 */
void
roa_free(struct roa *p)
{

	if (p == NULL)
		return;
	free(p->aia);
	free(p->aki);
	free(p->sia);
	free(p->ski);
	free(p->ips);
	free(p);
}

/*
 * Serialise parsed ROA content.
 * See roa_read() for reader.
 */
void
roa_buffer(struct ibuf *b, const struct roa *p)
{
	io_simple_buffer(b, &p->valid, sizeof(p->valid));
	io_simple_buffer(b, &p->asid, sizeof(p->asid));
	io_simple_buffer(b, &p->talid, sizeof(p->talid));
	io_simple_buffer(b, &p->ipsz, sizeof(p->ipsz));
	io_simple_buffer(b, &p->expires, sizeof(p->expires));

	io_simple_buffer(b, p->ips, p->ipsz * sizeof(p->ips[0]));

	io_str_buffer(b, p->aia);
	io_str_buffer(b, p->aki);
	io_str_buffer(b, p->ski);
}

/*
 * Read parsed ROA content from descriptor.
 * See roa_buffer() for writer.
 * Result must be passed to roa_free().
 */
struct roa *
roa_read(struct ibuf *b)
{
	struct roa	*p;

	if ((p = calloc(1, sizeof(struct roa))) == NULL)
		err(1, NULL);

	io_read_buf(b, &p->valid, sizeof(p->valid));
	io_read_buf(b, &p->asid, sizeof(p->asid));
	io_read_buf(b, &p->talid, sizeof(p->talid));
	io_read_buf(b, &p->ipsz, sizeof(p->ipsz));
	io_read_buf(b, &p->expires, sizeof(p->expires));

	if ((p->ips = calloc(p->ipsz, sizeof(struct roa_ip))) == NULL)
		err(1, NULL);
	io_read_buf(b, p->ips, p->ipsz * sizeof(p->ips[0]));

	io_read_str(b, &p->aia);
	io_read_str(b, &p->aki);
	io_read_str(b, &p->ski);
	assert(p->aia && p->aki && p->ski);

	return p;
}

/*
 * Add each IP address in the ROA into the VRP tree.
 * Updates "vrps" to be the number of VRPs and "uniqs" to be the unique
 * number of addresses.
 */
void
roa_insert_vrps(struct vrp_tree *tree, struct roa *roa, struct repo *rp)
{
	struct vrp	*v, *found;
	size_t		 i;

	for (i = 0; i < roa->ipsz; i++) {
		if ((v = malloc(sizeof(*v))) == NULL)
			err(1, NULL);
		v->afi = roa->ips[i].afi;
		v->addr = roa->ips[i].addr;
		v->maxlength = roa->ips[i].maxlength;
		v->asid = roa->asid;
		v->talid = roa->talid;
		if (rp != NULL)
			v->repoid = repo_id(rp);
		else
			v->repoid = 0;
		v->expires = roa->expires;

		/*
		 * Check if a similar VRP already exists in the tree.
		 * If the found VRP expires sooner, update it to this
		 * ROAs later expiry moment.
		 */
		if ((found = RB_INSERT(vrp_tree, tree, v)) != NULL) {
			/* already exists */
			if (found->expires < v->expires) {
				/* update found with preferred data */
				/* adjust unique count */
				repo_stat_inc(repo_byid(found->repoid),
				    found->talid, RTYPE_ROA, STYPE_DEC_UNIQUE);
				found->expires = v->expires;
				found->talid = v->talid;
				found->repoid = v->repoid;
				repo_stat_inc(rp, v->talid, RTYPE_ROA,
				    STYPE_UNIQUE);
			}
			free(v);
		} else
			repo_stat_inc(rp, v->talid, RTYPE_ROA, STYPE_UNIQUE);

		repo_stat_inc(rp, roa->talid, RTYPE_ROA, STYPE_TOTAL);
	}
}

static inline int
vrpcmp(struct vrp *a, struct vrp *b)
{
	int rv;

	if (a->afi > b->afi)
		return 1;
	if (a->afi < b->afi)
		return -1;
	switch (a->afi) {
	case AFI_IPV4:
		rv = memcmp(&a->addr.addr, &b->addr.addr, 4);
		if (rv)
			return rv;
		break;
	case AFI_IPV6:
		rv = memcmp(&a->addr.addr, &b->addr.addr, 16);
		if (rv)
			return rv;
		break;
	default:
		break;
	}
	/* a smaller prefixlen is considered bigger, e.g. /8 vs /10 */
	if (a->addr.prefixlen < b->addr.prefixlen)
		return 1;
	if (a->addr.prefixlen > b->addr.prefixlen)
		return -1;
	if (a->maxlength < b->maxlength)
		return 1;
	if (a->maxlength > b->maxlength)
		return -1;

	if (a->asid > b->asid)
		return 1;
	if (a->asid < b->asid)
		return -1;

	return 0;
}

RB_GENERATE(vrp_tree, vrp, entry, vrpcmp);