Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changes-entries/pr69743.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*) mod_ssl: Add SSLVHostSNIPolicy directive to control the virtual
host compatibility policy. PR 69743. [Joe Orton]

91 changes: 91 additions & 0 deletions docs/manual/mod/mod_ssl.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1816,6 +1816,97 @@ SSLStrictSNIVHostCheck on
</usage>
</directivesynopsis>

<directivesynopsis>
<name>SSLVHostSNIPolicy</name>
<description>Set compatibility policy for SNI client access to virtual hosts.</description>
<syntax>SSLVHostSNIPolicy strict|secure|authonly|insecure</syntax>
<default>SSLVHostSNIPolicy secure</default>
<contextlist><context>server config</context></contextlist>
<compatibility>Available in httpd 2.5 and later</compatibility>

<usage><p>This directive sets policy applied when checking whether the
<directive module="core" type="section">VirtualHost</directive>
identified by the <code>Host</code> request header in an HTTP request
is compatible with the <directive module="core"
type="section">VirtualHost</directive> identified from the SNI
extension sent during the initial TLS connection handshake. If an HTTP
request is associated with a virtual host which has an incompatible
SSL/TLS configuration under the policy used, an HTTP error response
with status code 421 ("Misdirected Request") will be sent.</p>

<p>The policy also applies to TLS connections where an SNI extension
is not sent during the handshake, implicitly using the default or
first virtual host definition. If the Host header in an HTTP request
on such a connection identifies any other non-default virtual host,
the compatibility policy is tested.</p>

<p>The <code>strict</code> policy blocks all HTTP requests which are
identified with a different virtual host to that identifed by SNI.
The <code>insecure</code> policy allows all HTTP requests regardless
of virtual host identified; such a configuration may be vulnerable to
<a
href="https://httpd.apache.org/security/vulnerabilities_24.html">CVE-2025-23048</a>.
</p>

<p>The (default) <code>secure</code>, and <code>authonly</code>
policies compare specific aspects of the SSL configuration for the two
virtual hosts, which are grouped into two categories:</p>

<ul>
<li><strong>server certificate/key, or protocol/cipher
restrictions</strong>: directives which determine the server
certificate or key (<directive
module="mod_ssl">SSLCertificateKeyFile</directive> etc), cipher or
protocol restrictions (<directive
module="mod_ssl">SSLCipherSuite</directive> and <directive
module="mod_ssl">SSLProtocol</directive>)</li>

<li><strong>client vertification and authentication
settings</strong>: directives which affect TLS client certificate
verification or authentication, such as <directive
module="mod_ssl">SSLVerifyClient</directive>, <directive
module="mod_ssl">SSLVerifyMode</directive>, <directive
module="mod_ssl">SSLCACertificatePath</directive>, <directive
module="mod_ssl">SSLSRPVerifierFile</directive>; any use of <directive
module="mod_ssl">SSLOpenSSLConfCmd</directive></li>
</ul>

<p>This table illustrates whether an HTTP request will be blocked or
allowed when the virtual host configurations differ as described,
under each different policy setting:</p>

<table border="1" style="zebra">
<columnspec><column width=".3"/><column width=".2"/><column width=".5"/>
</columnspec>
<tr>
<th>Policy mode</th>
<th>Any VirtualHost mismatch</th>
<th>Server certificate/key, <br />or protocol/cipher restrictions</th>
<th>Client verification/<br />authentication settings</th>
</tr>
<tr>
<td><code>strict</code></td><td>blocked</td><td>blocked</td><td>blocked</td>
</tr>
<tr>
<td><code>secure</code></td><td>allowed</td><td>blocked</td><td>blocked</td>
</tr>
<tr>
<td><code>authonly</code></td><td>allowed</td><td>allowed</td><td>blocked</td>
</tr>
<tr>
<td><code>insecure</code></td><td>allowed</td><td>allowed</td><td>allowed</td>
</tr>
</table>

<example><title>Example</title>
<highlight language="config">
SSLVHostSNIPolicy authonly
</highlight>

</example>
</usage>
</directivesynopsis>

