This is an automated email from the ASF dual-hosted git repository. cmcfarlen pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 8acd3df4aad62d159ae1229490fb5492aabe10f5 Author: Brian Neradt <[email protected]> AuthorDate: Tue Jun 23 12:43:51 2026 -0500 Test TLS async without ENGINE (#13264) Some OpenSSL builds expose the async APIs ATS needs while disabling the legacy ENGINE interface. The previous async handshake coverage depended on a test-only dynamic ENGINE just to make OpenSSL suspend during the TLS handshake, leaving those builds without the async-path AuTest coverage. This replaces the ENGINE plugin with a test plugin that pauses the current OpenSSL async job from a TLS certificate hook. This keeps the test focused on ATS's SSL_ERROR_WANT_ASYNC handling while avoiding the deprecated ENGINE API and dynamic ENGINE availability checks. (cherry picked from commit 422d770f3fe29f47f21695c2a1346835ea9a93a3) --- ..._engine.test.py => tls_async_handshake.test.py} | 43 +-- tests/tools/plugins/CMakeLists.txt | 6 +- tests/tools/plugins/async_engine.c | 325 --------------------- tests/tools/plugins/async_handshake.cc | 182 ++++++++++++ 4 files changed, 194 insertions(+), 362 deletions(-) diff --git a/tests/gold_tests/tls/tls_engine.test.py b/tests/gold_tests/tls/tls_async_handshake.test.py similarity index 66% rename from tests/gold_tests/tls/tls_engine.test.py rename to tests/gold_tests/tls/tls_async_handshake.test.py index 623f430538..5ac4e28959 100644 --- a/tests/gold_tests/tls/tls_engine.test.py +++ b/tests/gold_tests/tls/tls_async_handshake.test.py @@ -18,25 +18,23 @@ import os -# Someday should add client cert to origin to exercise the -# engine interface on the other side - Test.Summary = ''' -Test tls via the async interface with the sample async_engine +Test TLS handshakes through OpenSSL's async job interface. ''' +async_handshake = os.path.join(Test.Variables.AtsTestPluginsDir, 'async_handshake.so') + Test.SkipUnless( Condition.HasOpenSSLVersion('1.1.1'), Condition.IsOpenSSL(), + Condition(lambda: os.path.isfile(async_handshake), async_handshake + " not found."), ) -# Define default ATS ts = Test.MakeATSProcess("ts", enable_tls=True) server = Test.MakeOriginServer("server") -ts.Setup.Copy(os.path.join(Test.Variables.AtsTestPluginsDir, 'async_engine.so'), Test.RunDirectory) +Test.PrepareTestPlugin(async_handshake, ts) -# Add info the origin server responses server.addResponse( "sessionlog.json", { "headers": "GET / HTTP/1.1\r\nuuid: basic\r\n\r\n", @@ -44,12 +42,12 @@ server.addResponse( "body": "" }, { "headers": - "HTTP/1.1 200 OK\r\nServer: microserver\r\nConnection: close\r\nCache-Control: max-age=3600\r\nContent-Length: 2\r\n\r\n", + "HTTP/1.1 200 OK\r\nServer: microserver\r\nConnection: close\r\n" + "Cache-Control: max-age=3600\r\nContent-Length: 2\r\n\r\n", "timestamp": "1469733493.993", "body": "ok" }) -# add ssl materials like key, certificates for the server ts.addSSLfile("ssl/server.pem") ts.addSSLfile("ssl/server.key") @@ -61,36 +59,11 @@ ts.Disk.records_config.update( 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), 'proxy.config.exec_thread.autoconfig.scale': 1.0, - 'proxy.config.ssl.engine.conf_file': '{0}/ts/config/load_engine.cnf'.format(Test.RunDirectory), 'proxy.config.ssl.async.handshake.enabled': 1, 'proxy.config.diags.debug.enabled': 0, 'proxy.config.diags.debug.tags': 'ssl|http' }) -ts.Disk.MakeConfigFile('load_engine.cnf').AddLines( - [ - 'openssl_conf = openssl_init', - '', - '[openssl_init]', - '', - 'engines = engine_section', - '', - '[engine_section]', - '', - 'async = async_section', - '', - '[async_section]', - '', - 'dynamic_path = {0}/async_engine.so'.format(Test.RunDirectory), - '', - 'engine_id = async-test', - '', - 'default_algorithms = RSA', - '', - 'init = 1', - ]) - -# Make a basic request. Hopefully it goes through tr = Test.AddTestRun("Run-Test") tr.MakeCurlCommand("-k -v -H uuid:basic -H host:example.com https://127.0.0.1:{0}/".format(ts.Variables.ssl_port), ts=ts) tr.ReturnCode = 0 @@ -99,4 +72,4 @@ tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Varia tr.Processes.Default.Streams.All = Testers.ContainsExpression(r"HTTP/(2|1\.1) 200", "Request succeeds") tr.StillRunningAfter = server -ts.Disk.traffic_out.Content += Testers.ContainsExpression("Send signal to ", "The Async engine triggers") +ts.Disk.traffic_out.Content += Testers.ContainsExpression("resumed OpenSSL async job", "The OpenSSL async job resumes") diff --git a/tests/tools/plugins/CMakeLists.txt b/tests/tools/plugins/CMakeLists.txt index 05a4d3d6e1..bbdc5866cd 100644 --- a/tests/tools/plugins/CMakeLists.txt +++ b/tests/tools/plugins/CMakeLists.txt @@ -34,7 +34,6 @@ target_link_libraries(test_cppapi PRIVATE ts::tscppapi) add_autest_plugin(test_hooks test_hooks.cc) add_autest_plugin(test_log_interface test_log_interface.cc) add_autest_plugin(user_args user_args.cc) -add_autest_plugin(async_engine async_engine.c) add_autest_plugin(hook_tunnel_plugin hook_tunnel_plugin.cc) add_autest_plugin(tunnel_transform tunnel_transform.cc) add_autest_plugin(http2_close_connection http2_close_connection.cc) @@ -44,4 +43,7 @@ target_link_libraries(ssl_client_verify_test PRIVATE OpenSSL::SSL) target_link_libraries(ssl_hook_test PRIVATE OpenSSL::SSL) target_link_libraries(ssl_secret_load_test PRIVATE OpenSSL::SSL) target_link_libraries(ssl_verify_test PRIVATE OpenSSL::SSL) -target_link_libraries(async_engine PRIVATE OpenSSL::SSL) +if(TS_USE_TLS_ASYNC) + add_autest_plugin(async_handshake async_handshake.cc) + target_link_libraries(async_handshake PRIVATE OpenSSL::SSL) +endif() diff --git a/tests/tools/plugins/async_engine.c b/tests/tools/plugins/async_engine.c deleted file mode 100644 index 8032293449..0000000000 --- a/tests/tools/plugins/async_engine.c +++ /dev/null @@ -1,325 +0,0 @@ -/** @file - - Test crypto engine - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -/* - * Test engine to exercise the asynchronous job interface - * It performs the standard RSA operations, but for private key - * operations spawns a thread to sleep for 5 seconds before resuming - * the asynchronous job - */ - -/* TLS_USE_TLS_ASYNC defined in ink_config.h */ -#include "tscore/ink_config.h" -#include "tscore/ink_defs.h" -#if TS_USE_TLS_ASYNC - -#include <stdio.h> -#include <string.h> - -#include <openssl/engine.h> -#include <openssl/rsa.h> -#include <openssl/async.h> -#include <openssl/crypto.h> -#include <openssl/ssl.h> -#include <openssl/modes.h> -#include <pthread.h> -#include <unistd.h> - -/* Engine Id and Name */ -static const char *engine_id = "async-test"; -static const char *engine_name = "Asynchronous test engine"; - -/* Engine Lifetime functions */ -static int async_destroy(ENGINE *e); -static int engine_async_init(ENGINE *e); -static int async_finish(ENGINE *e); -void engine_load_async_int(void); - -static void async_pause_job(void); - -/* RSA */ - -static int async_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding); -static int async_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding); -static int async_rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding); -static int async_rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding); -static int async_rsa_mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx); - -static int async_rsa_init(RSA *rsa); -static int async_rsa_finish(RSA *rsa); - -static RSA_METHOD *async_rsa_method = NULL; - -EVP_PKEY * -async_load_privkey(ENGINE *e ATS_UNUSED, const char *s_key_id, UI_METHOD *ui_method ATS_UNUSED, void *callback_data ATS_UNUSED) -{ - fprintf(stderr, "Loading key %s\n", s_key_id); - FILE *f = fopen(s_key_id, "r"); - if (f) { - EVP_PKEY *key = PEM_read_PrivateKey(f, NULL, NULL, NULL); - fclose(f); - return key; - } else { - return NULL; - } -} - -static int -bind_async(ENGINE *e) -{ - /* Setup RSA_METHOD */ - if ((async_rsa_method = RSA_meth_new("Async RSA method", 0)) == NULL || - RSA_meth_set_pub_enc(async_rsa_method, async_pub_enc) == 0 || RSA_meth_set_pub_dec(async_rsa_method, async_pub_dec) == 0 || - RSA_meth_set_priv_enc(async_rsa_method, async_rsa_priv_enc) == 0 || - RSA_meth_set_priv_dec(async_rsa_method, async_rsa_priv_dec) == 0 || - RSA_meth_set_mod_exp(async_rsa_method, async_rsa_mod_exp) == 0 || - RSA_meth_set_bn_mod_exp(async_rsa_method, BN_mod_exp_mont) == 0 || RSA_meth_set_init(async_rsa_method, async_rsa_init) == 0 || - RSA_meth_set_finish(async_rsa_method, async_rsa_finish) == 0) { - fprintf(stderr, "Failed to initialize rsa method\n"); - return 0; - } - - /* Ensure the dasync error handling is set up */ - ERR_load_ASYNC_strings(); - - if (!ENGINE_set_id(e, engine_id) || !ENGINE_set_name(e, engine_name) || !ENGINE_set_RSA(e, async_rsa_method) || - !ENGINE_set_destroy_function(e, async_destroy) || !ENGINE_set_init_function(e, engine_async_init) || - !ENGINE_set_finish_function(e, async_finish) || !ENGINE_set_load_privkey_function(e, async_load_privkey)) { - fprintf(stderr, "Failed to initialize\n"); - return 0; - } - - return 1; -} - -#ifndef OPENSSL_NO_DYNAMIC_ENGINE -static int -bind_helper(ENGINE *e, const char *id) -{ - if (id && (strcmp(id, engine_id) != 0)) - return 0; - if (!bind_async(e)) - return 0; - return 1; -} - -IMPLEMENT_DYNAMIC_CHECK_FN() -IMPLEMENT_DYNAMIC_BIND_FN(bind_helper) -#endif - -static ENGINE * -engine_async(void) -{ - ENGINE *ret = ENGINE_new(); - if (!ret) - return NULL; - if (!bind_async(ret)) { - ENGINE_free(ret); - return NULL; - } - return ret; -} - -void -engine_load_async_int(void) -{ - ENGINE *toadd = engine_async(); - if (!toadd) - return; - ENGINE_add(toadd); - ENGINE_free(toadd); - ERR_clear_error(); -} - -static int -engine_async_init(ENGINE *e ATS_UNUSED) -{ - return 1; -} - -static int -async_finish(ENGINE *e ATS_UNUSED) -{ - return 1; -} - -static int -async_destroy(ENGINE *e ATS_UNUSED) -{ - RSA_meth_free(async_rsa_method); - return 1; -} - -static void -wait_cleanup(ASYNC_WAIT_CTX *ctx ATS_UNUSED, const void *key ATS_UNUSED, OSSL_ASYNC_FD readfd, void *pvwritefd) -{ - OSSL_ASYNC_FD *pwritefd = (OSSL_ASYNC_FD *)pvwritefd; - close(readfd); - close(*((OSSL_ASYNC_FD *)pwritefd)); - OPENSSL_free(pwritefd); - fprintf(stderr, "Cleanup %d and %d\n", readfd, *pwritefd); -} - -#define DUMMY_CHAR 'X' - -static void -async_pause_job(void) -{ - ASYNC_JOB *job; - ASYNC_WAIT_CTX *waitctx; - OSSL_ASYNC_FD pipefds[2] = {0, 0}; - OSSL_ASYNC_FD *writefd; - char buf = DUMMY_CHAR; - - if ((job = ASYNC_get_current_job()) == NULL) { - fprintf(stderr, "No job\n"); - return; - } - - waitctx = ASYNC_get_wait_ctx(job); - - if (ASYNC_WAIT_CTX_get_fd(waitctx, engine_id, &pipefds[0], (void **)&writefd)) { - fprintf(stderr, "Existing wait ctx %d\n", *writefd); - } else { - writefd = (OSSL_ASYNC_FD *)OPENSSL_malloc(sizeof(*writefd)); - if (writefd == NULL) - return; - if (pipe(pipefds) != 0) { - OPENSSL_free(writefd); - return; - } - *writefd = pipefds[1]; - - fprintf(stderr, "New wait ctx %d %d\n", pipefds[0], pipefds[1]); - - if (!ASYNC_WAIT_CTX_set_wait_fd(waitctx, engine_id, pipefds[0], writefd, wait_cleanup)) { - fprintf(stderr, "set_wait_fd failed\n"); - wait_cleanup(waitctx, engine_id, pipefds[0], writefd); - return; - } - } - - /* Ignore errors - we carry on anyway */ - ASYNC_pause_job(); - - /* Clear the wake signal */ - if (read(pipefds[0], &buf, 1) < 0) - return; -} - -void * -delay_method(void *arg) -{ - int signal_fd = (intptr_t)arg; - sleep(2); - char buf = DUMMY_CHAR; - if (write(signal_fd, &buf, sizeof(buf)) < 0) { - fprintf(stderr, "Failed to send signal to %d, errno=%d\n", signal_fd, errno); - } else { - fprintf(stderr, "Send signal to %d\n", signal_fd); - } - return NULL; -} - -void -spawn_delay_thread() -{ - pthread_t thread_id; - ASYNC_JOB *job; - if ((job = ASYNC_get_current_job()) == NULL) { - fprintf(stderr, "Spawn no job\n"); - return; - } - - ASYNC_WAIT_CTX *waitctx = ASYNC_get_wait_ctx(job); - - size_t numfds; - if (ASYNC_WAIT_CTX_get_all_fds(waitctx, NULL, &numfds) && numfds > 0) { - fprintf(stderr, "Spawn, wait_ctx exists. Go away, something else is using this job\n"); - } else { - OSSL_ASYNC_FD signal_fd; - OSSL_ASYNC_FD pipefds[2] = {0, 0}; - OSSL_ASYNC_FD *writefd = OPENSSL_malloc(sizeof(*writefd)); - if (pipe(pipefds) < 0) { - fprintf(stderr, "Spawn, failed to create pipe errno=%d\n", errno); - return; - } - signal_fd = *writefd = pipefds[1]; - ASYNC_WAIT_CTX_set_wait_fd(waitctx, engine_id, pipefds[0], writefd, wait_cleanup); - fprintf(stderr, "Spawn, create wait_ctx %d %d\n", pipefds[0], pipefds[1]); - pthread_create(&thread_id, NULL, delay_method, (void *)((intptr_t)signal_fd)); - } -} - -/* - * RSA implementation - */ - -static int -async_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) -{ - return RSA_meth_get_pub_enc(RSA_PKCS1_OpenSSL())(flen, from, to, rsa, padding); -} - -static int -async_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) -{ - return RSA_meth_get_pub_dec(RSA_PKCS1_OpenSSL())(flen, from, to, rsa, padding); -} - -static int -async_rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) -{ - fprintf(stderr, "async_priv_enc\n"); - spawn_delay_thread(); - async_pause_job(); - fprintf(stderr, "async_priv_enc resume\n"); - return RSA_meth_get_priv_enc(RSA_PKCS1_OpenSSL())(flen, from, to, rsa, padding); -} - -static int -async_rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) -{ - fprintf(stderr, "async_priv_dec\n"); - spawn_delay_thread(); - async_pause_job(); - return RSA_meth_get_priv_dec(RSA_PKCS1_OpenSSL())(flen, from, to, rsa, padding); -} - -static int -async_rsa_mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx) -{ - return RSA_meth_get_mod_exp(RSA_PKCS1_OpenSSL())(r0, I, rsa, ctx); -} - -static int -async_rsa_init(RSA *rsa) -{ - return RSA_meth_get_init(RSA_PKCS1_OpenSSL())(rsa); -} -static int -async_rsa_finish(RSA *rsa) -{ - return RSA_meth_get_finish(RSA_PKCS1_OpenSSL())(rsa); -} -#endif /* TS_USE_TLS_ASYNC */ diff --git a/tests/tools/plugins/async_handshake.cc b/tests/tools/plugins/async_handshake.cc new file mode 100644 index 0000000000..3747ce17eb --- /dev/null +++ b/tests/tools/plugins/async_handshake.cc @@ -0,0 +1,182 @@ +/** @file + + Test TLS handshakes that pause inside an OpenSSL async job. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include <ts/ts.h> + +#include <openssl/async.h> +#include <openssl/crypto.h> + +#include <cerrno> +#include <cstdint> +#include <cstdio> +#include <pthread.h> +#include <unistd.h> + +#define PLUGIN_NAME "async_handshake" +#define PCP "[" PLUGIN_NAME "] " + +namespace +{ +DbgCtl dbg_ctl{PLUGIN_NAME}; +char async_hook_key; + +void +wait_cleanup(ASYNC_WAIT_CTX * /* ctx ATS_UNUSED */, const void * /* key ATS_UNUSED */, OSSL_ASYNC_FD read_fd, void *write_fd_ptr) +{ + auto *write_fd = static_cast<OSSL_ASYNC_FD *>(write_fd_ptr); + + close(read_fd); + close(*write_fd); + OPENSSL_free(write_fd); +} + +void * +wake_async_job(void *arg) +{ + auto signal_fd = static_cast<OSSL_ASYNC_FD>(reinterpret_cast<intptr_t>(arg)); + char buf = 'X'; + + usleep(100 * 1000); + if (write(signal_fd, &buf, sizeof(buf)) < 0) { + fprintf(stderr, PCP "failed to send async wake signal to %d, errno=%d\n", signal_fd, errno); + } else { + fprintf(stderr, PCP "sent async wake signal to %d\n", signal_fd); + } + + return nullptr; +} + +bool +install_wait_fd(ASYNC_WAIT_CTX *wait_ctx, OSSL_ASYNC_FD &read_fd) +{ + OSSL_ASYNC_FD *write_fd = nullptr; + if (ASYNC_WAIT_CTX_get_fd(wait_ctx, &async_hook_key, &read_fd, reinterpret_cast<void **>(&write_fd))) { + return true; + } + + OSSL_ASYNC_FD pipe_fds[2] = {-1, -1}; + write_fd = static_cast<OSSL_ASYNC_FD *>(OPENSSL_malloc(sizeof(*write_fd))); + if (write_fd == nullptr) { + TSError(PCP "failed to allocate async wait fd storage"); + return false; + } + + if (pipe(pipe_fds) < 0) { + TSError(PCP "failed to create async wait pipe: errno=%d", errno); + OPENSSL_free(write_fd); + return false; + } + *write_fd = pipe_fds[1]; + + if (!ASYNC_WAIT_CTX_set_wait_fd(wait_ctx, &async_hook_key, pipe_fds[0], write_fd, wait_cleanup)) { + TSError(PCP "failed to install async wait fd"); + close(pipe_fds[0]); + close(pipe_fds[1]); + OPENSSL_free(write_fd); + return false; + } + + pthread_t thread_id; + if (pthread_create(&thread_id, nullptr, wake_async_job, reinterpret_cast<void *>(static_cast<intptr_t>(*write_fd))) == 0) { + pthread_detach(thread_id); + } else if (write(*write_fd, "X", 1) < 0) { + TSError(PCP "failed to start async wake thread and immediate wake failed: errno=%d", errno); + return false; + } + + read_fd = pipe_fds[0]; + return true; +} + +void +pause_current_async_job(TSVConn ssl_vc) +{ + ASYNC_JOB *job = ASYNC_get_current_job(); + if (job == nullptr) { + TSError(PCP "SSL cert hook is not running in an OpenSSL async job"); + TSVConnReenable(ssl_vc); + return; + } + + ASYNC_WAIT_CTX *wait_ctx = ASYNC_get_wait_ctx(job); + if (wait_ctx == nullptr) { + TSError(PCP "OpenSSL async job has no wait context"); + TSVConnReenable(ssl_vc); + return; + } + + OSSL_ASYNC_FD read_fd = -1; + if (!install_wait_fd(wait_ctx, read_fd)) { + TSVConnReenable(ssl_vc); + return; + } + + TSVConnReenable(ssl_vc); + + fprintf(stderr, PCP "pausing OpenSSL async job\n"); + if (!ASYNC_pause_job()) { + TSError(PCP "ASYNC_pause_job failed"); + return; + } + + fprintf(stderr, PCP "resumed OpenSSL async job\n"); +} + +int +handle_ssl_cert(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) +{ + TSVConn ssl_vc = static_cast<TSVConn>(edata); + + if (event == TS_EVENT_SSL_CERT) { + pause_current_async_job(ssl_vc); + } else { + TSVConnReenable(ssl_vc); + } + return TS_SUCCESS; +} + +} // namespace + +void +TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */) +{ + TSPluginRegistrationInfo info; + + info.plugin_name = PLUGIN_NAME; + info.vendor_name = "Apache Software Foundation"; + info.support_email = "[email protected]"; + + if (TS_SUCCESS != TSPluginRegister(&info)) { + TSError(PCP "registration failed"); + return; + } + + TSCont cb_cert = TSContCreate(handle_ssl_cert, TSMutexCreate()); + if (cb_cert == nullptr) { + TSError(PCP "failed to create SSL cert hook"); + return; + } + + TSHttpHookAdd(TS_SSL_CERT_HOOK, cb_cert); + Dbg(dbg_ctl, "plugin online"); +}
