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);