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
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 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 */
310 static void
insert_vap(struct vap * v,uint32_t idx,uint32_t * p)311 insert_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 */
324 void
aspa_insert_vaps(char * fn,struct vap_tree * tree,struct aspa * aspa,struct repo * rp)325 aspa_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
399 static inline int
vapcmp(struct vap * a,struct vap * b)400 vapcmp(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
410 RB_GENERATE(vap_tree, vap, entry, vapcmp);
411