#include <assert.h>
#include <err.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/evp.h>
#include <openssl/x509v3.h>
#include "extern.h"
#define GENTIME_LENGTH 15
ASN1_OBJECT *certpol_oid;
ASN1_OBJECT *caissuers_oid;
ASN1_OBJECT *carepo_oid;
ASN1_OBJECT *manifest_oid;
ASN1_OBJECT *signedobj_oid;
ASN1_OBJECT *notify_oid;
ASN1_OBJECT *roa_oid;
ASN1_OBJECT *mft_oid;
ASN1_OBJECT *bgpsec_oid;
ASN1_OBJECT *cnt_type_oid;
ASN1_OBJECT *msg_dgst_oid;
ASN1_OBJECT *sign_time_oid;
ASN1_OBJECT *rsc_oid;
ASN1_OBJECT *aspa_oid;
ASN1_OBJECT *tak_oid;
ASN1_OBJECT *spl_oid;
ASN1_OBJECT *ccr_oid;
static const struct {
const char *oid;
ASN1_OBJECT **ptr;
} oid_table[] = {
{
.oid = "1.3.6.1.5.5.7.14.2",
.ptr = &certpol_oid,
},
{
.oid = "1.3.6.1.5.5.7.48.2",
.ptr = &caissuers_oid,
},
{
.oid = "1.3.6.1.5.5.7.48.5",
.ptr = &carepo_oid,
},
{
.oid = "1.3.6.1.5.5.7.48.10",
.ptr = &manifest_oid,
},
{
.oid = "1.3.6.1.5.5.7.48.11",
.ptr = &signedobj_oid,
},
{
.oid = "1.3.6.1.5.5.7.48.13",
.ptr = ¬ify_oid,
},
{
.oid = "1.2.840.113549.1.9.16.1.24",
.ptr = &roa_oid,
},
{
.oid = "1.2.840.113549.1.9.16.1.26",
.ptr = &mft_oid,
},
{
.oid = "1.3.6.1.5.5.7.3.30",
.ptr = &bgpsec_oid,
},
{
.oid = "1.2.840.113549.1.9.3",
.ptr = &cnt_type_oid,
},
{
.oid = "1.2.840.113549.1.9.4",
.ptr = &msg_dgst_oid,
},
{
.oid = "1.2.840.113549.1.9.5",
.ptr = &sign_time_oid,
},
{
.oid = "1.2.840.113549.1.9.16.1.48",
.ptr = &rsc_oid,
},
{
.oid = "1.2.840.113549.1.9.16.1.49",
.ptr = &aspa_oid,
},
{
.oid = "1.2.840.113549.1.9.16.1.50",
.ptr = &tak_oid,
},
{
.oid = "1.2.840.113549.1.9.16.1.51",
.ptr = &spl_oid,
},
{
.oid = "1.2.840.113549.1.9.16.1.54",
.ptr = &ccr_oid,
},
};
void
x509_init_oid(void)
{
size_t i;
for (i = 0; i < sizeof(oid_table) / sizeof(oid_table[0]); i++) {
*oid_table[i].ptr = OBJ_txt2obj(oid_table[i].oid, 1);
if (*oid_table[i].ptr == NULL)
errx(1, "OBJ_txt2obj for %s failed", oid_table[i].oid);
}
}
char *
x509_pubkey_get_ski(X509_PUBKEY *pubkey, const char *fn)
{
X509_ALGOR *alg = NULL;
const ASN1_OBJECT *aobj = NULL;
int ptype = 0;
const void *pval = NULL;
const unsigned char *der;
int der_len;
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int md_len = EVP_MAX_MD_SIZE;
unsigned char buf[80];
if (!X509_PUBKEY_get0_param(NULL, &der, &der_len, &alg, pubkey)) {
warnx("%s: X509_PUBKEY_get0_param failed", fn);
return NULL;
}
X509_ALGOR_get0(&aobj, &ptype, &pval, alg);
if (OBJ_obj2nid(aobj) == NID_rsaEncryption) {
if (ptype != V_ASN1_NULL || pval != NULL) {
warnx("%s: RFC 4055, 1.2, rsaEncryption "
"parameters not NULL", fn);
return NULL;
}
goto done;
}
if (!experimental) {
warnx("%s: RFC 7935, 3.1 SPKI not RSAPublicKey", fn);
return NULL;
}
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);
return NULL;
}
if (OBJ_obj2nid(pval) != NID_X9_62_prime256v1) {
warnx("%s: RFC 8608, 3.1, named curve not P-256", fn);
return NULL;
}
goto done;
}
OBJ_obj2txt(buf, sizeof(buf), aobj, 0);
warnx("%s: unsupported public key type %s", fn, buf);
return NULL;
done:
if (!EVP_Digest(der, der_len, md, &md_len, EVP_sha1(), NULL)) {
warnx("%s: EVP_Digest failed", fn);
return NULL;
}
return hex_encode(md, md_len);
}
int
x509_inherits(X509 *x)
{
STACK_OF(IPAddressFamily) *addrblk = NULL;
ASIdentifiers *asidentifiers = NULL;
const IPAddressFamily *af;
int crit, i, rc = 0;
addrblk = X509_get_ext_d2i(x, NID_sbgp_ipAddrBlock, &crit, NULL);
if (addrblk == NULL) {
if (crit != -1)
warnx("error parsing ipAddrBlocks");
goto out;
}
for (i = 0; i < sk_IPAddressFamily_num(addrblk); i++) {
af = sk_IPAddressFamily_value(addrblk, i);
if (af->ipAddressChoice->type != IPAddressChoice_inherit)
goto out;
}
asidentifiers = X509_get_ext_d2i(x, NID_sbgp_autonomousSysNum, NULL,
NULL);
if (asidentifiers == NULL) {
if (crit != -1)
warnx("error parsing asIdentifiers");
goto out;
}
if (asidentifiers->asnum == NULL || asidentifiers->rdi != NULL)
goto out;
if (!X509v3_asid_inherits(asidentifiers))
goto out;
rc = 1;
out:
ASIdentifiers_free(asidentifiers);
sk_IPAddressFamily_pop_free(addrblk, IPAddressFamily_free);
return rc;
}
int
x509_any_inherits(X509 *x)
{
STACK_OF(IPAddressFamily) *addrblk = NULL;
ASIdentifiers *asidentifiers = NULL;
int crit, rc = 0;
addrblk = X509_get_ext_d2i(x, NID_sbgp_ipAddrBlock, &crit, NULL);
if (addrblk == NULL && crit != -1)
warnx("error parsing ipAddrBlocks");
if (X509v3_addr_inherits(addrblk))
rc = 1;
asidentifiers = X509_get_ext_d2i(x, NID_sbgp_autonomousSysNum, &crit,
NULL);
if (asidentifiers == NULL && crit != -1)
warnx("error parsing asIdentifiers");
if (X509v3_asid_inherits(asidentifiers))
rc = 1;
ASIdentifiers_free(asidentifiers);
sk_IPAddressFamily_pop_free(addrblk, IPAddressFamily_free);
return rc;
}
int
x509_get_time(const ASN1_TIME *at, time_t *t)
{
struct tm tm;
*t = 0;
memset(&tm, 0, sizeof(tm));
if (at == NULL)
return 0;
if (!ASN1_TIME_to_tm(at, &tm))
return 0;
if ((*t = timegm(&tm)) == -1)
errx(1, "timegm failed");
return 1;
}
int
x509_get_generalized_time(const char *fn, const char *descr,
const ASN1_TIME *at, time_t *t)
{
if (ASN1_STRING_length(at) != GENTIME_LENGTH) {
warnx("%s: %s time format invalid", fn, descr);
return 0;
}
if (!x509_get_time(at, t)) {
warnx("%s: parsing %s failed", fn, descr);
return 0;
}
return 1;
}
int
x509_location(const char *fn, const char *descr, GENERAL_NAME *location,
char **out)
{
const unsigned char *data;
int length;
assert(*out == NULL);
if (location->type != GEN_URI) {
warnx("%s: RFC 6487 section 4.8: %s not URI", fn, descr);
return 0;
}
data = ASN1_STRING_get0_data(location->d.uniformResourceIdentifier);
length = ASN1_STRING_length(location->d.uniformResourceIdentifier);
if (!valid_uri(data, length, NULL)) {
warnx("%s: RFC 6487 section 4.8: %s bad location", fn, descr);
return 0;
}
if ((*out = strndup(data, length)) == NULL)
err(1, NULL);
return 1;
}
static int
valid_printable_octet(const uint8_t u8)
{
if ('A' <= u8 && u8 <= 'Z')
return 1;
if ('a' <= u8 && u8 <= 'z')
return 1;
if ('0' <= u8 && u8 <= '9')
return 1;
return u8 == ' ' || u8 == '\'' || u8 == '(' || u8 == ')' || u8 == '+' ||
u8 == ',' || u8 == '-' || u8 == '.' || u8 == '/' || u8 == ':' ||
u8 == '=' || u8 == '?';
}
static int
valid_printable_string(const char *fn, const char *descr, const ASN1_STRING *as)
{
const unsigned char *data;
int i, length;
if (verbose > 1 && ASN1_STRING_type(as) != V_ASN1_PRINTABLESTRING) {
warnx("%s: RFC 6487 section 4.5: %s commonName is"
" not PrintableString", fn, descr);
#if 0
return 0;
#endif
}
data = ASN1_STRING_get0_data(as);
length = ASN1_STRING_length(as);
for (i = 0; i < length; i++) {
if (!valid_printable_octet(data[i])) {
warnx("%s: invalid %s: PrintableString contains 0x%02x",
fn, descr, data[i]);
return 0;
}
}
return 1;
}
static int
x509_valid_name_internal(const char *fn, const char *descr, const X509_NAME *xn)
{
const X509_NAME_ENTRY *ne;
const ASN1_OBJECT *ao;
const ASN1_STRING *as;
int cn = 0, sn = 0;
int i, nid;
for (i = 0; i < X509_NAME_entry_count(xn); i++) {
if ((ne = X509_NAME_get_entry(xn, i)) == NULL) {
warnx("%s: X509_NAME_get_entry", fn);
return 0;
}
if ((ao = X509_NAME_ENTRY_get_object(ne)) == NULL) {
warnx("%s: X509_NAME_ENTRY_get_object", fn);
return 0;
}
nid = OBJ_obj2nid(ao);
switch (nid) {
case NID_commonName:
if (cn++ > 0) {
warnx("%s: duplicate commonName in %s",
fn, descr);
return 0;
}
if ((as = X509_NAME_ENTRY_get_data(ne)) == NULL) {
warnx("%s: X509_NAME_ENTRY_get_data failed",
fn);
return 0;
}
if (!valid_printable_string(fn, descr, as))
return 0;
break;
case NID_serialNumber:
if (sn++ > 0) {
warnx("%s: duplicate serialNumber in %s",
fn, descr);
return 0;
}
break;
case NID_undef:
warnx("%s: OBJ_obj2nid failed", fn);
return 0;
default:
warnx("%s: RFC 6487 section 4.5: unexpected attribute"
" %s in %s", fn, nid2str(nid), descr);
return 0;
}
}
if (cn == 0) {
warnx("%s: RFC 6487 section 4.5: %s missing commonName",
fn, descr);
return 0;
}
return 1;
}
int
x509_valid_subject_name(const char *fn, const X509_NAME *xn)
{
return x509_valid_name_internal(fn, "subject", xn);
}
int
x509_valid_issuer_name(const char *fn, const X509_NAME *xn)
{
return x509_valid_name_internal(fn, "issuer", xn);
}
static BIGNUM *
x509_seqnum_to_bn(const char *fn, const char *descr, const ASN1_INTEGER *i)
{
BIGNUM *bn = NULL;
if ((bn = ASN1_INTEGER_to_BN(i, NULL)) == NULL) {
warnx("%s: %s: ASN1_INTEGER_to_BN error", fn, descr);
goto out;
}
if (BN_is_negative(bn)) {
warnx("%s: %s should be non-negative", fn, descr);
goto out;
}
if (BN_num_bytes(bn) > 20 || BN_is_bit_set(bn, 159)) {
warnx("%s: %s should fit in 20 octets", fn, descr);
goto out;
}
return bn;
out:
BN_free(bn);
return NULL;
}
char *
x509_convert_seqnum(const char *fn, const char *descr, const ASN1_INTEGER *i)
{
BIGNUM *bn = NULL;
char *s = NULL;
if (i == NULL)
goto out;
if ((bn = x509_seqnum_to_bn(fn, descr, i)) == NULL)
goto out;
if ((s = BN_bn2hex(bn)) == NULL)
warnx("%s: %s: BN_bn2hex error", fn, descr);
out:
BN_free(bn);
return s;
}
int
x509_valid_seqnum(const char *fn, const char *descr, const ASN1_INTEGER *i)
{
BIGNUM *bn;
if ((bn = x509_seqnum_to_bn(fn, descr, i)) == NULL)
return 0;
BN_free(bn);
return 1;
}
int
x509_check_tbs_sigalg(const char *fn, const X509_ALGOR *tbsalg)
{
const ASN1_OBJECT *aobj = NULL;
int ptype = 0;
int nid;
X509_ALGOR_get0(&aobj, &ptype, NULL, tbsalg);
if ((nid = OBJ_obj2nid(aobj)) == NID_undef) {
warnx("%s: unknown signature type", fn);
return 0;
}
if (nid == NID_sha256WithRSAEncryption) {
if (ptype != V_ASN1_NULL && ptype != V_ASN1_UNDEF) {
warnx("%s: RFC 4055, 5: wrong ASN.1 parameters for %s",
fn, LN_sha256WithRSAEncryption);
return 0;
}
if (verbose > 1 && ptype == V_ASN1_UNDEF)
warnx("%s: RFC 4055, 5: %s without ASN.1 parameters",
fn, LN_sha256WithRSAEncryption);
return 1;
}
if (experimental && nid == NID_ecdsa_with_SHA256) {
if (ptype != V_ASN1_UNDEF) {
warnx("%s: RFC 5758, 3.2: %s encoding MUST omit "
"the parameters", fn, SN_ecdsa_with_SHA256);
return 0;
}
if (verbose)
warnx("%s: P-256 support is experimental", fn);
return 1;
}
warnx("%s: RFC 7935: wrong signature algorithm %s, want %s",
fn, nid2str(nid), LN_sha256WithRSAEncryption);
return 0;
}
time_t
x509_find_expires(time_t notafter, struct auth *a, struct crl_tree *crls)
{
struct crl *crl;
time_t expires;
expires = notafter;
for (; a != NULL; a = a->issuer) {
if (expires > a->cert->notafter)
expires = a->cert->notafter;
crl = crl_get(crls, a);
if (crl != NULL && expires > crl->nextupdate)
expires = crl->nextupdate;
}
return expires;
}