/* * Copyright (C) Yichun Zhang (agentzh) */ #ifndef DDEBUG #define DDEBUG 0 #endif #include "ddebug.h" #if (NGX_STREAM_SSL) #include "ngx_stream_lua_cache.h" #include "ngx_stream_lua_initworkerby.h" #include "ngx_stream_lua_util.h" #include "ngx_stream_ssl_module.h" #include "ngx_stream_lua_contentby.h" #include "ngx_stream_lua_ssl_certby.h" #include "ngx_stream_lua_ssl_client_helloby.h" #include "ngx_stream_lua_directive.h" #include "ngx_stream_lua_ssl.h" static void ngx_stream_lua_ssl_client_hello_done(void *data); static void ngx_stream_lua_ssl_client_hello_aborted(void *data); static u_char *ngx_stream_lua_log_ssl_client_hello_error(ngx_log_t *log, u_char *buf, size_t len); static ngx_int_t ngx_stream_lua_ssl_client_hello_by_chunk(lua_State *L, ngx_stream_lua_request_t *r); ngx_int_t ngx_stream_lua_ssl_client_hello_handler_file(ngx_stream_lua_request_t *r, ngx_stream_lua_srv_conf_t *lscf, lua_State *L) { ngx_int_t rc; rc = ngx_stream_lua_cache_loadfile(r->connection->log, L, lscf->srv.ssl_client_hello_src.data, lscf->srv.ssl_client_hello_src_key); if (rc != NGX_OK) { return rc; } /* make sure we have a valid code chunk */ ngx_stream_lua_assert(lua_isfunction(L, -1)); return ngx_stream_lua_ssl_client_hello_by_chunk(L, r); } ngx_int_t ngx_stream_lua_ssl_client_hello_handler_inline(ngx_stream_lua_request_t *r, ngx_stream_lua_srv_conf_t *lscf, lua_State *L) { ngx_int_t rc; rc = ngx_stream_lua_cache_loadbuffer(r->connection->log, L, lscf->srv.ssl_client_hello_src.data, lscf->srv.ssl_client_hello_src.len, lscf->srv.ssl_client_hello_src_key, "=ssl_client_hello_by_lua"); if (rc != NGX_OK) { return rc; } /* make sure we have a valid code chunk */ ngx_stream_lua_assert(lua_isfunction(L, -1)); return ngx_stream_lua_ssl_client_hello_by_chunk(L, r); } char * ngx_stream_lua_ssl_client_hello_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; ngx_conf_t save; save = *cf; cf->handler = ngx_stream_lua_ssl_client_hello_by_lua; cf->handler_conf = conf; rv = ngx_stream_lua_conf_lua_block_parse(cf, cmd); *cf = save; return rv; } char * ngx_stream_lua_ssl_client_hello_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { #ifndef SSL_ERROR_WANT_CLIENT_HELLO_CB ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "at least OpenSSL 1.1.1 required but found " OPENSSL_VERSION_TEXT); return NGX_CONF_ERROR; #else u_char *p; u_char *name; ngx_str_t *value; ngx_stream_lua_srv_conf_t *lscf = conf; /* must specify a concrete handler */ if (cmd->post == NULL) { return NGX_CONF_ERROR; } if (lscf->srv.ssl_client_hello_handler) { return "is duplicate"; } if (ngx_stream_lua_ssl_init(cf->log) != NGX_OK) { return NGX_CONF_ERROR; } value = cf->args->elts; lscf->srv.ssl_client_hello_handler = (ngx_stream_lua_srv_conf_handler_pt) cmd->post; if (cmd->post == ngx_stream_lua_ssl_client_hello_handler_file) { /* Lua code in an external file */ name = ngx_stream_lua_rebase_path(cf->pool, value[1].data, value[1].len); if (name == NULL) { return NGX_CONF_ERROR; } lscf->srv.ssl_client_hello_src.data = name; lscf->srv.ssl_client_hello_src.len = ngx_strlen(name); p = ngx_palloc(cf->pool, NGX_STREAM_LUA_FILE_KEY_LEN + 1); if (p == NULL) { return NGX_CONF_ERROR; } lscf->srv.ssl_client_hello_src_key = p; p = ngx_copy(p, NGX_STREAM_LUA_FILE_TAG, NGX_STREAM_LUA_FILE_TAG_LEN); p = ngx_stream_lua_digest_hex(p, value[1].data, value[1].len); *p = '\0'; } else { /* inlined Lua code */ lscf->srv.ssl_client_hello_src = value[1]; p = ngx_palloc(cf->pool, sizeof("ssl_client_hello_by_lua") + NGX_STREAM_LUA_INLINE_KEY_LEN); if (p == NULL) { return NGX_CONF_ERROR; } lscf->srv.ssl_client_hello_src_key = p; p = ngx_copy(p, "ssl_client_hello_by_lua", sizeof("ssl_client_hello_by_lua") - 1); p = ngx_copy(p, NGX_STREAM_LUA_INLINE_TAG, NGX_STREAM_LUA_INLINE_TAG_LEN); p = ngx_stream_lua_digest_hex(p, value[1].data, value[1].len); *p = '\0'; } return NGX_CONF_OK; #endif /* NO SSL_ERROR_WANT_CLIENT_HELLO_CB */ } int ngx_stream_lua_ssl_client_hello_handler(ngx_ssl_conn_t *ssl_conn, int *al, void *arg) { lua_State *L; ngx_int_t rc; ngx_connection_t *c, *fc; ngx_stream_lua_request_t *r = NULL; ngx_pool_cleanup_t *cln; ngx_stream_lua_srv_conf_t *lscf; ngx_stream_lua_ssl_ctx_t *cctx; ngx_stream_core_srv_conf_t *cscf; ngx_stream_session_t *s, *fs; c = ngx_ssl_get_connection(ssl_conn); ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream ssl client hello: connection reusable: %ud", c->reusable); cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); dd("ssl client hello handler, client-hello-ctx=%p", cctx); if (cctx && cctx->entered_client_hello_handler) { /* not the first time */ if (cctx->done) { ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream lua_client_hello_by_lua:" " client hello cb exit code: %d", cctx->exit_code); dd("lua ssl client hello done, finally"); return cctx->exit_code; } return -1; } dd("first time"); ngx_reusable_connection(c, 0); s = c->data; fc = ngx_stream_lua_create_fake_connection(NULL); if (fc == NULL) { goto failed; } fc->log->handler = ngx_stream_lua_log_ssl_client_hello_error; fc->log->data = fc; fc->addr_text = c->addr_text; fc->listening = c->listening; fs = ngx_stream_lua_create_fake_session(fc); if (fs == NULL) { goto failed; } fs->main_conf = s->main_conf; fs->srv_conf = s->srv_conf; r = ngx_stream_lua_create_fake_request(fs); if (r == NULL) { goto failed; } fc->log->file = c->log->file; fc->log->log_level = c->log->log_level; fc->ssl = c->ssl; cscf = ngx_stream_get_module_srv_conf(fs, ngx_stream_core_module); #if defined(nginx_version) && nginx_version >= 1009000 ngx_set_connection_log(fc, cscf->error_log); #else #error "stream ssl_client_hello_by_lua only supports nginx >= 1.19.3" #endif if (cctx == NULL) { cctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_lua_ssl_ctx_t)); if (cctx == NULL) { goto failed; /* error */ } cctx->ctx_ref = LUA_NOREF; } cctx->exit_code = 1; /* successful by default */ cctx->connection = c; cctx->request = r; cctx->entered_client_hello_handler = 1; cctx->done = 0; dd("setting cctx"); if (SSL_set_ex_data(c->ssl->connection, ngx_stream_lua_ssl_ctx_index, cctx) == 0) { ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed"); goto failed; } lscf = ngx_stream_lua_get_module_srv_conf(r, ngx_stream_lua_module); /* TODO honor lua_code_cache off */ L = ngx_stream_lua_get_lua_vm(r, NULL); c->log->action = "loading SSL client hello by lua"; if (lscf->srv.ssl_client_hello_handler == NULL) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "no ssl_client_hello_by_lua* defined in " "server %s:%ui", &cscf->file_name, &cscf->line); goto failed; } rc = lscf->srv.ssl_client_hello_handler(r, lscf, L); if (rc >= NGX_OK || rc == NGX_ERROR) { cctx->done = 1; if (cctx->cleanup) { *cctx->cleanup = NULL; } ngx_log_debug2(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream lua_client_hello_by_lua:" " handler return value: %i, " "client hello cb exit code: %d", rc, cctx->exit_code); c->log->action = "SSL handshaking"; return cctx->exit_code; } /* rc == NGX_DONE */ cln = ngx_pool_cleanup_add(fc->pool, 0); if (cln == NULL) { goto failed; } cln->handler = ngx_stream_lua_ssl_client_hello_done; cln->data = cctx; if (cctx->cleanup == NULL) { cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { goto failed; } cln->data = cctx; cctx->cleanup = &cln->handler; } *cctx->cleanup = ngx_stream_lua_ssl_client_hello_aborted; return -1; #if 1 failed: if (r && r->pool) { ngx_stream_lua_free_fake_request(r); } if (fc) { ngx_stream_lua_close_fake_connection(fc); } return 0; #endif } static void ngx_stream_lua_ssl_client_hello_done(void *data) { ngx_connection_t *c; ngx_stream_lua_ssl_ctx_t *cctx = data; dd("lua ssl client hello done"); if (cctx->aborted) { return; } ngx_stream_lua_assert(cctx->done == 0); cctx->done = 1; if (cctx->cleanup) { *cctx->cleanup = NULL; } c = cctx->connection; c->log->action = "SSL handshaking"; ngx_post_event(c->write, &ngx_posted_events); } static void ngx_stream_lua_ssl_client_hello_aborted(void *data) { ngx_stream_lua_ssl_ctx_t *cctx = data; dd("lua ssl client hello done"); if (cctx->done) { /* completed successfully already */ return; } ngx_log_debug0(NGX_LOG_DEBUG_STREAM, cctx->connection->log, 0, "stream lua_client_hello_by_lua: client hello cb aborted"); cctx->aborted = 1; cctx->request->connection->ssl = NULL; ngx_stream_lua_finalize_fake_request(cctx->request, NGX_ERROR); } static u_char * ngx_stream_lua_log_ssl_client_hello_error(ngx_log_t *log, u_char *buf, size_t len) { u_char *p; ngx_connection_t *c; if (log->action) { p = ngx_snprintf(buf, len, " while %s", log->action); len -= p - buf; buf = p; } p = ngx_snprintf(buf, len, ", context: ssl_client_hello_by_lua*"); len -= p - buf; buf = p; c = log->data; if (c && c->addr_text.len) { p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); len -= p - buf; buf = p; } if (c && c->listening && c->listening->addr_text.len) { p = ngx_snprintf(buf, len, ", server: %V", &c->listening->addr_text); /* len -= p - buf; */ buf = p; } return buf; } static ngx_int_t ngx_stream_lua_ssl_client_hello_by_chunk(lua_State *L, ngx_stream_lua_request_t *r) { int co_ref; ngx_int_t rc; lua_State *co; ngx_stream_lua_ctx_t *ctx; ngx_stream_lua_cleanup_t *cln; ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module); if (ctx == NULL) { ctx = ngx_stream_lua_create_ctx(r->session); if (ctx == NULL) { rc = NGX_ERROR; ngx_stream_lua_finalize_request(r, rc); return rc; } } else { dd("reset ctx"); ngx_stream_lua_reset_ctx(r, L, ctx); } ctx->entered_content_phase = 1; /* {{{ new coroutine to handle request */ co = ngx_stream_lua_new_thread(r, L, &co_ref); if (co == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "stream failed to create new" " coroutine to handle request"); rc = NGX_ERROR; ngx_stream_lua_finalize_request(r, rc); return rc; } /* move code closure to new coroutine */ lua_xmove(L, co, 1); #ifndef OPENRESTY_LUAJIT /* set closure's env table to new coroutine's globals table */ ngx_stream_lua_get_globals_table(co); lua_setfenv(co, -2); #endif /* save nginx request in coroutine globals table */ ngx_stream_lua_set_req(co, r); ctx->cur_co_ctx = &ctx->entry_co_ctx; ctx->cur_co_ctx->co = co; ctx->cur_co_ctx->co_ref = co_ref; #ifdef NGX_LUA_USE_ASSERT ctx->cur_co_ctx->co_top = 1; #endif ngx_stream_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); /* register request cleanup hooks */ if (ctx->cleanup == NULL) { cln = ngx_stream_lua_cleanup_add(r, 0); if (cln == NULL) { rc = NGX_ERROR; ngx_stream_lua_finalize_request(r, rc); return rc; } cln->handler = ngx_stream_lua_request_cleanup_handler; cln->data = ctx; ctx->cleanup = &cln->handler; } ctx->context = NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO; rc = ngx_stream_lua_run_thread(L, r, ctx, 0); if (rc == NGX_ERROR || rc >= NGX_OK) { /* do nothing */ } else if (rc == NGX_AGAIN) { rc = ngx_stream_lua_content_run_posted_threads(L, r, ctx, 0); } else if (rc == NGX_DONE) { rc = ngx_stream_lua_content_run_posted_threads(L, r, ctx, 1); } else { rc = NGX_OK; } ngx_stream_lua_finalize_request(r, rc); return rc; } int ngx_stream_lua_ffi_ssl_get_client_hello_server_name( ngx_stream_lua_request_t *r, const char **name, size_t *namelen, char **err) { ngx_ssl_conn_t *ssl_conn; #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB const unsigned char *p; size_t remaining, len; #endif if (r->connection == NULL || r->connection->ssl == NULL) { *err = "bad request"; return NGX_ERROR; } ssl_conn = r->connection->ssl->connection; if (ssl_conn == NULL) { *err = "bad ssl conn"; return NGX_ERROR; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB remaining = 0; /* This code block is taken from OpenSSL's client_hello_select_server_ctx() * */ if (!SSL_client_hello_get0_ext(ssl_conn, TLSEXT_TYPE_server_name, &p, &remaining)) { return NGX_DECLINED; } if (remaining <= 2) { *err = "Bad SSL Client Hello Extension"; return NGX_ERROR; } len = (*(p++) << 8); len += *(p++); if (len + 2 != remaining) { *err = "Bad SSL Client Hello Extension"; return NGX_ERROR; } remaining = len; if (remaining == 0 || *p++ != TLSEXT_NAMETYPE_host_name) { *err = "Bad SSL Client Hello Extension"; return NGX_ERROR; } remaining--; if (remaining <= 2) { *err = "Bad SSL Client Hello Extension"; return NGX_ERROR; } len = (*(p++) << 8); len += *(p++); if (len + 2 > remaining) { *err = "Bad SSL Client Hello Extension"; return NGX_ERROR; } remaining = len; *name = (const char *) p; *namelen = len; return NGX_OK; #else *err = "OpenSSL too old to support this function"; return NGX_ERROR; #endif #else *err = "no TLS extension support"; return NGX_ERROR; #endif } int ngx_stream_lua_ffi_ssl_get_client_hello_ext(ngx_stream_lua_request_t *r, unsigned int type, const unsigned char **out, size_t *outlen, char **err) { ngx_ssl_conn_t *ssl_conn; if (r->connection == NULL || r->connection->ssl == NULL) { *err = "bad request"; return NGX_ERROR; } ssl_conn = r->connection->ssl->connection; if (ssl_conn == NULL) { *err = "bad ssl conn"; return NGX_ERROR; } #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB if (SSL_client_hello_get0_ext(ssl_conn, type, out, outlen) == 0) { return NGX_DECLINED; } return NGX_OK; #else *err = "OpenSSL too old to support this function"; return NGX_ERROR; #endif } int ngx_stream_lua_ffi_ssl_set_protocols(ngx_stream_lua_request_t *r, int protocols, char **err) { ngx_ssl_conn_t *ssl_conn; if (r->connection == NULL || r->connection->ssl == NULL) { *err = "bad request"; return NGX_ERROR; } ssl_conn = r->connection->ssl->connection; if (ssl_conn == NULL) { *err = "bad ssl conn"; return NGX_ERROR; } #if OPENSSL_VERSION_NUMBER >= 0x009080dfL /* only in 0.9.8m+ */ SSL_clear_options(ssl_conn, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1); #endif if (!(protocols & NGX_SSL_SSLv2)) { SSL_set_options(ssl_conn, SSL_OP_NO_SSLv2); } if (!(protocols & NGX_SSL_SSLv3)) { SSL_set_options(ssl_conn, SSL_OP_NO_SSLv3); } if (!(protocols & NGX_SSL_TLSv1)) { SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1); } #ifdef SSL_OP_NO_TLSv1_1 SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_1); if (!(protocols & NGX_SSL_TLSv1_1)) { SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_1); } #endif #ifdef SSL_OP_NO_TLSv1_2 SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_2); if (!(protocols & NGX_SSL_TLSv1_2)) { SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_2); } #endif #ifdef SSL_OP_NO_TLSv1_3 SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_3); if (!(protocols & NGX_SSL_TLSv1_3)) { SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_3); } #endif return NGX_OK; } #endif /* NGX_STREAM_SSL */