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