diff options
Diffstat (limited to 'modules/tls/tls_var.c')
-rw-r--r-- | modules/tls/tls_var.c | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/modules/tls/tls_var.c b/modules/tls/tls_var.c new file mode 100644 index 0000000..fa4ae2a --- /dev/null +++ b/modules/tls/tls_var.c @@ -0,0 +1,397 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <assert.h> +#include <apr_lib.h> +#include <apr_strings.h> + +#include <httpd.h> +#include <http_connection.h> +#include <http_core.h> +#include <http_main.h> +#include <http_log.h> +#include <ap_socache.h> + +#include <rustls.h> + +#include "tls_conf.h" +#include "tls_core.h" +#include "tls_cert.h" +#include "tls_util.h" +#include "tls_var.h" +#include "tls_version.h" + + +extern module AP_MODULE_DECLARE_DATA tls_module; +APLOG_USE_MODULE(tls); + +typedef struct { + apr_pool_t *p; + server_rec *s; + conn_rec *c; + request_rec *r; + tls_conf_conn_t *cc; + const char *name; + const char *arg_s; + int arg_i; +} tls_var_lookup_ctx_t; + +typedef const char *var_lookup(const tls_var_lookup_ctx_t *ctx); + +static const char *var_get_ssl_protocol(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->tls_protocol_name; +} + +static const char *var_get_ssl_cipher(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->tls_cipher_name; +} + +static const char *var_get_sni_hostname(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->sni_hostname; +} + +static const char *var_get_version_interface(const tls_var_lookup_ctx_t *ctx) +{ + tls_conf_server_t *sc = tls_conf_server_get(ctx->s); + return sc->global->module_version; +} + +static const char *var_get_version_library(const tls_var_lookup_ctx_t *ctx) +{ + tls_conf_server_t *sc = tls_conf_server_get(ctx->s); + return sc->global->crustls_version; +} + +static const char *var_get_false(const tls_var_lookup_ctx_t *ctx) +{ + (void)ctx; + return "false"; +} + +static const char *var_get_null(const tls_var_lookup_ctx_t *ctx) +{ + (void)ctx; + return "NULL"; +} + +static const char *var_get_client_s_dn_cn(const tls_var_lookup_ctx_t *ctx) +{ + /* There is no support in the crustls/rustls/webpki APIs to + * parse X.509 certificates and extract information about + * subject, issuer, etc. */ + if (!ctx->cc->peer_certs || !ctx->cc->peer_certs->nelts) return NULL; + return "Not Implemented"; +} + +static const char *var_get_client_verify(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->peer_certs? "SUCCESS" : "NONE"; +} + +static const char *var_get_session_resumed(const tls_var_lookup_ctx_t *ctx) +{ + return ctx->cc->session_id_cache_hit? "Resumed" : "Initial"; +} + +static const char *var_get_client_cert(const tls_var_lookup_ctx_t *ctx) +{ + const rustls_certificate *cert; + const char *pem; + apr_status_t rv; + int cert_idx = 0; + + if (ctx->arg_s) { + if (strcmp(ctx->arg_s, "chain")) return NULL; + /* ctx->arg_i'th chain cert, which is in out list as */ + cert_idx = ctx->arg_i + 1; + } + if (!ctx->cc->peer_certs || cert_idx >= ctx->cc->peer_certs->nelts) return NULL; + cert = APR_ARRAY_IDX(ctx->cc->peer_certs, cert_idx, const rustls_certificate*); + if (APR_SUCCESS != (rv = tls_cert_to_pem(&pem, ctx->p, cert))) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ctx->s, APLOGNO(10315) + "Failed to create client certificate PEM"); + return NULL; + } + return pem; +} + +static const char *var_get_server_cert(const tls_var_lookup_ctx_t *ctx) +{ + const rustls_certificate *cert; + const char *pem; + apr_status_t rv; + + if (!ctx->cc->key) return NULL; + cert = rustls_certified_key_get_certificate(ctx->cc->key, 0); + if (!cert) return NULL; + if (APR_SUCCESS != (rv = tls_cert_to_pem(&pem, ctx->p, cert))) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ctx->s, APLOGNO(10316) + "Failed to create server certificate PEM"); + return NULL; + } + return pem; +} + +typedef struct { + const char *name; + var_lookup* fn; + const char *arg_s; + int arg_i; +} var_def_t; + +static const var_def_t VAR_DEFS[] = { + { "SSL_PROTOCOL", var_get_ssl_protocol, NULL, 0 }, + { "SSL_CIPHER", var_get_ssl_cipher, NULL, 0 }, + { "SSL_TLS_SNI", var_get_sni_hostname, NULL, 0 }, + { "SSL_CLIENT_S_DN_CN", var_get_client_s_dn_cn, NULL, 0 }, + { "SSL_VERSION_INTERFACE", var_get_version_interface, NULL, 0 }, + { "SSL_VERSION_LIBRARY", var_get_version_library, NULL, 0 }, + { "SSL_SECURE_RENEG", var_get_false, NULL, 0 }, + { "SSL_COMPRESS_METHOD", var_get_null, NULL, 0 }, + { "SSL_CIPHER_EXPORT", var_get_false, NULL, 0 }, + { "SSL_CLIENT_VERIFY", var_get_client_verify, NULL, 0 }, + { "SSL_SESSION_RESUMED", var_get_session_resumed, NULL, 0 }, + { "SSL_CLIENT_CERT", var_get_client_cert, NULL, 0 }, + { "SSL_CLIENT_CHAIN_0", var_get_client_cert, "chain", 0 }, + { "SSL_CLIENT_CHAIN_1", var_get_client_cert, "chain", 1 }, + { "SSL_CLIENT_CHAIN_2", var_get_client_cert, "chain", 2 }, + { "SSL_CLIENT_CHAIN_3", var_get_client_cert, "chain", 3 }, + { "SSL_CLIENT_CHAIN_4", var_get_client_cert, "chain", 4 }, + { "SSL_CLIENT_CHAIN_5", var_get_client_cert, "chain", 5 }, + { "SSL_CLIENT_CHAIN_6", var_get_client_cert, "chain", 6 }, + { "SSL_CLIENT_CHAIN_7", var_get_client_cert, "chain", 7 }, + { "SSL_CLIENT_CHAIN_8", var_get_client_cert, "chain", 8 }, + { "SSL_CLIENT_CHAIN_9", var_get_client_cert, "chain", 9 }, + { "SSL_SERVER_CERT", var_get_server_cert, NULL, 0 }, +}; + +static const char *const TlsAlwaysVars[] = { + "SSL_TLS_SNI", + "SSL_PROTOCOL", + "SSL_CIPHER", + "SSL_CLIENT_S_DN_CN", +}; + +/* what mod_ssl defines, plus server cert and client cert DN and SAN entries */ +static const char *const StdEnvVars[] = { + "SSL_VERSION_INTERFACE", /* implemented: module version string */ + "SSL_VERSION_LIBRARY", /* implemented: crustls/rustls version string */ + "SSL_SECURE_RENEG", /* implemented: always "false" */ + "SSL_COMPRESS_METHOD", /* implemented: always "NULL" */ + "SSL_CIPHER_EXPORT", /* implemented: always "false" */ + "SSL_CIPHER_USEKEYSIZE", + "SSL_CIPHER_ALGKEYSIZE", + "SSL_CLIENT_VERIFY", /* implemented: always "SUCCESS" or "NONE" */ + "SSL_CLIENT_M_VERSION", + "SSL_CLIENT_M_SERIAL", + "SSL_CLIENT_V_START", + "SSL_CLIENT_V_END", + "SSL_CLIENT_V_REMAIN", + "SSL_CLIENT_S_DN", + "SSL_CLIENT_I_DN", + "SSL_CLIENT_A_KEY", + "SSL_CLIENT_A_SIG", + "SSL_CLIENT_CERT_RFC4523_CEA", + "SSL_SERVER_M_VERSION", + "SSL_SERVER_M_SERIAL", + "SSL_SERVER_V_START", + "SSL_SERVER_V_END", + "SSL_SERVER_S_DN", + "SSL_SERVER_I_DN", + "SSL_SERVER_A_KEY", + "SSL_SERVER_A_SIG", + "SSL_SESSION_ID", /* not implemented: highly sensitive data we do not expose */ + "SSL_SESSION_RESUMED", /* implemented: if our cache was hit successfully */ +}; + +/* Cert related variables, export when TLSOption ExportCertData is set */ +static const char *const ExportCertVars[] = { + "SSL_CLIENT_CERT", /* implemented: */ + "SSL_CLIENT_CHAIN_0", /* implemented: */ + "SSL_CLIENT_CHAIN_1", /* implemented: */ + "SSL_CLIENT_CHAIN_2", /* implemented: */ + "SSL_CLIENT_CHAIN_3", /* implemented: */ + "SSL_CLIENT_CHAIN_4", /* implemented: */ + "SSL_CLIENT_CHAIN_5", /* implemented: */ + "SSL_CLIENT_CHAIN_6", /* implemented: */ + "SSL_CLIENT_CHAIN_7", /* implemented: */ + "SSL_CLIENT_CHAIN_8", /* implemented: */ + "SSL_CLIENT_CHAIN_9", /* implemented: */ + "SSL_SERVER_CERT", /* implemented: */ +}; + +void tls_var_init_lookup_hash(apr_pool_t *pool, apr_hash_t *map) +{ + const var_def_t *def; + apr_size_t i; + + (void)pool; + for (i = 0; i < TLS_DIM(VAR_DEFS); ++i) { + def = &VAR_DEFS[i]; + apr_hash_set(map, def->name, APR_HASH_KEY_STRING, def); + } +} + +static const char *invoke(var_def_t* def, tls_var_lookup_ctx_t *ctx) +{ + if (TLS_CONN_ST_IS_ENABLED(ctx->cc)) { + const char *val = ctx->cc->subprocess_env? + apr_table_get(ctx->cc->subprocess_env, def->name) : NULL; + if (val && *val) return val; + ctx->arg_s = def->arg_s; + ctx->arg_i = def->arg_i; + return def->fn(ctx); + } + return NULL; +} + +static void set_var( + tls_var_lookup_ctx_t *ctx, apr_hash_t *lookups, apr_table_t *table) +{ + var_def_t* def = apr_hash_get(lookups, ctx->name, APR_HASH_KEY_STRING); + if (def) { + const char *val = invoke(def, ctx); + if (val && *val) { + apr_table_setn(table, ctx->name, val); + } + } +} + +const char *tls_var_lookup( + apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name) +{ + const char *val = NULL; + tls_conf_server_t *sc; + var_def_t* def; + + ap_assert(p); + ap_assert(name); + s = s? s : (r? r->server : (c? c->base_server : NULL)); + c = c? c : (r? r->connection : NULL); + + sc = tls_conf_server_get(s? s : ap_server_conf); + def = apr_hash_get(sc->global->var_lookups, name, APR_HASH_KEY_STRING); + if (def) { + tls_var_lookup_ctx_t ctx; + ctx.p = p; + ctx.s = s; + ctx.c = c; + ctx.r = r; + ctx.cc = c? tls_conf_conn_get(c->master? c->master : c) : NULL; + ctx.cc = c? tls_conf_conn_get(c->master? c->master : c) : NULL; + ctx.name = name; + val = invoke(def, &ctx); + ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c, "tls lookup of var '%s' -> '%s'", name, val); + } + return val; +} + +static void add_vars(apr_table_t *env, conn_rec *c, server_rec *s, request_rec *r) +{ + tls_conf_server_t *sc; + tls_conf_dir_t *dc, *sdc; + tls_var_lookup_ctx_t ctx; + apr_size_t i; + int overlap; + + sc = tls_conf_server_get(s); + dc = r? tls_conf_dir_get(r) : tls_conf_dir_server_get(s); + sdc = r? tls_conf_dir_server_get(s): dc; + ctx.p = r? r->pool : c->pool; + ctx.s = s; + ctx.c = c; + ctx.r = r; + ctx.cc = tls_conf_conn_get(c->master? c->master : c); + /* Can we re-use the precomputed connection values? */ + overlap = (r && ctx.cc->subprocess_env && r->server == ctx.cc->server); + if (overlap) { + apr_table_overlap(env, ctx.cc->subprocess_env, APR_OVERLAP_TABLES_SET); + } + else { + apr_table_setn(env, "HTTPS", "on"); + for (i = 0; i < TLS_DIM(TlsAlwaysVars); ++i) { + ctx.name = TlsAlwaysVars[i]; + set_var(&ctx, sc->global->var_lookups, env); + } + } + if (dc->std_env_vars == TLS_FLAG_TRUE) { + for (i = 0; i < TLS_DIM(StdEnvVars); ++i) { + ctx.name = StdEnvVars[i]; + set_var(&ctx, sc->global->var_lookups, env); + } + } + else if (overlap && sdc->std_env_vars == TLS_FLAG_TRUE) { + /* Remove variables added on connection init that are disabled here */ + for (i = 0; i < TLS_DIM(StdEnvVars); ++i) { + apr_table_unset(env, StdEnvVars[i]); + } + } + if (dc->export_cert_vars == TLS_FLAG_TRUE) { + for (i = 0; i < TLS_DIM(ExportCertVars); ++i) { + ctx.name = ExportCertVars[i]; + set_var(&ctx, sc->global->var_lookups, env); + } + } + else if (overlap && sdc->std_env_vars == TLS_FLAG_TRUE) { + /* Remove variables added on connection init that are disabled here */ + for (i = 0; i < TLS_DIM(ExportCertVars); ++i) { + apr_table_unset(env, ExportCertVars[i]); + } + } + } + +apr_status_t tls_var_handshake_done(conn_rec *c) +{ + tls_conf_conn_t *cc; + tls_conf_server_t *sc; + apr_status_t rv = APR_SUCCESS; + + cc = tls_conf_conn_get(c); + if (!TLS_CONN_ST_IS_ENABLED(cc)) goto cleanup; + + sc = tls_conf_server_get(cc->server); + if (cc->peer_certs && sc->var_user_name) { + cc->user_name = tls_var_lookup(c->pool, cc->server, c, NULL, sc->var_user_name); + if (!cc->user_name) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cc->server, APLOGNO(10317) + "Failed to set r->user to '%s'", sc->var_user_name); + } + } + cc->subprocess_env = apr_table_make(c->pool, 5); + add_vars(cc->subprocess_env, c, cc->server, NULL); + +cleanup: + return rv; +} + +int tls_var_request_fixup(request_rec *r) +{ + conn_rec *c = r->connection; + tls_conf_conn_t *cc; + + cc = tls_conf_conn_get(c->master? c->master : c); + if (!TLS_CONN_ST_IS_ENABLED(cc)) goto cleanup; + if (cc->user_name) { + /* why is r->user a char* and not const? */ + r->user = apr_pstrdup(r->pool, cc->user_name); + } + add_vars(r->subprocess_env, c, r->server, r); + +cleanup: + return DECLINED; +} |