/*
- * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no>
+ * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
+ * Copyright (C) 2010,2011 NORDUnet A/S
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/md5.h>
-#include <openssl/hmac.h>
#include <openssl/x509v3.h>
#include "debug.h"
-#include "list.h"
#include "hash.h"
#include "util.h"
-#include "gconfig.h"
+#include "hostport.h"
#include "radsecproxy.h"
-struct tls {
- char *name;
- char *cacertfile;
- char *cacertpath;
- char *certfile;
- char *certkeyfile;
- char *certkeypwd;
- uint8_t crlcheck;
- char **policyoids;
- uint32_t cacheexpiry;
- uint32_t tlsexpiry;
- uint32_t dtlsexpiry;
- X509_VERIFY_PARAM *vpm;
- SSL_CTX *tlsctx;
- SSL_CTX *dtlsctx;
-};
-
static struct hash *tlsconfs = NULL;
static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) {
debug(DBG_WARN, "verify error: num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, buf ? buf : "");
free(buf);
buf = NULL;
-
+
switch (err) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
if (err_cert) {
break;
}
}
-#ifdef DEBUG
+#ifdef DEBUG
printf("certificate verify returns %d\n", ok);
-#endif
+#endif
return ok;
}
X509_VERIFY_PARAM *pm;
ASN1_OBJECT *pobject;
int i;
-
+
pm = X509_VERIFY_PARAM_new();
if (!pm)
return NULL;
-
+
for (i = 0; poids[i]; i++) {
pobject = OBJ_txt2obj(poids[i], 0);
if (!pobject) {
static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) {
SSL_CTX *ctx = NULL;
unsigned long error;
+ long sslversion = SSLeay();
switch (type) {
-#ifdef RADPROT_TLS
+#ifdef RADPROT_TLS
case RAD_TLS:
ctx = SSL_CTX_new(TLSv1_method());
-#ifdef DEBUG
+#ifdef DEBUG
SSL_CTX_set_info_callback(ctx, ssl_info_callback);
-#endif
+#endif
break;
-#endif
-#ifdef RADPROT_DTLS
+#endif
+#ifdef RADPROT_DTLS
case RAD_DTLS:
ctx = SSL_CTX_new(DTLSv1_method());
-#ifdef DEBUG
+#ifdef DEBUG
SSL_CTX_set_info_callback(ctx, ssl_info_callback);
-#endif
+#endif
SSL_CTX_set_read_ahead(ctx, 1);
break;
-#endif
+#endif
}
if (!ctx) {
debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
return NULL;
}
-
+
+ if (sslversion < 0x00908100L ||
+ (sslversion >= 0x10000000L && sslversion < 0x10000020L)) {
+ debug(DBG_WARN, "%s: %s seems to be of a version with a "
+ "certain security critical bug (fixed in OpenSSL 0.9.8p and "
+ "1.0.0b). Disabling OpenSSL session caching for context %p.",
+ __func__, SSLeay_version(SSLEAY_VERSION), ctx);
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+ }
+
if (conf->certkeypwd) {
SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd);
SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb);
SSL_CTX *tlsgetctx(uint8_t type, struct tls *t) {
struct timeval now;
-
+
if (!t)
return NULL;
gettimeofday(&now, NULL);
-
+
switch (type) {
#ifdef RADPROT_TLS
case RAD_TLS:
return NULL;
}
+X509 *verifytlscert(SSL *ssl) {
+ X509 *cert;
+ unsigned long error;
+
+ if (SSL_get_verify_result(ssl) != X509_V_OK) {
+ debug(DBG_ERR, "verifytlscert: basic validation failed");
+ while ((error = ERR_get_error()))
+ debug(DBG_ERR, "verifytlscert: TLS: %s", ERR_error_string(error, NULL));
+ return NULL;
+ }
+
+ cert = SSL_get_peer_certificate(ssl);
+ if (!cert)
+ debug(DBG_ERR, "verifytlscert: failed to obtain certificate");
+ return cert;
+}
+
+static int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) {
+ int loc, i, l, n, r = 0;
+ char *v;
+ X509_EXTENSION *ex;
+ STACK_OF(GENERAL_NAME) *alt;
+ GENERAL_NAME *gn;
+
+ debug(DBG_DBG, "subjectaltnameaddr");
+
+ loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
+ if (loc < 0)
+ return r;
+
+ ex = X509_get_ext(cert, loc);
+ alt = X509V3_EXT_d2i(ex);
+ if (!alt)
+ return r;
+
+ n = sk_GENERAL_NAME_num(alt);
+ for (i = 0; i < n; i++) {
+ gn = sk_GENERAL_NAME_value(alt, i);
+ if (gn->type != GEN_IPADD)
+ continue;
+ r = -1;
+ v = (char *)ASN1_STRING_data(gn->d.ia5);
+ l = ASN1_STRING_length(gn->d.ia5);
+ if (((family == AF_INET && l == sizeof(struct in_addr)) || (family == AF_INET6 && l == sizeof(struct in6_addr)))
+ && !memcmp(v, &addr, l)) {
+ r = 1;
+ break;
+ }
+ }
+ GENERAL_NAMES_free(alt);
+ return r;
+}
+
+static int subjectaltnameregexp(X509 *cert, int type, char *exact, regex_t *regex) {
+ int loc, i, l, n, r = 0;
+ char *s, *v;
+ X509_EXTENSION *ex;
+ STACK_OF(GENERAL_NAME) *alt;
+ GENERAL_NAME *gn;
+
+ debug(DBG_DBG, "subjectaltnameregexp");
+
+ loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
+ if (loc < 0)
+ return r;
+
+ ex = X509_get_ext(cert, loc);
+ alt = X509V3_EXT_d2i(ex);
+ if (!alt)
+ return r;
+
+ n = sk_GENERAL_NAME_num(alt);
+ for (i = 0; i < n; i++) {
+ gn = sk_GENERAL_NAME_value(alt, i);
+ if (gn->type != type)
+ continue;
+ r = -1;
+ v = (char *)ASN1_STRING_data(gn->d.ia5);
+ l = ASN1_STRING_length(gn->d.ia5);
+ if (l <= 0)
+ continue;
+#ifdef DEBUG
+ printfchars(NULL, gn->type == GEN_DNS ? "dns" : "uri", NULL, v, l);
+#endif
+ if (exact) {
+ if (memcmp(v, exact, l))
+ continue;
+ } else {
+ s = stringcopy((char *)v, l);
+ if (!s) {
+ debug(DBG_ERR, "malloc failed");
+ continue;
+ }
+ if (regexec(regex, s, 0, NULL, 0)) {
+ free(s);
+ continue;
+ }
+ free(s);
+ }
+ r = 1;
+ break;
+ }
+ GENERAL_NAMES_free(alt);
+ return r;
+}
+
+static int cnregexp(X509 *cert, char *exact, regex_t *regex) {
+ int loc, l;
+ char *v, *s;
+ X509_NAME *nm;
+ X509_NAME_ENTRY *e;
+ ASN1_STRING *t;
+
+ nm = X509_get_subject_name(cert);
+ loc = -1;
+ for (;;) {
+ loc = X509_NAME_get_index_by_NID(nm, NID_commonName, loc);
+ if (loc == -1)
+ break;
+ e = X509_NAME_get_entry(nm, loc);
+ t = X509_NAME_ENTRY_get_data(e);
+ v = (char *) ASN1_STRING_data(t);
+ l = ASN1_STRING_length(t);
+ if (l < 0)
+ continue;
+ if (exact) {
+ if (l == strlen(exact) && !strncasecmp(exact, v, l))
+ return 1;
+ } else {
+ s = stringcopy((char *)v, l);
+ if (!s) {
+ debug(DBG_ERR, "malloc failed");
+ continue;
+ }
+ if (regexec(regex, s, 0, NULL, 0)) {
+ free(s);
+ continue;
+ }
+ free(s);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* this is a bit sloppy, should not always accept match to any */
+int certnamecheck(X509 *cert, struct list *hostports) {
+ struct list_node *entry;
+ struct hostportres *hp;
+ int r;
+ uint8_t type = 0; /* 0 for DNS, AF_INET for IPv4, AF_INET6 for IPv6 */
+ struct in6_addr addr;
+
+ for (entry = list_first(hostports); entry; entry = list_next(entry)) {
+ hp = (struct hostportres *)entry->data;
+ if (hp->prefixlen != 255) {
+ /* we disable the check for prefixes */
+ return 1;
+ }
+ if (inet_pton(AF_INET, hp->host, &addr))
+ type = AF_INET;
+ else if (inet_pton(AF_INET6, hp->host, &addr))
+ type = AF_INET6;
+ else
+ type = 0;
+
+ r = type ? subjectaltnameaddr(cert, type, &addr) : subjectaltnameregexp(cert, GEN_DNS, hp->host, NULL);
+ if (r) {
+ if (r > 0) {
+ debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", type ? "address" : "host", hp->host);
+ return 1;
+ }
+ debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", type ? "address" : "host", hp->host);
+ } else {
+ if (cnregexp(cert, hp->host, NULL)) {
+ debug(DBG_DBG, "certnamecheck: Found cn matching host %s", hp->host);
+ return 1;
+ }
+ debug(DBG_WARN, "certnamecheck: cn not matching host %s", hp->host);
+ }
+ }
+ return 0;
+}
+
+int verifyconfcert(X509 *cert, struct clsrvconf *conf) {
+ if (conf->certnamecheck) {
+ if (!certnamecheck(cert, conf->hostports)) {
+ debug(DBG_WARN, "verifyconfcert: certificate name check failed");
+ return 0;
+ }
+ debug(DBG_WARN, "verifyconfcert: certificate name check ok");
+ }
+ if (conf->certcnregex) {
+ if (cnregexp(cert, NULL, conf->certcnregex) < 1) {
+ debug(DBG_WARN, "verifyconfcert: CN not matching regex");
+ return 0;
+ }
+ debug(DBG_DBG, "verifyconfcert: CN matching regex");
+ }
+ if (conf->certuriregex) {
+ if (subjectaltnameregexp(cert, GEN_URI, NULL, conf->certuriregex) < 1) {
+ debug(DBG_WARN, "verifyconfcert: subjectaltname URI not matching regex");
+ return 0;
+ }
+ debug(DBG_DBG, "verifyconfcert: subjectaltname URI matching regex");
+ }
+ return 1;
+}
+
int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
struct tls *conf;
long int expiry = LONG_MIN;
-
+
debug(DBG_DBG, "conftls_cb called for %s", block);
-
+
conf = malloc(sizeof(struct tls));
if (!conf) {
debug(DBG_ERR, "conftls_cb: malloc failed");
return 0;
}
memset(conf, 0, sizeof(struct tls));
-
+
if (!getgenericconfig(cf, block,
"CACertificateFile", CONF_STR, &conf->cacertfile,
"CACertificatePath", CONF_STR, &conf->cacertpath,
"CRLCheck", CONF_BLN, &conf->crlcheck,
"PolicyOID", CONF_MSTR, &conf->policyoids,
NULL
- )) {
+ )) {
debug(DBG_ERR, "conftls_cb: configuration error in block %s", val);
goto errexit;
}
goto errexit;
}
conf->cacheexpiry = expiry;
- }
+ }
conf->name = stringcopy(val, 0);
if (!conf->name) {
debug(DBG_DBG, "conftls_cb: added TLS block %s", val);
return 1;
- errexit:
+errexit:
free(conf->cacertfile);
free(conf->cacertpath);
free(conf->certfile);
free(conf);
return 0;
}
+
+int addmatchcertattr(struct clsrvconf *conf) {
+ char *v;
+ regex_t **r;
+
+ if (!strncasecmp(conf->matchcertattr, "CN:/", 4)) {
+ r = &conf->certcnregex;
+ v = conf->matchcertattr + 4;
+ } else if (!strncasecmp(conf->matchcertattr, "SubjectAltName:URI:/", 20)) {
+ r = &conf->certuriregex;
+ v = conf->matchcertattr + 20;
+ } else
+ return 0;
+ if (!*v)
+ return 0;
+ /* regexp, remove optional trailing / if present */
+ if (v[strlen(v) - 1] == '/')
+ v[strlen(v) - 1] = '\0';
+ if (!*v)
+ return 0;
+
+ *r = malloc(sizeof(regex_t));
+ if (!*r) {
+ debug(DBG_ERR, "malloc failed");
+ return 0;
+ }
+ if (regcomp(*r, v, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
+ free(*r);
+ *r = NULL;
+ debug(DBG_ERR, "failed to compile regular expression %s", v);
+ return 0;
+ }
+ return 1;
+}
#else
/* Just to makes file non-empty, should rather avoid compiling this file when not needed */
static void tlsdummy() {
}
#endif
+
+/* Local Variables: */
+/* c-file-style: "stroustrup" */
+/* End: */