Skip to content
Closed
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
2 changes: 2 additions & 0 deletions modules/ssl/mod_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,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
41 changes: 41 additions & 0 deletions modules/ssl/ssl_engine_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ static SSLModConfigRec *ssl_config_global_create(apr_pool_t *pool, server_rec *s
* initialize per-module configuration
*/
mc->sesscache_mode = SSL_SESS_CACHE_OFF;
#ifdef HAVE_TLSEXT
mc->snivh_policy = MODSSL_SNIVH_SECURE;
#endif
#ifdef MODSSL_USE_SSLRAND
mc->aRandSeed = apr_array_make(pool, 4,
sizeof(ssl_randseed_t));
Expand Down Expand Up @@ -2038,6 +2041,44 @@ 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 (!mc) {
return "SSLVHostSNIPolicy cannot be used inside SSLPolicyDefine";
}

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
106 changes: 106 additions & 0 deletions modules/ssl/ssl_engine_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,110 @@ static int load_echkeys(SSL_CTX *ctx, const char *echdir, server_rec *s,
}
#endif

#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_dump(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_dump(ptemp, hash, "cert_files:", ctx->pks->cert_files);
md5_strarray_dump(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 name:", p->name);
md5_str_update(hash, "##param 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 @@ -563,6 +667,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
129 changes: 20 additions & 109 deletions modules/ssl/ssl_engine_kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,112 +105,26 @@ static int fill_reneg_buffer(request_rec *r, SSLDirConfigRec *dc)
}

#ifdef HAVE_TLSEXT
static int ap_array_same_str_set(apr_array_header_t *s1, apr_array_header_t *s2)
/* Check whether a transition from vhost sc1 to sc2 via SNI is
* permitted according to the SSLVHostSNIPolicy setting. Returns 1 if
* the policy treats the vhosts as compatible, else 0. */
static int ssl_check_vhost_sni_policy(SSLSrvConfigRec *sc1,
SSLSrvConfigRec *sc2)
{
int i;
const char *c;

if (s1 == s2) {
modssl_snivhpolicy_t policy = sc1->mc->snivh_policy;

/* Policy: insecure => allow everything. */
if (policy == MODSSL_SNIVH_INSECURE)
return 1;
}
else if (!s1 || !s2 || (s1->nelts != s2->nelts)) {
return 0;
}

for (i = 0; i < s1->nelts; i++) {
c = APR_ARRAY_IDX(s1, i, const char *);
if (!c || !ap_array_str_contains(s2, c)) {
return 0;
}
}
return 1;
}

static int ssl_pk_server_compatible(modssl_pk_server_t *pks1,
modssl_pk_server_t *pks2)
{
if (!pks1 || !pks2) {
/* Policy: strict => fail for any vhost transition. */
if (policy == MODSSL_SNIVH_STRICT && sc1 != sc2)
return 0;
}
/* both have the same certificates? */
if ((pks1->ca_name_path != pks2->ca_name_path)
&& (!pks1->ca_name_path || !pks2->ca_name_path
|| strcmp(pks1->ca_name_path, pks2->ca_name_path))) {
return 0;
}
if ((pks1->ca_name_file != pks2->ca_name_file)
&& (!pks1->ca_name_file || !pks2->ca_name_file
|| strcmp(pks1->ca_name_file, pks2->ca_name_file))) {
return 0;
}
if (!ap_array_same_str_set(pks1->cert_files, pks2->cert_files)
|| !ap_array_same_str_set(pks1->key_files, pks2->key_files)) {
return 0;
}
return 1;
}

static int ssl_auth_compatible(modssl_auth_ctx_t *a1,
modssl_auth_ctx_t *a2)
{
if (!a1 || !a2) {
return 0;
}
/* both have the same verification */
if ((a1->verify_depth != a2->verify_depth)
|| (a1->verify_mode != a2->verify_mode)) {
return 0;
}
/* both have the same ca path/file */
if ((a1->ca_cert_path != a2->ca_cert_path)
&& (!a1->ca_cert_path || !a2->ca_cert_path
|| strcmp(a1->ca_cert_path, a2->ca_cert_path))) {
return 0;
}
if ((a1->ca_cert_file != a2->ca_cert_file)
&& (!a1->ca_cert_file || !a2->ca_cert_file
|| strcmp(a1->ca_cert_file, a2->ca_cert_file))) {
return 0;
}
/* both have the same ca cipher suite string */
if ((a1->cipher_suite != a2->cipher_suite)
&& (!a1->cipher_suite || !a2->cipher_suite
|| strcmp(a1->cipher_suite, a2->cipher_suite))) {
return 0;
}
/* both have the same ca cipher suite string */
if ((a1->tls13_ciphers != a2->tls13_ciphers)
&& (!a1->tls13_ciphers || !a2->tls13_ciphers
|| strcmp(a1->tls13_ciphers, a2->tls13_ciphers))) {
return 0;
}
return 1;
}

static int ssl_ctx_compatible(modssl_ctx_t *ctx1,
modssl_ctx_t *ctx2)
{
if (!ctx1 || !ctx2
|| (ctx1->protocol != ctx2->protocol)
|| !ssl_auth_compatible(&ctx1->auth, &ctx2->auth)
|| !ssl_pk_server_compatible(ctx1->pks, ctx2->pks)) {
return 0;
}
return 1;
}
AP_DEBUG_ASSERT(sc1->sni_policy_hash);
AP_DEBUG_ASSERT(sc2->sni_policy_hash);

static int ssl_server_compatible(server_rec *s1, server_rec *s2)
{
SSLSrvConfigRec *sc1 = s1? mySrvConfig(s1) : NULL;
SSLSrvConfigRec *sc2 = s2? mySrvConfig(s2) : NULL;

/* both use the same TLS protocol? */
if (!sc1 || !sc2
|| !ssl_ctx_compatible(sc1->server, sc2->server)) {
return 0;
}

return 1;
return strcmp(sc1->sni_policy_hash, sc2->sni_policy_hash) == 0;
}
#endif

Expand Down Expand Up @@ -279,6 +193,8 @@ int ssl_hook_ReadReq(request_rec *r)
server_rec *handshakeserver = sslconn->server;
SSLSrvConfigRec *hssc = mySrvConfig(handshakeserver);

AP_DEBUG_ASSERT(hssc);

if ((servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
/*
* The SNI extension supplied a hostname. So don't accept requests
Expand Down Expand Up @@ -319,19 +235,14 @@ int ssl_hook_ReadReq(request_rec *r)
"which is required to access this server.<br />\n");
return HTTP_FORBIDDEN;
}
if (r->server != handshakeserver
&& !ssl_server_compatible(sslconn->server, r->server)) {
/*
* The request does not select the virtual host that was
* selected for handshaking and its SSL parameters are different
*/

/* Enforce SSL SNI vhost compatibility policy. */
if (!ssl_check_vhost_sni_policy(sc, hssc)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02032)
"Hostname %s %s and hostname %s provided"
" via HTTP have no compatible SSL setup",
" via HTTP have no compatible SSL setup for policy '%s'",
servername ? servername : handshakeserver->server_hostname,
servername ? "provided via SNI" : "(default host as no SNI was provided)",
r->hostname);
r->hostname, MODSSL_SNIVH_NAME(sc->mc->snivh_policy));
return HTTP_MISDIRECTED_REQUEST;
}
}
Expand Down
15 changes: 15 additions & 0 deletions modules/ssl/ssl_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,17 @@ typedef struct {
int nBytes;
} ssl_randseed_t;

