Skip to content

Commit 8ef9689

Browse files
committed
Draft: Add SSLVHostSNIPolicy directive, which sets a global
policy for SSL configuration compatibility between VirtualHosts. SSLVHostSNIPolicy strict => fail for any vhost mismatch authonly => fail only for client verification/auth differences secure => fail as authonly plus ciphersuite, protocol, keypair diff insecure => allow everything "secure" is the default. PR: 69743
1 parent e54735b commit 8ef9689

File tree

4 files changed

+167
-64
lines changed

4 files changed

+167
-64
lines changed

modules/ssl/mod_ssl.c

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

99101
/*
100102
* Per-server context configuration directives

modules/ssl/ssl_engine_config.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ static SSLModConfigRec *ssl_config_global_create(apr_pool_t *pool, server_rec *s
6060
* initialize per-module configuration
6161
*/
6262
mc->sesscache_mode = SSL_SESS_CACHE_OFF;
63+
#ifdef HAVE_TLSEXT
64+
mc->snivh_policy = MODSSL_SNIVH_SECURE;
65+
#endif
6366
#ifdef MODSSL_USE_SSLRAND
6467
mc->aRandSeed = apr_array_make(pool, 4,
6568
sizeof(ssl_randseed_t));
@@ -2038,6 +2041,45 @@ const char *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag
20382041
#endif
20392042
}
20402043

2044+
const char *ssl_cmd_SSLVHostSNIPolicy(cmd_parms *cmd, void *dcfg, const char *arg)
2045+
{
2046+
#ifdef HAVE_TLSEXT
2047+
SSLModConfigRec *mc = myModConfig(cmd->server);
2048+
const char *err;
2049+
2050+
if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
2051+
return err;
2052+
}
2053+
if (!mc) {
2054+
return "SSLVHostSNIPolicy cannot be used inside SSLPolicyDefine";
2055+
}
2056+
2057+
if (strcEQ(arg, "secure")) {
2058+
mc->snivh_policy = MODSSL_SNIVH_SECURE;
2059+
}
2060+
else if (strcEQ(arg, "strict")) {
2061+
mc->snivh_policy = MODSSL_SNIVH_STRICT;
2062+
}
2063+
else if (strcEQ(arg, "insecure")) {
2064+
mc->snivh_policy = MODSSL_SNIVH_INSECURE;
2065+
}
2066+
else if (strcEQ(arg, "authonly")) {
2067+
mc->snivh_policy = MODSSL_SNIVH_AUTHONLY;
2068+
}
2069+
else {
2070+
return apr_psprintf(cmd->pool, "Invalid SSLVhostSNIPolicy "
2071+
"argument '%s'", arg);
2072+
}
2073+
2074+
return NULL;
2075+
#else
2076+
return "SSLVHostSNIPolicy cannot be used, OpenSSL is not built with "
2077+
"support for TLS extensions and SNI indication. Refer to the "
2078+
"documentation, and build a compatible version of OpenSSL."
2079+
#endif
2080+
}
2081+
2082+
20412083
#ifdef HAVE_OCSP_STAPLING
20422084

20432085
const char *ssl_cmd_SSLStaplingCache(cmd_parms *cmd,

modules/ssl/ssl_engine_kernel.c

Lines changed: 108 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,63 @@ static int ap_array_same_str_set(apr_array_header_t *s1, apr_array_header_t *s2)
126126
return 1;
127127
}
128128

