[mod_openssl] ALPN and acme-tls/1 (fixes #2931)

ssl.acme-tls-1 = "/path/to/dir" containing .crt.pem and .key.pem
named with the SNI name ("<SNI>.crt.pem" and "<SNI>.key.pem")

x-ref:
  "Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension"
  https://tools.ietf.org/html/rfc7301
  "ACME TLS ALPN Challenge Extension" (TLS-ALPN-01)
  https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
  "Support for TLS-ALPN-01"
  https://redmine.lighttpd.net/issues/2931
personal/stbuehler/fix-fdevent
Glenn Strauss 2019-01-24 01:38:46 -05:00
parent f77cfe7ca8
commit b17d3c2407
1 changed files with 194 additions and 1 deletions

View File

@ -69,6 +69,7 @@ typedef struct {
buffer *ssl_dh_file;
buffer *ssl_ec_curve;
array *ssl_conf_cmd;
buffer *ssl_acme_tls_1;
} plugin_config;
typedef struct {
@ -87,7 +88,8 @@ typedef struct {
SSL *ssl;
connection *con;
int renegotiations; /* count of SSL_CB_HANDSHAKE_START */
int request_env_patched;
unsigned short request_env_patched;
unsigned short alpn;
plugin_config conf;
server *srv;
} handler_ctx;
@ -140,6 +142,7 @@ FREE_FUNC(mod_openssl_free)
buffer_free(s->ssl_ec_curve);
buffer_free(s->ssl_verifyclient_username);
array_free(s->ssl_conf_cmd);
buffer_free(s->ssl_acme_tls_1);
if (copy) continue;
SSL_CTX_free(s->ssl_ctx);
@ -350,6 +353,12 @@ network_ssl_servername_callback (SSL *ssl, int *al, server *srv)
/* use SNI to patch mod_openssl config and then reset COMP_HTTP_HOST */
buffer_copy_string_len(con->uri.authority, servername, len);
buffer_to_lower(con->uri.authority);
#if 0
/*(con->uri.authority used below for configuration before request read;
* revisit for h2)*/
if (0 != http_request_host_policy(con, con->uri.authority, con->uri.scheme))
return SSL_TLSEXT_ERR_ALERT_FATAL;
#endif
con->conditional_is_valid[COMP_HTTP_SCHEME] = 1;
con->conditional_is_valid[COMP_HTTP_HOST] = 1;
@ -520,6 +529,165 @@ network_openssl_load_pemfile (server *srv, plugin_config *s, size_t ndx)
}
#ifndef OPENSSL_NO_TLSEXT
#if OPENSSL_VERSION_NUMBER >= 0x10002000
static int
mod_openssl_acme_tls_1 (SSL *ssl, handler_ctx *hctx)
{
server *srv = hctx->srv;
buffer *b = srv->tmp_buf;
buffer *name = hctx->con->uri.authority;
X509 *ssl_pemfile_x509 = NULL;
EVP_PKEY *ssl_pemfile_pkey = NULL;
size_t len;
int rc = SSL_TLSEXT_ERR_ALERT_FATAL;
/* check if acme-tls/1 protocol is enabled (path to dir of cert(s) is set)*/
if (buffer_string_is_empty(hctx->conf.ssl_acme_tls_1))
return SSL_TLSEXT_ERR_NOACK; /*(reuse value here for not-configured)*/
buffer_copy_buffer(b, hctx->conf.ssl_acme_tls_1);
buffer_append_slash(b);
/* check if SNI set server name (required for acme-tls/1 protocol)
* and perform simple path checks for no '/'
* and no leading '.' (e.g. ignore "." or ".." or anything beginning '.') */
if (buffer_string_is_empty(name)) return rc;
if (NULL != strchr(name->ptr, '/')) return rc;
if (name->ptr[0] == '.') return rc;
#if 0
if (0 != http_request_host_policy(hctx->con, name, hctx->con->uri.scheme))
return rc;
#endif
buffer_append_string_buffer(b, name);
len = buffer_string_length(b);
do {
buffer_append_string_len(b, CONST_STR_LEN(".crt.pem"));
ssl_pemfile_x509 = x509_load_pem_file(srv, b->ptr);
if (NULL == ssl_pemfile_x509) {
log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
"Failed to load acme-tls/1 pemfile:", b);
break;
}
buffer_string_set_length(b, len); /*(remove ".crt.pem")*/
buffer_append_string_len(b, CONST_STR_LEN(".key.pem"));
ssl_pemfile_pkey = evp_pkey_load_pem_file(srv, b->ptr);
if (NULL == ssl_pemfile_pkey) {
log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
"Failed to load acme-tls/1 pemfile:", b);
break;
}
#if 0 /* redundant with below? */
if (!X509_check_private_key(ssl_pemfile_x509, ssl_pemfile_pkey)) {
log_error_write(srv, __FILE__, __LINE__, "sssb", "SSL:",
"Private key does not match acme-tls/1 certificate public key,"
" reason:" ERR_error_string(ERR_get_error(), NULL), b);
break;
}
#endif
/* first set certificate!
* setting private key checks whether certificate matches it */
if (1 != SSL_use_certificate(ssl, ssl_pemfile_x509)) {
log_error_write(srv, __FILE__, __LINE__, "ssb:s", "SSL:",
"failed to set acme-tls/1 certificate for TLS server name",
name, ERR_error_string(ERR_get_error(), NULL));
break;
}
if (1 != SSL_use_PrivateKey(ssl, ssl_pemfile_pkey)) {
log_error_write(srv, __FILE__, __LINE__, "ssb:s", "SSL:",
"failed to set acme-tls/1 private key for TLS server name",
name, ERR_error_string(ERR_get_error(), NULL));
break;
}
rc = SSL_TLSEXT_ERR_OK;
} while (0);
if (ssl_pemfile_pkey) EVP_PKEY_free(ssl_pemfile_pkey);
if (ssl_pemfile_x509) X509_free(ssl_pemfile_x509);
return rc;
}
enum {
MOD_OPENSSL_ALPN_HTTP11 = 1
,MOD_OPENSSL_ALPN_HTTP10 = 2
,MOD_OPENSSL_ALPN_H2 = 3
,MOD_OPENSSL_ALPN_ACME_TLS_1 = 4
};
/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids */
static int
mod_openssl_alpn_select_cb (SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
{
handler_ctx *hctx = (handler_ctx *) SSL_get_app_data(ssl);
unsigned short proto;
UNUSED(arg);
for (unsigned int i = 0, n; i < inlen; i += n) {
n = in[i++];
if (i+n > inlen) break;
switch (n) {
#if 0
case 2: /* "h2" */
if (in[i] == 'h' && in[i+1] == '2') {
proto = MOD_OPENSSL_ALPN_H2;
break;
}
continue;
#endif
case 8: /* "http/1.1" "http/1.0" */
if (0 == memcmp(in+i, "http/1.", 7)) {
if (in[i+7] == '1') {
proto = MOD_OPENSSL_ALPN_HTTP11;
break;
}
if (in[i+7] == '0') {
proto = MOD_OPENSSL_ALPN_HTTP10;
break;
}
}
continue;
case 10: /* "acme-tls/1" */
if (0 == memcmp(in+i, "acme-tls/1", 10)) {
int rc = mod_openssl_acme_tls_1(ssl, hctx);
if (rc == SSL_TLSEXT_ERR_OK) {
proto = MOD_OPENSSL_ALPN_ACME_TLS_1;
break;
}
/* (use SSL_TLSEXT_ERR_NOACK for not-configured) */
if (rc == SSL_TLSEXT_ERR_NOACK) continue;
return rc;
}
continue;
default:
continue;
}
hctx->alpn = proto;
*out = in+i;
*outlen = n;
return SSL_TLSEXT_ERR_OK;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
return SSL_TLSEXT_ERR_NOACK;
#else
return SSL_TLSEXT_ERR_ALERT_FATAL;
#endif
}
#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000 */
#endif /* OPENSSL_NO_TLSEXT */
static int
network_openssl_ssl_conf_cmd (server *srv, plugin_config *s)
{
@ -988,6 +1156,10 @@ network_init_ssl (server *srv, void *p_d)
"extension");
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10002000
SSL_CTX_set_alpn_select_cb(s->ssl_ctx,mod_openssl_alpn_select_cb,NULL);
#endif
#endif
if (s->ssl_conf_cmd->used) {
@ -1024,6 +1196,7 @@ SETDEFAULTS_FUNC(mod_openssl_set_defaults)
{ "ssl.ca-crl-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 18 */
{ "ssl.ca-dn-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 19 */
{ "ssl.openssl.ssl-conf-cmd", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 20 */
{ "ssl.acme-tls-1", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 21 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
@ -1061,6 +1234,7 @@ SETDEFAULTS_FUNC(mod_openssl_set_defaults)
s->ssl_conf_cmd = (0 == i)
? array_init()
: array_init_array(p->config_storage[0]->ssl_conf_cmd);
s->ssl_acme_tls_1 = buffer_init();
cv[0].destination = &(s->ssl_log_noise);
cv[1].destination = &(s->ssl_enabled);
@ -1083,6 +1257,7 @@ SETDEFAULTS_FUNC(mod_openssl_set_defaults)
cv[18].destination = s->ssl_ca_crl_file;
cv[19].destination = s->ssl_ca_dn_file;
cv[20].destination = s->ssl_conf_cmd;
cv[21].destination = s->ssl_acme_tls_1;
p->config_storage[i] = s;
@ -1157,6 +1332,7 @@ mod_openssl_patch_connection (server *srv, connection *con, handler_ctx *hctx)
PATCH(ssl_verifyclient_export_cert);
PATCH(ssl_disable_client_renegotiation);
PATCH(ssl_read_ahead);
PATCH(ssl_acme_tls_1);
PATCH(ssl_log_noise);
@ -1196,6 +1372,8 @@ mod_openssl_patch_connection (server *srv, connection *con, handler_ctx *hctx)
PATCH(ssl_disable_client_renegotiation);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.read-ahead"))) {
PATCH(ssl_read_ahead);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.acme-tls-1"))) {
PATCH(ssl_acme_tls_1);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-ssl-noise"))) {
PATCH(ssl_log_noise);
#if 0 /*(not patched)*/
@ -1459,6 +1637,21 @@ connection_read_cq_ssl (server *srv, connection *con,
"SSL: renegotiation initiated by client, killing connection");
return -1;
}
#if OPENSSL_VERSION_NUMBER >= 0x10002000
if (hctx->alpn) {
if (hctx->alpn == MOD_OPENSSL_ALPN_ACME_TLS_1) {
chunkqueue_reset(con->read_queue);
/* initiate handshake in order to send ServerHello.
* Once TLS handshake is complete, return -1 to result in
* CON_STATE_ERROR so that socket connection is quickly closed*/
if (1 == SSL_do_handshake(hctx->ssl)) return -1;
len = -1;
break;
}
hctx->alpn = 0;
}
#endif
} while (len > 0
&& (hctx->conf.ssl_read_ahead || SSL_pending(hctx->ssl) > 0));