/* SPDX-License-Identifier: MPL-2.0 */ // TODO remove this workaround for handling libsodium // To define SIZE_MAX with older compilers #define __STDC_LIMIT_MACROS #if defined ZMQ_CUSTOM_PLATFORM_HPP #include "platform.hpp" #else #include "../src/platform.hpp" #endif #ifndef ZMQ_USE_LIBSODIUM #define ZMQ_USE_LIBSODIUM #endif #include "testutil.hpp" #include "testutil_security.hpp" #include #include "../src/curve_client_tools.hpp" #include "../src/random.hpp" char error_message_buffer[256]; void *handler; void *zap_thread; void *server; void *server_mon; char my_endpoint[MAX_SOCKET_STRING]; void setUp () { setup_test_context (); setup_context_and_server_side (&handler, &zap_thread, &server, &server_mon, my_endpoint); } void tearDown () { shutdown_context_and_server_side (zap_thread, server, server_mon, handler); teardown_test_context (); } const int timeout = 250; const char large_routing_id[] = "0123456789012345678901234567890123456789" "0123456789012345678901234567890123456789" "0123456789012345678901234567890123456789" "0123456789012345678901234567890123456789" "0123456789012345678901234567890123456789" "0123456789012345678901234567890123456789" "012345678901234"; static void zap_handler_large_routing_id (void * /*unused_*/) { zap_handler_generic (zap_ok, large_routing_id); } void expect_new_client_curve_bounce_fail (const char *server_public_, const char *client_public_, const char *client_secret_, char *my_endpoint_, void *server_, void **client_mon_ = NULL, int expected_client_event_ = 0, int expected_client_value_ = 0) { curve_client_data_t curve_client_data = {server_public_, client_public_, client_secret_}; expect_new_client_bounce_fail ( my_endpoint_, server_, socket_config_curve_client, &curve_client_data, client_mon_, expected_client_event_, expected_client_value_); } void test_null_key (void *server_, void *server_mon_, char *my_endpoint_, char *server_public_, char *client_public_, char *client_secret_) { expect_new_client_curve_bounce_fail (server_public_, client_public_, client_secret_, my_endpoint_, server_); int handshake_failed_encryption_event_count = expect_monitor_event_multiple (server_mon_, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC); // handshake_failed_encryption_event_count should be at least two because // expect_bounce_fail involves two exchanges // however, with valgrind we see only one event (maybe the next one takes // very long, or does not happen at all because something else takes very // long) fprintf (stderr, "count of " "ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL/" "ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC events: %i\n", handshake_failed_encryption_event_count); } void test_curve_security_with_valid_credentials () { curve_client_data_t curve_client_data = { valid_server_public, valid_client_public, valid_client_secret}; void *client_mon; void *client = create_and_connect_client ( my_endpoint, socket_config_curve_client, &curve_client_data, &client_mon); bounce (server, client); test_context_socket_close (client); int event = get_monitor_event_with_timeout (server_mon, NULL, NULL, -1); assert (event == ZMQ_EVENT_HANDSHAKE_SUCCEEDED); assert_no_more_monitor_events_with_timeout (server_mon, timeout); event = get_monitor_event_with_timeout (client_mon, NULL, NULL, -1); assert (event == ZMQ_EVENT_HANDSHAKE_SUCCEEDED); assert_no_more_monitor_events_with_timeout (client_mon, timeout); test_context_socket_close (client_mon); } void test_curve_security_with_bogus_client_credentials () { // This must be caught by the ZAP handler char bogus_public[41]; char bogus_secret[41]; zmq_curve_keypair (bogus_public, bogus_secret); expect_new_client_curve_bounce_fail ( valid_server_public, bogus_public, bogus_secret, my_endpoint, server, NULL, ZMQ_EVENT_HANDSHAKE_FAILED_AUTH, 400); int server_event_count = 0; server_event_count = expect_monitor_event_multiple ( server_mon, ZMQ_EVENT_HANDSHAKE_FAILED_AUTH, 400); TEST_ASSERT_LESS_OR_EQUAL_INT (1, server_event_count); // there may be more than one ZAP request due to repeated attempts by the client TEST_ASSERT (0 == server_event_count || 1 <= zmq_atomic_counter_value (zap_requests_handled)); } void expect_zmtp_mechanism_mismatch (void *client_, char *my_endpoint_, void *server_, void *server_mon_) { // This must be caught by the curve_server class, not passed to ZAP TEST_ASSERT_SUCCESS_ERRNO (zmq_connect (client_, my_endpoint_)); expect_bounce_fail (server_, client_); test_context_socket_close_zero_linger (client_); expect_monitor_event_multiple (server_mon_, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH); TEST_ASSERT_EQUAL_INT (0, zmq_atomic_counter_value (zap_requests_handled)); } void test_curve_security_with_null_client_credentials () { void *client = test_context_socket (ZMQ_DEALER); expect_zmtp_mechanism_mismatch (client, my_endpoint, server, server_mon); } void test_curve_security_with_plain_client_credentials () { void *client = test_context_socket (ZMQ_DEALER); TEST_ASSERT_SUCCESS_ERRNO ( zmq_setsockopt (client, ZMQ_PLAIN_USERNAME, "admin", 5)); TEST_ASSERT_SUCCESS_ERRNO ( zmq_setsockopt (client, ZMQ_PLAIN_PASSWORD, "password", 8)); expect_zmtp_mechanism_mismatch (client, my_endpoint, server, server_mon); } void test_curve_security_unauthenticated_message () { // Unauthenticated messages from a vanilla socket shouldn't be received fd_t s = connect_socket (my_endpoint); // send anonymous ZMTP/1.0 greeting send (s, "\x01\x00", 2, 0); // send sneaky message that shouldn't be received send (s, "\x08\x00sneaky\0", 9, 0); zmq_setsockopt (server, ZMQ_RCVTIMEO, &timeout, sizeof (timeout)); char *buf = s_recv (server); TEST_ASSERT_NULL_MESSAGE (buf, "Received unauthenticated message"); close (s); } void send_all (fd_t fd_, const char *data_, socket_size_t size_) { while (size_ > 0) { int res = send (fd_, data_, size_, 0); TEST_ASSERT_GREATER_THAN_INT (0, res); size_ -= res; data_ += res; } } template void send (fd_t fd_, const char (&data_)[N]) { send_all (fd_, data_, N - 1); } template void send (fd_t fd_, const uint8_t (&data_)[N]) { send_all (fd_, reinterpret_cast (&data_), N); } void test_curve_security_invalid_hello_wrong_length () { fd_t s = connect_socket (my_endpoint); send (s, zmtp_greeting_curve); // send CURVE HELLO of wrong size send (s, "\x04\x06\x05HELLO"); expect_monitor_event_multiple ( server_mon, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO); close (s); } const size_t hello_length = 200; const size_t welcome_length = 168; zmq::curve_client_tools_t make_curve_client_tools () { uint8_t valid_client_secret_decoded[32]; uint8_t valid_client_public_decoded[32]; zmq_z85_decode (valid_client_public_decoded, valid_client_public); zmq_z85_decode (valid_client_secret_decoded, valid_client_secret); uint8_t valid_server_public_decoded[32]; zmq_z85_decode (valid_server_public_decoded, valid_server_public); return zmq::curve_client_tools_t (valid_client_public_decoded, valid_client_secret_decoded, valid_server_public_decoded); } // same as htonll, which is only available on few platforms (recent Windows, but not on Linux, e.g.( static uint64_t host_to_network (uint64_t value_) { // The answer is 42 static const int num = 42; // Check the endianness if (*reinterpret_cast (&num) == num) { const uint32_t high_part = htonl (static_cast (value_ >> 32)); const uint32_t low_part = htonl (static_cast (value_ & 0xFFFFFFFFLL)); return (static_cast (low_part) << 32) | high_part; } return value_; } template void send_command (fd_t s_, char (&command_)[N]) { if (N < 256) { send (s_, "\x04"); char len = (char) N; send_all (s_, &len, 1); } else { send (s_, "\x06"); uint64_t len = host_to_network (N); send_all (s_, reinterpret_cast (&len), 8); } send_all (s_, command_, N); } void test_curve_security_invalid_hello_command_name () { fd_t s = connect_socket (my_endpoint); send (s, zmtp_greeting_curve); zmq::curve_client_tools_t tools = make_curve_client_tools (); // send CURVE HELLO with a misspelled command name (but otherwise correct) char hello[hello_length]; TEST_ASSERT_SUCCESS_ERRNO (tools.produce_hello (hello, 0)); hello[5] = 'X'; send_command (s, hello); expect_monitor_event_multiple (server_mon, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND); close (s); } void test_curve_security_invalid_hello_version () { fd_t s = connect_socket (my_endpoint); send (s, zmtp_greeting_curve); zmq::curve_client_tools_t tools = make_curve_client_tools (); // send CURVE HELLO with a wrong version number (but otherwise correct) char hello[hello_length]; TEST_ASSERT_SUCCESS_ERRNO (tools.produce_hello (hello, 0)); hello[6] = 2; send_command (s, hello); expect_monitor_event_multiple ( server_mon, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO); close (s); } void flush_read (fd_t fd_) { int res; char buf[256]; while ((res = recv (fd_, buf, 256, 0)) == 256) { } TEST_ASSERT_NOT_EQUAL (-1, res); } void recv_all (fd_t fd_, uint8_t *data_, socket_size_t len_) { socket_size_t received = 0; while (received < len_) { int res = recv (fd_, reinterpret_cast (data_), len_, 0); TEST_ASSERT_GREATER_THAN_INT (0, res); data_ += res; received += res; } } void recv_greeting (fd_t fd_) { uint8_t greeting[64]; recv_all (fd_, greeting, 64); // TODO assert anything about the greeting received from the server? } fd_t connect_exchange_greeting_and_send_hello ( char *my_endpoint_, zmq::curve_client_tools_t &tools_) { fd_t s = connect_socket (my_endpoint_); send (s, zmtp_greeting_curve); recv_greeting (s); // send valid CURVE HELLO char hello[hello_length]; TEST_ASSERT_SUCCESS_ERRNO (tools_.produce_hello (hello, 0)); send_command (s, hello); return s; } void test_curve_security_invalid_initiate_wrong_length () { zmq::curve_client_tools_t tools = make_curve_client_tools (); fd_t s = connect_exchange_greeting_and_send_hello (my_endpoint, tools); // receive but ignore WELCOME flush_read (s); int res = get_monitor_event_with_timeout (server_mon, NULL, NULL, timeout); TEST_ASSERT_EQUAL_INT (-1, res); send (s, "\x04\x09\x08INITIATE"); expect_monitor_event_multiple ( server_mon, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE); close (s); } fd_t connect_exchange_greeting_and_hello_welcome ( char *my_endpoint_, void *server_mon_, int timeout_, zmq::curve_client_tools_t &tools_) { fd_t s = connect_exchange_greeting_and_send_hello (my_endpoint_, tools_); // receive but ignore WELCOME uint8_t welcome[welcome_length + 2]; recv_all (s, welcome, welcome_length + 2); uint8_t cn_precom[crypto_box_BEFORENMBYTES]; TEST_ASSERT_SUCCESS_ERRNO ( tools_.process_welcome (welcome + 2, welcome_length, cn_precom)); const int res = get_monitor_event_with_timeout (server_mon_, NULL, NULL, timeout_); TEST_ASSERT_EQUAL_INT (-1, res); return s; } void test_curve_security_invalid_initiate_command_name () { zmq::curve_client_tools_t tools = make_curve_client_tools (); fd_t s = connect_exchange_greeting_and_hello_welcome ( my_endpoint, server_mon, timeout, tools); char initiate[257]; tools.produce_initiate (initiate, 257, 1, NULL, 0); // modify command name initiate[5] = 'X'; send_command (s, initiate); expect_monitor_event_multiple (server_mon, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND); close (s); } void test_curve_security_invalid_initiate_command_encrypted_cookie () { zmq::curve_client_tools_t tools = make_curve_client_tools (); fd_t s = connect_exchange_greeting_and_hello_welcome ( my_endpoint, server_mon, timeout, tools); char initiate[257]; tools.produce_initiate (initiate, 257, 1, NULL, 0); // make garbage from encrypted cookie initiate[30] = !initiate[30]; send_command (s, initiate); expect_monitor_event_multiple (server_mon, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC); close (s); } void test_curve_security_invalid_initiate_command_encrypted_content () { zmq::curve_client_tools_t tools = make_curve_client_tools (); fd_t s = connect_exchange_greeting_and_hello_welcome ( my_endpoint, server_mon, timeout, tools); char initiate[257]; tools.produce_initiate (initiate, 257, 1, NULL, 0); // make garbage from encrypted content initiate[150] = !initiate[150]; send_command (s, initiate); expect_monitor_event_multiple (server_mon, ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL, ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC); close (s); } void test_curve_security_invalid_keysize (void *ctx_) { // Check return codes for invalid buffer sizes void *client = zmq_socket (ctx_, ZMQ_DEALER); TEST_ASSERT_NOT_NULL (client); errno = 0; int rc = zmq_setsockopt (client, ZMQ_CURVE_SERVERKEY, valid_server_public, 123); assert (rc == -1 && errno == EINVAL); errno = 0; rc = zmq_setsockopt (client, ZMQ_CURVE_PUBLICKEY, valid_client_public, 123); assert (rc == -1 && errno == EINVAL); errno = 0; rc = zmq_setsockopt (client, ZMQ_CURVE_SECRETKEY, valid_client_secret, 123); assert (rc == -1 && errno == EINVAL); TEST_ASSERT_SUCCESS_ERRNO (zmq_close (client)); } // TODO why isn't this const? char null_key[] = "0000000000000000000000000000000000000000"; void test_null_server_key () { // Check CURVE security with a null server key // This will be caught by the curve_server class, not passed to ZAP test_null_key (server, server_mon, my_endpoint, null_key, valid_client_public, valid_client_secret); } void test_null_client_public_key () { // Check CURVE security with a null client public key // This will be caught by the curve_server class, not passed to ZAP test_null_key (server, server_mon, my_endpoint, valid_server_public, null_key, valid_client_secret); } void test_null_client_secret_key () { // Check CURVE security with a null client public key // This will be caught by the curve_server class, not passed to ZAP test_null_key (server, server_mon, my_endpoint, valid_server_public, valid_client_public, null_key); } int main (void) { if (!zmq_has ("curve")) { printf ("CURVE encryption not installed, skipping test\n"); return 0; } zmq::random_open (); setup_testutil_security_curve (); setup_test_environment (180); UNITY_BEGIN (); RUN_TEST (test_curve_security_with_valid_credentials); RUN_TEST (test_null_server_key); RUN_TEST (test_null_client_public_key); RUN_TEST (test_null_client_secret_key); RUN_TEST (test_curve_security_with_bogus_client_credentials); RUN_TEST (test_curve_security_with_null_client_credentials); RUN_TEST (test_curve_security_with_plain_client_credentials); RUN_TEST (test_curve_security_unauthenticated_message); // tests with misbehaving CURVE client RUN_TEST (test_curve_security_invalid_hello_wrong_length); RUN_TEST (test_curve_security_invalid_hello_command_name); RUN_TEST (test_curve_security_invalid_hello_version); RUN_TEST (test_curve_security_invalid_initiate_wrong_length); RUN_TEST (test_curve_security_invalid_initiate_command_name); RUN_TEST (test_curve_security_invalid_initiate_command_encrypted_cookie); RUN_TEST (test_curve_security_invalid_initiate_command_encrypted_content); // TODO this requires a deviating test setup, must be moved to a separate executable/fixture // test with a large routing id (resulting in large metadata) fprintf (stderr, "test_curve_security_with_valid_credentials (large routing id)\n"); setup_test_context (); setup_context_and_server_side (&handler, &zap_thread, &server, &server_mon, my_endpoint, &zap_handler_large_routing_id, &socket_config_curve_server, &valid_server_secret, large_routing_id); test_curve_security_with_valid_credentials (); shutdown_context_and_server_side (zap_thread, server, server_mon, handler); teardown_test_context (); void *ctx = zmq_ctx_new (); test_curve_security_invalid_keysize (ctx); TEST_ASSERT_SUCCESS_ERRNO (zmq_ctx_term (ctx)); zmq::random_close (); return UNITY_END (); }