<directivesynopsis>
<name>SSLProxyMachineCertificatePath</name>
<description>Directory of PEM-encoded client certificates and keys to be used by the proxy</description>
Expand Down
2 changes: 2 additions & 0 deletions modules/ssl/mod_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ static const command_rec ssl_config_cmds[] = {
SSL_CMD_SRV(RandomSeed, TAKE23,
"SSL Pseudo Random Number Generator (PRNG) seeding source "
"('startup|connect builtin|file:/path|exec:/path [bytes]')")
SSL_CMD_SRV(VHostSNIPolicy, TAKE1,
"SSL VirtualHost SNI compatibility policy setting")

/*
* Per-server context configuration directives
Expand Down
38 changes: 38 additions & 0 deletions modules/ssl/ssl_engine_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ SSLModConfigRec *ssl_config_global_create(server_rec *s)
mc->sesscache_mode = SSL_SESS_CACHE_OFF;
mc->sesscache = NULL;
mc->pMutex = NULL;
#ifdef HAVE_TLSEXT
mc->snivh_policy = MODSSL_SNIVH_SECURE;
#endif
mc->aRandSeed = apr_array_make(pool, 4,
sizeof(ssl_randseed_t));
mc->tVHostKeys = apr_hash_make(pool);
Expand Down Expand Up @@ -1909,6 +1912,41 @@ const char *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag
#endif
}

const char *ssl_cmd_SSLVHostSNIPolicy(cmd_parms *cmd, void *dcfg, const char *arg)
{
#ifdef HAVE_TLSEXT
SSLModConfigRec *mc = myModConfig(cmd->server);
const char *err;

if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
return err;
}

if (strcEQ(arg, "secure")) {
mc->snivh_policy = MODSSL_SNIVH_SECURE;
}
else if (strcEQ(arg, "strict")) {
mc->snivh_policy = MODSSL_SNIVH_STRICT;
}
else if (strcEQ(arg, "insecure")) {
mc->snivh_policy = MODSSL_SNIVH_INSECURE;
}
else if (strcEQ(arg, "authonly")) {
mc->snivh_policy = MODSSL_SNIVH_AUTHONLY;
}
else {
return apr_psprintf(cmd->pool, "Invalid SSLVhostSNIPolicy "
"argument '%s'", arg);
}

return NULL;
#else
return "SSLVHostSNIPolicy cannot be used, OpenSSL is not built with "
"support for TLS extensions and SNI indication. Refer to the "
"documentation, and build a compatible version of OpenSSL."
#endif
}

#ifdef HAVE_OCSP_STAPLING

const char *ssl_cmd_SSLStaplingCache(cmd_parms *cmd,
Expand Down
108 changes: 108 additions & 0 deletions modules/ssl/ssl_engine_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
-- Unknown */
#include "ssl_private.h"

#include <apr_md5.h>

#include "mpm_common.h"
#include "mod_md.h"

Expand Down Expand Up @@ -186,6 +188,110 @@ static void ssl_add_version_components(apr_pool_t *ptemp, apr_pool_t *pconf,
modver, AP_SERVER_BASEVERSION, incver);
}

#ifdef HAVE_TLSEXT
/* Helper functions to create the SNI vhost policy hash. The policy
* hash captures the configuration elements relevant to the mode
* selected at runtime by SSLVHostSNIPolicy. */

#define md5_str_update(ctx_, pfx_, str_) do { apr_md5_update(ctx_, pfx_, strlen(pfx_)); apr_md5_update(ctx_, str_, strlen(str_)); } while (0)
#define md5_ifstr_update(ctx_, pfx_, str_) do { apr_md5_update(ctx_, pfx_, strlen(pfx_)); if (str_) apr_md5_update(ctx_, str_, strlen(str_)); } while (0)
#define md5_fmt_update(ctx_, fmt_, i_) do { char s_[128]; apr_snprintf(s_, sizeof s_, fmt_, i_); \
apr_md5_update(ctx_, s_, strlen(s_)); } while (0)

static int md5_strarray_cmp(const void *p1, const void *p2)
{
return strcmp(*(char **)p1, *(char **)p2);
}

