diff options
author | Glenn Strauss <gstrauss@gluelogic.com> | 2019-01-24 01:38:46 -0500 |
---|---|---|
committer | Glenn Strauss <gstrauss@gluelogic.com> | 2019-01-25 03:04:16 -0500 |
commit | b17d3c2407e204fc22ec12cf9811aee1b0e52df2 (patch) | |
tree | cf3752a63554af40e0e53a2ae6c8c689279c0ece | |
parent | f77cfe7ca8b3758b5bbe032c05e01e8a424b9f5f (diff) | |
download | lighttpd-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.c | 195 |
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)); |