xref: /openbsd/usr.sbin/rpki-client/aspa.c (revision f814cda1)
1 /*	$OpenBSD: aspa.c,v 1.31 2024/11/05 18:09:16 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 
35 extern ASN1_OBJECT	*aspa_oid;
36 
37 /*
38  * Types and templates for ASPA eContent draft-ietf-sidrops-aspa-profile-15
39  */
40 
41 ASN1_ITEM_EXP ASProviderAttestation_it;
42 
43 typedef struct {
44 	ASN1_INTEGER		*version;
45 	ASN1_INTEGER		*customerASID;
46 	STACK_OF(ASN1_INTEGER)	*providers;
47 } ASProviderAttestation;
48 
49 ASN1_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 
55 DECLARE_ASN1_FUNCTIONS(ASProviderAttestation);
56 IMPLEMENT_ASN1_FUNCTIONS(ASProviderAttestation);
57 
58 /*
59  * Parse the ProviderASSet sequence.
60  * Return zero on failure, non-zero on success.
61  */
62 static int
aspa_parse_providers(const char * fn,struct aspa * aspa,const STACK_OF (ASN1_INTEGER)* providers)63 aspa_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  */
123 static int
aspa_parse_econtent(const char * fn,struct aspa * aspa,const unsigned char * d,size_t dsz)124 aspa_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  */
163 struct aspa *
aspa_parse(X509 ** x509,const char * fn,int talid,const unsigned char * der,size_t len)164 aspa_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  */
239 void
aspa_free(struct aspa * p)240 aspa_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  */
257 void
aspa_buffer(struct ibuf * b,const struct aspa * p)258 aspa_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  */
279 struct aspa *
aspa_read(struct ibuf * b)280 aspa_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 
294 	if (p->providersz > 0) {
295 		if ((p->providers = calloc(p->providersz,
296 		    sizeof(p->providers[0]))) == NULL)
297 			err(1, NULL);
298 		io_read_buf(b, p->providers,
299 		    p->providersz * sizeof(p->providers[0]));
300 	}
301 
302 	io_read_str(b, &p->aia);
303 	io_read_str(b, &p->aki);
304 	io_read_str(b, &p->ski);
305 	assert(p->aia && p->aki && p->ski);
306 
307 	return p;
308 }
309 
310 /*
311  * Insert a new uint32_t at index idx in the struct vap v.
312  * All elements in the provider array from idx are moved up by one
313  * to make space for the new element.
314  */
315 static void
insert_vap(struct vap * v,uint32_t idx,uint32_t * p)316 insert_vap(struct vap *v, uint32_t idx, uint32_t *p)
317 {
318 	if (idx < v->providersz)
319 		memmove(v->providers + idx + 1, v->providers + idx,
320 		    (v->providersz - idx) * sizeof(*v->providers));
321 	v->providers[idx] = *p;
322 	v->providersz++;
323 }
324 
325 /*
326  * Add each ProviderAS entry into the Validated ASPA Providers (VAP) tree.
327  * Duplicated entries are merged.
328  */
329 void
aspa_insert_vaps(char * fn,struct vap_tree * tree,struct aspa * aspa,struct repo * rp)330 aspa_insert_vaps(char *fn, struct vap_tree *tree, struct aspa *aspa,
331     struct repo *rp)
332 {
333 	struct vap	*v, *found;
334 	size_t		 i, j;
335 
336 	if ((v = calloc(1, sizeof(*v))) == NULL)
337 		err(1, NULL);
338 	v->custasid = aspa->custasid;
339 	v->talid = aspa->talid;
340 	if (rp != NULL)
341 		v->repoid = repo_id(rp);
342 	else
343 		v->repoid = 0;
344 	v->expires = aspa->expires;
345 
346 	if ((found = RB_INSERT(vap_tree, tree, v)) != NULL) {
347 		if (found->overflowed) {
348 			free(v);
349 			return;
350 		}
351 		if (found->expires > v->expires) {
352 			/* decrement found */
353 			repo_stat_inc(repo_byid(found->repoid), found->talid,
354 			    RTYPE_ASPA, STYPE_DEC_UNIQUE);
355 			found->expires = v->expires;
356 			found->talid = v->talid;
357 			found->repoid = v->repoid;
358 			repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE);
359 		}
360 		free(v);
361 		v = found;
362 	} else
363 		repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE);
364 
365 	repo_stat_inc(rp, aspa->talid, RTYPE_ASPA, STYPE_TOTAL);
366 
367 	v->providers = reallocarray(v->providers,
368 	    v->providersz + aspa->providersz, sizeof(*v->providers));
369 	if (v->providers == NULL)
370 		err(1, NULL);
371 
372 	/*
373 	 * Merge all data from aspa into v: loop over all aspa providers,
374 	 * insert them in the right place in v->providers while keeping the
375 	 * order of the providers array.
376 	 */
377 	for (i = 0, j = 0; i < aspa->providersz; ) {
378 		if (j == v->providersz ||
379 		    aspa->providers[i] < v->providers[j]) {
380 			/* merge provider from aspa into v */
381 			repo_stat_inc(rp, v->talid, RTYPE_ASPA,
382 			    STYPE_PROVIDERS);
383 			insert_vap(v, j, &aspa->providers[i]);
384 			i++;
385 		} else if (aspa->providers[i] == v->providers[j])
386 			i++;
387 
388 		if (j < v->providersz)
389 			j++;
390 	}
391 
392 	if (v->providersz >= MAX_ASPA_PROVIDERS) {
393 		v->overflowed = 1;
394 		free(v->providers);
395 		v->providers = NULL;
396 		v->providersz = 0;
397 		repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_OVERFLOW);
398 		warnx("%s: too many providers for ASPA Customer ASID %u "
399 		    "(more than %d)", fn, v->custasid, MAX_ASPA_PROVIDERS);
400 		return;
401 	}
402 }
403 
404 static inline int
vapcmp(struct vap * a,struct vap * b)405 vapcmp(struct vap *a, struct vap *b)
406 {
407 	if (a->custasid > b->custasid)
408 		return 1;
409 	if (a->custasid < b->custasid)
410 		return -1;
411 
412 	return 0;
413 }
414 
415 RB_GENERATE(vap_tree, vap, entry, vapcmp);
416