From e9915b2bdb47a0dca4daa144a41a3c23edc3a59a Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Tue, 1 Oct 2024 16:09:11 +0000 Subject: [PATCH] mod_ssl: Add SSLClientHelloVars directive which exposes various ClientHello properties in new SSL_CLIENTHELLO_* variables. * modules/ssl/ssl_engine_kernel.c (ssl_hook_Fixup_vars): Add SSL_CLIENTHELLO_* vars. (copy_clienthello_vars): New function. (ssl_callback_ClientHello): Call it when needed. * modules/ssl/ssl_engine_vars.c (ssl_var_lookup_ssl_clienthello): New function. (ssl_var_lookup_ssl): Call it for SSL_CLIENTHELLO_*. * modules/ssl/ssl_private.h (modssl_clienthello_vars): Add type. (SSLConnRec): Add clienthello_vars pointer. * modules/ssl/ssl_engine_config.c, modules/ssl/mod_ssl.c: Add handling of new SSLClientHelloVars directive. Submitted by: Charles Smutz Github: closes #483 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1921074 13f79535-47bb-0310-9956-ffa450edef68 --- changes-entries/ssl-hello-vars.txt | 3 ++ docs/manual/mod/mod_ssl.xml | 32 ++++++++++++++++ modules/ssl/mod_ssl.c | 3 ++ modules/ssl/ssl_engine_config.c | 9 +++++ modules/ssl/ssl_engine_kernel.c | 59 +++++++++++++++++++++++++++++ modules/ssl/ssl_engine_vars.c | 60 ++++++++++++++++++++++++++++++ modules/ssl/ssl_private.h | 31 ++++++++++++++- 7 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 changes-entries/ssl-hello-vars.txt diff --git a/changes-entries/ssl-hello-vars.txt b/changes-entries/ssl-hello-vars.txt new file mode 100644 index 00000000000..130a159c343 --- /dev/null +++ b/changes-entries/ssl-hello-vars.txt @@ -0,0 +1,3 @@ + *) mod_ssl: Add SSLClientHelloVars directive to expose various + ClientHello properties as SSL_CLIENTHELLO_* variables. + [Charles Smutz ] diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml index c4be28c7cfa..b28ec9df4b7 100644 --- a/docs/manual/mod/mod_ssl.xml +++ b/docs/manual/mod/mod_ssl.xml @@ -110,6 +110,14 @@ compatibility variables.

SSL_SRP_USERINFO string SRP user info SSL_TLS_SNI string Contents of the SNI TLS extension (if supplied with ClientHello) SSL_HANDSHAKE_RTT number Round-trip time of TLS handshake in microseconds including endpoint processing (set to empty string if OpenSSL version prior to 3.2 or if round-trip time can not be determined) +SSL_CLIENTHELLO_VERSION string Version field (legacy) from ClientHello as four hex encoded characters +SSL_CLIENTHELLO_CIPHERS string Cipher Suites from ClientHello as four hex encoded characters per item +SSL_CLIENTHELLO_EXTENSIONS string Extension IDs from ClientHello as four hex encoded characters per item +SSL_CLIENTHELLO_GROUPS string Value of Supported Groups extension (10) from ClientHello as four hex encoded characters per item +SSL_CLIENTHELLO_EC_FORMATS string Value of EC Point Formats extension (11) from ClientHello as two hex encoded characters per item +SSL_CLIENTHELLO_SIG_ALGOS string Value of Signature Algorithms extension (13) from ClientHello as four hex encoded characters per item +SSL_CLIENTHELLO_ALPN string Value of ALPN extension (16) from ClientHello as hex encoded string including leading string lengths +SSL_CLIENTHELLO_VERSIONS string Value of Supported Versions extension (43) from ClientHello as four hex encoded characters per item

x509 specifies a component of an X.509 DN; one of @@ -142,6 +150,10 @@ suffix (if any). For example, SSL_SERVER_S_DN_OU_RAW or

SSL_CLIENT_V_REMAIN is only available in version 2.1 and later.

+

The SSL_CLIENTHELLO_* variables require the directive +SSLClientHelloVars to be +enabled or they will not be populated.

+

A number of additional environment variables can also be used in SSLRequire expressions, or in custom log formats:

@@ -2858,6 +2870,26 @@ be protected with file permissions similar to those used for + +SSLClientHelloVars +Enable collection of ClientHello variables +SSLClientHelloVars on|off +SSLClientHelloVars off +server config +virtual host +Available in httpd 2.5.2 and later, requires OpenSSL 1.1.1 or later + + +

