21#include <sys/socket.h>
22#include <netinet/in.h>
37 size_t payload_len = 0;
38 int result =
receive_packet(client_socket, &packet_type, (
void **)&payload, &payload_len);
51 log_debug(
"CLIENT_KEY_EXCHANGE: Received packet with payload_len=%zu, kex_size=%u, auth_size=%u, sig_size=%u",
57 size_t expected_auth_size =
64 if (!server_ephemeral_key) {
72 if (!server_identity_key || !server_signature) {
74 if (server_identity_key)
91 return validation_result;
99 log_info(
"Received authenticated KEY_EXCHANGE_INIT (%zu bytes)", expected_auth_size);
111 safe_snprintf(hex_id + i * 2, 3,
"%02x", server_identity_key[i]);
114 log_debug(
"Received identity key: %s", hex_id);
119 safe_snprintf(hex_eph + i * 2, 3,
"%02x", server_ephemeral_key[i]);
122 log_debug(
"Received ephemeral key: %s", hex_eph);
126 safe_snprintf(hex_sig + i * 2, 3,
"%02x", server_signature[i]);
129 log_debug(
"Received signature: %s", hex_sig);
132 log_debug(
"Verifying server's signature over ephemeral key (already logged above)");
137 log_info(
"Skipping server signature verification (no --server-key specified)");
138 log_warn(
"Connection is encrypted but server identity is NOT verified (vulnerable to MITM)");
141 const char *gpg_key_id = NULL;
144 size_t key_id_len = strlen(key_id_start);
146 if (key_id_len == 8 || key_id_len == 16 || key_id_len == 40) {
147 gpg_key_id = key_id_start;
148 log_debug(
"Using GPG key ID from --server-key for verification: %s", gpg_key_id);
153 server_signature, gpg_key_id) != 0) {
161 "This indicates: Server's identity key does not "
162 "match its ephemeral key, Potential man-in-the-middle attack, "
163 "Corrupted or malicious server");
165 log_info(
"Server signature verified successfully");
172 size_t num_expected_keys = 0;
174 num_expected_keys == 0) {
182 "Failed to parse expected server key: %s. Check that "
183 "--server-key value is valid (ssh-ed25519 "
184 "format, github:username, or hex)",
190 bool key_matched =
false;
191 for (
size_t i = 0; i < num_expected_keys; i++) {
194 log_info(
"Server identity key matched expected key %zu/%zu", i + 1, num_expected_keys);
207 "Server identity key mismatch - potential MITM attack! "
208 "Expected key(s) from: %s (checked %zu keys), Server presented a different key "
209 "than specified with --server-key, DO NOT CONNECT to this "
210 "server - likely man-in-the-middle attack!",
213 log_info(
"Server identity key verified against --server-key (%zu key(s) checked)", num_expected_keys);
219 struct sockaddr_storage server_addr;
220 socklen_t addr_len =
sizeof(server_addr);
221 if (getpeername(client_socket, (
struct sockaddr *)&server_addr, &addr_len) == 0) {
222 char ip_str[INET6_ADDRSTRLEN];
223 if (
format_ip_address(server_addr.ss_family, (
struct sockaddr *)&server_addr, ip_str,
sizeof(ip_str)) ==
226 if (server_addr.ss_family == AF_INET) {
227 struct sockaddr_in *addr_in = (
struct sockaddr_in *)&server_addr;
229 }
else if (server_addr.ss_family == AF_INET6) {
230 struct sockaddr_in6 *addr_in6 = (
struct sockaddr_in6 *)&server_addr;
235 log_warn(
"Failed to get server address from socket");
243 bool skip_known_hosts =
false;
244 const char *env_skip =
platform_getenv(
"ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK");
245 if (env_skip && strcmp(env_skip,
STR_ONE) == 0) {
247 "Skipping known_hosts checking for authenticated connection (ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK=1)");
248 skip_known_hosts =
true;
253 log_warn(
"Skipping known_hosts checking (CLAUDECODE set in debug build)");
254 skip_known_hosts =
true;
262 log_error(
"SECURITY: Server key does NOT match known_hosts entry!\n"
263 "This indicates a possible man-in-the-middle attack!");
275 "SECURITY: Connection aborted - server key mismatch (possible MITM attack)");
278 log_warn(
"SECURITY WARNING: User accepted MITM risk - continuing with connection");
301 "CRITICAL SECURITY ERROR: Failed to create known_hosts "
302 "file! This is a security vulnerability - the "
303 "program cannot track known hosts. Please check file "
304 "permissions and ensure the program can write to: %s",
307 log_info(
"Server host added to known_hosts successfully");
308 }
else if (known_host_result == 1) {
310 log_info(
"Server host key verified from known_hosts - connection secure");
319 return SET_ERRNO(known_host_result,
"SECURITY: known_hosts verification failed with error code %d",
325 log_info(
"Received simple KEY_EXCHANGE_INIT (%zu bytes) - server has no "
337 log_debug(
"Received ephemeral key (simple format)");
352 bool skip_known_hosts =
false;
354 const char *env_skip_known_hosts_checking =
platform_getenv(
"ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK");
355 if (env_skip_known_hosts_checking && strcmp(env_skip_known_hosts_checking,
STR_ONE) == 0) {
356 log_warn(
"Skipping known_hosts checking. This is a security vulnerability.");
357 skip_known_hosts =
true;
362 log_warn(
"Skipping known_hosts checking (CLAUDECODE set in debug build).");
363 skip_known_hosts =
true;
370 if (skip_known_hosts || known_host_result == 1) {
372 log_info(
"SECURITY: Server IP %s:%u is known (no-identity entry found) - connection verified", ctx->
server_ip,
376 log_warn(
"SECURITY: Unknown server IP %s:%u with no identity key\n"
377 "This connection is vulnerable to man-in-the-middle attacks\n"
378 "Anyone can intercept your connection and read your data",
402 "CRITICAL SECURITY ERROR: Failed to create known_hosts "
403 "file! This is a security vulnerability - the "
404 "program cannot track known hosts. Please check file "
405 "permissions and ensure the program can write to: %s",
408 log_info(
"Server host added to known_hosts successfully");
411 log_warn(
"SECURITY: Server previously had identity key but now has none - potential security issue");
437 "Invalid KEY_EXCHANGE_INIT size: %zu bytes (expected %zu or "
438 "%zu). This indicates: Protocol violation "
439 "or incompatible server version, Potential man-in-the-middle "
440 "attack, Network corruption",
468 if (server_requires_auth) {
480 if (gpg_key_id_len > 40) {
483 response_size += 1 + gpg_key_id_len;
496 if (client_has_identity_key) {
499 offset += ed25519_pubkey_size;
503 key_response + offset) != 0) {
510 offset += ed25519_sig_size;
513 memset(key_response + offset, 0, ed25519_pubkey_size);
514 offset += ed25519_pubkey_size;
515 memset(key_response + offset, 0, ed25519_sig_size);
516 offset += ed25519_sig_size;
520 key_response[offset] = gpg_key_id_len;
524 if (gpg_key_id_len > 0) {
526 offset += gpg_key_id_len;
540 sodium_memzero(key_response, response_size);
566 const uint8_t *nonce,
const char *auth_context) {
594 log_debug(
"Sending AUTH_RESPONSE packet with HMAC + client nonce (%zu bytes) - %s", auth_packet_size, auth_context);
605 const uint8_t *nonce,
const char *auth_context) {
617 sodium_memzero(signature,
sizeof(signature));
630 if (gpg_key_id_len > 40) {
633 auth_packet_size += 1 + gpg_key_id_len;
635 auth_packet_size += 1;
651 auth_packet[offset] = gpg_key_id_len;
655 if (gpg_key_id_len > 0) {
657 offset += gpg_key_id_len;
661 sodium_memzero(signature,
sizeof(signature));
663 log_debug(
"Sending AUTH_RESPONSE packet with Ed25519 signature + client "
664 "nonce + GPG key ID (%zu bytes) - %s",
665 auth_packet_size, auth_context);
682 size_t payload_len = 0;
683 int result =
receive_packet(client_socket, &packet_type, (
void **)&payload, &payload_len);
696 log_info(
"Crypto handshake completed successfully (no authentication required)");
714 "Expected AUTH_CHALLENGE, HANDSHAKE_COMPLETE, or AUTH_FAILED, "
715 "got packet type %d",
726 return validation_result;
730 uint8_t auth_flags = payload[0];
737 const uint8_t *nonce = nonce_buffer;
739 log_debug(
"Server auth requirements: password=%s, client_key=%s",
750 if (password_required && !has_password) {
751 if (client_key_required && !has_client_key) {
755 return SET_ERRNO(
ERROR_CRYPTO,
"Server requires both password and client key authentication. Please "
756 "provide --password and --key to authenticate");
760 if (
prompt_password(
"Server password required - please enter password:", prompted_password,
761 sizeof(prompted_password)) != 0) {
769 log_debug(
"Deriving key from prompted password");
771 sodium_memzero(prompted_password,
sizeof(prompted_password));
801 if (password_required) {
807 "Please provide --password for this server");
810 result = send_password_auth_response(ctx, client_socket, nonce,
"required password");
815 }
else if (client_key_required) {
817 if (!has_client_key) {
819 "Please provide --key with your authorized Ed25519 key");
822 result = send_key_auth_response(ctx, client_socket, nonce,
"required client key");
827 }
else if (has_password) {
830 result = send_password_auth_response(ctx, client_socket, nonce,
"optional password");
835 }
else if (has_client_key) {
838 result = send_key_auth_response(ctx, client_socket, nonce,
"optional identity");
847 log_debug(
"No authentication credentials provided - continuing without "
865 size_t payload_len = 0;
866 int result =
receive_packet(client_socket, &packet_type, (
void **)&payload, &payload_len);
887 "with your SSH key)");
917 "Server authentication failed - incorrect HMAC");
948 "Server authentication failed - incorrect HMAC");
958 log_info(
"Server authentication successful - mutual authentication complete");
⚠️‼️ Error and/or exit() when things go bad.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
asciichat_error_t crypto_handshake_validate_packet_size(const crypto_handshake_context_t *ctx, uint16_t packet_type, size_t packet_size)
Validate crypto packet size based on session parameters.
#define AUTH_REQUIRE_PASSWORD
Server requires password authentication.
#define AUTH_REQUIRE_CLIENT_KEY
Server requires client key authentication (whitelist)
🔄 Network byte order conversion helpers
#define NET_TO_HOST_U16(val)
GPG operations - main header.
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
Free a buffer back to the pool (lock-free)
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
bool prompt_unknown_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
Interactive prompt for unknown host - returns true if user wants to add, false to abort.
const char * get_known_hosts_path(void)
Get the path to the known_hosts file.
crypto_result_t crypto_set_peer_public_key(crypto_context_t *ctx, const uint8_t *peer_public_key)
Set peer's public key and compute shared secret (step 2 of handshake)
#define ED25519_SIGNATURE_SIZE
Ed25519 signature size in bytes.
crypto_result_t crypto_compute_auth_response(const crypto_context_t *ctx, const uint8_t nonce[32], uint8_t hmac_out[32])
Compute authentication response HMAC bound to DH shared_secret.
#define HMAC_SHA256_SIZE
HMAC-SHA256 output size in bytes.
crypto_result_t crypto_generate_nonce(uint8_t nonce[32])
Generate random nonce for authentication.
const char * crypto_result_to_string(crypto_result_t result)
Convert crypto result to human-readable string.
crypto_result_t
Cryptographic operation result codes.
bool prompt_unknown_host_no_identity(const char *server_ip, uint16_t port)
Interactive prompt for unknown host without identity key - returns true if user wants to continue,...
#define ED25519_PUBLIC_KEY_SIZE
Ed25519 public key size in bytes.
asciichat_error_t check_known_host_no_identity(const char *server_ip, uint16_t port)
Check known_hosts for servers without identity key (no-identity entries)
#define HEX_STRING_SIZE_32
Hex string size for 32-byte values (64 hex chars + null terminator)
asciichat_error_t add_known_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
Add server to known_hosts.
crypto_result_t crypto_derive_password_key(crypto_context_t *ctx, const char *password)
Derive key from password using Argon2id.
#define PASSWORD_BUFFER_SIZE
Password input buffer size (256 bytes)
#define ZERO_KEY_SIZE
Zero key array size (32 bytes, used for no-identity entries)
bool display_mitm_warning(const char *server_ip, uint16_t port, const uint8_t expected_key[32], const uint8_t received_key[32])
Display MITM warning with key comparison and prompt user for confirmation.
asciichat_error_t check_known_host(const char *server_ip, uint16_t port, const uint8_t server_key[32])
Check if server key is in known_hosts.
bool crypto_verify_auth_response(const crypto_context_t *ctx, const uint8_t nonce[32], const uint8_t expected_hmac[32])
Verify authentication response HMAC bound to DH shared_secret.
#define HEX_STRING_SIZE_64
Hex string size for 64-byte values (128 hex chars + null terminator)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
@ ERROR_CRYPTO_VERIFICATION
@ CRYPTO_HANDSHAKE_AUTHENTICATING
@ CRYPTO_HANDSHAKE_KEY_EXCHANGE
asciichat_error_t ed25519_verify_signature(const uint8_t public_key[32], const uint8_t *message, size_t message_len, const uint8_t signature[64], const char *gpg_key_id)
Verify an Ed25519 signature.
asciichat_error_t parse_public_keys(const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
Parse all SSH/GPG public keys from any format (returns all keys)
asciichat_error_t ed25519_sign_message(const private_key_t *key, const uint8_t *message, size_t message_len, uint8_t signature[64])
Sign a message with Ed25519 (uses SSH agent if available, otherwise in-memory key)
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
uint8_t reason_flags
Bitmask of auth_failure_reason_t values indicating failure causes.
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
int send_packet(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a basic packet without encryption.
packet_type_t
Network protocol packet type enumeration.
@ AUTH_FAIL_PASSWORD_INCORRECT
Password verification failed (incorrect password)
@ AUTH_FAIL_PASSWORD_REQUIRED
Server requires password but client didn't provide one.
@ AUTH_FAIL_CLIENT_KEY_REQUIRED
Server requires client key but client didn't provide one.
@ AUTH_FAIL_CLIENT_KEY_REJECTED
Client key not in whitelist (access denied)
@ AUTH_FAIL_SIGNATURE_INVALID
Client signature verification failed (invalid signature)
@ PACKET_TYPE_CRYPTO_AUTH_RESPONSE
Client -> Server: {HMAC[32]} (UNENCRYPTED)
@ PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE
Server -> Client: "encryption ready" (UNENCRYPTED)
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT
Server -> Client: {server_pubkey[32]} (UNENCRYPTED)
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP
Client -> Server: {client_pubkey[32]} (UNENCRYPTED)
@ PACKET_TYPE_CRYPTO_AUTH_FAILED
Server -> Client: "authentication failed" (UNENCRYPTED)
@ PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP
Server -> Client: {HMAC[32]} server proves knowledge (UNENCRYPTED)
@ PACKET_TYPE_CRYPTO_AUTH_CHALLENGE
Server -> Client: {nonce[32]} (UNENCRYPTED)
#define STR_ONE
String literal: "1" (one)
int prompt_password(const char *prompt, char *password, size_t max_len)
Prompt the user for a password with secure input.
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Format IP address from socket address structure.
🌍 IP Address Parsing and Formatting Utilities
Known hosts management for MITM attack prevention.
asciichat_error_t crypto_handshake_client_auth_response(crypto_handshake_context_t *ctx, socket_t client_socket)
Client: Process auth challenge and send response.
asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, socket_t client_socket)
Client: Process server's public key and send our public key.
asciichat_error_t crypto_handshake_client_complete(crypto_handshake_context_t *ctx, socket_t client_socket)
Client: Wait for handshake complete confirmation.
Packet protocol implementation with encryption and compression support.
Password prompting utilities with secure input and formatting.
Per-client state management and lifecycle orchestration.
Authentication failure packet structure.
uint16_t auth_public_key_size
bool key_exchange_complete
uint8_t auth_challenge_size
Cryptographic handshake context structure.
crypto_handshake_state_t state
bool server_uses_client_auth
uint8_t client_challenge_nonce[32]
char expected_server_key[256]
crypto_context_t crypto_ctx
char client_gpg_key_id[41]
private_key_t client_private_key
Common SIMD utilities and structures.