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 *);