From eef7bf08dd6e057b229afaca55712475770ad427 Mon Sep 17 00:00:00 2001 From: Carlos San Vicente Date: Fri, 17 Oct 2025 15:53:58 +0200 Subject: [PATCH 1/6] Add SOCKS5 proxy support --- CMakeLists.txt | 6 + include/aws/io/io.h | 14 + include/aws/io/logging.h | 1 + include/aws/io/socks5.h | 353 ++ include/aws/io/socks5_channel_handler.h | 169 + source/io.c | 41 + source/socks5.c | 1125 +++++++ source/socks5_channel_handler.c | 3308 +++++++++++++++++++ source/windows/secure_channel_tls_handler.c | 2051 ++++++++++++ tests/CMakeLists.txt | 8 + tests/socks5_test.c | 1193 +++++++ 11 files changed, 8269 insertions(+) create mode 100644 include/aws/io/socks5.h create mode 100644 include/aws/io/socks5_channel_handler.h create mode 100644 source/socks5.c create mode 100644 source/socks5_channel_handler.c create mode 100644 tests/socks5_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 191d1b6ed..4736d9e0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,10 +161,16 @@ file(GLOB IO_HEADERS ${AWS_IO_PRIV_HEADERS} ) +file(GLOB AWS_IO_SOCKS5_SRC + "source/socks5.c" + "source/socks5_channel_handler.c" + ) + file(GLOB IO_SRC ${AWS_IO_SRC} ${AWS_IO_OS_SRC} ${AWS_IO_TLS_SRC} + ${AWS_IO_SOCKS5_SRC} ) add_library(${PROJECT_NAME} ${LIBTYPE} ${IO_HEADERS} ${IO_SRC}) diff --git a/include/aws/io/io.h b/include/aws/io/io.h index 9d958939b..681c69a6b 100644 --- a/include/aws/io/io.h +++ b/include/aws/io/io.h @@ -257,6 +257,20 @@ enum aws_io_errors { AWS_IO_TLS_ERROR_READ_FAILURE, AWS_ERROR_PEM_MALFORMED, + + AWS_IO_SOCKS5_PROXY_ERROR_INIT, + AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_AUTH_METHOD, + AWS_IO_SOCKS5_PROXY_ERROR_AUTH_FAILED, + AWS_IO_SOCKS5_PROXY_ERROR_CONNECTION_FAILED, + AWS_IO_SOCKS5_PROXY_ERROR_REQUEST_FAILED, + AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE, + AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_ADDRESS_TYPE, + AWS_IO_SOCKS5_PROXY_ERROR_BAD_ADDRESS, + AWS_IO_SOCKS5_PROXY_ERROR_BAD_HANDSHAKE, + AWS_IO_SOCKS5_PROXY_ERROR_REJECTED, + AWS_IO_SOCKS5_PROXY_ERROR_GENERAL_FAILURE, + AWS_IO_SOCKS5_PROXY_ERROR_TTL_EXPIRED, + AWS_IO_SOCKS5_PROXY_ERROR_COMMAND_NOT_SUPPORTED, AWS_IO_SOCKET_MISSING_EVENT_LOOP, AWS_IO_TLS_UNKNOWN_ROOT_CERTIFICATE, diff --git a/include/aws/io/logging.h b/include/aws/io/logging.h index a3bbee2fa..665627af4 100644 --- a/include/aws/io/logging.h +++ b/include/aws/io/logging.h @@ -33,6 +33,7 @@ enum aws_io_log_subject { AWS_LS_IO_STANDARD_RETRY_STRATEGY, AWS_LS_IO_PKCS11, AWS_LS_IO_PEM, + AWS_LS_IO_SOCKS5, AWS_IO_LS_LAST = AWS_LOG_SUBJECT_END_RANGE(AWS_C_IO_PACKAGE_ID) }; AWS_POP_SANE_WARNING_LEVEL diff --git a/include/aws/io/socks5.h b/include/aws/io/socks5.h new file mode 100644 index 000000000..f895de021 --- /dev/null +++ b/include/aws/io/socks5.h @@ -0,0 +1,353 @@ +#ifndef AWS_IO_SOCKS5_H +#define AWS_IO_SOCKS5_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +/** + * SOCKS5 Protocol implementation for AWS CRT. + * + * This module provides functionality for connecting to TCP servers through a SOCKS5 proxy. + * It implements the client-side of the SOCKS5 protocol as defined in: + * + * - RFC 1928: "SOCKS Protocol Version 5" + * - RFC 1929: "Username/Password Authentication for SOCKS V5" + * + * The implementation supports: + * - No authentication and username/password authentication methods + * - IPv4, IPv6, and domain name address types + * - The CONNECT command + * + * Usage flow: + * 1. Initialize proxy options with aws_socks5_proxy_options_init() + * 2. Initialize SOCKS5 context with aws_socks5_context_init() + * 3. Perform the protocol handshake sequence: + * - Write greeting → Read greeting response + * - Write auth request → Read auth response (if required) + * - Write connect request → Read connect response + * 4. After success, the connection can be used normally for application protocols + */ + +/* SOCKS5 Protocol Constants */ +#define AWS_SOCKS5_VERSION 0x05 +#define AWS_SOCKS5_RESERVED 0x00 +#define AWS_SOCKS5_AUTH_VERSION 0x01 + +/* SOCKS5 Message Sizes */ +#define AWS_SOCKS5_GREETING_MIN_SIZE 3 +#define AWS_SOCKS5_GREETING_RESP_SIZE 2 +#define AWS_SOCKS5_AUTH_REQ_MIN_SIZE 5 /* Version(1) + ULen(1) + UName(1+) + PLen(1) + Pass(1+) */ +#define AWS_SOCKS5_AUTH_RESP_SIZE 2 +#define AWS_SOCKS5_CONN_REQ_MIN_SIZE 6 /* Version(1) + CMD(1) + RSV(1) + ATYP(1) + ADDR(1+) + PORT(2) */ +#define AWS_SOCKS5_CONN_RESP_MIN_SIZE 6 /* Version(1) + Status(1) + RSV(1) + ATYP(1) + ADDR(1+) + PORT(2) */ + +/* SOCKS5 Address Type */ +enum aws_socks5_address_type { + AWS_SOCKS5_ATYP_IPV4 = 0x01, + AWS_SOCKS5_ATYP_DOMAIN = 0x03, + AWS_SOCKS5_ATYP_IPV6 = 0x04, +}; + +/* SOCKS5 Authentication Methods */ +enum aws_socks5_auth_method { + AWS_SOCKS5_AUTH_NONE = 0x00, + AWS_SOCKS5_AUTH_GSSAPI = 0x01, + AWS_SOCKS5_AUTH_USERNAME_PASSWORD = 0x02, + AWS_SOCKS5_AUTH_NO_ACCEPTABLE = 0xFF, +}; + +/* SOCKS5 Commands */ +enum aws_socks5_command { + AWS_SOCKS5_COMMAND_CONNECT = 0x01, + AWS_SOCKS5_COMMAND_BIND = 0x02, + AWS_SOCKS5_COMMAND_UDP_ASSOCIATE = 0x03, +}; + +/* SOCKS5 Reply Status Codes */ +enum aws_socks5_response_status { + AWS_SOCKS5_STATUS_SUCCESS = 0x00, + AWS_SOCKS5_STATUS_GENERAL_FAILURE = 0x01, + AWS_SOCKS5_STATUS_CONNECTION_NOT_ALLOWED = 0x02, + AWS_SOCKS5_STATUS_NETWORK_UNREACHABLE = 0x03, + AWS_SOCKS5_STATUS_HOST_UNREACHABLE = 0x04, + AWS_SOCKS5_STATUS_CONNECTION_REFUSED = 0x05, + AWS_SOCKS5_STATUS_TTL_EXPIRED = 0x06, + AWS_SOCKS5_STATUS_COMMAND_NOT_SUPPORTED = 0x07, + AWS_SOCKS5_STATUS_ADDRESS_TYPE_NOT_SUPPORTED = 0x08, +}; + +/* SOCKS5 Protocol State */ +enum aws_socks5_state { + AWS_SOCKS5_STATE_INIT, + AWS_SOCKS5_STATE_GREETING_SENT, + AWS_SOCKS5_STATE_GREETING_RECEIVED, + AWS_SOCKS5_STATE_AUTH_STARTED, + AWS_SOCKS5_STATE_AUTH_COMPLETED, + AWS_SOCKS5_STATE_REQUEST_SENT, + AWS_SOCKS5_STATE_RESPONSE_RECEIVED, + AWS_SOCKS5_STATE_CONNECTED, + AWS_SOCKS5_STATE_ERROR, +}; + +/* SOCKS5 Proxy Options */ +enum aws_socks5_host_resolution_mode { + AWS_SOCKS5_HOST_RESOLUTION_PROXY = 0, + AWS_SOCKS5_HOST_RESOLUTION_CLIENT = 1, +}; + +struct aws_socks5_proxy_options { + /* Proxy server host and port */ + struct aws_string *host; + uint16_t port; + + /* Authentication credentials (optional) */ + struct aws_string *username; + struct aws_string *password; + + /* Configuration options */ + uint32_t connection_timeout_ms; + enum aws_socks5_host_resolution_mode host_resolution_mode; +}; + + +/* SOCKS5 Context - internal state for protocol handling */ +struct aws_socks5_context { + struct aws_allocator *allocator; + enum aws_socks5_state state; + struct aws_array_list auth_methods; /* List of enum aws_socks5_auth_method */ + enum aws_socks5_auth_method selected_auth; + + /* Connection information */ + struct aws_socks5_proxy_options options; + struct aws_string *endpoint_host; + uint16_t endpoint_port; + enum aws_socks5_address_type endpoint_address_type; + + /* Buffer management */ + struct aws_byte_buf send_buf; + struct aws_byte_buf recv_buf; +}; + +AWS_EXTERN_C_BEGIN + +/** + * Initialize SOCKS5 proxy options with defaults. + * + * @param options The options structure to initialize + * @param allocator The allocator to use for internal memory allocation + * @param host The proxy server hostname or IP address as a byte cursor + * @param port The proxy server port + * + * @return AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise with error code set + */ +AWS_IO_API int aws_socks5_proxy_options_init( + struct aws_socks5_proxy_options *options, + struct aws_allocator *allocator, + struct aws_byte_cursor host, + uint16_t port); + +/** + * Initialize SOCKS5 proxy options with default values. + * + * @param options The options structure to initialize + * + * @return AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise with error code set + */ +AWS_IO_API int aws_socks5_proxy_options_init_default( + struct aws_socks5_proxy_options *options); + +/** + * Deep copy SOCKS5 proxy options from source to destination. + * Destination must be already zero-initialized. + * + * @param dest The destination options structure (must be zero-initialized) + * @param src The source options structure to copy from + * + * @return AWS_OP_SUCCESS on success, AWS_OP_ERR on failure + */ +AWS_IO_API int aws_socks5_proxy_options_copy( + struct aws_socks5_proxy_options *dest, + const struct aws_socks5_proxy_options *src); + +/** + * Clean up SOCKS5 proxy options and free all internally allocated memory. + * + * @param options The SOCKS5 proxy options to clean up + */ +AWS_IO_API void aws_socks5_proxy_options_clean_up(struct aws_socks5_proxy_options *options); + +/** + * Set authentication credentials for SOCKS5 proxy. If set, the SOCKS5 client will + * attempt to authenticate using username/password authentication method. + * + * @param options The SOCKS5 proxy options to update + * @param username The username as a byte cursor + * @param password The password as a byte cursor + * + * @return AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise with error code set + * + * @note Both username and password must have length > 0 and <= 255 bytes as per RFC 1929 + */ +AWS_IO_API int aws_socks5_proxy_options_set_auth( + struct aws_socks5_proxy_options *options, + struct aws_allocator *allocator, + struct aws_byte_cursor username, + struct aws_byte_cursor password); + +/** + * Set the host resolution mode for the SOCKS5 proxy. + * + * @param options The SOCKS5 proxy options to update + * @param mode The host resolution mode to set + */ +AWS_IO_API void aws_socks5_proxy_options_set_host_resolution_mode( + struct aws_socks5_proxy_options *options, + enum aws_socks5_host_resolution_mode mode); + +/** + * Get the host resolution mode for the SOCKS5 proxy. + * + * @param options The SOCKS5 proxy options to query + * @return The host resolution mode + */ +AWS_IO_API enum aws_socks5_host_resolution_mode aws_socks5_proxy_options_get_host_resolution_mode( + const struct aws_socks5_proxy_options *options); + +/** + * Helper to infer the appropriate SOCKS5 address type for a given host string. If the host is an IPv4 or IPv6 literal, + * the corresponding address type will be returned even if AWS_SOCKS5_ATYP_DOMAIN was requested. + */ +AWS_IO_API enum aws_socks5_address_type aws_socks5_infer_address_type( + struct aws_byte_cursor host, + enum aws_socks5_address_type requested_type); + +/** + * Initialize a SOCKS5 protocol context for establishing a connection through a SOCKS5 proxy. + * The context manages the state and buffers needed for the SOCKS5 protocol handshake. + * + * @param context The context structure to initialize + * @param allocator The allocator to use for internal memory allocation + * @param options Configuration options for the SOCKS5 proxy connection + * @param target_host Destination host the proxy should reach (byte cursor) + * @param target_port Destination port the proxy should reach + * @param address_type Address type hint (DOMAIN will trigger automatic IPv4/IPv6 detection) + * + * @return AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise with error code set + */ +AWS_IO_API int aws_socks5_context_init( + struct aws_socks5_context *context, + struct aws_allocator *allocator, + const struct aws_socks5_proxy_options *options, + struct aws_byte_cursor target_host, + uint16_t target_port, + enum aws_socks5_address_type address_type); + +/** + * Clean up a SOCKS5 context and free all internally allocated memory. + * + * @param context The SOCKS5 context to clean up + */ +AWS_IO_API void aws_socks5_context_clean_up(struct aws_socks5_context *context); + +/** + * Format the initial SOCKS5 greeting message into the provided buffer. + * This message contains the list of supported authentication methods and + * is the first message sent to a SOCKS5 proxy server. + * + * @param context The SOCKS5 context containing connection information + * @param buffer The buffer to write the greeting message into (will be resized if needed) + * + * @return AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise with error code set + */ +AWS_IO_API int aws_socks5_write_greeting( + struct aws_socks5_context *context, + struct aws_byte_buf *buffer); + +/** + * Process the SOCKS5 greeting response from the server. + * The server selects an authentication method or rejects the connection. + * + * @param context The SOCKS5 context to update with server's selected auth method + * @param data The received data from the server to process + * + * @return AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise with error code set + * + * @note Updates context->state to AWS_SOCKS5_STATE_GREETING_RECEIVED on success + * @note Updates context->selected_auth with the server's chosen authentication method + */ +AWS_IO_API int aws_socks5_read_greeting_response( + struct aws_socks5_context *context, + struct aws_byte_cursor *data); + +/** + * Format the username/password authentication request into the provided buffer. + * This message is sent after the server has selected username/password authentication. + * + * @param context The SOCKS5 context containing authentication credentials + * @param buffer The buffer to write the authentication request into + * + * @return AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise with error code set + * + * @note If the selected authentication method is AWS_SOCKS5_AUTH_NONE, this function + * will update the context state without writing to the buffer + */ +AWS_IO_API int aws_socks5_write_auth_request( + struct aws_socks5_context *context, + struct aws_byte_buf *buffer); + +/** + * Process the SOCKS5 authentication response from the server. + * This verifies if authentication was successful. + * + * @param context The SOCKS5 context to update based on authentication result + * @param data The received data from the server to process + * + * @return AWS_OP_SUCCESS if authentication succeeded, AWS_OP_ERR otherwise with error code set + * + * @note Updates context->state to AWS_SOCKS5_STATE_AUTH_COMPLETED on success + * @note If the selected authentication method is AWS_SOCKS5_AUTH_NONE, this function + * will update the context state without processing the data + */ +AWS_IO_API int aws_socks5_read_auth_response( + struct aws_socks5_context *context, + struct aws_byte_cursor *data); + +/** + * Format the SOCKS5 connection request into the provided buffer. + * This message requests the proxy to establish a connection to the target host. + * + * @param context The SOCKS5 context containing target host information + * @param buffer The buffer to write the connect request into + * + * @return AWS_OP_SUCCESS if successful, AWS_OP_ERR otherwise with error code set + * + * @note Updates context->state to AWS_SOCKS5_STATE_REQUEST_SENT on success + * @note Currently only supports the CONNECT command (not BIND or UDP ASSOCIATE) + */ +AWS_IO_API int aws_socks5_write_connect_request( + struct aws_socks5_context *context, + struct aws_byte_buf *buffer); + +/** + * Process the SOCKS5 connection response from the server. + * This verifies if the connection to the target host was successful. + * + * @param context The SOCKS5 context to update based on connection result + * @param data The received data from the server to process + * + * @return AWS_OP_SUCCESS if connection succeeded, AWS_OP_ERR otherwise with error code set + * + * @note Updates context->state to AWS_SOCKS5_STATE_CONNECTED on success + * @note On error, the specific SOCKS5 error code will be mapped to an appropriate AWS error code + */ +AWS_IO_API int aws_socks5_read_connect_response( + struct aws_socks5_context *context, + struct aws_byte_cursor *data); + +AWS_EXTERN_C_END + +#endif /* AWS_IO_SOCKS5_H */ diff --git a/include/aws/io/socks5_channel_handler.h b/include/aws/io/socks5_channel_handler.h new file mode 100644 index 000000000..67f8804a6 --- /dev/null +++ b/include/aws/io/socks5_channel_handler.h @@ -0,0 +1,169 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifndef AWS_IO_SOCKS5_CHANNEL_HANDLER_H +#define AWS_IO_SOCKS5_CHANNEL_HANDLER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SOCKS5 proxy connection states */ +enum aws_socks5_proxy_connection_state { + AWS_SPCS_NONE = 0, + AWS_SPCS_SOCKET_CONNECT, + AWS_SPCS_SOCKS5_NEGOTIATION, + AWS_SPCS_SUCCESS, + AWS_SPCS_FAILURE, +}; + +/** + * Context struct for SOCKS5 proxy connections, used as user_data throughout the SOCKS5 bootstrap/setup chain. + * + * This structure coordinates the connection flow through a SOCKS5 proxy and optional TLS setup, + * maintaining state through the entire connection lifecycle and ensuring proper callback chaining. + * The structure serves as a bridge between the original connection request and the SOCKS5-specific + * connection process, preserving critical context needed for proper callback invocation. + * + * During a SOCKS5 proxy connection: + * 1. This structure is initialized with connection parameters and callbacks + * 2. The bootstrap process installs a SOCKS5 channel handler that handles the proxy protocol + * 3. After successful SOCKS5 handshake, TLS can be established if requested + * 4. Original callbacks are invoked at appropriate points to maintain the expected behavior + */ +struct aws_socks5_bootstrap { + /** Memory allocator used for all allocations related to this bootstrap */ + struct aws_allocator *allocator; + + /** SOCKS5 proxy configuration */ + struct aws_socks5_proxy_options *socks5_proxy_options; + + /** User data to pass to callbacks; preserved from the original connection request */ + void *user_data; + + /** + * Callback function called when the SOCKS5 setup has successfully completed. + */ + aws_client_bootstrap_on_channel_event_fn *on_socks5_setup_completed; + + /** Original callback function to invoke when the channel is successfully established */ + aws_client_bootstrap_on_channel_event_fn *setup_callback; + + /** Original callback function to invoke when the channel is being shut down */ + aws_client_bootstrap_on_channel_event_fn *shutdown_callback; + + /** TLS connection options to apply after SOCKS5 handshake completes (if TLS is requested) */ + struct aws_tls_connection_options *tls_options; + + /** Flag indicating whether to establish TLS after SOCKS5 handshake completes */ + bool use_tls; + + /** Reference to the client bootstrap used for the connection */ + struct aws_client_bootstrap *bootstrap; + + /** Original TLS negotiation callback to invoke after TLS handshake completes */ + aws_tls_on_negotiation_result_fn *original_on_negotiation_result; + + /** User data to pass to the original TLS negotiation callback */ + void *original_tls_user_data; + + /** Destination endpoint resolved from the original connection request */ + struct aws_string *endpoint_host; + struct aws_string *original_endpoint_host; + uint16_t endpoint_port; + enum aws_socks5_address_type endpoint_address_type; + enum aws_socks5_host_resolution_mode host_resolution_mode; + bool endpoint_ready; + bool resolution_in_progress; + int resolution_error_code; + struct aws_channel *pending_channel; + struct aws_channel_task resolution_success_task; + struct aws_channel_task resolution_failure_task; + bool resolution_task_scheduled; + bool resolution_failure_task_scheduled; + /** Set when shutdown is requested while resolution is still running so the bootstrap can be destroyed safely after the callback completes */ + bool cleanup_pending; + struct aws_host_resolution_config host_resolution_config; + bool has_host_resolution_override; + struct aws_mutex lock; +}; + +/** + * System vtable used to decouple production behavior from tests. + * Tests may override these functions to observe or modify bootstrap behavior. + */ +struct aws_socks5_system_vtable { + int (*aws_client_bootstrap_new_socket_channel)(struct aws_socket_channel_bootstrap_options *options); +}; + + +AWS_EXTERN_C_BEGIN + +/** + * Creates a SOCKS5 channel handler that will establish a connection through a SOCKS5 proxy + * to the target host and port specified in the options. + * + * This handler manages the initial SOCKS5 handshake and authentication, and then becomes transparent + * once the connection is established. + * + * For a TLS connection through a SOCKS5 proxy, this handler should be installed before the TLS handler. + */ +AWS_IO_API struct aws_channel_handler *aws_socks5_channel_handler_new( + struct aws_allocator *allocator, + const struct aws_socks5_proxy_options *proxy_options, + struct aws_byte_cursor endpoint_host, + uint16_t endpoint_port, + enum aws_socks5_address_type endpoint_address_type, + aws_channel_on_setup_completed_fn *on_setup_completed, + void *user_data); + +/** + * Creates a new socket channel through a SOCKS5 proxy using the provided bootstrap options. + * This function wraps the standard socket channel creation process to insert a SOCKS5 channel handler + * into the channel's handler chain, enabling connections through the specified SOCKS5 proxy. + * @param options The socket channel bootstrap options, including SOCKS5 proxy configuration + * @return AWS_OP_SUCCESS if the channel creation process was initiated successfully, AWS_OP_ERR otherwise with error code set + */ +AWS_IO_API int aws_socks5_client_bootstrap_new_socket_channel( + struct aws_socket_channel_bootstrap_options *options); + +/** + * Creates a new socket channel through a SOCKS5 proxy using the provided bootstrap options. + * This function is similar to aws_client_bootstrap_new_socket_channel but specifically handles + * the inclusion of SOCKS5 proxy options. + * @param allocator The allocator to use for memory allocations + * @param channel_options The socket channel bootstrap + * options, including SOCKS5 proxy configuration + * @param socks5_proxy_options The SOCKS5 proxy options to use for the connection + * @return AWS_OP_SUCCESS if the channel creation process was initiated successfully, AWS_OP_ERR otherwise + * with error code set + */ +AWS_IO_API int aws_client_bootstrap_new_socket_channel_with_socks5( + struct aws_allocator *allocator, + struct aws_socket_channel_bootstrap_options *channel_options, + const struct aws_socks5_proxy_options *socks5_proxy_options); + +/** + * Starts the SOCKS5 handshake process. Must be called after the handler is added to a slot. + */ +AWS_IO_API int aws_socks5_channel_handler_start_handshake( + struct aws_channel_handler *handler); + +/** + * Overrides the system vtable used by the SOCKS5 bootstrap logic. Pass NULL to restore defaults. + * Intended for testing. + */ +AWS_IO_API void aws_socks5_channel_handler_set_system_vtable( + const struct aws_socks5_system_vtable *system_vtable); + +AWS_EXTERN_C_END + +#endif /* AWS_IO_SOCKS5_CHANNEL_HANDLER_H */ diff --git a/source/io.c b/source/io.c index e8a5216f9..65c872eab 100644 --- a/source/io.c +++ b/source/io.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -351,6 +352,46 @@ static struct aws_error_info s_errors[] = { AWS_DEFINE_ERROR_INFO_IO( AWS_IO_TLS_HOST_NAME_MISMATCH, "Channel shutdown due to certificate's host name does not match the endpoint host name."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_INIT, + "Failed to initialize SOCKS5 proxy connection."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_AUTH_METHOD, + "SOCKS5 proxy server doesn't support any of the client's authentication methods."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_AUTH_FAILED, + "Authentication with SOCKS5 proxy server failed."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_CONNECTION_FAILED, + "Failed to establish connection through SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_REQUEST_FAILED, + "SOCKS5 proxy request failed."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE, + "Received malformed response from SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_ADDRESS_TYPE, + "SOCKS5 proxy doesn't support the requested address type."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_BAD_ADDRESS, + "Invalid address format for SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_BAD_HANDSHAKE, + "SOCKS5 handshake failed."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_REJECTED, + "Connection rejected by SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_GENERAL_FAILURE, + "General failure reported by SOCKS5 proxy server."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_TTL_EXPIRED, + "TTL expired for connection through SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_COMMAND_NOT_SUPPORTED, + "Command not supported by SOCKS5 proxy server."), + }; /* clang-format on */ diff --git a/source/socks5.c b/source/socks5.c new file mode 100644 index 000000000..4bc8beafc --- /dev/null +++ b/source/socks5.c @@ -0,0 +1,1125 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +AWS_STATIC_STRING_FROM_LITERAL(s_socks_none_method, "NONE"); +AWS_STATIC_STRING_FROM_LITERAL(s_socks_username_password_method, "USERNAME_PASSWORD"); +AWS_STATIC_STRING_FROM_LITERAL(s_socks_gssapi_method, "GSSAPI"); + +/* Buffer size constants for SOCKS5 protocol operations */ +#define AWS_SOCKS5_SEND_BUFFER_INITIAL_SIZE 256 +#define AWS_SOCKS5_RECV_BUFFER_INITIAL_SIZE 512 + +static size_t s_string_length(const struct aws_string *str) { + return str ? str->len : 0; +} + +static const uint8_t *s_string_bytes(const struct aws_string *str) { + return str ? aws_string_bytes(str) : NULL; +} + +/** + * Helper function to ensure a buffer has enough capacity for additional data. + * + * This function checks if the buffer has enough remaining capacity for the required + * space, and if not, reserves more space in the buffer. + * + * @param buffer The buffer to check and potentially resize + * @param required_space How much additional space is needed + * @return AWS_OP_SUCCESS if the buffer has enough space, AWS_OP_ERR otherwise + */ +static int s_ensure_buffer_has_capacity( + struct aws_byte_buf *buffer, + size_t required_space) { + + if (!buffer) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* Calculate available space safely */ + size_t available_space = (buffer->capacity > buffer->len) ? + (buffer->capacity - buffer->len) : 0; + + /* Only reserve more if we don't have enough */ + if (required_space > available_space) { + if (aws_byte_buf_reserve(buffer, buffer->len + required_space)) { + return AWS_OP_ERR; + } + } + + return AWS_OP_SUCCESS; +} + +/* Helper for converting auth method enum to string for logging */ +static struct aws_string *s_auth_method_to_string(enum aws_socks5_auth_method method) { + switch (method) { + case AWS_SOCKS5_AUTH_NONE: + return (struct aws_string *)s_socks_none_method; + case AWS_SOCKS5_AUTH_USERNAME_PASSWORD: + return (struct aws_string *)s_socks_username_password_method; + case AWS_SOCKS5_AUTH_GSSAPI: + return (struct aws_string *)s_socks_gssapi_method; + default: + return NULL; + } +} + +int aws_socks5_proxy_options_init( + struct aws_socks5_proxy_options *options, + struct aws_allocator *allocator, + struct aws_byte_cursor host, + uint16_t port) { + + if (!options || !allocator) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* Validate host cursor */ + if (!aws_byte_cursor_is_valid(&host) || host.len == 0 || host.ptr == NULL) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Invalid host provided to SOCKS5 proxy options init"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + aws_socks5_proxy_options_init_default(options); + options->port = port; + options->host = aws_string_new_from_cursor(allocator, &host); + if (options->host == NULL) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Failed to copy host for SOCKS5 proxy options"); + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} + +int aws_socks5_proxy_options_init_default( + struct aws_socks5_proxy_options *options) { + AWS_ZERO_STRUCT(*options); + options->port = 1080; /* Default SOCKS5 port */ + options->connection_timeout_ms = 3000; /* Default timeout of 3 seconds */ + options->host_resolution_mode = AWS_SOCKS5_HOST_RESOLUTION_PROXY; + + return AWS_OP_SUCCESS; +} + +/* Destination must be zero-initialized before calling to avoid leaking prior allocations. */ +int aws_socks5_proxy_options_copy( + struct aws_socks5_proxy_options *dest, + const struct aws_socks5_proxy_options *src) { + + if (!dest || !src) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + AWS_ZERO_STRUCT(*dest); + dest->port = src->port; + dest->connection_timeout_ms = src->connection_timeout_ms; + dest->host_resolution_mode = src->host_resolution_mode; + + if (src->host) { + dest->host = aws_string_new_from_string(src->host->allocator, src->host); + if (!dest->host) { + goto on_error; + } + } + + if (src->username) { + dest->username = aws_string_new_from_string(src->username->allocator, src->username); + if (!dest->username) { + goto on_error; + } + } + + if (src->password) { + dest->password = aws_string_new_from_string(src->password->allocator, src->password); + if (!dest->password) { + goto on_error; + } + } + + return AWS_OP_SUCCESS; + +on_error: + aws_socks5_proxy_options_clean_up(dest); + return AWS_OP_ERR; +} + +void aws_socks5_proxy_options_clean_up(struct aws_socks5_proxy_options *options) { + if (!options) { + return; + } + + aws_string_destroy(options->host); + aws_string_destroy_secure(options->username); + aws_string_destroy_secure(options->password); + + AWS_ZERO_STRUCT(*options); +} + +int aws_socks5_proxy_options_set_auth( + struct aws_socks5_proxy_options *options, + struct aws_allocator *allocator, + struct aws_byte_cursor username, + struct aws_byte_cursor password) { + + if (!options || !allocator) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (options->username) { + aws_string_destroy_secure(options->username); + options->username = NULL; + } + if (options->password) { + aws_string_destroy_secure(options->password); + options->password = NULL; + } + if (username.len > 0) { + options->username = aws_string_new_from_cursor(allocator, &username); + if (!options->username) { + return AWS_OP_ERR; + } + } + if (password.len > 0) { + options->password = aws_string_new_from_cursor(allocator, &password); + if (!options->password) { + return AWS_OP_ERR; + } + } + return AWS_OP_SUCCESS; +} + +void aws_socks5_proxy_options_set_host_resolution_mode( + struct aws_socks5_proxy_options *options, + enum aws_socks5_host_resolution_mode mode) { + + if (!options) { + return; + } + + options->host_resolution_mode = mode; +} + +enum aws_socks5_host_resolution_mode aws_socks5_proxy_options_get_host_resolution_mode( + const struct aws_socks5_proxy_options *options) { + + if (!options) { + return AWS_SOCKS5_HOST_RESOLUTION_PROXY; + } + + return options->host_resolution_mode; +} + +AWS_IO_API enum aws_socks5_address_type aws_socks5_infer_address_type( + struct aws_byte_cursor target_host, + enum aws_socks5_address_type requested_type) { + + if (requested_type != AWS_SOCKS5_ATYP_DOMAIN || target_host.len == 0 || target_host.ptr == NULL) { + return requested_type; + } + + char address_buffer[AWS_ADDRESS_MAX_LEN]; + size_t host_len = target_host.len; + if (host_len >= sizeof(address_buffer)) { + host_len = sizeof(address_buffer) - 1; + } + memcpy(address_buffer, target_host.ptr, host_len); + address_buffer[host_len] = '\0'; + + if (address_buffer[0] == '[') { + size_t buf_len = strlen(address_buffer); + if (buf_len > 1 && address_buffer[buf_len - 1] == ']') { + memmove(address_buffer, address_buffer + 1, buf_len - 2); + address_buffer[buf_len - 2] = '\0'; + } + } + + char *zone_delimiter = strchr(address_buffer, '%'); + if (zone_delimiter) { + *zone_delimiter = '\0'; + } + + unsigned char ipv4_buffer[4]; + unsigned char ipv6_buffer[16]; + + if (inet_pton(AF_INET, address_buffer, ipv4_buffer) == 1) { + return AWS_SOCKS5_ATYP_IPV4; + } + + if (inet_pton(AF_INET6, address_buffer, ipv6_buffer) == 1) { + return AWS_SOCKS5_ATYP_IPV6; + } + + return requested_type; +} + +int aws_socks5_context_init( + struct aws_socks5_context *context, + struct aws_allocator *allocator, + const struct aws_socks5_proxy_options *options, + struct aws_byte_cursor target_host, + uint16_t target_port, + enum aws_socks5_address_type address_type) { + + if (!context) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: context is NULL in aws_socks5_context_init"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (!allocator) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: allocator is NULL in aws_socks5_context_init"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (!options) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: options is NULL in aws_socks5_context_init"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (!target_host.ptr || target_host.len == 0) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: target host is invalid (NULL or empty) in aws_socks5_context_init"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Proxy options: host=%p len=%zu, port=%d", + (void *)options->host, + s_string_length(options->host), + options->port); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Proxy endpoint host='%.*s', port=%d", + (int)target_host.len, + target_host.ptr ? (const char *)target_host.ptr : "", + target_port); + + AWS_ZERO_STRUCT(*context); + context->allocator = allocator; + context->state = AWS_SOCKS5_STATE_INIT; + + if (aws_array_list_init_dynamic(&context->auth_methods, allocator, 3, sizeof(enum aws_socks5_auth_method))) { + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_INIT); + } + + enum aws_socks5_auth_method no_auth = AWS_SOCKS5_AUTH_NONE; + aws_array_list_push_back(&context->auth_methods, &no_auth); + + size_t options_username_len = options->username ? options->username->len : 0; + size_t options_password_len = options->password ? options->password->len : 0; + + if (options_username_len > 0 && options_password_len > 0) { + enum aws_socks5_auth_method user_pass = AWS_SOCKS5_AUTH_USERNAME_PASSWORD; + aws_array_list_push_back(&context->auth_methods, &user_pass); + } + + if (options->host == NULL || options->host->len == 0) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Invalid host in SOCKS5 proxy options (buffer=%p, len=%zu)", + (void *)options->host, + options->host ? options->host->len : 0); + aws_socks5_context_clean_up(context); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (aws_socks5_proxy_options_copy(&context->options, options)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Failed to copy proxy options: error=%d (%s)", + error_code, + aws_error_str(error_code)); + aws_array_list_clean_up(&context->auth_methods); + return AWS_OP_ERR; + } + + struct aws_byte_cursor host_copy = target_host; + context->endpoint_host = aws_string_new_from_cursor(allocator, &host_copy); + if (!context->endpoint_host) { + aws_array_list_clean_up(&context->auth_methods); + aws_socks5_proxy_options_clean_up(&context->options); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_INIT); + } + + context->endpoint_port = target_port; + context->endpoint_address_type = aws_socks5_infer_address_type(target_host, address_type); + + if (aws_byte_buf_init(&context->send_buf, allocator, AWS_SOCKS5_SEND_BUFFER_INITIAL_SIZE)) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: Failed to initialize send buffer"); + aws_array_list_clean_up(&context->auth_methods); + aws_socks5_proxy_options_clean_up(&context->options); + aws_string_destroy(context->endpoint_host); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_INIT); + } + + if (aws_byte_buf_init(&context->recv_buf, allocator, AWS_SOCKS5_RECV_BUFFER_INITIAL_SIZE)) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: Failed to initialize receive buffer"); + aws_array_list_clean_up(&context->auth_methods); + aws_socks5_proxy_options_clean_up(&context->options); + aws_string_destroy(context->endpoint_host); + aws_byte_buf_clean_up(&context->send_buf); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_INIT); + } + + return AWS_OP_SUCCESS; +} + +void aws_socks5_context_clean_up(struct aws_socks5_context *context) { + if (!context) { + return; + } + + aws_array_list_clean_up(&context->auth_methods); + aws_socks5_proxy_options_clean_up(&context->options); + aws_string_destroy(context->endpoint_host); + + if (context->send_buf.buffer) { + aws_byte_buf_clean_up(&context->send_buf); + } + + if (context->recv_buf.buffer) { + aws_byte_buf_clean_up(&context->recv_buf); + } + + AWS_ZERO_STRUCT(*context); +} + +int aws_socks5_write_greeting( + struct aws_socks5_context *context, + struct aws_byte_buf *buffer) { + + if (!context || !buffer) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + size_t num_methods = aws_array_list_length(&context->auth_methods); + if (num_methods == 0) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: No authentication methods available for SOCKS5 greeting"); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_INIT); + } + + /* SOCKS5 greeting format: + * +----+----------+----------+ + * |VER | NMETHODS | METHODS | + * +----+----------+----------+ + * | 1 | 1 | 1 to 255 | + * +----+----------+----------+ + */ + size_t greeting_size = 2 + num_methods; /* VER(1) + NMETHODS(1) + METHODS(n) */ + + /* Use the helper function to ensure buffer capacity */ + if (s_ensure_buffer_has_capacity(buffer, greeting_size)) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, "id=static: Failed to allocate buffer for SOCKS5 greeting, size=%zu", greeting_size); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_INIT); + } + + /* Write SOCKS5 version */ + buffer->buffer[buffer->len++] = AWS_SOCKS5_VERSION; + + /* Write number of auth methods */ + buffer->buffer[buffer->len++] = (uint8_t)num_methods; + + /* Write the auth methods */ + for (size_t i = 0; i < num_methods; i++) { + enum aws_socks5_auth_method method; + if (aws_array_list_get_at(&context->auth_methods, &method, i)) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: Failed to get auth method from list at index %zu", i); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_INIT); + } + + buffer->buffer[buffer->len++] = (uint8_t)method; + + /* Log which auth methods we're offering */ + struct aws_string *method_str = s_auth_method_to_string(method); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Offering SOCKS5 auth method %s", + method_str ? aws_string_c_str(method_str) : "UNKNOWN"); + } + + /* Update context state */ + context->state = AWS_SOCKS5_STATE_GREETING_SENT; + + AWS_LOGF_INFO( + AWS_LS_IO_SOCKS5, + "id=static: Prepared SOCKS5 greeting with %zu auth methods", + num_methods); + + return AWS_OP_SUCCESS; +} + +int aws_socks5_read_greeting_response( + struct aws_socks5_context *context, + struct aws_byte_cursor *data) { + + if (!context || !data || !data->ptr) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (context->state != AWS_SOCKS5_STATE_GREETING_SENT) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Invalid state for reading SOCKS5 greeting response, expected %d, got %d", + AWS_SOCKS5_STATE_GREETING_SENT, + context->state); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_HANDSHAKE); + } + + if (data->len < AWS_SOCKS5_GREETING_RESP_SIZE) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: SOCKS5 greeting response too short, expected %d bytes, got %zu", + AWS_SOCKS5_GREETING_RESP_SIZE, + data->len); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE); + } + + /* SOCKS5 greeting response format: + * +----+--------+ + * |VER | METHOD | + * +----+--------+ + * | 1 | 1 | + * +----+--------+ + */ + uint8_t version = data->ptr[0]; + uint8_t method = data->ptr[1]; + + /* Verify SOCKS version */ + if (version != AWS_SOCKS5_VERSION) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Unexpected SOCKS version in greeting response, expected %d, got %d", + AWS_SOCKS5_VERSION, + version); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE); + } + + /* Check selected auth method */ + if (method == AWS_SOCKS5_AUTH_NO_ACCEPTABLE) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server rejected all authentication methods"); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_AUTH_METHOD); + } + + /* Store the selected auth method */ + context->selected_auth = (enum aws_socks5_auth_method)method; + context->state = AWS_SOCKS5_STATE_GREETING_RECEIVED; + + struct aws_string *method_str = s_auth_method_to_string(context->selected_auth); + AWS_LOGF_INFO( + AWS_LS_IO_SOCKS5, + "id=static: SOCKS5 server selected auth method: %s", + method_str ? aws_string_c_str(method_str) : "UNKNOWN"); + + return AWS_OP_SUCCESS; +} + +int aws_socks5_write_auth_request( + struct aws_socks5_context *context, + struct aws_byte_buf *buffer) { + + if (!context || !buffer) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (context->state != AWS_SOCKS5_STATE_GREETING_RECEIVED) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Invalid state for writing SOCKS5 auth request, expected %d, got %d", + AWS_SOCKS5_STATE_GREETING_RECEIVED, + context->state); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_HANDSHAKE); + } + + /* Check which auth method was selected */ + switch (context->selected_auth) { + case AWS_SOCKS5_AUTH_NONE: + /* No authentication needed, skip to connection phase */ + context->state = AWS_SOCKS5_STATE_AUTH_COMPLETED; + return AWS_OP_SUCCESS; + + case AWS_SOCKS5_AUTH_USERNAME_PASSWORD: + /* Continue with username/password authentication */ + break; + + case AWS_SOCKS5_AUTH_GSSAPI: + /* GSSAPI not supported yet */ + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: GSSAPI authentication not supported"); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_AUTH_METHOD); + + default: + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, "id=static: Unknown authentication method: %d", context->selected_auth); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_AUTH_METHOD); + } + + /* Check if we have username/password in options */ + if (context->options.username->len == 0 || context->options.password->len == 0) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Username/password authentication required but credentials not provided"); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_AUTH_FAILED); + } + + /* Username/Password authentication (RFC 1929) + * +----+------+----------+------+----------+ + * |VER | ULEN | UNAME | PLEN | PASSWD | + * +----+------+----------+------+----------+ + * | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + * +----+------+----------+------+----------+ + */ + + /* Check username and password lengths (must be between 1-255 bytes) */ + size_t username_len = s_string_length(context->options.username); + size_t password_len = s_string_length(context->options.password); + if (username_len > 255 || password_len > 255) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Username or password too long (max 255 bytes): ulen=%zu, plen=%zu", + username_len, + password_len); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_AUTH_FAILED); + } + + /* Calculate total auth request size */ + size_t auth_size = 3 + username_len + password_len; + + /* Use the helper function to ensure buffer capacity */ + if (s_ensure_buffer_has_capacity(buffer, auth_size)) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, "id=static: Failed to allocate buffer for SOCKS5 auth request, size=%zu", auth_size); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_AUTH_FAILED); + } + + /* Write sub-negotiation version (0x01 for username/password) */ + buffer->buffer[buffer->len++] = AWS_SOCKS5_AUTH_VERSION; + + /* Write username length and username */ + buffer->buffer[buffer->len++] = (uint8_t)username_len; + if (username_len > 0) { + memcpy(buffer->buffer + buffer->len, s_string_bytes(context->options.username), username_len); + buffer->len += username_len; + } + + /* Write password length and password */ + buffer->buffer[buffer->len++] = (uint8_t)password_len; + if (password_len > 0) { + memcpy(buffer->buffer + buffer->len, s_string_bytes(context->options.password), password_len); + buffer->len += password_len; + } + + /* Update state */ + context->state = AWS_SOCKS5_STATE_AUTH_STARTED; + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Prepared SOCKS5 username/password auth request with username=%.*s", + (int)username_len, + (const char *)s_string_bytes(context->options.username)); + + return AWS_OP_SUCCESS; +} + +int aws_socks5_read_auth_response( + struct aws_socks5_context *context, + struct aws_byte_cursor *data) { + + if (!context || !data || !data->ptr) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* If no auth required, we can skip this step */ + if (context->selected_auth == AWS_SOCKS5_AUTH_NONE) { + context->state = AWS_SOCKS5_STATE_AUTH_COMPLETED; + return AWS_OP_SUCCESS; + } + + if (context->state != AWS_SOCKS5_STATE_AUTH_STARTED) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Invalid state for reading SOCKS5 auth response, expected %d, got %d", + AWS_SOCKS5_STATE_AUTH_STARTED, + context->state); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_HANDSHAKE); + } + + if (data->len < AWS_SOCKS5_AUTH_RESP_SIZE) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: SOCKS5 auth response too short, expected %d bytes, got %zu", + AWS_SOCKS5_AUTH_RESP_SIZE, + data->len); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE); + } + + /* Username/Password auth response format: + * +----+--------+ + * |VER | STATUS | + * +----+--------+ + * | 1 | 1 | + * +----+--------+ + */ + uint8_t version = data->ptr[0]; + uint8_t status = data->ptr[1]; + + /* Verify auth version */ + if (version != AWS_SOCKS5_AUTH_VERSION) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Unexpected auth version in SOCKS5 auth response, expected %d, got %d", + AWS_SOCKS5_AUTH_VERSION, + version); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE); + } + + /* Check auth status (0 = success, anything else = failure) */ + if (status != 0) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, "id=static: SOCKS5 authentication failed with status code %d", status); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_AUTH_FAILED); + } + + /* Authentication successful */ + context->state = AWS_SOCKS5_STATE_AUTH_COMPLETED; + + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 authentication successful"); + + return AWS_OP_SUCCESS; +} + +int aws_socks5_write_connect_request( + struct aws_socks5_context *context, + struct aws_byte_buf *buffer) { + + if (!context || !buffer) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (context->state != AWS_SOCKS5_STATE_AUTH_COMPLETED && + context->state != AWS_SOCKS5_STATE_GREETING_RECEIVED) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Invalid state for writing SOCKS5 connect request, expected %d, got %d", + AWS_SOCKS5_STATE_AUTH_COMPLETED, + context->state); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_HANDSHAKE); + } + + /* Check if target host and port are set */ + if (s_string_length(context->endpoint_host) == 0) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: Target host not set for SOCKS5 connection"); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_ADDRESS); + } + + /* Determine the address type and required buffer size */ + enum aws_socks5_address_type addr_type = context->endpoint_address_type; + size_t addr_size = 0; + + switch (addr_type) { + case AWS_SOCKS5_ATYP_DOMAIN: + /* Domain name (1 byte length + domain name) */ + if (s_string_length(context->endpoint_host) > 255) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Domain name too long for SOCKS5 (max 255 bytes): %zu", + s_string_length(context->endpoint_host)); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_ADDRESS); + } + addr_size = 1 + s_string_length(context->endpoint_host); /* Length byte + domain */ + break; + + case AWS_SOCKS5_ATYP_IPV4: + /* IPv4 address (4 bytes) */ + addr_size = 4; + break; + + case AWS_SOCKS5_ATYP_IPV6: + /* IPv6 address (16 bytes) */ + addr_size = 16; + break; + + default: + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, "id=static: Unsupported address type: %d", addr_type); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_ADDRESS_TYPE); + } + + /* SOCKS5 request format: + * +----+-----+-------+------+----------+----------+ + * |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + * +----+-----+-------+------+----------+----------+ + * | 1 | 1 | X'00' | 1 | Variable | 2 | + * +----+-----+-------+------+----------+----------+ + */ + + /* Calculate total request size */ + size_t req_size = 6 + addr_size; /* VER(1) + CMD(1) + RSV(1) + ATYP(1) + ADDR(var) + PORT(2) */ + + /* Use the helper function to ensure buffer capacity */ + if (s_ensure_buffer_has_capacity(buffer, req_size)) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, "id=static: Failed to allocate buffer for SOCKS5 connect request, size=%zu", req_size); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_REQUEST_FAILED); + } + + /* Write SOCKS5 version */ + buffer->buffer[buffer->len++] = AWS_SOCKS5_VERSION; + + /* Write command (CONNECT) */ + buffer->buffer[buffer->len++] = AWS_SOCKS5_COMMAND_CONNECT; + + /* Write reserved byte (0x00) */ + buffer->buffer[buffer->len++] = AWS_SOCKS5_RESERVED; + + /* Write address type */ + buffer->buffer[buffer->len++] = (uint8_t)addr_type; + + /* Write destination address */ + switch (addr_type) { + case AWS_SOCKS5_ATYP_DOMAIN: { + size_t target_len = s_string_length(context->endpoint_host); + buffer->buffer[buffer->len++] = (uint8_t)target_len; + if (target_len > 0) { + memcpy(buffer->buffer + buffer->len, s_string_bytes(context->endpoint_host), target_len); + buffer->len += target_len; + } + break; + } + + case AWS_SOCKS5_ATYP_IPV4: { + uint8_t binary_addr[4]; + size_t target_len = s_string_length(context->endpoint_host); + + if (target_len == 4) { + memcpy(buffer->buffer + buffer->len, s_string_bytes(context->endpoint_host), 4); + } else { + char ip_str[128]; + size_t copy_len = target_len < 127 ? target_len : 127; + memcpy(ip_str, s_string_bytes(context->endpoint_host), copy_len); + ip_str[copy_len] = '\0'; + + if (inet_pton(AF_INET, ip_str, binary_addr) != 1) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Failed to convert IPv4 address '%s' to binary", + ip_str); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_ADDRESS); + } + + memcpy(buffer->buffer + buffer->len, binary_addr, 4); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Converted IPv4 '%s' to binary: %d.%d.%d.%d", + ip_str, + binary_addr[0], binary_addr[1], binary_addr[2], binary_addr[3]); + } + + buffer->len += 4; + break; + } + + case AWS_SOCKS5_ATYP_IPV6: { + uint8_t binary_addr[16]; + size_t target_len = s_string_length(context->endpoint_host); + + if (target_len == 16) { + memcpy(buffer->buffer + buffer->len, s_string_bytes(context->endpoint_host), 16); + } else { + char ip_str[128]; + size_t copy_len = target_len < 127 ? target_len : 127; + memcpy(ip_str, s_string_bytes(context->endpoint_host), copy_len); + ip_str[copy_len] = '\0'; + + if (inet_pton(AF_INET6, ip_str, binary_addr) != 1) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Failed to convert IPv6 address '%s' to binary", + ip_str); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_ADDRESS); + } + + memcpy(buffer->buffer + buffer->len, binary_addr, 16); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Converted IPv6 '%s' to binary format", + ip_str); + } + + buffer->len += 16; + break; + } + + default: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: Unsupported address type: %d", addr_type); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_ADDRESS_TYPE); + } + + /* Write destination port (network byte order) */ + uint16_t port_n = htons(context->endpoint_port); + memcpy(buffer->buffer + buffer->len, &port_n, sizeof(uint16_t)); + buffer->len += sizeof(uint16_t); + + /* Update state */ + context->state = AWS_SOCKS5_STATE_REQUEST_SENT; + + /* Log the connection attempt */ + switch (addr_type) { + case AWS_SOCKS5_ATYP_DOMAIN: + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Prepared SOCKS5 CONNECT request for domain %.*s:%d", + (int)s_string_length(context->endpoint_host), + (const char *)s_string_bytes(context->endpoint_host), + context->endpoint_port); + break; + + case AWS_SOCKS5_ATYP_IPV4: + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Prepared SOCKS5 CONNECT request for IPv4 address %.*s:%d", + (int)s_string_length(context->endpoint_host), + (const char *)s_string_bytes(context->endpoint_host), + context->endpoint_port); + break; + + case AWS_SOCKS5_ATYP_IPV6: + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: Prepared SOCKS5 CONNECT request for IPv6 address %.*s:%d", + (int)s_string_length(context->endpoint_host), + (const char *)s_string_bytes(context->endpoint_host), + context->endpoint_port); + break; + + default: + /* This should never happen as we already checked earlier */ + break; + } + + return AWS_OP_SUCCESS; +} + +int aws_socks5_read_connect_response( + struct aws_socks5_context *context, + struct aws_byte_cursor *data) { + + if (!context || !data || !data->ptr) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (context->state != AWS_SOCKS5_STATE_REQUEST_SENT) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Invalid state for reading SOCKS5 connect response, expected %d, got %d", + AWS_SOCKS5_STATE_REQUEST_SENT, + context->state); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_BAD_HANDSHAKE); + } + + /* SOCKS5 response format: + * +----+-----+-------+------+----------+----------+ + * |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + * +----+-----+-------+------+----------+----------+ + * | 1 | 1 | X'00' | 1 | Variable | 2 | + * +----+-----+-------+------+----------+----------+ + */ + + /* Check minimum response size */ + if (data->len < AWS_SOCKS5_CONN_RESP_MIN_SIZE) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: SOCKS5 connect response too short, expected at least %d bytes, got %zu", + AWS_SOCKS5_CONN_RESP_MIN_SIZE, + data->len); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE); + } + + /* Read the fixed response fields */ + uint8_t version = data->ptr[0]; + uint8_t status = data->ptr[1]; + uint8_t reserved = data->ptr[2]; + uint8_t atype = data->ptr[3]; + + /* Verify SOCKS version */ + if (version != AWS_SOCKS5_VERSION) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: Unexpected SOCKS version in connect response, expected %d, got %d", + AWS_SOCKS5_VERSION, + version); + context->state = AWS_SOCKS5_STATE_ERROR; + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE); + } + + /* Verify reserved byte */ + if (reserved != AWS_SOCKS5_RESERVED) { + AWS_LOGF_WARN( + AWS_LS_IO_SOCKS5, + "id=static: Unexpected reserved byte in SOCKS5 connect response, expected 0, got %d", + reserved); + /* Continue anyway, as this isn't critical */ + } + + /* Check status code */ + if (status != AWS_SOCKS5_STATUS_SUCCESS) { + /* Handle specific error codes */ + context->state = AWS_SOCKS5_STATE_ERROR; + + switch (status) { + case AWS_SOCKS5_STATUS_GENERAL_FAILURE: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server reported general failure"); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_GENERAL_FAILURE); + + case AWS_SOCKS5_STATUS_CONNECTION_NOT_ALLOWED: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server rejected connection"); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_REJECTED); + + case AWS_SOCKS5_STATUS_NETWORK_UNREACHABLE: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server reported network unreachable"); + return aws_raise_error(AWS_IO_SOCKET_NO_ROUTE_TO_HOST); + + case AWS_SOCKS5_STATUS_HOST_UNREACHABLE: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server reported host unreachable"); + return aws_raise_error(AWS_IO_DNS_NO_ADDRESS_FOR_HOST); + + case AWS_SOCKS5_STATUS_CONNECTION_REFUSED: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server reported connection refused"); + return aws_raise_error(AWS_IO_SOCKET_CONNECTION_REFUSED); + + case AWS_SOCKS5_STATUS_TTL_EXPIRED: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server reported TTL expired"); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_TTL_EXPIRED); + + case AWS_SOCKS5_STATUS_COMMAND_NOT_SUPPORTED: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server does not support the CONNECT command"); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_COMMAND_NOT_SUPPORTED); + + case AWS_SOCKS5_STATUS_ADDRESS_TYPE_NOT_SUPPORTED: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server does not support the address type"); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_ADDRESS_TYPE); + + default: + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server returned unknown error: %d", status); + return aws_raise_error(AWS_IO_SOCKS5_PROXY_ERROR_CONNECTION_FAILED); + } + } + + /* Connection successful */ + context->state = AWS_SOCKS5_STATE_RESPONSE_RECEIVED; + + /* Parse bound address and port if needed (for informational purposes) + * Note: We don't actually need to use the bound address/port for CONNECT, + * but we'll log it for debugging purposes. + */ + size_t addr_offset = 4; + size_t addr_size = 0; + + switch (atype) { + case AWS_SOCKS5_ATYP_DOMAIN: { + /* Domain address format: [len][domain]... */ + uint8_t dom_len = data->ptr[addr_offset]; + addr_offset++; + addr_size = dom_len; + + if (data->len < addr_offset + addr_size + 2) { + AWS_LOGF_WARN(AWS_LS_IO_SOCKS5, "id=static: Truncated domain address in SOCKS5 response"); + /* Continue anyway, as we've already confirmed success */ + } else { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: SOCKS5 server bound to domain %.*s", + (int)addr_size, + (char *)(data->ptr + addr_offset)); + } + break; + } + + case AWS_SOCKS5_ATYP_IPV4: + /* IPv4 address (4 bytes) */ + addr_size = 4; + if (data->len < addr_offset + addr_size + 2) { + AWS_LOGF_WARN(AWS_LS_IO_SOCKS5, "id=static: Truncated IPv4 address in SOCKS5 response"); + } else { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=static: SOCKS5 server bound to IPv4 address %d.%d.%d.%d", + data->ptr[addr_offset], + data->ptr[addr_offset + 1], + data->ptr[addr_offset + 2], + data->ptr[addr_offset + 3]); + } + break; + + case AWS_SOCKS5_ATYP_IPV6: + /* IPv6 address (16 bytes) */ + addr_size = 16; + if (data->len < addr_offset + addr_size + 2) { + AWS_LOGF_WARN(AWS_LS_IO_SOCKS5, "id=static: Truncated IPv6 address in SOCKS5 response"); + } + break; + + default: + AWS_LOGF_WARN( + AWS_LS_IO_SOCKS5, + "id=static: Unknown address type in SOCKS5 connect response: %d", + atype); + /* Continue anyway, as we've already confirmed success */ + break; + } + + /* Read port if we have enough data */ + if (data->len >= addr_offset + addr_size + 2) { + uint16_t port; + memcpy(&port, data->ptr + addr_offset + addr_size, sizeof(uint16_t)); + port = ntohs(port); + + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 server bound to port %d", port); + } + + context->state = AWS_SOCKS5_STATE_CONNECTED; + + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKS5, "id=static: SOCKS5 connection established successfully"); + + return AWS_OP_SUCCESS; +} diff --git a/source/socks5_channel_handler.c b/source/socks5_channel_handler.c new file mode 100644 index 000000000..46090a551 --- /dev/null +++ b/source/socks5_channel_handler.c @@ -0,0 +1,3308 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Structure for passing both proxy options and original user data */ +struct aws_http_proxy_user_data { + struct aws_allocator *allocator; + void *proxy_options; + void *user_data; +}; + +/* Structure to store context for the channel-bootstrap adapter */ +struct aws_socks5_adapter_context { + struct aws_client_bootstrap *bootstrap; + aws_client_bootstrap_on_channel_event_fn *original_callback; + void *original_user_data; +}; + +static int s_socks5_bootstrap_begin_handshake( + struct aws_socks5_bootstrap *socks5_bootstrap, + struct aws_channel *channel); + +static int s_socks5_bootstrap_start_endpoint_resolution( + struct aws_socks5_bootstrap *socks5_bootstrap, + const struct aws_socket_channel_bootstrap_options *channel_options); + +static void s_socks5_bootstrap_resolution_success_task( + struct aws_channel_task *task, + void *arg, + enum aws_task_status status); + +static void s_socks5_bootstrap_resolution_failure_task( + struct aws_channel_task *task, + void *arg, + enum aws_task_status status); + +static void s_socks5_on_host_resolved( + struct aws_host_resolver *resolver, + const struct aws_string *host_name, + int err_code, + const struct aws_array_list *host_addresses, + void *user_data); + +static const struct aws_socks5_system_vtable s_default_socks5_system_vtable = { + .aws_client_bootstrap_new_socket_channel = aws_client_bootstrap_new_socket_channel, +}; + +static const struct aws_socks5_system_vtable *s_socks5_system_vtable = &s_default_socks5_system_vtable; + +void aws_socks5_channel_handler_set_system_vtable(const struct aws_socks5_system_vtable *system_vtable) { + if (system_vtable != NULL) { + s_socks5_system_vtable = system_vtable; + } else { + s_socks5_system_vtable = &s_default_socks5_system_vtable; + } +} + +/** + * State machine for the SOCKS5 channel handler + * + * Valid state transitions: + * INIT -> GREETING (when handshake starts) + * GREETING -> AUTH (if authentication required) + * GREETING -> CONNECT (if no authentication required) + * AUTH -> CONNECT (after authentication completes) + * CONNECT -> ESTABLISHED (when connection established) + * ANY -> ERROR (on any error) + * + * These states align with but are distinct from the protocol states in aws_socks5_state. + * This represents the channel handler's state specifically. + */ +enum aws_socks5_channel_state { + AWS_SOCKS5_CHANNEL_STATE_INIT, /* Initial state before handshake begins */ + AWS_SOCKS5_CHANNEL_STATE_GREETING, /* Sending greeting and processing response */ + AWS_SOCKS5_CHANNEL_STATE_AUTH, /* Performing authentication if required */ + AWS_SOCKS5_CHANNEL_STATE_CONNECT, /* Sending connect request and processing response */ + AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED, /* Connection established, ready for data transfer */ + AWS_SOCKS5_CHANNEL_STATE_ERROR /* Error occurred, no further progress possible */ +}; + +/** + * Converts a SOCKS5 channel state to a readable string for logging purposes. + * This improves log readability by providing human-readable state names. + * + */ +static inline const char *s_socks5_channel_state_to_string(enum aws_socks5_channel_state state) { + switch (state) { + case AWS_SOCKS5_CHANNEL_STATE_INIT: + return "INIT"; + case AWS_SOCKS5_CHANNEL_STATE_GREETING: + return "GREETING"; + case AWS_SOCKS5_CHANNEL_STATE_AUTH: + return "AUTH"; + case AWS_SOCKS5_CHANNEL_STATE_CONNECT: + return "CONNECT"; + case AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED: + return "ESTABLISHED"; + case AWS_SOCKS5_CHANNEL_STATE_ERROR: + return "ERROR"; + default: + return "UNKNOWN"; + } +} + +/** + * Returns a human-readable name for SOCKS5 message types used in logging. + * This aids in debugging by providing context about the type of message + * being processed in the SOCKS5 protocol. + */ +static inline const char *s_get_socks5_message_type_name(int message_type) { + switch (message_type) { + case 0: + return "INIT"; + case 1: + return "GREETING"; + case 2: + return "AUTH"; + case 3: + return "CONNECT"; + default: + return "UNKNOWN"; + } +} + +struct aws_socks5_channel_handler { + /* Base handler data */ + struct aws_channel_handler handler; + struct aws_allocator *allocator; + struct aws_channel_slot *slot; /* Current channel slot */ + + /* Channel and connection state */ + enum aws_socks5_channel_state channel_state; + int error_code; + bool process_incoming_data; + + /* SOCKS5 protocol context and buffers */ + struct aws_socks5_context context; + struct aws_byte_buf send_buffer; /* Buffer for outgoing SOCKS5 protocol messages */ + struct aws_byte_buf read_buffer; /* Buffer for accumulating incoming data */ + + /* Callback management */ + aws_channel_on_setup_completed_fn *on_setup_completed; + void *user_data; + + /* Timeout management */ + uint64_t connect_timeout_ns; + struct aws_channel_task timeout_task; + bool timeout_task_scheduled; +}; + +/** + * Cancels any pending timeout task for a SOCKS5 handler. + * + * This function safely cancels any scheduled timeout task to prevent it + * from executing after it's no longer needed (such as when a connection + * is successfully established or when shutting down). + * + * @param handler The SOCKS5 channel handler + */ +static void s_cancel_timeout_task(struct aws_socks5_channel_handler *handler) { + if (!handler) { + return; + } + + /* Only mark as not scheduled so the handler can check this when it runs */ + if (handler->timeout_task_scheduled) { + handler->timeout_task_scheduled = false; + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Marked SOCKS5 timeout task as canceled", + (void *)handler); + } +} + +/** + * Helper function for logging and handling state transitions in the SOCKS5 handler. + * + * This function manages state transitions with proper logging and error handling. + * It ensures that error codes are recorded when transitioning to an error state + * and provides detailed logging about state changes for debugging. + * + * @param handler The SOCKS5 channel handler + * @param new_state The state to transition to + * @param error_code Error code if transitioning due to an error, 0 otherwise + */ +static void s_transition_state( + struct aws_socks5_channel_handler *handler, + enum aws_socks5_channel_state new_state, + int error_code) { + + if (!handler) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_transition_state called with NULL handler"); + return; + } + + /* Store old state before modifying anything */ + enum aws_socks5_channel_state old_state = handler->channel_state; + + /* Validate state transition - certain transitions aren't allowed */ + if (old_state == AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED && + new_state != AWS_SOCKS5_CHANNEL_STATE_ERROR) { + AWS_LOGF_WARN( + AWS_LS_IO_SOCKS5, + "id=%p: Invalid state transition attempted: %s -> %s (only ERROR state allowed from ESTABLISHED)", + (void *)handler, + s_socks5_channel_state_to_string(old_state), + s_socks5_channel_state_to_string(new_state)); + return; + } + + if (old_state == AWS_SOCKS5_CHANNEL_STATE_ERROR) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: State transition from ERROR state ignored: %s -> %s", + (void *)handler, + s_socks5_channel_state_to_string(old_state), + s_socks5_channel_state_to_string(new_state)); + return; + } + + /* Get state names for logging */ + const char *old_state_name = s_socks5_channel_state_to_string(old_state); + const char *new_state_name = s_socks5_channel_state_to_string(new_state); + + /* Set new state */ + handler->channel_state = new_state; + + /* If transitioning to error state, record the error code */ + if (new_state == AWS_SOCKS5_CHANNEL_STATE_ERROR && error_code) { + handler->error_code = error_code; + } + + /* Log the state transition */ + if (old_state != new_state) { + uint64_t now = 0; + if (handler->slot && handler->slot->channel) { + /* Get current time for performance tracking if possible */ + aws_channel_current_clock_time(handler->slot->channel, &now); + } + + if (new_state == AWS_SOCKS5_CHANNEL_STATE_ERROR) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: State transition: %s -> %s with error %d (%s) at %" PRIu64 "ns", + (void *)handler, + old_state_name, + new_state_name, + error_code, + aws_error_str(error_code), + now); + } else { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: State transition: %s -> %s at %" PRIu64 "ns", + (void *)handler, + old_state_name, + new_state_name, + now); + + /* Add additional context based on the new state */ + if (new_state == AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED) { + AWS_LOGF_INFO( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 connection established successfully", + (void *)handler); + + /* Cancel any timeout task when established */ + s_cancel_timeout_task(handler); + + } else if (new_state == AWS_SOCKS5_CHANNEL_STATE_GREETING) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Beginning SOCKS5 handshake", + (void *)handler); + } + } + } +} + +/** + * Helper function to ensure a buffer has at least the specified capacity. + * If the buffer is NULL or not properly initialized, it initializes it. + * If the buffer doesn't have enough capacity, it reallocates. + * + * @return AWS_OP_SUCCESS on success, AWS_OP_ERR on failure + */ +static int s_ensure_buffer_capacity( + struct aws_byte_buf *buffer, + struct aws_allocator *allocator, + size_t needed_capacity) { + + /* Default minimum capacity for new buffers */ + const size_t DEFAULT_MIN_CAPACITY = 256; + + if (!buffer || !allocator) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* If buffer is not initialized or doesn't have enough capacity */ + if (buffer->buffer == NULL || buffer->capacity == 0 || buffer->capacity < needed_capacity) { + /* Clean up existing buffer if any */ + if (buffer->buffer != NULL) { + aws_byte_buf_clean_up(buffer); + } + + /* Calculate new capacity - at least double the needed capacity or DEFAULT_MIN_CAPACITY */ + size_t new_capacity = needed_capacity * 2; + if (new_capacity < DEFAULT_MIN_CAPACITY) { + new_capacity = DEFAULT_MIN_CAPACITY; + } + + /* Initialize with new capacity */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "Initializing buffer with capacity %zu (needed %zu)", + new_capacity, + needed_capacity); + + return aws_byte_buf_init(buffer, allocator, new_capacity); + } + + return AWS_OP_SUCCESS; +} + +/** + * Helper function to reset a buffer and ensure it has enough capacity. + * Useful before writing new data to a buffer. + * + * @return AWS_OP_SUCCESS on success, AWS_OP_ERR on failure + */ +static int s_reset_and_ensure_buffer( + struct aws_byte_buf *buffer, + struct aws_allocator *allocator, + size_t needed_capacity) { + + if (!buffer || !allocator) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* Reset the buffer but keep its memory */ + aws_byte_buf_reset(buffer, false); + + /* Ensure it has enough capacity */ + return s_ensure_buffer_capacity(buffer, allocator, needed_capacity); +} + +/** + * Cleanup and destroy function for the SOCKS5 channel handler. + * + * This function is responsible for properly cleaning up and releasing all resources + * associated with a SOCKS5 channel handler, including: + * - SOCKS5 protocol context (credentials, target info, etc.) + * - Internal buffers used for protocol messages + * - The handler structure itself + * + * It performs thorough null-checking to handle partially initialized handlers safely. + * Any sensitive data (like credentials) is zeroed out before memory is released. + */ +static void s_socks5_handler_destroy(struct aws_channel_handler *handler) { + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: Destroying SOCKS5 channel handler", (void *)handler); + + if (handler == NULL) { + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKS5, "id=static: s_socks5_handler_destroy - NULL handler, nothing to do"); + return; + } + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + if (socks5_handler == NULL) { + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKS5, "id=%p: s_socks5_handler_destroy - NULL implementation", (void *)handler); + return; + } + + /* Save allocator before cleaning up (we'll need it for final memory release) */ + struct aws_allocator *allocator = socks5_handler->allocator; + + if (!allocator) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=%p: s_socks5_handler_destroy - NULL allocator, memory leak likely", (void *)handler); + return; + } + + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKS5, "id=%p: Cleaning up SOCKS5 context and buffers", (void *)handler); + + /* Clean up the SOCKS5 context */ + aws_socks5_context_clean_up(&socks5_handler->context); + + /* Clean up buffers (safely handles non-initialized buffers) */ + if (socks5_handler->send_buffer.buffer != NULL) { + aws_byte_buf_clean_up(&socks5_handler->send_buffer); + } + + if (socks5_handler->read_buffer.buffer != NULL) { + aws_byte_buf_clean_up(&socks5_handler->read_buffer); + } + + /* Clear any sensitive data before releasing memory */ + AWS_ZERO_STRUCT(*socks5_handler); + + /* Release the handler memory */ + AWS_LOGF_DEBUG(AWS_LS_IO_SOCKS5, "id=%p: Releasing SOCKS5 handler memory", (void *)handler); + aws_mem_release(allocator, socks5_handler); +} + +/* Forward declarations of helper functions */ +static void s_process_read_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_io_message *message); + +static void s_handle_timeout(struct aws_channel_task *task, void *arg, enum aws_task_status status); + +static void s_forward_pending_data_task(struct aws_channel_task *task, void *arg, enum aws_task_status status); + + +/** + * Safely invokes the setup callback, ensuring it's only called once. + * + * Callbacks are invoked safely by: + * 1. Storing callback references locally before nulling them + * 2. Performing single-operation checks to prevent race conditions + * 3. Using proper error handling for all edge cases + * + * @param handler The SOCKS5 channel handler + * @param error_code Error code to pass to the callback (AWS_OP_SUCCESS for success) + */ +static void s_invoke_setup_callback_safely( + struct aws_socks5_channel_handler *handler, + int error_code) { + + /* Safety checks */ + if (!handler) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: s_invoke_setup_callback_safely - NULL handler"); + return; + } + + /* Store callback and data locally before clearing */ + aws_channel_on_setup_completed_fn *callback = handler->on_setup_completed; + void *user_data = handler->user_data; + + /* Clear callback first to prevent double-invocation in any subsequent calls */ + handler->on_setup_completed = NULL; + + /* Only proceed if we had a valid callback */ + if (!callback) { + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: No callback to invoke (already called or never set)", (void *)handler); + return; + } + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Invoking setup callback with error_code=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + /* Determine channel to use (might be NULL if handler isn't properly connected) */ + struct aws_channel *channel = NULL; + + if (handler->slot && handler->slot->channel) { + channel = handler->slot->channel; + } else if (error_code == AWS_OP_SUCCESS) { + /* If we're reporting success but don't have a channel, that's an error */ + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Attempted to signal success without valid channel", + (void *)handler); + error_code = AWS_ERROR_INVALID_STATE; + } + + /* Invoke the stored callback with appropriate parameters */ + callback(channel, error_code, user_data); +} + +/** + * Processes incoming messages from the channel. + * + * This function handles all incoming data based on the current state of the SOCKS5 handler: + * 1. In ESTABLISHED state: forwards messages upstream (transparent proxy mode) + * 2. In ERROR state: drops messages and logs errors + * 3. During handshake states: processes protocol messages for the SOCKS5 handshake + * + * The function is a critical part of the channel's read path, determining whether + * messages are processed for SOCKS5 protocol handling or forwarded to the application. + * + * @param handler The SOCKS5 channel handler + * @param slot The channel slot this handler belongs to + * @param message The incoming message to process + * @return AWS_OP_SUCCESS on successful handling (even if message is consumed) + * AWS_OP_ERR on error (with aws_last_error set) + */ +static int s_socks5_handler_process_read_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_io_message *message) { + + /* Validate input parameters */ + if (!handler || !slot || !message) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_socks5_handler_process_read_message - Invalid arguments: handler=%p, slot=%p, message=%p", + (void *)handler, + (void *)slot, + (void *)message); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + + if (!socks5_handler) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: s_socks5_handler_process_read_message - NULL implementation", + (void *)handler); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* If we're in established state, pass the message up the channel */ + if (socks5_handler->channel_state == AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Connection established, forwarding message of size %zu", + (void *)handler, + message->message_data.len); + + /* Forward the message to the next handler in the read direction */ + int result = aws_channel_slot_send_message(slot, message, AWS_CHANNEL_DIR_READ); + if (result != AWS_OP_SUCCESS) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to forward message upstream, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + } + return result; + } + + /* If we're in error state, drop the message */ + if (socks5_handler->channel_state == AWS_SOCKS5_CHANNEL_STATE_ERROR) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: In error state, dropping incoming message of size %zu", + (void *)handler, + message->message_data.len); + + /* Release the message since we're not forwarding it */ + aws_mem_release(message->allocator, message); + return AWS_OP_SUCCESS; + } + + /* We're in handshake state, check if we should process the message */ + if (socks5_handler->process_incoming_data) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Processing incoming data for SOCKS5 handshake in state %d", + (void *)handler, + socks5_handler->channel_state); + + /* Process the message using our internal handler */ + s_process_read_message(handler, slot, message); + return AWS_OP_SUCCESS; + } + + /* We're not processing data (unusual case), log and drop the message */ + AWS_LOGF_WARN( + AWS_LS_IO_SOCKS5, + "id=%p: Not processing incoming data (flag not set) in SOCKS5 state %d, dropping message of size %zu", + (void *)handler, + socks5_handler->channel_state, + message->message_data.len); + + aws_mem_release(message->allocator, message); + return AWS_OP_SUCCESS; +} + +/** + * Processes outgoing messages to the channel. + * + * This function handles all outgoing data based on the current state of the SOCKS5 handler: + * 1. In ESTABLISHED state: forwards messages downstream (transparent proxy mode) + * 2. During handshake or ERROR states: blocks application data from being sent + * until the SOCKS5 connection is fully established + * + * This ensures that application data isn't sent over the connection until the + * SOCKS5 handshake is complete and the tunnel is established. + * + * @param handler The SOCKS5 channel handler + * @param slot The channel slot this handler belongs to + * @param message The outgoing message to process + * @return AWS_OP_SUCCESS on successful handling (even if message is dropped) + * AWS_OP_ERR on error (with aws_last_error set) + */ +static int s_socks5_handler_process_write_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_io_message *message) { + + /* Validate input parameters */ + if (!handler || !slot || !message) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_socks5_handler_process_write_message - Invalid arguments: handler=%p, slot=%p, message=%p", + (void *)handler, + (void *)slot, + (void *)message); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + + if (!socks5_handler) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: s_socks5_handler_process_write_message - NULL implementation", + (void *)handler); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* If we're in established state, pass the message down the channel */ + if (socks5_handler->channel_state == AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Connection established, forwarding outgoing message of size %zu", + (void *)handler, + message->message_data.len); + + /* Forward the message to the next handler in the write direction */ + int result = aws_channel_slot_send_message(slot, message, AWS_CHANNEL_DIR_WRITE); + if (result != AWS_OP_SUCCESS) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to forward outgoing message, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + } + return result; + } + + /* If we're in error state or still in handshake, drop application data */ + AWS_LOGF_WARN( + AWS_LS_IO_SOCKS5, + "id=%p: Not in established state (current state: %d), dropping outgoing message of size %zu", + (void *)handler, + socks5_handler->channel_state, + message->message_data.len); + + /* Release the message since we're not forwarding it */ + aws_mem_release(message->allocator, message); + return AWS_OP_SUCCESS; +} + +/** + * Handles window updates for flow control in the SOCKS5 channel handler. + * + * When the handler receives a window update (meaning more data can be received), + * it propagates this update to the adjacent handler to maintain proper flow control + * throughout the channel. This function is part of the AWS CRT channel's + * backpressure mechanism. + * + * @param handler The SOCKS5 channel handler + * @param slot The channel slot this handler belongs to + * @param window_update The number of bytes to increase the window by + * @return AWS_OP_SUCCESS on successful handling + * AWS_OP_ERR on error (with aws_last_error set) + */ +static int s_socks5_handler_initial_window_update( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + size_t window_update) { + + /* Validate input parameters */ + if (!handler || !slot) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_socks5_handler_initial_window_update - Invalid arguments: handler=%p, slot=%p", + (void *)handler, + (void *)slot); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* Log the window update */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Window update of %zu bytes", + (void *)handler, + window_update); + + /* Propagate the window update to the adjacent handler */ + if (slot->adj_right) { + aws_channel_slot_increment_read_window(slot->adj_right, window_update); + } else { + /* Not having an adjacent slot is normal during setup/teardown */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: No adjacent slot for window update of %zu bytes", + (void *)handler, + window_update); + } + + return AWS_OP_SUCCESS; +} + +/** + * Handles channel shutdown events for the SOCKS5 channel handler. + * + * This function is called during channel shutdown to: + * 1. Cancel any pending timeouts + * 2. Record error information if shutdown occurs during handshake + * 3. Safely invoke any pending callbacks + * 4. Propagate the shutdown signal to adjacent handlers + * + * Proper handling of shutdown is critical for clean resource cleanup and + * appropriate error propagation throughout the channel stack. + * + * @param handler The SOCKS5 channel handler + * @param slot The channel slot this handler belongs to + * @param dir The direction of shutdown (read or write) + * @param error_code The error that caused shutdown, or 0 for normal shutdown + * @param free_scarce_resources_immediately Whether to free resources immediately + * @return AWS_OP_SUCCESS on successful shutdown handling + * AWS_OP_ERR on error (with aws_last_error set) + */ +static int s_socks5_handler_shutdown( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + enum aws_channel_direction dir, + int error_code, + bool free_scarce_resources_immediately) { + + /* Validate input parameters */ + if (!handler || !slot) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_socks5_handler_shutdown - Invalid arguments: handler=%p, slot=%p", + (void *)handler, + (void *)slot); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + + if (!socks5_handler) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: s_socks5_handler_shutdown - NULL implementation", + (void *)handler); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* Log shutdown information */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Shutting down SOCKS5 handler, direction=%s, error_code=%d, free_resources=%d", + (void *)handler, + dir == AWS_CHANNEL_DIR_READ ? "READ" : "WRITE", + error_code, + free_scarce_resources_immediately); + + /* For read direction with no error, use socket closed as the reason */ + if (dir == AWS_CHANNEL_DIR_READ && !error_code) { + error_code = AWS_IO_SOCKET_CLOSED; + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Read direction shutdown with no error, using AWS_IO_SOCKET_CLOSED", + (void *)handler); + } + + /* Properly cancel any pending timeout task */ + s_cancel_timeout_task(socks5_handler); + + /* Handle shutdown during handshake */ + if (socks5_handler->channel_state != AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED && + socks5_handler->channel_state != AWS_SOCKS5_CHANNEL_STATE_ERROR && + error_code) { + /* If we're not established yet, transition to error state */ + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Shutdown during handshake (state %d), error_code=%d (%s)", + (void *)handler, + socks5_handler->channel_state, + error_code, + aws_error_str(error_code)); + + /* Record the error and update state */ + socks5_handler->error_code = error_code; + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_ERROR, error_code); + + /* If we have a pending callback, invoke it with the error */ + if (socks5_handler->on_setup_completed != NULL) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Invoking pending callback with error during shutdown", + (void *)handler); + s_invoke_setup_callback_safely(socks5_handler, error_code); + } + } + + /* Propagate shutdown to adjacent handlers */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Propagating shutdown to adjacent handlers", + (void *)handler); + + aws_channel_slot_on_handler_shutdown_complete( + slot, dir, error_code, free_scarce_resources_immediately); + + return AWS_OP_SUCCESS; +} + +/** + * Returns the initial window size for the SOCKS5 channel handler. + * + * This function delegates to the next handler in the chain to ensure + * consistent window sizing throughout the channel. If there's no + * next handler available, it returns a sensible default value. + * + * The window size is critical for flow control in the channel architecture, + * controlling how much data a handler is willing to receive before needing + * acknowledgment. + */ +static size_t s_socks5_handler_get_initial_window_size(struct aws_channel_handler *handler) { + /* Default window size if we can't delegate */ + const size_t DEFAULT_WINDOW_SIZE = 16 * 1024; /* 16 KB is a reasonable default */ + + if (!handler) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_socks5_handler_get_initial_window_size - NULL handler"); + return DEFAULT_WINDOW_SIZE; + } + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + + /* Safety check for the handler implementation */ + if (!socks5_handler) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: s_socks5_handler_get_initial_window_size - NULL implementation", + (void *)handler); + return DEFAULT_WINDOW_SIZE; + } + + /* Safety check for the slot */ + if (!socks5_handler->slot) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: s_socks5_handler_get_initial_window_size - No slot assigned yet", + (void *)handler); + return DEFAULT_WINDOW_SIZE; + } + + struct aws_channel_slot *adj_slot = socks5_handler->slot->adj_right; + + /* Check for adjacent slot and handler */ + if (adj_slot && adj_slot->handler && adj_slot->handler->vtable && + adj_slot->handler->vtable->initial_window_size) { + + /* Delegate to the next handler */ + size_t next_window_size = adj_slot->handler->vtable->initial_window_size(adj_slot->handler); + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Using adjacent handler's window size: %zu", + (void *)handler, + next_window_size); + + return next_window_size; + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: No adjacent handler with window size, using default: %zu", + (void *)handler, + DEFAULT_WINDOW_SIZE); + + /* Return default window size if we can't delegate */ + return DEFAULT_WINDOW_SIZE; +} + +/** + * Returns the message overhead that the SOCKS5 handler adds to each message. + * + * Once the SOCKS5 handshake is complete, the handler becomes a simple pass-through + * that doesn't add any additional overhead to messages. Therefore, this function + * returns 0 to indicate no additional memory allocation is needed for messages + * passing through this handler. + * + * During the handshake phase, the handler processes protocol-specific messages + * internally and doesn't add overhead to application messages. + */ +static size_t s_socks5_handler_message_overhead(struct aws_channel_handler *handler) { + (void)handler; + /* Return 0 since SOCKS5 doesn't add any overhead to messages passing through */ + return 0; +} + +static struct aws_channel_handler_vtable s_socks5_handler_vtable = { + .destroy = s_socks5_handler_destroy, + .process_read_message = s_socks5_handler_process_read_message, + .process_write_message = s_socks5_handler_process_write_message, + .increment_read_window = s_socks5_handler_initial_window_update, + .shutdown = s_socks5_handler_shutdown, + .initial_window_size = s_socks5_handler_get_initial_window_size, + .message_overhead = s_socks5_handler_message_overhead, +}; + +/* Send a SOCKS5 protocol message down the channel */ +static int s_send_socks5_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_byte_buf *buffer) { + + if (!handler || !slot || !buffer || buffer->len == 0) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (!slot->channel || !slot->adj_left) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_io_message *message = aws_channel_acquire_message_from_pool( + slot->channel, + AWS_IO_MESSAGE_APPLICATION_DATA, + buffer->len); if (!message) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to acquire message from pool, size=%zu", + (void *)handler, + buffer->len); + return AWS_OP_ERR; + } + + /* Copy the buffer content into the message */ + if (!aws_byte_buf_write( + &message->message_data, buffer->buffer, buffer->len)) { + aws_mem_release(message->allocator, message); + return AWS_OP_ERR; + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Sending SOCKS5 message, size=%zu", + (void *)handler, + message->message_data.len); + + + /* Send the message down the channel */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=static: s_send_socks5_message - Sending message of size %zu down channel in slot %p", + message->message_data.len, (void *)slot); + if (aws_channel_slot_send_message(slot, message, AWS_CHANNEL_DIR_WRITE)) { + aws_mem_release(message->allocator, message); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_send_socks5_message - Failed to send message down channel"); + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} + +/* Invoke the user's setup completed callback */ +/** + * Helper function to transition to error state and invoke callback + * + * This centralizes error handling for the SOCKS5 handler by: + * 1. Transitioning to error state with proper logging + * 2. Recording the error code + * 3. Invoking the setup callback with the error + * + */ +static void s_transition_to_error( + struct aws_socks5_channel_handler *handler, + int error_code) { + + if (!handler) { + return; + } + + /* Update the state */ + s_transition_state(handler, AWS_SOCKS5_CHANNEL_STATE_ERROR, error_code); + + /* Invoke the callback with the error */ + s_invoke_setup_callback_safely(handler, error_code); +} + +/* Start the connection timeout timer */ +/** + * Schedules a timeout task for the SOCKS5 handshake. + * + * This ensures that the SOCKS5 handshake doesn't hang indefinitely + * if the proxy server doesn't respond or if there are network issues. + * The timeout is based on the connect_timeout_ns value specified in + * the handler's configuration. + * + * @param handler The SOCKS5 channel handler + */ +static void s_schedule_timeout(struct aws_socks5_channel_handler *handler) { + /* Validate handler and channel availability */ + if (!handler) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_schedule_timeout called with NULL handler"); + return; + } + + if (!handler->slot || !handler->slot->channel) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Cannot schedule timeout - missing slot or channel", + (void *)handler); + return; + } + + /* Skip if timeout disabled or already scheduled */ + if (handler->connect_timeout_ns == 0) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 connection timeout disabled (connect_timeout_ns=0)", + (void *)handler); + return; + } + + if (handler->timeout_task_scheduled) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 timeout task already scheduled", + (void *)handler); + return; + } + + /* Get current time for scheduling */ + uint64_t now = 0; + if (aws_channel_current_clock_time(handler->slot->channel, &now)) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to get current time for timeout scheduling", + (void *)handler); + return; + } + + /* Calculate absolute timeout time */ + uint64_t timeout_time = now + handler->connect_timeout_ns; + + /* Log timeout details in milliseconds for readability */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Scheduling SOCKS5 timeout for %" PRIu64 " ms from now (current state: %s)", + (void *)handler, + handler->connect_timeout_ns / 1000000, /* Convert ns to ms for more readable logs */ + s_socks5_channel_state_to_string(handler->channel_state)); + + /* Initialize and schedule the timeout task */ + aws_channel_task_init( + &handler->timeout_task, + s_handle_timeout, + handler, + "socks5_channel_connect_timeout"); + + aws_channel_schedule_task_future( + handler->slot->channel, + &handler->timeout_task, + timeout_time); + + handler->timeout_task_scheduled = true; + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 timeout task scheduled for absolute time %" PRIu64, + (void *)handler, + timeout_time); +} + +/** + * Task function for safely forwarding data after the SOCKS5 handshake completes. + * + * This is used to ensure that application data received alongside the final SOCKS5 + * response is properly forwarded to the application after all handlers have been + * properly installed in the channel. + * + * @param task The channel task + * @param arg Context containing the slot and message to forward and allocator + * @param status Task status (cancelled or running) + */ +static void s_forward_pending_data_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) { + /* Check if task was cancelled */ + if (status == AWS_TASK_STATUS_CANCELED) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=static: s_forward_pending_data_task - Task was cancelled"); + return; + } + + /* Check for valid context */ + if (!arg) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_forward_pending_data_task - NULL context"); + return; + } + + /* Extract context */ + struct { + struct aws_channel_slot *slot; + struct aws_io_message *message; + struct aws_allocator *allocator; + } *forward_ctx = arg; + + /* Free the task immediately using the allocator from our context */ + struct aws_allocator *allocator = forward_ctx->allocator; + if (task && allocator) { + aws_mem_release(allocator, task); + } + + /* Ensure we have valid slot and message */ + if (!forward_ctx->slot || !forward_ctx->message) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_forward_pending_data_task - Invalid slot or message"); + + /* Free the context using the allocator we captured */ + if (allocator) { + aws_mem_release(allocator, forward_ctx); + } + return; + } + + /* Extract local copies for safety */ + struct aws_channel_slot *slot = forward_ctx->slot; + struct aws_io_message *message = forward_ctx->message; + + /* Ensure channel is still valid */ + if (!slot->channel) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_forward_pending_data_task - Channel no longer valid"); + + /* Clean up the message and context */ + aws_mem_release(message->allocator, message); + if (allocator) { + aws_mem_release(allocator, forward_ctx); + } + return; + } + + /* Forward the message up the channel */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Forwarding %zu bytes of application data after SOCKS5 handshake", + (void *)slot, + message->message_data.len); + + if (aws_channel_slot_send_message(slot, message, AWS_CHANNEL_DIR_READ)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to forward pending data, error=%d (%s)", + (void *)slot, + error_code, + aws_error_str(error_code)); + + /* Clean up the message if send failed */ + aws_mem_release(message->allocator, message); + } + + /* Clean up our context */ + if (allocator) { + aws_mem_release(allocator, forward_ctx); + } +} + +/** + * Handles the SOCKS5 connection timeout event. + * + * This function is called when the timeout task executes, indicating that + * the SOCKS5 handshake has taken too long. It fails the connection with + * a timeout error and invokes the setup callback to notify higher layers. + * + * This implementation includes better thread safety with explicit state checks + * to ensure the timeout isn't processed if it was cancelled or the state changed. + * + * @param task The timeout task + * @param arg Handler pointer passed as context (cast to aws_socks5_channel_handler) + * @param status The task status (may be cancelled) + */ +static void s_handle_timeout(struct aws_channel_task *task, void *arg, enum aws_task_status status) { + (void)task; + + /* Check if task was cancelled or has invalid arg */ + if (status == AWS_TASK_STATUS_CANCELED) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=static: s_handle_timeout - Task was cancelled"); + return; + } + + if (!arg) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: s_handle_timeout - NULL handler argument"); + return; + } + + struct aws_socks5_channel_handler *handler = arg; + + /* Critical atomic check - don't run if task was cancelled via flag */ + if (!handler->timeout_task_scheduled) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Timeout task cancelled before execution", + (void *)handler); + return; + } + + /* Clear the scheduled flag to prevent double execution */ + handler->timeout_task_scheduled = false; + + /* Log the timeout execution with handler state */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 timeout handler executed in state %s", + (void *)handler, + s_socks5_channel_state_to_string(handler->channel_state)); + + /* Check if timeout is still relevant */ + if (handler->channel_state == AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 timeout ignored - connection already established", + (void *)handler); + return; + } + + if (handler->channel_state == AWS_SOCKS5_CHANNEL_STATE_ERROR) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 timeout ignored - already in error state with code %d (%s)", + (void *)handler, + handler->error_code, + aws_error_str(handler->error_code)); + return; + } + + /* Connection timed out - log details of the current state */ + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 connection timed out after %" PRIu64 " ms in state %s", + (void *)handler, + handler->connect_timeout_ns / 1000000, /* Convert to ms for readable logs */ + s_socks5_channel_state_to_string(handler->channel_state)); + + /* Use transition_state for consistent state management and logging */ + s_transition_state(handler, AWS_SOCKS5_CHANNEL_STATE_ERROR, AWS_IO_SOCKET_TIMEOUT); + + /* Invoke the callback with timeout error */ + s_invoke_setup_callback_safely(handler, AWS_IO_SOCKET_TIMEOUT); +} + +/* Initialize the SOCKS5 handshake */ +static int s_start_socks5_handshake(struct aws_channel_handler *handler, struct aws_channel_slot *slot) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=static: s_start_socks5_handshake called with handler %p, slot %p", + (void*)handler, (void*)slot); + + if (!handler) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: s_start_socks5_handshake - NULL handler!"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (!slot) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: s_start_socks5_handshake - NULL slot! (This is a programming error)"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (!slot->channel) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: s_start_socks5_handshake - Slot has no channel! (This is a programming error)"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + if (!socks5_handler) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: s_start_socks5_handshake - Handler has no impl! (This is a programming error)"); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* Store the slot for future reference */ + handler->slot = slot; + socks5_handler->slot = slot; + + /* Validate target host before proceeding */ + struct aws_string *ctx_target_host = socks5_handler->context.endpoint_host; + + + if (ctx_target_host == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=%p: Cannot start handshake - SOCKS5 target host buffer is NULL!", (void *)handler); + + int error_code = AWS_ERROR_INVALID_STATE; + socks5_handler->channel_state = AWS_SOCKS5_CHANNEL_STATE_ERROR; + socks5_handler->error_code = error_code; + s_invoke_setup_callback_safely(socks5_handler, error_code); + return aws_raise_error(error_code); + } + + if (ctx_target_host->len == 0) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=%p: Cannot start handshake - SOCKS5 target host length is 0!", (void *)handler); + + int error_code = AWS_ERROR_INVALID_STATE; + socks5_handler->channel_state = AWS_SOCKS5_CHANNEL_STATE_ERROR; + socks5_handler->error_code = error_code; + s_invoke_setup_callback_safely(socks5_handler, error_code); + return aws_raise_error(error_code); + } + + /* Debug target host info */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 target host: '%.*s', port: %d", + (void *)handler, + (int)ctx_target_host->len, + (const char *)ctx_target_host->bytes, + socks5_handler->context.endpoint_port); + + /* Clear the buffer for sending the greeting */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Resetting send buffer before greeting (buffer=%p, capacity=%zu, len=%zu)", + (void *)handler, + (void*)socks5_handler->send_buffer.buffer, + socks5_handler->send_buffer.capacity, + socks5_handler->send_buffer.len); + aws_byte_buf_reset(&socks5_handler->send_buffer, false); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: After reset: send buffer state (buffer=%p, capacity=%zu, len=%zu)", + (void *)handler, + (void*)socks5_handler->send_buffer.buffer, + socks5_handler->send_buffer.capacity, + socks5_handler->send_buffer.len); + + /* Start the timeout timer */ + s_schedule_timeout(socks5_handler); + + /* Start processing incoming data */ + socks5_handler->process_incoming_data = true; + + /* Start with greeting state */ + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: Starting handshake by transitioning to GREETING state", (void *)handler); + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_GREETING, 0); + + /* Write SOCKS5 greeting message */ + if (aws_socks5_write_greeting(&socks5_handler->context, &socks5_handler->send_buffer)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to write SOCKS5 greeting, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + s_transition_to_error(socks5_handler, error_code); + return AWS_OP_ERR; + } + + /* Debug the greeting bytes */ + + /* Send the greeting message */ + if (s_send_socks5_message(handler, slot, &socks5_handler->send_buffer)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to send SOCKS5 greeting, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + s_transition_to_error(socks5_handler, error_code); + return AWS_OP_ERR; + } + + AWS_LOGF_INFO(AWS_LS_IO_SOCKS5, "id=%p: Started SOCKS5 handshake", (void *)handler); + return AWS_OP_SUCCESS; +} + +/** + * Processes the SOCKS5 greeting response from the proxy server. + * + * This function handles the server's response to our initial greeting, + * which includes the authentication method selected by the server. + * Based on this response, we either proceed to authentication or + * directly to the connect phase. + * + * @param handler The SOCKS5 channel handler + * @param slot The channel slot this handler belongs to + * @param data Cursor pointing to the response data + * @return AWS_OP_SUCCESS if processing succeeded, AWS_OP_ERR otherwise + */ +static int s_process_greeting_response( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_byte_cursor *data) { + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + + /* Log greeting response data for debugging (limited bytes for security) */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Processing SOCKS5 greeting response of size %zu bytes", + (void *)handler, + data->len); + + /* Process the greeting response */ + uint64_t start_time = 0; + if (socks5_handler->slot && socks5_handler->slot->channel) { + aws_channel_current_clock_time(socks5_handler->slot->channel, &start_time); + } + + if (aws_socks5_read_greeting_response(&socks5_handler->context, data)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to process SOCKS5 greeting response, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + s_transition_to_error(socks5_handler, error_code); + return AWS_OP_ERR; + } + + /* Log success and selected authentication method */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 greeting response processed successfully, selected auth method: %d", + (void *)handler, + socks5_handler->context.selected_auth); + + uint64_t end_time = 0; + if (socks5_handler->slot && socks5_handler->slot->channel) { + aws_channel_current_clock_time(socks5_handler->slot->channel, &end_time); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Greeting response processing took %" PRIu64 "ns", + (void *)handler, + end_time - start_time); + } + + /* Reset and ensure buffer capacity for next message */ + if (s_reset_and_ensure_buffer(&socks5_handler->send_buffer, socks5_handler->allocator, 256)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to reset send buffer, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + s_transition_to_error(socks5_handler, error_code); + return AWS_OP_ERR; + } + + /* Check if authentication is needed */ + if (socks5_handler->context.selected_auth == AWS_SOCKS5_AUTH_NONE) { + /* No auth needed, proceed to connect phase */ + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_CONNECT, 0); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: No authentication needed, proceeding to connect phase", + (void *)handler); + + /* Send connect request */ + if (aws_socks5_write_connect_request(&socks5_handler->context, &socks5_handler->send_buffer)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to write SOCKS5 connect request, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + s_transition_to_error(socks5_handler, error_code); + return AWS_OP_ERR; + } + + /* Send the connect request message */ + if (s_send_socks5_message(handler, slot, &socks5_handler->send_buffer)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to send SOCKS5 connect request, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + s_transition_to_error(socks5_handler, error_code); + return AWS_OP_ERR; + } + } else { + /* Authentication needed */ + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_AUTH, 0); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Authentication required, sending auth request", + (void *)handler); + + /* Prepare auth request */ + if (aws_socks5_write_auth_request(&socks5_handler->context, &socks5_handler->send_buffer)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to write SOCKS5 auth request, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + socks5_handler->channel_state = AWS_SOCKS5_CHANNEL_STATE_ERROR; + socks5_handler->error_code = error_code; + s_invoke_setup_callback_safely(socks5_handler, error_code); + return AWS_OP_ERR; + } + + /* Send the auth request message */ + if (s_send_socks5_message(handler, slot, &socks5_handler->send_buffer)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to send SOCKS5 auth request, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + s_transition_to_error(socks5_handler, error_code); + return AWS_OP_ERR; + } + } + + return AWS_OP_SUCCESS; +} + +/** + * Processes the SOCKS5 authentication response from the proxy server. + * + * After sending authentication credentials to the proxy server, this function + * handles the server's response. If authentication succeeds, we proceed to + * the connect phase to establish the connection to the target server. + * + * @param handler The SOCKS5 channel handler + * @param slot The channel slot this handler belongs to + * @param data Cursor pointing to the response data + * @return AWS_OP_SUCCESS if processing succeeded, AWS_OP_ERR otherwise + */ +static int s_process_auth_response( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_byte_cursor *data) { + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + + /* Log authentication response data (limited bytes for security) */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Processing SOCKS5 authentication response of size %zu bytes", + (void *)handler, + data->len); + + /* Process the authentication response */ + uint64_t start_time = 0; + if (socks5_handler->slot && socks5_handler->slot->channel) { + aws_channel_current_clock_time(socks5_handler->slot->channel, &start_time); + } + + if (aws_socks5_read_auth_response(&socks5_handler->context, data)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 authentication failed, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + /* Use transition_state for consistency and better logging */ + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_ERROR, error_code); + s_invoke_setup_callback_safely(socks5_handler, error_code); + return AWS_OP_ERR; + } + + /* Log successful authentication */ + AWS_LOGF_INFO( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 authentication successful", + (void *)handler); + + uint64_t end_time = 0; + if (socks5_handler->slot && socks5_handler->slot->channel) { + aws_channel_current_clock_time(socks5_handler->slot->channel, &end_time); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Auth response processing took %" PRIu64 "ns", + (void *)handler, + end_time - start_time); + } + + /* Clear the buffer for connect request */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Resetting send buffer after auth response (buffer=%p, capacity=%zu, len=%zu)", + (void *)handler, + (void*)socks5_handler->send_buffer.buffer, + socks5_handler->send_buffer.capacity, + socks5_handler->send_buffer.len); + aws_byte_buf_reset(&socks5_handler->send_buffer, false); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: After reset: send buffer state (buffer=%p, capacity=%zu, len=%zu)", + (void *)handler, + (void*)socks5_handler->send_buffer.buffer, + socks5_handler->send_buffer.capacity, + socks5_handler->send_buffer.len); + + /* Authentication successful, proceed to connect phase */ + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_CONNECT, 0); + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Authentication successful, proceeding to connect phase", + (void *)handler); + + /* Send connect request */ + if (aws_socks5_write_connect_request(&socks5_handler->context, &socks5_handler->send_buffer)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to write SOCKS5 connect request, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + socks5_handler->channel_state = AWS_SOCKS5_CHANNEL_STATE_ERROR; + socks5_handler->error_code = error_code; + s_invoke_setup_callback_safely(socks5_handler, error_code); + return AWS_OP_ERR; + } + + /* Send the connect request message */ + if (s_send_socks5_message(handler, slot, &socks5_handler->send_buffer)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to send SOCKS5 connect request, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + socks5_handler->channel_state = AWS_SOCKS5_CHANNEL_STATE_ERROR; + socks5_handler->error_code = error_code; + s_invoke_setup_callback_safely(socks5_handler, error_code); + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} + +/** + * Processes the SOCKS5 connection response from the proxy server. + * + * After requesting a connection to the target server, this function + * handles the proxy's response. If successful, it transitions the handler + * to ESTABLISHED state and invokes the setup callback to notify higher + * layers that the connection is ready for use. + * + * This is the final step in the SOCKS5 handshake process. + * + * @param handler The SOCKS5 channel handler + * @param slot The channel slot this handler belongs to + * @param data Cursor pointing to the response data + * @return AWS_OP_SUCCESS if processing succeeded, AWS_OP_ERR otherwise + */ +static int s_process_connect_response( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_byte_cursor *data) { + + (void)slot; + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + uint64_t start_time = 0; + + if (socks5_handler->slot && socks5_handler->slot->channel) { + aws_channel_current_clock_time(socks5_handler->slot->channel, &start_time); + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Processing SOCKS5 connect response of size %zu bytes", + (void *)handler, + data->len); + + /* Log buffer details for comprehensive diagnostics */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: CONNECT phase - read_buffer details (buffer=%p, capacity=%zu, len=%zu)", + (void *)handler, + (void*)socks5_handler->read_buffer.buffer, + socks5_handler->read_buffer.capacity, + socks5_handler->read_buffer.len); + + /* Validate response format before processing */ + if (data->len < 4) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 CONNECT response format invalid - too short (%zu bytes, minimum 4 required)", + (void *)handler, + data->len); + + /* Use transition_state for consistent error handling */ + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_ERROR, AWS_ERROR_INVALID_ARGUMENT); + s_invoke_setup_callback_safely(socks5_handler, AWS_ERROR_INVALID_ARGUMENT); + return AWS_OP_ERR; + } + + /* Log response version and status code for debugging */ + if (data->len >= 2) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 CONNECT response - version=0x%02x, status=0x%02x", + (void *)handler, + data->ptr[0], + data->ptr[1]); + } + + /* Process the connection response */ + if (aws_socks5_read_connect_response(&socks5_handler->context, data)) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 connection request failed, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + /* If we have status code information, log it for diagnostics */ + if (data->len >= 2) { + uint8_t status = data->ptr[1]; + const char* status_str = "Unknown"; + + /* Convert SOCKS5 status codes to readable strings */ + switch(status) { + case 0: status_str = "Success"; break; + case 1: status_str = "General failure"; break; + case 2: status_str = "Connection not allowed"; break; + case 3: status_str = "Network unreachable"; break; + case 4: status_str = "Host unreachable"; break; + case 5: status_str = "Connection refused"; break; + case 6: status_str = "TTL expired"; break; + case 7: status_str = "Command not supported"; break; + case 8: status_str = "Address type not supported"; break; + } + + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 server returned status code %d (%s)", + (void *)handler, + status, + status_str); + } + + /* Use transition_state for consistent error handling */ + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_ERROR, error_code); + s_invoke_setup_callback_safely(socks5_handler, error_code); + return AWS_OP_ERR; + } + + /* Log successful connection with target details if available */ + struct aws_string * ctx_target_host_log = + socks5_handler->context.endpoint_host; + AWS_LOGF_INFO( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 connection to target host '%.*s:%d' established successfully", + (void *)handler, + (int)ctx_target_host_log->len, + (const char *)ctx_target_host_log->bytes, + socks5_handler->context.endpoint_port); + + /* Calculate handshake duration for performance metrics */ + uint64_t end_time = 0; + if (socks5_handler->slot && socks5_handler->slot->channel) { + aws_channel_current_clock_time(socks5_handler->slot->channel, &end_time); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 connect response processing took %" PRIu64 "ns", + (void *)handler, + end_time - start_time); + } + + /* Connection established, transition to established state */ + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED, 0); + + /* Cancel any pending timeout task */ + if (socks5_handler->timeout_task_scheduled) { + /* The task will remain in the event loop's task queue, but when it runs, + it will check if we're in ESTABLISHED state and do nothing */ + socks5_handler->timeout_task_scheduled = false; + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Connection established before timeout occurred, canceling timeout task", + (void *)handler); + } + + /* Debug handler setup callback and user data */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Callback %p, User data %p", + (void *)handler, + (void *)(uintptr_t)socks5_handler->on_setup_completed, + socks5_handler->user_data); + + /* Check for composite context in user_data */ + if (socks5_handler->user_data != NULL) { + struct { + struct aws_socks5_proxy_options *socks5_options; + void *original_user_data; + } *composite_ctx = socks5_handler->user_data; + + /* If this looks like our composite context, print details */ + if (composite_ctx->socks5_options != NULL && + composite_ctx->original_user_data != NULL) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Found composite context: socks5_options=%p, original_user_data=%p", + (void *)handler, + (void*)composite_ctx->socks5_options, + composite_ctx->original_user_data); + } + } + + /* Invoke the user callback with success */ + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: Invoking setup callback with success status", (void *)handler); + s_invoke_setup_callback_safely(socks5_handler, AWS_OP_SUCCESS); + + return AWS_OP_SUCCESS; +} + +/* Process incoming data during handshake */ +static void s_process_read_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_io_message *message) { + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: s_process_read_message called with message size %zu", + (void *)handler, + message->message_data.len); + + /* Debug raw message data */ + if (message->message_data.len > 0) { + } + + if (!handler || !slot || !message) { + return; /* Nothing we can do without valid parameters */ + } + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + + if (!socks5_handler) { + return; /* Can't process without a valid handler context */ + } + + /* Add the message data to our read buffer */ + struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data); + + /* Check if the read buffer is in a valid state */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Read buffer state check - buffer=%p, capacity=%zu, len=%zu, allocator=%p", + (void *)handler, + (void*)socks5_handler->read_buffer.buffer, + socks5_handler->read_buffer.capacity, + socks5_handler->read_buffer.len, + (void*)socks5_handler->read_buffer.allocator); + + /* Fail immediately if the buffer is NULL */ + if (socks5_handler->read_buffer.buffer == NULL) { + + int error_code = AWS_ERROR_INVALID_STATE; + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: CRITICAL ERROR - read_buffer.buffer is NULL! Cannot append data", + (void *)handler); + + socks5_handler->channel_state = AWS_SOCKS5_CHANNEL_STATE_ERROR; + socks5_handler->error_code = error_code; + s_invoke_setup_callback_safely(socks5_handler, error_code); + aws_mem_release(message->allocator, message); + return; + } + + /* Ensure buffer has sufficient capacity */ + size_t needed_capacity = socks5_handler->read_buffer.len + message_cursor.len; + if (s_ensure_buffer_capacity(&socks5_handler->read_buffer, socks5_handler->allocator, needed_capacity)) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to ensure read buffer capacity", + (void *)handler); + return; + } + + if (aws_byte_buf_append( + &socks5_handler->read_buffer, + &message_cursor)) { + + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to append to read buffer, error=%d (%s)", + (void *)handler, + error_code, + aws_error_str(error_code)); + + socks5_handler->channel_state = AWS_SOCKS5_CHANNEL_STATE_ERROR; + socks5_handler->error_code = error_code; + s_invoke_setup_callback_safely(socks5_handler, error_code); + aws_mem_release(message->allocator, message); + return; + } + + /* We've consumed the message, so we can release it */ + aws_mem_release(message->allocator, message); + + /* Process the data based on the current state */ + struct aws_byte_cursor data = aws_byte_cursor_from_buf(&socks5_handler->read_buffer); + int result = AWS_OP_SUCCESS; + + switch (socks5_handler->channel_state) { + case AWS_SOCKS5_CHANNEL_STATE_GREETING: + if (data.len >= AWS_SOCKS5_GREETING_RESP_SIZE) { + result = s_process_greeting_response(handler, slot, &data); + + /* Consume the processed data using memmove instead of temp buffer */ + size_t remaining_size = socks5_handler->read_buffer.len - AWS_SOCKS5_GREETING_RESP_SIZE; + if (remaining_size > 0) { + /* Shift the remaining data to the beginning of the buffer */ + memmove(socks5_handler->read_buffer.buffer, + socks5_handler->read_buffer.buffer + AWS_SOCKS5_GREETING_RESP_SIZE, + remaining_size); + } + /* Update the buffer length */ + socks5_handler->read_buffer.len = remaining_size; + } + break; + + case AWS_SOCKS5_CHANNEL_STATE_AUTH: + if (data.len >= AWS_SOCKS5_AUTH_RESP_SIZE) { + result = s_process_auth_response(handler, slot, &data); + + /* Consume the processed data using memmove instead of temp buffer */ + size_t remaining_size = socks5_handler->read_buffer.len - AWS_SOCKS5_AUTH_RESP_SIZE; + if (remaining_size > 0) { + /* Shift the remaining data to the beginning of the buffer */ + memmove(socks5_handler->read_buffer.buffer, + socks5_handler->read_buffer.buffer + AWS_SOCKS5_AUTH_RESP_SIZE, + remaining_size); + } + /* Update the buffer length */ + socks5_handler->read_buffer.len = remaining_size; + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: AUTH state - After memmove: read_buffer (buffer=%p, capacity=%zu, len=%zu)", + (void *)handler, + (void*)socks5_handler->read_buffer.buffer, + socks5_handler->read_buffer.capacity, + socks5_handler->read_buffer.len); + } + break; + + case AWS_SOCKS5_CHANNEL_STATE_CONNECT: + /* For connect response, we need to parse the first few bytes to determine size */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Processing CONNECT response, data length %zu", + (void *)handler, + data.len); + + /* Dump the response bytes for debugging */ + if (data.len > 0) { + } + + if (data.len >= 4) { /* At least VER(1) + REP(1) + RSV(1) + ATYP(1) */ + uint8_t ver = data.ptr[0]; + uint8_t rep = data.ptr[1]; + uint8_t atype = data.ptr[3]; + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 response - VER: %d, REP: %d, ATYP: %d", + (void *)handler, + ver, rep, atype); + + size_t addr_size = 0; + + /* Determine the address size based on the address type */ + switch (atype) { + case AWS_SOCKS5_ATYP_DOMAIN: + if (data.len >= 5) { /* Check if we can read the domain length byte */ + uint8_t dom_len = data.ptr[4]; + addr_size = 1 + dom_len; /* Length byte + domain */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Domain address type with length %u", + (void *)handler, + dom_len); + } else { + /* Wait for more data */ + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Waiting for more data to read domain length", + (void *)handler); + return; + } + break; + + case AWS_SOCKS5_ATYP_IPV4: + addr_size = 4; + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: IPv4 address type", (void *)handler); + break; + + case AWS_SOCKS5_ATYP_IPV6: + addr_size = 16; + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: IPv6 address type", (void *)handler); + break; + + default: + AWS_LOGF_WARN( + AWS_LS_IO_SOCKS5, + "id=%p: Unknown address type: %d", + (void *)handler, + atype); + /* Unknown address type, try to proceed with minimal parsing */ + addr_size = 1; + break; + } + + /* Calculate the full response size */ + size_t response_size = 4 + addr_size + 2; /* Header + address + port */ + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Expected response size: %zu, current data size: %zu", + (void *)handler, + response_size, data.len); + + /* Check if we have the complete response */ + if (addr_size > 0 && data.len >= response_size) { + result = s_process_connect_response(handler, slot, &data); + + /* If successful, we don't need to shift the buffer because + we're now in established state and will forward any remaining data */ + if (result == AWS_OP_SUCCESS && + socks5_handler->channel_state == AWS_SOCKS5_CHANNEL_STATE_ESTABLISHED) { + + /* Calculate the size of the SOCKS5 response */ + size_t response_size = 4 + addr_size + 2; /* Header + address + port */ + + /* If there's more data after the SOCKS5 response, we need to forward it */ + if (socks5_handler->read_buffer.len > response_size) { + AWS_LOGF_INFO( + AWS_LS_IO_SOCKS5, + "id=%p: Found %zu bytes of trailing data after SOCKS5 handshake completion", + (void *)handler, + socks5_handler->read_buffer.len - response_size); + + /* Create a new message with the remaining data for forwarding */ + struct aws_io_message *forward_message = aws_channel_acquire_message_from_pool( + slot->channel, + AWS_IO_MESSAGE_APPLICATION_DATA, + socks5_handler->read_buffer.len - response_size); + + if (forward_message) { + /* Copy the remaining data after the SOCKS5 response */ + if (aws_byte_buf_write( + &forward_message->message_data, + socks5_handler->read_buffer.buffer + response_size, + socks5_handler->read_buffer.len - response_size)) { + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Forwarding %zu bytes of application data received alongside final SOCKS5 response", + (void *)handler, + forward_message->message_data.len); + + /* We need to delay sending this message until after the setup callback + * completes to ensure higher layer handlers are properly installed */ + + /* Schedule a task to forward the data after the current event loop iteration */ + struct aws_channel_task *forward_task = aws_mem_calloc( + socks5_handler->allocator, 1, sizeof(struct aws_channel_task)); + + if (forward_task) { + struct { + struct aws_channel_slot *slot; + struct aws_io_message *message; + struct aws_allocator *allocator; + } *forward_ctx = aws_mem_calloc( + socks5_handler->allocator, 1, sizeof(*forward_ctx)); + + if (forward_ctx) { + forward_ctx->slot = slot; + forward_ctx->message = forward_message; + forward_ctx->allocator = socks5_handler->allocator; + + aws_channel_task_init( + forward_task, + s_forward_pending_data_task, + forward_ctx, + "socks5_forward_pending_data"); + + aws_channel_schedule_task_now(slot->channel, forward_task); + + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKS5, + "id=%p: Scheduled task to forward pending data", + (void *)handler); + } else { + aws_mem_release(socks5_handler->allocator, forward_task); + aws_mem_release(forward_message->allocator, forward_message); + } + } else { + aws_mem_release(forward_message->allocator, forward_message); + } + } else { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to write remaining data to forward message", + (void *)handler); + aws_mem_release(forward_message->allocator, forward_message); + } + } else { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to acquire message for forwarding remaining data", + (void *)handler); + } + + /* Reset the buffer now that we've handled the remaining data */ + socks5_handler->read_buffer.len = 0; + } else { + /* No remaining data, simply reset the buffer length to 0 but keep the capacity */ + socks5_handler->read_buffer.len = 0; + } + } + } + } + break; + + default: + /* In any other state, do nothing with the data */ + break; + } + + if (result != AWS_OP_SUCCESS) { + /* An error occurred while processing the message */ + socks5_handler->channel_state = AWS_SOCKS5_CHANNEL_STATE_ERROR; + socks5_handler->error_code = aws_last_error(); + } +} + +/* Public API functions */ + +struct aws_channel_handler *aws_socks5_channel_handler_new( + struct aws_allocator *allocator, + const struct aws_socks5_proxy_options *proxy_options, + struct aws_byte_cursor endpoint_host, + uint16_t endpoint_port, + enum aws_socks5_address_type endpoint_address_type, + aws_channel_on_setup_completed_fn *on_setup_completed, + void *user_data) { + + + AWS_ASSERT(allocator); + AWS_ASSERT(proxy_options); + + if (!allocator) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + if (!proxy_options) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + if (!endpoint_host.ptr || endpoint_host.len == 0) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_socks5_channel_handler *socks5_handler = aws_mem_calloc( + allocator, 1, sizeof(struct aws_socks5_channel_handler)); + + if (!socks5_handler) { + return NULL; + } + + AWS_ZERO_STRUCT(*socks5_handler); + socks5_handler->allocator = allocator; + + /* Initialize the SOCKS5 context */ + if (aws_socks5_context_init( + &socks5_handler->context, + allocator, + proxy_options, + endpoint_host, + endpoint_port, + endpoint_address_type)) { + goto on_error; + } + + /* Initialize the handler */ + socks5_handler->handler.impl = socks5_handler; + socks5_handler->handler.vtable = &s_socks5_handler_vtable; + socks5_handler->on_setup_completed = on_setup_completed; + socks5_handler->user_data = user_data; + s_transition_state(socks5_handler, AWS_SOCKS5_CHANNEL_STATE_INIT, 0); + socks5_handler->process_incoming_data = false; + + /* Initialize send buffer */ + if (aws_byte_buf_init(&socks5_handler->send_buffer, allocator, 256)) { + goto on_error; + } + + /* Initialize read buffer */ + if (aws_byte_buf_init(&socks5_handler->read_buffer, allocator, 256)) { + goto on_error; + } + + /* Set the connection timeout */ + socks5_handler->connect_timeout_ns = + aws_timestamp_convert(proxy_options->connection_timeout_ms, AWS_TIMESTAMP_MILLIS, AWS_TIMESTAMP_NANOS, NULL); + + return &socks5_handler->handler; + +on_error: + s_socks5_handler_destroy(&socks5_handler->handler); + return NULL; +} + +/** + * Custom TLS negotiation result callback that chains to the original callback + * and then calls the setup callback with the final result. + * + * This function is critical for SOCKS5+TLS integration as it ensures proper + * callback chaining and resource cleanup after TLS negotiation completes. + */ +static void s_release_bootstrap_resources(struct aws_socks5_bootstrap *bootstrap); + +static void s_socks5_tls_on_negotiation_result( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + int error_code, + void *user_data) { + + struct aws_socks5_bootstrap *socks5_bootstrap = (struct aws_socks5_bootstrap *)user_data; + + if (!socks5_bootstrap) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=static: TLS negotiation callback called with NULL bootstrap"); + return; + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: TLS negotiation completed with result %d (%s)", + (void *)socks5_bootstrap, + error_code, + aws_error_str(error_code)); + + /* Make local copies of all values we need, since bootstrap might be freed */ + struct aws_client_bootstrap *client_bootstrap = socks5_bootstrap->bootstrap; + void *callback_user_data = socks5_bootstrap->user_data; + aws_client_bootstrap_on_channel_event_fn *setup_callback = socks5_bootstrap->setup_callback; + aws_tls_on_negotiation_result_fn *original_on_negotiation_result = socks5_bootstrap->original_on_negotiation_result; + void *original_tls_user_data = socks5_bootstrap->original_tls_user_data; + + /* First call the original TLS negotiation callback if set */ + if (original_on_negotiation_result) { + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Calling original TLS negotiation callback %p with user data %p", + (void *)socks5_bootstrap, + (void *)(uintptr_t)original_on_negotiation_result, + original_tls_user_data); + + original_on_negotiation_result(handler, slot, error_code, original_tls_user_data); + } + + /* Always call the setup callback regardless of success/failure to ensure proper completion */ + if (setup_callback) { + if (error_code != AWS_ERROR_SUCCESS) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: TLS negotiation failed with error %d (%s), notifying client", + (void *)socks5_bootstrap, + error_code, + aws_error_str(error_code)); + + /* For failures, pass NULL channel to indicate connection failed */ + setup_callback(client_bootstrap, error_code, NULL, callback_user_data); + } else { + AWS_LOGF_INFO( + AWS_LS_IO_SOCKS5, + "id=%p: TLS negotiation successful, calling setup callback to complete connection", + (void *)socks5_bootstrap); + + setup_callback(client_bootstrap, AWS_ERROR_SUCCESS, slot->channel, callback_user_data); + } + } + + /* Release resources but keep bootstrap alive for the shutdown callback */ + s_release_bootstrap_resources(socks5_bootstrap); +} + +/** + * Helper function to install a TLS handler after SOCKS5 setup completes successfully. + * + * This function extracts the TLS handler installation logic from s_on_socks5_setup_completed + * to make the code more maintainable. + */ +static int s_install_tls_handler_after_socks5( + struct aws_channel *channel, + struct aws_socks5_bootstrap *bootstrap) { + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Installing TLS handler after SOCKS5 handshake", + (void *)bootstrap); + + /* Set our custom TLS negotiation result callback */ + bootstrap->tls_options->on_negotiation_result = s_socks5_tls_on_negotiation_result; + bootstrap->tls_options->user_data = bootstrap; + + /* Set up TLS handler */ + struct aws_channel_slot *tls_slot = aws_channel_slot_new(channel); + if (!tls_slot) { + int err_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to create TLS slot, error=%d (%s)", + (void *)bootstrap, + err_code, + aws_error_str(err_code)); + return err_code; + } + + /* Create TLS handler using stored TLS options */ + struct aws_channel_handler *tls_handler = aws_tls_client_handler_new( + bootstrap->allocator, bootstrap->tls_options, tls_slot); + + if (!tls_handler) { + int err_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to create TLS handler, error=%d (%s)", + (void *)bootstrap, + err_code, + aws_error_str(err_code)); + + aws_channel_slot_remove(tls_slot); + return err_code; + } + + /* Add TLS handler to channel */ + aws_channel_slot_insert_end(channel, tls_slot); + + if (aws_channel_slot_set_handler(tls_slot, tls_handler)) { + int err_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to set TLS handler on slot, error=%d (%s)", + (void *)bootstrap, + err_code, + aws_error_str(err_code)); + return err_code; + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Starting TLS negotiation after SOCKS5 handshake", + (void *)bootstrap); + + /* Start TLS negotiation - NOW it's safe to begin TLS handshake + * since SOCKS5 tunnel is established */ + if (aws_tls_client_handler_start_negotiation(tls_handler)) { + int err_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to start TLS negotiation, error=%d (%s)", + (void *)bootstrap, + err_code, + aws_error_str(err_code)); + return err_code; + } + + AWS_LOGF_INFO( + AWS_LS_IO_SOCKS5, + "id=%p: TLS handler installed and negotiation started after SOCKS5 handshake", + (void *)bootstrap); + + return AWS_OP_SUCCESS; +} + +/** + * Releases dynamically allocated members held by the bootstrap without freeing the struct itself. + * This allows the bootstrap wrapper to remain alive for shutdown callbacks while avoiding leaks. + */ +static void s_release_bootstrap_resources(struct aws_socks5_bootstrap *bootstrap) { + if (!bootstrap) { + return; + } + + struct aws_allocator *allocator = bootstrap->allocator; + + /* Clean up TLS options if present */ + if (bootstrap->tls_options) { + aws_tls_connection_options_clean_up(bootstrap->tls_options); + aws_mem_release(allocator, bootstrap->tls_options); + bootstrap->tls_options = NULL; + bootstrap->use_tls = false; + bootstrap->original_on_negotiation_result = NULL; + bootstrap->original_tls_user_data = NULL; + } + + /* Clean up SOCKS5 options if present */ + if (bootstrap->socks5_proxy_options) { + aws_socks5_proxy_options_clean_up(bootstrap->socks5_proxy_options); + aws_mem_release(allocator, bootstrap->socks5_proxy_options); + bootstrap->socks5_proxy_options = NULL; + } + + if (bootstrap->pending_channel) { + aws_channel_release_hold(bootstrap->pending_channel); + bootstrap->pending_channel = NULL; + } + + if (bootstrap->endpoint_host) { + aws_string_destroy(bootstrap->endpoint_host); + bootstrap->endpoint_host = NULL; + } + + if (bootstrap->original_endpoint_host) { + aws_string_destroy(bootstrap->original_endpoint_host); + bootstrap->original_endpoint_host = NULL; + } + + bootstrap->endpoint_ready = false; + bootstrap->resolution_in_progress = false; + bootstrap->resolution_error_code = AWS_ERROR_SUCCESS; + bootstrap->resolution_task_scheduled = false; + bootstrap->resolution_failure_task_scheduled = false; +} + +/** + * Helper function to clean up a bootstrap structure and its associated resources + */ +static void s_destroy_bootstrap(struct aws_socks5_bootstrap *bootstrap) { + if (!bootstrap) { + return; + } + + s_release_bootstrap_resources(bootstrap); + aws_mutex_clean_up(&bootstrap->lock); + aws_mem_release(bootstrap->allocator, bootstrap); +} + +static void s_cleanup_bootstrap(struct aws_socks5_bootstrap *bootstrap) { + if (!bootstrap) { + return; + } + + s_release_bootstrap_resources(bootstrap); + + bool defer_cleanup = false; + + aws_mutex_lock(&bootstrap->lock); + if (bootstrap->resolution_in_progress) { + /* Defer destruction until the resolver callback runs so it can drain outstanding tasks without touching freed memory */ + bootstrap->cleanup_pending = true; + defer_cleanup = true; + } else { + bootstrap->cleanup_pending = false; + } + aws_mutex_unlock(&bootstrap->lock); + + if (defer_cleanup) { + return; + } + + s_destroy_bootstrap(bootstrap); +} + +/** + * Called when the SOCKS5 handshake completes. + * If TLS is requested, this function will install the TLS handler. + * Otherwise, it will call the setup callback directly. + */ +static void s_on_socks5_setup_completed( + struct aws_channel *channel, + int error_code, + void *user_data) +{ + struct aws_socks5_bootstrap *bootstrap = (struct aws_socks5_bootstrap *)user_data; + + if (!bootstrap) { + AWS_LOGF_ERROR(AWS_LS_IO_SOCKS5, "id=static: s_on_socks5_setup_completed called with NULL bootstrap"); + return; + } + + /* Make a local copy of the data we need in case bootstrap gets freed */ + struct aws_client_bootstrap *client_bootstrap = bootstrap->bootstrap; + void *callback_user_data = bootstrap->user_data; + aws_client_bootstrap_on_channel_event_fn *setup_callback = bootstrap->setup_callback; + + if (error_code != AWS_ERROR_SUCCESS) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 handshake failed with error_code=%d (%s)", + (void *)bootstrap, + error_code, + aws_error_str(error_code)); + + /* Call the original callback with the error */ + if (setup_callback) { + setup_callback(client_bootstrap, error_code, NULL, callback_user_data); + } + + s_release_bootstrap_resources(bootstrap); + return; + } + + /* SOCKS5 handshake successful */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: SOCKS5 handshake completed successfully", + (void *)bootstrap); + + /* If TLS is requested, install TLS handler now that SOCKS5 is established */ + if (bootstrap->use_tls && bootstrap->tls_options) { + int result = s_install_tls_handler_after_socks5(channel, bootstrap); + if (result != AWS_OP_SUCCESS) { + /* Failed to install TLS handler, call setup callback with error */ + if (setup_callback) { + setup_callback(client_bootstrap, result, NULL, callback_user_data); + } + + s_release_bootstrap_resources(bootstrap); + } + /* Note: bootstrap is NOT cleaned up here on success, as that will be done + * in the TLS negotiation result callback */ + } else { + /* No TLS needed, call the original setup callback directly */ + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: No TLS requested, calling original callback", + (void *)bootstrap); + + if (setup_callback) { + setup_callback(client_bootstrap, AWS_ERROR_SUCCESS, channel, callback_user_data); + } + + s_release_bootstrap_resources(bootstrap); + } +} + +static void s_socks5_socket_channel_setup( + struct aws_client_bootstrap *bootstrap, + int error_code, + struct aws_channel *channel, + void *user_data) +{ + struct aws_socks5_bootstrap *socks5_bootstrap = (struct aws_socks5_bootstrap *)user_data; + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: Context=%p", (void *)channel, (void*)socks5_bootstrap); + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: setup_callback=%p", (void *)channel, (void *)(uintptr_t)socks5_bootstrap->setup_callback); + AWS_LOGF_TRACE(AWS_LS_IO_SOCKS5, "id=%p: original_user_data=%p", (void *)channel, (void*)socks5_bootstrap->user_data); + + if (error_code != AWS_ERROR_SUCCESS || channel == NULL) { + if (socks5_bootstrap->setup_callback) { + socks5_bootstrap->setup_callback(bootstrap, error_code, NULL, socks5_bootstrap->user_data); + } + if (channel == NULL) { + s_cleanup_bootstrap(socks5_bootstrap); + } else { + s_release_bootstrap_resources(socks5_bootstrap); + } + return; + } + + bool endpoint_ready = false; + bool resolution_in_progress = false; + int resolution_error = AWS_ERROR_SUCCESS; + + aws_mutex_lock(&socks5_bootstrap->lock); + endpoint_ready = socks5_bootstrap->endpoint_ready; + resolution_in_progress = socks5_bootstrap->resolution_in_progress; + resolution_error = socks5_bootstrap->resolution_error_code; + + if (!endpoint_ready && resolution_error == AWS_ERROR_SUCCESS && resolution_in_progress) { + /* DNS still running: hold the channel so the callback can resume the handshake later */ + if (!socks5_bootstrap->pending_channel) { + socks5_bootstrap->pending_channel = channel; + aws_channel_acquire_hold(channel); + } + aws_mutex_unlock(&socks5_bootstrap->lock); + return; + } + + if (error_code != AWS_ERROR_SUCCESS) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Client-side resolution failed for '%s' with error %d (%s)", + (void *)socks5_bootstrap, + socks5_bootstrap->original_endpoint_host + ? aws_string_c_str(socks5_bootstrap->original_endpoint_host) + : "(null)", + error_code, + aws_error_str(error_code)); + } + + aws_mutex_unlock(&socks5_bootstrap->lock); + + if (resolution_error != AWS_ERROR_SUCCESS) { + if (socks5_bootstrap->setup_callback) { + socks5_bootstrap->setup_callback(bootstrap, resolution_error, NULL, socks5_bootstrap->user_data); + } + aws_channel_shutdown(channel, resolution_error); + s_release_bootstrap_resources(socks5_bootstrap); + return; + } + + if (!endpoint_ready) { + int err_code = AWS_ERROR_INVALID_STATE; + if (socks5_bootstrap->setup_callback) { + socks5_bootstrap->setup_callback(bootstrap, err_code, NULL, socks5_bootstrap->user_data); + } + aws_channel_shutdown(channel, err_code); + s_release_bootstrap_resources(socks5_bootstrap); + return; + } + + if (s_socks5_bootstrap_begin_handshake(socks5_bootstrap, channel)) { + int err_code = aws_last_error(); + if (socks5_bootstrap->setup_callback) { + socks5_bootstrap->setup_callback(bootstrap, err_code, NULL, socks5_bootstrap->user_data); + } + s_release_bootstrap_resources(socks5_bootstrap); + return; + } + /* At this point, the SOCKS5 handler will invoke the setup callback. Final cleanup happens during shutdown. */ +} + +static int s_socks5_bootstrap_begin_handshake( + struct aws_socks5_bootstrap *socks5_bootstrap, + struct aws_channel *channel) { + + if (!socks5_bootstrap || !channel) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_channel_slot *slot = aws_channel_get_first_slot(channel); + if (!slot) { + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + + struct aws_byte_cursor endpoint_host_cursor = aws_byte_cursor_from_array( + socks5_bootstrap->endpoint_host ? aws_string_bytes(socks5_bootstrap->endpoint_host) : NULL, + socks5_bootstrap->endpoint_host ? socks5_bootstrap->endpoint_host->len : 0); + + if (endpoint_host_cursor.len == 0 || endpoint_host_cursor.ptr == NULL) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_channel_handler *socks5_handler = aws_socks5_channel_handler_new( + socks5_bootstrap->allocator, + socks5_bootstrap->socks5_proxy_options, + endpoint_host_cursor, + socks5_bootstrap->endpoint_port, + socks5_bootstrap->endpoint_address_type, + s_on_socks5_setup_completed, + socks5_bootstrap); + if (!socks5_handler) { + return AWS_OP_ERR; + } + + struct aws_channel_slot *socks5_slot = aws_channel_slot_new(channel); + if (!socks5_slot) { + aws_channel_handler_destroy(socks5_handler); + return AWS_OP_ERR; + } + + aws_channel_slot_insert_right(slot, socks5_slot); + socks5_handler->slot = socks5_slot; + + struct aws_socks5_channel_handler *impl_ptr = socks5_handler->impl; + if (impl_ptr) { + impl_ptr->slot = socks5_slot; + impl_ptr->user_data = socks5_bootstrap; + } + + if (aws_channel_slot_set_handler(socks5_slot, socks5_handler)) { + aws_channel_slot_remove(socks5_slot); + return AWS_OP_ERR; + } + + if (aws_socks5_channel_handler_start_handshake(socks5_handler)) { + aws_channel_slot_remove(socks5_slot); + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} + +static void s_socks5_bootstrap_resolution_success_task( + struct aws_channel_task *task, + void *arg, + enum aws_task_status status) { + (void)task; + + struct aws_socks5_bootstrap *socks5_bootstrap = arg; + if (!socks5_bootstrap) { + return; + } + + struct aws_channel *channel = NULL; + + aws_mutex_lock(&socks5_bootstrap->lock); + socks5_bootstrap->resolution_task_scheduled = false; + channel = socks5_bootstrap->pending_channel; + socks5_bootstrap->pending_channel = NULL; + aws_mutex_unlock(&socks5_bootstrap->lock); + + if (!channel) { + return; + } + + /* Defer handshake work to the channel's event-loop thread */ + if (status != AWS_TASK_STATUS_RUN_READY) { + aws_channel_release_hold(channel); + return; + } + + if (s_socks5_bootstrap_begin_handshake(socks5_bootstrap, channel)) { + int err_code = aws_last_error(); + if (socks5_bootstrap->setup_callback) { + socks5_bootstrap->setup_callback( + socks5_bootstrap->bootstrap, + err_code, + NULL, + socks5_bootstrap->user_data); + } + aws_channel_shutdown(channel, err_code); + s_release_bootstrap_resources(socks5_bootstrap); + } + + aws_channel_release_hold(channel); +} + +static void s_socks5_bootstrap_resolution_failure_task( + struct aws_channel_task *task, + void *arg, + enum aws_task_status status) { + (void)task; + struct aws_socks5_bootstrap *socks5_bootstrap = arg; + if (!socks5_bootstrap) { + return; + } + + struct aws_channel *channel = NULL; + int error_code = socks5_bootstrap->resolution_error_code; + if (error_code == AWS_ERROR_SUCCESS) { + error_code = AWS_IO_DNS_INVALID_NAME; + } + + aws_mutex_lock(&socks5_bootstrap->lock); + socks5_bootstrap->resolution_failure_task_scheduled = false; + channel = socks5_bootstrap->pending_channel; + socks5_bootstrap->pending_channel = NULL; + aws_mutex_unlock(&socks5_bootstrap->lock); + + /* Propagate DNS failure on the channel thread to keep shutdown ordering intact */ + if (channel && status == AWS_TASK_STATUS_RUN_READY) { + aws_channel_shutdown(channel, error_code); + aws_channel_release_hold(channel); + } else if (channel) { + aws_channel_release_hold(channel); + } + + if (socks5_bootstrap->setup_callback) { + socks5_bootstrap->setup_callback( + socks5_bootstrap->bootstrap, + error_code, + NULL, + socks5_bootstrap->user_data); + } + + s_release_bootstrap_resources(socks5_bootstrap); +} + +/* Handle channel shutdown */ +static void s_socks5_socket_channel_shutdown( + struct aws_client_bootstrap *bootstrap, + int error_code, + struct aws_channel *channel, + void *user_data) { + struct aws_socks5_bootstrap *socks5_bootstrap = (struct aws_socks5_bootstrap *)user_data; + if (!socks5_bootstrap) { + return; + } + + if (socks5_bootstrap->shutdown_callback) { + socks5_bootstrap->shutdown_callback(bootstrap, error_code, channel, socks5_bootstrap->user_data); + } + + s_cleanup_bootstrap(socks5_bootstrap); +} + +static void s_socks5_bootstrap_create_channel_options( + struct aws_socks5_bootstrap *socks5_bootstrap, + struct aws_socket_channel_bootstrap_options *channel_options) +{ + channel_options->host_name = aws_string_c_str(socks5_bootstrap->socks5_proxy_options->host); + channel_options->port = socks5_bootstrap->socks5_proxy_options->port; + channel_options->setup_callback = s_socks5_socket_channel_setup; + channel_options->shutdown_callback = s_socks5_socket_channel_shutdown; + channel_options->user_data = socks5_bootstrap; + channel_options->tls_options = NULL; // Handled internally after SOCKS5 handshake +} + +static int s_socks5_bootstrap_set_socks5_proxy_options( + struct aws_socks5_bootstrap *socks5_bootstrap, + struct aws_allocator *allocator, + const struct aws_socks5_proxy_options *source_proxy_options, + const char *host_name, + uint16_t port +) +{ + if (!source_proxy_options) { + return AWS_OP_SUCCESS; + } + + struct aws_socks5_proxy_options * socks5_proxy_options = + aws_mem_calloc(allocator, 1, sizeof(struct aws_socks5_proxy_options)); + if (!socks5_proxy_options) { + return AWS_OP_ERR; + } + + if (aws_socks5_proxy_options_copy(socks5_proxy_options, source_proxy_options)) { + aws_socks5_proxy_options_clean_up(socks5_proxy_options); + aws_mem_release(allocator, socks5_proxy_options); + return AWS_OP_ERR; + } + + if (!host_name || host_name[0] == '\0') { + aws_socks5_proxy_options_clean_up(socks5_proxy_options); + aws_mem_release(allocator, socks5_proxy_options); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_byte_cursor endpoint_host_cursor = aws_byte_cursor_from_c_str(host_name); + + aws_string_destroy(socks5_bootstrap->endpoint_host); + socks5_bootstrap->endpoint_host = NULL; + aws_string_destroy(socks5_bootstrap->original_endpoint_host); + socks5_bootstrap->original_endpoint_host = NULL; + + socks5_bootstrap->endpoint_port = port; + enum aws_socks5_address_type inferred_type = + aws_socks5_infer_address_type(endpoint_host_cursor, AWS_SOCKS5_ATYP_DOMAIN); + socks5_bootstrap->host_resolution_mode = + aws_socks5_proxy_options_get_host_resolution_mode(socks5_proxy_options); + socks5_bootstrap->resolution_error_code = AWS_ERROR_SUCCESS; + socks5_bootstrap->endpoint_ready = + socks5_bootstrap->host_resolution_mode != AWS_SOCKS5_HOST_RESOLUTION_CLIENT; + socks5_bootstrap->resolution_in_progress = false; + + if (socks5_bootstrap->host_resolution_mode == AWS_SOCKS5_HOST_RESOLUTION_CLIENT && + inferred_type != AWS_SOCKS5_ATYP_DOMAIN) { + socks5_bootstrap->endpoint_host = + aws_string_new_from_cursor(allocator, &endpoint_host_cursor); + if (!socks5_bootstrap->endpoint_host) { + aws_socks5_proxy_options_clean_up(socks5_proxy_options); + aws_mem_release(allocator, socks5_proxy_options); + return AWS_OP_ERR; + } + socks5_bootstrap->original_endpoint_host = + aws_string_new_from_cursor(allocator, &endpoint_host_cursor); + if (!socks5_bootstrap->original_endpoint_host) { + aws_string_destroy(socks5_bootstrap->endpoint_host); + socks5_bootstrap->endpoint_host = NULL; + aws_socks5_proxy_options_clean_up(socks5_proxy_options); + aws_mem_release(allocator, socks5_proxy_options); + return AWS_OP_ERR; + } + socks5_bootstrap->endpoint_address_type = inferred_type; + socks5_bootstrap->endpoint_ready = true; + } else if (socks5_bootstrap->host_resolution_mode == AWS_SOCKS5_HOST_RESOLUTION_CLIENT) { + socks5_bootstrap->original_endpoint_host = + aws_string_new_from_cursor(allocator, &endpoint_host_cursor); + if (!socks5_bootstrap->original_endpoint_host) { + aws_socks5_proxy_options_clean_up(socks5_proxy_options); + aws_mem_release(allocator, socks5_proxy_options); + return AWS_OP_ERR; + } + socks5_bootstrap->endpoint_address_type = AWS_SOCKS5_ATYP_DOMAIN; + socks5_bootstrap->endpoint_ready = false; + } else { + socks5_bootstrap->endpoint_host = + aws_string_new_from_cursor(allocator, &endpoint_host_cursor); + if (!socks5_bootstrap->endpoint_host) { + aws_socks5_proxy_options_clean_up(socks5_proxy_options); + aws_mem_release(allocator, socks5_proxy_options); + return AWS_OP_ERR; + } + socks5_bootstrap->endpoint_address_type = inferred_type; + } + + socks5_bootstrap->socks5_proxy_options = socks5_proxy_options; + + return AWS_OP_SUCCESS; +} + +static int s_socks5_bootstrap_set_tls_options( + struct aws_socks5_bootstrap *socks5_bootstrap, + struct aws_allocator *allocator, + const struct aws_tls_connection_options *tls_options) +{ + if (!tls_options) { + return AWS_OP_SUCCESS; + } + socks5_bootstrap->tls_options = + aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options)); + if (!socks5_bootstrap->tls_options) { + return AWS_OP_ERR; + } + socks5_bootstrap->original_on_negotiation_result = tls_options->on_negotiation_result; + socks5_bootstrap->original_tls_user_data = tls_options->user_data; + if (aws_tls_connection_options_copy(socks5_bootstrap->tls_options, tls_options)) { + aws_tls_connection_options_clean_up(socks5_bootstrap->tls_options); + aws_mem_release(allocator, socks5_bootstrap->tls_options); + socks5_bootstrap->tls_options = NULL; + return AWS_OP_ERR; + } + socks5_bootstrap->use_tls = true; + return AWS_OP_SUCCESS; +} + +static int s_socks5_bootstrap_start_endpoint_resolution( + struct aws_socks5_bootstrap *socks5_bootstrap, + const struct aws_socket_channel_bootstrap_options *channel_options) { + + if (!socks5_bootstrap || !channel_options) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (socks5_bootstrap->host_resolution_mode != AWS_SOCKS5_HOST_RESOLUTION_CLIENT || socks5_bootstrap->endpoint_ready) { + return AWS_OP_SUCCESS; + } + + if (!socks5_bootstrap->original_endpoint_host) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_client_bootstrap *client_bootstrap = channel_options->bootstrap; + if (!client_bootstrap || !client_bootstrap->host_resolver) { + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + + /* Prefer per-request overrides, otherwise fall back to bootstrap defaults */ + const struct aws_host_resolution_config *config_to_use = + channel_options->host_resolution_override_config; + + if (config_to_use) { + socks5_bootstrap->host_resolution_config = *config_to_use; + socks5_bootstrap->has_host_resolution_override = true; + config_to_use = &socks5_bootstrap->host_resolution_config; + } else { + socks5_bootstrap->host_resolution_config = client_bootstrap->host_resolver_config; + socks5_bootstrap->has_host_resolution_override = false; + config_to_use = &client_bootstrap->host_resolver_config; + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Starting client-side resolution for endpoint '%s'", + (void *)socks5_bootstrap, + aws_string_c_str(socks5_bootstrap->original_endpoint_host)); + + /* Track outstanding work so the setup path can defer the handshake */ + socks5_bootstrap->resolution_error_code = AWS_ERROR_SUCCESS; + socks5_bootstrap->resolution_in_progress = true; + + if (aws_host_resolver_resolve_host( + client_bootstrap->host_resolver, + socks5_bootstrap->original_endpoint_host, + s_socks5_on_host_resolved, + config_to_use, + socks5_bootstrap)) { + socks5_bootstrap->resolution_in_progress = false; + socks5_bootstrap->resolution_error_code = aws_last_error(); + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} + +static void s_socks5_on_host_resolved( + struct aws_host_resolver *resolver, + const struct aws_string *host_name, + int err_code, + const struct aws_array_list *host_addresses, + void *user_data) { + (void)resolver; + (void)host_name; + + struct aws_socks5_bootstrap *socks5_bootstrap = user_data; + if (!socks5_bootstrap) { + return; + } + + struct aws_channel *channel_for_success = NULL; + struct aws_channel *channel_for_failure = NULL; + struct aws_channel_task *success_task = NULL; + struct aws_channel_task *failure_task = NULL; + + int error_code = err_code; + + aws_mutex_lock(&socks5_bootstrap->lock); + socks5_bootstrap->resolution_in_progress = false; + + if (error_code != AWS_ERROR_SUCCESS) { + socks5_bootstrap->resolution_error_code = error_code; + } else { + size_t address_count = host_addresses ? aws_array_list_length(host_addresses) : 0; + if (!host_addresses || address_count == 0) { + error_code = AWS_IO_DNS_INVALID_NAME; + socks5_bootstrap->resolution_error_code = error_code; + } else { + const struct aws_host_address *chosen_address = NULL; + const struct aws_host_address *first_available = NULL; + + /* Prefer IPv4 when available, otherwise fall back to the first usable entry */ + for (size_t i = 0; i < address_count; ++i) { + const struct aws_host_address *current = NULL; + aws_array_list_get_at_ptr(host_addresses, (void **)¤t, i); + if (!current || !current->address) { + continue; + } + if (!first_available) { + first_available = current; + } + if (current->record_type == AWS_ADDRESS_RECORD_TYPE_A) { + chosen_address = current; + break; + } + } + + if (!chosen_address) { + chosen_address = first_available; + } + + if (!chosen_address || !chosen_address->address) { + error_code = AWS_IO_DNS_INVALID_NAME; + socks5_bootstrap->resolution_error_code = error_code; + } else { + struct aws_string *resolved_ip = + aws_string_new_from_string(socks5_bootstrap->allocator, chosen_address->address); + if (!resolved_ip) { + error_code = aws_last_error(); + socks5_bootstrap->resolution_error_code = error_code; + } else { + aws_string_destroy(socks5_bootstrap->endpoint_host); + socks5_bootstrap->endpoint_host = resolved_ip; + socks5_bootstrap->endpoint_address_type = + chosen_address->record_type == AWS_ADDRESS_RECORD_TYPE_AAAA + ? AWS_SOCKS5_ATYP_IPV6 + : AWS_SOCKS5_ATYP_IPV4; + socks5_bootstrap->endpoint_ready = true; + socks5_bootstrap->resolution_error_code = AWS_ERROR_SUCCESS; + error_code = AWS_ERROR_SUCCESS; + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKS5, + "id=%p: Resolved endpoint '%s' to %s", + (void *)socks5_bootstrap, + socks5_bootstrap->original_endpoint_host + ? aws_string_c_str(socks5_bootstrap->original_endpoint_host) + : "", + aws_string_c_str(resolved_ip)); + } + } + } + } + + if (error_code != AWS_ERROR_SUCCESS) { + socks5_bootstrap->endpoint_ready = false; + } + + bool cleanup_now = false; + + if (error_code == AWS_ERROR_SUCCESS) { + if (socks5_bootstrap->pending_channel && !socks5_bootstrap->resolution_task_scheduled) { + channel_for_success = socks5_bootstrap->pending_channel; + aws_channel_task_init( + &socks5_bootstrap->resolution_success_task, + s_socks5_bootstrap_resolution_success_task, + socks5_bootstrap, + "socks5_resolution_success"); + success_task = &socks5_bootstrap->resolution_success_task; + socks5_bootstrap->resolution_task_scheduled = true; + } + } else { + if (socks5_bootstrap->pending_channel && !socks5_bootstrap->resolution_failure_task_scheduled) { + channel_for_failure = socks5_bootstrap->pending_channel; + aws_channel_task_init( + &socks5_bootstrap->resolution_failure_task, + s_socks5_bootstrap_resolution_failure_task, + socks5_bootstrap, + "socks5_resolution_failure"); + failure_task = &socks5_bootstrap->resolution_failure_task; + socks5_bootstrap->resolution_failure_task_scheduled = true; + } + } + + if (socks5_bootstrap->cleanup_pending && !socks5_bootstrap->resolution_in_progress && + success_task == NULL && failure_task == NULL) { + /* shutdown requested earlier; resolver is the last owner so finish cleanup now */ + cleanup_now = true; + socks5_bootstrap->cleanup_pending = false; + } + + aws_mutex_unlock(&socks5_bootstrap->lock); + + if (success_task && channel_for_success) { + aws_channel_schedule_task_now(channel_for_success, success_task); + } + + if (failure_task && channel_for_failure) { + aws_channel_schedule_task_now(channel_for_failure, failure_task); + } + + if (cleanup_now) { + s_cleanup_bootstrap(socks5_bootstrap); + } +} + +int aws_socks5_client_bootstrap_new_socket_channel(struct aws_socket_channel_bootstrap_options *options) { + AWS_PRECONDITION(options); + AWS_FATAL_ASSERT( + s_socks5_system_vtable && s_socks5_system_vtable->aws_client_bootstrap_new_socket_channel && + "socks5 system vtable must provide aws_client_bootstrap_new_socket_channel"); + return s_socks5_system_vtable->aws_client_bootstrap_new_socket_channel(options); +} + +static int s_socks5_bootstrap_create_proxy_options( + struct aws_socks5_bootstrap *socks5_bootstrap, + struct aws_allocator *allocator, + const struct aws_socks5_proxy_options *socks5_proxy_options, + struct aws_socket_channel_bootstrap_options *channel_options) +{ + if (!socks5_bootstrap) { + return AWS_OP_ERR; + } + + socks5_bootstrap->allocator = allocator; + socks5_bootstrap->bootstrap = channel_options->bootstrap; + socks5_bootstrap->setup_callback = channel_options->setup_callback; + socks5_bootstrap->shutdown_callback = channel_options->shutdown_callback; + socks5_bootstrap->user_data = channel_options->user_data; + + if (s_socks5_bootstrap_set_socks5_proxy_options( + socks5_bootstrap, + allocator, + socks5_proxy_options, + channel_options->host_name, + channel_options->port)) { + s_release_bootstrap_resources(socks5_bootstrap); + return AWS_OP_ERR; + } + + if (s_socks5_bootstrap_set_tls_options(socks5_bootstrap, allocator, channel_options->tls_options)) { + s_release_bootstrap_resources(socks5_bootstrap); + return AWS_OP_ERR; + } + + return AWS_OP_SUCCESS; +} + +int aws_client_bootstrap_new_socket_channel_with_socks5( + struct aws_allocator *allocator, + struct aws_socket_channel_bootstrap_options *channel_options, + const struct aws_socks5_proxy_options *socks5_proxy_options) +{ + if (!allocator || !socks5_proxy_options || !channel_options) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_socks5_bootstrap *socks5_bootstrap = aws_mem_calloc(allocator, 1, sizeof(struct aws_socks5_bootstrap)); + if (!socks5_bootstrap) { + return AWS_OP_ERR; + } + + if (aws_mutex_init(&socks5_bootstrap->lock) != AWS_OP_SUCCESS) { + aws_mem_release(allocator, socks5_bootstrap); + return AWS_OP_ERR; + } + + if (s_socks5_bootstrap_create_proxy_options( + socks5_bootstrap, allocator, socks5_proxy_options, channel_options)) { + s_cleanup_bootstrap(socks5_bootstrap); + return AWS_OP_ERR; + } + + if (s_socks5_bootstrap_start_endpoint_resolution(socks5_bootstrap, channel_options)) { + s_cleanup_bootstrap(socks5_bootstrap); + return AWS_OP_ERR; + } + + // Update channel options for socks5 socket + s_socks5_bootstrap_create_channel_options(socks5_bootstrap, channel_options); + + AWS_FATAL_ASSERT( + s_socks5_system_vtable && s_socks5_system_vtable->aws_client_bootstrap_new_socket_channel && + "socks5 system vtable must provide aws_client_bootstrap_new_socket_channel"); + + int result = s_socks5_system_vtable->aws_client_bootstrap_new_socket_channel(channel_options); + if (result == AWS_OP_ERR) { + s_cleanup_bootstrap(socks5_bootstrap); + } + + return result; +} + +/* Start the SOCKS5 handshake process manually */ +int aws_socks5_channel_handler_start_handshake(struct aws_channel_handler *handler) { + AWS_ASSERT(handler); + + if (!handler) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + if (handler->vtable != &s_socks5_handler_vtable) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + struct aws_socks5_channel_handler *socks5_handler = handler->impl; + if (!socks5_handler) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + /* Check if handler has slot */ + if (!handler->slot) { + /* Don't fail here - the handshake will be started when the slot is set */ + return AWS_OP_SUCCESS; + } + + /* Make sure the slot has a channel */ + if (handler->slot->channel == NULL) { + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + + /* Don't start handshake if we're not in INIT state */ + if (socks5_handler->channel_state != AWS_SOCKS5_CHANNEL_STATE_INIT) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Cannot start handshake in state %d", + (void *)handler, + socks5_handler->channel_state); + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + + /* Make sure processing incoming data is enabled */ + socks5_handler->process_incoming_data = true; + + /* Start the SOCKS5 connection process */ + return s_start_socks5_handshake(handler, handler->slot); +} diff --git a/source/windows/secure_channel_tls_handler.c b/source/windows/secure_channel_tls_handler.c index 59f8c56b8..329ebc937 100644 --- a/source/windows/secure_channel_tls_handler.c +++ b/source/windows/secure_channel_tls_handler.c @@ -1,3 +1,4 @@ +<<<<<<< HEAD /** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. @@ -2504,3 +2505,2053 @@ struct aws_tls_ctx *aws_tls_server_ctx_new(struct aws_allocator *alloc, const st struct aws_tls_ctx *aws_tls_client_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) { return s_ctx_new(alloc, options, true); } +======= +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#define SECURITY_WIN32 + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +# pragma warning(disable : 4221) /* aggregate initializer using local variable addresses */ +# pragma warning(disable : 4204) /* non-constant aggregate initializer */ +# pragma warning(disable : 4306) /* Identifier is type cast to a larger pointer. */ +#endif + +#define KB_1 1024 +#define READ_OUT_SIZE (16 * KB_1) +#define READ_IN_SIZE READ_OUT_SIZE +#define EST_HANDSHAKE_SIZE (7 * KB_1) + +#define EST_TLS_RECORD_OVERHEAD 53 /* 5 byte header + 32 + 16 bytes for padding */ + +void aws_tls_init_static_state(struct aws_allocator *alloc) { + AWS_LOGF_INFO(AWS_LS_IO_TLS, "static: Initializing TLS using SecureChannel (SSPI)."); + (void)alloc; +} + +void aws_tls_clean_up_static_state(void) {} + +struct secure_channel_ctx { + struct aws_tls_ctx ctx; + struct aws_string *alpn_list; + SCHANNEL_CRED credentials; + PCERT_CONTEXT pcerts; + HCERTSTORE cert_store; + HCERTSTORE custom_trust_store; + HCRYPTPROV crypto_provider; + HCRYPTKEY private_key; + bool verify_peer; + bool should_free_pcerts; +}; + +struct secure_channel_handler { + struct aws_channel_handler handler; + struct aws_tls_channel_handler_shared shared_state; + CtxtHandle sec_handle; + CredHandle creds; + /* + * The SSPI API expects an array of len 1 of these where it's the leaf certificate associated with its private + * key. + */ + PCCERT_CONTEXT cert_context[1]; + HCERTSTORE cert_store; + HCERTSTORE custom_ca_store; + SecPkgContext_StreamSizes stream_sizes; + unsigned long ctx_req; + unsigned long ctx_ret_flags; + struct aws_channel_slot *slot; + struct aws_byte_buf protocol; + struct aws_byte_buf server_name; + TimeStamp sspi_timestamp; + int (*s_connection_state_fn)(struct aws_channel_handler *handler); + /* + * Give a little bit of extra head room, for split records. + */ + uint8_t buffered_read_in_data[READ_IN_SIZE + KB_1]; + struct aws_byte_buf buffered_read_in_data_buf; + size_t estimated_incomplete_size; + size_t read_extra; + /* This is to accommodate the extra head room we added above. + because we're allowing for splits, we may have more data decrypted + than we can fit in this buffer if we don't make them match. */ + uint8_t buffered_read_out_data[READ_OUT_SIZE + KB_1]; + struct aws_byte_buf buffered_read_out_data_buf; + struct aws_channel_task sequential_task_storage; + aws_tls_on_negotiation_result_fn *on_negotiation_result; + aws_tls_on_data_read_fn *on_data_read; + aws_tls_on_error_fn *on_error; + struct aws_string *alpn_list; + void *user_data; + bool advertise_alpn_message; + bool negotiation_finished; + bool verify_peer; +}; + +static size_t s_message_overhead(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + + if (AWS_UNLIKELY(!sc_handler->stream_sizes.cbMaximumMessage)) { + SECURITY_STATUS status = + QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_STREAM_SIZES, &sc_handler->stream_sizes); + + if (status != SEC_E_OK) { + return EST_TLS_RECORD_OVERHEAD; + } + } + + return sc_handler->stream_sizes.cbTrailer + sc_handler->stream_sizes.cbHeader; +} + +bool aws_tls_is_alpn_available(void) { +/* if you built on an old version of windows, still no support, but if you did, we still + want to check the OS version at runtime before agreeing to attempt alpn. */ +#ifdef SECBUFFER_APPLICATION_PROTOCOLS + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "static: This library was built with Windows 8.1 or later, " + "probing OS to see what we're actually running on."); + /* make sure we're on windows 8.1 or later. */ + OSVERSIONINFOEX os_version; + DWORDLONG condition_mask = 0; + VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + AWS_ZERO_STRUCT(os_version); + os_version.dwMajorVersion = HIBYTE(_WIN32_WINNT_WIN8); + os_version.dwMinorVersion = LOBYTE(_WIN32_WINNT_WIN8); + os_version.wServicePackMajor = 0; + os_version.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + if (VerifyVersionInfo( + &os_version, + VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, + condition_mask)) { + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: We're running on Windows 8.1 or later. ALPN is available."); + return true; + } + + AWS_LOGF_WARN( + AWS_LS_IO_TLS, + "static: Running on older version of windows, ALPN is not supported. " + "Please update your OS to take advantage of modern features."); +#else + AWS_LOGF_WARN( + AWS_LS_IO_TLS, + "static: This library was built using a Windows SDK prior to 8.1. " + "Please build with a version of windows >= 8.1 to take advantage modern features. ALPN is not supported."); +#endif /*SECBUFFER_APPLICATION_PROTOCOLS */ + return false; +} + +bool aws_tls_is_cipher_pref_supported(enum aws_tls_cipher_pref cipher_pref) { + switch (cipher_pref) { + case AWS_IO_TLS_CIPHER_PREF_SYSTEM_DEFAULT: + return true; + + case AWS_IO_TLS_CIPHER_PREF_KMS_PQ_TLSv1_0_2019_06: + default: + return false; + } +} + +/* technically we could lower this, but lets be forgiving */ +#define MAX_HOST_LENGTH 255 + +/* this only gets called if the user specified a custom ca. */ +static int s_manually_verify_peer_cert(struct aws_channel_handler *handler) { + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "id=%p: manually verifying certifcate chain because a custom CA is configured.", + (void *)handler); + struct secure_channel_handler *sc_handler = handler->impl; + + int result = AWS_OP_ERR; + CERT_CONTEXT *peer_certificate = NULL; + HCERTCHAINENGINE engine = NULL; + CERT_CHAIN_CONTEXT *cert_chain_ctx = NULL; + + /* get the peer's certificate so we can validate it.*/ + SECURITY_STATUS status = + QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &peer_certificate); + + if (status != SEC_E_OK || !peer_certificate) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: failed to load peer's certificate with SECURITY_STATUS %d", + (void *)handler, + (int)status); + return AWS_OP_ERR; + } + + /* this next bit scours the custom trust store to try and load a chain to verify + the leaf certificate against. */ + CERT_CHAIN_ENGINE_CONFIG engine_config; + AWS_ZERO_STRUCT(engine_config); + engine_config.cbSize = sizeof(engine_config); + engine_config.hExclusiveRoot = sc_handler->custom_ca_store; + + if (!CertCreateCertificateChainEngine(&engine_config, &engine)) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: failed to load a certificate chain engine with SECURITY_STATUS %d. " + "Most likely, the configured CA is corrupted.", + (void *)handler, + (int)status); + goto done; + } + + /* + * TODO: Investigate CRL options further on a per-platform basis. Add control APIs if appropriate. + */ + DWORD get_chain_flags = 0; + + /* mimic chromium here since we intend for this to be used generally */ + const LPCSTR usage_identifiers[] = { + szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE, + }; + + CERT_CHAIN_PARA chain_params; + AWS_ZERO_STRUCT(chain_params); + chain_params.cbSize = sizeof(chain_params); + chain_params.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + chain_params.RequestedUsage.Usage.cUsageIdentifier = AWS_ARRAY_SIZE(usage_identifiers); + chain_params.RequestedUsage.Usage.rgpszUsageIdentifier = (LPSTR *)usage_identifiers; + + if (!CertGetCertificateChain( + engine, + peer_certificate, + NULL, + peer_certificate->hCertStore, + &chain_params, + get_chain_flags, + NULL, + &cert_chain_ctx)) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: unable to find certificate in chain with SECURITY_STATUS %d.", + (void *)handler, + (int)status); + goto done; + } + + struct aws_byte_buf host = aws_tls_handler_server_name(handler); + if (host.len > MAX_HOST_LENGTH) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "id=%p: host name too long (%d).", (void *)handler, (int)host.len); + goto done; + } + + wchar_t whost[MAX_HOST_LENGTH + 1]; + AWS_ZERO_ARRAY(whost); + + int converted = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, (const char *)host.buffer, (int)host.len, whost, AWS_ARRAY_SIZE(whost)); + if ((size_t)converted != host.len) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: unable to convert host to wstr, %d -> %d, with last error 0x%x.", + (void *)handler, + (int)host.len, + (int)converted, + (int)GetLastError()); + goto done; + } + + /* check if the chain was trusted */ + LPCSTR policyiod = CERT_CHAIN_POLICY_SSL; + + SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslpolicy; + AWS_ZERO_STRUCT(sslpolicy); + sslpolicy.cbSize = sizeof(sslpolicy); + sslpolicy.dwAuthType = AUTHTYPE_SERVER; + sslpolicy.fdwChecks = 0; + sslpolicy.pwszServerName = whost; + + CERT_CHAIN_POLICY_PARA policypara; + AWS_ZERO_STRUCT(policypara); + policypara.cbSize = sizeof(policypara); + policypara.dwFlags = 0; + policypara.pvExtraPolicyPara = &sslpolicy; + + CERT_CHAIN_POLICY_STATUS policystatus; + AWS_ZERO_STRUCT(policystatus); + policystatus.cbSize = sizeof(policystatus); + + if (!CertVerifyCertificateChainPolicy(policyiod, cert_chain_ctx, &policypara, &policystatus)) { + int error = GetLastError(); + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "id=%p: CertVerifyCertificateChainPolicy() failed, error 0x%x", (void *)handler, (int)error); + goto done; + } + + if (policystatus.dwError) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: certificate verification failed, error 0x%x", + (void *)handler, + (int)policystatus.dwError); + goto done; + } + + /* if the chain was trusted, then we're good to go, if it was not + we bail out. */ + CERT_SIMPLE_CHAIN *simple_chain = cert_chain_ctx->rgpChain[0]; + DWORD trust_mask = ~(DWORD)CERT_TRUST_IS_NOT_TIME_NESTED; + trust_mask &= simple_chain->TrustStatus.dwErrorStatus; + + if (trust_mask != 0) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: peer certificate is un-trusted with SECURITY_STATUS %d.", + (void *)handler, + (int)trust_mask); + goto done; + } + + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: peer certificate is trusted.", (void *)handler); + result = AWS_OP_SUCCESS; + +done: + + if (cert_chain_ctx != NULL) { + CertFreeCertificateChain(cert_chain_ctx); + } + + if (engine != NULL) { + CertFreeCertificateChainEngine(engine); + } + + if (peer_certificate != NULL) { + CertFreeCertificateContext(peer_certificate); + } + + return result; +} + +static void s_invoke_negotiation_error(struct aws_channel_handler *handler, int err) { + struct secure_channel_handler *sc_handler = handler->impl; + + aws_on_tls_negotiation_completed(&sc_handler->shared_state, err); + + if (sc_handler->on_negotiation_result) { + sc_handler->on_negotiation_result(handler, sc_handler->slot, err, sc_handler->user_data); + } +} + +static void s_on_negotiation_success(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + + /* if the user provided an ALPN handler to the channel, we need to let them know what their protocol is. */ + if (sc_handler->slot->adj_right && sc_handler->advertise_alpn_message && sc_handler->protocol.len) { + struct aws_io_message *message = aws_channel_acquire_message_from_pool( + sc_handler->slot->channel, + AWS_IO_MESSAGE_APPLICATION_DATA, + sizeof(struct aws_tls_negotiated_protocol_message)); + message->message_tag = AWS_TLS_NEGOTIATED_PROTOCOL_MESSAGE; + struct aws_tls_negotiated_protocol_message *protocol_message = + (struct aws_tls_negotiated_protocol_message *)message->message_data.buffer; + + protocol_message->protocol = sc_handler->protocol; + message->message_data.len = sizeof(struct aws_tls_negotiated_protocol_message); + if (aws_channel_slot_send_message(sc_handler->slot, message, AWS_CHANNEL_DIR_READ)) { + aws_mem_release(message->allocator, message); + aws_channel_shutdown(sc_handler->slot->channel, aws_last_error()); + } + } + + aws_on_tls_negotiation_completed(&sc_handler->shared_state, AWS_ERROR_SUCCESS); + + if (sc_handler->on_negotiation_result) { + sc_handler->on_negotiation_result(handler, sc_handler->slot, AWS_OP_SUCCESS, sc_handler->user_data); + } +} + +static int s_determine_sspi_error(int sspi_status) { + switch (sspi_status) { + case SEC_E_INSUFFICIENT_MEMORY: + return AWS_ERROR_OOM; + case SEC_I_CONTEXT_EXPIRED: + return AWS_IO_TLS_ALERT_NOT_GRACEFUL; + case SEC_E_WRONG_PRINCIPAL: + return AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE; + /* + case SEC_E_INVALID_HANDLE: + case SEC_E_INVALID_TOKEN: + case SEC_E_LOGON_DENIED: + case SEC_E_TARGET_UNKNOWN: + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + case SEC_E_INTERNAL_ERROR: + case SEC_E_NO_CREDENTIALS: + case SEC_E_UNSUPPORTED_FUNCTION: + case SEC_E_APPLICATION_PROTOCOL_MISMATCH: + */ + default: + return AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE; + } +} + +#define CHECK_ALPN_BUFFER_SIZE(s, i, b) \ + if (s <= i) { \ + aws_array_list_clean_up(&b); \ + return aws_raise_error(AWS_ERROR_SHORT_BUFFER); \ + } + +/* construct ALPN extension data... apparently this works on big-endian machines? but I don't believe the docs + if you're running ARM and you find ALPN isn't working, it's probably because I trusted the documentation + and your bug is in here. Note, dotnet's corefx also acts like endianness isn't at play so if this is broken + so is everyone's dotnet code. */ +static int s_fillin_alpn_data( + struct aws_channel_handler *handler, + unsigned char *alpn_buffer_data, + size_t buffer_size, + size_t *written) { + *written = 0; + struct secure_channel_handler *sc_handler = handler->impl; + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, ""); + + struct aws_array_list alpn_buffers; + struct aws_byte_cursor alpn_buffer_array[4]; + aws_array_list_init_static(&alpn_buffers, alpn_buffer_array, 4, sizeof(struct aws_byte_cursor)); + + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "Setting ALPN extension with string %s.", aws_string_c_str(sc_handler->alpn_list)); + struct aws_byte_cursor alpn_str_cur = aws_byte_cursor_from_string(sc_handler->alpn_list); + if (aws_byte_cursor_split_on_char(&alpn_str_cur, ';', &alpn_buffers)) { + return AWS_OP_ERR; + } + + size_t protocols_count = aws_array_list_length(&alpn_buffers); + + size_t index = 0; + CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers) + uint32_t *extension_length = (uint32_t *)&alpn_buffer_data[index]; + index += sizeof(uint32_t); + CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers) + uint32_t *extension_name = (uint32_t *)&alpn_buffer_data[index]; + index += sizeof(uint32_t); + CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers) + uint16_t *protocols_byte_length = (uint16_t *)&alpn_buffer_data[index]; + index += sizeof(uint16_t); + CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint16_t), alpn_buffers) + + *extension_length += sizeof(uint32_t) + sizeof(uint16_t); + + *extension_name = SecApplicationProtocolNegotiationExt_ALPN; + /*now add the protocols*/ + for (size_t i = 0; i < protocols_count; ++i) { + struct aws_byte_cursor *protocol_ptr = NULL; + aws_array_list_get_at_ptr(&alpn_buffers, (void **)&protocol_ptr, i); + AWS_ASSERT(protocol_ptr); + *extension_length += (uint32_t)protocol_ptr->len + 1; + *protocols_byte_length += (uint16_t)protocol_ptr->len + 1; + CHECK_ALPN_BUFFER_SIZE(buffer_size, index + 1, alpn_buffers) + alpn_buffer_data[index++] = (unsigned char)protocol_ptr->len; + CHECK_ALPN_BUFFER_SIZE(buffer_size, index + protocol_ptr->len, alpn_buffers) + memcpy(alpn_buffer_data + index, protocol_ptr->ptr, protocol_ptr->len); + index += protocol_ptr->len; + } + + aws_array_list_clean_up(&alpn_buffers); + *written = *extension_length + sizeof(uint32_t); + return AWS_OP_SUCCESS; +} + +static int s_process_connection_state(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + return sc_handler->s_connection_state_fn(handler); +} + +static int s_do_application_data_decrypt(struct aws_channel_handler *handler); + +static int s_do_server_side_negotiation_step_2(struct aws_channel_handler *handler); + +/** invoked during the first step of the server's negotiation. It receives the client hello, + adds its alpn data if available, and if everything is good, sends out the server hello. */ +static int s_do_server_side_negotiation_step_1(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: server starting negotiation", (void *)handler); + + aws_on_drive_tls_negotiation(&sc_handler->shared_state); + + unsigned char alpn_buffer_data[128] = {0}; + SecBuffer input_bufs[] = { + { + .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer, + .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len, + .BufferType = SECBUFFER_TOKEN, + }, + { + .pvBuffer = NULL, + .cbBuffer = 0, + .BufferType = SECBUFFER_EMPTY, + }, + }; + + SecBufferDesc input_bufs_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 2, + .pBuffers = input_bufs, + }; + +#ifdef SECBUFFER_APPLICATION_PROTOCOLS + if (sc_handler->alpn_list && aws_tls_is_alpn_available()) { + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Setting ALPN to %s", handler, aws_string_c_str(sc_handler->alpn_list)); + size_t extension_length = 0; + if (s_fillin_alpn_data(handler, alpn_buffer_data, sizeof(alpn_buffer_data), &extension_length)) { + return AWS_OP_ERR; + } + + input_bufs[1].pvBuffer = alpn_buffer_data, input_bufs[1].cbBuffer = (unsigned long)extension_length, + input_bufs[1].BufferType = SECBUFFER_APPLICATION_PROTOCOLS; + } +#endif /* SECBUFFER_APPLICATION_PROTOCOLS*/ + + sc_handler->ctx_req = ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY | + ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM; + + if (sc_handler->verify_peer) { + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "id=%p: server configured to use mutual tls, expecting a certficate from client.", + (void *)handler); + sc_handler->ctx_req |= ASC_REQ_MUTUAL_AUTH; + } + + SecBuffer output_buffer = { + .pvBuffer = NULL, + .cbBuffer = 0, + .BufferType = SECBUFFER_TOKEN, + }; + + SecBufferDesc output_buffer_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 1, + .pBuffers = &output_buffer, + }; + + /* process the client hello. */ + SECURITY_STATUS status = AcceptSecurityContext( + &sc_handler->creds, + NULL, + &input_bufs_desc, + sc_handler->ctx_req, + 0, + &sc_handler->sec_handle, + &output_buffer_desc, + &sc_handler->ctx_ret_flags, + NULL); + + if (!(status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK)) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: error during processing of the ClientHello. SECURITY_STATUS is %d", + (void *)handler, + (int)status); + int error = s_determine_sspi_error(status); + aws_raise_error(error); + s_invoke_negotiation_error(handler, error); + return AWS_OP_ERR; + } + + size_t data_to_write_len = output_buffer.cbBuffer; + + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Sending ServerHello. Data size %zu", (void *)handler, data_to_write_len); + /* send the server hello. */ + struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( + sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, data_to_write_len); + if (!outgoing_message) { + FreeContextBuffer(output_buffer.pvBuffer); + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + + AWS_ASSERT(outgoing_message->message_data.capacity >= data_to_write_len); + memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer); + outgoing_message->message_data.len = output_buffer.cbBuffer; + FreeContextBuffer(output_buffer.pvBuffer); + + if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { + aws_mem_release(outgoing_message->allocator, outgoing_message); + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + + sc_handler->s_connection_state_fn = s_do_server_side_negotiation_step_2; + + return AWS_OP_SUCCESS; +} + +/* cipher change, key exchange, mutual TLS stuff. */ +static int s_do_server_side_negotiation_step_2(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, "id=%p: running step 2 of negotiation (cipher change, key exchange etc...)", (void *)handler); + SecBuffer input_buffers[] = { + [0] = + { + .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer, + .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len, + .BufferType = SECBUFFER_TOKEN, + }, + [1] = + { + .pvBuffer = NULL, + .cbBuffer = 0, + .BufferType = SECBUFFER_EMPTY, + }, + }; + + SecBufferDesc input_buffers_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 2, + .pBuffers = input_buffers, + }; + + SecBuffer output_buffers[3]; + AWS_ZERO_ARRAY(output_buffers); + output_buffers[0].BufferType = SECBUFFER_TOKEN; + output_buffers[1].BufferType = SECBUFFER_ALERT; + + SecBufferDesc output_buffers_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 3, + .pBuffers = output_buffers, + }; + + sc_handler->read_extra = 0; + sc_handler->estimated_incomplete_size = 0; + + SECURITY_STATUS status = AcceptSecurityContext( + &sc_handler->creds, + &sc_handler->sec_handle, + &input_buffers_desc, + sc_handler->ctx_req, + 0, + NULL, + &output_buffers_desc, + &sc_handler->ctx_ret_flags, + &sc_handler->sspi_timestamp); + + if (status != SEC_E_INCOMPLETE_MESSAGE && status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "id=%p: Error during negotiation. SECURITY_STATUS is %d", (void *)handler, (int)status); + int aws_error = s_determine_sspi_error(status); + aws_raise_error(aws_error); + s_invoke_negotiation_error(handler, aws_error); + return AWS_OP_ERR; + } + + if (status == SEC_E_INCOMPLETE_MESSAGE) { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, "id=%p: Last processed buffer was incomplete, waiting on more data.", (void *)handler); + sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer; + return aws_raise_error(AWS_IO_READ_WOULD_BLOCK); + }; + /* any output buffers that were filled in with SECBUFFER_TOKEN need to be sent, + SECBUFFER_EXTRA means we need to account for extra data and shift everything for the next run. */ + if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) { + for (size_t i = 0; i < output_buffers_desc.cBuffers; ++i) { + SecBuffer *buf_ptr = &output_buffers[i]; + + if (buf_ptr->BufferType == SECBUFFER_TOKEN && buf_ptr->cbBuffer) { + struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( + sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, buf_ptr->cbBuffer); + + if (!outgoing_message) { + FreeContextBuffer(buf_ptr->pvBuffer); + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + + memcpy(outgoing_message->message_data.buffer, buf_ptr->pvBuffer, buf_ptr->cbBuffer); + outgoing_message->message_data.len = buf_ptr->cbBuffer; + FreeContextBuffer(buf_ptr->pvBuffer); + + if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { + aws_mem_release(outgoing_message->allocator, outgoing_message); + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + } + } + + if (input_buffers[1].BufferType == SECBUFFER_EXTRA && input_buffers[1].cbBuffer > 0) { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Extra data recieved. Extra size is %lu", + (void *)handler, + input_buffers[1].cbBuffer); + sc_handler->read_extra = input_buffers[1].cbBuffer; + } + } + + if (status == SEC_E_OK) { + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: handshake completed", (void *)handler); + /* if a custom CA store was configured, we have to do the verification ourselves. */ + if (sc_handler->custom_ca_store) { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Custom CA was configured, evaluating trust before completing connection", + (void *)handler); + + if (s_manually_verify_peer_cert(handler)) { + aws_raise_error(AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); + s_invoke_negotiation_error(handler, AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); + return AWS_OP_ERR; + } + } + sc_handler->negotiation_finished = true; + + /* force query of the sizes so future calls to encrypt will be loaded. */ + s_message_overhead(handler); + + /* + grab the negotiated protocol out of the session. + */ +#ifdef SECBUFFER_APPLICATION_PROTOCOLS + if (sc_handler->alpn_list && aws_tls_is_alpn_available()) { + SecPkgContext_ApplicationProtocol alpn_result; + status = QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result); + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: ALPN is configured. Checking for negotiated protocol", handler); + + if (status == SEC_E_OK && alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { + aws_byte_buf_init(&sc_handler->protocol, handler->alloc, alpn_result.ProtocolIdSize + 1); + memset(sc_handler->protocol.buffer, 0, alpn_result.ProtocolIdSize + 1); + memcpy(sc_handler->protocol.buffer, alpn_result.ProtocolId, alpn_result.ProtocolIdSize); + sc_handler->protocol.len = alpn_result.ProtocolIdSize; + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, "id=%p: negotiated protocol %s", handler, (char *)sc_handler->protocol.buffer); + } else { + AWS_LOGF_WARN( + AWS_LS_IO_TLS, + "id=%p: Error retrieving negotiated protocol. SECURITY_STATUS is %d", + handler, + (int)status); + int aws_error = s_determine_sspi_error(status); + aws_raise_error(aws_error); + } + } +#endif + sc_handler->s_connection_state_fn = s_do_application_data_decrypt; + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: TLS handshake completed successfully.", (void *)handler); + s_on_negotiation_success(handler); + } + + return AWS_OP_SUCCESS; +} + +static int s_do_client_side_negotiation_step_2(struct aws_channel_handler *handler); + +/* send the client hello */ +static int s_do_client_side_negotiation_step_1(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: client starting negotiation", (void *)handler); + + aws_on_drive_tls_negotiation(&sc_handler->shared_state); + + unsigned char alpn_buffer_data[128] = {0}; + SecBuffer input_buf = { + .pvBuffer = NULL, + .cbBuffer = 0, + .BufferType = SECBUFFER_EMPTY, + }; + + SecBufferDesc input_buf_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 1, + .pBuffers = &input_buf, + }; + + SecBufferDesc *alpn_sspi_data = NULL; + + /* add alpn data to the client hello if it's supported. */ +#ifdef SECBUFFER_APPLICATION_PROTOCOLS + if (sc_handler->alpn_list && aws_tls_is_alpn_available()) { + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, "id=%p: Setting ALPN data as %s", handler, aws_string_c_str(sc_handler->alpn_list)); + size_t extension_length = 0; + if (s_fillin_alpn_data(handler, alpn_buffer_data, sizeof(alpn_buffer_data), &extension_length)) { + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + + input_buf.pvBuffer = alpn_buffer_data, input_buf.cbBuffer = (unsigned long)extension_length, + input_buf.BufferType = SECBUFFER_APPLICATION_PROTOCOLS; + + alpn_sspi_data = &input_buf_desc; + } +#endif /* SECBUFFER_APPLICATION_PROTOCOLS*/ + + sc_handler->ctx_req = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | + ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; + + SecBuffer output_buffer = { + .pvBuffer = NULL, + .cbBuffer = 0, + .BufferType = SECBUFFER_EMPTY, + }; + + SecBufferDesc output_buffer_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 1, + .pBuffers = &output_buffer, + }; + + char server_name_cstr[256]; + AWS_ZERO_ARRAY(server_name_cstr); + AWS_ASSERT(sc_handler->server_name.len < 256); + memcpy(server_name_cstr, sc_handler->server_name.buffer, sc_handler->server_name.len); + + SECURITY_STATUS status = InitializeSecurityContextA( + &sc_handler->creds, + NULL, + (SEC_CHAR *)server_name_cstr, + sc_handler->ctx_req, + 0, + 0, + alpn_sspi_data, + 0, + &sc_handler->sec_handle, + &output_buffer_desc, + &sc_handler->ctx_ret_flags, + &sc_handler->sspi_timestamp); + + if (status != SEC_I_CONTINUE_NEEDED) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: Error sending client/receiving server handshake data. SECURITY_STATUS is %d", + (void *)handler, + (int)status); + int aws_error = s_determine_sspi_error(status); + aws_raise_error(aws_error); + s_invoke_negotiation_error(handler, aws_error); + return AWS_OP_ERR; + } + + size_t data_to_write_len = output_buffer.cbBuffer; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, "id=%p: Sending client handshake data of size %zu", (void *)handler, data_to_write_len); + + struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( + sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, data_to_write_len); + if (!outgoing_message) { + FreeContextBuffer(output_buffer.pvBuffer); + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + + AWS_ASSERT(outgoing_message->message_data.capacity >= data_to_write_len); + memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer); + outgoing_message->message_data.len = output_buffer.cbBuffer; + FreeContextBuffer(output_buffer.pvBuffer); + + if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { + aws_mem_release(outgoing_message->allocator, outgoing_message); + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + + sc_handler->s_connection_state_fn = s_do_client_side_negotiation_step_2; + + return AWS_OP_SUCCESS; +} + +/* cipher exchange, key exchange etc.... */ +static int s_do_client_side_negotiation_step_2(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: running step 2 of client-side negotiation (cipher change, key exchange etc...)", + (void *)handler); + + SecBuffer input_buffers[] = { + [0] = + { + .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer, + .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len, + .BufferType = SECBUFFER_TOKEN, + }, + [1] = + { + .pvBuffer = NULL, + .cbBuffer = 0, + .BufferType = SECBUFFER_EMPTY, + }, + }; + + SecBufferDesc input_buffers_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 2, + .pBuffers = input_buffers, + }; + + SecBuffer output_buffers[3]; + AWS_ZERO_ARRAY(output_buffers); + output_buffers[0].BufferType = SECBUFFER_TOKEN; + output_buffers[1].BufferType = SECBUFFER_ALERT; + + SecBufferDesc output_buffers_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 3, + .pBuffers = output_buffers, + }; + + SECURITY_STATUS status = SEC_E_OK; + + sc_handler->read_extra = 0; + sc_handler->estimated_incomplete_size = 0; + + char server_name_cstr[256]; + AWS_ZERO_ARRAY(server_name_cstr); + AWS_FATAL_ASSERT(sc_handler->server_name.len < sizeof(server_name_cstr)); + memcpy(server_name_cstr, sc_handler->server_name.buffer, sc_handler->server_name.len); + + status = InitializeSecurityContextA( + &sc_handler->creds, + &sc_handler->sec_handle, + (SEC_CHAR *)server_name_cstr, + sc_handler->ctx_req, + 0, + 0, + &input_buffers_desc, + 0, + NULL, + &output_buffers_desc, + &sc_handler->ctx_ret_flags, + &sc_handler->sspi_timestamp); + + if (status != SEC_E_INCOMPLETE_MESSAGE && status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "id=%p: Error during negotiation. SECURITY_STATUS is %d", (void *)handler, (int)status); + int aws_error = s_determine_sspi_error(status); + aws_raise_error(aws_error); + s_invoke_negotiation_error(handler, aws_error); + return AWS_OP_ERR; + } + + if (status == SEC_E_INCOMPLETE_MESSAGE) { + sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Incomplete buffer recieved. Incomplete size is %zu. Waiting for more data.", + (void *)handler, + sc_handler->estimated_incomplete_size); + return aws_raise_error(AWS_IO_READ_WOULD_BLOCK); + } + + if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) { + for (size_t i = 0; i < output_buffers_desc.cBuffers; ++i) { + SecBuffer *buf_ptr = &output_buffers[i]; + + if (buf_ptr->BufferType == SECBUFFER_TOKEN && buf_ptr->cbBuffer) { + struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( + sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, buf_ptr->cbBuffer); + + if (!outgoing_message) { + FreeContextBuffer(buf_ptr->pvBuffer); + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + + memcpy(outgoing_message->message_data.buffer, buf_ptr->pvBuffer, buf_ptr->cbBuffer); + outgoing_message->message_data.len = buf_ptr->cbBuffer; + FreeContextBuffer(buf_ptr->pvBuffer); + + if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { + aws_mem_release(outgoing_message->allocator, outgoing_message); + s_invoke_negotiation_error(handler, aws_last_error()); + return AWS_OP_ERR; + } + } + } + + if (input_buffers[1].BufferType == SECBUFFER_EXTRA && input_buffers[1].cbBuffer > 0) { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Extra data recieved. Extra data size is %lu.", + (void *)handler, + input_buffers[1].cbBuffer); + sc_handler->read_extra = input_buffers[1].cbBuffer; + } + } + + if (status == SEC_E_OK) { + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: handshake completed", handler); + /* if a custom CA store was configured, we have to do the verification ourselves. */ + if (sc_handler->custom_ca_store) { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Custom CA was configured, evaluating trust before completing connection", + (void *)handler); + if (s_manually_verify_peer_cert(handler)) { + aws_raise_error(AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); + s_invoke_negotiation_error(handler, AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); + return AWS_OP_ERR; + } + } + sc_handler->negotiation_finished = true; + /* force the sizes query, so future Encrypt message calls work.*/ + s_message_overhead(handler); + +#ifdef SECBUFFER_APPLICATION_PROTOCOLS + if (sc_handler->alpn_list && aws_tls_is_alpn_available()) { + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Retrieving negotiated protocol.", handler); + SecPkgContext_ApplicationProtocol alpn_result; + status = QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result); + + if (status == SEC_E_OK && alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { + aws_byte_buf_init(&sc_handler->protocol, handler->alloc, alpn_result.ProtocolIdSize + 1); + memset(sc_handler->protocol.buffer, 0, alpn_result.ProtocolIdSize + 1); + memcpy(sc_handler->protocol.buffer, alpn_result.ProtocolId, alpn_result.ProtocolIdSize); + sc_handler->protocol.len = alpn_result.ProtocolIdSize; + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, "id=%p: Negotiated protocol %s", handler, (char *)sc_handler->protocol.buffer); + } else { + AWS_LOGF_WARN( + AWS_LS_IO_TLS, + "id=%p: Error retrieving negotiated protocol. SECURITY_STATUS is %d", + handler, + (int)status); + int aws_error = s_determine_sspi_error(status); + aws_raise_error(aws_error); + } + } +#endif + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: TLS handshake completed successfully.", (void *)handler); + sc_handler->s_connection_state_fn = s_do_application_data_decrypt; + s_on_negotiation_success(handler); + } + + return AWS_OP_SUCCESS; +} + +static int s_do_application_data_decrypt(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + + /* I know this is an unncessary initialization, it's initialized here to make linters happy.*/ + int error = AWS_OP_ERR; + /* when we get an Extra buffer we have to move the pointer and replay the buffer, so we loop until we don't have + any extra buffers left over, in the last phase, we then go ahead and send the output. This state function will + always say BLOCKED_ON_READ, AWS_IO_TLS_ERROR_READ_FAILURE or SUCCESS. There will never be left over reads.*/ + do { + error = AWS_OP_ERR; + /* 4 buffers are needed, only one is input, the others get zeroed out for the output operation. */ + SecBuffer input_buffers[4]; + AWS_ZERO_ARRAY(input_buffers); + + size_t read_len = sc_handler->read_extra ? sc_handler->read_extra : sc_handler->buffered_read_in_data_buf.len; + size_t offset = sc_handler->read_extra ? sc_handler->buffered_read_in_data_buf.len - sc_handler->read_extra : 0; + sc_handler->read_extra = 0; + + input_buffers[0] = (SecBuffer){ + .cbBuffer = (unsigned long)(read_len), + .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer + offset, + .BufferType = SECBUFFER_DATA, + }; + + SecBufferDesc buffer_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 4, + .pBuffers = input_buffers, + }; + + SECURITY_STATUS status = DecryptMessage(&sc_handler->sec_handle, &buffer_desc, 0, NULL); + + if (status == SEC_E_OK) { + error = AWS_OP_SUCCESS; + /* if SECBUFFER_DATA is the buffer type of the second buffer, we have decrypted data to process. + If SECBUFFER_DATA is the type for the fourth buffer we need to keep track of it so we can shift + everything before doing another decrypt operation. + We don't care what's in the third buffer for TLS usage.*/ + if (input_buffers[1].BufferType == SECBUFFER_DATA) { + size_t decrypted_length = input_buffers[1].cbBuffer; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, "id=%p: Decrypted message with length %zu.", (void *)handler, decrypted_length); + + struct aws_byte_cursor to_append = + aws_byte_cursor_from_array(input_buffers[1].pvBuffer, decrypted_length); + int append_failed = aws_byte_buf_append(&sc_handler->buffered_read_out_data_buf, &to_append); + AWS_ASSERT(!append_failed); + (void)append_failed; + + /* if we have extra we have to move the pointer and do another Decrypt operation. */ + if (input_buffers[3].BufferType == SECBUFFER_EXTRA) { + sc_handler->read_extra = input_buffers[3].cbBuffer; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Extra (incomplete) message received with length %zu.", + (void *)handler, + sc_handler->read_extra); + } else { + error = AWS_OP_SUCCESS; + /* this means we processed everything in the buffer. */ + sc_handler->buffered_read_in_data_buf.len = 0; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Decrypt ended exactly on the end of the record, resetting buffer.", + (void *)handler); + } + } + } + /* SEC_E_INCOMPLETE_MESSAGE means the message we tried to decrypt isn't a full record and we need to + append our next read to it and try again. */ + else if (status == SEC_E_INCOMPLETE_MESSAGE) { + sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: (incomplete) message received. Expecting remaining portion of size %zu.", + (void *)handler, + sc_handler->estimated_incomplete_size); + memmove( + sc_handler->buffered_read_in_data_buf.buffer, + sc_handler->buffered_read_in_data_buf.buffer + offset, + read_len); + sc_handler->buffered_read_in_data_buf.len = read_len; + aws_raise_error(AWS_IO_READ_WOULD_BLOCK); + } + /* SEC_I_CONTEXT_EXPIRED means that the message sender has shut down the connection. One such case + where this can happen is an unaccepted certificate. */ + else if (status == SEC_I_CONTEXT_EXPIRED) { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Alert received. Message sender has shut down the connection. SECURITY_STATUS is %d.", + (void *)handler, + (int)status); + + struct aws_channel_slot *slot = handler->slot; + aws_channel_shutdown(slot->channel, AWS_OP_SUCCESS); + error = AWS_OP_SUCCESS; + } else { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "id=%p: Error decrypting message. SECURITY_STATUS is %d.", (void *)handler, (int)status); + aws_raise_error(AWS_IO_TLS_ERROR_READ_FAILURE); + } + } while (sc_handler->read_extra); + + return error; +} + +static int s_process_pending_output_messages(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + + size_t downstream_window = SIZE_MAX; + + if (sc_handler->slot->adj_right) { + downstream_window = aws_channel_slot_downstream_read_window(sc_handler->slot); + } + + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Processing incomming messages. Downstream window is %zu", + (void *)handler, + downstream_window); + while (sc_handler->buffered_read_out_data_buf.len && downstream_window) { + size_t requested_message_size = sc_handler->buffered_read_out_data_buf.len > downstream_window + ? downstream_window + : sc_handler->buffered_read_out_data_buf.len; + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Requested message size is %zu", (void *)handler, requested_message_size); + + if (sc_handler->slot->adj_right) { + struct aws_io_message *read_out_msg = aws_channel_acquire_message_from_pool( + sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, requested_message_size); + + if (!read_out_msg) { + return AWS_OP_ERR; + } + + size_t copy_size = read_out_msg->message_data.capacity < requested_message_size + ? read_out_msg->message_data.capacity + : requested_message_size; + + memcpy(read_out_msg->message_data.buffer, sc_handler->buffered_read_out_data_buf.buffer, copy_size); + read_out_msg->message_data.len = copy_size; + + memmove( + sc_handler->buffered_read_out_data_buf.buffer, + sc_handler->buffered_read_out_data_buf.buffer + copy_size, + sc_handler->buffered_read_out_data_buf.len - copy_size); + sc_handler->buffered_read_out_data_buf.len -= copy_size; + + if (sc_handler->on_data_read) { + sc_handler->on_data_read(handler, sc_handler->slot, &read_out_msg->message_data, sc_handler->user_data); + } + if (aws_channel_slot_send_message(sc_handler->slot, read_out_msg, AWS_CHANNEL_DIR_READ)) { + aws_mem_release(read_out_msg->allocator, read_out_msg); + return AWS_OP_ERR; + } + + if (sc_handler->slot->adj_right) { + downstream_window = aws_channel_slot_downstream_read_window(sc_handler->slot); + } + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Downstream window is %zu", (void *)handler, downstream_window); + } else { + if (sc_handler->on_data_read) { + sc_handler->on_data_read( + handler, sc_handler->slot, &sc_handler->buffered_read_out_data_buf, sc_handler->user_data); + } + sc_handler->buffered_read_out_data_buf.len = 0; + } + } + + return AWS_OP_SUCCESS; +} + +static void s_process_pending_output_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) { + (void)task; + struct aws_channel_handler *handler = arg; + + aws_channel_task_init(task, NULL, NULL, "secure_channel_handler_process_pending_output"); + if (status == AWS_TASK_STATUS_RUN_READY) { + if (s_process_pending_output_messages(handler)) { + struct secure_channel_handler *sc_handler = arg; + aws_channel_shutdown(sc_handler->slot->channel, aws_last_error()); + } + } +} + +static int s_process_read_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_io_message *message) { + + struct secure_channel_handler *sc_handler = handler->impl; + + if (message) { + /* note, most of these functions log internally, so the log messages in this function are sparse. */ + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: processing incoming message of size %zu", + (void *)handler, + message->message_data.len); + + struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data); + + /* The SSPI interface forces us to manage incomplete records manually. So when we had extra after + the previous read, it needs to be shifted to the beginning of the current read, then the current + read data is appended to it. If we had an incomplete record, we don't need to shift anything but + we do need to append the current read data to the end of the incomplete record from the previous read. + Keep going until we've processed everything in the message we were just passed. + */ + int err = AWS_OP_SUCCESS; + while (!err && message_cursor.len) { + + size_t available_buffer_space = + sc_handler->buffered_read_in_data_buf.capacity - sc_handler->buffered_read_in_data_buf.len; + size_t available_message_len = message_cursor.len; + size_t amount_to_move_to_buffer = + available_buffer_space > available_message_len ? available_message_len : available_buffer_space; + + memcpy( + sc_handler->buffered_read_in_data_buf.buffer + sc_handler->buffered_read_in_data_buf.len, + message_cursor.ptr, + amount_to_move_to_buffer); + sc_handler->buffered_read_in_data_buf.len += amount_to_move_to_buffer; + + err = sc_handler->s_connection_state_fn(handler); + + if (err && aws_last_error() == AWS_IO_READ_WOULD_BLOCK) { + if (sc_handler->buffered_read_in_data_buf.len == sc_handler->buffered_read_in_data_buf.capacity) { + /* throw this one as a protocol error. */ + aws_raise_error(AWS_IO_TLS_ERROR_WRITE_FAILURE); + } else { + if (sc_handler->buffered_read_out_data_buf.len) { + err = s_process_pending_output_messages(handler); + if (err) { + break; + } + } + /* prevent a deadlock due to downstream handlers wanting more data, but we have an incomplete + record, and the amount they're requesting is less than the size of a tls record. */ + size_t window_size = slot->window_size; + if (!window_size && + aws_channel_slot_increment_read_window(slot, sc_handler->estimated_incomplete_size)) { + err = AWS_OP_ERR; + } else { + sc_handler->estimated_incomplete_size = 0; + err = AWS_OP_SUCCESS; + } + } + aws_byte_cursor_advance(&message_cursor, amount_to_move_to_buffer); + continue; + } else if (err) { + break; + } + + /* handle any left over extra data from the decrypt operation here. */ + if (sc_handler->read_extra) { + size_t move_pos = sc_handler->buffered_read_in_data_buf.len - sc_handler->read_extra; + memmove( + sc_handler->buffered_read_in_data_buf.buffer, + sc_handler->buffered_read_in_data_buf.buffer + move_pos, + sc_handler->read_extra); + sc_handler->buffered_read_in_data_buf.len = sc_handler->read_extra; + sc_handler->read_extra = 0; + } else { + sc_handler->buffered_read_in_data_buf.len = 0; + } + + if (sc_handler->buffered_read_out_data_buf.len) { + err = s_process_pending_output_messages(handler); + if (err) { + break; + } + } + aws_byte_cursor_advance(&message_cursor, amount_to_move_to_buffer); + } + + if (!err) { + aws_mem_release(message->allocator, message); + return AWS_OP_SUCCESS; + } + + aws_channel_shutdown(slot->channel, aws_last_error()); + return AWS_OP_ERR; + } + + if (sc_handler->buffered_read_out_data_buf.len) { + if (s_process_pending_output_messages(handler)) { + return AWS_OP_ERR; + } + aws_mem_release(message->allocator, message); + } + + return AWS_OP_SUCCESS; +} + +static int s_process_write_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_io_message *message) { + + struct secure_channel_handler *sc_handler = (struct secure_channel_handler *)handler->impl; + AWS_ASSERT(sc_handler->negotiation_finished); + SECURITY_STATUS status = SEC_E_OK; + + if (message) { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, "id=%p: processing ougoing message of size %zu", (void *)handler, message->message_data.len); + + struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data); + + while (message_cursor.len) { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, "id=%p: processing message fragment of size %zu", (void *)handler, message_cursor.len); + /* message size will be the lesser of either payload + record overhead or the max TLS record size.*/ + size_t upstream_overhead = aws_channel_slot_upstream_message_overhead(sc_handler->slot); + upstream_overhead += sc_handler->stream_sizes.cbHeader + sc_handler->stream_sizes.cbTrailer; + size_t requested_length = message_cursor.len + upstream_overhead; + size_t to_write = sc_handler->stream_sizes.cbMaximumMessage < requested_length + ? sc_handler->stream_sizes.cbMaximumMessage + : requested_length; + struct aws_io_message *outgoing_message = + aws_channel_acquire_message_from_pool(slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, to_write); + + if (!outgoing_message) { + return AWS_OP_ERR; + } + if (outgoing_message->message_data.capacity <= upstream_overhead) { + aws_mem_release(outgoing_message->allocator, outgoing_message); + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + + /* what if message is larger than one record? */ + size_t original_message_fragment_to_process = outgoing_message->message_data.capacity - upstream_overhead; + memcpy( + outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader, + message_cursor.ptr, + original_message_fragment_to_process); + + if (original_message_fragment_to_process == message_cursor.len) { + outgoing_message->on_completion = message->on_completion; + outgoing_message->user_data = message->user_data; + } + + SecBuffer buffers[4] = { + [0] = + { + .BufferType = SECBUFFER_STREAM_HEADER, + .pvBuffer = outgoing_message->message_data.buffer, + .cbBuffer = sc_handler->stream_sizes.cbHeader, + }, + [1] = + { + .BufferType = SECBUFFER_DATA, + .pvBuffer = outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader, + .cbBuffer = (unsigned long)original_message_fragment_to_process, + }, + [2] = + { + .BufferType = SECBUFFER_STREAM_TRAILER, + .pvBuffer = outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader + + original_message_fragment_to_process, + .cbBuffer = sc_handler->stream_sizes.cbTrailer, + }, + [3] = + { + .BufferType = SECBUFFER_EMPTY, + .pvBuffer = NULL, + .cbBuffer = 0, + }, + }; + + SecBufferDesc buffer_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 4, + .pBuffers = buffers, + }; + + status = EncryptMessage(&sc_handler->sec_handle, 0, &buffer_desc, 0); + + if (status == SEC_E_OK) { + outgoing_message->message_data.len = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p:message fragment encrypted successfully: size is %zu", + (void *)handler, + outgoing_message->message_data.len); + + if (aws_channel_slot_send_message(slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { + aws_mem_release(outgoing_message->allocator, outgoing_message); + return AWS_OP_ERR; + } + + aws_byte_cursor_advance(&message_cursor, original_message_fragment_to_process); + } else { + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, + "id=%p: Error encrypting message. SECURITY_STATUS is %d", + (void *)handler, + (int)status); + return aws_raise_error(AWS_IO_TLS_ERROR_WRITE_FAILURE); + } + } + + aws_mem_release(message->allocator, message); + } + + return AWS_OP_SUCCESS; +} + +static int s_increment_read_window(struct aws_channel_handler *handler, struct aws_channel_slot *slot, size_t size) { + (void)size; + struct secure_channel_handler *sc_handler = handler->impl; + AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Increment read window message received %zu", (void *)handler, size); + + /* You can't query a context if negotiation isn't completed, since ciphers haven't been negotiated + * and it couldn't possibly know the overhead size yet. */ + if (sc_handler->negotiation_finished && !sc_handler->stream_sizes.cbMaximumMessage) { + SECURITY_STATUS status = + QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_STREAM_SIZES, &sc_handler->stream_sizes); + + if (status != SEC_E_OK) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "id=%p: QueryContextAttributes failed with error %d", (void *)handler, (int)status); + aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + aws_channel_shutdown(slot->channel, AWS_ERROR_SYS_CALL_FAILURE); + return AWS_OP_ERR; + } + } + + size_t total_desired_size = size; + size_t downstream_size = aws_channel_slot_downstream_read_window(slot); + size_t current_window_size = slot->window_size; + + /* the only time this branch isn't taken is when a window update is propagated during tls negotiation. + * in that case just pass it through. */ + if (sc_handler->stream_sizes.cbMaximumMessage) { + size_t likely_records_count = (size_t)ceil((double)(downstream_size) / (double)(READ_IN_SIZE)); + size_t offset_size = aws_mul_size_saturating( + likely_records_count, sc_handler->stream_sizes.cbTrailer + sc_handler->stream_sizes.cbHeader); + total_desired_size = aws_add_size_saturating(offset_size, downstream_size); + } + + if (total_desired_size > current_window_size) { + size_t window_update_size = total_desired_size - current_window_size; + AWS_LOGF_TRACE( + AWS_LS_IO_TLS, "id=%p: Propagating read window increment of size %zu", (void *)handler, window_update_size); + aws_channel_slot_increment_read_window(slot, window_update_size); + } + + if (sc_handler->negotiation_finished && !sc_handler->sequential_task_storage.task_fn) { + aws_channel_task_init( + &sc_handler->sequential_task_storage, + s_process_pending_output_task, + handler, + "secure_channel_handler_process_pending_output_on_window_increment"); + aws_channel_schedule_task_now(slot->channel, &sc_handler->sequential_task_storage); + } + return AWS_OP_SUCCESS; +} + +static size_t s_initial_window_size(struct aws_channel_handler *handler) { + (void)handler; + + /* set this to just enough for the handshake, once the handshake completes, the downstream + handler will tell us the new window size. */ + return EST_HANDSHAKE_SIZE; +} + +static int s_handler_shutdown( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + enum aws_channel_direction dir, + int error_code, + bool abort_immediately) { + struct secure_channel_handler *sc_handler = handler->impl; + + if (dir == AWS_CHANNEL_DIR_WRITE) { + if (!abort_immediately && error_code != AWS_IO_SOCKET_CLOSED) { + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Shutting down the write direction", (void *)handler); + + /* send a TLS alert. */ + SECURITY_STATUS status; + + DWORD shutdown_code = SCHANNEL_SHUTDOWN; + SecBuffer shutdown_buffer = { + .pvBuffer = &shutdown_code, + .cbBuffer = sizeof(shutdown_code), + .BufferType = SECBUFFER_TOKEN, + }; + + SecBufferDesc shutdown_buffer_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 1, + .pBuffers = &shutdown_buffer, + }; + + /* this updates the SSPI internal state machine. */ + status = ApplyControlToken(&sc_handler->sec_handle, &shutdown_buffer_desc); + + if (status != SEC_E_OK) { + aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + return aws_channel_slot_on_handler_shutdown_complete( + slot, dir, AWS_ERROR_SYS_CALL_FAILURE, abort_immediately); + } + + SecBuffer output_buffer = { + .pvBuffer = NULL, + .cbBuffer = 0, + .BufferType = SECBUFFER_EMPTY, + }; + + SecBufferDesc output_buffer_desc = { + .ulVersion = SECBUFFER_VERSION, + .cBuffers = 1, + .pBuffers = &output_buffer, + }; + + struct aws_byte_buf server_name = aws_tls_handler_server_name(handler); + char server_name_cstr[256]; + AWS_ZERO_ARRAY(server_name_cstr); + AWS_FATAL_ASSERT(server_name.len < sizeof(server_name_cstr)); + memcpy(server_name_cstr, server_name.buffer, server_name.len); + /* this acutally gives us an Alert record to send. */ + status = InitializeSecurityContextA( + &sc_handler->creds, + &sc_handler->sec_handle, + (SEC_CHAR *)server_name_cstr, + sc_handler->ctx_req, + 0, + 0, + NULL, + 0, + NULL, + &output_buffer_desc, + &sc_handler->ctx_ret_flags, + NULL); + + if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED) { + struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( + slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, output_buffer.cbBuffer); + + if (!outgoing_message || outgoing_message->message_data.capacity < output_buffer.cbBuffer) { + return aws_channel_slot_on_handler_shutdown_complete(slot, dir, aws_last_error(), true); + } + memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer); + outgoing_message->message_data.len = output_buffer.cbBuffer; + + /* we don't really care if this succeeds or not, it's just sending the TLS alert. */ + if (aws_channel_slot_send_message(slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { + aws_mem_release(outgoing_message->allocator, outgoing_message); + } + } + } + } + + return aws_channel_slot_on_handler_shutdown_complete(slot, dir, error_code, abort_immediately); +} + +static void s_do_negotiation_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct aws_channel_handler *handler = arg; + struct secure_channel_handler *sc_handler = handler->impl; + + if (status == AWS_TASK_STATUS_RUN_READY) { + int err = sc_handler->s_connection_state_fn(handler); + if (err) { + aws_channel_shutdown(sc_handler->slot->channel, aws_last_error()); + } + } +} + +static void s_secure_channel_handler_destroy( + struct aws_allocator *allocator, + struct secure_channel_handler *sc_handler) { + + if (sc_handler == NULL) { + return; + } + + if (sc_handler->protocol.buffer) { + aws_byte_buf_clean_up(&sc_handler->protocol); + } + + if (sc_handler->alpn_list) { + aws_string_destroy(sc_handler->alpn_list); + } + + if (sc_handler->server_name.buffer) { + aws_byte_buf_clean_up(&sc_handler->server_name); + } + + if (sc_handler->sec_handle.dwLower || sc_handler->sec_handle.dwUpper) { + DeleteSecurityContext(&sc_handler->sec_handle); + } + + if (sc_handler->creds.dwLower || sc_handler->creds.dwUpper) { + DeleteSecurityContext(&sc_handler->creds); + } + + aws_tls_channel_handler_shared_clean_up(&sc_handler->shared_state); + + aws_mem_release(allocator, sc_handler); +} + +static void s_handler_destroy(struct aws_channel_handler *handler) { + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: destroying handler", (void *)handler); + struct secure_channel_handler *sc_handler = handler->impl; + + s_secure_channel_handler_destroy(handler->alloc, sc_handler); +} + +static void s_reset_statistics(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + + aws_crt_statistics_tls_reset(&sc_handler->shared_state.stats); +} + +static void s_gather_statistics(struct aws_channel_handler *handler, struct aws_array_list *stats) { + struct secure_channel_handler *sc_handler = handler->impl; + + void *stats_base = &sc_handler->shared_state.stats; + aws_array_list_push_back(stats, &stats_base); +} + +int aws_tls_client_handler_start_negotiation(struct aws_channel_handler *handler) { + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Kicking off TLS negotiation", (void *)handler); + + struct secure_channel_handler *sc_handler = handler->impl; + + if (aws_channel_thread_is_callers_thread(sc_handler->slot->channel)) { + int err = sc_handler->s_connection_state_fn(handler); + if (err) { + aws_channel_shutdown(sc_handler->slot->channel, aws_last_error()); + } + return err; + } + + aws_channel_task_init( + &sc_handler->sequential_task_storage, + s_do_negotiation_task, + handler, + "secure_channel_handler_start_negotation"); + aws_channel_schedule_task_now(sc_handler->slot->channel, &sc_handler->sequential_task_storage); + return AWS_OP_SUCCESS; +} + +struct aws_byte_buf aws_tls_handler_protocol(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + return sc_handler->protocol; +} + +struct aws_byte_buf aws_tls_handler_server_name(struct aws_channel_handler *handler) { + struct secure_channel_handler *sc_handler = handler->impl; + return sc_handler->server_name; +} + +static struct aws_channel_handler_vtable s_handler_vtable = { + .destroy = s_handler_destroy, + .process_read_message = s_process_read_message, + .process_write_message = s_process_write_message, + .shutdown = s_handler_shutdown, + .increment_read_window = s_increment_read_window, + .initial_window_size = s_initial_window_size, + .message_overhead = s_message_overhead, + .reset_statistics = s_reset_statistics, + .gather_statistics = s_gather_statistics, +}; + +static struct aws_channel_handler *s_tls_handler_new( + struct aws_allocator *alloc, + struct aws_tls_connection_options *options, + struct aws_channel_slot *slot, + bool is_client_mode) { + AWS_ASSERT(options->ctx); + + struct secure_channel_handler *sc_handler = aws_mem_calloc(alloc, 1, sizeof(struct secure_channel_handler)); + if (!sc_handler) { + return NULL; + } + + sc_handler->handler.alloc = alloc; + sc_handler->handler.impl = sc_handler; + sc_handler->handler.vtable = &s_handler_vtable; + sc_handler->handler.slot = slot; + + aws_tls_channel_handler_shared_init(&sc_handler->shared_state, &sc_handler->handler, options); + + struct secure_channel_ctx *sc_ctx = options->ctx->impl; + + unsigned long credential_use = SECPKG_CRED_INBOUND; + if (is_client_mode) { + credential_use = SECPKG_CRED_OUTBOUND; + } + + SECURITY_STATUS status = AcquireCredentialsHandleA( + NULL, + UNISP_NAME, + credential_use, + NULL, + &sc_ctx->credentials, + NULL, + NULL, + &sc_handler->creds, + &sc_handler->sspi_timestamp); + + if (status != SEC_E_OK) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "Error on AcquireCredentialsHandle. SECURITY_STATUS is %d", (int)status); + int aws_error = s_determine_sspi_error(status); + aws_raise_error(aws_error); + goto on_error; + } + + sc_handler->advertise_alpn_message = options->advertise_alpn_message; + sc_handler->on_data_read = options->on_data_read; + sc_handler->on_error = options->on_error; + sc_handler->on_negotiation_result = options->on_negotiation_result; + sc_handler->user_data = options->user_data; + + if (!options->alpn_list && sc_ctx->alpn_list) { + sc_handler->alpn_list = aws_string_new_from_string(alloc, sc_ctx->alpn_list); + if (!sc_handler->alpn_list) { + goto on_error; + } + } else if (options->alpn_list) { + sc_handler->alpn_list = aws_string_new_from_string(alloc, options->alpn_list); + if (!sc_handler->alpn_list) { + goto on_error; + } + } + + if (options->server_name) { + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "id=%p: Setting SNI to %s", + (void *)&sc_handler->handler, + aws_string_c_str(options->server_name)); + struct aws_byte_cursor server_name_crsr = aws_byte_cursor_from_string(options->server_name); + if (aws_byte_buf_init_copy_from_cursor(&sc_handler->server_name, alloc, server_name_crsr)) { + goto on_error; + } + } + + sc_handler->slot = slot; + + if (is_client_mode) { + sc_handler->s_connection_state_fn = s_do_client_side_negotiation_step_1; + } else { + sc_handler->s_connection_state_fn = s_do_server_side_negotiation_step_1; + } + + sc_handler->custom_ca_store = sc_ctx->custom_trust_store; + sc_handler->buffered_read_in_data_buf = + aws_byte_buf_from_array(sc_handler->buffered_read_in_data, sizeof(sc_handler->buffered_read_in_data)); + sc_handler->buffered_read_in_data_buf.len = 0; + sc_handler->buffered_read_out_data_buf = + aws_byte_buf_from_array(sc_handler->buffered_read_out_data, sizeof(sc_handler->buffered_read_out_data)); + sc_handler->buffered_read_out_data_buf.len = 0; + sc_handler->verify_peer = sc_ctx->verify_peer; + + return &sc_handler->handler; + +on_error: + + s_secure_channel_handler_destroy(alloc, sc_handler); + + return NULL; +} +struct aws_channel_handler *aws_tls_client_handler_new( + struct aws_allocator *allocator, + struct aws_tls_connection_options *options, + struct aws_channel_slot *slot) { + + return s_tls_handler_new(allocator, options, slot, true); +} + +struct aws_channel_handler *aws_tls_server_handler_new( + struct aws_allocator *allocator, + struct aws_tls_connection_options *options, + struct aws_channel_slot *slot) { + + return s_tls_handler_new(allocator, options, slot, false); +} + +static void s_secure_channel_ctx_destroy(struct secure_channel_ctx *secure_channel_ctx) { + if (secure_channel_ctx == NULL) { + return; + } + + if (secure_channel_ctx->private_key) { + CryptDestroyKey(secure_channel_ctx->private_key); + } + + if (secure_channel_ctx->crypto_provider) { + CryptReleaseContext(secure_channel_ctx->crypto_provider, 0); + } + + if (secure_channel_ctx->custom_trust_store) { + aws_close_cert_store(secure_channel_ctx->custom_trust_store); + } + + if (secure_channel_ctx->pcerts) { + /** + * Only free the private certificate context if the private key is NOT + * from the certificate context because freeing the private key + * using CryptDestroyKey frees the certificate context and then + * trying to access it leads to a access violation. + */ + if (secure_channel_ctx->should_free_pcerts == true) { + CertFreeCertificateContext(secure_channel_ctx->pcerts); + } + } + + if (secure_channel_ctx->cert_store) { + aws_close_cert_store(secure_channel_ctx->cert_store); + } + + if (secure_channel_ctx->alpn_list) { + aws_string_destroy(secure_channel_ctx->alpn_list); + } + + aws_mem_release(secure_channel_ctx->ctx.alloc, secure_channel_ctx); +} + +struct aws_tls_ctx *s_ctx_new( + struct aws_allocator *alloc, + const struct aws_tls_ctx_options *options, + bool is_client_mode) { + + if (!aws_tls_is_cipher_pref_supported(options->cipher_pref)) { + aws_raise_error(AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED); + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: TLS Cipher Preference is not supported: %d.", options->cipher_pref); + return NULL; + } + + struct secure_channel_ctx *secure_channel_ctx = aws_mem_calloc(alloc, 1, sizeof(struct secure_channel_ctx)); + if (!secure_channel_ctx) { + return NULL; + } + + secure_channel_ctx->ctx.alloc = alloc; + secure_channel_ctx->ctx.impl = secure_channel_ctx; + aws_ref_count_init( + &secure_channel_ctx->ctx.ref_count, + secure_channel_ctx, + (aws_simple_completion_callback *)s_secure_channel_ctx_destroy); + + if (options->alpn_list) { + secure_channel_ctx->alpn_list = aws_string_new_from_string(alloc, options->alpn_list); + if (!secure_channel_ctx->alpn_list) { + goto clean_up; + } + } + + secure_channel_ctx->verify_peer = options->verify_peer; + secure_channel_ctx->credentials.dwVersion = SCHANNEL_CRED_VERSION; + secure_channel_ctx->should_free_pcerts = true; + + secure_channel_ctx->credentials.grbitEnabledProtocols = 0; + + if (is_client_mode) { + switch (options->minimum_tls_version) { + case AWS_IO_SSLv3: + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_SSL3_CLIENT; + case AWS_IO_TLSv1: + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_0_CLIENT; + case AWS_IO_TLSv1_1: + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_1_CLIENT; + case AWS_IO_TLSv1_2: +#if defined(SP_PROT_TLS1_2_CLIENT) + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_2_CLIENT; +#endif + case AWS_IO_TLSv1_3: +#if defined(SP_PROT_TLS1_3_CLIENT) + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_3_CLIENT; +#endif + break; + case AWS_IO_TLS_VER_SYS_DEFAULTS: + secure_channel_ctx->credentials.grbitEnabledProtocols = 0; + break; + } + } else { + switch (options->minimum_tls_version) { + case AWS_IO_SSLv3: + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_SSL3_SERVER; + case AWS_IO_TLSv1: + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_0_SERVER; + case AWS_IO_TLSv1_1: + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_1_SERVER; + case AWS_IO_TLSv1_2: +#if defined(SP_PROT_TLS1_2_SERVER) + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_2_SERVER; +#endif + case AWS_IO_TLSv1_3: +#if defined(SP_PROT_TLS1_3_SERVER) + secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_3_SERVER; +#endif + break; + case AWS_IO_TLS_VER_SYS_DEFAULTS: + secure_channel_ctx->credentials.grbitEnabledProtocols = 0; + break; + } + } + + if (options->verify_peer && aws_tls_options_buf_is_set(&options->ca_file)) { + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: loading custom CA file."); + secure_channel_ctx->credentials.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION; + + struct aws_byte_cursor ca_blob_cur = aws_byte_cursor_from_buf(&options->ca_file); + int error = aws_import_trusted_certificates(alloc, &ca_blob_cur, &secure_channel_ctx->custom_trust_store); + + if (error) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import custom CA with error %d", aws_last_error()); + goto clean_up; + } + } else if (is_client_mode) { + secure_channel_ctx->credentials.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION; + } + + if (is_client_mode && !options->verify_peer) { + AWS_LOGF_WARN( + AWS_LS_IO_TLS, + "static: x.509 validation has been disabled. " + "If this is not running in a test environment, this is likely a security vulnerability."); + + secure_channel_ctx->credentials.dwFlags &= ~(SCH_CRED_AUTO_CRED_VALIDATION); + secure_channel_ctx->credentials.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | + SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_NO_SERVERNAME_CHECK | + SCH_CRED_MANUAL_CRED_VALIDATION; + } else if (is_client_mode) { + secure_channel_ctx->credentials.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN | SCH_CRED_IGNORE_REVOCATION_OFFLINE; + } + + /* if someone wants to use broken algorithms like rc4/md5/des they'll need to ask for a special control */ + secure_channel_ctx->credentials.dwFlags |= SCH_USE_STRONG_CRYPTO; + + /* if using a system store. */ + if (options->system_certificate_path) { + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: assuming certificate is in a system store, loading now."); + + if (aws_load_cert_from_system_cert_store( + options->system_certificate_path, &secure_channel_ctx->cert_store, &secure_channel_ctx->pcerts)) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to load %s", options->system_certificate_path); + goto clean_up; + } + + secure_channel_ctx->credentials.paCred = &secure_channel_ctx->pcerts; + secure_channel_ctx->credentials.cCreds = 1; + /* if using traditional PEM armored PKCS#7 and ASN Encoding public/private key pairs */ + } else if (aws_tls_options_buf_is_set(&options->certificate) && aws_tls_options_buf_is_set(&options->private_key)) { + + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: certificate and key have been set, setting them up now."); + + if (!aws_text_is_utf8(options->certificate.buffer, options->certificate.len)) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import certificate, must be ASCII/UTF-8 encoded"); + aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE); + goto clean_up; + } + + if (!aws_text_is_utf8(options->private_key.buffer, options->private_key.len)) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import private key, must be ASCII/UTF-8 encoded"); + aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE); + goto clean_up; + } + + struct aws_byte_cursor cert_chain_cur = aws_byte_cursor_from_buf(&options->certificate); + struct aws_byte_cursor pk_cur = aws_byte_cursor_from_buf(&options->private_key); + int err = aws_import_key_pair_to_cert_context( + alloc, + &cert_chain_cur, + &pk_cur, + is_client_mode, + &secure_channel_ctx->cert_store, + &secure_channel_ctx->pcerts, + &secure_channel_ctx->crypto_provider, + &secure_channel_ctx->private_key); + + if (err) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "static: failed to import certificate and private key with error %d.", aws_last_error()); + goto clean_up; + } + + secure_channel_ctx->credentials.paCred = &secure_channel_ctx->pcerts; + secure_channel_ctx->credentials.cCreds = 1; + secure_channel_ctx->should_free_pcerts = false; + } + + return &secure_channel_ctx->ctx; + +clean_up: + s_secure_channel_ctx_destroy(secure_channel_ctx); + return NULL; +} + +struct aws_tls_ctx *aws_tls_server_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) { + return s_ctx_new(alloc, options, false); +} + +struct aws_tls_ctx *aws_tls_client_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) { + return s_ctx_new(alloc, options, true); +} +>>>>>>> 5064829 (Add SOCKS5 proxy support) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 571b5d42a..822d1c280 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,14 @@ endmacro() add_test_case(io_library_init) add_test_case(io_library_init_cleanup_init_cleanup) add_test_case(io_library_error_order) +add_test_case(socks5_proxy_options_basic) +add_test_case(socks5_infer_address_type_cases) +add_test_case(socks5_context_init_lifecycle) +add_test_case(socks5_handshake_happy_path) +add_test_case(socks5_handshake_error_paths) +add_test_case(socks5_channel_handler_happy_path) +add_test_case(socks5_channel_handler_greeting_failure) +add_test_case(socks5_bootstrap_system_vtable_failure) # Dispatch Queue does not support pipe if(NOT AWS_USE_APPLE_NETWORK_FRAMEWORK) diff --git a/tests/socks5_test.c b/tests/socks5_test.c new file mode 100644 index 000000000..33a16f8a4 --- /dev/null +++ b/tests/socks5_test.c @@ -0,0 +1,1193 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int(s_channel_run_fn)(struct aws_channel *channel, void *user_data); + +struct channel_call_context { + struct aws_channel *channel; + s_channel_run_fn *fn; + void *user_data; + struct aws_mutex mutex; + struct aws_condition_variable condition; + bool completed; + int result; + int error_code; + struct aws_channel_task task; +}; + +static bool s_channel_call_complete_predicate(void *user_data) { + struct channel_call_context *context = user_data; + return context->completed; +} + +static void s_channel_call_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) { + (void)task; + + struct channel_call_context *context = arg; + int result = AWS_OP_ERR; + int error_code = AWS_ERROR_SUCCESS; + + if (status == AWS_TASK_STATUS_CANCELED) { + result = AWS_OP_ERR; + error_code = AWS_ERROR_INVALID_STATE; + } else { + result = context->fn(context->channel, context->user_data); + if (result != AWS_OP_SUCCESS) { + error_code = aws_last_error(); + } + } + + aws_mutex_lock(&context->mutex); + context->result = result; + context->error_code = error_code; + context->completed = true; + aws_mutex_unlock(&context->mutex); + aws_condition_variable_notify_one(&context->condition); +} + +static int s_channel_run_on_thread(struct aws_channel *channel, s_channel_run_fn *fn, void *user_data) { + + /* If already on the channel's thread, run directly; otherwise, schedule as a task */ + if (aws_channel_thread_is_callers_thread(channel)) { + return fn(channel, user_data); + } + + struct channel_call_context context; + AWS_ZERO_STRUCT(context); + + context.channel = channel; + context.fn = fn; + context.user_data = user_data; + context.result = AWS_OP_ERR; + context.completed = false; + context.error_code = AWS_ERROR_UNKNOWN; + + aws_mutex_init(&context.mutex); + aws_condition_variable_init(&context.condition); + aws_channel_task_init(&context.task, s_channel_call_task, &context, "socks5_test_channel_call"); + + aws_channel_schedule_task_now(channel, &context.task); + + aws_mutex_lock(&context.mutex); + int wait_result = aws_condition_variable_wait_pred( + &context.condition, &context.mutex, s_channel_call_complete_predicate, &context); + int result = context.result; + aws_mutex_unlock(&context.mutex); + + aws_condition_variable_clean_up(&context.condition); + aws_mutex_clean_up(&context.mutex); + + if (wait_result) { + int error_code = aws_last_error(); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: s_channel_run_on_thread wait failed with error %d (%s)", + (void *)channel, + error_code, + aws_error_str(error_code)); + return aws_raise_error(error_code); + } + + if (result == AWS_OP_SUCCESS) { + return AWS_OP_SUCCESS; + } + + if (context.error_code != AWS_ERROR_SUCCESS && context.error_code != AWS_ERROR_UNKNOWN) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: s_channel_run_on_thread task failed with error %d (%s)", + (void *)channel, + context.error_code, + aws_error_str(context.error_code)); + return aws_raise_error(context.error_code); + } + + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: s_channel_run_on_thread task failed with unknown error", + (void *)channel); + return aws_raise_error(AWS_ERROR_UNKNOWN); +} + +struct install_handler_args { + struct aws_channel_handler *handler; + struct aws_channel_slot **out_slot; +}; + +static int s_install_handler_on_thread(struct aws_channel *channel, void *user_data) { + struct install_handler_args *args = user_data; + + + /* Create a new slot for the handler in the channel */ + struct aws_channel_slot *slot = aws_channel_slot_new(channel); + if (!slot) { + return aws_raise_error(AWS_ERROR_OOM); + } + + struct aws_channel_slot *first_slot = aws_channel_get_first_slot(channel); + if (first_slot != slot) { + if (aws_channel_slot_insert_end(channel, slot)) { + int error_code = aws_last_error(); + aws_mem_release(slot->alloc, slot); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to insert slot at end, error %d (%s)", + (void *)channel, + error_code, + aws_error_str(error_code)); + return aws_raise_error(error_code); + } + } + + if (aws_channel_slot_set_handler(slot, args->handler)) { + int error_code = aws_last_error(); + aws_channel_slot_remove(slot); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKS5, + "id=%p: Failed to set handler on slot, error %d (%s)", + (void *)channel, + error_code, + aws_error_str(error_code)); + return aws_raise_error(error_code); + } + + *args->out_slot = slot; + return AWS_OP_SUCCESS; +} + +static int s_install_handler( + struct aws_channel *channel, + struct aws_channel_handler *handler, + struct aws_channel_slot **out_slot) { + + struct install_handler_args args = { + .handler = handler, + .out_slot = out_slot, + }; + return s_channel_run_on_thread(channel, s_install_handler_on_thread, &args); +} + +struct send_message_args { + struct aws_allocator *allocator; + struct aws_channel_slot *slot; + enum aws_channel_direction direction; + struct aws_byte_buf payload; +}; + +static int s_send_message_on_thread(struct aws_channel *channel, void *user_data) { + (void)channel; + + struct send_message_args *args = user_data; + + + /* Acquire a message from the pool and fill with payload */ + struct aws_io_message *message = aws_channel_acquire_message_from_pool( + args->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, args->payload.len); + if (!message) { + return AWS_OP_ERR; + } + + struct aws_byte_cursor payload_cursor = aws_byte_cursor_from_buf(&args->payload); + if (aws_byte_buf_append(&message->message_data, &payload_cursor)) { + aws_mem_release(message->allocator, message); + return AWS_OP_ERR; + } + + if (aws_channel_slot_send_message(args->slot, message, args->direction)) { + int error_code = aws_last_error(); + aws_mem_release(message->allocator, message); + return aws_raise_error(error_code); + } + + return AWS_OP_SUCCESS; +} + +static int s_channel_send_bytes( + struct aws_allocator *allocator, + struct aws_channel_slot *slot, + enum aws_channel_direction direction, + const uint8_t *data, + size_t len) { + + struct send_message_args args; + AWS_ZERO_STRUCT(args); + args.allocator = allocator; + args.slot = slot; + args.direction = direction; + + if (aws_byte_buf_init_copy_from_cursor( + &args.payload, allocator, aws_byte_cursor_from_array(data, len))) { + return AWS_OP_ERR; + } + + int result = s_channel_run_on_thread(slot->channel, s_send_message_on_thread, &args); + aws_byte_buf_clean_up(&args.payload); + return result; +} + +static int s_channel_send_cursor( + struct aws_allocator *allocator, + struct aws_channel_slot *slot, + enum aws_channel_direction direction, + struct aws_byte_cursor cursor) { + return s_channel_send_bytes(allocator, slot, direction, cursor.ptr, cursor.len); +} + +static int s_start_handshake_on_thread(struct aws_channel *channel, void *user_data) { + struct aws_channel_handler *handler = user_data; + return aws_socks5_channel_handler_start_handshake(handler); +} + +static int s_socks5_proxy_options_basic(struct aws_allocator *allocator, void *ctx) { + + /* Test basic initialization and configuration of SOCKS5 proxy options */ + (void)ctx; + + struct aws_socks5_proxy_options defaults; + ASSERT_SUCCESS(aws_socks5_proxy_options_init_default(&defaults)); + ASSERT_INT_EQUALS(1080, defaults.port); + ASSERT_INT_EQUALS(3000, defaults.connection_timeout_ms); + ASSERT_INT_EQUALS(AWS_SOCKS5_HOST_RESOLUTION_PROXY, defaults.host_resolution_mode); + aws_socks5_proxy_options_clean_up(&defaults); + + struct aws_socks5_proxy_options options; + AWS_ZERO_STRUCT(options); + struct aws_byte_cursor proxy_host = aws_byte_cursor_from_c_str("proxy.example.com"); + ASSERT_SUCCESS(aws_socks5_proxy_options_init(&options, allocator, proxy_host, 9000)); + ASSERT_NOT_NULL(options.host); + ASSERT_BIN_ARRAYS_EQUALS( + proxy_host.ptr, proxy_host.len, aws_string_bytes(options.host), options.host->len); + ASSERT_INT_EQUALS(9000, options.port); + ASSERT_INT_EQUALS(3000, options.connection_timeout_ms); + ASSERT_INT_EQUALS(AWS_SOCKS5_HOST_RESOLUTION_PROXY, options.host_resolution_mode); + + struct aws_byte_cursor username = aws_byte_cursor_from_c_str("user"); + struct aws_byte_cursor password = aws_byte_cursor_from_c_str("pass"); + ASSERT_SUCCESS(aws_socks5_proxy_options_set_auth(&options, allocator, username, password)); + ASSERT_NOT_NULL(options.username); + ASSERT_NOT_NULL(options.password); + ASSERT_BIN_ARRAYS_EQUALS( + username.ptr, username.len, aws_string_bytes(options.username), options.username->len); + ASSERT_BIN_ARRAYS_EQUALS( + password.ptr, password.len, aws_string_bytes(options.password), options.password->len); + + aws_socks5_proxy_options_set_host_resolution_mode(&options, AWS_SOCKS5_HOST_RESOLUTION_CLIENT); + ASSERT_INT_EQUALS(AWS_SOCKS5_HOST_RESOLUTION_CLIENT, aws_socks5_proxy_options_get_host_resolution_mode(&options)); + + aws_socks5_proxy_options_clean_up(&options); + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(socks5_proxy_options_basic, s_socks5_proxy_options_basic) + +static int s_socks5_infer_address_type_cases(struct aws_allocator *allocator, void *ctx) { + + /* Test address type inference for various host formats */ + (void)allocator; + (void)ctx; + + struct aws_byte_cursor ipv4_host = aws_byte_cursor_from_c_str("127.0.0.1"); + ASSERT_INT_EQUALS( + AWS_SOCKS5_ATYP_IPV4, aws_socks5_infer_address_type(ipv4_host, AWS_SOCKS5_ATYP_DOMAIN)); + + struct aws_byte_cursor ipv6_host = aws_byte_cursor_from_c_str("2001:db8::1"); + ASSERT_INT_EQUALS( + AWS_SOCKS5_ATYP_IPV6, aws_socks5_infer_address_type(ipv6_host, AWS_SOCKS5_ATYP_DOMAIN)); + + struct aws_byte_cursor bracketed_ipv6 = aws_byte_cursor_from_c_str("[fe80::1]"); + ASSERT_INT_EQUALS( + AWS_SOCKS5_ATYP_IPV6, aws_socks5_infer_address_type(bracketed_ipv6, AWS_SOCKS5_ATYP_DOMAIN)); + + struct aws_byte_cursor scoped_ipv6 = aws_byte_cursor_from_c_str("fe80::1%eth0"); + ASSERT_INT_EQUALS( + AWS_SOCKS5_ATYP_IPV6, aws_socks5_infer_address_type(scoped_ipv6, AWS_SOCKS5_ATYP_DOMAIN)); + + struct aws_byte_cursor domain_host = aws_byte_cursor_from_c_str("example.com"); + ASSERT_INT_EQUALS( + AWS_SOCKS5_ATYP_DOMAIN, aws_socks5_infer_address_type(domain_host, AWS_SOCKS5_ATYP_DOMAIN)); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(socks5_infer_address_type_cases, s_socks5_infer_address_type_cases) + +static int s_socks5_context_init_lifecycle(struct aws_allocator *allocator, void *ctx) { + + /* Test context initialization, cleanup, and error handling */ + (void)ctx; + + struct aws_socks5_proxy_options options; + AWS_ZERO_STRUCT(options); + struct aws_byte_cursor proxy_host = aws_byte_cursor_from_c_str("proxy.example.com"); + ASSERT_SUCCESS(aws_socks5_proxy_options_init(&options, allocator, proxy_host, 1080)); + + struct aws_byte_cursor username = aws_byte_cursor_from_c_str("user"); + struct aws_byte_cursor password = aws_byte_cursor_from_c_str("pass"); + ASSERT_SUCCESS(aws_socks5_proxy_options_set_auth(&options, allocator, username, password)); + + struct aws_socks5_context context; + AWS_ZERO_STRUCT(context); + struct aws_byte_cursor endpoint_host = aws_byte_cursor_from_c_str("destination.example.com"); + + ASSERT_SUCCESS(aws_socks5_context_init( + &context, + allocator, + &options, + endpoint_host, + 443, + AWS_SOCKS5_ATYP_DOMAIN)); + + ASSERT_NOT_NULL(context.endpoint_host); + ASSERT_BIN_ARRAYS_EQUALS( + endpoint_host.ptr, endpoint_host.len, aws_string_bytes(context.endpoint_host), context.endpoint_host->len); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_INIT, context.state); + ASSERT_INT_EQUALS(AWS_SOCKS5_ATYP_DOMAIN, context.endpoint_address_type); + ASSERT_UINT_EQUALS(2, aws_array_list_length(&context.auth_methods)); + + aws_socks5_context_clean_up(&context); + ASSERT_NULL(context.endpoint_host); + ASSERT_NULL(context.options.host); + ASSERT_UINT_EQUALS(0, context.auth_methods.length); + + struct aws_socks5_context bad_context; + AWS_ZERO_STRUCT(bad_context); + struct aws_byte_cursor empty_host = { + .ptr = NULL, + .len = 0, + }; + ASSERT_ERROR( + AWS_ERROR_INVALID_ARGUMENT, + aws_socks5_context_init( + &bad_context, + allocator, + &options, + empty_host, + 443, + AWS_SOCKS5_ATYP_DOMAIN)); + + aws_socks5_proxy_options_clean_up(&options); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(socks5_context_init_lifecycle, s_socks5_context_init_lifecycle) + +static int s_socks5_handshake_happy_path(struct aws_allocator *allocator, void *ctx) { + + /* Simulate a successful SOCKS5 handshake sequence */ + (void)ctx; + + struct aws_socks5_proxy_options options; + AWS_ZERO_STRUCT(options); + struct aws_byte_cursor proxy_host = aws_byte_cursor_from_c_str("proxy.example.com"); + ASSERT_SUCCESS(aws_socks5_proxy_options_init(&options, allocator, proxy_host, 1080)); + struct aws_byte_cursor username = aws_byte_cursor_from_c_str("user"); + struct aws_byte_cursor password = aws_byte_cursor_from_c_str("pass"); + ASSERT_SUCCESS(aws_socks5_proxy_options_set_auth(&options, allocator, username, password)); + + struct aws_socks5_context context; + AWS_ZERO_STRUCT(context); + struct aws_byte_cursor endpoint_host = aws_byte_cursor_from_c_str("destination.example.com"); + ASSERT_SUCCESS(aws_socks5_context_init( + &context, + allocator, + &options, + endpoint_host, + 443, + AWS_SOCKS5_ATYP_DOMAIN)); + + struct aws_byte_buf buffer; + ASSERT_SUCCESS(aws_byte_buf_init(&buffer, allocator, 64)); + + ASSERT_SUCCESS(aws_socks5_write_greeting(&context, &buffer)); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_GREETING_SENT, context.state); + ASSERT_UINT_EQUALS(4, buffer.len); /* VER + NMETHODS + 2 methods */ + ASSERT_UINT_EQUALS(AWS_SOCKS5_VERSION, buffer.buffer[0]); + ASSERT_UINT_EQUALS(2, buffer.buffer[1]); + + uint8_t greeting_resp[] = {AWS_SOCKS5_VERSION, AWS_SOCKS5_AUTH_USERNAME_PASSWORD}; + struct aws_byte_cursor cursor = aws_byte_cursor_from_array(greeting_resp, sizeof(greeting_resp)); + ASSERT_SUCCESS(aws_socks5_read_greeting_response(&context, &cursor)); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_GREETING_RECEIVED, context.state); + ASSERT_INT_EQUALS(AWS_SOCKS5_AUTH_USERNAME_PASSWORD, context.selected_auth); + + aws_byte_buf_reset(&buffer, false); + ASSERT_SUCCESS(aws_socks5_write_auth_request(&context, &buffer)); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_AUTH_STARTED, context.state); + ASSERT_UINT_EQUALS(3 + username.len + password.len, buffer.len); + ASSERT_UINT_EQUALS(AWS_SOCKS5_AUTH_VERSION, buffer.buffer[0]); + ASSERT_UINT_EQUALS(username.len, buffer.buffer[1]); + ASSERT_UINT_EQUALS(password.len, buffer.buffer[1 + 1 + username.len]); + + uint8_t auth_resp[] = {AWS_SOCKS5_AUTH_VERSION, 0}; + cursor = aws_byte_cursor_from_array(auth_resp, sizeof(auth_resp)); + ASSERT_SUCCESS(aws_socks5_read_auth_response(&context, &cursor)); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_AUTH_COMPLETED, context.state); + + aws_byte_buf_reset(&buffer, false); + ASSERT_SUCCESS(aws_socks5_write_connect_request(&context, &buffer)); + ASSERT_TRUE(buffer.len > AWS_SOCKS5_CONN_REQ_MIN_SIZE); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_REQUEST_SENT, context.state); + + uint8_t connect_resp[] = { + AWS_SOCKS5_VERSION, + AWS_SOCKS5_STATUS_SUCCESS, + AWS_SOCKS5_RESERVED, + AWS_SOCKS5_ATYP_IPV4, + 10, + 0, + 0, + 1, + 0, + 80}; + cursor = aws_byte_cursor_from_array(connect_resp, sizeof(connect_resp)); + ASSERT_SUCCESS(aws_socks5_read_connect_response(&context, &cursor)); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_CONNECTED, context.state); + + aws_byte_buf_clean_up(&buffer); + aws_socks5_context_clean_up(&context); + aws_socks5_proxy_options_clean_up(&options); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(socks5_handshake_happy_path, s_socks5_handshake_happy_path) + +static int s_socks5_handshake_error_paths(struct aws_allocator *allocator, void *ctx) { + + /* Test error handling for handshake failures (greeting, auth, connect) */ + (void)ctx; + + /* Greeting rejection */ + struct aws_socks5_proxy_options options_default; + AWS_ZERO_STRUCT(options_default); + struct aws_byte_cursor proxy_host = aws_byte_cursor_from_c_str("proxy.example.com"); + ASSERT_SUCCESS(aws_socks5_proxy_options_init(&options_default, allocator, proxy_host, 1080)); + + struct aws_socks5_context context_default; + AWS_ZERO_STRUCT(context_default); + struct aws_byte_cursor endpoint_host = aws_byte_cursor_from_c_str("endpoint.example.com"); + ASSERT_SUCCESS(aws_socks5_context_init( + &context_default, + allocator, + &options_default, + endpoint_host, + 80, + AWS_SOCKS5_ATYP_DOMAIN)); + + struct aws_byte_buf buffer; + ASSERT_SUCCESS(aws_byte_buf_init(&buffer, allocator, 32)); + ASSERT_SUCCESS(aws_socks5_write_greeting(&context_default, &buffer)); + + uint8_t reject_resp[] = {AWS_SOCKS5_VERSION, AWS_SOCKS5_AUTH_NO_ACCEPTABLE}; + struct aws_byte_cursor cursor = aws_byte_cursor_from_array(reject_resp, sizeof(reject_resp)); + ASSERT_ERROR( + AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_AUTH_METHOD, + aws_socks5_read_greeting_response(&context_default, &cursor)); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_ERROR, context_default.state); + + aws_byte_buf_clean_up(&buffer); + aws_socks5_context_clean_up(&context_default); + aws_socks5_proxy_options_clean_up(&options_default); + + /* Auth failure */ + struct aws_socks5_proxy_options auth_options; + AWS_ZERO_STRUCT(auth_options); + ASSERT_SUCCESS(aws_socks5_proxy_options_init(&auth_options, allocator, proxy_host, 1080)); + struct aws_byte_cursor username = aws_byte_cursor_from_c_str("user"); + struct aws_byte_cursor password = aws_byte_cursor_from_c_str("pass"); + ASSERT_SUCCESS(aws_socks5_proxy_options_set_auth(&auth_options, allocator, username, password)); + + struct aws_socks5_context auth_context; + AWS_ZERO_STRUCT(auth_context); + ASSERT_SUCCESS(aws_socks5_context_init( + &auth_context, + allocator, + &auth_options, + endpoint_host, + 443, + AWS_SOCKS5_ATYP_DOMAIN)); + + ASSERT_SUCCESS(aws_byte_buf_init(&buffer, allocator, 32)); + ASSERT_SUCCESS(aws_socks5_write_greeting(&auth_context, &buffer)); + uint8_t greeting_resp[] = {AWS_SOCKS5_VERSION, AWS_SOCKS5_AUTH_USERNAME_PASSWORD}; + cursor = aws_byte_cursor_from_array(greeting_resp, sizeof(greeting_resp)); + ASSERT_SUCCESS(aws_socks5_read_greeting_response(&auth_context, &cursor)); + aws_byte_buf_reset(&buffer, false); + ASSERT_SUCCESS(aws_socks5_write_auth_request(&auth_context, &buffer)); + uint8_t auth_fail_resp[] = {AWS_SOCKS5_AUTH_VERSION, 1}; + cursor = aws_byte_cursor_from_array(auth_fail_resp, sizeof(auth_fail_resp)); + ASSERT_ERROR(AWS_IO_SOCKS5_PROXY_ERROR_AUTH_FAILED, aws_socks5_read_auth_response(&auth_context, &cursor)); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_ERROR, auth_context.state); + + aws_byte_buf_clean_up(&buffer); + aws_socks5_context_clean_up(&auth_context); + aws_socks5_proxy_options_clean_up(&auth_options); + + /* Connect failure */ + struct aws_socks5_proxy_options connect_options; + AWS_ZERO_STRUCT(connect_options); + ASSERT_SUCCESS(aws_socks5_proxy_options_init(&connect_options, allocator, proxy_host, 1080)); + ASSERT_SUCCESS(aws_socks5_proxy_options_set_auth(&connect_options, allocator, username, password)); + + struct aws_socks5_context connect_context; + AWS_ZERO_STRUCT(connect_context); + ASSERT_SUCCESS(aws_socks5_context_init( + &connect_context, + allocator, + &connect_options, + endpoint_host, + 443, + AWS_SOCKS5_ATYP_DOMAIN)); + + ASSERT_SUCCESS(aws_byte_buf_init(&buffer, allocator, 32)); + ASSERT_SUCCESS(aws_socks5_write_greeting(&connect_context, &buffer)); + cursor = aws_byte_cursor_from_array(greeting_resp, sizeof(greeting_resp)); + ASSERT_SUCCESS(aws_socks5_read_greeting_response(&connect_context, &cursor)); + aws_byte_buf_reset(&buffer, false); + ASSERT_SUCCESS(aws_socks5_write_auth_request(&connect_context, &buffer)); + uint8_t auth_ok_resp[] = {AWS_SOCKS5_AUTH_VERSION, 0}; + cursor = aws_byte_cursor_from_array(auth_ok_resp, sizeof(auth_ok_resp)); + ASSERT_SUCCESS(aws_socks5_read_auth_response(&connect_context, &cursor)); + aws_byte_buf_reset(&buffer, false); + ASSERT_SUCCESS(aws_socks5_write_connect_request(&connect_context, &buffer)); + uint8_t connect_fail_resp[] = { + AWS_SOCKS5_VERSION, + AWS_SOCKS5_STATUS_CONNECTION_REFUSED, + AWS_SOCKS5_RESERVED, + AWS_SOCKS5_ATYP_IPV4, + 10, + 0, + 0, + 1, + 0, + 80}; + cursor = aws_byte_cursor_from_array(connect_fail_resp, sizeof(connect_fail_resp)); + ASSERT_ERROR(AWS_IO_SOCKET_CONNECTION_REFUSED, aws_socks5_read_connect_response(&connect_context, &cursor)); + ASSERT_INT_EQUALS(AWS_SOCKS5_STATE_ERROR, connect_context.state); + + aws_byte_buf_clean_up(&buffer); + aws_socks5_context_clean_up(&connect_context); + aws_socks5_proxy_options_clean_up(&connect_options); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(socks5_handshake_error_paths, s_socks5_handshake_error_paths) + +struct socks5_peer_impl { + struct aws_allocator *allocator; + struct aws_mutex mutex; + struct aws_condition_variable condition; + struct aws_byte_buf last_write; + bool has_write; + size_t write_count; + struct aws_channel_slot *slot; +}; + +static bool s_peer_has_write_predicate(void *user_data) { + struct socks5_peer_impl *impl = user_data; + return impl->has_write; +} + +static int s_peer_wait_for_write(struct socks5_peer_impl *impl, struct aws_byte_buf *out_buf) { + int result = AWS_OP_SUCCESS; + + + /* Wait until the peer handler has written data */ + aws_mutex_lock(&impl->mutex); + if (aws_condition_variable_wait_pred(&impl->condition, &impl->mutex, s_peer_has_write_predicate, impl)) { + result = AWS_OP_ERR; + goto done; + } + + result = aws_byte_buf_init_copy_from_cursor( + out_buf, + impl->allocator, + aws_byte_cursor_from_buf(&impl->last_write)); + impl->has_write = false; + aws_byte_buf_clean_up(&impl->last_write); + AWS_ZERO_STRUCT(impl->last_write); + +done: + aws_mutex_unlock(&impl->mutex); + return result; +} + +static int s_peer_send(struct socks5_peer_impl *impl, const uint8_t *data, size_t len) { + struct aws_channel_slot *slot = NULL; + + + /* Send data to the peer's slot (simulates network input) */ + aws_mutex_lock(&impl->mutex); + slot = impl->slot; + aws_mutex_unlock(&impl->mutex); + + if (!slot) { + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + + return s_channel_send_bytes(impl->allocator, slot, AWS_CHANNEL_DIR_READ, data, len); +} + +static int s_peer_process_write_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_io_message *message) { + + struct socks5_peer_impl *impl = handler->impl; + struct aws_byte_cursor payload = aws_byte_cursor_from_buf(&message->message_data); + + /* Store the written message so the test can inspect it */ + aws_mutex_lock(&impl->mutex); + + if (impl->last_write.buffer) { + aws_byte_buf_clean_up(&impl->last_write); + AWS_ZERO_STRUCT(impl->last_write); + } + + if (aws_byte_buf_init_copy_from_cursor(&impl->last_write, impl->allocator, payload)) { + aws_mutex_unlock(&impl->mutex); + aws_mem_release(message->allocator, message); + return AWS_OP_ERR; + } + + impl->has_write = true; + impl->write_count++; + impl->slot = slot; + aws_condition_variable_notify_one(&impl->condition); + + aws_mutex_unlock(&impl->mutex); + + aws_mem_release(message->allocator, message); + return AWS_OP_SUCCESS; +} + +static int s_peer_process_read_message( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + struct aws_io_message *message) { + (void)handler; + (void)slot; + + aws_mem_release(message->allocator, message); + return AWS_OP_SUCCESS; +} + +static int s_peer_increment_read_window( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + size_t size) { + (void)handler; + (void)slot; + (void)size; + return AWS_OP_SUCCESS; +} + +static int s_peer_shutdown( + struct aws_channel_handler *handler, + struct aws_channel_slot *slot, + enum aws_channel_direction dir, + int error_code, + bool abort_immediately) { + (void)handler; + + return aws_channel_slot_on_handler_shutdown_complete(slot, dir, error_code, abort_immediately); +} + +static size_t s_peer_initial_window_size(struct aws_channel_handler *handler) { + (void)handler; + return SIZE_MAX; +} + +static size_t s_peer_message_overhead(struct aws_channel_handler *handler) { + (void)handler; + return 0; +} + +static void s_peer_destroy(struct aws_channel_handler *handler) { + struct socks5_peer_impl *impl = handler->impl; + if (!impl) { + return; + } + + aws_byte_buf_clean_up(&impl->last_write); + aws_condition_variable_clean_up(&impl->condition); + aws_mutex_clean_up(&impl->mutex); + aws_mem_release(handler->alloc, impl); + aws_mem_release(handler->alloc, handler); +} + +static struct aws_channel_handler_vtable s_peer_handler_vtable = { + .process_read_message = s_peer_process_read_message, + .process_write_message = s_peer_process_write_message, + .increment_read_window = s_peer_increment_read_window, + .shutdown = s_peer_shutdown, + .initial_window_size = s_peer_initial_window_size, + .message_overhead = s_peer_message_overhead, + .destroy = s_peer_destroy, +}; + +static struct aws_channel_handler *s_peer_handler_new( + struct aws_allocator *allocator, + struct socks5_peer_impl **out_impl) { + struct aws_channel_handler *handler = aws_mem_calloc(allocator, 1, sizeof(struct aws_channel_handler)); + if (!handler) { + return NULL; + } + + struct socks5_peer_impl *impl = aws_mem_calloc(allocator, 1, sizeof(struct socks5_peer_impl)); + if (!impl) { + aws_mem_release(allocator, handler); + return NULL; + } + + impl->allocator = allocator; + if (aws_mutex_init(&impl->mutex)) { + aws_mem_release(allocator, impl); + aws_mem_release(allocator, handler); + return NULL; + } + if (aws_condition_variable_init(&impl->condition)) { + aws_mutex_clean_up(&impl->mutex); + aws_mem_release(allocator, impl); + aws_mem_release(allocator, handler); + return NULL; + } + + handler->alloc = allocator; + handler->impl = impl; + handler->vtable = &s_peer_handler_vtable; + + *out_impl = impl; + return handler; +} + +struct socks5_channel_fixture { + struct aws_mutex mutex; + struct aws_condition_variable condition; + bool setup_completed; + int setup_error; + bool shutdown_completed; + int shutdown_error; +}; + +static void s_socks5_channel_on_setup_completed(struct aws_channel *channel, int error_code, void *user_data) { + (void)channel; + struct socks5_channel_fixture *fixture = user_data; + + aws_mutex_lock(&fixture->mutex); + fixture->setup_completed = true; + fixture->setup_error = error_code; + aws_condition_variable_notify_one(&fixture->condition); + aws_mutex_unlock(&fixture->mutex); +} + +static void s_socks5_channel_on_shutdown_completed(struct aws_channel *channel, int error_code, void *user_data) { + (void)channel; + struct socks5_channel_fixture *fixture = user_data; + + aws_mutex_lock(&fixture->mutex); + fixture->shutdown_completed = true; + fixture->shutdown_error = error_code; + aws_condition_variable_notify_one(&fixture->condition); + aws_mutex_unlock(&fixture->mutex); +} + +static bool s_socks5_channel_setup_predicate(void *user_data) { + struct socks5_channel_fixture *fixture = user_data; + return fixture->setup_completed; +} + +static bool s_socks5_channel_shutdown_predicate(void *user_data) { + struct socks5_channel_fixture *fixture = user_data; + return fixture->shutdown_completed; +} + +struct socks5_handler_context { + struct aws_mutex mutex; + struct aws_condition_variable condition; + bool invoked; + int error_code; +}; + +static void s_socks5_handler_on_setup_completed(struct aws_channel *channel, int error_code, void *user_data) { + (void)channel; + struct socks5_handler_context *context = user_data; + + aws_mutex_lock(&context->mutex); + context->invoked = true; + context->error_code = error_code; + aws_condition_variable_notify_one(&context->condition); + aws_mutex_unlock(&context->mutex); +} + +static bool s_socks5_handler_invoked_predicate(void *user_data) { + struct socks5_handler_context *context = user_data; + return context->invoked; +} + +static int s_socks5_channel_handler_happy_path(struct aws_allocator *allocator, void *ctx) { + + /* Test full channel handler flow and data forwarding */ + (void)ctx; + + /* Set up event loop and channel for handler integration test */ + struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + ASSERT_NOT_NULL(event_loop); + ASSERT_SUCCESS(aws_event_loop_run(event_loop)); + + struct socks5_channel_fixture channel_fixture = { + .mutex = AWS_MUTEX_INIT, + .condition = AWS_CONDITION_VARIABLE_INIT, + .setup_completed = false, + .setup_error = AWS_ERROR_SUCCESS, + .shutdown_completed = false, + .shutdown_error = AWS_ERROR_SUCCESS, + }; + + struct aws_channel_options channel_options = { + .on_setup_completed = s_socks5_channel_on_setup_completed, + .setup_user_data = &channel_fixture, + .on_shutdown_completed = s_socks5_channel_on_shutdown_completed, + .shutdown_user_data = &channel_fixture, + .event_loop = event_loop, + }; + + struct aws_channel *channel = aws_channel_new(allocator, &channel_options); + ASSERT_NOT_NULL(channel); + + aws_mutex_lock(&channel_fixture.mutex); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &channel_fixture.condition, &channel_fixture.mutex, s_socks5_channel_setup_predicate, &channel_fixture)); + aws_mutex_unlock(&channel_fixture.mutex); + ASSERT_INT_EQUALS(0, channel_fixture.setup_error); + + /* Install a peer handler to simulate the remote SOCKS5 server */ + struct socks5_peer_impl *peer_impl = NULL; + struct aws_channel_handler *peer_handler = s_peer_handler_new(allocator, &peer_impl); + ASSERT_NOT_NULL(peer_handler); + + struct aws_channel_slot *peer_slot = NULL; + ASSERT_SUCCESS(s_install_handler(channel, peer_handler, &peer_slot)); + aws_mutex_lock(&peer_impl->mutex); + peer_impl->slot = peer_slot; + aws_mutex_unlock(&peer_impl->mutex); + + struct aws_channel_slot *socks5_slot = NULL; + + struct aws_socks5_proxy_options proxy_options; + ASSERT_SUCCESS(aws_socks5_proxy_options_init( + &proxy_options, allocator, aws_byte_cursor_from_c_str("proxy.example.com"), 1080)); + ASSERT_SUCCESS(aws_socks5_proxy_options_set_auth( + &proxy_options, allocator, aws_byte_cursor_from_c_str("user"), aws_byte_cursor_from_c_str("pass"))); + + struct socks5_handler_context handler_context = { + .mutex = AWS_MUTEX_INIT, + .condition = AWS_CONDITION_VARIABLE_INIT, + .invoked = false, + .error_code = AWS_ERROR_SUCCESS, + }; + + struct aws_channel_handler *socks5_handler = aws_socks5_channel_handler_new( + allocator, + &proxy_options, + aws_byte_cursor_from_c_str("destination.example.com"), + 443, + AWS_SOCKS5_ATYP_DOMAIN, + s_socks5_handler_on_setup_completed, + &handler_context); + ASSERT_NOT_NULL(socks5_handler); + ASSERT_SUCCESS(s_install_handler(channel, socks5_handler, &socks5_slot)); + + ASSERT_SUCCESS(s_channel_run_on_thread(channel, s_start_handshake_on_thread, socks5_handler)); + + struct aws_byte_buf greeting; + ASSERT_SUCCESS(s_peer_wait_for_write(peer_impl, &greeting)); + ASSERT_TRUE(greeting.len >= AWS_SOCKS5_GREETING_MIN_SIZE); + ASSERT_UINT_EQUALS(AWS_SOCKS5_VERSION, greeting.buffer[0]); + ASSERT_UINT_EQUALS(2, greeting.buffer[1]); + aws_byte_buf_clean_up(&greeting); + + uint8_t greeting_response[] = {AWS_SOCKS5_VERSION, AWS_SOCKS5_AUTH_USERNAME_PASSWORD}; + ASSERT_SUCCESS(s_peer_send(peer_impl, greeting_response, sizeof(greeting_response))); + + struct aws_byte_buf auth_request; + ASSERT_SUCCESS(s_peer_wait_for_write(peer_impl, &auth_request)); + ASSERT_UINT_EQUALS(3 + 4 + 4, auth_request.len); + aws_byte_buf_clean_up(&auth_request); + + uint8_t auth_response[] = {AWS_SOCKS5_AUTH_VERSION, 0}; + ASSERT_SUCCESS(s_peer_send(peer_impl, auth_response, sizeof(auth_response))); + + struct aws_byte_buf connect_request; + ASSERT_SUCCESS(s_peer_wait_for_write(peer_impl, &connect_request)); + ASSERT_TRUE(connect_request.len > AWS_SOCKS5_CONN_REQ_MIN_SIZE); + aws_byte_buf_clean_up(&connect_request); + + uint8_t connect_success[] = { + AWS_SOCKS5_VERSION, + AWS_SOCKS5_STATUS_SUCCESS, + AWS_SOCKS5_RESERVED, + AWS_SOCKS5_ATYP_IPV4, + 1, + 1, + 1, + 1, + 0, + 80}; + ASSERT_SUCCESS(s_peer_send(peer_impl, connect_success, sizeof(connect_success))); + + aws_mutex_lock(&handler_context.mutex); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &handler_context.condition, &handler_context.mutex, s_socks5_handler_invoked_predicate, &handler_context)); + int handshake_error = handler_context.error_code; + aws_mutex_unlock(&handler_context.mutex); + ASSERT_INT_EQUALS(AWS_ERROR_SUCCESS, handshake_error); + + struct aws_byte_cursor ping_cur = aws_byte_cursor_from_c_str("ping"); + ASSERT_SUCCESS(s_channel_send_cursor(allocator, socks5_slot, AWS_CHANNEL_DIR_WRITE, ping_cur)); + + struct aws_byte_buf forwarded_ping; + ASSERT_SUCCESS(s_peer_wait_for_write(peer_impl, &forwarded_ping)); + ASSERT_UINT_EQUALS(4, forwarded_ping.len); + ASSERT_BIN_ARRAYS_EQUALS("ping", 4, forwarded_ping.buffer, forwarded_ping.len); + aws_byte_buf_clean_up(&forwarded_ping); + + struct aws_byte_cursor pong_cur = aws_byte_cursor_from_c_str("pong"); + ASSERT_SUCCESS(s_channel_send_cursor(allocator, socks5_slot, AWS_CHANNEL_DIR_WRITE, pong_cur)); + + struct aws_byte_buf forwarded_pong; + ASSERT_SUCCESS(s_peer_wait_for_write(peer_impl, &forwarded_pong)); + ASSERT_UINT_EQUALS(4, forwarded_pong.len); + ASSERT_BIN_ARRAYS_EQUALS("pong", 4, forwarded_pong.buffer, forwarded_pong.len); + aws_byte_buf_clean_up(&forwarded_pong); + + aws_channel_shutdown(channel, AWS_OP_SUCCESS); + aws_mutex_lock(&channel_fixture.mutex); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &channel_fixture.condition, &channel_fixture.mutex, s_socks5_channel_shutdown_predicate, &channel_fixture)); + aws_mutex_unlock(&channel_fixture.mutex); + if (channel_fixture.shutdown_error != AWS_ERROR_SUCCESS) { + ASSERT_INT_EQUALS(AWS_IO_SOCKET_CLOSED, channel_fixture.shutdown_error); + } + + aws_channel_destroy(channel); + aws_event_loop_destroy(event_loop); + aws_mutex_clean_up(&handler_context.mutex); + aws_condition_variable_clean_up(&handler_context.condition); + aws_mutex_clean_up(&channel_fixture.mutex); + aws_condition_variable_clean_up(&channel_fixture.condition); + aws_socks5_proxy_options_clean_up(&proxy_options); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(socks5_channel_handler_happy_path, s_socks5_channel_handler_happy_path) + +static int s_socks5_channel_handler_greeting_failure(struct aws_allocator *allocator, void *ctx) { + + /* Test handler behavior on malformed greeting response */ + (void)ctx; + + struct aws_event_loop *event_loop = aws_event_loop_new_default(allocator, aws_high_res_clock_get_ticks); + ASSERT_NOT_NULL(event_loop); + ASSERT_SUCCESS(aws_event_loop_run(event_loop)); + + struct socks5_channel_fixture channel_fixture = { + .mutex = AWS_MUTEX_INIT, + .condition = AWS_CONDITION_VARIABLE_INIT, + .setup_completed = false, + .setup_error = AWS_ERROR_SUCCESS, + .shutdown_completed = false, + .shutdown_error = AWS_ERROR_SUCCESS, + }; + + struct aws_channel_options channel_options = { + .on_setup_completed = s_socks5_channel_on_setup_completed, + .setup_user_data = &channel_fixture, + .on_shutdown_completed = s_socks5_channel_on_shutdown_completed, + .shutdown_user_data = &channel_fixture, + .event_loop = event_loop, + }; + + struct aws_channel *channel = aws_channel_new(allocator, &channel_options); + ASSERT_NOT_NULL(channel); + + aws_mutex_lock(&channel_fixture.mutex); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &channel_fixture.condition, &channel_fixture.mutex, s_socks5_channel_setup_predicate, &channel_fixture)); + aws_mutex_unlock(&channel_fixture.mutex); + ASSERT_INT_EQUALS(0, channel_fixture.setup_error); + + struct socks5_peer_impl *peer_impl = NULL; + struct aws_channel_handler *peer_handler = s_peer_handler_new(allocator, &peer_impl); + ASSERT_NOT_NULL(peer_handler); + struct aws_channel_slot *peer_slot = NULL; + ASSERT_SUCCESS(s_install_handler(channel, peer_handler, &peer_slot)); + aws_mutex_lock(&peer_impl->mutex); + peer_impl->slot = peer_slot; + aws_mutex_unlock(&peer_impl->mutex); + + struct aws_channel_slot *socks5_slot = NULL; + + struct aws_socks5_proxy_options proxy_options; + ASSERT_SUCCESS(aws_socks5_proxy_options_init( + &proxy_options, allocator, aws_byte_cursor_from_c_str("proxy.example.com"), 1080)); + + struct socks5_handler_context handler_context = { + .mutex = AWS_MUTEX_INIT, + .condition = AWS_CONDITION_VARIABLE_INIT, + .invoked = false, + .error_code = AWS_ERROR_SUCCESS, + }; + + struct aws_channel_handler *socks5_handler = aws_socks5_channel_handler_new( + allocator, + &proxy_options, + aws_byte_cursor_from_c_str("destination.example.com"), + 80, + AWS_SOCKS5_ATYP_DOMAIN, + s_socks5_handler_on_setup_completed, + &handler_context); + ASSERT_NOT_NULL(socks5_handler); + ASSERT_SUCCESS(s_install_handler(channel, socks5_handler, &socks5_slot)); + + ASSERT_SUCCESS(s_channel_run_on_thread(channel, s_start_handshake_on_thread, socks5_handler)); + + struct aws_byte_buf greeting; + ASSERT_SUCCESS(s_peer_wait_for_write(peer_impl, &greeting)); + ASSERT_TRUE(greeting.len >= AWS_SOCKS5_GREETING_MIN_SIZE); + aws_byte_buf_clean_up(&greeting); + + uint8_t invalid_greeting[] = {0x04, AWS_SOCKS5_AUTH_NO_ACCEPTABLE}; + ASSERT_SUCCESS(s_peer_send(peer_impl, invalid_greeting, sizeof(invalid_greeting))); + + aws_mutex_lock(&handler_context.mutex); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &handler_context.condition, &handler_context.mutex, s_socks5_handler_invoked_predicate, &handler_context)); + int failure_code = handler_context.error_code; + aws_mutex_unlock(&handler_context.mutex); + ASSERT_INT_EQUALS(AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE, failure_code); + ASSERT_INT_EQUALS(1, (int)peer_impl->write_count); + + aws_channel_shutdown(channel, AWS_OP_SUCCESS); + aws_mutex_lock(&channel_fixture.mutex); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &channel_fixture.condition, &channel_fixture.mutex, s_socks5_channel_shutdown_predicate, &channel_fixture)); + aws_mutex_unlock(&channel_fixture.mutex); + + aws_channel_destroy(channel); + aws_event_loop_destroy(event_loop); + aws_mutex_clean_up(&handler_context.mutex); + aws_condition_variable_clean_up(&handler_context.condition); + aws_mutex_clean_up(&channel_fixture.mutex); + aws_condition_variable_clean_up(&channel_fixture.condition); + aws_socks5_proxy_options_clean_up(&proxy_options); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(socks5_channel_handler_greeting_failure, s_socks5_channel_handler_greeting_failure) + +struct socks5_bootstrap_stub_context { + bool invoked; + const char *host_name; + uint16_t port; + void *user_data; + const struct aws_tls_connection_options *tls_options; +}; + +static struct socks5_bootstrap_stub_context *s_stub_context = NULL; + +/* Stub function for simulating an error on a new socket channel creation */ +static int s_stub_bootstrap_new_socket_channel(struct aws_socket_channel_bootstrap_options *options) { + if (s_stub_context) { + s_stub_context->invoked = true; + s_stub_context->host_name = options->host_name; + s_stub_context->port = options->port; + s_stub_context->user_data = options->user_data; + s_stub_context->tls_options = options->tls_options; + } + aws_raise_error(AWS_ERROR_UNKNOWN); + return AWS_OP_ERR; +} + +static int s_socks5_bootstrap_system_vtable_failure(struct aws_allocator *allocator, void *ctx) { + + // Test socket creation error via vtable during bootstrap + (void)ctx; + + aws_io_library_init(allocator); + + struct aws_event_loop_group *el_group = aws_event_loop_group_new_default(allocator, 1, NULL); + ASSERT_NOT_NULL(el_group); + + struct aws_client_bootstrap_options bootstrap_options = { + .event_loop_group = el_group, + .host_resolver = NULL, + }; + struct aws_client_bootstrap *client_bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); + ASSERT_NOT_NULL(client_bootstrap); + + struct aws_socket_options socket_options = { + .type = AWS_SOCKET_STREAM, + .domain = AWS_SOCKET_IPV4, + .connect_timeout_ms = 1000, + }; + + struct aws_socket_channel_bootstrap_options channel_options; + AWS_ZERO_STRUCT(channel_options); + channel_options.bootstrap = client_bootstrap; + channel_options.host_name = "target.example.com"; + channel_options.port = 443; + channel_options.socket_options = &socket_options; + + struct aws_socks5_proxy_options proxy_options; + ASSERT_SUCCESS(aws_socks5_proxy_options_init( + &proxy_options, allocator, aws_byte_cursor_from_c_str("proxy.local"), 1080)); + + struct socks5_bootstrap_stub_context stub_context; + AWS_ZERO_STRUCT(stub_context); + s_stub_context = &stub_context; + + struct aws_socks5_system_vtable stub_vtable = { + .aws_client_bootstrap_new_socket_channel = s_stub_bootstrap_new_socket_channel, + }; + aws_socks5_channel_handler_set_system_vtable(&stub_vtable); + + ASSERT_ERROR( + AWS_ERROR_UNKNOWN, + aws_client_bootstrap_new_socket_channel_with_socks5(allocator, &channel_options, &proxy_options)); + + ASSERT_TRUE(stub_context.invoked); + ASSERT_STR_EQUALS("proxy.local", stub_context.host_name); + ASSERT_INT_EQUALS(1080, stub_context.port); + ASSERT_NOT_NULL(stub_context.user_data); + ASSERT_NULL(stub_context.tls_options); + + aws_socks5_channel_handler_set_system_vtable(NULL); + s_stub_context = NULL; + + channel_options.host_name = "target.example.com"; + aws_socks5_proxy_options_clean_up(&proxy_options); + aws_client_bootstrap_release(client_bootstrap); + aws_event_loop_group_release(el_group); + + aws_io_library_clean_up(); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(socks5_bootstrap_system_vtable_failure, s_socks5_bootstrap_system_vtable_failure) From e0bbe13e8a83172df7e996a4bff8fb319e49ad93 Mon Sep 17 00:00:00 2001 From: Carlos San Vicente Date: Fri, 17 Oct 2025 15:53:58 +0200 Subject: [PATCH 2/6] Add SOCKS5 proxy support --- source/io.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/source/io.c b/source/io.c index 65c872eab..0292c042d 100644 --- a/source/io.c +++ b/source/io.c @@ -307,6 +307,7 @@ static struct aws_error_info s_errors[] = { AWS_IO_TLS_ERROR_READ_FAILURE, "Failure during TLS read."), AWS_DEFINE_ERROR_INFO_IO(AWS_ERROR_PEM_MALFORMED, "Malformed PEM object encountered."), + AWS_DEFINE_ERROR_INFO_IO( AWS_IO_SOCKET_MISSING_EVENT_LOOP, "Socket is missing its event loop."), @@ -392,6 +393,47 @@ static struct aws_error_info s_errors[] = { AWS_IO_SOCKS5_PROXY_ERROR_COMMAND_NOT_SUPPORTED, "Command not supported by SOCKS5 proxy server."), + + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_INIT, + "Failed to initialize SOCKS5 proxy connection."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_AUTH_METHOD, + "SOCKS5 proxy server doesn't support any of the client's authentication methods."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_AUTH_FAILED, + "Authentication with SOCKS5 proxy server failed."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_CONNECTION_FAILED, + "Failed to establish connection through SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_REQUEST_FAILED, + "SOCKS5 proxy request failed."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_MALFORMED_RESPONSE, + "Received malformed response from SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_UNSUPPORTED_ADDRESS_TYPE, + "SOCKS5 proxy doesn't support the requested address type."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_BAD_ADDRESS, + "Invalid address format for SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_BAD_HANDSHAKE, + "SOCKS5 handshake failed."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_REJECTED, + "Connection rejected by SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_GENERAL_FAILURE, + "General failure reported by SOCKS5 proxy server."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_TTL_EXPIRED, + "TTL expired for connection through SOCKS5 proxy."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_SOCKS5_PROXY_ERROR_COMMAND_NOT_SUPPORTED, + "Command not supported by SOCKS5 proxy server."), + }; /* clang-format on */ From 177997eb6a82da07424f57d6860629770335bbbc Mon Sep 17 00:00:00 2001 From: Carlos San Vicente Date: Mon, 27 Oct 2025 17:23:04 +0100 Subject: [PATCH 3/6] Apply clang format --- source/io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/io.c b/source/io.c index 0292c042d..41fee80e6 100644 --- a/source/io.c +++ b/source/io.c @@ -10,7 +10,7 @@ #include #include -#define AWS_DEFINE_ERROR_INFO_IO(CODE, STR) [(CODE) - 0x0400] = AWS_DEFINE_ERROR_INFO(CODE, STR, "aws-c-io") +#define AWS_DEFINE_ERROR_INFO_IO(CODE, STR) [(CODE)-0x0400] = AWS_DEFINE_ERROR_INFO(CODE, STR, "aws-c-io") #define AWS_DEFINE_ERROR_PKCS11_CKR(CKR) \ AWS_DEFINE_ERROR_INFO_IO( \ From 96c62f8db1af79305256e22b5b3f1967af944d21 Mon Sep 17 00:00:00 2001 From: Carlos San Vicente Date: Fri, 7 Nov 2025 17:20:54 +0100 Subject: [PATCH 4/6] Fix Ipv6 parsing --- source/socks5.c | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/source/socks5.c b/source/socks5.c index 4bc8beafc..97010a8ef 100644 --- a/source/socks5.c +++ b/source/socks5.c @@ -69,6 +69,31 @@ static int s_ensure_buffer_has_capacity( return AWS_OP_SUCCESS; } +/* Normalizes IPv4/IPv6 literals by stripping RFC-3986 decorations such as + * surrounding brackets and scope identifiers (e.g. "%eth0"). Operates in-place + * on a null-terminated buffer. */ +static void s_normalize_ip_literal(char *address_buffer) { + if (!address_buffer || address_buffer[0] == '\0') { + return; + } + + size_t buf_len = strlen(address_buffer); + if (buf_len > 1 && address_buffer[0] == '[') { + char *closing = strchr(address_buffer, ']'); + if (closing && closing > address_buffer) { + size_t literal_len = (size_t)(closing - (address_buffer + 1)); + memmove(address_buffer, address_buffer + 1, literal_len); + address_buffer[literal_len] = '\0'; + buf_len = literal_len; + } + } + + char *zone_delimiter = strchr(address_buffer, '%'); + if (zone_delimiter) { + *zone_delimiter = '\0'; + } +} + /* Helper for converting auth method enum to string for logging */ static struct aws_string *s_auth_method_to_string(enum aws_socks5_auth_method method) { switch (method) { @@ -248,18 +273,7 @@ AWS_IO_API enum aws_socks5_address_type aws_socks5_infer_address_type( memcpy(address_buffer, target_host.ptr, host_len); address_buffer[host_len] = '\0'; - if (address_buffer[0] == '[') { - size_t buf_len = strlen(address_buffer); - if (buf_len > 1 && address_buffer[buf_len - 1] == ']') { - memmove(address_buffer, address_buffer + 1, buf_len - 2); - address_buffer[buf_len - 2] = '\0'; - } - } - - char *zone_delimiter = strchr(address_buffer, '%'); - if (zone_delimiter) { - *zone_delimiter = '\0'; - } + s_normalize_ip_literal(address_buffer); unsigned char ipv4_buffer[4]; unsigned char ipv6_buffer[16]; @@ -833,6 +847,7 @@ int aws_socks5_write_connect_request( size_t copy_len = target_len < 127 ? target_len : 127; memcpy(ip_str, s_string_bytes(context->endpoint_host), copy_len); ip_str[copy_len] = '\0'; + s_normalize_ip_literal(ip_str); if (inet_pton(AF_INET, ip_str, binary_addr) != 1) { AWS_LOGF_ERROR( @@ -867,6 +882,7 @@ int aws_socks5_write_connect_request( size_t copy_len = target_len < 127 ? target_len : 127; memcpy(ip_str, s_string_bytes(context->endpoint_host), copy_len); ip_str[copy_len] = '\0'; + s_normalize_ip_literal(ip_str); if (inet_pton(AF_INET6, ip_str, binary_addr) != 1) { AWS_LOGF_ERROR( From ef355f276dae92afcf64d4ade1dabea210911756 Mon Sep 17 00:00:00 2001 From: Carlos San Vicente Date: Fri, 7 Nov 2025 17:52:38 +0100 Subject: [PATCH 5/6] Fix ipv6 bug --- source/socks5.c | 14 +++++++++++- source/socks5_channel_handler.c | 40 ++++++++++++++------------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/source/socks5.c b/source/socks5.c index 97010a8ef..0d08fa283 100644 --- a/source/socks5.c +++ b/source/socks5.c @@ -25,6 +25,17 @@ AWS_STATIC_STRING_FROM_LITERAL(s_socks_none_method, "NONE"); AWS_STATIC_STRING_FROM_LITERAL(s_socks_username_password_method, "USERNAME_PASSWORD"); AWS_STATIC_STRING_FROM_LITERAL(s_socks_gssapi_method, "GSSAPI"); +static struct aws_byte_cursor s_trim_ipv6_brackets(struct aws_byte_cursor host_cursor) { + if (host_cursor.len >= 2 && host_cursor.ptr && host_cursor.ptr[0] == '[') { + size_t last_index = host_cursor.len - 1; + if (host_cursor.ptr[last_index] == ']') { + host_cursor.ptr += 1; + host_cursor.len -= 2; + } + } + return host_cursor; +} + /* Buffer size constants for SOCKS5 protocol operations */ #define AWS_SOCKS5_SEND_BUFFER_INITIAL_SIZE 256 #define AWS_SOCKS5_RECV_BUFFER_INITIAL_SIZE 512 @@ -128,7 +139,8 @@ int aws_socks5_proxy_options_init( aws_socks5_proxy_options_init_default(options); options->port = port; - options->host = aws_string_new_from_cursor(allocator, &host); + struct aws_byte_cursor normalized_host = s_trim_ipv6_brackets(host); + options->host = aws_string_new_from_cursor(allocator, &normalized_host); if (options->host == NULL) { AWS_LOGF_ERROR( AWS_LS_IO_SOCKS5, diff --git a/source/socks5_channel_handler.c b/source/socks5_channel_handler.c index 46090a551..908488f46 100644 --- a/source/socks5_channel_handler.c +++ b/source/socks5_channel_handler.c @@ -72,6 +72,19 @@ void aws_socks5_channel_handler_set_system_vtable(const struct aws_socks5_system } } +/* SOCKS5 URIs follow RFC-3986, where IPv6 literals are enclosed in brackets. + * DNS APIs expect bare literals, so strip a single leading '[' and trailing ']'. */ +static struct aws_byte_cursor s_normalize_proxy_host_cursor(struct aws_byte_cursor host_cursor) { + if (host_cursor.len >= 2 && host_cursor.ptr[0] == '[') { + size_t last_index = host_cursor.len - 1; + if (host_cursor.ptr[last_index] == ']') { + host_cursor.ptr += 1; + host_cursor.len -= 2; + } + } + return host_cursor; +} + /** * State machine for the SOCKS5 channel handler * @@ -119,26 +132,6 @@ static inline const char *s_socks5_channel_state_to_string(enum aws_socks5_chann } } -/** - * Returns a human-readable name for SOCKS5 message types used in logging. - * This aids in debugging by providing context about the type of message - * being processed in the SOCKS5 protocol. - */ -static inline const char *s_get_socks5_message_type_name(int message_type) { - switch (message_type) { - case 0: - return "INIT"; - case 1: - return "GREETING"; - case 2: - return "AUTH"; - case 3: - return "CONNECT"; - default: - return "UNKNOWN"; - } -} - struct aws_socks5_channel_handler { /* Base handler data */ struct aws_channel_handler handler; @@ -2889,6 +2882,7 @@ static int s_socks5_bootstrap_set_socks5_proxy_options( } struct aws_byte_cursor endpoint_host_cursor = aws_byte_cursor_from_c_str(host_name); + struct aws_byte_cursor normalized_host_cursor = s_normalize_proxy_host_cursor(endpoint_host_cursor); aws_string_destroy(socks5_bootstrap->endpoint_host); socks5_bootstrap->endpoint_host = NULL; @@ -2897,7 +2891,7 @@ static int s_socks5_bootstrap_set_socks5_proxy_options( socks5_bootstrap->endpoint_port = port; enum aws_socks5_address_type inferred_type = - aws_socks5_infer_address_type(endpoint_host_cursor, AWS_SOCKS5_ATYP_DOMAIN); + aws_socks5_infer_address_type(normalized_host_cursor, AWS_SOCKS5_ATYP_DOMAIN); socks5_bootstrap->host_resolution_mode = aws_socks5_proxy_options_get_host_resolution_mode(socks5_proxy_options); socks5_bootstrap->resolution_error_code = AWS_ERROR_SUCCESS; @@ -2908,7 +2902,7 @@ static int s_socks5_bootstrap_set_socks5_proxy_options( if (socks5_bootstrap->host_resolution_mode == AWS_SOCKS5_HOST_RESOLUTION_CLIENT && inferred_type != AWS_SOCKS5_ATYP_DOMAIN) { socks5_bootstrap->endpoint_host = - aws_string_new_from_cursor(allocator, &endpoint_host_cursor); + aws_string_new_from_cursor(allocator, &normalized_host_cursor); if (!socks5_bootstrap->endpoint_host) { aws_socks5_proxy_options_clean_up(socks5_proxy_options); aws_mem_release(allocator, socks5_proxy_options); @@ -2937,7 +2931,7 @@ static int s_socks5_bootstrap_set_socks5_proxy_options( socks5_bootstrap->endpoint_ready = false; } else { socks5_bootstrap->endpoint_host = - aws_string_new_from_cursor(allocator, &endpoint_host_cursor); + aws_string_new_from_cursor(allocator, &normalized_host_cursor); if (!socks5_bootstrap->endpoint_host) { aws_socks5_proxy_options_clean_up(socks5_proxy_options); aws_mem_release(allocator, socks5_proxy_options); From d59622050857cfe63ec3459745ce4b08d4fba6d2 Mon Sep 17 00:00:00 2001 From: Carlos San Vicente Date: Mon, 17 Nov 2025 18:48:08 +0100 Subject: [PATCH 6/6] Restore secure_channel_tls_handler.c --- source/windows/secure_channel_tls_handler.c | 2051 ------------------- 1 file changed, 2051 deletions(-) diff --git a/source/windows/secure_channel_tls_handler.c b/source/windows/secure_channel_tls_handler.c index 329ebc937..59f8c56b8 100644 --- a/source/windows/secure_channel_tls_handler.c +++ b/source/windows/secure_channel_tls_handler.c @@ -1,4 +1,3 @@ -<<<<<<< HEAD /** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. @@ -2505,2053 +2504,3 @@ struct aws_tls_ctx *aws_tls_server_ctx_new(struct aws_allocator *alloc, const st struct aws_tls_ctx *aws_tls_client_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) { return s_ctx_new(alloc, options, true); } -======= -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ -#define SECURITY_WIN32 - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include - -#ifdef _MSC_VER -# pragma warning(disable : 4221) /* aggregate initializer using local variable addresses */ -# pragma warning(disable : 4204) /* non-constant aggregate initializer */ -# pragma warning(disable : 4306) /* Identifier is type cast to a larger pointer. */ -#endif - -#define KB_1 1024 -#define READ_OUT_SIZE (16 * KB_1) -#define READ_IN_SIZE READ_OUT_SIZE -#define EST_HANDSHAKE_SIZE (7 * KB_1) - -#define EST_TLS_RECORD_OVERHEAD 53 /* 5 byte header + 32 + 16 bytes for padding */ - -void aws_tls_init_static_state(struct aws_allocator *alloc) { - AWS_LOGF_INFO(AWS_LS_IO_TLS, "static: Initializing TLS using SecureChannel (SSPI)."); - (void)alloc; -} - -void aws_tls_clean_up_static_state(void) {} - -struct secure_channel_ctx { - struct aws_tls_ctx ctx; - struct aws_string *alpn_list; - SCHANNEL_CRED credentials; - PCERT_CONTEXT pcerts; - HCERTSTORE cert_store; - HCERTSTORE custom_trust_store; - HCRYPTPROV crypto_provider; - HCRYPTKEY private_key; - bool verify_peer; - bool should_free_pcerts; -}; - -struct secure_channel_handler { - struct aws_channel_handler handler; - struct aws_tls_channel_handler_shared shared_state; - CtxtHandle sec_handle; - CredHandle creds; - /* - * The SSPI API expects an array of len 1 of these where it's the leaf certificate associated with its private - * key. - */ - PCCERT_CONTEXT cert_context[1]; - HCERTSTORE cert_store; - HCERTSTORE custom_ca_store; - SecPkgContext_StreamSizes stream_sizes; - unsigned long ctx_req; - unsigned long ctx_ret_flags; - struct aws_channel_slot *slot; - struct aws_byte_buf protocol; - struct aws_byte_buf server_name; - TimeStamp sspi_timestamp; - int (*s_connection_state_fn)(struct aws_channel_handler *handler); - /* - * Give a little bit of extra head room, for split records. - */ - uint8_t buffered_read_in_data[READ_IN_SIZE + KB_1]; - struct aws_byte_buf buffered_read_in_data_buf; - size_t estimated_incomplete_size; - size_t read_extra; - /* This is to accommodate the extra head room we added above. - because we're allowing for splits, we may have more data decrypted - than we can fit in this buffer if we don't make them match. */ - uint8_t buffered_read_out_data[READ_OUT_SIZE + KB_1]; - struct aws_byte_buf buffered_read_out_data_buf; - struct aws_channel_task sequential_task_storage; - aws_tls_on_negotiation_result_fn *on_negotiation_result; - aws_tls_on_data_read_fn *on_data_read; - aws_tls_on_error_fn *on_error; - struct aws_string *alpn_list; - void *user_data; - bool advertise_alpn_message; - bool negotiation_finished; - bool verify_peer; -}; - -static size_t s_message_overhead(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - - if (AWS_UNLIKELY(!sc_handler->stream_sizes.cbMaximumMessage)) { - SECURITY_STATUS status = - QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_STREAM_SIZES, &sc_handler->stream_sizes); - - if (status != SEC_E_OK) { - return EST_TLS_RECORD_OVERHEAD; - } - } - - return sc_handler->stream_sizes.cbTrailer + sc_handler->stream_sizes.cbHeader; -} - -bool aws_tls_is_alpn_available(void) { -/* if you built on an old version of windows, still no support, but if you did, we still - want to check the OS version at runtime before agreeing to attempt alpn. */ -#ifdef SECBUFFER_APPLICATION_PROTOCOLS - AWS_LOGF_DEBUG( - AWS_LS_IO_TLS, - "static: This library was built with Windows 8.1 or later, " - "probing OS to see what we're actually running on."); - /* make sure we're on windows 8.1 or later. */ - OSVERSIONINFOEX os_version; - DWORDLONG condition_mask = 0; - VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); - - AWS_ZERO_STRUCT(os_version); - os_version.dwMajorVersion = HIBYTE(_WIN32_WINNT_WIN8); - os_version.dwMinorVersion = LOBYTE(_WIN32_WINNT_WIN8); - os_version.wServicePackMajor = 0; - os_version.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - - if (VerifyVersionInfo( - &os_version, - VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, - condition_mask)) { - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: We're running on Windows 8.1 or later. ALPN is available."); - return true; - } - - AWS_LOGF_WARN( - AWS_LS_IO_TLS, - "static: Running on older version of windows, ALPN is not supported. " - "Please update your OS to take advantage of modern features."); -#else - AWS_LOGF_WARN( - AWS_LS_IO_TLS, - "static: This library was built using a Windows SDK prior to 8.1. " - "Please build with a version of windows >= 8.1 to take advantage modern features. ALPN is not supported."); -#endif /*SECBUFFER_APPLICATION_PROTOCOLS */ - return false; -} - -bool aws_tls_is_cipher_pref_supported(enum aws_tls_cipher_pref cipher_pref) { - switch (cipher_pref) { - case AWS_IO_TLS_CIPHER_PREF_SYSTEM_DEFAULT: - return true; - - case AWS_IO_TLS_CIPHER_PREF_KMS_PQ_TLSv1_0_2019_06: - default: - return false; - } -} - -/* technically we could lower this, but lets be forgiving */ -#define MAX_HOST_LENGTH 255 - -/* this only gets called if the user specified a custom ca. */ -static int s_manually_verify_peer_cert(struct aws_channel_handler *handler) { - AWS_LOGF_DEBUG( - AWS_LS_IO_TLS, - "id=%p: manually verifying certifcate chain because a custom CA is configured.", - (void *)handler); - struct secure_channel_handler *sc_handler = handler->impl; - - int result = AWS_OP_ERR; - CERT_CONTEXT *peer_certificate = NULL; - HCERTCHAINENGINE engine = NULL; - CERT_CHAIN_CONTEXT *cert_chain_ctx = NULL; - - /* get the peer's certificate so we can validate it.*/ - SECURITY_STATUS status = - QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &peer_certificate); - - if (status != SEC_E_OK || !peer_certificate) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, - "id=%p: failed to load peer's certificate with SECURITY_STATUS %d", - (void *)handler, - (int)status); - return AWS_OP_ERR; - } - - /* this next bit scours the custom trust store to try and load a chain to verify - the leaf certificate against. */ - CERT_CHAIN_ENGINE_CONFIG engine_config; - AWS_ZERO_STRUCT(engine_config); - engine_config.cbSize = sizeof(engine_config); - engine_config.hExclusiveRoot = sc_handler->custom_ca_store; - - if (!CertCreateCertificateChainEngine(&engine_config, &engine)) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, - "id=%p: failed to load a certificate chain engine with SECURITY_STATUS %d. " - "Most likely, the configured CA is corrupted.", - (void *)handler, - (int)status); - goto done; - } - - /* - * TODO: Investigate CRL options further on a per-platform basis. Add control APIs if appropriate. - */ - DWORD get_chain_flags = 0; - - /* mimic chromium here since we intend for this to be used generally */ - const LPCSTR usage_identifiers[] = { - szOID_PKIX_KP_SERVER_AUTH, - szOID_SERVER_GATED_CRYPTO, - szOID_SGC_NETSCAPE, - }; - - CERT_CHAIN_PARA chain_params; - AWS_ZERO_STRUCT(chain_params); - chain_params.cbSize = sizeof(chain_params); - chain_params.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; - chain_params.RequestedUsage.Usage.cUsageIdentifier = AWS_ARRAY_SIZE(usage_identifiers); - chain_params.RequestedUsage.Usage.rgpszUsageIdentifier = (LPSTR *)usage_identifiers; - - if (!CertGetCertificateChain( - engine, - peer_certificate, - NULL, - peer_certificate->hCertStore, - &chain_params, - get_chain_flags, - NULL, - &cert_chain_ctx)) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, - "id=%p: unable to find certificate in chain with SECURITY_STATUS %d.", - (void *)handler, - (int)status); - goto done; - } - - struct aws_byte_buf host = aws_tls_handler_server_name(handler); - if (host.len > MAX_HOST_LENGTH) { - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "id=%p: host name too long (%d).", (void *)handler, (int)host.len); - goto done; - } - - wchar_t whost[MAX_HOST_LENGTH + 1]; - AWS_ZERO_ARRAY(whost); - - int converted = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, (const char *)host.buffer, (int)host.len, whost, AWS_ARRAY_SIZE(whost)); - if ((size_t)converted != host.len) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, - "id=%p: unable to convert host to wstr, %d -> %d, with last error 0x%x.", - (void *)handler, - (int)host.len, - (int)converted, - (int)GetLastError()); - goto done; - } - - /* check if the chain was trusted */ - LPCSTR policyiod = CERT_CHAIN_POLICY_SSL; - - SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslpolicy; - AWS_ZERO_STRUCT(sslpolicy); - sslpolicy.cbSize = sizeof(sslpolicy); - sslpolicy.dwAuthType = AUTHTYPE_SERVER; - sslpolicy.fdwChecks = 0; - sslpolicy.pwszServerName = whost; - - CERT_CHAIN_POLICY_PARA policypara; - AWS_ZERO_STRUCT(policypara); - policypara.cbSize = sizeof(policypara); - policypara.dwFlags = 0; - policypara.pvExtraPolicyPara = &sslpolicy; - - CERT_CHAIN_POLICY_STATUS policystatus; - AWS_ZERO_STRUCT(policystatus); - policystatus.cbSize = sizeof(policystatus); - - if (!CertVerifyCertificateChainPolicy(policyiod, cert_chain_ctx, &policypara, &policystatus)) { - int error = GetLastError(); - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, "id=%p: CertVerifyCertificateChainPolicy() failed, error 0x%x", (void *)handler, (int)error); - goto done; - } - - if (policystatus.dwError) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, - "id=%p: certificate verification failed, error 0x%x", - (void *)handler, - (int)policystatus.dwError); - goto done; - } - - /* if the chain was trusted, then we're good to go, if it was not - we bail out. */ - CERT_SIMPLE_CHAIN *simple_chain = cert_chain_ctx->rgpChain[0]; - DWORD trust_mask = ~(DWORD)CERT_TRUST_IS_NOT_TIME_NESTED; - trust_mask &= simple_chain->TrustStatus.dwErrorStatus; - - if (trust_mask != 0) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, - "id=%p: peer certificate is un-trusted with SECURITY_STATUS %d.", - (void *)handler, - (int)trust_mask); - goto done; - } - - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: peer certificate is trusted.", (void *)handler); - result = AWS_OP_SUCCESS; - -done: - - if (cert_chain_ctx != NULL) { - CertFreeCertificateChain(cert_chain_ctx); - } - - if (engine != NULL) { - CertFreeCertificateChainEngine(engine); - } - - if (peer_certificate != NULL) { - CertFreeCertificateContext(peer_certificate); - } - - return result; -} - -static void s_invoke_negotiation_error(struct aws_channel_handler *handler, int err) { - struct secure_channel_handler *sc_handler = handler->impl; - - aws_on_tls_negotiation_completed(&sc_handler->shared_state, err); - - if (sc_handler->on_negotiation_result) { - sc_handler->on_negotiation_result(handler, sc_handler->slot, err, sc_handler->user_data); - } -} - -static void s_on_negotiation_success(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - - /* if the user provided an ALPN handler to the channel, we need to let them know what their protocol is. */ - if (sc_handler->slot->adj_right && sc_handler->advertise_alpn_message && sc_handler->protocol.len) { - struct aws_io_message *message = aws_channel_acquire_message_from_pool( - sc_handler->slot->channel, - AWS_IO_MESSAGE_APPLICATION_DATA, - sizeof(struct aws_tls_negotiated_protocol_message)); - message->message_tag = AWS_TLS_NEGOTIATED_PROTOCOL_MESSAGE; - struct aws_tls_negotiated_protocol_message *protocol_message = - (struct aws_tls_negotiated_protocol_message *)message->message_data.buffer; - - protocol_message->protocol = sc_handler->protocol; - message->message_data.len = sizeof(struct aws_tls_negotiated_protocol_message); - if (aws_channel_slot_send_message(sc_handler->slot, message, AWS_CHANNEL_DIR_READ)) { - aws_mem_release(message->allocator, message); - aws_channel_shutdown(sc_handler->slot->channel, aws_last_error()); - } - } - - aws_on_tls_negotiation_completed(&sc_handler->shared_state, AWS_ERROR_SUCCESS); - - if (sc_handler->on_negotiation_result) { - sc_handler->on_negotiation_result(handler, sc_handler->slot, AWS_OP_SUCCESS, sc_handler->user_data); - } -} - -static int s_determine_sspi_error(int sspi_status) { - switch (sspi_status) { - case SEC_E_INSUFFICIENT_MEMORY: - return AWS_ERROR_OOM; - case SEC_I_CONTEXT_EXPIRED: - return AWS_IO_TLS_ALERT_NOT_GRACEFUL; - case SEC_E_WRONG_PRINCIPAL: - return AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE; - /* - case SEC_E_INVALID_HANDLE: - case SEC_E_INVALID_TOKEN: - case SEC_E_LOGON_DENIED: - case SEC_E_TARGET_UNKNOWN: - case SEC_E_NO_AUTHENTICATING_AUTHORITY: - case SEC_E_INTERNAL_ERROR: - case SEC_E_NO_CREDENTIALS: - case SEC_E_UNSUPPORTED_FUNCTION: - case SEC_E_APPLICATION_PROTOCOL_MISMATCH: - */ - default: - return AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE; - } -} - -#define CHECK_ALPN_BUFFER_SIZE(s, i, b) \ - if (s <= i) { \ - aws_array_list_clean_up(&b); \ - return aws_raise_error(AWS_ERROR_SHORT_BUFFER); \ - } - -/* construct ALPN extension data... apparently this works on big-endian machines? but I don't believe the docs - if you're running ARM and you find ALPN isn't working, it's probably because I trusted the documentation - and your bug is in here. Note, dotnet's corefx also acts like endianness isn't at play so if this is broken - so is everyone's dotnet code. */ -static int s_fillin_alpn_data( - struct aws_channel_handler *handler, - unsigned char *alpn_buffer_data, - size_t buffer_size, - size_t *written) { - *written = 0; - struct secure_channel_handler *sc_handler = handler->impl; - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, ""); - - struct aws_array_list alpn_buffers; - struct aws_byte_cursor alpn_buffer_array[4]; - aws_array_list_init_static(&alpn_buffers, alpn_buffer_array, 4, sizeof(struct aws_byte_cursor)); - - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "Setting ALPN extension with string %s.", aws_string_c_str(sc_handler->alpn_list)); - struct aws_byte_cursor alpn_str_cur = aws_byte_cursor_from_string(sc_handler->alpn_list); - if (aws_byte_cursor_split_on_char(&alpn_str_cur, ';', &alpn_buffers)) { - return AWS_OP_ERR; - } - - size_t protocols_count = aws_array_list_length(&alpn_buffers); - - size_t index = 0; - CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers) - uint32_t *extension_length = (uint32_t *)&alpn_buffer_data[index]; - index += sizeof(uint32_t); - CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers) - uint32_t *extension_name = (uint32_t *)&alpn_buffer_data[index]; - index += sizeof(uint32_t); - CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers) - uint16_t *protocols_byte_length = (uint16_t *)&alpn_buffer_data[index]; - index += sizeof(uint16_t); - CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint16_t), alpn_buffers) - - *extension_length += sizeof(uint32_t) + sizeof(uint16_t); - - *extension_name = SecApplicationProtocolNegotiationExt_ALPN; - /*now add the protocols*/ - for (size_t i = 0; i < protocols_count; ++i) { - struct aws_byte_cursor *protocol_ptr = NULL; - aws_array_list_get_at_ptr(&alpn_buffers, (void **)&protocol_ptr, i); - AWS_ASSERT(protocol_ptr); - *extension_length += (uint32_t)protocol_ptr->len + 1; - *protocols_byte_length += (uint16_t)protocol_ptr->len + 1; - CHECK_ALPN_BUFFER_SIZE(buffer_size, index + 1, alpn_buffers) - alpn_buffer_data[index++] = (unsigned char)protocol_ptr->len; - CHECK_ALPN_BUFFER_SIZE(buffer_size, index + protocol_ptr->len, alpn_buffers) - memcpy(alpn_buffer_data + index, protocol_ptr->ptr, protocol_ptr->len); - index += protocol_ptr->len; - } - - aws_array_list_clean_up(&alpn_buffers); - *written = *extension_length + sizeof(uint32_t); - return AWS_OP_SUCCESS; -} - -static int s_process_connection_state(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - return sc_handler->s_connection_state_fn(handler); -} - -static int s_do_application_data_decrypt(struct aws_channel_handler *handler); - -static int s_do_server_side_negotiation_step_2(struct aws_channel_handler *handler); - -/** invoked during the first step of the server's negotiation. It receives the client hello, - adds its alpn data if available, and if everything is good, sends out the server hello. */ -static int s_do_server_side_negotiation_step_1(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: server starting negotiation", (void *)handler); - - aws_on_drive_tls_negotiation(&sc_handler->shared_state); - - unsigned char alpn_buffer_data[128] = {0}; - SecBuffer input_bufs[] = { - { - .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer, - .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len, - .BufferType = SECBUFFER_TOKEN, - }, - { - .pvBuffer = NULL, - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - }, - }; - - SecBufferDesc input_bufs_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 2, - .pBuffers = input_bufs, - }; - -#ifdef SECBUFFER_APPLICATION_PROTOCOLS - if (sc_handler->alpn_list && aws_tls_is_alpn_available()) { - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Setting ALPN to %s", handler, aws_string_c_str(sc_handler->alpn_list)); - size_t extension_length = 0; - if (s_fillin_alpn_data(handler, alpn_buffer_data, sizeof(alpn_buffer_data), &extension_length)) { - return AWS_OP_ERR; - } - - input_bufs[1].pvBuffer = alpn_buffer_data, input_bufs[1].cbBuffer = (unsigned long)extension_length, - input_bufs[1].BufferType = SECBUFFER_APPLICATION_PROTOCOLS; - } -#endif /* SECBUFFER_APPLICATION_PROTOCOLS*/ - - sc_handler->ctx_req = ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY | - ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM; - - if (sc_handler->verify_peer) { - AWS_LOGF_DEBUG( - AWS_LS_IO_TLS, - "id=%p: server configured to use mutual tls, expecting a certficate from client.", - (void *)handler); - sc_handler->ctx_req |= ASC_REQ_MUTUAL_AUTH; - } - - SecBuffer output_buffer = { - .pvBuffer = NULL, - .cbBuffer = 0, - .BufferType = SECBUFFER_TOKEN, - }; - - SecBufferDesc output_buffer_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 1, - .pBuffers = &output_buffer, - }; - - /* process the client hello. */ - SECURITY_STATUS status = AcceptSecurityContext( - &sc_handler->creds, - NULL, - &input_bufs_desc, - sc_handler->ctx_req, - 0, - &sc_handler->sec_handle, - &output_buffer_desc, - &sc_handler->ctx_ret_flags, - NULL); - - if (!(status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK)) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, - "id=%p: error during processing of the ClientHello. SECURITY_STATUS is %d", - (void *)handler, - (int)status); - int error = s_determine_sspi_error(status); - aws_raise_error(error); - s_invoke_negotiation_error(handler, error); - return AWS_OP_ERR; - } - - size_t data_to_write_len = output_buffer.cbBuffer; - - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Sending ServerHello. Data size %zu", (void *)handler, data_to_write_len); - /* send the server hello. */ - struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( - sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, data_to_write_len); - if (!outgoing_message) { - FreeContextBuffer(output_buffer.pvBuffer); - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - - AWS_ASSERT(outgoing_message->message_data.capacity >= data_to_write_len); - memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer); - outgoing_message->message_data.len = output_buffer.cbBuffer; - FreeContextBuffer(output_buffer.pvBuffer); - - if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { - aws_mem_release(outgoing_message->allocator, outgoing_message); - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - - sc_handler->s_connection_state_fn = s_do_server_side_negotiation_step_2; - - return AWS_OP_SUCCESS; -} - -/* cipher change, key exchange, mutual TLS stuff. */ -static int s_do_server_side_negotiation_step_2(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, "id=%p: running step 2 of negotiation (cipher change, key exchange etc...)", (void *)handler); - SecBuffer input_buffers[] = { - [0] = - { - .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer, - .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len, - .BufferType = SECBUFFER_TOKEN, - }, - [1] = - { - .pvBuffer = NULL, - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - }, - }; - - SecBufferDesc input_buffers_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 2, - .pBuffers = input_buffers, - }; - - SecBuffer output_buffers[3]; - AWS_ZERO_ARRAY(output_buffers); - output_buffers[0].BufferType = SECBUFFER_TOKEN; - output_buffers[1].BufferType = SECBUFFER_ALERT; - - SecBufferDesc output_buffers_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 3, - .pBuffers = output_buffers, - }; - - sc_handler->read_extra = 0; - sc_handler->estimated_incomplete_size = 0; - - SECURITY_STATUS status = AcceptSecurityContext( - &sc_handler->creds, - &sc_handler->sec_handle, - &input_buffers_desc, - sc_handler->ctx_req, - 0, - NULL, - &output_buffers_desc, - &sc_handler->ctx_ret_flags, - &sc_handler->sspi_timestamp); - - if (status != SEC_E_INCOMPLETE_MESSAGE && status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, "id=%p: Error during negotiation. SECURITY_STATUS is %d", (void *)handler, (int)status); - int aws_error = s_determine_sspi_error(status); - aws_raise_error(aws_error); - s_invoke_negotiation_error(handler, aws_error); - return AWS_OP_ERR; - } - - if (status == SEC_E_INCOMPLETE_MESSAGE) { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, "id=%p: Last processed buffer was incomplete, waiting on more data.", (void *)handler); - sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer; - return aws_raise_error(AWS_IO_READ_WOULD_BLOCK); - }; - /* any output buffers that were filled in with SECBUFFER_TOKEN need to be sent, - SECBUFFER_EXTRA means we need to account for extra data and shift everything for the next run. */ - if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) { - for (size_t i = 0; i < output_buffers_desc.cBuffers; ++i) { - SecBuffer *buf_ptr = &output_buffers[i]; - - if (buf_ptr->BufferType == SECBUFFER_TOKEN && buf_ptr->cbBuffer) { - struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( - sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, buf_ptr->cbBuffer); - - if (!outgoing_message) { - FreeContextBuffer(buf_ptr->pvBuffer); - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - - memcpy(outgoing_message->message_data.buffer, buf_ptr->pvBuffer, buf_ptr->cbBuffer); - outgoing_message->message_data.len = buf_ptr->cbBuffer; - FreeContextBuffer(buf_ptr->pvBuffer); - - if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { - aws_mem_release(outgoing_message->allocator, outgoing_message); - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - } - } - - if (input_buffers[1].BufferType == SECBUFFER_EXTRA && input_buffers[1].cbBuffer > 0) { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Extra data recieved. Extra size is %lu", - (void *)handler, - input_buffers[1].cbBuffer); - sc_handler->read_extra = input_buffers[1].cbBuffer; - } - } - - if (status == SEC_E_OK) { - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: handshake completed", (void *)handler); - /* if a custom CA store was configured, we have to do the verification ourselves. */ - if (sc_handler->custom_ca_store) { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Custom CA was configured, evaluating trust before completing connection", - (void *)handler); - - if (s_manually_verify_peer_cert(handler)) { - aws_raise_error(AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); - s_invoke_negotiation_error(handler, AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); - return AWS_OP_ERR; - } - } - sc_handler->negotiation_finished = true; - - /* force query of the sizes so future calls to encrypt will be loaded. */ - s_message_overhead(handler); - - /* - grab the negotiated protocol out of the session. - */ -#ifdef SECBUFFER_APPLICATION_PROTOCOLS - if (sc_handler->alpn_list && aws_tls_is_alpn_available()) { - SecPkgContext_ApplicationProtocol alpn_result; - status = QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result); - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: ALPN is configured. Checking for negotiated protocol", handler); - - if (status == SEC_E_OK && alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { - aws_byte_buf_init(&sc_handler->protocol, handler->alloc, alpn_result.ProtocolIdSize + 1); - memset(sc_handler->protocol.buffer, 0, alpn_result.ProtocolIdSize + 1); - memcpy(sc_handler->protocol.buffer, alpn_result.ProtocolId, alpn_result.ProtocolIdSize); - sc_handler->protocol.len = alpn_result.ProtocolIdSize; - AWS_LOGF_DEBUG( - AWS_LS_IO_TLS, "id=%p: negotiated protocol %s", handler, (char *)sc_handler->protocol.buffer); - } else { - AWS_LOGF_WARN( - AWS_LS_IO_TLS, - "id=%p: Error retrieving negotiated protocol. SECURITY_STATUS is %d", - handler, - (int)status); - int aws_error = s_determine_sspi_error(status); - aws_raise_error(aws_error); - } - } -#endif - sc_handler->s_connection_state_fn = s_do_application_data_decrypt; - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: TLS handshake completed successfully.", (void *)handler); - s_on_negotiation_success(handler); - } - - return AWS_OP_SUCCESS; -} - -static int s_do_client_side_negotiation_step_2(struct aws_channel_handler *handler); - -/* send the client hello */ -static int s_do_client_side_negotiation_step_1(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: client starting negotiation", (void *)handler); - - aws_on_drive_tls_negotiation(&sc_handler->shared_state); - - unsigned char alpn_buffer_data[128] = {0}; - SecBuffer input_buf = { - .pvBuffer = NULL, - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - }; - - SecBufferDesc input_buf_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 1, - .pBuffers = &input_buf, - }; - - SecBufferDesc *alpn_sspi_data = NULL; - - /* add alpn data to the client hello if it's supported. */ -#ifdef SECBUFFER_APPLICATION_PROTOCOLS - if (sc_handler->alpn_list && aws_tls_is_alpn_available()) { - AWS_LOGF_DEBUG( - AWS_LS_IO_TLS, "id=%p: Setting ALPN data as %s", handler, aws_string_c_str(sc_handler->alpn_list)); - size_t extension_length = 0; - if (s_fillin_alpn_data(handler, alpn_buffer_data, sizeof(alpn_buffer_data), &extension_length)) { - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - - input_buf.pvBuffer = alpn_buffer_data, input_buf.cbBuffer = (unsigned long)extension_length, - input_buf.BufferType = SECBUFFER_APPLICATION_PROTOCOLS; - - alpn_sspi_data = &input_buf_desc; - } -#endif /* SECBUFFER_APPLICATION_PROTOCOLS*/ - - sc_handler->ctx_req = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | - ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; - - SecBuffer output_buffer = { - .pvBuffer = NULL, - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - }; - - SecBufferDesc output_buffer_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 1, - .pBuffers = &output_buffer, - }; - - char server_name_cstr[256]; - AWS_ZERO_ARRAY(server_name_cstr); - AWS_ASSERT(sc_handler->server_name.len < 256); - memcpy(server_name_cstr, sc_handler->server_name.buffer, sc_handler->server_name.len); - - SECURITY_STATUS status = InitializeSecurityContextA( - &sc_handler->creds, - NULL, - (SEC_CHAR *)server_name_cstr, - sc_handler->ctx_req, - 0, - 0, - alpn_sspi_data, - 0, - &sc_handler->sec_handle, - &output_buffer_desc, - &sc_handler->ctx_ret_flags, - &sc_handler->sspi_timestamp); - - if (status != SEC_I_CONTINUE_NEEDED) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, - "id=%p: Error sending client/receiving server handshake data. SECURITY_STATUS is %d", - (void *)handler, - (int)status); - int aws_error = s_determine_sspi_error(status); - aws_raise_error(aws_error); - s_invoke_negotiation_error(handler, aws_error); - return AWS_OP_ERR; - } - - size_t data_to_write_len = output_buffer.cbBuffer; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, "id=%p: Sending client handshake data of size %zu", (void *)handler, data_to_write_len); - - struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( - sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, data_to_write_len); - if (!outgoing_message) { - FreeContextBuffer(output_buffer.pvBuffer); - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - - AWS_ASSERT(outgoing_message->message_data.capacity >= data_to_write_len); - memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer); - outgoing_message->message_data.len = output_buffer.cbBuffer; - FreeContextBuffer(output_buffer.pvBuffer); - - if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { - aws_mem_release(outgoing_message->allocator, outgoing_message); - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - - sc_handler->s_connection_state_fn = s_do_client_side_negotiation_step_2; - - return AWS_OP_SUCCESS; -} - -/* cipher exchange, key exchange etc.... */ -static int s_do_client_side_negotiation_step_2(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: running step 2 of client-side negotiation (cipher change, key exchange etc...)", - (void *)handler); - - SecBuffer input_buffers[] = { - [0] = - { - .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer, - .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len, - .BufferType = SECBUFFER_TOKEN, - }, - [1] = - { - .pvBuffer = NULL, - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - }, - }; - - SecBufferDesc input_buffers_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 2, - .pBuffers = input_buffers, - }; - - SecBuffer output_buffers[3]; - AWS_ZERO_ARRAY(output_buffers); - output_buffers[0].BufferType = SECBUFFER_TOKEN; - output_buffers[1].BufferType = SECBUFFER_ALERT; - - SecBufferDesc output_buffers_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 3, - .pBuffers = output_buffers, - }; - - SECURITY_STATUS status = SEC_E_OK; - - sc_handler->read_extra = 0; - sc_handler->estimated_incomplete_size = 0; - - char server_name_cstr[256]; - AWS_ZERO_ARRAY(server_name_cstr); - AWS_FATAL_ASSERT(sc_handler->server_name.len < sizeof(server_name_cstr)); - memcpy(server_name_cstr, sc_handler->server_name.buffer, sc_handler->server_name.len); - - status = InitializeSecurityContextA( - &sc_handler->creds, - &sc_handler->sec_handle, - (SEC_CHAR *)server_name_cstr, - sc_handler->ctx_req, - 0, - 0, - &input_buffers_desc, - 0, - NULL, - &output_buffers_desc, - &sc_handler->ctx_ret_flags, - &sc_handler->sspi_timestamp); - - if (status != SEC_E_INCOMPLETE_MESSAGE && status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, "id=%p: Error during negotiation. SECURITY_STATUS is %d", (void *)handler, (int)status); - int aws_error = s_determine_sspi_error(status); - aws_raise_error(aws_error); - s_invoke_negotiation_error(handler, aws_error); - return AWS_OP_ERR; - } - - if (status == SEC_E_INCOMPLETE_MESSAGE) { - sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Incomplete buffer recieved. Incomplete size is %zu. Waiting for more data.", - (void *)handler, - sc_handler->estimated_incomplete_size); - return aws_raise_error(AWS_IO_READ_WOULD_BLOCK); - } - - if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) { - for (size_t i = 0; i < output_buffers_desc.cBuffers; ++i) { - SecBuffer *buf_ptr = &output_buffers[i]; - - if (buf_ptr->BufferType == SECBUFFER_TOKEN && buf_ptr->cbBuffer) { - struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( - sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, buf_ptr->cbBuffer); - - if (!outgoing_message) { - FreeContextBuffer(buf_ptr->pvBuffer); - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - - memcpy(outgoing_message->message_data.buffer, buf_ptr->pvBuffer, buf_ptr->cbBuffer); - outgoing_message->message_data.len = buf_ptr->cbBuffer; - FreeContextBuffer(buf_ptr->pvBuffer); - - if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { - aws_mem_release(outgoing_message->allocator, outgoing_message); - s_invoke_negotiation_error(handler, aws_last_error()); - return AWS_OP_ERR; - } - } - } - - if (input_buffers[1].BufferType == SECBUFFER_EXTRA && input_buffers[1].cbBuffer > 0) { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Extra data recieved. Extra data size is %lu.", - (void *)handler, - input_buffers[1].cbBuffer); - sc_handler->read_extra = input_buffers[1].cbBuffer; - } - } - - if (status == SEC_E_OK) { - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: handshake completed", handler); - /* if a custom CA store was configured, we have to do the verification ourselves. */ - if (sc_handler->custom_ca_store) { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Custom CA was configured, evaluating trust before completing connection", - (void *)handler); - if (s_manually_verify_peer_cert(handler)) { - aws_raise_error(AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); - s_invoke_negotiation_error(handler, AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); - return AWS_OP_ERR; - } - } - sc_handler->negotiation_finished = true; - /* force the sizes query, so future Encrypt message calls work.*/ - s_message_overhead(handler); - -#ifdef SECBUFFER_APPLICATION_PROTOCOLS - if (sc_handler->alpn_list && aws_tls_is_alpn_available()) { - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Retrieving negotiated protocol.", handler); - SecPkgContext_ApplicationProtocol alpn_result; - status = QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result); - - if (status == SEC_E_OK && alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { - aws_byte_buf_init(&sc_handler->protocol, handler->alloc, alpn_result.ProtocolIdSize + 1); - memset(sc_handler->protocol.buffer, 0, alpn_result.ProtocolIdSize + 1); - memcpy(sc_handler->protocol.buffer, alpn_result.ProtocolId, alpn_result.ProtocolIdSize); - sc_handler->protocol.len = alpn_result.ProtocolIdSize; - AWS_LOGF_DEBUG( - AWS_LS_IO_TLS, "id=%p: Negotiated protocol %s", handler, (char *)sc_handler->protocol.buffer); - } else { - AWS_LOGF_WARN( - AWS_LS_IO_TLS, - "id=%p: Error retrieving negotiated protocol. SECURITY_STATUS is %d", - handler, - (int)status); - int aws_error = s_determine_sspi_error(status); - aws_raise_error(aws_error); - } - } -#endif - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: TLS handshake completed successfully.", (void *)handler); - sc_handler->s_connection_state_fn = s_do_application_data_decrypt; - s_on_negotiation_success(handler); - } - - return AWS_OP_SUCCESS; -} - -static int s_do_application_data_decrypt(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - - /* I know this is an unncessary initialization, it's initialized here to make linters happy.*/ - int error = AWS_OP_ERR; - /* when we get an Extra buffer we have to move the pointer and replay the buffer, so we loop until we don't have - any extra buffers left over, in the last phase, we then go ahead and send the output. This state function will - always say BLOCKED_ON_READ, AWS_IO_TLS_ERROR_READ_FAILURE or SUCCESS. There will never be left over reads.*/ - do { - error = AWS_OP_ERR; - /* 4 buffers are needed, only one is input, the others get zeroed out for the output operation. */ - SecBuffer input_buffers[4]; - AWS_ZERO_ARRAY(input_buffers); - - size_t read_len = sc_handler->read_extra ? sc_handler->read_extra : sc_handler->buffered_read_in_data_buf.len; - size_t offset = sc_handler->read_extra ? sc_handler->buffered_read_in_data_buf.len - sc_handler->read_extra : 0; - sc_handler->read_extra = 0; - - input_buffers[0] = (SecBuffer){ - .cbBuffer = (unsigned long)(read_len), - .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer + offset, - .BufferType = SECBUFFER_DATA, - }; - - SecBufferDesc buffer_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 4, - .pBuffers = input_buffers, - }; - - SECURITY_STATUS status = DecryptMessage(&sc_handler->sec_handle, &buffer_desc, 0, NULL); - - if (status == SEC_E_OK) { - error = AWS_OP_SUCCESS; - /* if SECBUFFER_DATA is the buffer type of the second buffer, we have decrypted data to process. - If SECBUFFER_DATA is the type for the fourth buffer we need to keep track of it so we can shift - everything before doing another decrypt operation. - We don't care what's in the third buffer for TLS usage.*/ - if (input_buffers[1].BufferType == SECBUFFER_DATA) { - size_t decrypted_length = input_buffers[1].cbBuffer; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, "id=%p: Decrypted message with length %zu.", (void *)handler, decrypted_length); - - struct aws_byte_cursor to_append = - aws_byte_cursor_from_array(input_buffers[1].pvBuffer, decrypted_length); - int append_failed = aws_byte_buf_append(&sc_handler->buffered_read_out_data_buf, &to_append); - AWS_ASSERT(!append_failed); - (void)append_failed; - - /* if we have extra we have to move the pointer and do another Decrypt operation. */ - if (input_buffers[3].BufferType == SECBUFFER_EXTRA) { - sc_handler->read_extra = input_buffers[3].cbBuffer; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Extra (incomplete) message received with length %zu.", - (void *)handler, - sc_handler->read_extra); - } else { - error = AWS_OP_SUCCESS; - /* this means we processed everything in the buffer. */ - sc_handler->buffered_read_in_data_buf.len = 0; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Decrypt ended exactly on the end of the record, resetting buffer.", - (void *)handler); - } - } - } - /* SEC_E_INCOMPLETE_MESSAGE means the message we tried to decrypt isn't a full record and we need to - append our next read to it and try again. */ - else if (status == SEC_E_INCOMPLETE_MESSAGE) { - sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: (incomplete) message received. Expecting remaining portion of size %zu.", - (void *)handler, - sc_handler->estimated_incomplete_size); - memmove( - sc_handler->buffered_read_in_data_buf.buffer, - sc_handler->buffered_read_in_data_buf.buffer + offset, - read_len); - sc_handler->buffered_read_in_data_buf.len = read_len; - aws_raise_error(AWS_IO_READ_WOULD_BLOCK); - } - /* SEC_I_CONTEXT_EXPIRED means that the message sender has shut down the connection. One such case - where this can happen is an unaccepted certificate. */ - else if (status == SEC_I_CONTEXT_EXPIRED) { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Alert received. Message sender has shut down the connection. SECURITY_STATUS is %d.", - (void *)handler, - (int)status); - - struct aws_channel_slot *slot = handler->slot; - aws_channel_shutdown(slot->channel, AWS_OP_SUCCESS); - error = AWS_OP_SUCCESS; - } else { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, "id=%p: Error decrypting message. SECURITY_STATUS is %d.", (void *)handler, (int)status); - aws_raise_error(AWS_IO_TLS_ERROR_READ_FAILURE); - } - } while (sc_handler->read_extra); - - return error; -} - -static int s_process_pending_output_messages(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - - size_t downstream_window = SIZE_MAX; - - if (sc_handler->slot->adj_right) { - downstream_window = aws_channel_slot_downstream_read_window(sc_handler->slot); - } - - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Processing incomming messages. Downstream window is %zu", - (void *)handler, - downstream_window); - while (sc_handler->buffered_read_out_data_buf.len && downstream_window) { - size_t requested_message_size = sc_handler->buffered_read_out_data_buf.len > downstream_window - ? downstream_window - : sc_handler->buffered_read_out_data_buf.len; - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Requested message size is %zu", (void *)handler, requested_message_size); - - if (sc_handler->slot->adj_right) { - struct aws_io_message *read_out_msg = aws_channel_acquire_message_from_pool( - sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, requested_message_size); - - if (!read_out_msg) { - return AWS_OP_ERR; - } - - size_t copy_size = read_out_msg->message_data.capacity < requested_message_size - ? read_out_msg->message_data.capacity - : requested_message_size; - - memcpy(read_out_msg->message_data.buffer, sc_handler->buffered_read_out_data_buf.buffer, copy_size); - read_out_msg->message_data.len = copy_size; - - memmove( - sc_handler->buffered_read_out_data_buf.buffer, - sc_handler->buffered_read_out_data_buf.buffer + copy_size, - sc_handler->buffered_read_out_data_buf.len - copy_size); - sc_handler->buffered_read_out_data_buf.len -= copy_size; - - if (sc_handler->on_data_read) { - sc_handler->on_data_read(handler, sc_handler->slot, &read_out_msg->message_data, sc_handler->user_data); - } - if (aws_channel_slot_send_message(sc_handler->slot, read_out_msg, AWS_CHANNEL_DIR_READ)) { - aws_mem_release(read_out_msg->allocator, read_out_msg); - return AWS_OP_ERR; - } - - if (sc_handler->slot->adj_right) { - downstream_window = aws_channel_slot_downstream_read_window(sc_handler->slot); - } - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Downstream window is %zu", (void *)handler, downstream_window); - } else { - if (sc_handler->on_data_read) { - sc_handler->on_data_read( - handler, sc_handler->slot, &sc_handler->buffered_read_out_data_buf, sc_handler->user_data); - } - sc_handler->buffered_read_out_data_buf.len = 0; - } - } - - return AWS_OP_SUCCESS; -} - -static void s_process_pending_output_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) { - (void)task; - struct aws_channel_handler *handler = arg; - - aws_channel_task_init(task, NULL, NULL, "secure_channel_handler_process_pending_output"); - if (status == AWS_TASK_STATUS_RUN_READY) { - if (s_process_pending_output_messages(handler)) { - struct secure_channel_handler *sc_handler = arg; - aws_channel_shutdown(sc_handler->slot->channel, aws_last_error()); - } - } -} - -static int s_process_read_message( - struct aws_channel_handler *handler, - struct aws_channel_slot *slot, - struct aws_io_message *message) { - - struct secure_channel_handler *sc_handler = handler->impl; - - if (message) { - /* note, most of these functions log internally, so the log messages in this function are sparse. */ - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: processing incoming message of size %zu", - (void *)handler, - message->message_data.len); - - struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data); - - /* The SSPI interface forces us to manage incomplete records manually. So when we had extra after - the previous read, it needs to be shifted to the beginning of the current read, then the current - read data is appended to it. If we had an incomplete record, we don't need to shift anything but - we do need to append the current read data to the end of the incomplete record from the previous read. - Keep going until we've processed everything in the message we were just passed. - */ - int err = AWS_OP_SUCCESS; - while (!err && message_cursor.len) { - - size_t available_buffer_space = - sc_handler->buffered_read_in_data_buf.capacity - sc_handler->buffered_read_in_data_buf.len; - size_t available_message_len = message_cursor.len; - size_t amount_to_move_to_buffer = - available_buffer_space > available_message_len ? available_message_len : available_buffer_space; - - memcpy( - sc_handler->buffered_read_in_data_buf.buffer + sc_handler->buffered_read_in_data_buf.len, - message_cursor.ptr, - amount_to_move_to_buffer); - sc_handler->buffered_read_in_data_buf.len += amount_to_move_to_buffer; - - err = sc_handler->s_connection_state_fn(handler); - - if (err && aws_last_error() == AWS_IO_READ_WOULD_BLOCK) { - if (sc_handler->buffered_read_in_data_buf.len == sc_handler->buffered_read_in_data_buf.capacity) { - /* throw this one as a protocol error. */ - aws_raise_error(AWS_IO_TLS_ERROR_WRITE_FAILURE); - } else { - if (sc_handler->buffered_read_out_data_buf.len) { - err = s_process_pending_output_messages(handler); - if (err) { - break; - } - } - /* prevent a deadlock due to downstream handlers wanting more data, but we have an incomplete - record, and the amount they're requesting is less than the size of a tls record. */ - size_t window_size = slot->window_size; - if (!window_size && - aws_channel_slot_increment_read_window(slot, sc_handler->estimated_incomplete_size)) { - err = AWS_OP_ERR; - } else { - sc_handler->estimated_incomplete_size = 0; - err = AWS_OP_SUCCESS; - } - } - aws_byte_cursor_advance(&message_cursor, amount_to_move_to_buffer); - continue; - } else if (err) { - break; - } - - /* handle any left over extra data from the decrypt operation here. */ - if (sc_handler->read_extra) { - size_t move_pos = sc_handler->buffered_read_in_data_buf.len - sc_handler->read_extra; - memmove( - sc_handler->buffered_read_in_data_buf.buffer, - sc_handler->buffered_read_in_data_buf.buffer + move_pos, - sc_handler->read_extra); - sc_handler->buffered_read_in_data_buf.len = sc_handler->read_extra; - sc_handler->read_extra = 0; - } else { - sc_handler->buffered_read_in_data_buf.len = 0; - } - - if (sc_handler->buffered_read_out_data_buf.len) { - err = s_process_pending_output_messages(handler); - if (err) { - break; - } - } - aws_byte_cursor_advance(&message_cursor, amount_to_move_to_buffer); - } - - if (!err) { - aws_mem_release(message->allocator, message); - return AWS_OP_SUCCESS; - } - - aws_channel_shutdown(slot->channel, aws_last_error()); - return AWS_OP_ERR; - } - - if (sc_handler->buffered_read_out_data_buf.len) { - if (s_process_pending_output_messages(handler)) { - return AWS_OP_ERR; - } - aws_mem_release(message->allocator, message); - } - - return AWS_OP_SUCCESS; -} - -static int s_process_write_message( - struct aws_channel_handler *handler, - struct aws_channel_slot *slot, - struct aws_io_message *message) { - - struct secure_channel_handler *sc_handler = (struct secure_channel_handler *)handler->impl; - AWS_ASSERT(sc_handler->negotiation_finished); - SECURITY_STATUS status = SEC_E_OK; - - if (message) { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, "id=%p: processing ougoing message of size %zu", (void *)handler, message->message_data.len); - - struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data); - - while (message_cursor.len) { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, "id=%p: processing message fragment of size %zu", (void *)handler, message_cursor.len); - /* message size will be the lesser of either payload + record overhead or the max TLS record size.*/ - size_t upstream_overhead = aws_channel_slot_upstream_message_overhead(sc_handler->slot); - upstream_overhead += sc_handler->stream_sizes.cbHeader + sc_handler->stream_sizes.cbTrailer; - size_t requested_length = message_cursor.len + upstream_overhead; - size_t to_write = sc_handler->stream_sizes.cbMaximumMessage < requested_length - ? sc_handler->stream_sizes.cbMaximumMessage - : requested_length; - struct aws_io_message *outgoing_message = - aws_channel_acquire_message_from_pool(slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, to_write); - - if (!outgoing_message) { - return AWS_OP_ERR; - } - if (outgoing_message->message_data.capacity <= upstream_overhead) { - aws_mem_release(outgoing_message->allocator, outgoing_message); - return aws_raise_error(AWS_ERROR_INVALID_STATE); - } - - /* what if message is larger than one record? */ - size_t original_message_fragment_to_process = outgoing_message->message_data.capacity - upstream_overhead; - memcpy( - outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader, - message_cursor.ptr, - original_message_fragment_to_process); - - if (original_message_fragment_to_process == message_cursor.len) { - outgoing_message->on_completion = message->on_completion; - outgoing_message->user_data = message->user_data; - } - - SecBuffer buffers[4] = { - [0] = - { - .BufferType = SECBUFFER_STREAM_HEADER, - .pvBuffer = outgoing_message->message_data.buffer, - .cbBuffer = sc_handler->stream_sizes.cbHeader, - }, - [1] = - { - .BufferType = SECBUFFER_DATA, - .pvBuffer = outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader, - .cbBuffer = (unsigned long)original_message_fragment_to_process, - }, - [2] = - { - .BufferType = SECBUFFER_STREAM_TRAILER, - .pvBuffer = outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader + - original_message_fragment_to_process, - .cbBuffer = sc_handler->stream_sizes.cbTrailer, - }, - [3] = - { - .BufferType = SECBUFFER_EMPTY, - .pvBuffer = NULL, - .cbBuffer = 0, - }, - }; - - SecBufferDesc buffer_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 4, - .pBuffers = buffers, - }; - - status = EncryptMessage(&sc_handler->sec_handle, 0, &buffer_desc, 0); - - if (status == SEC_E_OK) { - outgoing_message->message_data.len = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p:message fragment encrypted successfully: size is %zu", - (void *)handler, - outgoing_message->message_data.len); - - if (aws_channel_slot_send_message(slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { - aws_mem_release(outgoing_message->allocator, outgoing_message); - return AWS_OP_ERR; - } - - aws_byte_cursor_advance(&message_cursor, original_message_fragment_to_process); - } else { - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, - "id=%p: Error encrypting message. SECURITY_STATUS is %d", - (void *)handler, - (int)status); - return aws_raise_error(AWS_IO_TLS_ERROR_WRITE_FAILURE); - } - } - - aws_mem_release(message->allocator, message); - } - - return AWS_OP_SUCCESS; -} - -static int s_increment_read_window(struct aws_channel_handler *handler, struct aws_channel_slot *slot, size_t size) { - (void)size; - struct secure_channel_handler *sc_handler = handler->impl; - AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Increment read window message received %zu", (void *)handler, size); - - /* You can't query a context if negotiation isn't completed, since ciphers haven't been negotiated - * and it couldn't possibly know the overhead size yet. */ - if (sc_handler->negotiation_finished && !sc_handler->stream_sizes.cbMaximumMessage) { - SECURITY_STATUS status = - QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_STREAM_SIZES, &sc_handler->stream_sizes); - - if (status != SEC_E_OK) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, "id=%p: QueryContextAttributes failed with error %d", (void *)handler, (int)status); - aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); - aws_channel_shutdown(slot->channel, AWS_ERROR_SYS_CALL_FAILURE); - return AWS_OP_ERR; - } - } - - size_t total_desired_size = size; - size_t downstream_size = aws_channel_slot_downstream_read_window(slot); - size_t current_window_size = slot->window_size; - - /* the only time this branch isn't taken is when a window update is propagated during tls negotiation. - * in that case just pass it through. */ - if (sc_handler->stream_sizes.cbMaximumMessage) { - size_t likely_records_count = (size_t)ceil((double)(downstream_size) / (double)(READ_IN_SIZE)); - size_t offset_size = aws_mul_size_saturating( - likely_records_count, sc_handler->stream_sizes.cbTrailer + sc_handler->stream_sizes.cbHeader); - total_desired_size = aws_add_size_saturating(offset_size, downstream_size); - } - - if (total_desired_size > current_window_size) { - size_t window_update_size = total_desired_size - current_window_size; - AWS_LOGF_TRACE( - AWS_LS_IO_TLS, "id=%p: Propagating read window increment of size %zu", (void *)handler, window_update_size); - aws_channel_slot_increment_read_window(slot, window_update_size); - } - - if (sc_handler->negotiation_finished && !sc_handler->sequential_task_storage.task_fn) { - aws_channel_task_init( - &sc_handler->sequential_task_storage, - s_process_pending_output_task, - handler, - "secure_channel_handler_process_pending_output_on_window_increment"); - aws_channel_schedule_task_now(slot->channel, &sc_handler->sequential_task_storage); - } - return AWS_OP_SUCCESS; -} - -static size_t s_initial_window_size(struct aws_channel_handler *handler) { - (void)handler; - - /* set this to just enough for the handshake, once the handshake completes, the downstream - handler will tell us the new window size. */ - return EST_HANDSHAKE_SIZE; -} - -static int s_handler_shutdown( - struct aws_channel_handler *handler, - struct aws_channel_slot *slot, - enum aws_channel_direction dir, - int error_code, - bool abort_immediately) { - struct secure_channel_handler *sc_handler = handler->impl; - - if (dir == AWS_CHANNEL_DIR_WRITE) { - if (!abort_immediately && error_code != AWS_IO_SOCKET_CLOSED) { - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Shutting down the write direction", (void *)handler); - - /* send a TLS alert. */ - SECURITY_STATUS status; - - DWORD shutdown_code = SCHANNEL_SHUTDOWN; - SecBuffer shutdown_buffer = { - .pvBuffer = &shutdown_code, - .cbBuffer = sizeof(shutdown_code), - .BufferType = SECBUFFER_TOKEN, - }; - - SecBufferDesc shutdown_buffer_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 1, - .pBuffers = &shutdown_buffer, - }; - - /* this updates the SSPI internal state machine. */ - status = ApplyControlToken(&sc_handler->sec_handle, &shutdown_buffer_desc); - - if (status != SEC_E_OK) { - aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); - return aws_channel_slot_on_handler_shutdown_complete( - slot, dir, AWS_ERROR_SYS_CALL_FAILURE, abort_immediately); - } - - SecBuffer output_buffer = { - .pvBuffer = NULL, - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - }; - - SecBufferDesc output_buffer_desc = { - .ulVersion = SECBUFFER_VERSION, - .cBuffers = 1, - .pBuffers = &output_buffer, - }; - - struct aws_byte_buf server_name = aws_tls_handler_server_name(handler); - char server_name_cstr[256]; - AWS_ZERO_ARRAY(server_name_cstr); - AWS_FATAL_ASSERT(server_name.len < sizeof(server_name_cstr)); - memcpy(server_name_cstr, server_name.buffer, server_name.len); - /* this acutally gives us an Alert record to send. */ - status = InitializeSecurityContextA( - &sc_handler->creds, - &sc_handler->sec_handle, - (SEC_CHAR *)server_name_cstr, - sc_handler->ctx_req, - 0, - 0, - NULL, - 0, - NULL, - &output_buffer_desc, - &sc_handler->ctx_ret_flags, - NULL); - - if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED) { - struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool( - slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, output_buffer.cbBuffer); - - if (!outgoing_message || outgoing_message->message_data.capacity < output_buffer.cbBuffer) { - return aws_channel_slot_on_handler_shutdown_complete(slot, dir, aws_last_error(), true); - } - memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer); - outgoing_message->message_data.len = output_buffer.cbBuffer; - - /* we don't really care if this succeeds or not, it's just sending the TLS alert. */ - if (aws_channel_slot_send_message(slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) { - aws_mem_release(outgoing_message->allocator, outgoing_message); - } - } - } - } - - return aws_channel_slot_on_handler_shutdown_complete(slot, dir, error_code, abort_immediately); -} - -static void s_do_negotiation_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) { - (void)task; - - struct aws_channel_handler *handler = arg; - struct secure_channel_handler *sc_handler = handler->impl; - - if (status == AWS_TASK_STATUS_RUN_READY) { - int err = sc_handler->s_connection_state_fn(handler); - if (err) { - aws_channel_shutdown(sc_handler->slot->channel, aws_last_error()); - } - } -} - -static void s_secure_channel_handler_destroy( - struct aws_allocator *allocator, - struct secure_channel_handler *sc_handler) { - - if (sc_handler == NULL) { - return; - } - - if (sc_handler->protocol.buffer) { - aws_byte_buf_clean_up(&sc_handler->protocol); - } - - if (sc_handler->alpn_list) { - aws_string_destroy(sc_handler->alpn_list); - } - - if (sc_handler->server_name.buffer) { - aws_byte_buf_clean_up(&sc_handler->server_name); - } - - if (sc_handler->sec_handle.dwLower || sc_handler->sec_handle.dwUpper) { - DeleteSecurityContext(&sc_handler->sec_handle); - } - - if (sc_handler->creds.dwLower || sc_handler->creds.dwUpper) { - DeleteSecurityContext(&sc_handler->creds); - } - - aws_tls_channel_handler_shared_clean_up(&sc_handler->shared_state); - - aws_mem_release(allocator, sc_handler); -} - -static void s_handler_destroy(struct aws_channel_handler *handler) { - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: destroying handler", (void *)handler); - struct secure_channel_handler *sc_handler = handler->impl; - - s_secure_channel_handler_destroy(handler->alloc, sc_handler); -} - -static void s_reset_statistics(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - - aws_crt_statistics_tls_reset(&sc_handler->shared_state.stats); -} - -static void s_gather_statistics(struct aws_channel_handler *handler, struct aws_array_list *stats) { - struct secure_channel_handler *sc_handler = handler->impl; - - void *stats_base = &sc_handler->shared_state.stats; - aws_array_list_push_back(stats, &stats_base); -} - -int aws_tls_client_handler_start_negotiation(struct aws_channel_handler *handler) { - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Kicking off TLS negotiation", (void *)handler); - - struct secure_channel_handler *sc_handler = handler->impl; - - if (aws_channel_thread_is_callers_thread(sc_handler->slot->channel)) { - int err = sc_handler->s_connection_state_fn(handler); - if (err) { - aws_channel_shutdown(sc_handler->slot->channel, aws_last_error()); - } - return err; - } - - aws_channel_task_init( - &sc_handler->sequential_task_storage, - s_do_negotiation_task, - handler, - "secure_channel_handler_start_negotation"); - aws_channel_schedule_task_now(sc_handler->slot->channel, &sc_handler->sequential_task_storage); - return AWS_OP_SUCCESS; -} - -struct aws_byte_buf aws_tls_handler_protocol(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - return sc_handler->protocol; -} - -struct aws_byte_buf aws_tls_handler_server_name(struct aws_channel_handler *handler) { - struct secure_channel_handler *sc_handler = handler->impl; - return sc_handler->server_name; -} - -static struct aws_channel_handler_vtable s_handler_vtable = { - .destroy = s_handler_destroy, - .process_read_message = s_process_read_message, - .process_write_message = s_process_write_message, - .shutdown = s_handler_shutdown, - .increment_read_window = s_increment_read_window, - .initial_window_size = s_initial_window_size, - .message_overhead = s_message_overhead, - .reset_statistics = s_reset_statistics, - .gather_statistics = s_gather_statistics, -}; - -static struct aws_channel_handler *s_tls_handler_new( - struct aws_allocator *alloc, - struct aws_tls_connection_options *options, - struct aws_channel_slot *slot, - bool is_client_mode) { - AWS_ASSERT(options->ctx); - - struct secure_channel_handler *sc_handler = aws_mem_calloc(alloc, 1, sizeof(struct secure_channel_handler)); - if (!sc_handler) { - return NULL; - } - - sc_handler->handler.alloc = alloc; - sc_handler->handler.impl = sc_handler; - sc_handler->handler.vtable = &s_handler_vtable; - sc_handler->handler.slot = slot; - - aws_tls_channel_handler_shared_init(&sc_handler->shared_state, &sc_handler->handler, options); - - struct secure_channel_ctx *sc_ctx = options->ctx->impl; - - unsigned long credential_use = SECPKG_CRED_INBOUND; - if (is_client_mode) { - credential_use = SECPKG_CRED_OUTBOUND; - } - - SECURITY_STATUS status = AcquireCredentialsHandleA( - NULL, - UNISP_NAME, - credential_use, - NULL, - &sc_ctx->credentials, - NULL, - NULL, - &sc_handler->creds, - &sc_handler->sspi_timestamp); - - if (status != SEC_E_OK) { - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "Error on AcquireCredentialsHandle. SECURITY_STATUS is %d", (int)status); - int aws_error = s_determine_sspi_error(status); - aws_raise_error(aws_error); - goto on_error; - } - - sc_handler->advertise_alpn_message = options->advertise_alpn_message; - sc_handler->on_data_read = options->on_data_read; - sc_handler->on_error = options->on_error; - sc_handler->on_negotiation_result = options->on_negotiation_result; - sc_handler->user_data = options->user_data; - - if (!options->alpn_list && sc_ctx->alpn_list) { - sc_handler->alpn_list = aws_string_new_from_string(alloc, sc_ctx->alpn_list); - if (!sc_handler->alpn_list) { - goto on_error; - } - } else if (options->alpn_list) { - sc_handler->alpn_list = aws_string_new_from_string(alloc, options->alpn_list); - if (!sc_handler->alpn_list) { - goto on_error; - } - } - - if (options->server_name) { - AWS_LOGF_DEBUG( - AWS_LS_IO_TLS, - "id=%p: Setting SNI to %s", - (void *)&sc_handler->handler, - aws_string_c_str(options->server_name)); - struct aws_byte_cursor server_name_crsr = aws_byte_cursor_from_string(options->server_name); - if (aws_byte_buf_init_copy_from_cursor(&sc_handler->server_name, alloc, server_name_crsr)) { - goto on_error; - } - } - - sc_handler->slot = slot; - - if (is_client_mode) { - sc_handler->s_connection_state_fn = s_do_client_side_negotiation_step_1; - } else { - sc_handler->s_connection_state_fn = s_do_server_side_negotiation_step_1; - } - - sc_handler->custom_ca_store = sc_ctx->custom_trust_store; - sc_handler->buffered_read_in_data_buf = - aws_byte_buf_from_array(sc_handler->buffered_read_in_data, sizeof(sc_handler->buffered_read_in_data)); - sc_handler->buffered_read_in_data_buf.len = 0; - sc_handler->buffered_read_out_data_buf = - aws_byte_buf_from_array(sc_handler->buffered_read_out_data, sizeof(sc_handler->buffered_read_out_data)); - sc_handler->buffered_read_out_data_buf.len = 0; - sc_handler->verify_peer = sc_ctx->verify_peer; - - return &sc_handler->handler; - -on_error: - - s_secure_channel_handler_destroy(alloc, sc_handler); - - return NULL; -} -struct aws_channel_handler *aws_tls_client_handler_new( - struct aws_allocator *allocator, - struct aws_tls_connection_options *options, - struct aws_channel_slot *slot) { - - return s_tls_handler_new(allocator, options, slot, true); -} - -struct aws_channel_handler *aws_tls_server_handler_new( - struct aws_allocator *allocator, - struct aws_tls_connection_options *options, - struct aws_channel_slot *slot) { - - return s_tls_handler_new(allocator, options, slot, false); -} - -static void s_secure_channel_ctx_destroy(struct secure_channel_ctx *secure_channel_ctx) { - if (secure_channel_ctx == NULL) { - return; - } - - if (secure_channel_ctx->private_key) { - CryptDestroyKey(secure_channel_ctx->private_key); - } - - if (secure_channel_ctx->crypto_provider) { - CryptReleaseContext(secure_channel_ctx->crypto_provider, 0); - } - - if (secure_channel_ctx->custom_trust_store) { - aws_close_cert_store(secure_channel_ctx->custom_trust_store); - } - - if (secure_channel_ctx->pcerts) { - /** - * Only free the private certificate context if the private key is NOT - * from the certificate context because freeing the private key - * using CryptDestroyKey frees the certificate context and then - * trying to access it leads to a access violation. - */ - if (secure_channel_ctx->should_free_pcerts == true) { - CertFreeCertificateContext(secure_channel_ctx->pcerts); - } - } - - if (secure_channel_ctx->cert_store) { - aws_close_cert_store(secure_channel_ctx->cert_store); - } - - if (secure_channel_ctx->alpn_list) { - aws_string_destroy(secure_channel_ctx->alpn_list); - } - - aws_mem_release(secure_channel_ctx->ctx.alloc, secure_channel_ctx); -} - -struct aws_tls_ctx *s_ctx_new( - struct aws_allocator *alloc, - const struct aws_tls_ctx_options *options, - bool is_client_mode) { - - if (!aws_tls_is_cipher_pref_supported(options->cipher_pref)) { - aws_raise_error(AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED); - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: TLS Cipher Preference is not supported: %d.", options->cipher_pref); - return NULL; - } - - struct secure_channel_ctx *secure_channel_ctx = aws_mem_calloc(alloc, 1, sizeof(struct secure_channel_ctx)); - if (!secure_channel_ctx) { - return NULL; - } - - secure_channel_ctx->ctx.alloc = alloc; - secure_channel_ctx->ctx.impl = secure_channel_ctx; - aws_ref_count_init( - &secure_channel_ctx->ctx.ref_count, - secure_channel_ctx, - (aws_simple_completion_callback *)s_secure_channel_ctx_destroy); - - if (options->alpn_list) { - secure_channel_ctx->alpn_list = aws_string_new_from_string(alloc, options->alpn_list); - if (!secure_channel_ctx->alpn_list) { - goto clean_up; - } - } - - secure_channel_ctx->verify_peer = options->verify_peer; - secure_channel_ctx->credentials.dwVersion = SCHANNEL_CRED_VERSION; - secure_channel_ctx->should_free_pcerts = true; - - secure_channel_ctx->credentials.grbitEnabledProtocols = 0; - - if (is_client_mode) { - switch (options->minimum_tls_version) { - case AWS_IO_SSLv3: - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_SSL3_CLIENT; - case AWS_IO_TLSv1: - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_0_CLIENT; - case AWS_IO_TLSv1_1: - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_1_CLIENT; - case AWS_IO_TLSv1_2: -#if defined(SP_PROT_TLS1_2_CLIENT) - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_2_CLIENT; -#endif - case AWS_IO_TLSv1_3: -#if defined(SP_PROT_TLS1_3_CLIENT) - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_3_CLIENT; -#endif - break; - case AWS_IO_TLS_VER_SYS_DEFAULTS: - secure_channel_ctx->credentials.grbitEnabledProtocols = 0; - break; - } - } else { - switch (options->minimum_tls_version) { - case AWS_IO_SSLv3: - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_SSL3_SERVER; - case AWS_IO_TLSv1: - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_0_SERVER; - case AWS_IO_TLSv1_1: - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_1_SERVER; - case AWS_IO_TLSv1_2: -#if defined(SP_PROT_TLS1_2_SERVER) - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_2_SERVER; -#endif - case AWS_IO_TLSv1_3: -#if defined(SP_PROT_TLS1_3_SERVER) - secure_channel_ctx->credentials.grbitEnabledProtocols |= SP_PROT_TLS1_3_SERVER; -#endif - break; - case AWS_IO_TLS_VER_SYS_DEFAULTS: - secure_channel_ctx->credentials.grbitEnabledProtocols = 0; - break; - } - } - - if (options->verify_peer && aws_tls_options_buf_is_set(&options->ca_file)) { - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: loading custom CA file."); - secure_channel_ctx->credentials.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION; - - struct aws_byte_cursor ca_blob_cur = aws_byte_cursor_from_buf(&options->ca_file); - int error = aws_import_trusted_certificates(alloc, &ca_blob_cur, &secure_channel_ctx->custom_trust_store); - - if (error) { - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import custom CA with error %d", aws_last_error()); - goto clean_up; - } - } else if (is_client_mode) { - secure_channel_ctx->credentials.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION; - } - - if (is_client_mode && !options->verify_peer) { - AWS_LOGF_WARN( - AWS_LS_IO_TLS, - "static: x.509 validation has been disabled. " - "If this is not running in a test environment, this is likely a security vulnerability."); - - secure_channel_ctx->credentials.dwFlags &= ~(SCH_CRED_AUTO_CRED_VALIDATION); - secure_channel_ctx->credentials.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | - SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_NO_SERVERNAME_CHECK | - SCH_CRED_MANUAL_CRED_VALIDATION; - } else if (is_client_mode) { - secure_channel_ctx->credentials.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN | SCH_CRED_IGNORE_REVOCATION_OFFLINE; - } - - /* if someone wants to use broken algorithms like rc4/md5/des they'll need to ask for a special control */ - secure_channel_ctx->credentials.dwFlags |= SCH_USE_STRONG_CRYPTO; - - /* if using a system store. */ - if (options->system_certificate_path) { - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: assuming certificate is in a system store, loading now."); - - if (aws_load_cert_from_system_cert_store( - options->system_certificate_path, &secure_channel_ctx->cert_store, &secure_channel_ctx->pcerts)) { - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to load %s", options->system_certificate_path); - goto clean_up; - } - - secure_channel_ctx->credentials.paCred = &secure_channel_ctx->pcerts; - secure_channel_ctx->credentials.cCreds = 1; - /* if using traditional PEM armored PKCS#7 and ASN Encoding public/private key pairs */ - } else if (aws_tls_options_buf_is_set(&options->certificate) && aws_tls_options_buf_is_set(&options->private_key)) { - - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: certificate and key have been set, setting them up now."); - - if (!aws_text_is_utf8(options->certificate.buffer, options->certificate.len)) { - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import certificate, must be ASCII/UTF-8 encoded"); - aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE); - goto clean_up; - } - - if (!aws_text_is_utf8(options->private_key.buffer, options->private_key.len)) { - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import private key, must be ASCII/UTF-8 encoded"); - aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE); - goto clean_up; - } - - struct aws_byte_cursor cert_chain_cur = aws_byte_cursor_from_buf(&options->certificate); - struct aws_byte_cursor pk_cur = aws_byte_cursor_from_buf(&options->private_key); - int err = aws_import_key_pair_to_cert_context( - alloc, - &cert_chain_cur, - &pk_cur, - is_client_mode, - &secure_channel_ctx->cert_store, - &secure_channel_ctx->pcerts, - &secure_channel_ctx->crypto_provider, - &secure_channel_ctx->private_key); - - if (err) { - AWS_LOGF_ERROR( - AWS_LS_IO_TLS, "static: failed to import certificate and private key with error %d.", aws_last_error()); - goto clean_up; - } - - secure_channel_ctx->credentials.paCred = &secure_channel_ctx->pcerts; - secure_channel_ctx->credentials.cCreds = 1; - secure_channel_ctx->should_free_pcerts = false; - } - - return &secure_channel_ctx->ctx; - -clean_up: - s_secure_channel_ctx_destroy(secure_channel_ctx); - return NULL; -} - -struct aws_tls_ctx *aws_tls_server_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) { - return s_ctx_new(alloc, options, false); -} - -struct aws_tls_ctx *aws_tls_client_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) { - return s_ctx_new(alloc, options, true); -} ->>>>>>> 5064829 (Add SOCKS5 proxy support)