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