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