Skip to content

Commit 29a575d

Browse files
committed
mod_ssl: Add SSLVHostSNIPolicy directive to set the compatibility
level required for VirtualHost matching. For "secure" and "authonly" modes, a hash of the policy-relevant vhost configuration is created and stored in the post_config hooks, reducing the runtime code complexity (and overhead). * modules/ssl/ssl_engine_kernel.c (ssl_check_vhost_sni_policy): New function, replacing ssl_server_compatible et al. * modules/ssl/ssl_engine_config.c (ssl_cmd_SSLVHostSNIPolicy): New function. * modules/ssl/ssl_engine_init.c (md5_strarray_cmp, md5_strarray_hash, hash_sni_policy_pk, hash_sni_policy_auth, create_sni_policy_hash): New functions. (ssl_init_Module): Invoke create_sni_policy_hash to store the hash for every SSLSrvConfigRec. * modules/ssl/ssl_private.h (SSLModConfigRec): Add snivh_policy field. (SSLSrvConfigRec): Add sni_policy_hash field. PR: 69743
1 parent 6146c77 commit 29a575d

6 files changed

Lines changed: 190 additions & 109 deletions

File tree

changes-entries/pr69743.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*) mod_ssl: Add SSLVHostSNIPolicy directive to control the virtual
2+
host compatibility policy. PR 69743. [Joe Orton]
3+

modules/ssl/mod_ssl.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ static const command_rec ssl_config_cmds[] = {
8080
SSL_CMD_SRV(RandomSeed, TAKE23,
8181
"SSL Pseudo Random Number Generator (PRNG) seeding source "
8282
"('startup|connect builtin|file:/path|exec:/path [bytes]')")
83+
SSL_CMD_SRV(VHostSNIPolicy, TAKE1,
84+
"SSL VirtualHost SNI compatibility policy setting")
8385

8486
/*
8587
* Per-server context configuration directives

modules/ssl/ssl_engine_config.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ SSLModConfigRec *ssl_config_global_create(server_rec *s)
6363
mc->sesscache_mode = SSL_SESS_CACHE_OFF;
6464
mc->sesscache = NULL;
6565
mc->pMutex = NULL;
66+
#ifdef HAVE_TLSEXT
67+
mc->snivh_policy = MODSSL_SNIVH_SECURE;
68+
#endif
6669
mc->aRandSeed = apr_array_make(pool, 4,
6770
sizeof(ssl_randseed_t));
6871
mc->tVHostKeys = apr_hash_make(pool);
@@ -1909,6 +1912,41 @@ const char *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag
19091912
#endif
19101913
}
19111914

1915+
const char *ssl_cmd_SSLVHostSNIPolicy(cmd_parms *cmd, void *dcfg, const char *arg)
1916+
{
1917+
#ifdef HAVE_TLSEXT
1918+
SSLModConfigRec *mc = myModConfig(cmd->server);
1919+
const char *err;
1920+
1921+
if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
1922+
return err;
1923+
}
1924+
1925+
if (strcEQ(arg, "secure")) {
1926+
mc->snivh_policy = MODSSL_SNIVH_SECURE;
1927+
}
1928+
else if (strcEQ(arg, "strict")) {
1929+
mc->snivh_policy = MODSSL_SNIVH_STRICT;
1930+
}
1931+
else if (strcEQ(arg, "insecure")) {
1932+
mc->snivh_policy = MODSSL_SNIVH_INSECURE;
1933+
}
1934+
else if (strcEQ(arg, "authonly")) {
1935+
mc->snivh_policy = MODSSL_SNIVH_AUTHONLY;
1936+
}
1937+
else {
1938+
return apr_psprintf(cmd->pool, "Invalid SSLVhostSNIPolicy "
1939+
"argument '%s'", arg);
1940+
}
1941+
1942+
return NULL;
1943+
#else
1944+
return "SSLVHostSNIPolicy cannot be used, OpenSSL is not built with "
1945+
"support for TLS extensions and SNI indication. Refer to the "
1946+
"documentation, and build a compatible version of OpenSSL."
1947+
#endif
1948+
}
1949+
19121950
#ifdef HAVE_OCSP_STAPLING
19131951

19141952
const char *ssl_cmd_SSLStaplingCache(cmd_parms *cmd,

modules/ssl/ssl_engine_init.c

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
-- Unknown */
2929
#include "ssl_private.h"
3030

31+
#include <apr_md5.h>
32+
3133
#include "mpm_common.h"
3234
#include "mod_md.h"
3335

@@ -186,6 +188,110 @@ static void ssl_add_version_components(apr_pool_t *ptemp, apr_pool_t *pconf,
186188
modver, AP_SERVER_BASEVERSION, incver);
187189
}
188190

