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

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

Revision 1.34, 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, HEAD
Changes since 1.33: +61 -70 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: rsc.c,v 1.34 2024/02/21 09:17:06 tb Exp $ */
/*
 * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
 * Copyright (c) 2022 Job Snijders <job@fastly.com>
 * 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 <err.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <openssl/asn1.h>
#include <openssl/asn1t.h>
#include <openssl/safestack.h>
#include <openssl/stack.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

#include "extern.h"

extern ASN1_OBJECT	*rsc_oid;

/*
 * Types and templates for RSC eContent - RFC 9323
 */

ASN1_ITEM_EXP ConstrainedASIdentifiers_it;
ASN1_ITEM_EXP ConstrainedIPAddressFamily_it;
ASN1_ITEM_EXP ConstrainedIPAddrBlocks_it;
ASN1_ITEM_EXP FileNameAndHash_it;
ASN1_ITEM_EXP ResourceBlock_it;
ASN1_ITEM_EXP RpkiSignedChecklist_it;

typedef struct {
	ASIdOrRanges		*asnum;
} ConstrainedASIdentifiers;

ASN1_SEQUENCE(ConstrainedASIdentifiers) = {
	ASN1_EXP_SEQUENCE_OF(ConstrainedASIdentifiers, asnum, ASIdOrRange, 0),
} ASN1_SEQUENCE_END(ConstrainedASIdentifiers);

typedef struct {
	ASN1_OCTET_STRING		*addressFamily;
	STACK_OF(IPAddressOrRange)	*addressesOrRanges;
} ConstrainedIPAddressFamily;

ASN1_SEQUENCE(ConstrainedIPAddressFamily) = {
	ASN1_SIMPLE(ConstrainedIPAddressFamily, addressFamily,
	    ASN1_OCTET_STRING),
	ASN1_SEQUENCE_OF(ConstrainedIPAddressFamily, addressesOrRanges,
	    IPAddressOrRange),
} ASN1_SEQUENCE_END(ConstrainedIPAddressFamily);

typedef STACK_OF(ConstrainedIPAddressFamily) ConstrainedIPAddrBlocks;
DECLARE_STACK_OF(ConstrainedIPAddressFamily);

ASN1_ITEM_TEMPLATE(ConstrainedIPAddrBlocks) =
	ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, ConstrainedIPAddrBlocks,
	    ConstrainedIPAddressFamily)
ASN1_ITEM_TEMPLATE_END(ConstrainedIPAddrBlocks);

typedef struct {
	ConstrainedASIdentifiers	*asID;
	ConstrainedIPAddrBlocks		*ipAddrBlocks;
} ResourceBlock;

ASN1_SEQUENCE(ResourceBlock) = {
	ASN1_EXP_OPT(ResourceBlock, asID, ConstrainedASIdentifiers, 0),
	ASN1_EXP_SEQUENCE_OF_OPT(ResourceBlock, ipAddrBlocks,
	    ConstrainedIPAddressFamily, 1)
} ASN1_SEQUENCE_END(ResourceBlock);

typedef struct {
	ASN1_IA5STRING		*fileName;
	ASN1_OCTET_STRING	*hash;
} FileNameAndHash;

DECLARE_STACK_OF(FileNameAndHash);

#ifndef DEFINE_STACK_OF
#define sk_ConstrainedIPAddressFamily_num(sk) \
    SKM_sk_num(ConstrainedIPAddressFamily, (sk))
#define sk_ConstrainedIPAddressFamily_value(sk, i) \
    SKM_sk_value(ConstrainedIPAddressFamily, (sk), (i))

#define sk_FileNameAndHash_num(sk)	SKM_sk_num(FileNameAndHash, (sk))
#define sk_FileNameAndHash_value(sk, i)	SKM_sk_value(FileNameAndHash, (sk), (i))
#endif

ASN1_SEQUENCE(FileNameAndHash) = {
	ASN1_OPT(FileNameAndHash, fileName, ASN1_IA5STRING),
	ASN1_SIMPLE(FileNameAndHash, hash, ASN1_OCTET_STRING),
} ASN1_SEQUENCE_END(FileNameAndHash);

typedef struct {
	ASN1_INTEGER			*version;
	ResourceBlock			*resources;
	X509_ALGOR			*digestAlgorithm;
	STACK_OF(FileNameAndHash)	*checkList;
} RpkiSignedChecklist;

ASN1_SEQUENCE(RpkiSignedChecklist) = {
	ASN1_EXP_OPT(RpkiSignedChecklist, version, ASN1_INTEGER, 0),
	ASN1_SIMPLE(RpkiSignedChecklist, resources, ResourceBlock),
	ASN1_SIMPLE(RpkiSignedChecklist, digestAlgorithm, X509_ALGOR),
	ASN1_SEQUENCE_OF(RpkiSignedChecklist, checkList, FileNameAndHash),
} ASN1_SEQUENCE_END(RpkiSignedChecklist);

