1/*	$OpenBSD: aspa.c,v 1.30 2024/04/08 14:02:13 tb Exp $ */
2/*
3 * Copyright (c) 2022 Job Snijders <job@fastly.com>
4 * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
5 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <assert.h>
21#include <err.h>
22#include <stdint.h>
23#include <stdlib.h>
24#include <string.h>
25#include <unistd.h>
26
27#include <openssl/asn1.h>
28#include <openssl/asn1t.h>
29#include <openssl/stack.h>
30#include <openssl/safestack.h>
31#include <openssl/x509.h>
32
33#include "extern.h"
34
35extern ASN1_OBJECT	*aspa_oid;
36
37/*
38 * Types and templates for ASPA eContent draft-ietf-sidrops-aspa-profile-15
39 */
40
41ASN1_ITEM_EXP ASProviderAttestation_it;
42
43typedef struct {
44	ASN1_INTEGER		*version;
45	ASN1_INTEGER		*customerASID;
46	STACK_OF(ASN1_INTEGER)	*providers;
47} ASProviderAttestation;
48
49ASN1_SEQUENCE(ASProviderAttestation) = {
50	ASN1_EXP_OPT(ASProviderAttestation, version, ASN1_INTEGER, 0),
51	ASN1_SIMPLE(ASProviderAttestation, customerASID, ASN1_INTEGER),
52	ASN1_SEQUENCE_OF(ASProviderAttestation, providers, ASN1_INTEGER),
53} ASN1_SEQUENCE_END(ASProviderAttestation);
54
55DECLARE_ASN1_FUNCTIONS(ASProviderAttestation);
56IMPLEMENT_ASN1_FUNCTIONS(ASProviderAttestation);
57
58/*
59 * Parse the ProviderASSet sequence.
60 * Return zero on failure, non-zero on success.
61 */
62static int
63aspa_parse_providers(const char *fn, struct aspa *aspa,
64    const STACK_OF(ASN1_INTEGER) *providers)
65{
66	const ASN1_INTEGER	*pa;
67	uint32_t		 provider;
68	size_t			 providersz, i;
69
70	if ((providersz = sk_ASN1_INTEGER_num(providers)) == 0) {
71		warnx("%s: ASPA: ProviderASSet needs at least one entry", fn);
72		return 0;
73	}
74
75	if (providersz >= MAX_ASPA_PROVIDERS) {
76		warnx("%s: ASPA: too many providers (more than %d)", fn,
77		    MAX_ASPA_PROVIDERS);
78		return 0;
79	}
80
81	aspa->providers = calloc(providersz, sizeof(provider));
82	if (aspa->providers == NULL)
83		err(1, NULL);
84
85	for (i = 0; i < providersz; i++) {
86		pa = sk_ASN1_INTEGER_value(providers, i);
87
88		memset(&provider, 0, sizeof(provider));
89
90		if (!as_id_parse(pa, &provider)) {
91			warnx("%s: ASPA: malformed ProviderAS", fn);
92			return 0;
93		}
94
95		if (aspa->custasid == provider) {
96			warnx("%s: ASPA: CustomerASID can't also be Provider",
97			    fn);
98			return 0;
99		}
100
101		if (i > 0) {
102			if (aspa->providers[i - 1] > provider) {
103				warnx("%s: ASPA: invalid ProviderASSet order",
104				    fn);
105				return 0;
106			}
107			if (aspa->providers[i - 1] == provider) {
108				warnx("%s: ASPA: duplicate ProviderAS", fn);
109				return 0;
110			}
111		}
112
113		aspa->providers[aspa->providersz++] = provider;
114	}
115
116	return 1;
117}
118
119/*
120 * Parse the eContent of an ASPA file.
121 * Returns zero on failure, non-zero on success.
122 */
123static int
124aspa_parse_econtent(const char *fn, struct aspa *aspa, const unsigned char *d,
125    size_t dsz)
126{
127	const unsigned char	*oder;
128	ASProviderAttestation	*aspa_asn1;
129	int			 rc = 0;
130
131	oder = d;
132	if ((aspa_asn1 = d2i_ASProviderAttestation(NULL, &d, dsz)) == NULL) {
133		warnx("%s: ASPA: failed to parse ASProviderAttestation", fn);
134		goto out;
135	}
136	if (d != oder + dsz) {
137		warnx("%s: %td bytes trailing garbage in eContent", fn,
138		    oder + dsz - d);
139		goto out;
140	}
141
142	if (!valid_econtent_version(fn, aspa_asn1->version, 1))
143		goto out;
144
145	if (!as_id_parse(aspa_asn1->customerASID, &aspa->custasid)) {
146		warnx("%s: malformed CustomerASID", fn);
147		goto out;
148	}
149
150	if (!aspa_parse_providers(fn, aspa, aspa_asn1->providers))
151		goto out;
152
153	rc = 1;
154 out:
155	ASProviderAttestation_free(aspa_asn1);
156	return rc;
157}
158
159/*
160 * Parse a full ASPA file.
161 * Returns the payload or NULL if the file was malformed.
162 */
163struct aspa *
164aspa_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
165    size_t len)
166{
167	struct aspa	*aspa;
168	size_t		 cmsz;
169	unsigned char	*cms;
170	struct cert	*cert = NULL;
171	time_t		 signtime = 0;
172	int		 rc = 0;
173
174	cms = cms_parse_validate(x509, fn, der, len, aspa_oid, &cmsz,
175	    &signtime);
176	if (cms == NULL)
177		return NULL;
178
179	if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
180		err(1, NULL);
181
182	aspa->signtime = signtime;
183
184	if (!x509_get_aia(*x509, fn, &aspa->aia))
185		goto out;
186	if (!x509_get_aki(*x509, fn, &aspa->aki))
187		goto out;
188	if (!x509_get_sia(*x509, fn, &aspa->sia))
189		goto out;
190	if (!x509_get_ski(*x509, fn, &aspa->ski))
191		goto out;
192	if (aspa->aia == NULL || aspa->aki == NULL || aspa->sia == NULL ||
193	    aspa->ski == NULL) {
194		warnx("%s: RFC 6487 section 4.8: "
195		    "missing AIA, AKI, SIA, or SKI X509 extension", fn);
196		goto out;
197	}
198
199	if (X509_get_ext_by_NID(*x509, NID_sbgp_ipAddrBlock, -1) != -1) {
200		warnx("%s: superfluous IP Resources extension present", fn);
201		goto out;
202	}
203
204	if (!x509_get_notbefore(*x509, fn, &aspa->notbefore))
205		goto out;
206	if (!x509_get_notafter(*x509, fn, &aspa->notafter))
207		goto out;
208
209	if (x509_any_inherits(*x509)) {
210		warnx("%s: inherit elements not allowed in EE cert", fn);
211		goto out;
212	}
213
214	if (!aspa_parse_econtent(fn, aspa, cms, cmsz))
215		goto out;
216
217	if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
218		goto out;
219
220	aspa->valid = valid_aspa(fn, cert, aspa);
221
222	rc = 1;
223 out:
224	if (rc == 0) {
225		aspa_free(aspa);
226		aspa = NULL;
227		X509_free(*x509);
228		*x509 = NULL;
229	}
230	cert_free(cert);
231	free(cms);
232	return aspa;
233}
234
235/*
236 * Free an ASPA pointer.
237 * Safe to call with NULL.
238 */
239void
240aspa_free(struct aspa *p)
241{
242	if (p == NULL)
243		return;
244
245	free(p->aia);
246	free(p->aki);
247	free(p->sia);
248	free(p->ski);
249	free(p->providers);
250	free(p);
251}
252
253/*
254 * Serialise parsed ASPA content.
255 * See aspa_read() for the reader on the other side.
256 */
257void
258aspa_buffer(struct ibuf *b, const struct aspa *p)
259{
260	io_simple_buffer(b, &p->valid, sizeof(p->valid));
261	io_simple_buffer(b, &p->custasid, sizeof(p->custasid));
262	io_simple_buffer(b, &p->talid, sizeof(p->talid));
263	io_simple_buffer(b, &p->expires, sizeof(p->expires));
264
265	io_simple_buffer(b, &p->providersz, sizeof(size_t));
266	io_simple_buffer(b, p->providers,
267	    p->providersz * sizeof(p->providers[0]));
268
269	io_str_buffer(b, p->aia);
270	io_str_buffer(b, p->aki);
271	io_str_buffer(b, p->ski);
272}
273
274/*
275 * Read parsed ASPA content from descriptor.
276 * See aspa_buffer() for writer.
277 * Result must be passed to aspa_free().
278 */
279struct aspa *
280aspa_read(struct ibuf *b)
281{
282	struct aspa	*p;
283
284	if ((p = calloc(1, sizeof(struct aspa))) == NULL)
285		err(1, NULL);
286
287	io_read_buf(b, &p->valid, sizeof(p->valid));
288	io_read_buf(b, &p->custasid, sizeof(p->custasid));
289	io_read_buf(b, &p->talid, sizeof(p->talid));
290	io_read_buf(b, &p->expires, sizeof(p->expires));
291
292	io_read_buf(b, &p->providersz, sizeof(size_t));
293	if ((p->providers = calloc(p->providersz, sizeof(uint32_t))) == NULL)
294		err(1, NULL);
295	io_read_buf(b, p->providers, p->providersz * sizeof(p->providers[0]));
296
297	io_read_str(b, &p->aia);
298	io_read_str(b, &p->aki);
299	io_read_str(b, &p->ski);
300	assert(p->aia && p->aki && p->ski);
301
302	return p;
303}
304
305/*
306 * Insert a new uint32_t at index idx in the struct vap v.
307 * All elements in the provider array from idx are moved up by one
308 * to make space for the new element.
309 */
310static void
311insert_vap(struct vap *v, uint32_t idx, uint32_t *p)
312{
313	if (idx < v->providersz)
314		memmove(v->providers + idx + 1, v->providers + idx,
315		    (v->providersz - idx) * sizeof(*v->providers));
316	v->providers[idx] = *p;
317	v->providersz++;
318}
319
320/*
321 * Add each ProviderAS entry into the Validated ASPA Providers (VAP) tree.
322 * Duplicated entries are merged.
323 */
324void
325aspa_insert_vaps(char *fn, struct vap_tree *tree, struct aspa *aspa,
326    struct repo *rp)
327{
328	struct vap	*v, *found;
329	size_t		 i, j;
330
331	if ((v = calloc(1, sizeof(*v))) == NULL)
332		err(1, NULL);
333	v->custasid = aspa->custasid;
334	v->talid = aspa->talid;
335	if (rp != NULL)
336		v->repoid = repo_id(rp);
337	else
338		v->repoid = 0;
339	v->expires = aspa->expires;
340
341	if ((found = RB_INSERT(vap_tree, tree, v)) != NULL) {
342		if (found->overflowed) {
343			free(v);
344			return;
345		}
346		if (found->expires > v->expires) {
347			/* decrement found */
348			repo_stat_inc(repo_byid(found->repoid), found->talid,
349			    RTYPE_ASPA, STYPE_DEC_UNIQUE);
350			found->expires = v->expires;
351			found->talid = v->talid;
352			found->repoid = v->repoid;
353			repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE);
354		}
355		free(v);
356		v = found;
357	} else
358		repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE);
359
360	repo_stat_inc(rp, aspa->talid, RTYPE_ASPA, STYPE_TOTAL);
361
362	v->providers = reallocarray(v->providers,
363	    v->providersz + aspa->providersz, sizeof(*v->providers));
364	if (v->providers == NULL)
365		err(1, NULL);
366
367	/*
368	 * Merge all data from aspa into v: loop over all aspa providers,
369	 * insert them in the right place in v->providers while keeping the
370	 * order of the providers array.
371	 */
372	for (i = 0, j = 0; i < aspa->providersz; ) {
373		if (j == v->providersz ||
374		    aspa->providers[i] < v->providers[j]) {
375			/* merge provider from aspa into v */
376			repo_stat_inc(rp, v->talid, RTYPE_ASPA,
377			    STYPE_PROVIDERS);
378			insert_vap(v, j, &aspa->providers[i]);
379			i++;
380		} else if (aspa->providers[i] == v->providers[j])
381			i++;
382
383		if (j < v->providersz)
384			j++;
385	}
386
387	if (v->providersz >= MAX_ASPA_PROVIDERS) {
388		v->overflowed = 1;
389		free(v->providers);
390		v->providers = NULL;
391		v->providersz = 0;
392		repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_OVERFLOW);
393		warnx("%s: too many providers for ASPA Customer ASID %u "
394		    "(more than %d)", fn, v->custasid, MAX_ASPA_PROVIDERS);
395		return;
396	}
397}
398
399static inline int
400vapcmp(struct vap *a, struct vap *b)
401{
402	if (a->custasid > b->custasid)
403		return 1;
404	if (a->custasid < b->custasid)
405		return -1;
406
407	return 0;
408}
409
410RB_GENERATE(vap_tree, vap, entry, vapcmp);
411