191+
#ifdef HAVE_TLSEXT
192+
/* Helper functions to create the SNI vhost policy hash. The policy
193+
* hash captures the configuration elements relevant to the mode
194+
* selected at runtime by SSLVHostSNIPolicy. */
195+
196+
#define md5_str_update(ctx_, pfx_, str_) do { apr_md5_update(ctx_, pfx_, strlen(pfx_)); apr_md5_update(ctx_, str_, strlen(str_)); } while (0)
197+
#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)
198+
#define md5_fmt_update(ctx_, fmt_, i_) do { char s_[128]; apr_snprintf(s_, sizeof s_, fmt_, i_); \
199+
apr_md5_update(ctx_, s_, strlen(s_)); } while (0)
200+
201+
static int md5_strarray_cmp(const void *p1, const void *p2)
202+
{
203+
return strcmp(*(char **)p1, *(char **)p2);
204+
}
205+
206+
/* Hashes an array of strings in sorted order. */
207+
static void md5_strarray_hash(apr_pool_t *ptemp, apr_md5_ctx_t *hash,
208+
const char *pfx, apr_array_header_t *s)
209+
{
210+
char **elts = apr_pmemdup(ptemp, s->elts, s->nelts * sizeof *elts);
211+
int i;
212+
213+
qsort(elts, s->nelts, sizeof(char *), md5_strarray_cmp);
214+
215+
apr_md5_update(hash, pfx, strlen(pfx));
216+
for (i = 0; i < s->nelts; i++) {
217+
md5_str_update(hash, "elm:", elts[i]);
218+
}
219+
}
220+
221+
static void hash_sni_policy_pk(apr_pool_t *ptemp, apr_md5_ctx_t *hash, modssl_ctx_t *ctx)
222+
{
223+
md5_fmt_update(hash, "protocol:%d", ctx->protocol);
224+
225+
md5_ifstr_update(hash, "ciphers:", ctx->auth.cipher_suite);
226+
md5_ifstr_update(hash, "tls13_ciphers:", ctx->auth.tls13_ciphers);
227+
228+
md5_strarray_hash(ptemp, hash, "cert_files:", ctx->pks->cert_files);
229+
md5_strarray_hash(ptemp, hash, "key_files:", ctx->pks->key_files);
230+
}
231+
232+
static void hash_sni_policy_auth(apr_md5_ctx_t *hash, modssl_ctx_t *ctx)
233+
{
234+
modssl_pk_server_t *pks = ctx->pks;
235+
modssl_auth_ctx_t *a = &ctx->auth;
236+
237+
md5_fmt_update(hash, "verify_depth:%d", a->verify_depth);
238+
md5_fmt_update(hash, "verify_mode:%d", a->verify_mode);
239+
240+
md5_ifstr_update(hash, "ca_name_path:", pks->ca_name_path);
241+
md5_ifstr_update(hash, "ca_name_file:", pks->ca_name_file);
242+
md5_ifstr_update(hash, "ca_cert_path:", a->ca_cert_path);
243+
md5_ifstr_update(hash, "ca_cert_file:", a->ca_cert_file);
244+
md5_ifstr_update(hash, "crl_path:", ctx->crl_path);
245+
md5_ifstr_update(hash, "crl_file:", ctx->crl_file);
246+
md5_fmt_update(hash, "crl_check_mask:%d", ctx->crl_check_mask);
247+
md5_fmt_update(hash, "ocsp_mask:%d", ctx->ocsp_mask);
248+
md5_fmt_update(hash, "ocsp_force_default:%d", ctx->ocsp_force_default);
249+
md5_ifstr_update(hash, "ocsp_responder:", ctx->ocsp_responder);
250+
251+
#ifdef HAVE_SRP
252+
md5_ifstr_update(hash, "srp_vfile:", ctx->srp_vfile);
253+
#endif
254+
255+
#ifdef HAVE_SSL_CONF_CMD
256+
{
257+
apr_array_header_t *parms = ctx->ssl_ctx_param;
258+
int n;
259+
260+
for (n = 0; n < parms->nelts; n++) {
261+
ssl_ctx_param_t *p = &APR_ARRAY_IDX(parms, n, ssl_ctx_param_t);
262+
263+
md5_str_update(hash, "param:", p->name);
264+
md5_str_update(hash, "value:", p->value);
265+
}
266+
}
267+
#endif
268+
}
269+
#endif
270+
271+
static char *create_sni_policy_hash(apr_pool_t *p, apr_pool_t *ptemp,
272+
modssl_snivhpolicy_t policy,
273+
SSLSrvConfigRec *sc)
274+
{
275+
char *rv = NULL;
276+
#ifdef HAVE_TLSEXT
277+
if (policy != MODSSL_SNIVH_STRICT && policy != MODSSL_SNIVH_INSECURE) {
278+
apr_md5_ctx_t hash;
279+
unsigned char digest[APR_MD5_DIGESTSIZE];
280+
281+
/* Create the vhost policy hash for comparison later. */
282+
apr_md5_init(&hash);
283+
hash_sni_policy_auth(&hash, sc->server);
284+
if (policy == MODSSL_SNIVH_SECURE)
285+
hash_sni_policy_pk(ptemp, &hash, sc->server);
286+
apr_md5_final(digest, &hash);
287+
288+
rv = apr_palloc(p, 2 * APR_MD5_DIGESTSIZE + 1);
289+
ap_bin2hex(digest, APR_MD5_DIGESTSIZE, rv); /* sets final '\0' */
290+
}
291+
#endif
292+
return rv;
293+
}
294+
189295
/* _________________________________________________________________
190296
**
191297
** Let other answer special connection attempts.
@@ -439,6 +545,8 @@ apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog,
439545
return rv;
440546
}
441547
}
548+
549+
sc->sni_policy_hash = create_sni_policy_hash(p, ptemp, mc->snivh_policy, sc);
442550
}
443551

444552
/*

modules/ssl/ssl_engine_kernel.c

Lines changed: 22 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -101,112 +101,28 @@ static int fill_reneg_buffer(request_rec *r, SSLDirConfigRec *dc)
101101
}
102102

103103
#ifdef HAVE_TLSEXT
104-
static int ap_array_same_str_set(apr_array_header_t *s1, apr_array_header_t *s2)
104+
/* Check whether a transition from vhost sc1 to sc2 from SNI to Host:
105+
* vhost selection is permitted according to the SSLVHostSNIPolicy
106+
* setting. Returns 1 if the policy treats the vhosts as compatible,
107+
* else 0. */
108+
static int ssl_check_vhost_sni_policy(SSLSrvConfigRec *sc1,
109+
SSLSrvConfigRec *sc2)
105110
{
106-
int i;
107-
const char *c;
108-
109-
if (s1 == s2) {
111+
modssl_snivhpolicy_t policy = sc1->mc->snivh_policy;
112+
113+
/* Policy: insecure => allow everything. */
114+
if (policy == MODSSL_SNIVH_INSECURE)
110115
return 1;
111-
}
112-
else if (!s1 || !s2 || (s1->nelts != s2->nelts)) {
113-
return 0;
114-
}
115116

116-
for (i = 0; i < s1->nelts; i++) {
117-
c = APR_ARRAY_IDX(s1, i, const char *);
118-
if (!c || !ap_array_str_contains(s2, c)) {
119-
return 0;
120-
}
121-
}
122-
return 1;
123-
}
124-
125-
static int ssl_pk_server_compatible(modssl_pk_server_t *pks1,
126-
modssl_pk_server_t *pks2)
127-
{
128-
if (!pks1 || !pks2) {
129-
return 0;
130-
}
131-
/* both have the same certificates? */
132-
if ((pks1->ca_name_path != pks2->ca_name_path)
133-
&& (!pks1->ca_name_path || !pks2->ca_name_path
134-
|| strcmp(pks1->ca_name_path, pks2->ca_name_path))) {
135-
return 0;
136-
}
137-
if ((pks1->ca_name_file != pks2->ca_name_file)
138-
&& (!pks1->ca_name_file || !pks2->ca_name_file
139-
|| strcmp(pks1->ca_name_file, pks2->ca_name_file))) {
140-
return 0;
141-
}
142-
if (!ap_array_same_str_set(pks1->cert_files, pks2->cert_files)
143-
|| !ap_array_same_str_set(pks1->key_files, pks2->key_files)) {
144-
return 0;
145-
}
146-
return 1;
147-
}
148-
149-
static int ssl_auth_compatible(modssl_auth_ctx_t *a1,
150-
modssl_auth_ctx_t *a2)
151-
{
152-
if (!a1 || !a2) {
153-
return 0;
154-
}
155-
/* both have the same verification */
156-
if ((a1->verify_depth != a2->verify_depth)
157-
|| (a1->verify_mode != a2->verify_mode)) {
158-
return 0;
159-
}
160-
/* both have the same ca path/file */
161-
if ((a1->ca_cert_path != a2->ca_cert_path)
162-
&& (!a1->ca_cert_path || !a2->ca_cert_path
163-
|| strcmp(a1->ca_cert_path, a2->ca_cert_path))) {
164-
return 0;
165-
}
166-
if ((a1->ca_cert_file != a2->ca_cert_file)
167-
&& (!a1->ca_cert_file || !a2->ca_cert_file
168-
|| strcmp(a1->ca_cert_file, a2->ca_cert_file))) {
169-
return 0;
170-
}
171-
/* both have the same ca cipher suite string */
172-
if ((a1->cipher_suite != a2->cipher_suite)
173-
&& (!a1->cipher_suite || !a2->cipher_suite
174-
|| strcmp(a1->cipher_suite, a2->cipher_suite))) {
175-
return 0;
176-
}
177-
/* both have the same ca cipher suite string */
178-
if ((a1->tls13_ciphers != a2->tls13_ciphers)
179-
&& (!a1->tls13_ciphers || !a2->tls13_ciphers
180-
|| strcmp(a1->tls13_ciphers, a2->tls13_ciphers))) {
181-
return 0;
182-
}
183-
return 1;
184-
}
185-
186-
static int ssl_ctx_compatible(modssl_ctx_t *ctx1,
187-
modssl_ctx_t *ctx2)
188-
{
189-
if (!ctx1 || !ctx2
190-
|| (ctx1->protocol != ctx2->protocol)
191-
|| !ssl_auth_compatible(&ctx1->auth, &ctx2->auth)
192-
|| !ssl_pk_server_compatible(ctx1->pks, ctx2->pks)) {
117+
/* Policy: strict => fail for any vhost transition. */
118+
if (policy == MODSSL_SNIVH_STRICT && sc1 != sc2)
193119
return 0;
194-
}
195-
return 1;
196-
}
197120