typedef enum {
MODSSL_SNIVH_STRICT = 0, /* default */
MODSSL_SNIVH_SECURE = 1,
MODSSL_SNIVH_AUTHONLY = 2,
MODSSL_SNIVH_INSECURE = 3
} modssl_snivhpolicy_t;

#define MODSSL_SNIVH_NAME(p_) ((p_) == MODSSL_SNIVH_STRICT ? "strict" : \
((p_) == MODSSL_SNIVH_SECURE ? "secure" : \
((p_) == MODSSL_SNIVH_AUTHONLY ? "authonly" : "insecure" )))

/**
* Define the structure of an ASN.1 anything
*/
Expand Down Expand Up @@ -723,6 +734,8 @@ typedef struct {
#ifdef HAVE_FIPS
BOOL fips;
#endif

modssl_snivhpolicy_t snivh_policy;
} SSLModConfigRec;

/** Structure representing configured filenames for certs and keys for
Expand Down Expand Up @@ -876,6 +889,7 @@ struct SSLSrvConfigRec {
modssl_ctx_t *server;
#ifdef HAVE_TLSEXT
ssl_enabled_t strict_sni_vhost_check;
const char *sni_policy_hash;
#endif
#ifndef OPENSSL_NO_COMP
BOOL compression;
Expand Down Expand Up @@ -961,6 +975,7 @@ const char *ssl_cmd_SSLRequire(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLUserName(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLRenegBufferSize(cmd_parms *cmd, void *dcfg, const char *arg);
const char *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag);
const char *ssl_cmd_SSLVHostSNIPolicy(cmd_parms *cmd, void *dcfg, const char *arg);
const char *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int flag);

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