/* Hashes an array of strings in sorted order. */
static void md5_strarray_hash(apr_pool_t *ptemp, apr_md5_ctx_t *hash,
const char *pfx, apr_array_header_t *s)
{
char **elts = apr_pmemdup(ptemp, s->elts, s->nelts * sizeof *elts);
int i;

qsort(elts, s->nelts, sizeof(char *), md5_strarray_cmp);

apr_md5_update(hash, pfx, strlen(pfx));
for (i = 0; i < s->nelts; i++) {
md5_str_update(hash, "elm:", elts[i]);
}
}

static void hash_sni_policy_pk(apr_pool_t *ptemp, apr_md5_ctx_t *hash, modssl_ctx_t *ctx)
{
md5_fmt_update(hash, "protocol:%d", ctx->protocol);

md5_ifstr_update(hash, "ciphers:", ctx->auth.cipher_suite);
md5_ifstr_update(hash, "tls13_ciphers:", ctx->auth.tls13_ciphers);

md5_strarray_hash(ptemp, hash, "cert_files:", ctx->pks->cert_files);
md5_strarray_hash(ptemp, hash, "key_files:", ctx->pks->key_files);
}

static void hash_sni_policy_auth(apr_md5_ctx_t *hash, modssl_ctx_t *ctx)
{
modssl_pk_server_t *pks = ctx->pks;
modssl_auth_ctx_t *a = &ctx->auth;

md5_fmt_update(hash, "verify_depth:%d", a->verify_depth);
md5_fmt_update(hash, "verify_mode:%d", a->verify_mode);

md5_ifstr_update(hash, "ca_name_path:", pks->ca_name_path);
md5_ifstr_update(hash, "ca_name_file:", pks->ca_name_file);
md5_ifstr_update(hash, "ca_cert_path:", a->ca_cert_path);
md5_ifstr_update(hash, "ca_cert_file:", a->ca_cert_file);
md5_ifstr_update(hash, "crl_path:", ctx->crl_path);
md5_ifstr_update(hash, "crl_file:", ctx->crl_file);
md5_fmt_update(hash, "crl_check_mask:%d", ctx->crl_check_mask);
md5_fmt_update(hash, "ocsp_mask:%d", ctx->ocsp_mask);
md5_fmt_update(hash, "ocsp_force_default:%d", ctx->ocsp_force_default);
md5_ifstr_update(hash, "ocsp_responder:", ctx->ocsp_responder);

#ifdef HAVE_SRP
md5_ifstr_update(hash, "srp_vfile:", ctx->srp_vfile);
#endif

#ifdef HAVE_SSL_CONF_CMD
{
apr_array_header_t *parms = ctx->ssl_ctx_param;
int n;

for (n = 0; n < parms->nelts; n++) {
ssl_ctx_param_t *p = &APR_ARRAY_IDX(parms, n, ssl_ctx_param_t);

md5_str_update(hash, "param:", p->name);
md5_str_update(hash, "value:", p->value);
}
}
#endif
}
#endif

static char *create_sni_policy_hash(apr_pool_t *p, apr_pool_t *ptemp,
modssl_snivhpolicy_t policy,
SSLSrvConfigRec *sc)
{
char *rv = NULL;
#ifdef HAVE_TLSEXT
if (policy != MODSSL_SNIVH_STRICT && policy != MODSSL_SNIVH_INSECURE) {
apr_md5_ctx_t hash;
unsigned char digest[APR_MD5_DIGESTSIZE];

/* Create the vhost policy hash for comparison later. */
apr_md5_init(&hash);
hash_sni_policy_auth(&hash, sc->server);
if (policy == MODSSL_SNIVH_SECURE)
hash_sni_policy_pk(ptemp, &hash, sc->server);
apr_md5_final(digest, &hash);

rv = apr_palloc(p, 2 * APR_MD5_DIGESTSIZE + 1);
ap_bin2hex(digest, APR_MD5_DIGESTSIZE, rv); /* sets final '\0' */
}
#endif
return rv;
}

/* _________________________________________________________________
**
** Let other answer special connection attempts.
Expand Down Expand Up @@ -439,6 +545,8 @@ apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog,
return rv;
}
}

sc->sni_policy_hash = create_sni_policy_hash(p, ptemp, mc->snivh_policy, sc);
}

/*
Expand Down
Loading