198-
static int ssl_server_compatible(server_rec *s1, server_rec *s2)
199-
{
200-
SSLSrvConfigRec *sc1 = s1? mySrvConfig(s1) : NULL;
201-
SSLSrvConfigRec *sc2 = s2? mySrvConfig(s2) : NULL;
121+
/* For authonly/secure policy, compare the hash. */
122+
AP_DEBUG_ASSERT(sc1->sni_policy_hash);
123+
AP_DEBUG_ASSERT(sc2->sni_policy_hash);
202124

203-
/* both use the same TLS protocol? */
204-
if (!sc1 || !sc2
205-
|| !ssl_ctx_compatible(sc1->server, sc2->server)) {
206-
return 0;
207-
}
208-
209-
return 1;
125+
return strcmp(sc1->sni_policy_hash, sc2->sni_policy_hash) == 0;
210126
}
211127
#endif
212128

@@ -275,6 +191,8 @@ int ssl_hook_ReadReq(request_rec *r)
275191
server_rec *handshakeserver = sslconn->server;
276192
SSLSrvConfigRec *hssc = mySrvConfig(handshakeserver);
277193

194+
AP_DEBUG_ASSERT(hssc);
195+
278196
if ((servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
279197
/*
280198
* The SNI extension supplied a hostname. So don't accept requests
@@ -315,19 +233,14 @@ int ssl_hook_ReadReq(request_rec *r)
315233
"which is required to access this server.<br />\n");
316234
return HTTP_FORBIDDEN;
317235
}
318-
if (r->server != handshakeserver
319-
&& !ssl_server_compatible(sslconn->server, r->server)) {
320-
/*
321-
* The request does not select the virtual host that was
322-
* selected for handshaking and its SSL parameters are different
323-
*/
324-
236+
/* Enforce SSL SNI vhost compatibility policy. */
237+
if (!ssl_check_vhost_sni_policy(sc, hssc)) {
325238
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02032)
326239
"Hostname %s %s and hostname %s provided"
327-
" via HTTP have no compatible SSL setup",
240+
" via HTTP have no compatible SSL setup for policy '%s'",
328241
servername ? servername : handshakeserver->server_hostname,
329242
servername ? "provided via SNI" : "(default host as no SNI was provided)",
330-
r->hostname);
243+
r->hostname, MODSSL_SNIVH_NAME(sc->mc->snivh_policy));
331244
return HTTP_MISDIRECTED_REQUEST;
332245
}
333246
}

0 commit comments

Comments
 (0)