src/sbin/isakmpd/x509.c

1383 lines
33 KiB
C

/* $OpenBSD: x509.c,v 1.126 2024/04/28 16:43:42 florian Exp $ */
/* $EOM: x509.c,v 1.54 2001/01/16 18:42:16 ho Exp $ */
/*
* Copyright (c) 1998, 1999 Niels Provos. All rights reserved.
* Copyright (c) 1999, 2000, 2001 Niklas Hallqvist. All rights reserved.
* Copyright (c) 1999, 2000, 2001 Angelos D. Keromytis. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This code was written under funding by Ericsson Radio Systems.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <regex.h>
#include <keynote.h>
#include "cert.h"
#include "conf.h"
#include "exchange.h"
#include "hash.h"
#include "ike_auth.h"
#include "ipsec.h"
#include "log.h"
#include "dh.h"
#include "monitor.h"
#include "policy.h"
#include "sa.h"
#include "util.h"
#include "x509.h"
static u_int16_t x509_hash(u_int8_t *, size_t);
static void x509_hash_init(void);
static X509 *x509_hash_find(u_int8_t *, size_t);
static int x509_hash_enter(X509 *);
/*
* X509_STOREs do not support subjectAltNames, so we have to build
* our own hash table.
*/
/*
* XXX Actually this store is not really useful, we never use it as we have
* our own hash table. It also gets collisions if we have several certificates
* only differing in subjectAltName.
*/
static X509_STORE *x509_certs = 0;
static X509_STORE *x509_cas = 0;
static int n_x509_cas = 0;
/* Initial number of bits used as hash. */
#define INITIAL_BUCKET_BITS 6
struct x509_hash {
LIST_ENTRY(x509_hash) link;
X509 *cert;
};
static LIST_HEAD(x509_list, x509_hash) *x509_tab = 0;
/* Works both as a maximum index and a mask. */
static int bucket_mask;
/*
* Given an X509 certificate, create a KeyNote assertion where
* Issuer/Subject -> Authorizer/Licensees.
* XXX RSA-specific.
*/
int
x509_generate_kn(int id, X509 *cert)
{
static const char fmt[] = "Authorizer: \"rsa-hex:%s\"\nLicensees: \"rsa-hex:%s"
"\"\nConditions: %s >= \"%s\" && %s <= \"%s\";\n";
char *ikey = NULL, *skey = NULL, *buf = NULL;
char isname[256], subname[256];
static const char fmt2[] = "Authorizer: \"DN:%s\"\nLicensees: \"DN:%s\"\n"
"Conditions: %s >= \"%s\" && %s <= \"%s\";\n";
X509_NAME *issuer, *subject;
struct keynote_deckey dc;
X509_STORE_CTX *csc = NULL;
X509_OBJECT *obj = NULL;
X509 *icert;
RSA *key = NULL;
time_t tt;
char before[15], after[15], *timecomp, *timecomp2;
ASN1_TIME *tm;
int i;
LOG_DBG((LOG_POLICY, 90,
"x509_generate_kn: generating KeyNote policy for certificate %p",
cert));
issuer = X509_get_issuer_name(cert);
subject = X509_get_subject_name(cert);
/* Missing or self-signed, ignore cert but don't report failure. */
if (!issuer || !subject || !X509_NAME_cmp(issuer, subject))
return 1;
if (!x509_cert_get_key(cert, &key)) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: failed to get public key from cert"));
return 0;
}
dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
dc.dec_key = key;
ikey = kn_encode_key(&dc, INTERNAL_ENC_PKCS1, ENCODING_HEX,
KEYNOTE_PUBLIC_KEY);
if (keynote_errno == ERROR_MEMORY) {
log_print("x509_generate_kn: failed to get memory for "
"public key");
LOG_DBG((LOG_POLICY, 30, "x509_generate_kn: cannot get "
"subject key"));
goto fail;
}
if (!ikey) {
LOG_DBG((LOG_POLICY, 30, "x509_generate_kn: cannot get "
"subject key"));
goto fail;
}
RSA_free(key);
key = NULL;
csc = X509_STORE_CTX_new();
if (csc == NULL) {
log_print("x509_generate_kn: failed to get memory for "
"certificate store");
goto fail;
}
obj = X509_OBJECT_new();
if (obj == NULL) {
log_print("x509_generate_kn: failed to get memory for "
"certificate object");
goto fail;
}
/* Now find issuer's certificate so we can get the public key. */
X509_STORE_CTX_init(csc, x509_cas, cert, NULL);
if (X509_STORE_get_by_subject(csc, X509_LU_X509, issuer, obj) !=
X509_LU_X509) {
X509_STORE_CTX_cleanup(csc);
X509_STORE_CTX_init(csc, x509_certs, cert, NULL);
if (X509_STORE_get_by_subject(csc, X509_LU_X509, issuer, obj)
!= X509_LU_X509) {
X509_STORE_CTX_cleanup(csc);
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: no certificate found for "
"issuer"));
goto fail;
}
}
X509_STORE_CTX_free(csc);
csc = NULL;
icert = X509_OBJECT_get0_X509(obj);
if (icert == NULL) {
LOG_DBG((LOG_POLICY, 30, "x509_generate_kn: "
"missing certificates, cannot construct X509 chain"));
goto fail;
}
if (!x509_cert_get_key(icert, &key)) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: failed to get public key from cert"));
goto fail;
}
X509_OBJECT_free(obj);
obj = NULL;
dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
dc.dec_key = key;
skey = kn_encode_key(&dc, INTERNAL_ENC_PKCS1, ENCODING_HEX,
KEYNOTE_PUBLIC_KEY);
if (keynote_errno == ERROR_MEMORY) {
log_error("x509_generate_kn: failed to get memory for public "
"key");
LOG_DBG((LOG_POLICY, 30, "x509_generate_kn: cannot get issuer "
"key"));
goto fail;
}
if (!skey) {
LOG_DBG((LOG_POLICY, 30, "x509_generate_kn: cannot get issuer "
"key"));
goto fail;
}
RSA_free(key);
key = NULL;
if (((tm = X509_get_notBefore(cert)) == NULL) ||
(tm->type != V_ASN1_UTCTIME &&
tm->type != V_ASN1_GENERALIZEDTIME)) {
struct tm *ltm;
tt = time(NULL);
if ((ltm = localtime(&tt)) == NULL) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid local time"));
goto fail;
}
strftime(before, 14, "%Y%m%d%H%M%S", ltm);
timecomp = "LocalTimeOfDay";
} else {
if (tm->data[tm->length - 1] == 'Z') {
timecomp = "GMTTimeOfDay";
i = tm->length - 2;
} else {
timecomp = "LocalTimeOfDay";
i = tm->length - 1;
}
for (; i >= 0; i--) {
if (tm->data[i] < '0' || tm->data[i] > '9') {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid data in "
"NotValidBefore time field"));
goto fail;
}
}
if (tm->type == V_ASN1_UTCTIME) {
if ((tm->length < 10) || (tm->length > 13)) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid length "
"of NotValidBefore time field (%d)",
tm->length));
goto fail;
}
/* Validity checks. */
if ((tm->data[2] != '0' && tm->data[2] != '1') ||
(tm->data[2] == '0' && tm->data[3] == '0') ||
(tm->data[2] == '1' && tm->data[3] > '2') ||
(tm->data[4] > '3') ||
(tm->data[4] == '0' && tm->data[5] == '0') ||
(tm->data[4] == '3' && tm->data[5] > '1') ||
(tm->data[6] > '2') ||
(tm->data[6] == '2' && tm->data[7] > '3') ||
(tm->data[8] > '5')) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid value in "
"NotValidBefore time field"));
goto fail;
}
/* Stupid UTC tricks. */
if (tm->data[0] < '5')
snprintf(before, sizeof before, "20%s",
tm->data);
else
snprintf(before, sizeof before, "19%s",
tm->data);
} else { /* V_ASN1_GENERICTIME */
if ((tm->length < 12) || (tm->length > 15)) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid length of "
"NotValidBefore time field (%d)",
tm->length));
goto fail;
}
/* Validity checks. */
if ((tm->data[4] != '0' && tm->data[4] != '1') ||
(tm->data[4] == '0' && tm->data[5] == '0') ||
(tm->data[4] == '1' && tm->data[5] > '2') ||
(tm->data[6] > '3') ||
(tm->data[6] == '0' && tm->data[7] == '0') ||
(tm->data[6] == '3' && tm->data[7] > '1') ||
(tm->data[8] > '2') ||
(tm->data[8] == '2' && tm->data[9] > '3') ||
(tm->data[10] > '5')) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid value in "
"NotValidBefore time field"));
goto fail;
}
snprintf(before, sizeof before, "%s", tm->data);
}
/* Fix missing seconds. */
if (tm->length < 12) {
before[12] = '0';
before[13] = '0';
}
/* This will overwrite trailing 'Z'. */
before[14] = '\0';
}
tm = X509_get_notAfter(cert);
if (tm == NULL ||
(tm->type != V_ASN1_UTCTIME &&
tm->type != V_ASN1_GENERALIZEDTIME)) {
struct tm *ltm;
tt = time(0);
if ((ltm = localtime(&tt)) == NULL) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid local time"));
goto fail;
}
strftime(after, 14, "%Y%m%d%H%M%S", ltm);
timecomp2 = "LocalTimeOfDay";
} else {
if (tm->data[tm->length - 1] == 'Z') {
timecomp2 = "GMTTimeOfDay";
i = tm->length - 2;
} else {
timecomp2 = "LocalTimeOfDay";
i = tm->length - 1;
}
for (; i >= 0; i--) {
if (tm->data[i] < '0' || tm->data[i] > '9') {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid data in "
"NotValidAfter time field"));
goto fail;
}
}
if (tm->type == V_ASN1_UTCTIME) {
if ((tm->length < 10) || (tm->length > 13)) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid length of "
"NotValidAfter time field (%d)",
tm->length));
goto fail;
}
/* Validity checks. */
if ((tm->data[2] != '0' && tm->data[2] != '1') ||
(tm->data[2] == '0' && tm->data[3] == '0') ||
(tm->data[2] == '1' && tm->data[3] > '2') ||
(tm->data[4] > '3') ||
(tm->data[4] == '0' && tm->data[5] == '0') ||
(tm->data[4] == '3' && tm->data[5] > '1') ||
(tm->data[6] > '2') ||
(tm->data[6] == '2' && tm->data[7] > '3') ||
(tm->data[8] > '5')) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid value in "
"NotValidAfter time field"));
goto fail;
}
/* Stupid UTC tricks. */
if (tm->data[0] < '5')
snprintf(after, sizeof after, "20%s",
tm->data);
else
snprintf(after, sizeof after, "19%s",
tm->data);
} else { /* V_ASN1_GENERICTIME */
if ((tm->length < 12) || (tm->length > 15)) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid length of "
"NotValidAfter time field (%d)",
tm->length));
goto fail;
}
/* Validity checks. */
if ((tm->data[4] != '0' && tm->data[4] != '1') ||
(tm->data[4] == '0' && tm->data[5] == '0') ||
(tm->data[4] == '1' && tm->data[5] > '2') ||
(tm->data[6] > '3') ||
(tm->data[6] == '0' && tm->data[7] == '0') ||
(tm->data[6] == '3' && tm->data[7] > '1') ||
(tm->data[8] > '2') ||
(tm->data[8] == '2' && tm->data[9] > '3') ||
(tm->data[10] > '5')) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: invalid value in "
"NotValidAfter time field"));
goto fail;
}
snprintf(after, sizeof after, "%s", tm->data);
}
/* Fix missing seconds. */
if (tm->length < 12) {
after[12] = '0';
after[13] = '0';
}
after[14] = '\0'; /* This will overwrite trailing 'Z' */
}
if (asprintf(&buf, fmt, skey, ikey, timecomp, before, timecomp2,
after) == -1) {
log_error("x509_generate_kn: "
"failed to allocate memory for KeyNote credential");
goto fail;
}
free(ikey);
ikey = NULL;
free(skey);
skey = NULL;
if (kn_add_assertion(id, buf, strlen(buf), ASSERT_FLAG_LOCAL) == -1) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: failed to add new KeyNote credential"));
goto fail;
}
/* We could print the assertion here, but log_print() truncates... */
LOG_DBG((LOG_POLICY, 60, "x509_generate_kn: added credential"));
free(buf);
buf = NULL;
if (!X509_NAME_oneline(issuer, isname, 256)) {
LOG_DBG((LOG_POLICY, 50,
"x509_generate_kn: "
"X509_NAME_oneline (issuer, ...) failed"));
goto fail;
}
if (!X509_NAME_oneline(subject, subname, 256)) {
LOG_DBG((LOG_POLICY, 50,
"x509_generate_kn: "
"X509_NAME_oneline (subject, ...) failed"));
goto fail;
}
if (asprintf(&buf, fmt2, isname, subname, timecomp, before,
timecomp2, after) == -1) {
log_error("x509_generate_kn: malloc failed");
return 0;
}
if (kn_add_assertion(id, buf, strlen(buf), ASSERT_FLAG_LOCAL) == -1) {
LOG_DBG((LOG_POLICY, 30,
"x509_generate_kn: failed to add new KeyNote credential"));
goto fail;
}
LOG_DBG((LOG_POLICY, 80, "x509_generate_kn: added credential:\n%s",
buf));
free(buf);
return 1;
fail:
X509_STORE_CTX_free(csc);
X509_OBJECT_free(obj);
free(buf);
free(skey);
free(ikey);
if (key)
RSA_free(key);
return 0;
}
static u_int16_t
x509_hash(u_int8_t *id, size_t len)
{
u_int16_t bucket = 0;
size_t i;
/* XXX We might resize if we are crossing a certain threshold. */
for (i = 4; i < (len & ~1); i += 2) {
/* Doing it this way avoids alignment problems. */
bucket ^= (id[i] + 1) * (id[i + 1] + 257);
}
/* Hash in the last character of odd length IDs too. */
if (i < len)
bucket ^= (id[i] + 1) * (id[i] + 257);
bucket &= bucket_mask;
return bucket;
}
static void
x509_hash_init(void)
{
struct x509_hash *certh;
int i;
bucket_mask = (1 << INITIAL_BUCKET_BITS) - 1;
/* If reinitializing, free existing entries. */
if (x509_tab) {
for (i = 0; i <= bucket_mask; i++)
for (certh = LIST_FIRST(&x509_tab[i]); certh;
certh = LIST_FIRST(&x509_tab[i])) {
LIST_REMOVE(certh, link);
free(certh);
}
free(x509_tab);
}
x509_tab = calloc(bucket_mask + 1, sizeof(struct x509_list));
if (!x509_tab)
log_fatal("x509_hash_init: malloc (%lu) failed",
(bucket_mask + 1) *
(unsigned long)sizeof(struct x509_list));
for (i = 0; i <= bucket_mask; i++) {
LIST_INIT(&x509_tab[i]);
}
}
/* Lookup a certificate by an ID blob. */
static X509 *
x509_hash_find(u_int8_t *id, size_t len)
{
struct x509_hash *cert;
u_int8_t **cid;
u_int32_t *clen;
int n, i, id_found;
for (cert = LIST_FIRST(&x509_tab[x509_hash(id, len)]); cert;
cert = LIST_NEXT(cert, link)) {
if (!x509_cert_get_subjects(cert->cert, &n, &cid, &clen))
continue;
id_found = 0;
for (i = 0; i < n; i++) {
LOG_DBG_BUF((LOG_CRYPTO, 70, "cert_cmp", id, len));
LOG_DBG_BUF((LOG_CRYPTO, 70, "cert_cmp", cid[i],
clen[i]));
/*
* XXX This identity predicate needs to be
* understood.
*/
if (clen[i] == len && id[0] == cid[i][0] &&
memcmp(id + 4, cid[i] + 4, len - 4) == 0) {
id_found++;
break;
}
}
cert_free_subjects(n, cid, clen);
if (!id_found)
continue;
LOG_DBG((LOG_CRYPTO, 70, "x509_hash_find: return X509 %p",
cert->cert));
return cert->cert;
}
LOG_DBG((LOG_CRYPTO, 70,
"x509_hash_find: no certificate matched query"));
return 0;
}
static int
x509_hash_enter(X509 *cert)
{
u_int16_t bucket = 0;
u_int8_t **id;
u_int32_t *len;
struct x509_hash *certh;
int n, i;
if (!x509_cert_get_subjects(cert, &n, &id, &len)) {
log_print("x509_hash_enter: cannot retrieve subjects");
return 0;
}
for (i = 0; i < n; i++) {
certh = calloc(1, sizeof *certh);
if (!certh) {
cert_free_subjects(n, id, len);
log_error("x509_hash_enter: calloc (1, %lu) failed",
(unsigned long)sizeof *certh);
return 0;
}
certh->cert = cert;
bucket = x509_hash(id[i], len[i]);
LIST_INSERT_HEAD(&x509_tab[bucket], certh, link);
LOG_DBG((LOG_CRYPTO, 70,
"x509_hash_enter: cert %p added to bucket %d",
cert, bucket));
}
cert_free_subjects(n, id, len);
return 1;
}
/* X509 Certificate Handling functions. */
int
x509_read_from_dir(X509_STORE *ctx, char *name, int hash, int *pcount)
{
FILE *certfp;
X509 *cert;
struct stat sb;
char fullname[PATH_MAX];
char file[PATH_MAX];
int fd;
if (strlen(name) >= sizeof fullname - 1) {
log_print("x509_read_from_dir: directory name too long");
return 0;
}
LOG_DBG((LOG_CRYPTO, 40, "x509_read_from_dir: reading certs from %s",
name));
if (monitor_req_readdir(name) == -1) {
LOG_DBG((LOG_CRYPTO, 10,
"x509_read_from_dir: opendir (\"%s\") failed: %s",
name, strerror(errno)));
return 0;
}
while ((fd = monitor_readdir(file, sizeof file)) != -1) {
LOG_DBG((LOG_CRYPTO, 60,
"x509_read_from_dir: reading certificate %s",
file));
if (fstat(fd, &sb) == -1) {
log_error("x509_read_from_dir: fstat failed");
close(fd);
continue;
}
if (!S_ISREG(sb.st_mode)) {
close(fd);
continue;
}
if ((certfp = fdopen(fd, "r")) == NULL) {
log_error("x509_read_from_dir: fdopen failed");
close(fd);
continue;
}
#if SSLEAY_VERSION_NUMBER >= 0x00904100L
cert = PEM_read_X509(certfp, NULL, NULL, NULL);
#else
cert = PEM_read_X509(certfp, NULL, NULL);
#endif
fclose(certfp);
if (cert == NULL) {
log_print("x509_read_from_dir: PEM_read_X509 "
"failed for %s", file);
continue;
}
if (pcount != NULL)
(*pcount)++;
if (!X509_STORE_add_cert(ctx, cert)) {
/*
* This is actually expected if we have several
* certificates only differing in subjectAltName,
* which is not an something that is strange.
* Consider multi-homed machines.
*/
LOG_DBG((LOG_CRYPTO, 50,
"x509_read_from_dir: X509_STORE_add_cert failed "
"for %s", file));
}
if (hash)
if (!x509_hash_enter(cert))
log_print("x509_read_from_dir: "
"x509_hash_enter (%s) failed",
file);
}
return 1;
}
/* XXX share code with x509_read_from_dir() ? */
int
x509_read_crls_from_dir(X509_STORE *ctx, char *name)
{
FILE *crlfp;
X509_CRL *crl;
struct stat sb;
char fullname[PATH_MAX];
char file[PATH_MAX];
int fd;
if (strlen(name) >= sizeof fullname - 1) {
log_print("x509_read_crls_from_dir: directory name too long");
return 0;
}
LOG_DBG((LOG_CRYPTO, 40, "x509_read_crls_from_dir: reading CRLs "
"from %s", name));
if (monitor_req_readdir(name) == -1) {
LOG_DBG((LOG_CRYPTO, 10, "x509_read_crls_from_dir: opendir "
"(\"%s\") failed: %s", name, strerror(errno)));
return 0;
}
strlcpy(fullname, name, sizeof fullname);
while ((fd = monitor_readdir(file, sizeof file)) != -1) {
LOG_DBG((LOG_CRYPTO, 60, "x509_read_crls_from_dir: reading "
"CRL %s", file));
if (fstat(fd, &sb) == -1) {
log_error("x509_read_crls_from_dir: fstat failed");
close(fd);
continue;
}
if (!S_ISREG(sb.st_mode)) {
close(fd);
continue;
}
if ((crlfp = fdopen(fd, "r")) == NULL) {
log_error("x509_read_crls_from_dir: fdopen failed");
close(fd);
continue;
}
crl = PEM_read_X509_CRL(crlfp, NULL, NULL, NULL);
fclose(crlfp);
if (crl == NULL) {
log_print("x509_read_crls_from_dir: "
"PEM_read_X509_CRL failed for %s",
file);
continue;
}
if (!X509_STORE_add_crl(ctx, crl)) {
LOG_DBG((LOG_CRYPTO, 50, "x509_read_crls_from_dir: "
"X509_STORE_add_crl failed for %s", file));
continue;
}
/*
* XXX This is to make x509_cert_validate set this (and
* XXX another) flag when validating certificates. Currently,
* XXX OpenSSL defaults to reject an otherwise valid
* XXX certificate (chain) if these flags are set but there
* XXX are no CRLs to check. The current workaround is to only
* XXX set the flags if we actually loaded some CRL data.
*/
X509_STORE_set_flags(ctx, X509_V_FLAG_CRL_CHECK);
}
return 1;
}
/* Initialize our databases and load our own certificates. */
int
x509_cert_init(void)
{
char *dirname;
x509_hash_init();
/* Process CA certificates we will trust. */
dirname = conf_get_str("X509-certificates", "CA-directory");
if (!dirname) {
log_print("x509_cert_init: no CA-directory");
return 0;
}
/* Free if already initialized. */
if (x509_cas)
X509_STORE_free(x509_cas);
x509_cas = X509_STORE_new();
if (!x509_cas) {
log_print("x509_cert_init: creating new X509_STORE failed");
return 0;
}
if (!x509_read_from_dir(x509_cas, dirname, 0, &n_x509_cas)) {
log_print("x509_cert_init: x509_read_from_dir failed");
return 0;
}
/* Process client certificates we will accept. */
dirname = conf_get_str("X509-certificates", "Cert-directory");
if (!dirname) {
log_print("x509_cert_init: no Cert-directory");
return 0;
}
/* Free if already initialized. */
if (x509_certs)
X509_STORE_free(x509_certs);
x509_certs = X509_STORE_new();
if (!x509_certs) {
log_print("x509_cert_init: creating new X509_STORE failed");
return 0;
}
if (!x509_read_from_dir(x509_certs, dirname, 1, NULL)) {
log_print("x509_cert_init: x509_read_from_dir failed");
return 0;
}
return 1;
}
int
x509_crl_init(void)
{
/*
* XXX I'm not sure if the method to use CRLs in certificate validation
* is valid for OpenSSL versions prior to 0.9.7. For now, simply do not
* support it.
*/
char *dirname;
dirname = conf_get_str("X509-certificates", "CRL-directory");
if (!dirname) {
log_print("x509_crl_init: no CRL-directory");
return 0;
}
if (!x509_read_crls_from_dir(x509_cas, dirname)) {
LOG_DBG((LOG_MISC, 10,
"x509_crl_init: x509_read_crls_from_dir failed"));
return 0;
}
return 1;
}
void *
x509_cert_get(u_int8_t *asn, u_int32_t len)
{
return x509_from_asn(asn, len);
}
int
x509_cert_validate(void *scert)
{
X509_STORE_CTX *csc;
X509_NAME *issuer, *subject;
X509 *cert = (X509 *) scert;
EVP_PKEY *key;
int res, err, flags;
/*
* Validate the peer certificate by checking with the CA certificates
* we trust.
*/
csc = X509_STORE_CTX_new();
if (csc == NULL) {
log_print("x509_cert_validate: failed to get memory for "
"certificate store");
return 0;
}
X509_STORE_CTX_init(csc, x509_cas, cert, NULL);
/* XXX See comment in x509_read_crls_from_dir. */
flags = X509_VERIFY_PARAM_get_flags(X509_STORE_get0_param(x509_cas));
if (flags & X509_V_FLAG_CRL_CHECK) {
X509_STORE_CTX_set_flags(csc, X509_V_FLAG_CRL_CHECK);
X509_STORE_CTX_set_flags(csc, X509_V_FLAG_CRL_CHECK_ALL);
}
res = X509_verify_cert(csc);
err = X509_STORE_CTX_get_error(csc);
X509_STORE_CTX_free(csc);
/*
* Return if validation succeeded or self-signed certs are not
* accepted.
*
* XXX X509_verify_cert seems to return -1 if the validation should be
* retried somehow. We take this as an error and give up.
*/
if (res > 0)
return 1;
else if (res < 0 ||
(res == 0 && err != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)) {
if (err)
log_print("x509_cert_validate: %.100s",
X509_verify_cert_error_string(err));
return 0;
} else if (!conf_get_str("X509-certificates", "Accept-self-signed")) {
if (err)
log_print("x509_cert_validate: %.100s",
X509_verify_cert_error_string(err));
return 0;
}
issuer = X509_get_issuer_name(cert);
subject = X509_get_subject_name(cert);
if (!issuer || !subject || X509_NAME_cmp(issuer, subject))
return 0;
key = X509_get_pubkey(cert);
if (!key) {
log_print("x509_cert_validate: could not get public key from "
"self-signed cert");
return 0;
}
if (X509_verify(cert, key) == -1) {
log_print("x509_cert_validate: self-signed cert is bad");
return 0;
}
return 1;
}
int
x509_cert_insert(int id, void *scert)
{
X509 *cert;
int res;
cert = X509_dup((X509 *)scert);
if (!cert) {
log_print("x509_cert_insert: X509_dup failed");
return 0;
}
if (x509_generate_kn(id, cert) == 0) {
LOG_DBG((LOG_POLICY, 50,
"x509_cert_insert: x509_generate_kn failed"));
X509_free(cert);
return 0;
}
res = x509_hash_enter(cert);
if (!res)
X509_free(cert);
return res;
}
static struct x509_hash *
x509_hash_lookup(X509 *cert)
{
struct x509_hash *certh;
int i;
for (i = 0; i <= bucket_mask; i++)
for (certh = LIST_FIRST(&x509_tab[i]); certh;
certh = LIST_NEXT(certh, link))
if (certh->cert == cert)
return certh;
return 0;
}
void
x509_cert_free(void *cert)
{
struct x509_hash *certh = x509_hash_lookup((X509 *) cert);
if (certh)
LIST_REMOVE(certh, link);
X509_free((X509 *) cert);
}
/* Validate the BER Encoding of a RDNSequence in the CERT_REQ payload. */
int
x509_certreq_validate(u_int8_t *asn, u_int32_t len)
{
int res = 1;
#if 0
struct norm_type name = SEQOF("issuer", RDNSequence);
if (!asn_template_clone(&name, 1) ||
(asn = asn_decode_sequence(asn, len, &name)) == 0) {
log_print("x509_certreq_validate: can not decode 'acceptable "
"CA' info");
res = 0;
}
asn_free(&name);
#endif
/* XXX - not supported directly in SSL - later. */
return res;
}
/* Decode the BER Encoding of a RDNSequence in the CERT_REQ payload. */
int
x509_certreq_decode(void **pdata, u_int8_t *asn, u_int32_t len)
{
#if 0
/* XXX This needs to be done later. */
struct norm_type aca = SEQOF("aca", RDNSequence);
struct norm_type *tmp;
struct x509_aca naca, *ret;
if (!asn_template_clone(&aca, 1) ||
(asn = asn_decode_sequence(asn, len, &aca)) == 0) {
log_print("x509_certreq_decode: can not decode 'acceptable "
"CA' info");
goto fail;
}
bzero(&naca, sizeof(naca));
tmp = asn_decompose("aca.RelativeDistinguishedName."
"AttributeValueAssertion", &aca);
if (!tmp)
goto fail;
x509_get_attribval(tmp, &naca.name1);
tmp = asn_decompose("aca.RelativeDistinguishedName[1]"
".AttributeValueAssertion", &aca);
if (tmp)
x509_get_attribval(tmp, &naca.name2);
asn_free(&aca);
ret = malloc(sizeof(struct x509_aca));
if (ret)
memcpy(ret, &naca, sizeof(struct x509_aca));
else {
log_error("x509_certreq_decode: malloc (%lu) failed",
(unsigned long) sizeof(struct x509_aca));
x509_free_aca(&aca);
}
return ret;
fail:
asn_free(&aca);
#endif
return 1;
}
void
x509_free_aca(void *blob)
{
struct x509_aca *aca = blob;
if (aca != NULL) {
free(aca->name1.type);
free(aca->name1.val);
free(aca->name2.type);
free(aca->name2.val);
}
}
X509 *
x509_from_asn(u_char *asn, u_int len)
{
BIO *certh;
X509 *scert = 0;
certh = BIO_new(BIO_s_mem());
if (!certh) {
log_error("x509_from_asn: BIO_new (BIO_s_mem ()) failed");
return 0;
}
if (BIO_write(certh, asn, len) == -1) {
log_error("x509_from_asn: BIO_write failed\n");
goto end;
}
scert = d2i_X509_bio(certh, NULL);
if (!scert) {
log_print("x509_from_asn: d2i_X509_bio failed\n");
goto end;
}
end:
BIO_free(certh);
return scert;
}
/*
* Obtain a certificate from an acceptable CA.
* XXX We don't check if the certificate we find is from an accepted CA.
*/
int
x509_cert_obtain(u_int8_t *id, size_t id_len, void *data, u_int8_t **cert,
u_int32_t *certlen)
{
struct x509_aca *aca = data;
X509 *scert;
if (aca)
LOG_DBG((LOG_CRYPTO, 60, "x509_cert_obtain: "
"acceptable certificate authorities here"));
/* We need our ID to find a certificate. */
if (!id) {
log_print("x509_cert_obtain: ID is missing");
return 0;
}
scert = x509_hash_find(id, id_len);
if (!scert)
return 0;
x509_serialize(scert, cert, certlen);
if (!*cert)
return 0;
return 1;
}
/* Returns a pointer to the subjectAltName information of X509 certificate. */
int
x509_cert_subjectaltname(X509 *scert, u_int8_t **altname, u_int32_t *len)
{
X509_EXTENSION *subjectaltname;
ASN1_OCTET_STRING *sanasn1data;
u_int8_t *sandata;
int extpos, santype, sanlen;
extpos = X509_get_ext_by_NID(scert, NID_subject_alt_name, -1);
if (extpos == -1) {
log_print("x509_cert_subjectaltname: "
"certificate does not contain subjectAltName");
return 0;
}
subjectaltname = X509_get_ext(scert, extpos);
sanasn1data = X509_EXTENSION_get_data(subjectaltname);
if (!subjectaltname || !sanasn1data || !sanasn1data->data ||
sanasn1data->length < 4) {
log_print("x509_cert_subjectaltname: invalid "
"subjectaltname extension");
return 0;
}
/* SSL does not handle unknown ASN stuff well, do it by hand. */
sandata = sanasn1data->data;
santype = sandata[2] & 0x3f;
sanlen = sandata[3];
sandata += 4;
/*
* The test here used to be !=, but some certificates can include
* extra stuff in subjectAltName, so we will just take the first
* salen bytes, and not worry about what follows.
*/
if (sanlen + 4 > sanasn1data->length) {
log_print("x509_cert_subjectaltname: subjectaltname invalid "
"length");
return 0;
}
*len = sanlen;
*altname = sandata;
return santype;
}
int
x509_cert_get_subjects(void *scert, int *cnt, u_int8_t ***id,
u_int32_t **id_len)
{
X509 *cert = scert;
X509_NAME *subject;
int type;
u_int8_t *altname;
u_int32_t altlen;
u_int8_t *buf = 0;
unsigned char *ubuf;
int i;
*id = 0;
*id_len = 0;
/*
* XXX There can be a collection of subjectAltNames, but for now I
* only return the subjectName and a single subjectAltName, if
* present.
*/
type = x509_cert_subjectaltname(cert, &altname, &altlen);
if (!type) {
*cnt = 1;
altlen = 0;
} else
*cnt = 2;
*id = calloc(*cnt, sizeof **id);
if (!*id) {
log_print("x509_cert_get_subject: malloc (%lu) failed",
*cnt * (unsigned long)sizeof **id);
*cnt = 0;
goto fail;
}
*id_len = calloc(*cnt, sizeof **id_len);
if (!*id_len) {
log_print("x509_cert_get_subject: malloc (%lu) failed",
*cnt * (unsigned long)sizeof **id_len);
goto fail;
}
/* Stash the subjectName into the first slot. */
subject = X509_get_subject_name(cert);
if (!subject)
goto fail;
(*id_len)[0] =
ISAKMP_ID_DATA_OFF + i2d_X509_NAME(subject, NULL) -
ISAKMP_GEN_SZ;
(*id)[0] = malloc((*id_len)[0]);
if (!(*id)[0]) {
log_print("x509_cert_get_subject: malloc (%d) failed",
(*id_len)[0]);
goto fail;
}
SET_ISAKMP_ID_TYPE((*id)[0] - ISAKMP_GEN_SZ, IPSEC_ID_DER_ASN1_DN);
ubuf = (*id)[0] + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ;
i2d_X509_NAME(subject, &ubuf);
if (altlen) {
/* Stash the subjectAltName into the second slot. */
buf = malloc(altlen + ISAKMP_ID_DATA_OFF);
if (!buf) {
log_print("x509_cert_get_subject: malloc (%d) failed",
altlen + ISAKMP_ID_DATA_OFF);
goto fail;
}
switch (type) {
case X509v3_DNS_NAME:
SET_ISAKMP_ID_TYPE(buf, IPSEC_ID_FQDN);
break;
case X509v3_RFC_NAME:
SET_ISAKMP_ID_TYPE(buf, IPSEC_ID_USER_FQDN);
break;
case X509v3_IP_ADDR:
/*
* XXX I dislike the numeric constants, but I don't
* know what we should use otherwise.
*/
switch (altlen) {
case 4:
SET_ISAKMP_ID_TYPE(buf, IPSEC_ID_IPV4_ADDR);
break;
case 16:
SET_ISAKMP_ID_TYPE(buf, IPSEC_ID_IPV6_ADDR);
break;
default:
log_print("x509_cert_get_subject: invalid "
"subjectAltName IPaddress length %d ",
altlen);
goto fail;
}
break;
}
SET_IPSEC_ID_PROTO(buf + ISAKMP_ID_DOI_DATA_OFF, 0);
SET_IPSEC_ID_PORT(buf + ISAKMP_ID_DOI_DATA_OFF, 0);
memcpy(buf + ISAKMP_ID_DATA_OFF, altname, altlen);
(*id_len)[1] = ISAKMP_ID_DATA_OFF + altlen - ISAKMP_GEN_SZ;
(*id)[1] = malloc((*id_len)[1]);
if (!(*id)[1]) {
log_print("x509_cert_get_subject: malloc (%d) failed",
(*id_len)[1]);
goto fail;
}
memcpy((*id)[1], buf + ISAKMP_GEN_SZ, (*id_len)[1]);
free(buf);
buf = 0;
}
return 1;
fail:
for (i = 0; i < *cnt; i++)
free((*id)[i]);
free(*id);
free(*id_len);
free(buf);
return 0;
}
int
x509_cert_get_key(void *scert, void *keyp)
{
X509 *cert = scert;
EVP_PKEY *key;
key = X509_get_pubkey(cert);
/* Check if we got the right key type. */
if (EVP_PKEY_id(key) != EVP_PKEY_RSA) {
log_print("x509_cert_get_key: public key is not a RSA key");
X509_free(cert);
return 0;
}
*(RSA **)keyp = RSAPublicKey_dup(EVP_PKEY_get0_RSA(key));
return *(RSA **)keyp == NULL ? 0 : 1;
}
void *
x509_cert_dup(void *scert)
{
return X509_dup(scert);
}
void
x509_serialize(void *scert, u_int8_t **data, u_int32_t *datalen)
{
u_int8_t *p;
*datalen = i2d_X509((X509 *)scert, NULL);
*data = p = malloc(*datalen);
if (!p) {
log_error("x509_serialize: malloc (%d) failed", *datalen);
return;
}
*datalen = i2d_X509((X509 *)scert, &p);
}
/* From cert to printable */
char *
x509_printable(void *cert)
{
char *s;
u_int8_t *data;
u_int32_t datalen;
x509_serialize(cert, &data, &datalen);
if (!data)
return 0;
s = raw2hex(data, datalen);
free(data);
return s;
}
/* From printable to cert */
void *
x509_from_printable(char *cert)
{
u_int8_t *buf;
int plen, ret;
void *foo;
plen = (strlen(cert) + 1) / 2;
buf = malloc(plen);
if (!buf) {
log_error("x509_from_printable: malloc (%d) failed", plen);
return 0;
}
ret = hex2raw(cert, buf, plen);
if (ret == -1) {
free(buf);
log_print("x509_from_printable: badly formatted cert");
return 0;
}
foo = x509_cert_get(buf, plen);
free(buf);
if (!foo)
log_print("x509_from_printable: "
"could not retrieve certificate");
return foo;
}
char *
x509_DN_string(u_int8_t *asn1, size_t sz)
{
X509_NAME *name;
const u_int8_t *p = asn1;
char buf[256]; /* XXX Just a guess at a maximum length. */
long len = sz;
name = d2i_X509_NAME(NULL, &p, len);
if (!name) {
log_print("x509_DN_string: d2i_X509_NAME failed");
return 0;
}
if (!X509_NAME_oneline(name, buf, sizeof buf - 1)) {
log_print("x509_DN_string: X509_NAME_oneline failed");
X509_NAME_free(name);
return 0;
}
X509_NAME_free(name);
buf[sizeof buf - 1] = '\0';
return strdup(buf);
}
/* Number of CAs we trust (to decide whether we can send CERT_REQ) */
int
x509_ca_count(void)
{
return n_x509_cas;
}