summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2019-01-24 01:38:46 -0500
committerGlenn Strauss <gstrauss@gluelogic.com>2019-01-25 03:04:16 -0500
commitb17d3c2407e204fc22ec12cf9811aee1b0e52df2 (patch)
treecf3752a63554af40e0e53a2ae6c8c689279c0ece
parentf77cfe7ca8b3758b5bbe032c05e01e8a424b9f5f (diff)
downloadlighttpd-git-b17d3c2407e204fc22ec12cf9811aee1b0e52df2.tar.gz
[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
-rw-r--r--src/mod_openssl.c195
1 files changed, 194 insertions, 1 deletions
diff --git a/src/mod_openssl.c b/src/mod_openssl.c
index 109808bd..43bfffa8 100644
--- a/src/mod_openssl.c
+++ b/src/mod_openssl.c
@@ -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));