129-
static int ssl_pk_server_compatible(modssl_pk_server_t *pks1,
130-
modssl_pk_server_t *pks2)
129+
#ifdef HAVE_SSL_CONF_CMD
130+
static int array_contains_ssl_param(apr_array_header_t *a,
131+
const char *name, const char *value)
131132
{
132-
if (!pks1 || !pks2) {
133+
int n;
134+
135+
for (n = 0; n < a->nelts; n++) {
136+
ssl_ctx_param_t *p = &APR_ARRAY_IDX(a, n, ssl_ctx_param_t);
137+
138+
if (strcmp(p->name, name) == 0 && strcmp(p->value, value) == 0)
139+
return 1;
140+
}
141+
142+
return 0;
143+
}
144+
145+
/* Return non-zero if the two ssl_ctx_param arrays match exactly, else
146+
* zero. */
147+
static int array_cmp_ssl_ctx_params(apr_array_header_t *a1,
148+
apr_array_header_t *a2)
149+
{
150+
int n;
151+
152+
if (a1 == a2)
153+
return 1;
154+
if (a1->nelts != a2->nelts)
133155
return 0;
156+
157+
for (n = 0; n < a1->nelts; n++) {
158+
ssl_ctx_param_t *p = &APR_ARRAY_IDX(a1, n, ssl_ctx_param_t);
159+
160+
if (!array_contains_ssl_param(a2, p->name, p->value))
161+
return 0;
134162
}
135-
/* both have the same certificates? */
136-
if ((pks1->ca_name_path != pks2->ca_name_path)
137-
&& (!pks1->ca_name_path || !pks2->ca_name_path
138-
|| strcmp(pks1->ca_name_path, pks2->ca_name_path))) {
163+
164+
return 1;
165+
}
166+
#endif
167+
168+
#define MATCH_STR_CONFIG(a_, b_) ((a_ != b_) && (!a_ || !b_ || strcmp(a_, b_)))
169+
170+
static int ssl_pk_server_compatible(SSLSrvConfigRec *sc1,
171+
SSLSrvConfigRec *sc2)
172+
{
173+
modssl_pk_server_t *pks1 = sc1->server->pks, *pks2 = sc2->server->pks;
174+
modssl_auth_ctx_t *a1 = &sc1->server->auth, *a2 = &sc2->server->auth;
175+
176+
if (sc1->server->protocol != sc2->server->protocol)
177+
return 0;
178+
179+
/* Check both TLS<1.3 and TLS/1.3 cipher config matches */
180+
if (MATCH_STR_CONFIG(a1->cipher_suite, a2->cipher_suite)
181+
|| MATCH_STR_CONFIG(a1->tls13_ciphers, a2->tls13_ciphers)) {
139182
return 0;
140183
}
141-
if ((pks1->ca_name_file != pks2->ca_name_file)
142-
&& (!pks1->ca_name_file || !pks2->ca_name_file
143-
|| strcmp(pks1->ca_name_file, pks2->ca_name_file))) {
184+
185+
if (!pks1 || !pks2) {
144186
return 0;
145187
}
146188
if (!ap_array_same_str_set(pks1->cert_files, pks2->cert_files)
@@ -150,67 +192,72 @@ static int ssl_pk_server_compatible(modssl_pk_server_t *pks1,
150192
return 1;
151193
}
152194

153-
static int ssl_auth_compatible(modssl_auth_ctx_t *a1,
154-
modssl_auth_ctx_t *a2)
195+
static int ssl_auth_compatible(SSLSrvConfigRec *sc1, SSLSrvConfigRec *sc2)
155196
{
156-
if (!a1 || !a2) {
157-
return 0;
158-
}
197+
modssl_ctx_t *ctx1 = sc1->server, *ctx2 = sc2->server;
198+
modssl_pk_server_t *pks1 = ctx1->pks, *pks2 = ctx2->pks;
199+
modssl_auth_ctx_t *a1 = &ctx1->auth, *a2 = &ctx2->auth;
200+
159201
/* both have the same verification */
160202
if ((a1->verify_depth != a2->verify_depth)
161203
|| (a1->verify_mode != a2->verify_mode)) {
162204
return 0;
163205
}
164-
/* both have the same ca path/file */
165-
if ((a1->ca_cert_path != a2->ca_cert_path)
166-
&& (!a1->ca_cert_path || !a2->ca_cert_path
167-
|| strcmp(a1->ca_cert_path, a2->ca_cert_path))) {
168-
return 0;
169-
}
170-
if ((a1->ca_cert_file != a2->ca_cert_file)
171-
&& (!a1->ca_cert_file || !a2->ca_cert_file
172-
|| strcmp(a1->ca_cert_file, a2->ca_cert_file))) {
173-
return 0;
174-
}
175-
/* both have the same ca cipher suite string */
176-
if ((a1->cipher_suite != a2->cipher_suite)
177-
&& (!a1->cipher_suite || !a2->cipher_suite
178-
|| strcmp(a1->cipher_suite, a2->cipher_suite))) {
206+
207+
/* Check that CA, CRL and OCSP settings match. */
208+
if (MATCH_STR_CONFIG(pks1->ca_name_path, pks2->ca_name_path)
209+
|| MATCH_STR_CONFIG(pks1->ca_name_file, pks2->ca_name_file)
210+
|| MATCH_STR_CONFIG(a1->ca_cert_path, a2->ca_cert_path)
211+
|| MATCH_STR_CONFIG(a1->ca_cert_file, a2->ca_cert_file)
212+
|| MATCH_STR_CONFIG(ctx1->crl_path, ctx2->crl_path)
213+
|| MATCH_STR_CONFIG(ctx1->crl_file, ctx2->crl_file)
214+
|| ctx1->crl_check_mask != ctx2->crl_check_mask
215+
|| ctx1->ocsp_mask != ctx2->ocsp_mask
216+
|| ctx1->ocsp_force_default != ctx2->ocsp_force_default
217+
|| MATCH_STR_CONFIG(ctx1->ocsp_responder, ctx2->ocsp_responder)) {
179218
return 0;
180219
}
181-
/* both have the same ca cipher suite string */
182-
if ((a1->tls13_ciphers != a2->tls13_ciphers)
183-
&& (!a1->tls13_ciphers || !a2->tls13_ciphers
184-
|| strcmp(a1->tls13_ciphers, a2->tls13_ciphers))) {
220+
221+
#ifdef HAVE_SRP
222+
if (MATCH_STR_CONFIG(ctx1->srp_vfile, ctx2->srp_vfile))
185223
return 0;
186-
}
187-
return 1;
188-
}
224+
#endif
189225

190-
static int ssl_ctx_compatible(modssl_ctx_t *ctx1,
191-
modssl_ctx_t *ctx2)
192-
{
193-
if (!ctx1 || !ctx2
194-
|| (ctx1->protocol != ctx2->protocol)
195-
|| !ssl_auth_compatible(&ctx1->auth, &ctx2->auth)
196-
|| !ssl_pk_server_compatible(ctx1->pks, ctx2->pks)) {
226+
#ifdef HAVE_SSL_CONF_CMD
227+
/* Without validating (and understanding) each specific
228+
* SSLOpenSSLConfCmd command, assume any difference is
229+
* relevant to authentication. */
230+
if (!array_cmp_ssl_ctx_params(ctx1->ssl_ctx_param, ctx2->ssl_ctx_param))
197231
return 0;
198-
}
232+
#endif
233+
199234
return 1;
200235
}
201236

202-
static int ssl_server_compatible(server_rec *s1, server_rec *s2)
237+
/* Check whether a transition from vhost sc1 to sc2 via SNI is
238+
* permitted according to the SSLVHostSNIPolicy setting. Returns 1 if
239+
* the policy treats the vhosts as compatible, else 0. */
240+
static int ssl_check_vhost_sni_policy(SSLSrvConfigRec *sc1,
241+
SSLSrvConfigRec *sc2)
203242
{
204-
SSLSrvConfigRec *sc1 = s1? mySrvConfig(s1) : NULL;
205-
SSLSrvConfigRec *sc2 = s2? mySrvConfig(s2) : NULL;
243+
modssl_snivhpolicy_t policy = sc1->mc->snivh_policy;
206244

207-
/* both use the same TLS protocol? */
208-
if (!sc1 || !sc2
209-
|| !ssl_ctx_compatible(sc1->server, sc2->server)) {
210-
return 0;
211-
}
245+
/* Policy: insecure => allow everything. */
246+
if (policy == MODSSL_SNIVH_INSECURE)
247+
return 1;
212248

213-
return 1;
249+
/* Policy: strict => fail for any vhost transition. */
250+
if (policy == MODSSL_SNIVH_STRICT && sc1 != sc2)
251+
return 0;
252+
253+
/* Policy: secure => fail for any difference in definition of
254+
* SSLProtocol/Cipher, certs or keys. */
255+
if (policy == MODSSL_SNIVH_SECURE && !ssl_pk_server_compatible(sc1, sc2))
256+
return 0;
257+
258+
/* Policy: authonly or secure => fail for any differences in
259+
* authentication/trust setting. */
260+
return ssl_auth_compatible(sc1, sc2);
214261
}
215262
#endif
216263

@@ -279,6 +326,8 @@ int ssl_hook_ReadReq(request_rec *r)
279326
server_rec *handshakeserver = sslconn->server;
280327
SSLSrvConfigRec *hssc = mySrvConfig(handshakeserver);
281328

329+
AP_DEBUG_ASSERT(hssc);
330+
282331
if ((servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
283332
/*
284333
* The SNI extension supplied a hostname. So don't accept requests
@@ -319,19 +368,14 @@ int ssl_hook_ReadReq(request_rec *r)
319368
"which is required to access this server.<br />\n");
320369
return HTTP_FORBIDDEN;
321370
}
322-
if (r->server != handshakeserver
323-
&& !ssl_server_compatible(sslconn->server, r->server)) {
324-
/*
325-
* The request does not select the virtual host that was
326-
* selected for handshaking and its SSL parameters are different
327-
*/
328-
371+
/* Enforce SSL SNI vhost compatibility policy. */
372+
if (!ssl_check_vhost_sni_policy(sc, hssc)) {
329373
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02032)
330-
"Hostname %s %s and hostname %s provided"
331-
" via HTTP have no compatible SSL setup",
374+
"Hostname %s %s and hostname %s provided"
375+
" via HTTP have no compatible SSL setup for policy '%s'",
332376
servername ? servername : handshakeserver->server_hostname,
333377
servername ? "provided via SNI" : "(default host as no SNI was provided)",
334-
r->hostname);
378+
r->hostname, MODSSL_SNIVH_NAME(sc->mc->snivh_policy));
335379
return HTTP_MISDIRECTED_REQUEST;
336380
}
337381
}

modules/ssl/ssl_private.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,18 @@ typedef struct {
558558
int nBytes;
559559
} ssl_randseed_t;
560560

561+
typedef enum {
562+
MODSSL_SNIVH_STRICT = 0, /* default */
563+
MODSSL_SNIVH_SECURE = 1,
564+
MODSSL_SNIVH_AUTHONLY = 2,
565+
MODSSL_SNIVH_INSECURE = 3
566+
} modssl_snivhpolicy_t;
567+
568+
#define MODSSL_SNIVH_NAME(p_) ((p_) == MODSSL_SNIVH_STRICT ? "strict" : \
569+
((p_) == MODSSL_SNIVH_SECURE ? "secure" : \
570+
((p_) == MODSSL_SNIVH_AUTHONLY ? "authonly" : "insecure" )))
571+
572+
561573
/**
562574
* Define the structure of an ASN.1 anything
563575
*/
@@ -723,6 +735,8 @@ typedef struct {
723735
#ifdef HAVE_FIPS
724736
BOOL fips;
725737
#endif
738+
739+
modssl_snivhpolicy_t snivh_policy;
726740
} SSLModConfigRec;
727741

728742
/** Structure representing configured filenames for certs and keys for
@@ -961,6 +975,7 @@ const char *ssl_cmd_SSLRequire(cmd_parms *, void *, const char *);
961975
const char *ssl_cmd_SSLUserName(cmd_parms *, void *, const char *);
962976
const char *ssl_cmd_SSLRenegBufferSize(cmd_parms *cmd, void *dcfg, const char *arg);
963977
const char *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag);
978+
const char *ssl_cmd_SSLVHostSNIPolicy(cmd_parms *cmd, void *dcfg, const char *arg);
964979
const char *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int flag);
965980

966981
const char *ssl_cmd_SSLProxyEngine(cmd_parms *cmd, void *dcfg, int flag);

0 commit comments

Comments
 (0)