diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de93ac1..3c3331b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Add new entries under `Unreleased`; published release sections are history. ## Unreleased +- TLS 1.3 handshake builder failures preserve the original error and release + the unsent packet, instead of leaking it or trying to send it. - TLS servers reject malformed curve and signature-algorithm lists without reading past them. Curve selection no longer mistakes a list's length for an offered curve. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ca973c04..d2769e36 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ add_subdirectory(atf) add_subdirectory(fuzz) add_subdirectory(toolchain) add_subdirectory(x509) +add_subdirectory(tls13_handshake_fault) add_subdirectory(leak) add_subdirectory(concurrent) add_subdirectory(bracket) diff --git a/tests/tls13_handshake_fault/CMakeLists.txt b/tests/tls13_handshake_fault/CMakeLists.txt new file mode 100644 index 00000000..bd0d30d7 --- /dev/null +++ b/tests/tls13_handshake_fault/CMakeLists.txt @@ -0,0 +1,97 @@ +# Host-only bounded fault-injection test for the TLS 1.3 handshake send +# builders (N-107 server / N-109 client). +# +# tests/x509 answers "does the verifier reject what it must and accept what it +# must". This answers the handshake half of the same question for the builder +# failure paths: when _nx_secure_tls_send_certificate/_certificate_verify/ +# _finished/_certificate_request fails, the driver must (a) return that status +# unchanged, (b) release the packet it allocated exactly once, and (c) not send +# a record for the failed builder. It also asserts that a record-send failure +# is NOT double-freed (the record layer already owns that release). +# +# Unlike tests/x509, this does NOT glob the nx_secure/nx_crypto trees into +# archives. It compiles ONLY the two drivers under test +# (_nx_secure_tls_1_3_client_handshake, _nx_secure_tls_1_3_server_handshake) +# and links them against a hand-written stub surface for every other symbol +# they reference -- no NX_PACKET pools, no record layer, no ThreadX scheduler, +# no broad netstack/guest. That is the point: each builder failure can be +# injected and its release contract observed directly. +# +# Base vs fix: on the fixed drivers (bda9d37d) all 9 checks pass. On the +# unfixed drivers (0fb76b63) the three client builders leak the packet (one +# check each) and the four server builders drop the status, skip the release +# and still send the record (three checks each) -- 15 failures. With the +# shipping flags above, probing the unfixed pin hangs instead: the unfixed +# server send_certificate_request guards its builder with +# NX_ASSERT(status == NX_SUCCESS), and NX_ASSERT is gated on NX_DISABLE_ASSERT, +# not NX_DISABLE_ERROR_CHECKING, so the assert is live and spins forever -- +# the DoS the fix removes. Run the unfixed pin under a timeout, or with +# -DNX_DISABLE_ASSERT for the deterministic 15-failure count. +# +# HOST ONLY: the stubs are native host C; nothing here runs on m68k. + +if(CMAKE_CROSSCOMPILING) + return() +endif() + +set(_hs_src "${AMINETXDUO_NETXDUO}/nx_secure/src") + +add_library(test_tls13_hs_stubs STATIC + nx_secure_tls_1_3_handshake_fault_stubs.c) +add_library(test_tls13_hs_stubs_shipping STATIC + nx_secure_tls_1_3_handshake_fault_stubs.c) + +# Identical define/port surface to src/tls/CMakeLists.txt + tests/x509, so the +# shipping arm omits the optional server client-certificate feature. The +# separate optional arm enables it to reach send_certificate_request; compile +# its stubs and drivers together so the session layouts cannot be mixed. +foreach(lib test_tls13_hs_stubs test_tls13_hs_stubs_shipping) + target_include_directories(${lib} PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_SOURCE_DIR}/tests/perf/host/shim" # tx_port.h, first + "${AMINETXDUO_NETXDUO}/ports/linux/gnu/inc" # nx_port.h + "${CMAKE_SOURCE_DIR}/port/netxduo-amiga/inc" # nx_user.h, ours + "${AMINETXDUO_NETXDUO}/common/inc" + "${AMINETXDUO_THREADX}/common/inc" + "${AMINETXDUO_NETXDUO}/nx_secure/inc" + "${AMINETXDUO_NETXDUO}/nx_secure/ports" + "${AMINETXDUO_NETXDUO}/crypto_libraries/inc") + + target_compile_definitions(${lib} PUBLIC + NX_PACKET_ALIGNMENT=8 + NX_INCLUDE_USER_DEFINE_FILE + AMINETXDUO_HOST_VENDORED_TX_PORT="${AMINETXDUO_THREADX}/ports/linux/gnu/inc/tx_port.h" + NX_DISABLE_ERROR_CHECKING + TX_DISABLE_ERROR_CHECKING + NX_SECURE_KEY_CLEAR + NX_SECURE_ENABLE_ECC_CIPHERSUITE + NX_SECURE_ENABLE_AEAD_CIPHER + NX_SECURE_TLS_ENABLE_TLS_1_3) +endforeach() +target_compile_definitions(test_tls13_hs_stubs PUBLIC + NX_SECURE_ENABLE_CLIENT_CERTIFICATE_VERIFY) + +# The two drivers under test are compiled as PART of the executable so their +# failure paths are exercised with the exact same flags as the stubs. +add_executable(test_tls13_handshake_fault + nx_secure_tls_1_3_handshake_fault_test.c + "${_hs_src}/nx_secure_tls_1_3_client_handshake.c" + "${_hs_src}/nx_secure_tls_1_3_server_handshake.c") + +target_link_libraries(test_tls13_handshake_fault PRIVATE test_tls13_hs_stubs) + +target_compile_options(test_tls13_handshake_fault PRIVATE -Wall -Wextra -UAMINETXDUO_IPV6) + +include(CTest) +add_test(NAME tls13_handshake_fault COMMAND test_tls13_handshake_fault) + +add_executable(test_tls13_handshake_fault_shipping + nx_secure_tls_1_3_handshake_fault_test.c + "${_hs_src}/nx_secure_tls_1_3_client_handshake.c" + "${_hs_src}/nx_secure_tls_1_3_server_handshake.c") +target_link_libraries(test_tls13_handshake_fault_shipping PRIVATE + test_tls13_hs_stubs_shipping) +target_compile_options(test_tls13_handshake_fault_shipping PRIVATE + -Wall -Wextra -UAMINETXDUO_IPV6) +add_test(NAME tls13_handshake_fault_shipping + COMMAND test_tls13_handshake_fault_shipping) diff --git a/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_stubs.c b/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_stubs.c new file mode 100644 index 00000000..f7d795de --- /dev/null +++ b/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_stubs.c @@ -0,0 +1,283 @@ +/* + * N-107 / N-109 bounded fault-injection fixture: stub surface. + * + * Defines every symbol the two handshake drivers reference but that lives + * elsewhere in nx_secure/ThreadX. Builders (send_*), the handshake-record + * sender, packet allocation, transcript/key hashing, and the message + * processors are all controllable so each failure path can be injected. + * + * Release semantics modelled here match the real code: + * - The three client builders + four server builders own NO packet_release + * internally; the CALLER (handshake driver) owns the packet until it is + * handed to _nx_secure_tls_send_handshake_record. + * - _nx_secure_tls_send_handshake_record owns the release on record-send + * failure (real impl: nx_secure_tls_send_handshake_record.c). The stub + * therefore does NOT touch g_caller_release_count on failure. + * - nx_secure_tls_packet_release is _nx_secure_tls_packet_release under + * NX_SECURE_KEY_CLEAR (the shipping config), so the caller release is the + * single call the driver makes, counted here. + */ + +#include "nx_secure_tls_1_3_handshake_fault_stubs.h" + +/* TLS 1.2 downgrade sentinels (defined in nx_secure_tls_send_serverhello.c). */ +const UCHAR _nx_secure_tls_1_1_random[8] = {0}; +const UCHAR _nx_secure_tls_1_2_random[8] = {0}; + +inject_target_t g_inject = INJECT_NONE; +UCHAR g_record_send_fail_type = 0; +UINT g_caller_release_count = 0; +UINT g_record_send_count = 0; + +/* A single dummy packet handed out by the allocate stub; never dereferenced. */ +static NX_PACKET g_dummy_packet; + +void test_reset(void) +{ + g_inject = INJECT_NONE; + g_record_send_fail_type = 0; + g_caller_release_count = 0; + g_record_send_count = 0; +} + +/* --- packet allocation / release --- */ + +UINT _nx_secure_tls_allocate_handshake_packet(NX_SECURE_TLS_SESSION *tls_session, + NX_PACKET_POOL *packet_pool, + NX_PACKET **send_packet, ULONG wait_option) +{ + (void)tls_session; + (void)packet_pool; + (void)wait_option; + *send_packet = &g_dummy_packet; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_packet_release(NX_PACKET *packet_ptr) +{ + (void)packet_ptr; + g_caller_release_count++; + return NX_SUCCESS; +} + +/* --- record send (owns release on failure, does NOT count a caller release) --- */ + +UINT _nx_secure_tls_send_handshake_record(NX_SECURE_TLS_SESSION *tls_session, + NX_PACKET *send_packet, UCHAR handshake_type, + ULONG wait_option) +{ + (void)tls_session; + (void)send_packet; + (void)wait_option; + g_record_send_count++; + if (g_record_send_fail_type != 0 && handshake_type == g_record_send_fail_type) + { + return INJECTED_STATUS; + } + return NX_SUCCESS; +} + +/* --- key / transcript / hash machinery (succeed unless injected) --- */ + +UINT _nx_secure_tls_1_3_generate_handshake_keys(NX_SECURE_TLS_SESSION *tls_session) +{ + (void)tls_session; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_1_3_generate_session_keys(NX_SECURE_TLS_SESSION *tls_session) +{ + (void)tls_session; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_1_3_session_keys_set(NX_SECURE_TLS_SESSION *tls_session, USHORT key_set) +{ + (void)tls_session; + (void)key_set; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_1_3_transcript_hash_save(NX_SECURE_TLS_SESSION *tls_session, + UINT hash_index, UINT need_copy) +{ + (void)tls_session; + (void)hash_index; + (void)need_copy; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_handshake_hash_init(NX_SECURE_TLS_SESSION *tls_session) +{ + (void)tls_session; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_handshake_hash_update(NX_SECURE_TLS_SESSION *tls_session, + UCHAR *data, UINT length) +{ + (void)tls_session; + (void)data; + (void)length; + return NX_SUCCESS; +} + +/* --- message processors (set the state the driver then acts on) --- */ + +UINT _nx_secure_tls_process_handshake_header(UCHAR *packet_buffer, USHORT *message_type, + UINT *header_size, UINT *message_length) +{ + /* Real TLS handshake header: 1-byte type, 3-byte big-endian length. */ + *message_type = (USHORT)packet_buffer[0]; + *message_length = ((UINT)packet_buffer[1] << 16) | ((UINT)packet_buffer[2] << 8) | + (UINT)packet_buffer[3]; + *header_size = 4; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_process_finished(NX_SECURE_TLS_SESSION *tls_session, + UCHAR *packet_buffer, UINT message_length) +{ + (void)packet_buffer; + (void)message_length; + /* Client: move to the state that sends Cert/CertVerify/Finished. */ + tls_session -> nx_secure_tls_client_state = NX_SECURE_TLS_CLIENT_STATE_HANDSHAKE_FINISHED; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_process_clienthello(NX_SECURE_TLS_SESSION *tls_session, + UCHAR *packet_buffer, UINT message_length) +{ + (void)packet_buffer; + (void)message_length; + /* Negotiate TLS 1.3 so the server proceeds past the version gate. */ + tls_session -> nx_secure_tls_1_3 = NX_TRUE; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_process_certificate_request(NX_SECURE_TLS_SESSION *tls_session, + UCHAR *packet_buffer, UINT message_length) +{ + (void)tls_session; + (void)packet_buffer; + (void)message_length; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_process_certificate_verify(NX_SECURE_TLS_SESSION *tls_session, + UCHAR *packet_buffer, UINT message_length) +{ + (void)tls_session; + (void)packet_buffer; + (void)message_length; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_process_encrypted_extensions(NX_SECURE_TLS_SESSION *tls_session, + UCHAR *packet_buffer, UINT message_length) +{ + (void)tls_session; + (void)packet_buffer; + (void)message_length; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_process_remote_certificate(NX_SECURE_TLS_SESSION *tls_session, + UCHAR *packet_buffer, UINT message_length, + UINT data_length) +{ + (void)tls_session; + (void)packet_buffer; + (void)message_length; + (void)data_length; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_process_serverhello(NX_SECURE_TLS_SESSION *tls_session, + UCHAR *packet_buffer, UINT message_length) +{ + (void)tls_session; + (void)packet_buffer; + (void)message_length; + return NX_SUCCESS; +} + +/* --- builders (client + server). Each returns INJECTED_STATUS when selected. --- */ + +UINT _nx_secure_tls_send_certificate(NX_SECURE_TLS_SESSION *tls_session, NX_PACKET *send_packet, + ULONG wait_option) +{ + (void)tls_session; + (void)send_packet; + (void)wait_option; + if (g_inject == INJECT_CLIENT_SEND_CERTIFICATE || + g_inject == INJECT_SERVER_SEND_CERTIFICATE) + { + return INJECTED_STATUS; + } + return NX_SUCCESS; +} + +UINT _nx_secure_tls_send_certificate_verify(NX_SECURE_TLS_SESSION *tls_session, NX_PACKET *send_packet) +{ + (void)tls_session; + (void)send_packet; + if (g_inject == INJECT_CLIENT_SEND_CERTIFICATE_VERIFY || + g_inject == INJECT_SERVER_SEND_CERTIFICATE_VERIFY) + { + return INJECTED_STATUS; + } + return NX_SUCCESS; +} + +UINT _nx_secure_tls_send_finished(NX_SECURE_TLS_SESSION *tls_session, NX_PACKET *send_packet) +{ + (void)tls_session; + (void)send_packet; + if (g_inject == INJECT_CLIENT_SEND_FINISHED || + g_inject == INJECT_SERVER_SEND_FINISHED) + { + return INJECTED_STATUS; + } + return NX_SUCCESS; +} + +UINT _nx_secure_tls_send_certificate_request(NX_SECURE_TLS_SESSION *tls_session, NX_PACKET *send_packet) +{ + (void)tls_session; + (void)send_packet; + if (g_inject == INJECT_SERVER_SEND_CERTIFICATE_REQUEST) + { + return INJECTED_STATUS; + } + return NX_SUCCESS; +} + +UINT _nx_secure_tls_send_serverhello(NX_SECURE_TLS_SESSION *tls_session, NX_PACKET *send_packet) +{ + (void)tls_session; + (void)send_packet; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_send_encrypted_extensions(NX_SECURE_TLS_SESSION *tls_session, NX_PACKET *send_packet) +{ + (void)tls_session; + (void)send_packet; + return NX_SUCCESS; +} + +UINT _nx_secure_tls_send_clienthello(NX_SECURE_TLS_SESSION *tls_session, NX_PACKET *send_packet) +{ + (void)tls_session; + (void)send_packet; + return NX_SUCCESS; +} + +/* --- ThreadX (only _tx_thread_sleep is referenced by the drivers) --- */ + +UINT _tx_thread_sleep(ULONG timer_ticks) +{ + (void)timer_ticks; + return NX_SUCCESS; +} diff --git a/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_stubs.h b/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_stubs.h new file mode 100644 index 00000000..82acc58f --- /dev/null +++ b/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_stubs.h @@ -0,0 +1,43 @@ +/* + * N-107 / N-109 bounded fault-injection fixture: shared stub controls. + * + * This test links ONLY the two handshake drivers under test + * (_nx_secure_tls_1_3_client_handshake, _nx_secure_tls_1_3_server_handshake) + * against minimal stubs for every other symbol they reference. It is NOT a + * broad netstack/guest harness: no ThreadX scheduler, no NX_PACKET pools, no + * record layer. Every send/builder/allocate/hash function is a controllable + * stub so each TLS 1.3 builder-failure and record-send-failure path can be + * driven to completion and its release contract asserted directly. + */ +#ifndef N107_HANDSHAKE_FAULT_STUBS_H +#define N107_HANDSHAKE_FAULT_STUBS_H + +#include "nx_api.h" +#include "nx_secure_tls.h" + +/* Distinct status a stubbed builder / record-send returns when told to fail. + * The handshake driver must return this value unchanged. */ +#define INJECTED_STATUS (0x1234U) + +/* Which builder to fail, or INJECT_NONE. */ +typedef enum +{ + INJECT_NONE = 0, + INJECT_CLIENT_SEND_CERTIFICATE, + INJECT_CLIENT_SEND_CERTIFICATE_VERIFY, + INJECT_CLIENT_SEND_FINISHED, + INJECT_SERVER_SEND_CERTIFICATE_REQUEST, + INJECT_SERVER_SEND_CERTIFICATE, + INJECT_SERVER_SEND_CERTIFICATE_VERIFY, + INJECT_SERVER_SEND_FINISHED +} inject_target_t; + +/* Shared, resettable injection/count state (defined in stubs.c). */ +extern inject_target_t g_inject; +extern UCHAR g_record_send_fail_type; /* handshake_type that fails; 0 = never fail */ +extern UINT g_caller_release_count; /* invocations of _nx_secure_tls_packet_release */ +extern UINT g_record_send_count; /* invocations of _nx_secure_tls_send_handshake_record */ + +void test_reset(void); + +#endif /* N107_HANDSHAKE_FAULT_STUBS_H */ diff --git a/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_test.c b/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_test.c new file mode 100644 index 00000000..b5b77eae --- /dev/null +++ b/tests/tls13_handshake_fault/nx_secure_tls_1_3_handshake_fault_test.c @@ -0,0 +1,170 @@ +/* + * N-107 / N-109 bounded fault-injection fixture: driver. + * + * Exercises the two handshake drivers in the states that reach the three + * client builders (send_certificate, send_certificate_verify, send_finished) + * and the four server builders (send_certificate_request, send_certificate, + * send_certificate_verify, send_finished). For each builder it asserts the + * bounded benign-fault contract: + * + * 1. the handshake driver returns the ORIGINAL builder status unchanged + * (INJECTED_STATUS), and + * 2. the packet allocated for the failed builder is released EXACTLY ONCE + * by the caller (no leak, no double-free), and + * 3. no record for the failed builder is sent. + * + * It separately injects a record-send failure and asserts the driver does NOT + * release the packet itself (the record layer owns that release), i.e. zero + * caller releases => no double-free. + */ + +#include +#include +#include "nx_secure_tls_1_3_handshake_fault_stubs.h" + +static int g_failures = 0; + +#define CHECK(cond, name) \ + do \ + { \ + if (!(cond)) \ + { \ + printf(" FAIL: %s\n", name); \ + g_failures++; \ + } \ + } while (0) + +static void make_header(UCHAR *buf, UCHAR message_type, UINT message_length) +{ + buf[0] = message_type; + buf[1] = (UCHAR)((message_length >> 16) & 0xFF); + buf[2] = (UCHAR)((message_length >> 8) & 0xFF); + buf[3] = (UCHAR)(message_length & 0xFF); +} + +/* Drive the client handshake with a Finished message, optional certificate + * request, and the configured injection. Returns the driver's status. */ +static UINT run_client(UINT certificate_requested) +{ + NX_SECURE_TLS_SESSION session; + UCHAR buf[4]; + UINT ret; + + memset(&session, 0, sizeof(session)); + session.nx_secure_tls_1_3 = NX_TRUE; + session.nx_secure_tls_client_certificate_requested = certificate_requested; + + make_header(buf, NX_SECURE_TLS_FINISHED, 0); + ret = _nx_secure_tls_1_3_client_handshake(&session, buf, sizeof(buf), 0); + return ret; +} + +/* Drive the server handshake with a ClientHello, optional client-certificate + * verification, and the configured injection. Returns the driver's status. */ +static UINT run_server(UINT verify_client_certificate) +{ + NX_SECURE_TLS_SESSION session; + /* The SEND_HELLO state NX_ASSERTs that a ciphersuite was chosen by + * ClientHello processing. A real process_clienthello selects one; our + * stub does not, so supply a dummy non-NULL pointer to satisfy the + * (active) assert and reach the builders under test. */ + static const NX_SECURE_TLS_CIPHERSUITE_INFO dummy_ciphersuite; + UCHAR buf[4]; + UINT ret; + + memset(&session, 0, sizeof(session)); + session.nx_secure_tls_1_3 = NX_TRUE; + session.nx_secure_tls_verify_client_certificate = verify_client_certificate; + session.nx_secure_tls_session_ciphersuite = &dummy_ciphersuite; + + make_header(buf, NX_SECURE_TLS_CLIENT_HELLO, 0); + ret = _nx_secure_tls_1_3_server_handshake(&session, buf, sizeof(buf), 0); + return ret; +} + +/* Assert the common builder-failure contract and report a named case. */ +static void expect_builder_failure(const char *name, UINT ret, + UINT expected_record_sends) +{ + CHECK(ret == INJECTED_STATUS, name); + CHECK(g_caller_release_count == 1, name); + CHECK(g_record_send_count == expected_record_sends, name); +} + +/* Assert the record-send-failure contract (record layer owns release). */ +static void expect_record_send_failure(const char *name, UINT ret, + UINT expected_record_sends) +{ + CHECK(ret == INJECTED_STATUS, name); + CHECK(g_caller_release_count == 0, name); /* no caller release => no double-free */ + CHECK(g_record_send_count == expected_record_sends, name); +} + +int main(void) +{ + UINT ret; + + + /* --- Client builder failures (N-109) --- */ + + test_reset(); + g_inject = INJECT_CLIENT_SEND_CERTIFICATE; + ret = run_client(1); /* cert requested -> send_certificate runs */ + expect_builder_failure("client send_certificate fails", ret, 0); + + test_reset(); + g_inject = INJECT_CLIENT_SEND_CERTIFICATE_VERIFY; + ret = run_client(1); /* cert succeeds, cert_verify fails */ + expect_builder_failure("client send_certificate_verify fails", ret, 1); + + test_reset(); + g_inject = INJECT_CLIENT_SEND_FINISHED; + ret = run_client(0); /* no cert requested -> straight to finished */ + expect_builder_failure("client send_finished fails", ret, 0); + + /* --- Client record-send failure (record layer owns release) --- */ + + test_reset(); + g_record_send_fail_type = NX_SECURE_TLS_FINISHED; + ret = run_client(0); + expect_record_send_failure("client record-send(finished) fails", ret, 1); + + /* --- Server builder failures (N-107) --- */ + +#ifdef NX_SECURE_ENABLE_CLIENT_CERTIFICATE_VERIFY + test_reset(); + g_inject = INJECT_SERVER_SEND_CERTIFICATE_REQUEST; + ret = run_server(1); /* verify_client_certificate -> cert_request runs */ + expect_builder_failure("server send_certificate_request fails", ret, 2); +#endif + + test_reset(); + g_inject = INJECT_SERVER_SEND_CERTIFICATE; + ret = run_server(0); /* no verify -> cert runs */ + expect_builder_failure("server send_certificate fails", ret, 2); + + test_reset(); + g_inject = INJECT_SERVER_SEND_CERTIFICATE_VERIFY; + ret = run_server(0); + expect_builder_failure("server send_certificate_verify fails", ret, 3); + + test_reset(); + g_inject = INJECT_SERVER_SEND_FINISHED; + ret = run_server(0); + expect_builder_failure("server send_finished fails", ret, 4); + + /* --- Server record-send failure (record layer owns release) --- */ + + test_reset(); + g_record_send_fail_type = NX_SECURE_TLS_CERTIFICATE_MSG; + ret = run_server(0); + expect_record_send_failure("server record-send(certificate) fails", ret, 3); + + if (g_failures == 0) + { + printf("PASS: all N-107/N-109 fault-injection checks\n"); + return 0; + } + printf("FAILED: %d check(s)\n", g_failures); + return 1; +} diff --git a/tests/x509/test_tls_x509.c b/tests/x509/test_tls_x509.c index c05e4b40..3894d2c8 100644 --- a/tests/x509/test_tls_x509.c +++ b/tests/x509/test_tls_x509.c @@ -1207,6 +1207,140 @@ static void test_tls13_key_schedule(void) "guard: later transcript hashes intact"); } +/* N-108: _nx_secure_tls_1_3_transcript_hash_save writes a hash_size digest + into nx_secure_tls_transcript_hashes[hash_index]. An index equal to + NX_SECURE_TLS_1_3_MAX_TRANSCRIPT_HASHES, or a ciphersuite hash longer than + NX_SECURE_TLS_MAX_HASH_SIZE, has to be refused before anything is written. + The rows and the start of nx_secure_tls_handshake_cache, which follows + them, are guard-filled. */ +#define N108_ROW_GUARD 0xA5 +#define N108_CACHE_GUARD 0x5A +#define N108_CACHE_BYTES 64u + +/* FIPS 180-2 B.1: SHA-256("abc"). */ +static const UCHAR n108_sha256_abc[32] = { + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, + 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}; + +static void n108_fill(NX_SECURE_TLS_KEY_MATERIAL *km) +{ + memset(km->nx_secure_tls_transcript_hashes, N108_ROW_GUARD, sizeof(km->nx_secure_tls_transcript_hashes)); + memset(km->nx_secure_tls_handshake_cache, N108_CACHE_GUARD, N108_CACHE_BYTES); +} + +static int n108_rows_intact(NX_SECURE_TLS_KEY_MATERIAL *km, int skip_row) +{ + int row; + + for (row = 0; row < NX_SECURE_TLS_1_3_MAX_TRANSCRIPT_HASHES; row++) + { + if (row != skip_row && !all_guard(km->nx_secure_tls_transcript_hashes[row], NX_SECURE_TLS_MAX_HASH_SIZE)) + { + return 0; + } + } + return 1; +} + +static int n108_cache_intact(NX_SECURE_TLS_KEY_MATERIAL *km) +{ + unsigned i; + + for (i = 0; i < N108_CACHE_BYTES; i++) + { + if (km->nx_secure_tls_handshake_cache[i] != N108_CACHE_GUARD) + { + return 0; + } + } + return 1; +} + +/* Its own session: _nx_secure_tls_session_create links the control block into + the created list, so creating tls13_session a second time would corrupt it. */ +static NX_SECURE_TLS_SESSION n108_session; +static UCHAR n108_metadata[32768]; + +static void test_n108_transcript_hash_save(void) +{ + NX_SECURE_TLS_KEY_MATERIAL *km = &n108_session.nx_secure_tls_key_material; + NX_SECURE_TLS_CRYPTO *table; + NX_SECURE_TLS_CIPHERSUITE_INFO *sha256_suite = NX_NULL; + NX_SECURE_TLS_CIPHERSUITE_INFO long_hash_suite; + UCHAR message[3] = {'a', 'b', 'c'}; + UINT status; + USHORT i; + + printf("n108: tls 1.3 transcript hash save\n"); + + status = _nx_secure_tls_session_create(&n108_session, &nx_crypto_tls_ciphers_ecc, + n108_metadata, sizeof(n108_metadata)); + check(status == NX_SUCCESS, "n108: session create"); + if (status != NX_SUCCESS) + { + return; + } + + table = n108_session.nx_secure_tls_crypto_table; + for (i = 0; i < table->nx_secure_tls_ciphersuite_lookup_table_size; i++) + { + if (table->nx_secure_tls_ciphersuite_lookup_table[i].nx_secure_tls_ciphersuite == TLS_AES_128_GCM_SHA256) + { + sha256_suite = &table->nx_secure_tls_ciphersuite_lookup_table[i]; + break; + } + } + check(sha256_suite != NX_NULL, "n108: TLS_AES_128_GCM_SHA256 in table"); + if (sha256_suite == NX_NULL) + { + return; + } + + n108_session.nx_secure_tls_1_3 = 1; + n108_session.nx_secure_tls_session_ciphersuite = sha256_suite; + status = _nx_secure_tls_handshake_hash_init(&n108_session); + if (status == NX_SUCCESS) + { + status = _nx_secure_tls_handshake_hash_update(&n108_session, message, sizeof(message)); + } + check(status == NX_SUCCESS, "n108: handshake hash over \"abc\""); + if (status != NX_SUCCESS) + { + return; + } + + /* Control: the last row, SHA-256, exactly 32 bytes. */ + n108_fill(km); + status = _nx_secure_tls_1_3_transcript_hash_save(&n108_session, NX_SECURE_TLS_TRANSCRIPT_IDX_SERVER_FINISHED, NX_TRUE); + check(status == NX_SUCCESS, "n108: index 4 SHA-256 saves"); + check(memcmp(km->nx_secure_tls_transcript_hashes[NX_SECURE_TLS_TRANSCRIPT_IDX_SERVER_FINISHED], + n108_sha256_abc, sizeof(n108_sha256_abc)) == 0, + "n108: index 4 holds SHA-256(\"abc\")"); + check(n108_rows_intact(km, NX_SECURE_TLS_TRANSCRIPT_IDX_SERVER_FINISHED), + "n108: index 4 leaves rows 0-3 intact"); + check(n108_cache_intact(km), "n108: index 4 leaves the cache intact"); + + /* One past the last row. */ + n108_fill(km); + status = _nx_secure_tls_1_3_transcript_hash_save(&n108_session, NX_SECURE_TLS_1_3_MAX_TRANSCRIPT_HASHES, NX_TRUE); + check(status == NX_INVALID_PARAMETERS, "n108: index 5 refused"); + check(n108_rows_intact(km, -1), "n108: index 5 leaves all rows intact"); + check(n108_cache_intact(km), "n108: index 5 leaves the cache intact"); + + /* A 48-byte digest into a 32-byte row. */ + long_hash_suite = *sha256_suite; + long_hash_suite.nx_secure_tls_hash = &crypto_method_sha384; + n108_session.nx_secure_tls_session_ciphersuite = &long_hash_suite; + n108_fill(km); + status = _nx_secure_tls_1_3_transcript_hash_save(&n108_session, NX_SECURE_TLS_TRANSCRIPT_IDX_SERVER_FINISHED, NX_TRUE); + check(status == NX_INVALID_PARAMETERS, "n108: SHA-384 refused"); + check(n108_rows_intact(km, -1), "n108: SHA-384 leaves all rows intact"); + check(n108_cache_intact(km), "n108: SHA-384 leaves the cache intact"); + + n108_session.nx_secure_tls_session_ciphersuite = sha256_suite; +} + int main(void) { _nx_crypto_initialize(); @@ -1222,6 +1356,7 @@ int main(void) test_tls_key_usage(); test_aes128_block(); test_tls13_key_schedule(); + test_n108_transcript_hash_save(); if (failures != 0) { diff --git a/third_party/netxduo b/third_party/netxduo index 76c67564..2f9be697 160000 --- a/third_party/netxduo +++ b/third_party/netxduo @@ -1 +1 @@ -Subproject commit 76c67564f6bd399b06a484d6dacc63f762989319 +Subproject commit 2f9be697ee19133b34df72c1ff5556a7ec55c957 diff --git a/tools/ci.sh b/tools/ci.sh index fa644162..a58fbabf 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -451,7 +451,13 @@ host_test_targets() { # builddir # parser against the same guard page (all hosts). # 544 with tls_clienthello_lists (N-115): the server's ClientHello # group and signature-algorithm lists against a guard page (all hosts). -HOST_TESTS_EXPECTED=544 +# 545 with tls13_handshake_fault (N-107/N-109): the TLS 1.3 handshake +# send-builders' benign-fault contract -- the failed builder's status is +# returned unchanged, the packet is released exactly once by the caller, +# no record is sent, and a record-send failure is not double-freed. +# 546 with tls13_handshake_fault_shipping: the same contract without +# the optional server client-certificate feature, as in shipped images. +HOST_TESTS_EXPECTED=546 case "$(uname -m)" in x86_64|amd64) ;; # test_inet, test_route, test_expunge, test_expunge_cork, test_select,