This directive enables collection of ClientHello data during the handshake that is retained for +the length of the connection so it can be exposed as SSL_CLIENTHELLLO_* environment +variables for requests depending upon the StdEnvVars setting. The variables are +formatted as the hex-encoded raw buffers seen in the raw network protocol and as provided +by OpenSSL. GREASE (RFC 8701) values are filtered by OpenSSL when enumerating extension IDs, but +otherwise, are passed through unchanged for other variables. If this directive is not enabled or +if OpenSSL prior to version 1.1.1 is used, these variables will not have a value set.

+
+
+ SSLCompression Enable compression on the SSL level diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c index 420ae6b79ac..93a46f2506c 100644 --- a/modules/ssl/mod_ssl.c +++ b/modules/ssl/mod_ssl.c @@ -166,6 +166,9 @@ static const command_rec ssl_config_cmds[] = { "('[+-][" SSL_PROTOCOLS "] ...' - see manual)") SSL_CMD_SRV(HonorCipherOrder, FLAG, "Use the server's cipher ordering preference") + SSL_CMD_SRV(ClientHelloVars, FLAG, + "Enable SSL ClientHello variable collection " + "(`on', `off')") SSL_CMD_SRV(Compression, FLAG, "Enable SSL level compression " "(`on', `off')") diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c index 0f96ee8ddc0..43593d799c7 100644 --- a/modules/ssl/ssl_engine_config.c +++ b/modules/ssl/ssl_engine_config.c @@ -220,6 +220,7 @@ static SSLSrvConfigRec *ssl_config_server_new(apr_pool_t *p) #ifndef OPENSSL_NO_COMP sc->compression = UNSET; #endif + sc->clienthello_vars = UNSET; sc->session_tickets = UNSET; modssl_ctx_init_server(sc, p); @@ -347,6 +348,7 @@ void *ssl_config_server_merge(apr_pool_t *p, void *basev, void *addv) cfgMerge(enabled, SSL_ENABLED_UNSET); cfgMergeInt(session_cache_timeout); cfgMergeBool(cipher_server_pref); + cfgMergeBool(clienthello_vars); #ifdef HAVE_TLSEXT cfgMerge(strict_sni_vhost_check, SSL_ENABLED_UNSET); #endif @@ -957,6 +959,13 @@ const char *ssl_cmd_SSLCompression(cmd_parms *cmd, void *dcfg, int flag) return NULL; } +const char *ssl_cmd_SSLClientHelloVars(cmd_parms *cmd, void *dcfg, int flag) +{ + SSLSrvConfigRec *sc = mySrvConfig(cmd->server); + sc->clienthello_vars = flag ? TRUE : FALSE; + return NULL; +} + const char *ssl_cmd_SSLHonorCipherOrder(cmd_parms *cmd, void *dcfg, int flag) { #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 4ce98aa80bf..ac03b2ef7f5 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -1547,6 +1547,14 @@ static const char *const ssl_hook_Fixup_vars[] = { "SSL_SRP_USERINFO", #endif "SSL_HANDSHAKE_RTT", + "SSL_CLIENTHELLO_VERSION", + "SSL_CLIENTHELLO_CIPHERS", + "SSL_CLIENTHELLO_EXTENSIONS", + "SSL_CLIENTHELLO_GROUPS", + "SSL_CLIENTHELLO_EC_FORMATS", + "SSL_CLIENTHELLO_SIG_ALGOS", + "SSL_CLIENTHELLO_ALPN", + "SSL_CLIENTHELLO_VERSIONS", NULL }; @@ -2465,6 +2473,53 @@ int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx) } #if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) +/* + * Copy data from clienthello for env vars use later + */ +static void copy_clienthello_vars(conn_rec *c, SSL *ssl) +{ + SSLConnRec *sslcon; + modssl_clienthello_vars *clienthello_vars; + const unsigned char *data; + int *ids; + + sslcon = myConnConfig(c); + + sslcon->clienthello_vars = apr_pcalloc(c->pool, sizeof(*clienthello_vars)); + clienthello_vars = sslcon->clienthello_vars; + + clienthello_vars->version = SSL_client_hello_get0_legacy_version(ssl); + clienthello_vars->ciphers_len = SSL_client_hello_get0_ciphers(ssl, &data); + if (clienthello_vars->ciphers_len > 0) { + clienthello_vars->ciphers_data = apr_pmemdup(c->pool, data, clienthello_vars->ciphers_len); + } + if (SSL_client_hello_get1_extensions_present(ssl, &ids, &clienthello_vars->extids_len) == 1) { + if (clienthello_vars->extids_len > 0) + clienthello_vars->extids_data = apr_pmemdup(c->pool, ids, clienthello_vars->extids_len * sizeof(int)); + OPENSSL_free(ids); + } + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_supported_groups, &data, &clienthello_vars->ecgroups_len) == 1) { + if (clienthello_vars->ecgroups_len > 0) + clienthello_vars->ecgroups_data = apr_pmemdup(c->pool, data, clienthello_vars->ecgroups_len); + } + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_ec_point_formats, &data, &clienthello_vars->ecformats_len) == 1) { + if (clienthello_vars->ecformats_len > 0) + clienthello_vars->ecformats_data = apr_pmemdup(c->pool, data, clienthello_vars->ecformats_len); + } + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_signature_algorithms, &data, &clienthello_vars->sigalgos_len) == 1) { + if (clienthello_vars->sigalgos_len > 0) + clienthello_vars->sigalgos_data = apr_pmemdup(c->pool, data, clienthello_vars->sigalgos_len); + } + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_application_layer_protocol_negotiation, &data, &clienthello_vars->alpn_len) == 1) { + if (clienthello_vars->alpn_len > 0) + clienthello_vars->alpn_data = apr_pmemdup(c->pool, data, clienthello_vars->alpn_len); + } + if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_supported_versions, &data, &clienthello_vars->versions_len) == 1) { + if (clienthello_vars->versions_len > 0) + clienthello_vars->versions_data = apr_pmemdup(c->pool, data, clienthello_vars->versions_len); + } +} + /* * This callback function is called when the ClientHello is received. */ @@ -2520,6 +2575,10 @@ int ssl_callback_ClientHello(SSL *ssl, int *al, void *arg) give_up: init_vhost(c, ssl, servername); + + if (mySrvConfigFromConn(c)->clienthello_vars == TRUE) + copy_clienthello_vars(c, ssl); + return SSL_CLIENT_HELLO_SUCCESS; } #endif /* OPENSSL_VERSION_NUMBER < 0x10101000L */ diff --git a/modules/ssl/ssl_engine_vars.c b/modules/ssl/ssl_engine_vars.c index 7d09846c27e..45a5a5bab6f 100644 --- a/modules/ssl/ssl_engine_vars.c +++ b/modules/ssl/ssl_engine_vars.c @@ -52,6 +52,7 @@ static const char *ssl_var_lookup_ssl_cert_verify(apr_pool_t *p, const SSLConnRe static const char *ssl_var_lookup_ssl_cipher(apr_pool_t *p, const SSLConnRec *sslconn, const char *var); static void ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize); static const char *ssl_var_lookup_ssl_handshake_rtt(apr_pool_t *p, SSL *ssl); +static const char *ssl_var_lookup_ssl_clienthello(apr_pool_t *p, const SSLConnRec *sslconn, const char *var); static const char *ssl_var_lookup_ssl_version(const char *var); static const char *ssl_var_lookup_ssl_compress_meth(SSL *ssl); @@ -476,6 +477,9 @@ static const char *ssl_var_lookup_ssl(apr_pool_t *p, const SSLConnRec *sslconn, else if (ssl != NULL && strcEQ(var, "HANDSHAKE_RTT")) { result = ssl_var_lookup_ssl_handshake_rtt(p, ssl); } + else if (ssl != NULL && strlen(var) >= 12 && strcEQn(var, "CLIENTHELLO_", 12)) { + result = ssl_var_lookup_ssl_clienthello(p, sslconn, var+12); + } else if (ssl != NULL && strlen(var) > 18 && strcEQn(var, "CLIENT_CERT_CHAIN_", 18)) { sk = SSL_get_peer_cert_chain(ssl); result = ssl_var_lookup_ssl_cert_chain(p, sk, var+18, 1); @@ -975,6 +979,62 @@ static const char *ssl_var_lookup_ssl_handshake_rtt(apr_pool_t *p, SSL *ssl) return NULL; } +static const char *ssl_var_lookup_ssl_clienthello(apr_pool_t *p, const SSLConnRec *sslconn, const char *var) +{ +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) + char *value; + modssl_clienthello_vars *clienthello_vars; + apr_size_t i; + + clienthello_vars = sslconn->clienthello_vars; + + if (!clienthello_vars) + return NULL; + + if (strEQ(var, "VERSION")) { + return apr_psprintf(p, "%04x", (uint16_t) clienthello_vars->version); + } + else if (strEQ(var, "CIPHERS") && (clienthello_vars->ciphers_len > 0)) { + value = apr_palloc(p, clienthello_vars->ciphers_len * 2 + 1); + ap_bin2hex(clienthello_vars->ciphers_data, clienthello_vars->ciphers_len, value); + return value; + } + else if (strEQ(var, "EXTENSIONS") && (clienthello_vars->extids_len > 0)) { + value = apr_palloc(p, clienthello_vars->extids_len * 4 + 1); + for (i = 0; i < clienthello_vars->extids_len; i++) { + apr_snprintf(value + i * 4, 5, "%04x", (uint16_t) clienthello_vars->extids_data[i]); + } + return value; + } + else if (strEQ(var, "GROUPS") && (clienthello_vars->ecgroups_len > 2)) { + value = apr_palloc(p, clienthello_vars->ecgroups_len * 2 + 1 - 2); + ap_bin2hex(clienthello_vars->ecgroups_data + 2, clienthello_vars->ecgroups_len - 2, value); + return value; + } + else if (strEQ(var, "EC_FORMATS") && (clienthello_vars->ecformats_len > 1)) { + value = apr_palloc(p, clienthello_vars->ecformats_len * 2 + 1 - 1); + ap_bin2hex(clienthello_vars->ecformats_data + 1, clienthello_vars->ecformats_len - 1, value); + return value; + } + else if (strEQ(var, "SIG_ALGOS") && (clienthello_vars->sigalgos_len > 2)) { + value = apr_palloc(p, clienthello_vars->sigalgos_len * 2 + 1 - 2); + ap_bin2hex(clienthello_vars->sigalgos_data + 2, clienthello_vars->sigalgos_len - 2, value); + return value; + } + else if (strEQ(var, "ALPN") && (clienthello_vars->alpn_len > 2)) { + value = apr_palloc(p, clienthello_vars->alpn_len * 2 + 1 - 2); + ap_bin2hex(clienthello_vars->alpn_data + 2, clienthello_vars->alpn_len - 2, value); + return value; + } + else if (strEQ(var, "VERSIONS") && (clienthello_vars->versions_len > 1)) { + value = apr_palloc(p, clienthello_vars->versions_len * 2 + 1 - 1); + ap_bin2hex(clienthello_vars->versions_data + 1, clienthello_vars->versions_len - 1, value); + return value; + } +#endif + return NULL; +} + static const char *ssl_var_lookup_ssl_version(const char *var) { if (strEQ(var, "INTERFACE")) { diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index 2f7bb51fa5a..e3e41b7dff9 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -574,6 +574,30 @@ typedef enum { SSL_SHUTDOWN_TYPE_ACCURATE } ssl_shutdown_type_e; +/** + * Define the structure to hold clienthello variables + * (later exposed as environment vars) + */ +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) +typedef struct { + unsigned int version; + apr_size_t ciphers_len; + const unsigned char *ciphers_data; + apr_size_t extids_len; + const int *extids_data; + apr_size_t ecgroups_len; + const unsigned char *ecgroups_data; + apr_size_t ecformats_len; + const unsigned char *ecformats_data; + apr_size_t sigalgos_len; + const unsigned char *sigalgos_data; + apr_size_t alpn_len; + const unsigned char *alpn_data; + apr_size_t versions_len; + const unsigned char *versions_data; +} modssl_clienthello_vars; +#endif + typedef struct { SSL *ssl; const char *client_dn; @@ -604,6 +628,10 @@ typedef struct { const char *cipher_suite; /* cipher suite used in last reneg */ int service_unavailable; /* thouugh we negotiate SSL, no requests will be served */ int vhost_found; /* whether we found vhost from SNI already */ + +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) + modssl_clienthello_vars *clienthello_vars; /* info from clienthello callback */ +#endif } SSLConnRec; /* Private keys are retained across reloads, since decryption @@ -833,7 +861,7 @@ struct SSLSrvConfigRec { BOOL compression; #endif BOOL session_tickets; - + BOOL clienthello_vars; }; /** @@ -893,6 +921,7 @@ const char *ssl_cmd_SSLCARevocationPath(cmd_parms *, void *, const char *); const char *ssl_cmd_SSLCARevocationFile(cmd_parms *, void *, const char *); const char *ssl_cmd_SSLCARevocationCheck(cmd_parms *, void *, const char *); const char *ssl_cmd_SSLHonorCipherOrder(cmd_parms *cmd, void *dcfg, int flag); +const char *ssl_cmd_SSLClientHelloVars(cmd_parms *, void *, int flag); const char *ssl_cmd_SSLCompression(cmd_parms *, void *, int flag); const char *ssl_cmd_SSLSessionTickets(cmd_parms *, void *, int flag); const char *ssl_cmd_SSLVerifyClient(cmd_parms *, void *, const char *);