#include <assert.h>
#include <err.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/asn1.h>
#include <openssl/asn1t.h>
#include <openssl/stack.h>
#include <openssl/safestack.h>
#include <openssl/x509.h>
#include "extern.h"
#include "rpki-asn1.h"
ASN1_ITEM_EXP ASProviderAttestation_it;
ASN1_SEQUENCE(ASProviderAttestation) = {
ASN1_EXP_OPT(ASProviderAttestation, version, ASN1_INTEGER, 0),
ASN1_SIMPLE(ASProviderAttestation, customerASID, ASN1_INTEGER),
ASN1_SEQUENCE_OF(ASProviderAttestation, providers, ASN1_INTEGER),
} ASN1_SEQUENCE_END(ASProviderAttestation);
IMPLEMENT_ASN1_FUNCTIONS(ASProviderAttestation);
static int
aspa_parse_providers(const char *fn, struct aspa *aspa,
const STACK_OF(ASN1_INTEGER) *providers)
{
const ASN1_INTEGER *pa;
size_t providersz, i;
if ((providersz = sk_ASN1_INTEGER_num(providers)) == 0) {
warnx("%s: ASPA: ProviderASSet needs at least one entry", fn);
return 0;
}
if (providersz >= MAX_ASPA_PROVIDERS) {
warnx("%s: ASPA: too many providers (more than %d)", fn,
MAX_ASPA_PROVIDERS);
return 0;
}
aspa->providers = calloc(providersz, sizeof(aspa->providers[0]));
if (aspa->providers == NULL)
err(1, NULL);
for (i = 0; i < providersz; i++) {
uint32_t provider = 0;
pa = sk_ASN1_INTEGER_value(providers, i);
if (!as_id_parse(pa, &provider)) {
warnx("%s: ASPA: malformed ProviderAS", fn);
return 0;
}
if (aspa->custasid == provider) {
warnx("%s: ASPA: CustomerASID can't also be Provider",
fn);
return 0;
}
if (i > 0) {
if (aspa->providers[i - 1] > provider) {
warnx("%s: ASPA: invalid ProviderASSet order",
fn);
return 0;
}
if (aspa->providers[i - 1] == provider) {
warnx("%s: ASPA: duplicate ProviderAS", fn);
return 0;
}
}
aspa->providers[aspa->num_providers++] = provider;
}
if (aspa->num_providers > 1 && aspa->providers[0] == 0) {
warnx("%s: ASPA: invalid ProviderASSet", fn);
return 0;
}
return 1;
}
static int
aspa_parse_econtent(const char *fn, struct aspa *aspa, const unsigned char *d,
size_t dsz)
{
const unsigned char *oder;
ASProviderAttestation *aspa_asn1;
int rc = 0;
oder = d;
if ((aspa_asn1 = d2i_ASProviderAttestation(NULL, &d, dsz)) == NULL) {
warnx("%s: ASPA: failed to parse ASProviderAttestation", fn);
goto out;
}
if (d != oder + dsz) {
warnx("%s: %td bytes trailing garbage in eContent", fn,
oder + dsz - d);
goto out;
}
if (!valid_econtent_version(fn, aspa_asn1->version, 1))
goto out;
if (!as_id_parse(aspa_asn1->customerASID, &aspa->custasid)) {
warnx("%s: malformed CustomerASID", fn);
goto out;
}
if (!aspa_parse_providers(fn, aspa, aspa_asn1->providers))
goto out;
rc = 1;
out:
ASProviderAttestation_free(aspa_asn1);
return rc;
}
struct aspa *
aspa_parse(struct cert **out_cert, const char *fn, int talid,
const unsigned char *der, size_t len)
{
struct aspa *aspa;
struct cert *cert = NULL;
size_t cmsz;
unsigned char *cms;
time_t signtime = 0;
int rc = 0;
assert(*out_cert == NULL);
cms = cms_parse_validate(&cert, fn, talid, der, len, aspa_oid, &cmsz,
&signtime);
if (cms == NULL)
return NULL;
if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
err(1, NULL);
aspa->signtime = signtime;
if (cert->num_ips > 0) {
warnx("%s: superfluous IP Resources extension present", fn);
goto out;
}
if (x509_any_inherits(cert->x509)) {
warnx("%s: inherit elements not allowed in EE cert", fn);
goto out;
}
if (!aspa_parse_econtent(fn, aspa, cms, cmsz))
goto out;
aspa->valid = valid_aspa(fn, cert, aspa);
*out_cert = cert;
cert = NULL;
rc = 1;
out:
if (rc == 0) {
aspa_free(aspa);
aspa = NULL;
}
cert_free(cert);
free(cms);
return aspa;
}
void
aspa_free(struct aspa *p)
{
if (p == NULL)
return;
free(p->providers);
free(p);
}
void
aspa_buffer(struct ibuf *b, const struct aspa *p)
{
io_simple_buffer(b, &p->valid, sizeof(p->valid));
io_simple_buffer(b, &p->custasid, sizeof(p->custasid));
io_simple_buffer(b, &p->talid, sizeof(p->talid));
io_simple_buffer(b, &p->expires, sizeof(p->expires));
io_simple_buffer(b, &p->num_providers, sizeof(size_t));
io_simple_buffer(b, p->providers,
p->num_providers * sizeof(p->providers[0]));
}
struct aspa *
aspa_read(struct ibuf *b)
{
struct aspa *p;
if ((p = calloc(1, sizeof(struct aspa))) == NULL)
err(1, NULL);
io_read_buf(b, &p->valid, sizeof(p->valid));
io_read_buf(b, &p->custasid, sizeof(p->custasid));
io_read_buf(b, &p->talid, sizeof(p->talid));
io_read_buf(b, &p->expires, sizeof(p->expires));
io_read_buf(b, &p->num_providers, sizeof(size_t));
if (p->num_providers > 0) {
if ((p->providers = calloc(p->num_providers,
sizeof(p->providers[0]))) == NULL)
err(1, NULL);
io_read_buf(b, p->providers,
p->num_providers * sizeof(p->providers[0]));
}
return p;
}
static void
insert_vap(struct vap *v, uint32_t idx, uint32_t *p)
{
if (idx < v->num_providers)
memmove(v->providers + idx + 1, v->providers + idx,
(v->num_providers - idx) * sizeof(v->providers[0]));
v->providers[idx] = *p;
v->num_providers++;
}
void
aspa_insert_vaps(char *fn, struct vap_tree *tree, struct aspa *aspa,
struct repo *rp)
{
struct vap *v, *found;
size_t i, j;
if ((v = calloc(1, sizeof(*v))) == NULL)
err(1, NULL);
v->custasid = aspa->custasid;
v->talid = aspa->talid;
v->repoid = repo_id(rp);
v->expires = aspa->expires;
if ((found = RB_INSERT(vap_tree, tree, v)) != NULL) {
if (found->overflowed) {
free(v);
return;
}
if (found->expires > v->expires) {
repo_stat_inc(repo_byid(found->repoid), found->talid,
RTYPE_ASPA, STYPE_DEC_UNIQUE);
found->expires = v->expires;
found->talid = v->talid;
found->repoid = v->repoid;
repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE);
}
free(v);
v = found;
} else
repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_UNIQUE);
repo_stat_inc(rp, aspa->talid, RTYPE_ASPA, STYPE_TOTAL);
v->providers = reallocarray(v->providers,
v->num_providers + aspa->num_providers, sizeof(v->providers[0]));
if (v->providers == NULL)
err(1, NULL);
for (i = 0, j = 0; i < aspa->num_providers; ) {
if (j == v->num_providers ||
aspa->providers[i] < v->providers[j]) {
repo_stat_inc(rp, v->talid, RTYPE_ASPA,
STYPE_PROVIDERS);
insert_vap(v, j, &aspa->providers[i]);
i++;
} else if (aspa->providers[i] == v->providers[j])
i++;
if (j < v->num_providers)
j++;
}
if (v->num_providers >= MAX_ASPA_PROVIDERS) {
v->overflowed = 1;
free(v->providers);
v->providers = NULL;
v->num_providers = 0;
repo_stat_inc(rp, v->talid, RTYPE_ASPA, STYPE_OVERFLOW);
warnx("%s: too many providers for ASPA Customer ASID %u "
"(more than %d)", fn, v->custasid, MAX_ASPA_PROVIDERS);
return;
}
}
static inline int
vapcmp(struct vap *a, struct vap *b)
{
if (a->custasid > b->custasid)
return 1;
if (a->custasid < b->custasid)
return -1;
return 0;
}
RB_GENERATE(vap_tree, vap, entry, vapcmp);