1 /* $OpenBSD: aspa.c,v 1.17 2023/04/26 16:32:41 claudio 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 /* 36 * Parse results and data of the ASPA object. 37 */ 38 struct parse { 39 const char *fn; /* ASPA file name */ 40 struct aspa *res; /* results */ 41 }; 42 43 extern ASN1_OBJECT *aspa_oid; 44 45 /* 46 * Types and templates for ASPA eContent draft-ietf-sidrops-aspa-profile-08 47 */ 48 49 typedef struct { 50 ASN1_INTEGER *providerASID; 51 ASN1_OCTET_STRING *afiLimit; 52 } ProviderAS; 53 54 DECLARE_STACK_OF(ProviderAS); 55 56 #ifndef DEFINE_STACK_OF 57 #define sk_ProviderAS_num(sk) SKM_sk_num(ProviderAS, (sk)) 58 #define sk_ProviderAS_value(sk, i) SKM_sk_value(ProviderAS, (sk), (i)) 59 #endif 60 61 ASN1_SEQUENCE(ProviderAS) = { 62 ASN1_SIMPLE(ProviderAS, providerASID, ASN1_INTEGER), 63 ASN1_OPT(ProviderAS, afiLimit, ASN1_OCTET_STRING), 64 } ASN1_SEQUENCE_END(ProviderAS); 65 66 typedef struct { 67 ASN1_INTEGER *version; 68 ASN1_INTEGER *customerASID; 69 STACK_OF(ProviderAS) *providers; 70 } ASProviderAttestation; 71 72 ASN1_SEQUENCE(ASProviderAttestation) = { 73 ASN1_EXP_OPT(ASProviderAttestation, version, ASN1_INTEGER, 0), 74 ASN1_SIMPLE(ASProviderAttestation, customerASID, ASN1_INTEGER), 75 ASN1_SEQUENCE_OF(ASProviderAttestation, providers, ProviderAS), 76 } ASN1_SEQUENCE_END(ASProviderAttestation); 77 78 DECLARE_ASN1_FUNCTIONS(ASProviderAttestation); 79 IMPLEMENT_ASN1_FUNCTIONS(ASProviderAttestation); 80 81 /* 82 * Parse the ProviderASSet sequence. 83 * Return zero on failure, non-zero on success. 84 */ 85 static int 86 aspa_parse_providers(struct parse *p, const STACK_OF(ProviderAS) *providers) 87 { 88 ProviderAS *pa; 89 struct aspa_provider provider; 90 size_t providersz, i; 91 92 if ((providersz = sk_ProviderAS_num(providers)) == 0) { 93 warnx("%s: ASPA: ProviderASSet needs at least one entry", 94 p->fn); 95 return 0; 96 } 97 98 if (providersz >= MAX_ASPA_PROVIDERS) { 99 warnx("%s: ASPA: too many providers (more than %d)", p->fn, 100 MAX_ASPA_PROVIDERS); 101 return 0; 102 } 103 104 p->res->providers = calloc(providersz, sizeof(provider)); 105 if (p->res->providers == NULL) 106 err(1, NULL); 107 108 for (i = 0; i < providersz; i++) { 109 pa = sk_ProviderAS_value(providers, i); 110 111 memset(&provider, 0, sizeof(provider)); 112 113 if (!as_id_parse(pa->providerASID, &provider.as)) { 114 warnx("%s: ASPA: malformed ProviderAS", p->fn); 115 return 0; 116 } 117 118 if (p->res->custasid == provider.as) { 119 warnx("%s: ASPA: CustomerASID can't also be Provider", 120 p->fn); 121 return 0; 122 } 123 124 if (i > 0) { 125 if (p->res->providers[i - 1].as > provider.as) { 126 warnx("%s: ASPA: invalid ProviderASSet order", 127 p->fn); 128 return 0; 129 } 130 if (p->res->providers[i - 1].as == provider.as) { 131 warnx("%s: ASPA: duplicate ProviderAS", p->fn); 132 return 0; 133 } 134 } 135 136 if (pa->afiLimit != NULL && !ip_addr_afi_parse(p->fn, 137 pa->afiLimit, &provider.afi)) { 138 warnx("%s: ASPA: invalid afiLimit", p->fn); 139 return 0; 140 } 141 142 p->res->providers[p->res->providersz++] = provider; 143 } 144 145 return 1; 146 } 147 148 /* 149 * Parse the eContent of an ASPA file. 150 * Returns zero on failure, non-zero on success. 151 */ 152 static int 153 aspa_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) 154 { 155 ASProviderAttestation *aspa; 156 int rc = 0; 157 158 if ((aspa = d2i_ASProviderAttestation(NULL, &d, dsz)) == NULL) { 159 cryptowarnx("%s: ASPA: failed to parse ASProviderAttestation", 160 p->fn); 161 goto out; 162 } 163 164 if (!valid_econtent_version(p->fn, aspa->version)) 165 goto out; 166 167 if (!as_id_parse(aspa->customerASID, &p->res->custasid)) { 168 warnx("%s: malformed CustomerASID", p->fn); 169 goto out; 170 } 171 172 if (!aspa_parse_providers(p, aspa->providers)) 173 goto out; 174 175 rc = 1; 176 out: 177 ASProviderAttestation_free(aspa); 178 return rc; 179 } 180 181 /* 182 * Parse a full ASPA file. 183 * Returns the payload or NULL if the file was malformed. 184 */ 185 struct aspa * 186 aspa_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len) 187 { 188 struct parse p; 189 size_t cmsz; 190 unsigned char *cms; 191 struct cert *cert = NULL; 192 time_t signtime = 0; 193 int rc = 0; 194 195 memset(&p, 0, sizeof(struct parse)); 196 p.fn = fn; 197 198 cms = cms_parse_validate(x509, fn, der, len, aspa_oid, &cmsz, 199 &signtime); 200 if (cms == NULL) 201 return NULL; 202 203 if ((p.res = calloc(1, sizeof(*p.res))) == NULL) 204 err(1, NULL); 205 206 p.res->signtime = signtime; 207 208 if (!x509_get_aia(*x509, fn, &p.res->aia)) 209 goto out; 210 if (!x509_get_aki(*x509, fn, &p.res->aki)) 211 goto out; 212 if (!x509_get_sia(*x509, fn, &p.res->sia)) 213 goto out; 214 if (!x509_get_ski(*x509, fn, &p.res->ski)) 215 goto out; 216 if (p.res->aia == NULL || p.res->aki == NULL || p.res->sia == NULL || 217 p.res->ski == NULL) { 218 warnx("%s: RFC 6487 section 4.8: " 219 "missing AIA, AKI, SIA, or SKI X509 extension", fn); 220 goto out; 221 } 222 223 if (X509_get_ext_by_NID(*x509, NID_sbgp_ipAddrBlock, -1) != -1) { 224 warnx("%s: superfluous IP Resources extension present", fn); 225 goto out; 226 } 227 228 if (!x509_get_notbefore(*x509, fn, &p.res->notbefore)) 229 goto out; 230 if (!x509_get_notafter(*x509, fn, &p.res->notafter)) 231 goto out; 232 233 if (x509_any_inherits(*x509)) { 234 warnx("%s: inherit elements not allowed in EE cert", fn); 235 goto out; 236 } 237 238 if (!aspa_parse_econtent(cms, cmsz, &p)) 239 goto out; 240 241 if ((cert = cert_parse_ee_cert(fn, *x509)) == NULL) 242 goto out; 243 244 p.res->valid = valid_aspa(fn, cert, p.res); 245 246 rc = 1; 247 out: 248 if (rc == 0) { 249 aspa_free(p.res); 250 p.res = NULL; 251 X509_free(*x509); 252 *x509 = NULL; 253 } 254 cert_free(cert); 255 free(cms); 256 return p.res; 257 } 258 259 /* 260 * Free an ASPA pointer. 261 * Safe to call with NULL. 262 */ 263 void 264 aspa_free(struct aspa *p) 265 { 266 if (p == NULL) 267 return; 268 269 free(p->aia); 270 free(p->aki); 271 free(p->sia); 272 free(p->ski); 273 free(p->providers); 274 free(p); 275 } 276 277 /* 278 * Serialise parsed ASPA content. 279 * See aspa_read() for the reader on the other side. 280 */ 281 void 282 aspa_buffer(struct ibuf *b, const struct aspa *p) 283 { 284 io_simple_buffer(b, &p->valid, sizeof(p->valid)); 285 io_simple_buffer(b, &p->custasid, sizeof(p->custasid)); 286 io_simple_buffer(b, &p->talid, sizeof(p->talid)); 287 io_simple_buffer(b, &p->expires, sizeof(p->expires)); 288 289 io_simple_buffer(b, &p->providersz, sizeof(size_t)); 290 io_simple_buffer(b, p->providers, 291 p->providersz * sizeof(p->providers[0])); 292 293 io_str_buffer(b, p->aia); 294 io_str_buffer(b, p->aki); 295 io_str_buffer(b, p->ski); 296 } 297 298 /* 299 * Read parsed ASPA content from descriptor. 300 * See aspa_buffer() for writer. 301 * Result must be passed to aspa_free(). 302 */ 303 struct aspa * 304 aspa_read(struct ibuf *b) 305 { 306 struct aspa *p; 307 308 if ((p = calloc(1, sizeof(struct aspa))) == NULL) 309 err(1, NULL); 310 311 io_read_buf(b, &p->valid, sizeof(p->valid)); 312 io_read_buf(b, &p->custasid, sizeof(p->custasid)); 313 io_read_buf(b, &p->talid, sizeof(p->talid)); 314 io_read_buf(b, &p->expires, sizeof(p->expires)); 315 316 io_read_buf(b, &p->providersz, sizeof(size_t)); 317 if ((p->providers = calloc(p->providersz, 318 sizeof(struct aspa_provider))) == NULL) 319 err(1, NULL); 320 io_read_buf(b, p->providers, p->providersz * sizeof(p->providers[0])); 321 322 io_read_str(b, &p->aia); 323 io_read_str(b, &p->aki); 324 io_read_str(b, &p->ski); 325 assert(p->aia && p->aki && p->ski); 326 327 return p; 328 } 329 330 /* 331 * Insert a new aspa_provider at index idx in the struct vap v. 332 * All elements in the provider array from idx are moved up by one 333 * to make space for the new element. 334 */ 335 static void 336 insert_vap(struct vap *v, uint32_t idx, struct aspa_provider *p) 337 { 338 if (idx < v->providersz) 339 memmove(v->providers + idx + 1, v->providers + idx, 340 (v->providersz - idx) * sizeof(*v->providers)); 341 v->providers[idx] = *p; 342 v->providersz++; 343 } 344 345 /* 346 * Add each ProviderAS entry into the Validated ASPA Providers (VAP) tree. 347 * Duplicated entries are merged. 348 */ 349 void 350 aspa_insert_vaps(struct vap_tree *tree, struct aspa *aspa, struct repo *rp) 351 { 352 struct vap *v, *found; 353 size_t i, j; 354 355 if ((v = calloc(1, sizeof(*v))) == NULL) 356 err(1, NULL); 357 v->custasid = aspa->custasid; 358 v->talid = aspa->talid; 359 if (rp != NULL) 360 v->repoid = repo_id(rp); 361 else 362 v->repoid = 0; 363 v->expires = aspa->expires; 364 365 if ((found = RB_INSERT(vap_tree, tree, v)) != NULL) { 366 if (found->expires > v->expires) { 367 /* decrement found */ 368 repo_stat_inc(repo_byid(found->repoid), found->talid, 369 RTYPE_ASPA, STYPE_DEC_UNIQUE); 370 found->expires = v->expires; 371 found->talid = v->talid; 372 found->repoid = v->repoid; 373 repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE); 374 } 375 free(v); 376 v = found; 377 } else 378 repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE); 379 380 repo_stat_inc(rp, aspa->talid, RTYPE_ASPA, STYPE_TOTAL); 381 382 v->providers = reallocarray(v->providers, 383 v->providersz + aspa->providersz, sizeof(*v->providers)); 384 if (v->providers == NULL) 385 err(1, NULL); 386 387 /* 388 * Merge all data from aspa into v: loop over all aspa providers, 389 * insert them in the right place in v->providers while keeping the 390 * order of the providers array. 391 */ 392 for (i = 0, j = 0; i < aspa->providersz; ) { 393 if (j == v->providersz || 394 aspa->providers[i].as < v->providers[j].as) { 395 /* merge provider from aspa into v */ 396 repo_stat_inc(rp, v->talid, RTYPE_ASPA, 397 STYPE_BOTH + aspa->providers[i].afi); 398 insert_vap(v, j, &aspa->providers[i]); 399 i++; 400 } else if (aspa->providers[i].as == v->providers[j].as) { 401 /* duplicate provider, merge afi */ 402 if (v->providers[j].afi != aspa->providers[i].afi) { 403 repo_stat_inc(rp, v->talid, RTYPE_ASPA, 404 STYPE_BOTH + aspa->providers[i].afi); 405 v->providers[j].afi = 0; 406 } 407 i++; 408 } 409 if (j < v->providersz) 410 j++; 411 } 412 } 413 414 static inline int 415 vapcmp(struct vap *a, struct vap *b) 416 { 417 if (a->custasid > b->custasid) 418 return 1; 419 if (a->custasid < b->custasid) 420 return -1; 421 422 return 0; 423 } 424 425 RB_GENERATE(vap_tree, vap, entry, vapcmp); 426