DECLARE_ASN1_FUNCTIONS(RpkiSignedChecklist);
IMPLEMENT_ASN1_FUNCTIONS(RpkiSignedChecklist);

/*
 * Parse asID (inside ResourceBlock)
 * Return 0 on failure.
 */
static int
rsc_parse_aslist(const char *fn, struct rsc *rsc,
    const ConstrainedASIdentifiers *asids)
{
	int	 i, asz;

	if (asids == NULL)
		return 1;

	if ((asz = sk_ASIdOrRange_num(asids->asnum)) == 0) {
		warnx("%s: RSC asID empty", fn);
		return 0;
	}

	if (asz >= MAX_AS_SIZE) {
		warnx("%s: too many AS number entries: limit %d",
		    fn, MAX_AS_SIZE);
		return 0;
	}

	rsc->as = calloc(asz, sizeof(struct cert_as));
	if (rsc->as == NULL)
		err(1, NULL);

	for (i = 0; i < asz; i++) {
		const ASIdOrRange *aor;

		aor = sk_ASIdOrRange_value(asids->asnum, i);

		switch (aor->type) {
		case ASIdOrRange_id:
			if (!sbgp_as_id(fn, rsc->as, &rsc->asz, aor->u.id))
				return 0;
			break;
		case ASIdOrRange_range:
			if (!sbgp_as_range(fn, rsc->as, &rsc->asz,
			    aor->u.range))
				return 0;
			break;
		default:
			warnx("%s: RSC AsList: unknown type %d", fn, aor->type);
			return 0;
		}
	}

	return 1;
}

static int
rsc_parse_iplist(const char *fn, struct rsc *rsc,
    const ConstrainedIPAddrBlocks *ipAddrBlocks)
{
	const ConstrainedIPAddressFamily	*af;
	const IPAddressOrRanges			*aors;
	const IPAddressOrRange			*aor;
	size_t					 ipsz;
	enum afi				 afi;
	int					 i, j;

	if (ipAddrBlocks == NULL)
		return 1;

	if (sk_ConstrainedIPAddressFamily_num(ipAddrBlocks) == 0) {
		warnx("%s: RSC ipAddrBlocks empty", fn);
		return 0;
	}

	for (i = 0; i < sk_ConstrainedIPAddressFamily_num(ipAddrBlocks); i++) {
		af = sk_ConstrainedIPAddressFamily_value(ipAddrBlocks, i);
		aors = af->addressesOrRanges;

		ipsz = rsc->ipsz + sk_IPAddressOrRange_num(aors);
		if (ipsz >= MAX_IP_SIZE) {
			warnx("%s: too many IP address entries: limit %d",
			    fn, MAX_IP_SIZE);
			return 0;
		}

		rsc->ips = recallocarray(rsc->ips, rsc->ipsz, ipsz,
		    sizeof(struct cert_ip));
		if (rsc->ips == NULL)
			err(1, NULL);

		if (!ip_addr_afi_parse(fn, af->addressFamily, &afi)) {
			warnx("%s: RSC: invalid AFI", fn);
			return 0;
		}

		for (j = 0; j < sk_IPAddressOrRange_num(aors); j++) {
			aor = sk_IPAddressOrRange_value(aors, j);
			switch (aor->type) {
			case IPAddressOrRange_addressPrefix:
				if (!sbgp_addr(fn, rsc->ips,
				    &rsc->ipsz, afi, aor->u.addressPrefix))
					return 0;
				break;
			case IPAddressOrRange_addressRange:
				if (!sbgp_addr_range(fn, rsc->ips,
				    &rsc->ipsz, afi, aor->u.addressRange))
					return 0;
				break;
			default:
				warnx("%s: RFC 3779: IPAddressOrRange: "
				    "unknown type %d", fn, aor->type);
				return 0;
			}
		}
	}

	return 1;
}

static int
rsc_check_digesttype(const char *fn, struct rsc *rsc, const X509_ALGOR *alg)
{
	const ASN1_OBJECT	*obj;
	int			 type, nid;

	X509_ALGOR_get0(&obj, &type, NULL, alg);

	if (type != V_ASN1_UNDEF) {
		warnx("%s: RSC DigestAlgorithmIdentifier unexpected parameters:"
		    " %d", fn, type);
		return 0;
	}

	if ((nid = OBJ_obj2nid(obj)) != NID_sha256) {
		warnx("%s: RSC DigestAlgorithmIdentifier: want SHA256, have %s"
		    " (NID %d)", fn, ASN1_tag2str(nid), nid);
		return 0;
	}

	return 1;
}

/*
 * Parse the FileNameAndHash sequence, RFC 9323, section 4.4.
 * Return zero on failure, non-zero on success.
 */
