2
0
Fork 0

[mod_openssl] support key-value list for multiple listen parameters

This commit is contained in:
Stefan Bühler 2013-08-18 15:49:12 +02:00
parent 8eae9f3b50
commit dc2f0b7885
1 changed files with 173 additions and 75 deletions

View File

@ -5,9 +5,9 @@
* mod_openssl listens on separate sockets for ssl connections (https://...)
*
* Setups:
* openssl - setup a ssl socket; takes a hash of following parameters:
* openssl - setup a ssl socket; takes a hash/key-value list of following parameters:
* listen - (mandatory) the socket address (same as standard listen)
* pemfile - (mandatory) contains key and direct certificate for the key (PEM format)
* pemfile - (mandatory) contains key and certificate (+ optional chain) for the key (PEM format)
* ca-file - contains certificate chain
* ciphers - contains colon separated list of allowed ciphers
* default: "ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM"
@ -29,8 +29,11 @@
* "server-cert" - set SSL_SERVER_CERT to server certificate PEM
*
* Example config:
* setup openssl [ "listen": "0.0.0.0:8443", "pemfile": "server.pem" ];
* setup openssl [ "listen": "[::]:8443", "pemfile": "server.pem" ];
* setup openssl (
* "listen": "0.0.0.0:443",
* "listen": "0.0.0.0:443",
* "pemfile": "server.pem"
* );
* openssl.setenv "client";
*
* Author:
@ -77,6 +80,8 @@ struct openssl_connection_ctx {
};
struct openssl_context {
gint refcount;
SSL_CTX *ssl_ctx;
};
@ -87,6 +92,31 @@ enum {
SE_SERVER_CERT = 0x8
};
static openssl_context* mod_openssl_context_new(void) {
openssl_context *ctx = g_slice_new0(openssl_context);
ctx->refcount = 1;
return ctx;
}
static void mod_openssl_context_release(openssl_context *ctx) {
if (NULL == ctx) return;
assert(g_atomic_int_get(&ctx->refcount) > 0);
if (g_atomic_int_dec_and_test(&ctx->refcount)) {
if (NULL != ctx->ssl_ctx) {
SSL_CTX_free(ctx->ssl_ctx);
ctx->ssl_ctx = NULL;
}
g_slice_free(openssl_context, ctx);
}
}
static void mod_openssl_context_acquire(openssl_context *ctx) {
assert(g_atomic_int_get(&ctx->refcount) > 0);
g_atomic_int_inc(&ctx->refcount);
}
static void tcp_io_cb(liIOStream *stream, liIOStreamEvent event) {
openssl_connection_ctx *conctx = stream->data;
@ -236,10 +266,7 @@ static gboolean openssl_con_new(liConnection *con, int fd) {
static void openssl_sock_release(liServerSocket *srv_sock) {
openssl_context *ctx = srv_sock->data;
if (!ctx) return;
SSL_CTX_free(ctx->ssl_ctx);
g_slice_free(openssl_context, ctx);
mod_openssl_context_release(ctx);
}
static void openssl_setenv_X509_add_entries(liVRequest *vr, X509 *x509, const gchar *prefix, guint prefix_len) {
@ -353,8 +380,7 @@ static void openssl_setup_listen_cb(liServer *srv, int fd, gpointer data) {
UNUSED(data);
if (-1 == fd) {
SSL_CTX_free(ctx->ssl_ctx);
g_slice_free(openssl_context, ctx);
mod_openssl_context_release(ctx);
return;
}
@ -460,18 +486,24 @@ static int openssl_verify_any_cb(int ok, X509_STORE_CTX *ctx) { UNUSED(ok); UNUS
static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer userdata) {
openssl_context *ctx;
GHashTableIter hti;
gpointer hkey, hvalue;
GString *htkey;
liValue *htval;
STACK_OF(X509_NAME) *client_ca_list;
liValue *v;
guint i;
const char
*default_ciphers = "ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM",
*default_ecdh_curve = "prime256v1";
/* setup defaults */
GString *ipstr = NULL;
gboolean
have_listen_parameter = FALSE,
have_options_parameter = FALSE,
have_verify_parameter = FALSE,
have_verify_depth_parameter = FALSE,
have_verify_any_parameter = FALSE,
have_verify_require_parameter = FALSE;
const char
*ciphers = "ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM",
*pemfile = NULL, *ca_file = NULL, *client_ca_file = NULL, *dh_params_file = NULL;
*ciphers = NULL, *pemfile = NULL, *ca_file = NULL, *client_ca_file = NULL, *dh_params_file = NULL, *ecdh_curve = NULL;
long
options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_DH_USE
#ifdef SSL_OP_NO_COMPRESSION
@ -481,8 +513,6 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
| SSL_OP_SINGLE_ECDH_USE
#endif
;
const char
*ecdh_curve = "prime256v1";
guint
verify_mode = 0, verify_depth = 1;
gboolean
@ -490,66 +520,98 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
UNUSED(p); UNUSED(userdata);
if (val->type != LI_VALUE_HASH) {
ERROR(srv, "%s", "openssl expects a hash as parameter");
if (NULL == (val = li_value_to_key_value_list(val))) {
ERROR(srv, "%s", "openssl expects a hash/key-value list as parameter");
return FALSE;
}
g_hash_table_iter_init(&hti, val->data.hash);
while (g_hash_table_iter_next(&hti, &hkey, &hvalue)) {
htkey = hkey; htval = hvalue;
for (i = 0; i < val->data.list->len; ++i) {
liValue *entryKey = g_array_index(g_array_index(val->data.list, liValue*, i)->data.list, liValue*, 0);
liValue *entryValue = g_array_index(g_array_index(val->data.list, liValue*, i)->data.list, liValue*, 1);
GString *entryKeyStr;
if (g_str_equal(htkey->str, "listen")) {
if (htval->type != LI_VALUE_STRING) {
if (entryKey->type == LI_VALUE_NONE) {
ERROR(srv, "%s", "openssl doesn't take null keys");
return FALSE;
}
entryKeyStr = entryKey->data.string; /* keys are either NONE or STRING */
if (g_str_equal(entryKeyStr->str, "listen")) {
if (entryValue->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl listen expects a string as parameter");
return FALSE;
}
ipstr = htval->data.string;
} else if (g_str_equal(htkey->str, "pemfile")) {
if (htval->type != LI_VALUE_STRING) {
have_listen_parameter = TRUE;
} else if (g_str_equal(entryKeyStr->str, "pemfile")) {
if (entryValue->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl pemfile expects a string as parameter");
return FALSE;
}
pemfile = htval->data.string->str;
} else if (g_str_equal(htkey->str, "ca-file")) {
if (htval->type != LI_VALUE_STRING) {
if (NULL != pemfile) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
pemfile = entryValue->data.string->str;
} else if (g_str_equal(entryKeyStr->str, "ca-file")) {
if (entryValue->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl ca-file expects a string as parameter");
return FALSE;
}
ca_file = htval->data.string->str;
} else if (g_str_equal(htkey->str, "ciphers")) {
if (htval->type != LI_VALUE_STRING) {
if (NULL != ca_file) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
ca_file = entryValue->data.string->str;
} else if (g_str_equal(entryKeyStr->str, "ciphers")) {
if (entryValue->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl ciphers expects a string as parameter");
return FALSE;
}
ciphers = htval->data.string->str;
} else if (g_str_equal(htkey->str, "dh-params")) {
if (NULL != ciphers) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
ciphers = entryValue->data.string->str;
} else if (g_str_equal(entryKeyStr->str, "dh-params")) {
#ifndef USE_OPENSSL_DH
WARNING(srv, "%s", "the openssl library in use doesn't support DH => dh-params has no effect");
#endif
if (htval->type != LI_VALUE_STRING) {
if (entryValue->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl dh-params expects a string as parameter");
return FALSE;
}
dh_params_file = htval->data.string->str;
} else if (g_str_equal(htkey->str, "ecdh-curve")) {
if (NULL != dh_params_file) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
dh_params_file = entryValue->data.string->str;
} else if (g_str_equal(entryKeyStr->str, "ecdh-curve")) {
#ifndef USE_OPENSSL_ECDH
WARNING(srv, "%s", "the openssl library in use doesn't support ECDH => ecdh-curve has no effect");
#endif
if (htval->type != LI_VALUE_STRING) {
if (entryValue->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl ecdh-curve expects a string as parameter");
return FALSE;
}
ecdh_curve = htval->data.string->str;
} else if (g_str_equal(htkey->str, "options")) {
guint i;
if (NULL != ecdh_curve) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
ecdh_curve = entryValue->data.string->str;
} else if (g_str_equal(entryKeyStr->str, "options")) {
guint j;
if (htval->type != LI_VALUE_LIST) {
if (entryValue->type != LI_VALUE_LIST) {
ERROR(srv, "%s", "openssl options expects a list of strings as parameter");
return FALSE;
}
for (i = 0; i < htval->data.list->len; i++) {
v = g_array_index(htval->data.list, liValue*, i);
if (have_options_parameter) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
have_options_parameter = TRUE;
for (j = 0; j < entryValue->data.list->len; j++) {
v = g_array_index(entryValue->data.list, liValue*, j);
if (v->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl options expects a list of strings as parameter");
return FALSE;
@ -559,42 +621,66 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
return FALSE;
}
}
} else if (g_str_equal(htkey->str, "verify")) {
if (htval->type != LI_VALUE_BOOLEAN) {
} else if (g_str_equal(entryKeyStr->str, "verify")) {
if (entryValue->type != LI_VALUE_BOOLEAN) {
ERROR(srv, "%s", "openssl verify expects a boolean as parameter");
return FALSE;
}
if (htval->data.boolean)
if (have_verify_parameter) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
have_verify_parameter = TRUE;
if (entryValue->data.boolean)
verify_mode |= SSL_VERIFY_PEER;
} else if (g_str_equal(htkey->str, "verify-any")) {
if (htval->type != LI_VALUE_BOOLEAN) {
} else if (g_str_equal(entryKeyStr->str, "verify-any")) {
if (entryValue->type != LI_VALUE_BOOLEAN) {
ERROR(srv, "%s", "openssl verify-any expects a boolean as parameter");
return FALSE;
}
verify_any = htval->data.boolean;
} else if (g_str_equal(htkey->str, "verify-depth")) {
if (htval->type != LI_VALUE_NUMBER) {
if (have_verify_any_parameter) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
have_verify_any_parameter = TRUE;
verify_any = entryValue->data.boolean;
} else if (g_str_equal(entryKeyStr->str, "verify-depth")) {
if (entryValue->type != LI_VALUE_NUMBER) {
ERROR(srv, "%s", "openssl verify-depth expects a number as parameter");
return FALSE;
}
verify_depth = htval->data.number;
} else if (g_str_equal(htkey->str, "verify-require")) {
if (htval->type != LI_VALUE_BOOLEAN) {
if (have_verify_depth_parameter) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
have_verify_depth_parameter = TRUE;
verify_depth = entryValue->data.number;
} else if (g_str_equal(entryKeyStr->str, "verify-require")) {
if (entryValue->type != LI_VALUE_BOOLEAN) {
ERROR(srv, "%s", "openssl verify-require expects a boolean as parameter");
return FALSE;
}
if (htval->data.boolean)
if (have_verify_require_parameter) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
have_verify_require_parameter = TRUE;
if (entryValue->data.boolean)
verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
} else if (g_str_equal(htkey->str, "client-ca-file")) {
if (htval->type != LI_VALUE_STRING) {
} else if (g_str_equal(entryKeyStr->str, "client-ca-file")) {
if (entryValue->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl client-ca-file expects a string as parameter");
return FALSE;
}
client_ca_file = htval->data.string->str;
if (NULL != client_ca_file) {
ERROR(srv, "openssl unexpected duplicate parameter %s", entryKeyStr->str);
return FALSE;
}
client_ca_file = entryValue->data.string->str;
}
}
if (!ipstr) {
if (!have_listen_parameter) {
ERROR(srv, "%s", "openssl needs a listen parameter");
return FALSE;
}
@ -604,7 +690,7 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
return FALSE;
}
ctx = g_slice_new0(openssl_context);
ctx = mod_openssl_context_new();
if (NULL == (ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) {
ERROR(srv, "SSL_CTX_new: %s", ERR_error_string(ERR_get_error(), NULL));
@ -616,12 +702,10 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
goto error_free_socket;
}
if (ciphers) {
/* Disable support for low encryption ciphers */
if (SSL_CTX_set_cipher_list(ctx->ssl_ctx, ciphers) != 1) {
ERROR(srv, "SSL_CTX_set_cipher_list('%s'): %s", ciphers, ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
if (NULL == ciphers) ciphers = default_ciphers;
if (SSL_CTX_set_cipher_list(ctx->ssl_ctx, ciphers) != 1) {
ERROR(srv, "SSL_CTX_set_cipher_list('%s'): %s", ciphers, ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
#ifdef USE_OPENSSL_DH
@ -660,6 +744,7 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
EC_KEY *ecdh;
int ecdh_nid;
if (NULL == ecdh_curve) ecdh_curve = default_ecdh_curve;
ecdh_nid = OBJ_sn2nid(ecdh_curve);
if (NID_undef == ecdh_nid) {
ERROR(srv, "SSL: Unknown curve name '%s'", ecdh_curve);
@ -674,6 +759,8 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
SSL_CTX_set_tmp_ecdh(ctx->ssl_ctx, ecdh);
EC_KEY_free(ecdh);
}
#else
UNUSED(default_ecdh_curve);
#endif
if (ca_file) {
@ -725,15 +812,26 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
SSL_CTX_set_default_read_ahead(ctx->ssl_ctx, 1);
SSL_CTX_set_mode(ctx->ssl_ctx, SSL_CTX_get_mode(ctx->ssl_ctx) | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
li_angel_listen(srv, ipstr, openssl_setup_listen_cb, ctx);
for (i = 0; i < val->data.list->len; ++i) {
liValue *entryKey = g_array_index(g_array_index(val->data.list, liValue*, i)->data.list, liValue*, 0);
liValue *entryValue = g_array_index(g_array_index(val->data.list, liValue*, i)->data.list, liValue*, 1);
GString *entryKeyStr;
if (entryKey->type == LI_VALUE_NONE) continue;
entryKeyStr = entryKey->data.string; /* keys are either NONE or STRING */
if (g_str_equal(entryKeyStr->str, "listen")) {
mod_openssl_context_acquire(ctx);
li_angel_listen(srv, entryValue->data.string, openssl_setup_listen_cb, ctx);
}
}
mod_openssl_context_release(ctx);
return TRUE;
error_free_socket:
if (ctx) {
if (ctx->ssl_ctx) SSL_CTX_free(ctx->ssl_ctx);
g_slice_free(openssl_context, ctx);
}
mod_openssl_context_release(ctx);
return FALSE;
}