Skip to content
Permalink
1.9.2-mpi
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
/* Copyright (c) 2007-2009, UNINETT AS
* Copyright (c) 2010-2011,2015-2016, NORDUnet A/S */
/* See LICENSE for licensing information. */
#if defined(RADPROT_TLS) || defined(RADPROT_DTLS)
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <regex.h>
#include <libgen.h>
#include <pthread.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/md5.h>
#include <openssl/x509v3.h>
#include <assert.h>
#include "debug.h"
#include "hash.h"
#include "util.h"
#include "hostport.h"
#include "radsecproxy.h"
static struct hash *tlsconfs = NULL;
#define COOKIE_SECRET_LENGTH 16
static unsigned char cookie_secret[COOKIE_SECRET_LENGTH];
static uint8_t cookie_secret_initialized = 0;
struct certattrmatch {
int (*matchfn)(GENERAL_NAME *, struct certattrmatch *);
int type;
char * exact;
regex_t *regex;
ASN1_OBJECT *oid;
struct in6_addr ipaddr;
int af;
char * debugname;
};
/* callbacks for making OpenSSL < 1.1 thread safe */
#if OPENSSL_VERSION_NUMBER < 0x10100000
static pthread_mutex_t *ssl_locks = NULL;
#if OPENSSL_VERSION_NUMBER < 0x10000000
unsigned long ssl_thread_id() {
return (unsigned long)pthread_self();
}
#else
void ssl_thread_id(CRYPTO_THREADID *id) {
CRYPTO_THREADID_set_numeric(id, (unsigned long)pthread_self());
}
#endif
void ssl_locking_callback(int mode, int type, const char *file, int line) {
if (mode & CRYPTO_LOCK)
pthread_mutex_lock(&ssl_locks[type]);
else
pthread_mutex_unlock(&ssl_locks[type]);
}
#endif
void sslinit() {
#if OPENSSL_VERSION_NUMBER < 0x10100000
int i;
SSL_library_init();
ssl_locks = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
if (!ssl_locks)
debugx(1, DBG_ERR, "malloc failed");
for (i = 0; i < CRYPTO_num_locks(); i++) {
pthread_mutex_init(&ssl_locks[i], NULL);
}
#if OPENSSL_VERSION_NUMBER < 0x10000000
CRYPTO_set_id_callback(ssl_thread_id);
#else
CRYPTO_THREADID_set_callback(ssl_thread_id);
#endif
CRYPTO_set_locking_callback(ssl_locking_callback);
SSL_load_error_strings();
#else
OPENSSL_init_ssl(0, NULL);
#endif
}
/**
* Print a human readable form of X509_NAME
*
* This is a direct replacement for X509_NAME_oneline() which should no longer be used.
*
* @param name The X509_Name to be printed.
*/
static char *print_x509_name(X509_NAME *name) {
BIO *bio;
char *buf;
bio = BIO_new(BIO_s_mem());
if (!bio) {
debug(DBG_ERR, "getcertsubject: BIO_new failed");
return NULL;
}
X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253);
buf = malloc(BIO_number_written(bio)+1);
if (buf) {
BIO_read(bio, buf, BIO_number_written(bio));
buf[BIO_number_written(bio)] = '\0';
} else {
debug(DBG_ERR, "getcertsubject: malloc failed");
}
BIO_free(bio);
return buf;
}
static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) {
int pwdlen = strlen(userdata);
if (rwflag != 0 || pwdlen > size) /* not for decryption or too large */
return 0;
memcpy(buf, userdata, pwdlen);
return pwdlen;
}
static int verify_cb(int ok, X509_STORE_CTX *ctx) {
char *buf = NULL;
X509 *err_cert;
int err, depth;
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
if (depth > MAX_CERT_DEPTH) {
ok = 0;
err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
X509_STORE_CTX_set_error(ctx, err);
}
if (!ok) {
if (err_cert)
buf = print_x509_name(X509_get_subject_name(err_cert));
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) {
buf = print_x509_name(X509_get_issuer_name(err_cert));
if (buf) {
debug(DBG_WARN, "\tIssuer=%s", buf);
free(buf);
buf = NULL;
}
}
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
debug(DBG_WARN, "\tCertificate not yet valid");
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
debug(DBG_WARN, "Certificate has expired");
break;
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
debug(DBG_WARN, "Certificate no longer valid (after notAfter)");
break;
case X509_V_ERR_NO_EXPLICIT_POLICY:
debug(DBG_WARN, "No Explicit Certificate Policy");
break;
}
}
#ifdef DEBUG
printf("certificate verify returns %d\n", ok);
#endif
return ok;
}
static int cookie_calculate_hash(struct sockaddr *peer, time_t time, uint8_t *result, unsigned int *resultlength) {
uint8_t *buf;
int length;
length = SOCKADDRP_SIZE(peer) + sizeof(time_t);
buf = OPENSSL_malloc(length);
if (!buf) {
debug(DBG_ERR, "cookie_calculate_hash: malloc failed");
return 0;
}
memcpy(buf, &time, sizeof(time_t));
memcpy(buf+sizeof(time_t), peer, SOCKADDRP_SIZE(peer));
HMAC(EVP_sha256(), (const void*) cookie_secret, COOKIE_SECRET_LENGTH,
buf, length, result, resultlength);
OPENSSL_free(buf);
return 1;
}
static int cookie_generate_cb(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) {
struct sockaddr_storage peer;
struct timeval now;
uint8_t result[EVP_MAX_MD_SIZE];
unsigned int resultlength;
if (!cookie_secret_initialized) {
if (!RAND_bytes(cookie_secret, COOKIE_SECRET_LENGTH))
debugx(1,DBG_ERR, "cookie_generate_cg: error generating random secret");
cookie_secret_initialized = 1;
}
if (BIO_dgram_get_peer(SSL_get_rbio(ssl), &peer) <= 0)
return 0;
gettimeofday(&now, NULL);
if (!cookie_calculate_hash((struct sockaddr *)&peer, now.tv_sec, result, &resultlength))
return 0;
memcpy(cookie, &now.tv_sec, sizeof(time_t));
memcpy(cookie + sizeof(time_t), result, resultlength);
*cookie_len = resultlength + sizeof(time_t);
return 1;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000
static int cookie_verify_cb(SSL *ssl, unsigned char *cookie, unsigned int cookie_len) {
#else
static int cookie_verify_cb(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len) {
#endif
struct sockaddr_storage peer;
struct timeval now;
time_t cookie_time;
uint8_t result[EVP_MAX_MD_SIZE];
unsigned int resultlength;
if (!cookie_secret_initialized)
return 0;
if (cookie_len < sizeof(time_t)) {
debug(DBG_DBG, "cookie_verify_cb: cookie too short. ignoring.");
return 0;
}
gettimeofday(&now, NULL);
cookie_time = *(time_t *)cookie;
if (now.tv_sec - cookie_time > 5) {
debug(DBG_DBG, "cookie_verify_cb: cookie invalid or older than 5s. ignoring.");
return 0;
}
if (BIO_dgram_get_peer(SSL_get_rbio(ssl), &peer) <= 0)
return 0;
if (!cookie_calculate_hash((struct sockaddr *)&peer, cookie_time, result, &resultlength))
return 0;
if (resultlength + sizeof(time_t) != cookie_len) {
debug(DBG_DBG, "cookie_verify_cb: invalid cookie length. ignoring.");
return 0;
}
if (memcmp(cookie + sizeof(time_t), result, resultlength)) {
debug(DBG_DBG, "cookie_verify_cb: cookie not valid. ignoring.");
return 0;
}
return 1;
}
#ifdef DEBUG
static void ssl_info_callback(const SSL *ssl, int where, int ret) {
const char *s;
int w;
w = where & ~SSL_ST_MASK;
if (w & SSL_ST_CONNECT)
s = "SSL_connect";
else if (w & SSL_ST_ACCEPT)
s = "SSL_accept";
else
s = "undefined";
if (where & SSL_CB_LOOP)
debug(DBG_DBG, "%s:%s\n", s, SSL_state_string_long(ssl));
else if (where & SSL_CB_ALERT) {
s = (where & SSL_CB_READ) ? "read" : "write";
debug(DBG_DBG, "SSL3 alert %s:%s:%s\n", s, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
}
else if (where & SSL_CB_EXIT) {
if (ret == 0)
debug(DBG_DBG, "%s:failed in %s\n", s, SSL_state_string_long(ssl));
else if (ret < 0)
debug(DBG_DBG, "%s:error in %s\n", s, SSL_state_string_long(ssl));
}
}
#endif
static X509_VERIFY_PARAM *createverifyparams(char **poids) {
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) {
X509_VERIFY_PARAM_free(pm);
return NULL;
}
X509_VERIFY_PARAM_add0_policy(pm, pobject);
}
X509_VERIFY_PARAM_set_flags(pm, X509_V_FLAG_POLICY_CHECK | X509_V_FLAG_EXPLICIT_POLICY);
return pm;
}
static int tlsaddcacrl(SSL_CTX *ctx, struct tls *conf) {
STACK_OF(X509_NAME) *calist;
X509_STORE *x509_s;
unsigned long error;
SSL_CTX_set_cert_store(ctx, X509_STORE_new());
if (!SSL_CTX_load_verify_locations(ctx, conf->cacertfile, conf->cacertpath)) {
while ((error = ERR_get_error()))
debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
debug(DBG_ERR, "tlsaddcacrl: Error updating TLS context %s", conf->name);
return 0;
}
calist = conf->cacertfile ? SSL_load_client_CA_file(conf->cacertfile) : NULL;
if (!conf->cacertfile || calist) {
if (conf->cacertpath) {
if (!calist)
calist = sk_X509_NAME_new_null();
if (!SSL_add_dir_cert_subjects_to_stack(calist, conf->cacertpath)) {
sk_X509_NAME_free(calist);
calist = NULL;
}
}
}
if (!calist) {
while ((error = ERR_get_error()))
debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
debug(DBG_ERR, "tlsaddcacrl: Error adding CA subjects in TLS context %s", conf->name);
return 0;
}
ERR_clear_error(); /* add_dir_cert_subj returns errors on success */
SSL_CTX_set_client_CA_list(ctx, calist);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb);
SSL_CTX_set_verify_depth(ctx, MAX_CERT_DEPTH + 1);
SSL_CTX_set_cookie_generate_cb(ctx, cookie_generate_cb);
SSL_CTX_set_cookie_verify_cb(ctx, cookie_verify_cb);
if (conf->crlcheck || conf->vpm) {
x509_s = SSL_CTX_get_cert_store(ctx);
if (conf->crlcheck)
X509_STORE_set_flags(x509_s, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
if (conf->vpm)
X509_STORE_set1_param(x509_s, conf->vpm);
}
debug(DBG_DBG, "tlsaddcacrl: updated TLS context %s", conf->name);
return 1;
}
static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) {
SSL_CTX *ctx = NULL;
unsigned long error;
switch (type) {
#ifdef RADPROT_TLS
case RAD_TLS:
#if OPENSSL_VERSION_NUMBER >= 0x10100000
/* TLS_method() was introduced in OpenSSL 1.1.0. */
ctx = SSL_CTX_new(TLS_method());
if (conf->tlsminversion >= 0)
SSL_CTX_set_min_proto_version(ctx, conf->tlsminversion);
if (conf->tlsmaxversion >= 0)
SSL_CTX_set_max_proto_version(ctx, conf->tlsmaxversion);
#else
/* No TLS_method(), use SSLv23_method() and disable SSLv2 and SSLv3. */
ctx = SSL_CTX_new(SSLv23_method());
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
#endif
#ifdef DEBUG
SSL_CTX_set_info_callback(ctx, ssl_info_callback);
#endif
break;
#endif
#ifdef RADPROT_DTLS
case RAD_DTLS:
#if OPENSSL_VERSION_NUMBER >= 0x10002000
/* DTLS_method() seems to have been introduced in OpenSSL 1.0.2. */
ctx = SSL_CTX_new(DTLS_method());
#if OPENSSL_VERSION_NUMBER >= 0x10100000
if (conf->dtlsminversion >= 0)
SSL_CTX_set_min_proto_version(ctx, conf->dtlsminversion);
if (conf->dtlsmaxversion >= 0)
SSL_CTX_set_max_proto_version(ctx, conf->dtlsmaxversion);
#endif
#else
ctx = SSL_CTX_new(DTLSv1_method());
#endif
#ifdef DEBUG
SSL_CTX_set_info_callback(ctx, ssl_info_callback);
#endif
SSL_CTX_set_read_ahead(ctx, 1);
break;
#endif
}
if (!ctx) {
debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
return NULL;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
{
long sslversion = SSLeay();
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);
}
}
#endif
if (conf->certkeypwd) {
SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd);
SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb);
}
if (!SSL_CTX_use_certificate_chain_file(ctx, conf->certfile) ||
!SSL_CTX_use_PrivateKey_file(ctx, conf->certkeyfile, SSL_FILETYPE_PEM) ||
!SSL_CTX_check_private_key(ctx)) {
while ((error = ERR_get_error()))
debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
SSL_CTX_free(ctx);
return NULL;
}
if (conf->policyoids) {
if (!conf->vpm) {
conf->vpm = createverifyparams(conf->policyoids);
if (!conf->vpm) {
debug(DBG_ERR, "tlscreatectx: Failed to add policyOIDs in TLS context %s", conf->name);
SSL_CTX_free(ctx);
return NULL;
}
}
}
if (!tlsaddcacrl(ctx, conf)) {
if (conf->vpm) {
X509_VERIFY_PARAM_free(conf->vpm);
conf->vpm = NULL;
}
SSL_CTX_free(ctx);
return NULL;
}
if (conf->cipherlist) {
if (!SSL_CTX_set_cipher_list(ctx, conf->cipherlist)) {
debug(DBG_ERR, "tlscreatectx: Failed to set cipher list in TLS context %s", conf->name);
SSL_CTX_free(ctx);
return NULL;
}
}
#if OPENSSL_VERSION_NUMBER >= 0x10101000
if (conf->ciphersuites) {
if (!SSL_CTX_set_ciphersuites(ctx, conf->ciphersuites)) {
debug(DBG_ERR, "tlscreatectx: Failed to set ciphersuites in TLS context %s", conf->name);
SSL_CTX_free(ctx);
return NULL;
}
}
#endif
if (conf->dhparam) {
#if OPENSSL_VERSION_NUMBER >= 0x30000000
if (!SSL_CTX_set0_tmp_dh_pkey(ctx, conf->dhparam)) {
#else
if (!SSL_CTX_set_tmp_dh(ctx, conf->dhparam)) {
#endif
while ((error = ERR_get_error()))
debug(DBG_WARN, "tlscreatectx: SSL: %s", ERR_error_string(error, NULL));
debug(DBG_WARN, "tlscreatectx: Failed to set dh params. Can continue, but some ciphers might not be available.");
}
}
#if OPENSSL_VERSION_NUMBER >= 0x10101000
else {
if (!SSL_CTX_set_dh_auto(ctx, 1)) {
while ((error = ERR_get_error()))
debug(DBG_WARN, "tlscreatectx: SSL: %s", ERR_error_string(error, NULL));
debug(DBG_WARN, "tlscreatectx: Failed to set automatic dh params. Can continue, but some ciphers might not be available.");
}
}
#endif
debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name);
return ctx;
}
struct tls *tlsgettls(char *alt1, char *alt2) {
struct tls *t;
t = hash_read(tlsconfs, alt1, strlen(alt1));
if (!t && alt2)
t = hash_read(tlsconfs, alt2, strlen(alt2));
return t;
}
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:
if (t->tlsexpiry && t->tlsctx) {
if (t->tlsexpiry < now.tv_sec) {
t->tlsexpiry = now.tv_sec + t->cacheexpiry;
tlsaddcacrl(t->tlsctx, t);
}
}
if (!t->tlsctx) {
t->tlsctx = tlscreatectx(RAD_TLS, t);
if (t->cacheexpiry)
t->tlsexpiry = now.tv_sec + t->cacheexpiry;
}
return t->tlsctx;
#endif
#ifdef RADPROT_DTLS
case RAD_DTLS:
if (t->dtlsexpiry && t->dtlsctx) {
if (t->dtlsexpiry < now.tv_sec) {
t->dtlsexpiry = now.tv_sec + t->cacheexpiry;
tlsaddcacrl(t->dtlsctx, t);
}
}
if (!t->dtlsctx) {
t->dtlsctx = tlscreatectx(RAD_DTLS, t);
if (t->cacheexpiry)
t->dtlsexpiry = now.tv_sec + t->cacheexpiry;
}
return t->dtlsctx;
#endif
}
return NULL;
}
void tlsreloadcrls() {
struct tls *conf;
struct hash_entry *entry;
struct timeval now;
debug (DBG_NOTICE, "reloading CRLs");
gettimeofday(&now, NULL);
for (entry = hash_first(tlsconfs); entry; entry = hash_next(entry)) {
conf = (struct tls *)entry->data;
#ifdef RADPROT_TLS
if (conf->tlsctx) {
if (conf->tlsexpiry)
conf->tlsexpiry = now.tv_sec + conf->cacheexpiry;
tlsaddcacrl(conf->tlsctx, conf);
}
#endif
#ifdef RADPROT_DTLS
if (conf->dtlsctx) {
if (conf->dtlsexpiry)
conf->dtlsexpiry = now.tv_sec + conf->cacheexpiry;
tlsaddcacrl(conf->dtlsctx, conf);
}
#endif
}
}
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 certattr_matchrid(GENERAL_NAME *gn, struct certattrmatch *match) {
return OBJ_cmp(gn->d.registeredID, match->oid) == 0 ? 1 : 0;
}
static int certattr_matchip(GENERAL_NAME *gn, struct certattrmatch *match){
int l = ASN1_STRING_length(gn->d.iPAddress);
return (((match->af == AF_INET && l == sizeof(struct in_addr)) || (match->af == AF_INET6 && l == sizeof(struct in6_addr)))
&& !memcmp(ASN1_STRING_get0_data(gn->d.iPAddress), &match->ipaddr, l)) ? 1 : 0 ;
}
static int _general_name_regex_match(char *v, int l, struct certattrmatch *match) {
char *s;
if (l <= 0 )
return 0;
if (match->exact) {
if (l == strlen(match->exact) && memcmp(v, match->exact, l) == 0)
return 1;
return 0;
}
s = stringcopy((char *)v, l);
if (!s) {
debug(DBG_ERR, "malloc failed");
return 0;
}
debug(DBG_DBG, "matchtregex: matching %s", s);
if (regexec(match->regex, s, 0, NULL, 0) == 0) {
free(s);
return 1;
}
free(s);
return 0;
}
static int certattr_matchregex(GENERAL_NAME *gn, struct certattrmatch *match) {
return _general_name_regex_match((char *)ASN1_STRING_get0_data(gn->d.ia5), ASN1_STRING_length(gn->d.ia5), match);
}
static int certattr_matchothername(GENERAL_NAME *gn, struct certattrmatch *match) {
if (OBJ_cmp(gn->d.otherName->type_id, match->oid) != 0)
return 0;
return _general_name_regex_match((char *)ASN1_STRING_get0_data(gn->d.otherName->value->value.octet_string),
ASN1_STRING_length(gn->d.otherName->value->value.octet_string),
match);
}
static int certattr_matchcn(X509 *cert, struct certattrmatch *match){
int loc;
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);
if (_general_name_regex_match((char *) ASN1_STRING_get0_data(t), ASN1_STRING_length(t), match))
return 1;
}
return 0;
}
/* returns
1 if expected type is present and matches
0 if expected type is not present
-1 if expected type is present but does not match */
static int matchsubjaltname(X509 *cert, struct certattrmatch* match) {
GENERAL_NAME *gn;
int loc, n,i,r = 0;
char *fail = NULL, *tmp, *s;
STACK_OF(GENERAL_NAME) *alt;
/*special case: don't search in SAN, but CN field in subject */
if (match->type == -1)
return certattr_matchcn(cert, match);
loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
if (loc < 0)
return 0;
alt = X509V3_EXT_d2i(X509_get_ext(cert, loc));
if (!alt)
return 0;
n = sk_GENERAL_NAME_num(alt);
for (i = 0; i < n; i++) {
gn = sk_GENERAL_NAME_value(alt, i);
if (gn->type == match->type) {
r = match->matchfn(gn, match);
if (r)
break;
r = -1;
}
/*legacy print non-matching SAN*/
if (gn->type == GEN_DNS || gn->type == GEN_URI) {
s = stringcopy((char *)ASN1_STRING_get0_data(gn->d.ia5), ASN1_STRING_length(gn->d.ia5));
if (!s) continue;
tmp = fail;
if (asprintf(&fail, "%s%s%s", tmp ? tmp : "", tmp ? ", " : "", s) >= 0)
free(tmp);
else
fail = tmp;
free(s);
}
}
if (r<1)
debug(DBG_DBG, "matchsubjaltname: no matching Subject Alt Name found! (%s)", fail);
free(fail);
GENERAL_NAMES_free(alt);
return r;
}
int certnamecheck(X509 *cert, struct hostportres *hp) {
int r = 0;
struct certattrmatch match;
memset(&match, 0, sizeof(struct certattrmatch));
r = 0;
if (hp->prefixlen != 255) {
/* we disable the check for prefixes */
return 1;
}
if (inet_pton(AF_INET, hp->host, &match.ipaddr))
match.af = AF_INET;
else if (inet_pton(AF_INET6, hp->host, &match.ipaddr))
match.af = AF_INET6;
else
match.af = 0;
match.exact = hp->host;
if (match.af) {
match.matchfn = &certattr_matchip;
match.type = GEN_IPADD;
r = matchsubjaltname(cert, &match);
}
if (!r) {
match.matchfn = &certattr_matchregex;
match.type = GEN_DNS;
r = matchsubjaltname(cert, &match);
}
if (r) {
if (r > 0) {
debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", match.af ? "address" : "host", hp->host);
return 1;
}
debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", match.af ? "address" : "host", hp->host);
} else { /* as per RFC 6125 6.4.4: CN MUST NOT be matched if SAN is present */
if (certattr_matchcn(cert, &match)) {
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 certnamecheckany(X509 *cert, struct list *hostports) {
struct list_node *entry;
for (entry = list_first(hostports); entry; entry = list_next(entry)) {
if (certnamecheck(cert, (struct hostportres *)entry->data)) return 1;
}
return 0;
}
int verifyconfcert(X509 *cert, struct clsrvconf *conf, struct hostportres *hpconnected) {
char *subject;
int ok = 1;
struct list_node *entry;
subject = getcertsubject(cert);
debug(DBG_DBG, "verifyconfcert: verify certificate for host %s, subject %s", conf->name, subject);
if (conf->certnamecheck) {
debug(DBG_DBG, "verifyconfcert: verify hostname");
if (hpconnected) {
if (!certnamecheck(cert, hpconnected)) {
debug(DBG_WARN, "verifyconfcert: certificate name check failed for host %s (%s)", conf->name, hpconnected->host);
ok = 0;
}
} else {
if (!certnamecheckany(cert, conf->hostports)) {
debug(DBG_DBG, "verifyconfcert: no matching CN or SAN found for host %s", conf->name);
ok = 0;
}
}
}
for (entry = list_first(conf->matchcertattrs); entry; entry = list_next(entry)) {
if (matchsubjaltname(cert, (struct certattrmatch *)entry->data) < 1) {
debug(DBG_WARN, "verifyconfcert: %s not matching for host %s (%s)", ((struct certattrmatch *)entry->data)->debugname, conf->name, subject);
ok = 0;
} else {
debug(DBG_DBG, "verifyconfcert: %s matching for host %s (%s)", ((struct certattrmatch *)entry->data)->debugname, conf->name, subject);
}
}
free(subject);
return ok;
}
char *getcertsubject(X509 *cert) {
return print_x509_name(X509_get_subject_name(cert));
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000
static int parse_tls_version(const char* version) {
if (!strcasecmp("SSL3", version)) {
return SSL3_VERSION;
} else if (!strcasecmp("TLS1", version)) {
return TLS1_VERSION;
} else if (!strcasecmp("TLS1_1", version)) {
return TLS1_1_VERSION;
} else if (!strcasecmp("TLS1_2", version)) {
return TLS1_2_VERSION;
#if OPENSSL_VERSION_NUMBER >= 0x10101000
} else if (!strcasecmp("TLS1_3", version)) {
return TLS1_3_VERSION;
#endif
} else if (!strcasecmp("DTLS1", version)) {
return DTLS1_VERSION;
} else if (!strcasecmp("DTLS1_2", version)) {
return DTLS1_2_VERSION;
} else if (!strcasecmp("", version)) {
return 0;
} else {
return -1;
}
}
static int conf_tls_version(const char *version, int *min, int *max) {
char *ver, *s, *smin, *smax;
ver = stringcopy(version, strlen(version));
s = strchr(ver, ':');
if (!s) {
smin = smax = ver;
} else {
*s = '\0';
smin = ver;
smax = s+1;
}
*min = parse_tls_version(smin);
*max = parse_tls_version(smax);
free(ver);
return *min >=0 && *max >=0 && (*max == 0 || *min <= *max);
}
#endif
int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
struct tls *conf;
long int expiry = LONG_MIN;
char *tlsversion = NULL;
char *dtlsversion = NULL;
char *dhfile = NULL;
unsigned long error;
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,
"CertificateFile", CONF_STR, &conf->certfile,
"CertificateKeyFile", CONF_STR, &conf->certkeyfile,
"CertificateKeyPassword", CONF_STR, &conf->certkeypwd,
"CacheExpiry", CONF_LINT, &expiry,
"CRLCheck", CONF_BLN, &conf->crlcheck,
"PolicyOID", CONF_MSTR, &conf->policyoids,
"CipherList", CONF_STR, &conf->cipherlist,
"CipherSuites", CONF_STR, &conf->ciphersuites,
"TlsVersion", CONF_STR, &tlsversion,
"DtlsVersion", CONF_STR, &dtlsversion,
"DhFile", CONF_STR, &dhfile,
NULL
)) {
debug(DBG_ERR, "conftls_cb: configuration error in block %s", val);
goto errexit;
}
if (!conf->certfile || !conf->certkeyfile) {
debug(DBG_ERR, "conftls_cb: TLSCertificateFile and TLSCertificateKeyFile must be specified in block %s", val);
goto errexit;
}
if (!conf->cacertfile && !conf->cacertpath) {
debug(DBG_ERR, "conftls_cb: CA Certificate file or path need to be specified in block %s", val);
goto errexit;
}
if (expiry != LONG_MIN) {
if (expiry < 0) {
debug(DBG_ERR, "error in block %s, value of option CacheExpiry is %ld, may not be negative", val, expiry);
goto errexit;
}
conf->cacheexpiry = expiry;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000
/* use -1 as 'not set' value */
conf->tlsminversion = conf->tlsmaxversion = conf->dtlsminversion = conf->dtlsmaxversion = -1;
if (tlsversion) {
if(!conf_tls_version(tlsversion, &conf->tlsminversion, &conf->tlsmaxversion)) {
debug(DBG_ERR, "error in block %s, invalid TlsVersion %s", val, tlsversion);
goto errexit;
}
free (tlsversion);
tlsversion = NULL;
}
if (dtlsversion) {
if(!conf_tls_version(dtlsversion, &conf->dtlsminversion, &conf->dtlsmaxversion)) {
debug(DBG_ERR, "error in block %s, invalid DtlsVersion %s", val, dtlsversion);
goto errexit;
}
free (dtlsversion);
dtlsversion = NULL;
}
#else
if (tlsversion || dtlsversion) {
debug(DBG_ERR, "error in block %s, setting tls/dtls version requires openssl 1.1.0 or later", val);
goto errexit;
}
#endif
if (dhfile) {
#if OPENSSL_VERSION_NUMBER >= 0x30000000
BIO *bio = BIO_new_file(dhfile, "r");
if (bio) {
conf->dhparam = EVP_PKEY_new();
if (!PEM_read_bio_Parameters(bio, &conf->dhparam)) {
BIO_free(bio);
while ((error = ERR_get_error()))
debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
debug(DBG_ERR, "error in block %s: Failed to load DhFile %s.", val, dhfile);
goto errexit;
}
BIO_free(bio);
}
#else
FILE *dhfp = fopen(dhfile, "r");
if (dhfp) {
conf->dhparam = PEM_read_DHparams(dhfp, NULL, NULL, NULL);
fclose(dhfp);
if (!conf->dhparam) {
while ((error = ERR_get_error()))
debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
debug(DBG_ERR, "error in block %s: Failed to load DhFile %s.", val, dhfile);
goto errexit;
}
} else {
debug(DBG_ERR, "error in block %s, DhFile: can't open file %s", val, dhfile);
goto errexit;
}
free(dhfile);
dhfile = NULL;
#endif
}
conf->name = stringcopy(val, 0);
if (!conf->name) {
debug(DBG_ERR, "conftls_cb: malloc failed");
goto errexit;
}
pthread_mutex_init(&conf->lock, NULL);
if (!tlsconfs)
tlsconfs = hash_create();
if (!hash_insert(tlsconfs, val, strlen(val), conf)) {
debug(DBG_ERR, "conftls_cb: malloc failed");
goto errexit;
}
if (!tlsgetctx(RAD_TLS, conf))
debug(DBG_ERR, "conftls_cb: error creating ctx for TLS block %s", val);
debug(DBG_DBG, "conftls_cb: added TLS block %s", val);
return 1;
errexit:
free(conf->cacertfile);
free(conf->cacertpath);
free(conf->certfile);
free(conf->certkeyfile);
free(conf->certkeypwd);
freegconfmstr(conf->policyoids);
free(tlsversion);
free(dtlsversion);
free(dhfile);
#if OPENSSL_VERSION_NUMBER >= 0x30000000
EVP_PKEY_free(conf->dhparam);
#else
DH_free(conf->dhparam);
#endif
free(conf);
return 0;
}
static regex_t *compileregex(char *regstr) {
regex_t *result;
if (regstr[0] != '/')
return NULL;
regstr++;
if (regstr[strlen(regstr) - 1] == '/')
regstr[strlen(regstr) - 1] = '\0';
if (!*regstr)
return NULL;
result = malloc(sizeof(regex_t));
if (!result) {
debug(DBG_ERR, "malloc failed");
return NULL;
}
if (regcomp(result, regstr, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
free(result);
debug(DBG_ERR, "failed to compile regular expression %s", regstr);
return NULL;
}
return result;
}
int addmatchcertattr(struct clsrvconf *conf, const char *match) {
struct certattrmatch *certattrmatch;
char *pos, *colon, *matchcopy;
if (!conf->matchcertattrs) {
conf->matchcertattrs = list_create();
}
certattrmatch = malloc(sizeof(struct certattrmatch));
if (!certattrmatch) return 0;
memset(certattrmatch, 0, sizeof(struct certattrmatch));
matchcopy = stringcopy(match,0);
pos = matchcopy;
colon = strchr(pos, ':');
if (!colon) goto errexit;
if (strncasecmp(pos, "CN", colon - pos) == 0) {
if(!(certattrmatch->regex = compileregex(colon+1))) goto errexit;
certattrmatch->type = -1;
certattrmatch->matchfn = NULL; /*special case: don't search in SAN, but CN field in subject */
}
else if (strncasecmp(pos, "SubjectAltName", colon - pos) == 0) {
pos = colon+1;
colon = strchr(pos, ':');
if (!colon) goto errexit;
if (strncasecmp(pos, "IP", colon - pos) == 0) {
pos = colon+1;
if (inet_pton(AF_INET, pos, &certattrmatch->ipaddr))
certattrmatch->af = AF_INET;
else if (inet_pton(AF_INET6, pos, &certattrmatch->ipaddr))
certattrmatch->af = AF_INET6;
else
goto errexit;
certattrmatch->type = GEN_IPADD;
certattrmatch->matchfn = &certattr_matchip;
}
else if(strncasecmp(pos, "URI", colon - pos) == 0) {
if(!(certattrmatch->regex = compileregex(colon+1))) goto errexit;
certattrmatch->type = GEN_URI;
certattrmatch->matchfn = &certattr_matchregex;
}
else if(strncasecmp(pos, "DNS", colon - pos) == 0) {
if(!(certattrmatch->regex = compileregex(colon+1))) goto errexit;
certattrmatch->type = GEN_DNS;
certattrmatch->matchfn = &certattr_matchregex;
}
else if(strncasecmp(pos, "rID", colon - pos) == 0) {
certattrmatch->oid = OBJ_txt2obj(colon+1, 0);
if (!certattrmatch->oid) goto errexit;
certattrmatch->type = GEN_RID;
certattrmatch->matchfn = &certattr_matchrid;
}
else if(strncasecmp(pos, "otherNAme", colon - pos) == 0){
pos = colon+1;
colon = strchr(pos, ':');
if(!colon) goto errexit;
*colon = '\0';
if(!(certattrmatch->oid = OBJ_txt2obj(pos,0))) goto errexit;
if(!(certattrmatch->regex = compileregex(colon+1))) goto errexit;
certattrmatch->type = GEN_OTHERNAME;
certattrmatch->matchfn = &certattr_matchothername;
}
else goto errexit;
}
else goto errexit;
certattrmatch->debugname = stringcopy(match, 0);
if(!list_push(conf->matchcertattrs, certattrmatch)) goto errexit;
free(matchcopy);
return 1;
errexit:
free(certattrmatch);
free(matchcopy);
return 0;
}
void freematchcertattr(struct clsrvconf *conf) {
struct list_node *entry;
struct certattrmatch *match;
if (conf->matchcertattrs) {
for (entry = list_first(conf->matchcertattrs); entry; entry=list_next(entry)) {
match = ((struct certattrmatch*)entry->data);
free(match->debugname);
free(match->exact);
ASN1_OBJECT_free(match->oid);
if(match->regex)
regfree(match->regex);
free(match->regex);
}
list_destroy(conf->matchcertattrs);
conf->matchcertattrs = NULL;
}
}
int sslaccepttimeout (SSL *ssl, int timeout) {
int socket, origflags, ndesc, r = -1, sockerr = 0;
socklen_t errlen = sizeof(sockerr);
struct pollfd fds[1];
uint8_t want_write = 1;
socket = SSL_get_fd(ssl);
origflags = fcntl(socket, F_GETFL, 0);
if (origflags == -1) {
debugerrno(errno, DBG_WARN, "Failed to get flags");
return -1;
}
if (fcntl(socket, F_SETFL, origflags | O_NONBLOCK) == -1) {
debugerrno(errno, DBG_WARN, "Failed to set O_NONBLOCK");
return -1;
}
while (r < 1) {
fds[0].fd = socket;
fds[0].events = POLLIN;
if (want_write) {
fds[0].events |= POLLOUT;
want_write = 0;
}
if ((ndesc = poll(fds, 1, timeout * 1000)) < 1) {
if (ndesc == 0)
debug(DBG_DBG, "sslaccepttimeout: timeout during SSL_accept");
else
debugerrno(errno, DBG_DBG, "sslaccepttimeout: poll error");
break;
}
if (fds[0].revents & POLLERR) {
if(!getsockopt(socket, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &errlen))
debug(DBG_WARN, "SSL Accept failed: %s", strerror(sockerr));
else
debug(DBG_WARN, "SSL Accept failed: unknown error");
} else if (fds[0].revents & POLLHUP) {
debug(DBG_WARN, "SSL Accept error: hang up");
} else if (fds[0].revents & POLLNVAL) {
debug(DBG_WARN, "SSL Accept error: fd not open");
} else {
r = SSL_accept(ssl);
if (r <= 0) {
switch (SSL_get_error(ssl, r)) {
case SSL_ERROR_WANT_WRITE:
want_write = 1;
case SSL_ERROR_WANT_READ:
continue;
case SSL_ERROR_SYSCALL:
debugerrno(errno, DBG_ERR, "sslaccepttimeout: syscall error ");
}
}
}
break;
}
if (fcntl(socket, F_SETFL, origflags) == -1)
debugerrno(errno, DBG_WARN, "Failed to set original flags back");
return r;
}
int sslconnecttimeout(SSL *ssl, int timeout) {
int socket, origflags, ndesc, r = -1, sockerr = 0;
socklen_t errlen = sizeof(sockerr);
struct pollfd fds[1];
uint8_t want_write = 1;
socket = SSL_get_fd(ssl);
origflags = fcntl(socket, F_GETFL, 0);
if (origflags == -1) {
debugerrno(errno, DBG_WARN, "Failed to get flags");
return -1;
}
if (fcntl(socket, F_SETFL, origflags | O_NONBLOCK) == -1) {
debugerrno(errno, DBG_WARN, "Failed to set O_NONBLOCK");
return -1;
}
while (r < 1) {
fds[0].fd = socket;
fds[0].events = POLLIN;
if (want_write) {
fds[0].events |= POLLOUT;
want_write = 0;
}
if ((ndesc = poll(fds, 1, timeout * 1000)) < 1) {
if (ndesc == 0)
debug(DBG_DBG, "sslconnecttimeout: timeout during SSL_connect");
else
debugerrno(errno, DBG_DBG, "sslconnecttimeout: poll error");
break;
}
if (fds[0].revents & POLLERR) {
if(!getsockopt(socket, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &errlen))
debug(DBG_WARN, "SSL Connection failed: %s", strerror(sockerr));
else
debug(DBG_WARN, "SSL Connection failed: unknown error");
} else if (fds[0].revents & POLLHUP) {
debug(DBG_WARN, "SSL Connect error: hang up");
} else if (fds[0].revents & POLLNVAL) {
debug(DBG_WARN, "SSL Connect error: fd not open");
} else {
r = SSL_connect(ssl);
if (r <= 0) {
switch (SSL_get_error(ssl, r)) {
case SSL_ERROR_WANT_WRITE:
want_write = 1;
case SSL_ERROR_WANT_READ:
continue;
}
}
}
break;
}
if (fcntl(socket, F_SETFL, origflags) == -1)
debugerrno(errno, DBG_WARN, "Failed to set original flags back");
return r;
}
/**
* @brief read from ssl connection with timeout.
* In case of error, ssl connection will be closed and shutdown state is set.
*
* @param ssl SSL connection
* @param buf destination buffer
* @param num number of bytes to read
* @param timeout maximum time to wait for data, 0 waits indefinetely
* @param lock the lock to aquire before performing any operation on the ssl conneciton
* @return number of bytes received, 0 on timeout, -1 on error (connection lost)
*/
int sslreadtimeout(SSL *ssl, unsigned char *buf, int num, int timeout, pthread_mutex_t *lock) {
int ndesc, cnt = 0, len, sockerr = 0;
socklen_t errlen = sizeof(sockerr);
struct pollfd fds[1];
unsigned long error;
uint8_t want_write = 0;
assert(lock);
pthread_mutex_lock(lock);
for (len = 0; len < num; len += cnt) {
if (SSL_pending(ssl) == 0) {
fds[0].fd = SSL_get_fd(ssl);
fds[0].events = POLLIN;
if (want_write) {
fds[0].events |= POLLOUT;
want_write = 0;
}
pthread_mutex_unlock(lock);
ndesc = poll(fds, 1, timeout ? timeout * 1000 : -1);
if (ndesc == 0)
return ndesc;
pthread_mutex_lock(lock);
if (ndesc < 0 || fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
if (fds[0].revents & POLLERR) {
if(!getsockopt(SSL_get_fd(ssl), SOL_SOCKET, SO_ERROR, (void *)&sockerr, &errlen))
debug(DBG_INFO, "sslreadtimeout: connection lost: %s", strerror(sockerr));
else
debug(DBG_INFO, "sslreadtimeout: connection lost: unknown error");
} else if (fds[0].revents & POLLHUP) {
debug(DBG_INFO, "sslreadtimeout: connection lost: hang up");
} else if (fds[0].revents & POLLNVAL) {
debug(DBG_ERR, "sslreadtimeout: connection error: fd not open");
}
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
pthread_mutex_unlock(lock);
return -1;
}
}
cnt = SSL_read(ssl, buf + len, num - len);
if (cnt <= 0) {
switch (SSL_get_error(ssl, cnt)) {
case SSL_ERROR_WANT_WRITE:
want_write = 1;
/* fallthrough */
case SSL_ERROR_WANT_READ:
cnt = 0;
continue;
case SSL_ERROR_ZERO_RETURN:
debug(DBG_DBG, "sslreadtimeout: got ssl shutdown");
SSL_shutdown(ssl);
break;
case SSL_ERROR_SYSCALL:
if (errno)
debugerrno(errno, DBG_INFO, "sslreadtimeout: connection lost");
else
debug(DBG_INFO, "sslreadtimeout: connection lost: EOF");
/* fallthrough */
case SSL_ERROR_SSL:
while ((error = ERR_get_error()))
debug(DBG_ERR, "sslreadtimeout: SSL: %s", ERR_error_string(error, NULL));
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
break;
default:
debug(DBG_ERR, "sslreadtimeout: uncaught SSL error");
SSL_shutdown(ssl);
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
}
pthread_mutex_unlock(lock);
return -1;
}
}
pthread_mutex_unlock(lock);
return cnt;
}
#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: */