static int
rsc_parse_checklist(const char *fn, struct rsc *rsc,
    const STACK_OF(FileNameAndHash) *checkList)
{
	FileNameAndHash		*fh;
	ASN1_IA5STRING		*fileName;
	struct rscfile		*file;
	size_t			 sz, i;

	if ((sz = sk_FileNameAndHash_num(checkList)) == 0) {
		warnx("%s: RSC checkList needs at least one entry", fn);
		return 0;
	}

	if (sz >= MAX_CHECKLIST_ENTRIES) {
		warnx("%s: %zu exceeds checklist entry limit (%d)", fn, sz,
		    MAX_CHECKLIST_ENTRIES);
		return 0;
	}

	rsc->files = calloc(sz, sizeof(struct rscfile));
	if (rsc->files == NULL)
		err(1, NULL);
	rsc->filesz = sz;

	for (i = 0; i < sz; i++) {
		fh = sk_FileNameAndHash_value(checkList, i);

		file = &rsc->files[i];

		if (fh->hash->length != SHA256_DIGEST_LENGTH) {
			warnx("%s: RSC Digest: invalid SHA256 length", fn);
			return 0;
		}
		memcpy(file->hash, fh->hash->data, SHA256_DIGEST_LENGTH);

		if ((fileName = fh->fileName) == NULL)
			continue;

		if (!valid_filename(fileName->data, fileName->length)) {
			warnx("%s: RSC FileNameAndHash: bad filename", fn);
			return 0;
		}

		file->filename = strndup(fileName->data, fileName->length);
		if (file->filename == NULL)
			err(1, NULL);
	}

	return 1;
}

/*
 * Parses the eContent segment of an RSC file
 * RFC 9323, section 4
 * Returns zero on failure, non-zero on success.
 */
static int
rsc_parse_econtent(const char *fn, struct rsc *rsc, const unsigned char *d,
    size_t dsz)
{
	const unsigned char	*oder;
	RpkiSignedChecklist	*rsc_asn1;
	ResourceBlock		*resources;
	int			 rc = 0;

	/*
	 * RFC 9323 section 4
	 */

	oder = d;
	if ((rsc_asn1 = d2i_RpkiSignedChecklist(NULL, &d, dsz)) == NULL) {
		warnx("%s: RSC: failed to parse RpkiSignedChecklist", 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, rsc_asn1->version, 0))
		goto out;

	resources = rsc_asn1->resources;
	if (resources->asID == NULL && resources->ipAddrBlocks == NULL) {
		warnx("%s: RSC: one of asID or ipAddrBlocks must be present",
		    fn);
		goto out;
	}

	if (!rsc_parse_aslist(fn, rsc, resources->asID))
		goto out;

	if (!rsc_parse_iplist(fn, rsc, resources->ipAddrBlocks))
		goto out;

	if (!rsc_check_digesttype(fn, rsc, rsc_asn1->digestAlgorithm))
		goto out;

	if (!rsc_parse_checklist(fn, rsc, rsc_asn1->checkList))
		goto out;

	rc = 1;
 out:
	RpkiSignedChecklist_free(rsc_asn1);
	return rc;
}

/*
 * Parse a full RFC 9323 file.
 * Returns the RSC or NULL if the object was malformed.
 */
struct rsc *
rsc_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
    size_t len)
{
	struct rsc		*rsc;
	unsigned char		*cms;
	size_t			 cmsz;
	struct cert		*cert = NULL;
	time_t			 signtime = 0;
	int			 rc = 0;

	cms = cms_parse_validate(x509, fn, der, len, rsc_oid, &cmsz,
	    &signtime);
	if (cms == NULL)
		return NULL;

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

	if (!x509_get_aia(*x509, fn, &rsc->aia))
		goto out;
	if (!x509_get_aki(*x509, fn, &rsc->aki))
		goto out;
	if (!x509_get_ski(*x509, fn, &rsc->ski))
		goto out;
	if (rsc->aia == NULL || rsc->aki == NULL || rsc->ski == NULL) {
		warnx("%s: RFC 6487 section 4.8: "
		    "missing AIA, AKI or SKI X509 extension", fn);
		goto out;
	}

	if (!x509_get_notbefore(*x509, fn, &rsc->notbefore))
		goto out;
	if (!x509_get_notafter(*x509, fn, &rsc->notafter))
		goto out;

	if (X509_get_ext_by_NID(*x509, NID_sinfo_access, -1) != -1) {
		warnx("%s: RSC: EE cert must not have an SIA extension", fn);
		goto out;
	}

	if (x509_any_inherits(*x509)) {
		warnx("%s: inherit elements not allowed in EE cert", fn);
		goto out;
	}

	if (!rsc_parse_econtent(fn, rsc, cms, cmsz))
		goto out;

	if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
		goto out;

	rsc->valid = valid_rsc(fn, cert, rsc);

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

/*
 * Free an RSC pointer.
 * Safe to call with NULL.
 */
void
rsc_free(struct rsc *p)
{
	size_t	i;

	if (p == NULL)
		return;

	for (i = 0; i < p->filesz; i++)
		free(p->files[i].filename);

	free(p->aia);
	free(p->aki);
	free(p->ski);
	free(p->ips);
	free(p->as);
	free(p->files);
	free(p);
}