#include <assert.h>
#include <err.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/asn1.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include "extern.h"
int certid = TALSZ_MAX;
static pthread_rwlock_t cert_lk = PTHREAD_RWLOCK_INITIALIZER;
static int
cert_extension_oid_cmp(const X509_EXTENSION *const *a,
const X509_EXTENSION *const *b)
{
const ASN1_OBJECT *ao = X509_EXTENSION_get_object((X509_EXTENSION *)*a);
const ASN1_OBJECT *bo = X509_EXTENSION_get_object((X509_EXTENSION *)*b);
return OBJ_cmp(ao, bo);
}
static int
cert_extension_oids_are_unique(const char *fn, const struct cert *cert)
{
const X509 *x509 = cert->x509;
const STACK_OF(X509_EXTENSION) *cexts = NULL;
STACK_OF(X509_EXTENSION) *exts = NULL;
const X509_EXTENSION *prev, *curr;
const ASN1_OBJECT *obj;
int i, nid, rc = 0;
if (X509_get_ext_count(x509) <= 1)
goto done;
if ((cexts = X509_get0_extensions(x509)) == NULL)
goto out;
if ((exts = sk_X509_EXTENSION_dup(cexts)) == NULL)
goto out;
(void)sk_X509_EXTENSION_set_cmp_func(exts, cert_extension_oid_cmp);
sk_X509_EXTENSION_sort(exts);
prev = sk_X509_EXTENSION_value(exts, 0);
for (i = 1; i < sk_X509_EXTENSION_num(exts); i++) {
curr = sk_X509_EXTENSION_value(exts, i);
if (cert_extension_oid_cmp(&prev, &curr) == 0) {
obj = X509_EXTENSION_get_object((X509_EXTENSION *)curr);
nid = OBJ_obj2nid(obj);
warnx("%s: RFC 5280 section 4.2: duplicate extension: "
"%s", fn, nid2str(nid));
goto out;
}
prev = curr;
}
done:
rc = 1;
out:
sk_X509_EXTENSION_free(exts);
return rc;
}
static int
cert_check_purpose(const char *fn, struct cert *cert)
{
X509 *x = cert->x509;
BASIC_CONSTRAINTS *bc = NULL;
EXTENDED_KEY_USAGE *eku = NULL;
const X509_EXTENSION *ku;
int crit, ext_flags, i, is_ca, ku_idx;
cert->purpose = CERT_PURPOSE_INVALID;
if (X509_check_purpose(x, -1, 0) <= 0) {
warnx("%s: could not cache X509v3 extensions", fn);
goto out;
}
if (!cert_extension_oids_are_unique(fn, cert))
goto out;
ext_flags = X509_get_extension_flags(x);
if ((ku_idx = X509_get_ext_by_NID(x, NID_key_usage, -1)) < 0) {
warnx("%s: RFC 6487, section 4.8.4: missing KeyUsage", fn);
goto out;
}
if ((ku = X509_get_ext(x, ku_idx)) == NULL) {
warnx("%s: RFC 6487, section 4.8.4: missing KeyUsage", fn);
goto out;
}
if (!X509_EXTENSION_get_critical(ku)) {
warnx("%s: RFC 6487, section 4.8.4: KeyUsage not critical", fn);
goto out;
}
if ((is_ca = X509_check_ca(x)) > 1) {
if (is_ca == 4)
warnx("%s: RFC 6487: sections 4.8.1 and 4.8.4: "
"no basic constraints, but keyCertSign set", fn);
else
warnx("%s: unexpected legacy certificate", fn);
goto out;
}
if (is_ca) {
bc = X509_get_ext_d2i(x, NID_basic_constraints, &crit, NULL);
if (bc == NULL) {
if (crit != -1)
warnx("%s: RFC 6487 section 4.8.1: "
"error parsing basic constraints", fn);
else
warnx("%s: RFC 6487 section 4.8.1: "
"missing basic constraints", fn);
goto out;
}
if (crit != 1) {
warnx("%s: RFC 6487 section 4.8.1: Basic Constraints "
"must be marked critical", fn);
goto out;
}
if (bc->pathlen != NULL) {
warnx("%s: RFC 6487 section 4.8.1: Path Length "
"Constraint must be absent", fn);
goto out;
}
if (X509_get_key_usage(x) != (KU_KEY_CERT_SIGN | KU_CRL_SIGN)) {
warnx("%s: RFC 6487 section 4.8.4: key usage violation",
fn);
goto out;
}
if (X509_get_extended_key_usage(x) != UINT32_MAX) {
warnx("%s: RFC 6487 section 4.8.5: EKU not allowed",
fn);
goto out;
}
if ((ext_flags & EXFLAG_SS) != 0)
cert->purpose = CERT_PURPOSE_TA;
else if ((ext_flags & EXFLAG_SI) == 0)
cert->purpose = CERT_PURPOSE_CA;
else
warnx("%s: RFC 6487, section 4.8.3: "
"self-issued cert with AKI-SKI mismatch", fn);
goto out;
}
if ((ext_flags & EXFLAG_BCONS) != 0) {
warnx("%s: Basic Constraints ext in non-CA cert", fn);
goto out;
}
if ((ext_flags & (EXFLAG_SI | EXFLAG_SS)) != 0) {
warnx("%s: EE cert must not be self-issued or self-signed", fn);
goto out;
}
if (X509_get_key_usage(x) != KU_DIGITAL_SIGNATURE) {
warnx("%s: RFC 6487 section 4.8.4: KU must be digitalSignature",
fn);
goto out;
}
eku = X509_get_ext_d2i(x, NID_ext_key_usage, &crit, NULL);
if (eku == NULL) {
if (crit != -1)
warnx("%s: error parsing EKU", fn);
else
cert->purpose = CERT_PURPOSE_EE;
goto out;
}
if (crit != 0) {
warnx("%s: EKU: extension must not be marked critical", fn);
goto out;
}
for (i = 0; i < sk_ASN1_OBJECT_num(eku); i++) {
if (OBJ_cmp(bgpsec_oid, sk_ASN1_OBJECT_value(eku, i)) == 0) {
cert->purpose = CERT_PURPOSE_BGPSEC_ROUTER;
goto out;
}
}
warnx("%s: unknown certificate purpose", fn);
assert(cert->purpose == CERT_PURPOSE_INVALID);
out:
BASIC_CONSTRAINTS_free(bc);
EXTENDED_KEY_USAGE_free(eku);
return cert->purpose != CERT_PURPOSE_INVALID;
}
static int
cert_check_sigalg(const char *fn, const struct cert *cert)
{
const X509 *x = cert->x509;
const X509_ALGOR *alg = NULL, *tbsalg;
X509_get0_signature(NULL, &alg, x);
if (alg == NULL) {
warnx("%s: missing signatureAlgorithm in certificate", fn);
return 0;
}
if ((tbsalg = X509_get0_tbs_sigalg(x)) == NULL) {
warnx("%s: missing signature in tbsCertificate", fn);
return 0;
}
if (X509_ALGOR_cmp(alg, tbsalg) != 0) {
warnx("%s: RFC 5280, 4.1.1.2: signatureAlgorithm and signature "
"AlgorithmIdentifier mismatch", fn);
return 0;
}
return x509_check_tbs_sigalg(fn, tbsalg);
}
static int
cert_check_subject_and_issuer(const char *fn, const struct cert *cert)
{
const X509_NAME *name;
if ((name = X509_get_subject_name(cert->x509)) == NULL) {
warnx("%s: X509_get_subject_name", fn);
return 0;
}
if (!x509_valid_subject_name(fn, name))
return 0;
if ((name = X509_get_issuer_name(cert->x509)) == NULL) {
warnx("%s: X509_get_issuer_name", fn);
return 0;
}
if (!x509_valid_issuer_name(fn, name))
return 0;
return 1;
}
static int
cert_check_validity_period(const char *fn, struct cert *cert)
{
const ASN1_TIME *at;
if ((at = X509_get0_notBefore(cert->x509)) == NULL) {
warnx("%s: X509_get0_notBefore() failed", fn);
return 0;
}
if (!x509_get_time(at, &cert->notbefore)) {
warnx("%s: x509_get_time() failed", fn);
return 0;
}
if ((at = X509_get0_notAfter(cert->x509)) == NULL) {
warnx("%s: X509_get0_notAfter() failed", fn);
return 0;
}
if (!x509_get_time(at, &cert->notafter)) {
warnx("%s: x509_get_time() failed", fn);
return 0;
}
if (cert->notbefore > cert->notafter) {
warnx("%s: RFC 6487, 4.6: notAfter precedes notBefore", fn);
return 0;
}
return 1;
}
static int
cert_compliant_rsa_key(const char *fn, struct cert *cert)
{
EVP_PKEY *pkey;
const RSA *rsa;
const BIGNUM *rsa_n, *rsa_e;
if ((pkey = X509_get0_pubkey(cert->x509)) == NULL) {
warnx("%s: cert without public key", fn);
return 0;
}
if ((rsa = EVP_PKEY_get0_RSA(pkey)) == NULL) {
warnx("%s: expected RSA key", fn);
return 0;
}
if ((rsa_n = RSA_get0_n(rsa)) == NULL ||
(rsa_e = RSA_get0_e(rsa)) == NULL) {
warnx("%s: missing RSA public key component", fn);
return 0;
}
if (BN_num_bits(rsa_n) != 2048) {
warnx("%s: RFC 7935, 3: want 2048-bit RSA modulus, have %d", fn,
BN_num_bits(rsa_n));
return 0;
}
if (!BN_is_word(rsa_e, 65537)) {
warnx("%s: RFC 7935, 3: public RSA exponent not %d", fn, 65537);
return 0;
}
return 1;
}
static int
cert_compliant_ec_key(const char *fn, struct cert *cert)
{
EVP_PKEY *pkey;
const EC_KEY *ec_key;
if ((pkey = X509_get0_pubkey(cert->x509)) == NULL) {
warnx("%s: cert without public key", fn);
return 0;
}
if ((ec_key = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) {
warnx("%s: expected EC key", fn);
return 0;
}
if (EC_KEY_get_conv_form(ec_key) != POINT_CONVERSION_UNCOMPRESSED) {
warnx("%s: RFC 8608: 3.1 public key not uncompressed", fn);
return 0;
}
if (!EC_KEY_check_key(ec_key)) {
warnx("%s: EC_KEY_check_key failed", fn);
return 0;
}
if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
unsigned char *der = NULL;
int der_len;
if ((der_len = i2d_PUBKEY(pkey, &der)) <= 0) {
warnx("%s: i2d_PUBKEY failed", fn);
return 0;
}
if (base64_encode(der, der_len, &cert->pubkey) == -1)
errx(1, "base64_encode");
free(der);
}
return 1;
}
static int
cert_check_spki(const char *fn, struct cert *cert)
{
X509_PUBKEY *pubkey;
X509_ALGOR *alg = NULL;
const ASN1_OBJECT *aobj = NULL;
int ptype = 0;
const void *pval = NULL;
int rc = 0;
pubkey = (X509_PUBKEY *)X509_get_X509_PUBKEY(cert->x509);
if (pubkey == NULL) {
warnx("%s: RFC 6487, 4.7: certificate without SPKI", fn);
goto out;
}
if (!X509_PUBKEY_get0_param(NULL, NULL, NULL, &alg, pubkey) ||
alg == NULL) {
warnx("%s: RFC 6487, 4.7: no AlgorithmIdentifier in SPKI", fn);
goto out;
}
X509_ALGOR_get0(&aobj, &ptype, &pval, alg);
switch (cert->purpose) {
case CERT_PURPOSE_TA:
case CERT_PURPOSE_CA:
case CERT_PURPOSE_EE:
if (OBJ_obj2nid(aobj) == NID_rsaEncryption) {
if (ptype != V_ASN1_NULL || pval != NULL) {
warnx("%s: RFC 4055, 1.2, rsaEncryption "
"parameters not NULL", fn);
goto out;
}
if (!cert_compliant_rsa_key(fn, cert))
goto out;
break;
}
if (!experimental) {
warnx("%s: RFC 7935, 3.1 SPKI not RSAPublicKey", fn);
goto out;
}
case CERT_PURPOSE_BGPSEC_ROUTER:
if (OBJ_obj2nid(aobj) == NID_X9_62_id_ecPublicKey) {
if (ptype != V_ASN1_OBJECT) {
warnx("%s: RFC 5480, 2.1.1, ecPublicKey "
"parameters not namedCurve", fn);
goto out;
}
if (OBJ_obj2nid(pval) != NID_X9_62_prime256v1) {
warnx("%s: RFC 8608, 3.1, named curve not "
"P-256", fn);
goto out;
}
if (!cert_compliant_ec_key(fn, cert))
goto out;
break;
}
warnx("%s: RFC 8608, 3.1, SPKI not an ecPublicKey", fn);
goto out;
default:
abort();
}
rc = 1;
out:
return rc;
}
static int
cert_ski(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
ASN1_OCTET_STRING *os = NULL;
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int md_len = EVP_MAX_MD_SIZE;
int length, rc = 0;
assert(cert->ski == NULL);
if (X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.2: "
"SKI: extension not non-critical", fn);
goto out;
}
if ((os = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.2: error parsing SKI", fn);
goto out;
}
if (!X509_pubkey_digest(cert->x509, EVP_sha1(), md, &md_len)) {
warnx("%s: X509_pubkey_digest", fn);
goto out;
}
length = ASN1_STRING_length(os);
if (length < 0 || md_len != (unsigned int)length) {
warnx("%s: RFC 6487 section 4.8.2: SKI: "
"want %u bytes SHA1 hash, have %d bytes",
fn, md_len, length);
goto out;
}
if (memcmp(ASN1_STRING_get0_data(os), md, md_len) != 0) {
warnx("%s: SKI does not match SHA1 hash of SPK", fn);
goto out;
}
cert->ski = hex_encode(md, md_len);
rc = 1;
out:
ASN1_OCTET_STRING_free(os);
return rc;
}
static int
cert_aki(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
AUTHORITY_KEYID *akid = NULL;
int length, rc = 0;
assert(cert->aki == NULL);
if (X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.3: "
"AKI extension not non-critical", fn);
goto out;
}
if ((akid = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.3: error parsing AKI", fn);
goto out;
}
if (akid->issuer != NULL || akid->serial != NULL) {
warnx("%s: RFC 6487 section 4.8.3: AKI: authorityCertIssuer or "
"authorityCertSerialNumber present", fn);
goto out;
}
if (akid->keyid == NULL) {
warnx("%s: RFC 6487 section 4.8.3: AKI: Key Identifier missing",
fn);
goto out;
}
length = ASN1_STRING_length(akid->keyid);
if (length != SHA_DIGEST_LENGTH) {
warnx("%s: RFC 6487 section 4.8.3: AKI: "
"want %d bytes SHA1 hash, have %d bytes",
fn, SHA_DIGEST_LENGTH, length);
goto out;
}
cert->aki = hex_encode(ASN1_STRING_get0_data(akid->keyid), length);
rc = 1;
out:
AUTHORITY_KEYID_free(akid);
return rc;
}
static int
cert_crldp(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
CRL_DIST_POINTS *crldp = NULL;
DIST_POINT *dp;
GENERAL_NAMES *names;
GENERAL_NAME *name;
int i, rc = 0;
assert(cert->crl == NULL);
if (cert->purpose == CERT_PURPOSE_TA) {
warnx("%s: RFC 6487 section 4.8.6: CRL distribution point "
"must be omitted from TA certificates", fn);
goto out;
}
if (X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.6: CRL distribution point "
"extension not non-critical", fn);
goto out;
}
if ((crldp = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.6: CRL distribution point: "
"failed extension parse", fn);
goto out;
}
if (sk_DIST_POINT_num(crldp) != 1) {
warnx("%s: RFC 6487 section 4.8.6: CRL distribution point: "
"want 1 element, have %d", fn, sk_DIST_POINT_num(crldp));
goto out;
}
dp = sk_DIST_POINT_value(crldp, 0);
if (dp->CRLissuer != NULL) {
warnx("%s: RFC 6487 section 4.8.6: CRL CRLIssuer field"
" disallowed", fn);
goto out;
}
if (dp->reasons != NULL) {
warnx("%s: RFC 6487 section 4.8.6: CRL Reasons field"
" disallowed", fn);
goto out;
}
if (dp->distpoint == NULL) {
warnx("%s: RFC 6487 section 4.8.6: CRL: "
"no distribution point name", fn);
goto out;
}
if (dp->distpoint->dpname != NULL) {
warnx("%s: RFC 6487 section 4.8.6: nameRelativeToCRLIssuer"
" disallowed", fn);
goto out;
}
if (dp->distpoint->type != 0) {
warnx("%s: RFC 6487 section 4.8.6: CRL DistributionPointName:"
" expected fullName, have %d", fn, dp->distpoint->type);
goto out;
}
names = dp->distpoint->name.fullname;
for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
char *crl = NULL;
name = sk_GENERAL_NAME_value(names, i);
if (!x509_location(fn, "CRL distribution point", name, &crl))
goto out;
if (cert->crl == NULL && strncasecmp(crl, RSYNC_PROTO,
RSYNC_PROTO_LEN) == 0) {
cert->crl = crl;
continue;
}
if (verbose)
warnx("%s: ignoring CRL distribution point %s",
fn, crl);
free(crl);
}
if (cert->crl == NULL) {
warnx("%s: RFC 6487 section 4.8.6: no rsync URI in "
"CRL distribution point", fn);
goto out;
}
rc = 1;
out:
CRL_DIST_POINTS_free(crldp);
return rc;
}
static int
cert_aia(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
AUTHORITY_INFO_ACCESS *aia = NULL;
ACCESS_DESCRIPTION *ad;
ASN1_OBJECT *oid;
char *caissuers = NULL;
int i, rc = 0;
assert(cert->aia == NULL);
if (cert->purpose == CERT_PURPOSE_TA) {
warnx("%s: RFC 6487 section 4.8.7: AIA must be absent from "
"a self-signed certificate", fn);
goto out;
}
if (X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.7: AIA: "
"extension not non-critical", fn);
goto out;
}
if ((aia = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.7: AIA: failed extension parse",
fn);
goto out;
}
for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) {
ad = sk_ACCESS_DESCRIPTION_value(aia, i);
oid = ad->method;
if (OBJ_cmp(oid, caissuers_oid) == 0) {
if (!x509_location(fn, "AIA: caIssuers", ad->location,
&caissuers))
goto out;
if (cert->aia == NULL && strncasecmp(caissuers,
RSYNC_PROTO, RSYNC_PROTO_LEN) == 0) {
cert->aia = caissuers;
caissuers = NULL;
continue;
}
if (verbose)
warnx("%s: RFC 6487 section 4.8.7: AIA: "
"ignoring location %s", fn, caissuers);
free(caissuers);
caissuers = NULL;
} else {
char buf[128];
OBJ_obj2txt(buf, sizeof(buf), oid, 0);
warnx("%s: RFC 6487 section 4.8.7: unexpected"
" accessMethod: %s", fn, buf);
goto out;
}
}
if (cert->aia == NULL) {
warnx("%s: RFC 6487 section 4.8.7: AIA: expected caIssuers "
"accessMethod with rsync protocol", fn);
goto out;
}
rc = 1;
out:
AUTHORITY_INFO_ACCESS_free(aia);
return rc;
}
static int
cert_ca_sia(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
AUTHORITY_INFO_ACCESS *sia = NULL;
ACCESS_DESCRIPTION *ad;
ASN1_OBJECT *oid;
const char *mftfilename;
char *carepo = NULL, *rpkimft = NULL, *notify = NULL;
int i, rc = 0;
assert(cert->repo == NULL && cert->mft == NULL && cert->notify == NULL);
if (X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.8: SIA: "
"extension not non-critical", fn);
goto out;
}
if ((sia = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.8: SIA: failed extension parse",
fn);
goto out;
}
for (i = 0; i < sk_ACCESS_DESCRIPTION_num(sia); i++) {
ad = sk_ACCESS_DESCRIPTION_value(sia, i);
oid = ad->method;
if (OBJ_cmp(oid, carepo_oid) == 0) {
if (!x509_location(fn, "SIA: caRepository",
ad->location, &carepo))
goto out;
if (cert->repo == NULL && strncasecmp(carepo,
RSYNC_PROTO, RSYNC_PROTO_LEN) == 0) {
if (carepo[strlen(carepo) - 1] != '/') {
char *carepo_tmp;
if (asprintf(&carepo_tmp, "%s/",
carepo) == -1)
errx(1, NULL);
free(carepo);
carepo = carepo_tmp;
}
cert->repo = carepo;
carepo = NULL;
continue;
}
if (verbose)
warnx("%s: RFC 6487 section 4.8.8: SIA: "
"ignoring location %s", fn, carepo);
free(carepo);
carepo = NULL;
} else if (OBJ_cmp(oid, manifest_oid) == 0) {
if (!x509_location(fn, "SIA: rpkiManifest",
ad->location, &rpkimft))
goto out;
if (cert->mft == NULL && strncasecmp(rpkimft,
RSYNC_PROTO, RSYNC_PROTO_LEN) == 0) {
cert->mft = rpkimft;
rpkimft = NULL;
continue;
}
if (verbose)
warnx("%s: RFC 6487 section 4.8.8: SIA: "
"ignoring location %s", fn, rpkimft);
free(rpkimft);
rpkimft = NULL;
} else if (OBJ_cmp(oid, notify_oid) == 0) {
if (!x509_location(fn, "SIA: rpkiNotify",
ad->location, ¬ify))
goto out;
if (strncasecmp(notify, HTTPS_PROTO,
HTTPS_PROTO_LEN) != 0) {
warnx("%s: non-https uri in rpkiNotify: %s",
fn, notify);
free(notify);
goto out;
}
if (cert->notify != NULL) {
warnx("%s: unexpected rpkiNotify accessMethod",
fn);
free(notify);
goto out;
}
cert->notify = notify;
notify = NULL;
} else {
char buf[128];
OBJ_obj2txt(buf, sizeof(buf), oid, 0);
warnx("%s: RFC 6487 section 4.8.8.1: unexpected"
" accessMethod: %s", fn, buf);
goto out;
}
}
if (cert->mft == NULL || cert->repo == NULL) {
warnx("%s: RFC 6487 section 4.8.8: SIA: missing caRepository "
"or rpkiManifest", fn);
goto out;
}
mftfilename = strrchr(cert->mft, '/');
if (mftfilename == NULL) {
warnx("%s: SIA: invalid rpkiManifest entry", fn);
goto out;
}
mftfilename++;
if (!valid_filename(mftfilename, strlen(mftfilename))) {
warnx("%s: SIA: rpkiManifest invalid filename", fn);
goto out;
}
if (strstr(cert->mft, cert->repo) != cert->mft ||
cert->mft + strlen(cert->repo) != mftfilename) {
warnx("%s: RFC 6487 section 4.8.8: SIA: "
"conflicting URIs for caRepository and rpkiManifest", fn);
goto out;
}
if (rtype_from_file_extension(cert->mft) != RTYPE_MFT) {
warnx("%s: RFC 6487 section 4.8.8: SIA: not an MFT file", fn);
goto out;
}
rc = 1;
out:
AUTHORITY_INFO_ACCESS_free(sia);
return rc;
}
static int
cert_ee_sia(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
AUTHORITY_INFO_ACCESS *sia = NULL;
ACCESS_DESCRIPTION *ad;
ASN1_OBJECT *oid;
char *signedobj = NULL;
int i, rc = 0;
assert(cert->signedobj == NULL);
if (X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.8: SIA: "
"extension not non-critical", fn);
goto out;
}
if ((sia = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.8: SIA: failed extension parse",
fn);
goto out;
}
for (i = 0; i < sk_ACCESS_DESCRIPTION_num(sia); i++) {
ad = sk_ACCESS_DESCRIPTION_value(sia, i);
oid = ad->method;
if (OBJ_cmp(oid, notify_oid) == 0) {
if (verbose > 1)
warnx("%s: RFC 6487 section 4.8.8.2: SIA should"
" not contain rpkiNotify accessMethod", fn);
continue;
} else if (OBJ_cmp(oid, signedobj_oid) == 0) {
if (!x509_location(fn, "SIA: signedObject",
ad->location, &signedobj))
goto out;
if (cert->signedobj == NULL && strncasecmp(signedobj,
RSYNC_PROTO, RSYNC_PROTO_LEN) == 0) {
cert->signedobj = signedobj;
signedobj = NULL;
continue;
}
if (verbose)
warnx("%s: RFC 6487 section 4.8.8: SIA: "
"ignoring location %s", fn, signedobj);
free(signedobj);
signedobj = NULL;
} else {
char buf[128];
OBJ_obj2txt(buf, sizeof(buf), oid, 0);
warnx("%s: RFC 6487 section 4.8.8.1: unexpected"
" accessMethod: %s", fn, buf);
goto out;
}
}
if (cert->signedobj == NULL) {
warnx("%s: RFC 6487 section 4.8.8: SIA: no signedObject", fn);
goto out;
}
if (!filemode) {
const char *p = cert->signedobj + RSYNC_PROTO_LEN;
size_t fnlen, plen;
fnlen = strlen(fn);
plen = strlen(p);
if (fnlen < plen || strcmp(p, fn + fnlen - plen) != 0) {
warnx("%s: RFC 9981 section 4: mismatch between "
"pathname and SIA (%s)", fn, cert->signedobj);
goto out;
}
}
rc = 1;
out:
AUTHORITY_INFO_ACCESS_free(sia);
return rc;
}
static int
cert_sia(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
switch (cert->purpose) {
case CERT_PURPOSE_TA:
case CERT_PURPOSE_CA:
return cert_ca_sia(fn, cert, ext);
case CERT_PURPOSE_EE:
return cert_ee_sia(fn, cert, ext);
case CERT_PURPOSE_BGPSEC_ROUTER:
warnx("%s: RFC 8209, 3.1.3.3, SIA MUST be omitted from "
"BGPsec router certs", fn);
return 0;
default:
abort();
}
}
static int
cert_policies(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
STACK_OF(POLICYINFO) *policies = NULL;
POLICYINFO *policy;
STACK_OF(POLICYQUALINFO) *qualifiers;
POLICYQUALINFO *qualifier;
int nid;
int rc = 0;
if (!X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.9: certificatePolicies: "
"extension not critical", fn);
goto out;
}
if ((policies = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.9: certificatePolicies: "
"failed extension parse", fn);
goto out;
}
if (sk_POLICYINFO_num(policies) != 1) {
warnx("%s: RFC 6487 section 4.8.9: certificatePolicies: "
"want 1 policy, got %d", fn, sk_POLICYINFO_num(policies));
goto out;
}
policy = sk_POLICYINFO_value(policies, 0);
assert(policy != NULL && policy->policyid != NULL);
if (OBJ_cmp(policy->policyid, certpol_oid) != 0) {
char pbuf[128], cbuf[128];
OBJ_obj2txt(pbuf, sizeof(pbuf), policy->policyid, 1);
OBJ_obj2txt(cbuf, sizeof(cbuf), certpol_oid, 1);
warnx("%s: RFC 7318 section 2: certificatePolicies: "
"unexpected OID: %s, want %s", fn, pbuf, cbuf);
goto out;
}
if ((qualifiers = policy->qualifiers) == NULL) {
rc = 1;
goto out;
}
if (sk_POLICYQUALINFO_num(qualifiers) != 1) {
warnx("%s: RFC 7318 section 2: certificatePolicies: "
"want 1 policy qualifier, got %d", fn,
sk_POLICYQUALINFO_num(qualifiers));
goto out;
}
qualifier = sk_POLICYQUALINFO_value(qualifiers, 0);
assert(qualifier != NULL && qualifier->pqualid != NULL);
if ((nid = OBJ_obj2nid(qualifier->pqualid)) != NID_id_qt_cps) {
warnx("%s: RFC 7318 section 2: certificatePolicies: "
"want CPS, got %s", fn, nid2str(nid));
goto out;
}
rc = 1;
out:
sk_POLICYINFO_pop_free(policies, POLICYINFO_free);
return rc;
}
static int
cert_ipaddrblocks(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
IPAddrBlocks *addrs = NULL;
int rc = 0;
if (!X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.10: ipAddrBlocks: "
"extension not critical", fn);
goto out;
}
if ((addrs = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.10: ipAddrBlocks: "
"failed extension parse", fn);
goto out;
}
if (!sbgp_parse_ipaddrblocks(fn, addrs, &cert->ips, &cert->num_ips))
goto out;
if (cert->num_ips == 0) {
warnx("%s: RFC 6487 section 4.8.10: empty ipAddrBlocks", fn);
goto out;
}
rc = 1;
out:
IPAddrBlocks_free(addrs);
return rc;
}
static int
cert_as_inherit(const struct cert *cert)
{
if (cert->num_ases != 1)
return 0;
return cert->ases[0].type == CERT_AS_INHERIT;
}
static int
cert_has_one_as(const struct cert *cert)
{
if (cert->num_ases != 1)
return 0;
return cert->ases[0].type == CERT_AS_ID;
}
static int
cert_asids(const char *fn, struct cert *cert, const X509_EXTENSION *ext)
{
ASIdentifiers *asidentifiers = NULL;
int rc = 0;
if (!X509_EXTENSION_get_critical(ext)) {
warnx("%s: RFC 6487 section 4.8.11: autonomousSysIds: "
"extension not critical", fn);
goto out;
}
if ((asidentifiers = X509V3_EXT_d2i((X509_EXTENSION *)ext)) == NULL) {
warnx("%s: RFC 6487 section 4.8.11: autonomousSysIds: "
"failed extension parse", fn);
goto out;
}
if (!sbgp_parse_asids(fn, asidentifiers, &cert->ases, &cert->num_ases))
goto out;
rc = 1;
out:
ASIdentifiers_free(asidentifiers);
return rc;
}
static int
cert_parse_extensions(const char *fn, struct cert *cert)
{
X509 *x = cert->x509;
const X509_EXTENSION *ext;
const ASN1_OBJECT *obj;
int extsz, i, nid, bc, ski, aki, ku, eku, crldp, aia, sia, cp, ip, as;
nid = bc = ski = aki = ku = eku = crldp = aia = sia = cp = ip = as = 0;
assert(cert->purpose != CERT_PURPOSE_INVALID);
if ((extsz = X509_get_ext_count(x)) <= 0) {
warnx("%s: certificate without X.509v3 extensions", fn);
goto out;
}
for (i = 0; i < extsz; i++) {
ext = X509_get_ext(x, i);
assert(ext != NULL);
obj = X509_EXTENSION_get_object((X509_EXTENSION *)ext);
assert(obj != NULL);
switch (nid = OBJ_obj2nid(obj)) {
case NID_basic_constraints:
if (bc++ > 0)
goto dup;
break;
case NID_subject_key_identifier:
if (ski++ > 0)
goto dup;
if (!cert_ski(fn, cert, ext))
goto out;
break;
case NID_authority_key_identifier:
if (aki++ > 0)
goto dup;
if (!cert_aki(fn, cert, ext))
goto out;
break;
case NID_key_usage:
if (ku++ > 0)
goto dup;
break;
case NID_ext_key_usage:
if (eku++ > 0)
goto dup;
break;
case NID_crl_distribution_points:
if (crldp++ > 0)
goto dup;
if (!cert_crldp(fn, cert, ext))
goto out;
break;
case NID_info_access:
if (aia++ > 0)
goto dup;
if (!cert_aia(fn, cert, ext))
goto out;
break;
case NID_sinfo_access:
if (sia++ > 0)
goto dup;
if (!cert_sia(fn, cert, ext))
goto out;
break;
case NID_certificate_policies:
if (cp++ > 0)
goto dup;
if (!cert_policies(fn, cert, ext))
goto out;
break;
case NID_sbgp_ipAddrBlock:
if (ip++ > 0)
goto dup;
if (!cert_ipaddrblocks(fn, cert, ext))
goto out;
break;
case NID_sbgp_autonomousSysNum:
if (as++ > 0)
goto dup;
if (!cert_asids(fn, cert, ext))
goto out;
break;
default:
{
char objn[64];
OBJ_obj2txt(objn, sizeof(objn), obj, 0);
if (X509_EXTENSION_get_critical(ext)) {
warnx("%s: unknown critical extension "
"%s (NID %d)", fn, objn, nid);
goto out;
}
warnx("%s: ignoring %s (NID %d)",
fn, objn, nid);
}
break;
}
}
if (bc == 0) {
if (cert->purpose == CERT_PURPOSE_TA ||
cert->purpose == CERT_PURPOSE_CA) {
warnx("%s: RFC 6487, 4.8.1: CA cert without "
"basic constraints", fn);
goto out;
}
} else {
if (cert->purpose != CERT_PURPOSE_TA &&
cert->purpose != CERT_PURPOSE_CA) {
warnx("%s: RFC 6487, 4.8.1: non-CA cert with "
"basic constraints", fn);
goto out;
}
}
if (ski == 0) {
warnx("%s: RFC 6487, 4.8.2: cert without SKI", fn);
goto out;
}
if (aki == 0) {
if (cert->purpose != CERT_PURPOSE_TA) {
warnx("%s: RFC 6487, 4.8.3: non-TA cert without "
"AKI", fn);
goto out;
}
} else {
if (cert->purpose == CERT_PURPOSE_TA) {
if (strcmp(cert->ski, cert->aki) != 0) {
warnx("%s: RFC 6487, 4.8.3: TA cert with "
"mismatch between AKI and SKI", fn);
goto out;
}
} else {
if (strcmp(cert->ski, cert->aki) == 0) {
warnx("%s: RFC 6487, 4.8.3: non-TA cert "
"must not have matching AKI and SKI", fn);
goto out;
}
}
}
if (ku == 0) {
warnx("%s: RFC 6487, 4.8.4: cert without key usage", fn);
goto out;
}
if (eku == 0) {
if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
warnx("%s: RFC 8209, 3.1.3.2: BGPsec Router cert "
"without extended key usage", fn);
goto out;
}
} else {
if (cert->purpose != CERT_PURPOSE_BGPSEC_ROUTER) {
warnx("%s: RFC 6487, 4.8.5: non-BGPsec cert with "
"extended key usage", fn);
goto out;
}
}
if (crldp == 0) {
if (cert->purpose != CERT_PURPOSE_TA) {
warnx("%s: RFC 6487, 4.8.6: non-TA cert without "
"CRL Distribution Point", fn);
goto out;
}
} else {
if (cert->purpose == CERT_PURPOSE_TA) {
warnx("%s: RFC 6487, 4.8.6: TA cert with "
"CRL Distribution Point", fn);
goto out;
}
}
if (aia == 0) {
if (cert->purpose != CERT_PURPOSE_TA) {
warnx("%s: RFC 6487, 4.8.7: non-TA cert without "
"AIA", fn);
goto out;
}
} else {
if (cert->purpose == CERT_PURPOSE_TA) {
warnx("%s: RFC 6487, 4.8.7: TA cert with AIA", fn);
goto out;
}
}
if (sia == 0) {
if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
;
} else if (filemode && cert->purpose == CERT_PURPOSE_EE &&
rtype_from_file_extension(fn) == RTYPE_RSC) {
;
} else {
warnx("%s: RFC 6487, 4.8.8: cert without SIA", fn);
goto out;
}
} else {
if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
warnx("%s: RFC 8209, 3.1.3.3: BGPsec Router cert "
"with SIA", fn);
goto out;
}
}
if (cp == 0) {
warnx("%s: RFC 6487, 4.8.9: missing certificate policy", fn);
goto out;
}
if (ip == 0 && as == 0) {
warnx("%s: RFC 6487, 4.8.10 and 4.8.11: cert without "
"IP or AS resources", fn);
goto out;
}
if (cert->purpose == CERT_PURPOSE_TA) {
if (x509_any_inherits(cert->x509)) {
warnx("%s: RFC 8630, 2.3: Trust Anchor INRs "
"must not inherit", fn);
goto out;
}
if (cert->num_ips == 0 && cert->num_ases == 0) {
warnx("%s: RFC 8630, 2.3: Trust Anchor INR set "
"must not be empty", fn);
goto out;
}
}
if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
if (ip != 0) {
warnx("%s: RFC 8209, 3.1.3.4: BGPsec Router cert "
"with IP resources", fn);
goto out;
}
if (as == 0) {
warnx("%s: RFC 8209, 3.1.3.5: BGPsec Router cert "
"without AS resources", fn);
goto out;
}
if (cert_as_inherit(cert)) {
warnx("%s: RFC 8209, 3.1.3.5: BGPsec Router cert "
"with inherit element", fn);
goto out;
}
if (!cert_has_one_as(cert)) {
warnx("%s: BGPsec Router certs with more than one "
"AS number are not supported", fn);
goto out;
}
}
return 1;
dup:
warnx("%s: RFC 5280 section 4.2: duplicate extension: %s", fn,
nid2str(nid));
out:
return 0;
}
static struct cert *
cert_parse_internal(const char *fn, X509 *x)
{
struct cert *cert;
const ASN1_INTEGER *serial;
const ASN1_BIT_STRING *issuer_uid = NULL, *subject_uid = NULL;
if ((cert = calloc(1, sizeof(*cert))) == NULL)
err(1, NULL);
cert->x509 = x;
if (!cert_check_purpose(fn, cert))
goto out;
if (X509_get_version(x) != 2) {
warnx("%s: RFC 6487 4.1: X.509 version must be v3", fn);
goto out;
}
if ((serial = X509_get0_serialNumber(x)) == NULL) {
warnx("%s: RFC 6487 4.2: missing serialNumber", fn);
goto out;
}
if (!x509_valid_seqnum(fn, "RFC 6487 4.2: serialNumber", serial))
goto out;
if (!cert_check_sigalg(fn, cert))
goto out;
if (!cert_check_subject_and_issuer(fn, cert))
goto out;
if (!cert_check_validity_period(fn, cert))
goto out;
if (!cert_check_spki(fn, cert))
goto out;
X509_get0_uids(x, &issuer_uid, &subject_uid);
if (issuer_uid != NULL || subject_uid != NULL) {
warnx("%s: issuer or subject unique identifiers not allowed",
fn);
goto out;
}
if (!cert_parse_extensions(fn, cert))
goto out;
return cert;
out:
cert_free(cert);
return NULL;
}
struct cert *
cert_parse_ee_cert(const char *fn, int talid, X509 *x)
{
struct cert *cert = NULL;
if (!X509_up_ref(x))
goto out;
if ((cert = cert_parse_internal(fn, x)) == NULL)
goto out;
cert->talid = talid;
if (cert->purpose != CERT_PURPOSE_EE) {
warnx("%s: expected EE cert, got %s", fn,
purpose2str(cert->purpose));
goto out;
}
if (!constraints_validate(fn, cert))
goto out;
return cert;
out:
cert_free(cert);
return NULL;
}
static struct cert *
cert_deserialize_and_parse(const char *fn, const unsigned char *der, size_t len)
{
struct cert *cert = NULL;
const unsigned char *oder;
X509 *x = NULL;
oder = der;
if ((x = d2i_X509(NULL, &der, len)) == NULL) {
warnx("%s: d2i_X509", fn);
goto out;
}
if (der != oder + len) {
warnx("%s: %td bytes trailing garbage", fn, oder + len - der);
goto out;
}
if (!X509_up_ref(x)) {
warnx("%s: X509_up_ref failed", fn);
goto out;
}
if ((cert = cert_parse_internal(fn, x)) == NULL)
goto out;
X509_free(x);
return cert;
out:
cert_free(cert);
X509_free(x);
return NULL;
}
struct cert *
cert_parse_ca_or_brk(const char *fn, const unsigned char *der, size_t len)
{
struct cert *cert = NULL;
if (der == NULL)
return NULL;
if ((cert = cert_deserialize_and_parse(fn, der, len)) == NULL)
goto out;
if (cert->purpose != CERT_PURPOSE_CA &&
cert->purpose != CERT_PURPOSE_BGPSEC_ROUTER) {
warnx("%s: want CA or BGPsec Router cert, got %s",
fn, purpose2str(cert->purpose));
goto out;
}
return cert;
out:
cert_free(cert);
return NULL;
}
struct cert *
cert_parse_filemode(const char *fn, const unsigned char *der, size_t len)
{
struct cert *cert = NULL;
if (der == NULL)
return NULL;
if ((cert = cert_deserialize_and_parse(fn, der, len)) == NULL)
goto out;
if (cert->purpose == CERT_PURPOSE_EE) {
warnx("%s: unexpected EE cert", fn);
goto out;
}
return cert;
out:
cert_free(cert);
return NULL;
}
static int
ta_check_pubkey(const char *fn, struct cert *cert, const unsigned char *spki,
size_t spkisz)
{
EVP_PKEY *cert_pkey, *tal_pkey;
int rv = 0;
tal_pkey = d2i_PUBKEY(NULL, &spki, spkisz);
if (tal_pkey == NULL) {
warnx("%s: RFC 6487 (trust anchor): bad TAL pubkey", fn);
goto out;
}
if ((cert_pkey = X509_get0_pubkey(cert->x509)) == NULL) {
warnx("%s: RFC 6487 (trust anchor): missing pubkey", fn);
goto out;
}
if (EVP_PKEY_cmp(cert_pkey, tal_pkey) != 1) {
warnx("%s: RFC 6487 (trust anchor): "
"pubkey does not match TAL pubkey", fn);
goto out;
}
if (X509_verify(cert->x509, tal_pkey) != 1) {
warnx("%s: failed to verify signature", fn);
goto out;
}
rv = 1;
out:
EVP_PKEY_free(tal_pkey);
return rv;
}
static int
ta_check_validity(const char *fn, struct cert *cert)
{
time_t now = get_current_time();
if (cert->notbefore > now) {
warnx("%s: certificate not yet valid", fn);
return 0;
}
if (cert->notafter < now) {
warnx("%s: certificate has expired", fn);
return 0;
}
return 1;
}
struct cert *
ta_validate(const char *fn, struct cert *cert, const unsigned char *spki,
size_t spkisz)
{
if (cert == NULL)
return NULL;
if (cert->purpose != CERT_PURPOSE_TA) {
warnx("%s: expected trust anchor purpose, got %s", fn,
purpose2str(cert->purpose));
goto out;
}
if (!ta_check_pubkey(fn, cert, spki, spkisz))
goto out;
if (!ta_check_validity(fn, cert))
goto out;
return cert;
out:
cert_free(cert);
return NULL;
}
struct cert *
cert_parse_ta(const char *fn, const unsigned char *der, size_t len,
const unsigned char *spki, size_t spkisz)
{
struct cert *cert = NULL;
if (der == NULL)
return NULL;
if ((cert = cert_deserialize_and_parse(fn, der, len)) == NULL)
return NULL;
return ta_validate(fn, cert, spki, spkisz);
}
void
cert_free(struct cert *cert)
{
if (cert == NULL)
return;
free(cert->crl);
free(cert->repo);
free(cert->path);
free(cert->mft);
free(cert->notify);
free(cert->signedobj);
free(cert->ips);
free(cert->ases);
free(cert->aia);
free(cert->aki);
free(cert->ski);
free(cert->pubkey);
X509_free(cert->x509);
free(cert);
}
void
cert_buffer(struct ibuf *b, const struct cert *cert)
{
io_simple_buffer(b, &cert->notafter, sizeof(cert->notafter));
io_simple_buffer(b, &cert->purpose, sizeof(cert->purpose));
io_simple_buffer(b, &cert->talid, sizeof(cert->talid));
io_simple_buffer(b, &cert->certid, sizeof(cert->certid));
io_simple_buffer(b, &cert->repoid, sizeof(cert->repoid));
io_simple_buffer(b, &cert->num_ips, sizeof(cert->num_ips));
io_simple_buffer(b, &cert->num_ases, sizeof(cert->num_ases));
io_simple_buffer(b, cert->ips, cert->num_ips * sizeof(cert->ips[0]));
io_simple_buffer(b, cert->ases, cert->num_ases * sizeof(cert->ases[0]));
io_str_buffer(b, cert->path);
if (cert->purpose == CERT_PURPOSE_TA) {
io_str_buffer(b, cert->mft);
io_opt_str_buffer(b, cert->notify);
io_str_buffer(b, cert->repo);
io_opt_str_buffer(b, cert->aki);
io_str_buffer(b, cert->ski);
} else if (cert->purpose == CERT_PURPOSE_CA) {
io_str_buffer(b, cert->mft);
io_opt_str_buffer(b, cert->notify);
io_str_buffer(b, cert->repo);
io_str_buffer(b, cert->crl);
io_str_buffer(b, cert->aia);
io_str_buffer(b, cert->aki);
io_str_buffer(b, cert->ski);
io_simple_buffer(b, &cert->mfthash, sizeof(cert->mfthash));
} else if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
io_str_buffer(b, cert->crl);
io_str_buffer(b, cert->aia);
io_str_buffer(b, cert->aki);
io_str_buffer(b, cert->ski);
io_str_buffer(b, cert->pubkey);
} else {
errx(1, "%s: unexpected %s", __func__,
purpose2str(cert->purpose));
}
}
struct cert *
cert_read(struct ibuf *b)
{
struct cert *cert;
if ((cert = calloc(1, sizeof(struct cert))) == NULL)
err(1, NULL);
io_read_buf(b, &cert->notafter, sizeof(cert->notafter));
io_read_buf(b, &cert->purpose, sizeof(cert->purpose));
io_read_buf(b, &cert->talid, sizeof(cert->talid));
io_read_buf(b, &cert->certid, sizeof(cert->certid));
io_read_buf(b, &cert->repoid, sizeof(cert->repoid));
io_read_buf(b, &cert->num_ips, sizeof(cert->num_ips));
io_read_buf(b, &cert->num_ases, sizeof(cert->num_ases));
if (cert->num_ips > 0) {
cert->ips = calloc(cert->num_ips, sizeof(cert->ips[0]));
if (cert->ips == NULL)
err(1, NULL);
io_read_buf(b, cert->ips,
cert->num_ips * sizeof(cert->ips[0]));
}
if (cert->num_ases > 0) {
cert->ases = calloc(cert->num_ases, sizeof(cert->ases[0]));
if (cert->ases == NULL)
err(1, NULL);
io_read_buf(b, cert->ases,
cert->num_ases * sizeof(cert->ases[0]));
}
io_read_str(b, &cert->path);
if (cert->purpose == CERT_PURPOSE_TA) {
io_read_str(b, &cert->mft);
io_read_opt_str(b, &cert->notify);
io_read_str(b, &cert->repo);
io_read_opt_str(b, &cert->aki);
io_read_str(b, &cert->ski);
} else if (cert->purpose == CERT_PURPOSE_CA) {
io_read_str(b, &cert->mft);
io_read_opt_str(b, &cert->notify);
io_read_str(b, &cert->repo);
io_read_str(b, &cert->crl);
io_read_str(b, &cert->aia);
io_read_str(b, &cert->aki);
io_read_str(b, &cert->ski);
io_read_buf(b, &cert->mfthash, sizeof(cert->mfthash));
} else if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
io_read_str(b, &cert->crl);
io_read_str(b, &cert->aia);
io_read_str(b, &cert->aki);
io_read_str(b, &cert->ski);
io_read_str(b, &cert->pubkey);
} else {
errx(1, "%s: unexpected %s", __func__,
purpose2str(cert->purpose));
}
return cert;
}
static inline int
authcmp(struct auth *a, struct auth *b)
{
if (a->cert->certid > b->cert->certid)
return 1;
if (a->cert->certid < b->cert->certid)
return -1;
return 0;
}
RB_GENERATE_STATIC(auth_tree, auth, entry, authcmp);
void
auth_tree_free(struct auth_tree *auths)
{
struct auth *auth, *tauth;
int error;
if ((error = pthread_rwlock_wrlock(&cert_lk)) != 0)
errx(1, "pthread_rwlock_wrlock: %s", strerror(error));
RB_FOREACH_SAFE(auth, auth_tree, auths, tauth) {
RB_REMOVE(auth_tree, auths, auth);
cert_free(auth->cert);
free(auth);
}
if ((error = pthread_rwlock_unlock(&cert_lk)) != 0)
errx(1, "pthread_rwlock_unlock: %s", strerror(error));
if ((error = pthread_rwlock_destroy(&cert_lk)) != 0)
errx(1, "pthread_rwlock_destroy: %s", strerror(error));
}
struct auth *
auth_find(struct auth_tree *auths, int id)
{
struct auth a, *f;
struct cert c;
int error;
c.certid = id;
a.cert = &c;
if ((error = pthread_rwlock_rdlock(&cert_lk)) != 0)
errx(1, "pthread_rwlock_rdlock: %s", strerror(error));
f = RB_FIND(auth_tree, auths, &a);
if ((error = pthread_rwlock_unlock(&cert_lk)) != 0)
errx(1, "pthread_rwlock_unlock: %s", strerror(error));
return f;
}
struct auth *
auth_insert(const char *fn, struct auth_tree *auths, struct cert *cert,
struct auth *issuer)
{
struct auth *na;
int error;
na = calloc(1, sizeof(*na));
if (na == NULL)
err(1, NULL);
if ((error = pthread_rwlock_wrlock(&cert_lk)) != 0)
errx(1, "pthread_rwlock_wrlock: %s", strerror(error));
if (issuer == NULL) {
cert->certid = cert->talid;
} else {
cert->certid = ++certid;
if (certid > CERTID_MAX) {
if (certid == CERTID_MAX + 1)
warnx("%s: too many certificates in store", fn);
goto fail;
}
na->depth = issuer->depth + 1;
}
if (na->depth >= MAX_CERT_DEPTH) {
warnx("%s: maximum certificate chain depth exhausted", fn);
goto fail;
}
na->issuer = issuer;
na->cert = cert;
na->any_inherits = x509_any_inherits(cert->x509);
if (RB_INSERT(auth_tree, auths, na) != NULL)
errx(1, "auth tree corrupted");
if ((error = pthread_rwlock_unlock(&cert_lk)) != 0)
errx(1, "pthread_rwlock_unlock: %s", strerror(error));
return na;
fail:
if ((error = pthread_rwlock_unlock(&cert_lk)) != 0)
errx(1, "pthread_rwlock_unlock: %s", strerror(error));
free(na);
return NULL;
}
static void
insert_brk(struct brk_tree *tree, struct cert *cert, uint32_t asid)
{
struct brk *b, *found;
if ((b = calloc(1, sizeof(*b))) == NULL)
err(1, NULL);
b->asid = asid;
b->expires = cert->notafter;
b->talid = cert->talid;
if ((b->ski = strdup(cert->ski)) == NULL)
err(1, NULL);
if ((b->pubkey = strdup(cert->pubkey)) == NULL)
err(1, NULL);
if ((found = RB_INSERT(brk_tree, tree, b)) != NULL) {
if (found->expires < b->expires) {
found->expires = b->expires;
found->talid = b->talid;
}
free(b->ski);
free(b->pubkey);
free(b);
}
}
void
cert_insert_brks(struct brk_tree *tree, struct cert *cert)
{
size_t i, asid;
for (i = 0; i < cert->num_ases; i++) {
switch (cert->ases[i].type) {
case CERT_AS_ID:
insert_brk(tree, cert, cert->ases[i].id);
break;
case CERT_AS_RANGE:
for (asid = cert->ases[i].range.min;
asid <= cert->ases[i].range.max; asid++)
insert_brk(tree, cert, asid);
break;
default:
warnx("invalid AS identifier type");
continue;
}
}
}
static inline int
brkcmp(struct brk *a, struct brk *b)
{
int rv;
if (a->asid > b->asid)
return 1;
if (a->asid < b->asid)
return -1;
rv = strcmp(a->ski, b->ski);
if (rv > 0)
return 1;
if (rv < 0)
return -1;
return strcmp(a->pubkey, b->pubkey);
}
RB_GENERATE(brk_tree, brk, entry, brkcmp);