ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
client.h File Reference

Client-side handshake functions. More...

Go to the source code of this file.

Functions

Client Handshake Protocol
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_auth_response (crypto_handshake_context_t *ctx, socket_t client_socket)
 Client: Process auth challenge and send response.
 
asciichat_error_t crypto_handshake_client_complete (crypto_handshake_context_t *ctx, socket_t client_socket)
 Client: Wait for handshake complete confirmation.
 

Detailed Description

Client-side handshake functions.

Definition in file lib/crypto/handshake/client.h.

Function Documentation

◆ crypto_handshake_client_auth_response()

asciichat_error_t crypto_handshake_client_auth_response ( crypto_handshake_context_t ctx,
socket_t  client_socket 
)

Client: Process auth challenge and send response.

Parameters
ctxHandshake context (must be in CRYPTO_HANDSHAKE_KEY_EXCHANGE state)
client_socketSocket to send/receive on
Returns
ASCIICHAT_OK on success, error code on failure

Client processes server's AUTH_CHALLENGE packet and sends AUTH_RESPONSE. Generates HMAC bound to shared secret using password or client key.

Note
State transition: CRYPTO_HANDSHAKE_KEY_EXCHANGE -> CRYPTO_HANDSHAKE_AUTHENTICATING

Definition at line 674 of file lib/crypto/handshake/client.c.

674 {
675 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_KEY_EXCHANGE) {
676 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
677 }
678
679 // Receive AUTH_CHALLENGE or HANDSHAKE_COMPLETE packet
680 packet_type_t packet_type;
681 uint8_t *payload = NULL;
682 size_t payload_len = 0;
683 int result = receive_packet(client_socket, &packet_type, (void **)&payload, &payload_len);
684 if (result != ASCIICHAT_OK) {
685 return SET_ERRNO(ERROR_NETWORK, "Failed to receive packet from server");
686 }
687
688 // If server sent HANDSHAKE_COMPLETE, authentication was skipped (client has
689 // no key)
690 if (packet_type == PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE) {
691 if (payload) {
692 buffer_pool_free(NULL, payload, payload_len);
693 }
695 ctx->crypto_ctx.handshake_complete = true; // Mark crypto context as ready for rekeying
696 log_info("Crypto handshake completed successfully (no authentication required)");
697 return ASCIICHAT_OK;
698 }
699
700 // If server sent AUTH_FAILED, client is not authorized
701 if (packet_type == PACKET_TYPE_CRYPTO_AUTH_FAILED) {
702 if (payload) {
703 buffer_pool_free(NULL, payload, payload_len);
704 }
705 return SET_ERRNO(ERROR_CRYPTO, "Server rejected authentication - client key not authorized");
706 }
707
708 // Otherwise, verify packet type is AUTH_CHALLENGE
709 if (packet_type != PACKET_TYPE_CRYPTO_AUTH_CHALLENGE) {
710 if (payload) {
711 buffer_pool_free(NULL, payload, payload_len);
712 }
714 "Expected AUTH_CHALLENGE, HANDSHAKE_COMPLETE, or AUTH_FAILED, "
715 "got packet type %d",
716 packet_type);
717 }
718
719 // Validate packet size using session parameters
720 asciichat_error_t validation_result =
722 if (validation_result != ASCIICHAT_OK) {
723 if (payload) {
724 buffer_pool_free(NULL, payload, payload_len);
725 }
726 return validation_result;
727 }
728
729 // Parse auth requirement flags
730 uint8_t auth_flags = payload[0];
731
732 // Copy nonce to local buffer before freeing payload
733 // Use auth_challenge_size since that's what the server sent
734 // Note: auth_challenge_size is uint8_t (max 255), buffer is 256 bytes, so always sufficient
735 uint8_t nonce_buffer[256];
736 memcpy(nonce_buffer, payload + 1, ctx->crypto_ctx.auth_challenge_size);
737 const uint8_t *nonce = nonce_buffer;
738
739 log_debug("Server auth requirements: password=%s, client_key=%s",
740 (auth_flags & AUTH_REQUIRE_PASSWORD) ? "required" : "no",
741 (auth_flags & AUTH_REQUIRE_CLIENT_KEY) ? "required" : "no");
742
743 // Check if we can satisfy the server's authentication requirements
744 bool has_password = ctx->crypto_ctx.has_password;
745 bool has_client_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
746 bool password_required = (auth_flags & AUTH_REQUIRE_PASSWORD);
747 bool client_key_required = (auth_flags & AUTH_REQUIRE_CLIENT_KEY);
748
749 // Provide specific error messages based on what's missing
750 if (password_required && !has_password) {
751 if (client_key_required && !has_client_key) {
752 if (payload) {
753 buffer_pool_free(NULL, payload, payload_len);
754 }
755 return SET_ERRNO(ERROR_CRYPTO, "Server requires both password and client key authentication. Please "
756 "provide --password and --key to authenticate");
757 }
758 // Prompt for password interactively
759 char prompted_password[PASSWORD_BUFFER_SIZE];
760 if (prompt_password("Server password required - please enter password:", prompted_password,
761 sizeof(prompted_password)) != 0) {
762 if (payload) {
763 buffer_pool_free(NULL, payload, payload_len);
764 }
765 return SET_ERRNO(ERROR_CRYPTO, "Failed to read password");
766 }
767
768 // Derive password key from prompted password
769 log_debug("Deriving key from prompted password");
770 crypto_result_t crypto_result = crypto_derive_password_key(&ctx->crypto_ctx, prompted_password);
771 sodium_memzero(prompted_password, sizeof(prompted_password));
772
773 if (crypto_result != CRYPTO_OK) {
774 if (payload) {
775 buffer_pool_free(NULL, payload, payload_len);
776 }
777 return SET_ERRNO(ERROR_CRYPTO, "Failed to derive password key: %s", crypto_result_to_string(crypto_result));
778 }
779
780 // Mark that password auth is now available
781 ctx->crypto_ctx.has_password = true;
782 has_password = true; // Update flag for logic below
783 }
784
785 // Authentication response priority:
786 // NOTE: Identity verification happens during KEY_EXCHANGE phase, not
787 // AUTH_RESPONSE!
788 // 1. If server requires password → MUST send HMAC (hmac_size bytes), error if no password
789 // 2. Else if server requires identity (whitelist) → MUST send Ed25519 signature (signature_size bytes), error if no
790 // key
791 // 3. Else if client has password → send HMAC (optional password auth)
792 // 4. Else if client has SSH key → send Ed25519 signature (optional identity
793 // proof)
794 // 5. Else → no authentication available
795
796 // Clean up payload before any early returns
797 if (payload) {
798 buffer_pool_free(NULL, payload, payload_len);
799 }
800
801 if (password_required) {
802 // Server requires password - HIGHEST PRIORITY
803 // (Identity was already verified in KEY_EXCHANGE phase if whitelist is
804 // enabled)
805 if (!has_password) {
806 return SET_ERRNO(ERROR_CRYPTO, "Server requires password authentication\n"
807 "Please provide --password for this server");
808 }
809
810 result = send_password_auth_response(ctx, client_socket, nonce, "required password");
811 if (result != ASCIICHAT_OK) {
812 SET_ERRNO(ERROR_NETWORK, "Failed to send password auth response");
813 return result;
814 }
815 } else if (client_key_required) {
816 // Server requires client key (whitelist) - SECOND PRIORITY
817 if (!has_client_key) {
818 return SET_ERRNO(ERROR_CRYPTO, "Server requires client key authentication (whitelist)\n"
819 "Please provide --key with your authorized Ed25519 key");
820 }
821
822 result = send_key_auth_response(ctx, client_socket, nonce, "required client key");
823 if (result != ASCIICHAT_OK) {
824 SET_ERRNO(ERROR_NETWORK, "Failed to send key auth response");
825 return result;
826 }
827 } else if (has_password) {
828 // No server requirements, but client has password → send HMAC + client
829 // nonce (optional)
830 result = send_password_auth_response(ctx, client_socket, nonce, "optional password");
831 if (result != ASCIICHAT_OK) {
832 SET_ERRNO(ERROR_NETWORK, "Failed to send password auth response");
833 return result;
834 }
835 } else if (has_client_key) {
836 // No server requirements, but client has SSH key → send Ed25519 signature +
837 // client nonce (optional)
838 result = send_key_auth_response(ctx, client_socket, nonce, "optional identity");
839 if (result != ASCIICHAT_OK) {
840 SET_ERRNO(ERROR_NETWORK, "Failed to send key auth response");
841 return result;
842 }
843 } else {
844 // No authentication method available
845 // Continue without authentication (server will decide if this is
846 // acceptable)
847 log_debug("No authentication credentials provided - continuing without "
848 "authentication");
849 }
850
852
853 return ASCIICHAT_OK;
854}
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)
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
Free a buffer back to the pool (lock-free)
unsigned char uint8_t
Definition common.h:56
const char * crypto_result_to_string(crypto_result_t result)
Convert crypto result to human-readable string.
crypto_result_t
Cryptographic operation result codes.
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)
@ CRYPTO_OK
#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)
Definition error_codes.h:46
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:69
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:73
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CRYPTO
Definition error_codes.h:88
@ CRYPTO_HANDSHAKE_AUTHENTICATING
@ CRYPTO_HANDSHAKE_KEY_EXCHANGE
@ CRYPTO_HANDSHAKE_READY
key_type_t type
Definition key_types.h:92
@ KEY_TYPE_ED25519
Definition key_types.h:52
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:767
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:281
@ PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE
Server -> Client: "encryption ready" (UNENCRYPTED)
Definition packet.h:329
@ PACKET_TYPE_CRYPTO_AUTH_FAILED
Server -> Client: "authentication failed" (UNENCRYPTED)
Definition packet.h:325
@ PACKET_TYPE_CRYPTO_AUTH_CHALLENGE
Server -> Client: {nonce[32]} (UNENCRYPTED)
Definition packet.h:321
int prompt_password(const char *prompt, char *password, size_t max_len)
Prompt the user for a password with secure input.
Definition password.c:13
crypto_handshake_state_t state

References ASCIICHAT_OK, crypto_context_t::auth_challenge_size, AUTH_REQUIRE_CLIENT_KEY, AUTH_REQUIRE_PASSWORD, buffer_pool_free(), crypto_handshake_context_t::client_private_key, crypto_handshake_context_t::crypto_ctx, crypto_derive_password_key(), CRYPTO_HANDSHAKE_AUTHENTICATING, CRYPTO_HANDSHAKE_KEY_EXCHANGE, CRYPTO_HANDSHAKE_READY, crypto_handshake_validate_packet_size(), CRYPTO_OK, crypto_result_to_string(), ERROR_CRYPTO, ERROR_INVALID_STATE, ERROR_NETWORK, ERROR_NETWORK_PROTOCOL, crypto_context_t::handshake_complete, crypto_context_t::has_password, KEY_TYPE_ED25519, log_debug, log_info, PACKET_TYPE_CRYPTO_AUTH_CHALLENGE, PACKET_TYPE_CRYPTO_AUTH_FAILED, PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE, PASSWORD_BUFFER_SIZE, prompt_password(), receive_packet(), SET_ERRNO, crypto_handshake_context_t::state, and private_key_t::type.

Referenced by client_crypto_handshake().

◆ crypto_handshake_client_complete()

asciichat_error_t crypto_handshake_client_complete ( crypto_handshake_context_t ctx,
socket_t  client_socket 
)

Client: Wait for handshake complete confirmation.

Parameters
ctxHandshake context (must be in CRYPTO_HANDSHAKE_AUTHENTICATING state)
client_socketSocket to receive on
Returns
ASCIICHAT_OK on success, error code on failure

Client waits for server's HANDSHAKE_COMPLETE packet (empty payload). After receiving, handshake is complete and encryption is ready.

Note
State transition: CRYPTO_HANDSHAKE_AUTHENTICATING -> CRYPTO_HANDSHAKE_READY

Definition at line 856 of file lib/crypto/handshake/client.c.

856 {
857 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_AUTHENTICATING) {
858 SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
859 return ERROR_INVALID_STATE;
860 }
861
862 // Receive HANDSHAKE_COMPLETE or AUTH_FAILED packet
863 packet_type_t packet_type;
864 uint8_t *payload = NULL;
865 size_t payload_len = 0;
866 int result = receive_packet(client_socket, &packet_type, (void **)&payload, &payload_len);
867 if (result != ASCIICHAT_OK) {
868 SET_ERRNO(ERROR_NETWORK, "Failed to receive handshake completion packet");
869 return ERROR_NETWORK;
870 }
871
872 // Check packet type
873 if (packet_type == PACKET_TYPE_CRYPTO_AUTH_FAILED) {
874 // Parse the auth failure packet to get specific reasons
875 if (payload_len >= sizeof(auth_failure_packet_t)) {
876 auth_failure_packet_t *failure = (auth_failure_packet_t *)payload;
877 SET_ERRNO(ERROR_CRYPTO_AUTH, "Server rejected authentication:");
878
880 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Incorrect password");
881 }
883 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Server requires a password (use --password)");
884 }
886 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Server requires a whitelisted client key (use --key "
887 "with your SSH key)");
888 }
890 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Your client key is not in the server's whitelist");
891 }
893 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Client signature verification failed");
894 }
895
896 // Provide helpful guidance
900 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Server requires BOTH correct password AND "
901 "whitelisted key");
902 } else if (failure->reason_flags & AUTH_FAIL_PASSWORD_INCORRECT) {
903 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Check your password and try again");
904 } else if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REQUIRED) {
905 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Provide your SSH key with --key ~/.ssh/id_ed25519");
906 } else if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REJECTED) {
907 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Your key needs to be added to the server's whitelist");
908 }
909 }
910 } else {
911 SET_ERRNO(ERROR_CRYPTO_AUTH, "Server rejected authentication (no details provided)");
912 }
913 if (payload) {
914 buffer_pool_free(NULL, payload, payload_len);
915 }
917 "Server authentication failed - incorrect HMAC"); // Special code for
918 // auth failure - do
919 // not retry
920 }
921
922 if (packet_type != PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP) {
923 if (payload) {
924 buffer_pool_free(NULL, payload, payload_len);
925 }
926 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Expected SERVER_AUTH_RESPONSE or AUTH_FAILED, got packet type %d",
927 packet_type);
928 }
929
930 // Verify server's HMAC for mutual authentication
931 // Use ctx->crypto_ctx.hmac_size (negotiated during handshake) rather than SERVER_AUTH_RESPONSE_SIZE constant
932 if (payload_len != ctx->crypto_ctx.hmac_size) {
933 if (payload) {
934 buffer_pool_free(NULL, payload, payload_len);
935 }
936 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid SERVER_AUTH_RESPONSE size: %zu bytes (expected %u)", payload_len,
937 ctx->crypto_ctx.hmac_size);
938 }
939
940 // Verify server's HMAC (binds to DH shared_secret to prevent MITM)
942 SET_ERRNO(ERROR_CRYPTO_AUTH, "SECURITY: Server authentication failed - incorrect HMAC");
943 SET_ERRNO(ERROR_CRYPTO_AUTH, "This may indicate a man-in-the-middle attack!");
944 if (payload) {
945 buffer_pool_free(NULL, payload, payload_len);
946 }
948 "Server authentication failed - incorrect HMAC"); // Authentication
949 // failure - do not
950 // retry
951 }
952
953 if (payload) {
954 buffer_pool_free(NULL, payload, payload_len);
955 }
956
958 log_info("Server authentication successful - mutual authentication complete");
959
960 return ASCIICHAT_OK;
961}
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.
@ ERROR_CRYPTO_AUTH
Definition error_codes.h:90
uint8_t reason_flags
Bitmask of auth_failure_reason_t values indicating failure causes.
Definition packet.h:676
@ AUTH_FAIL_PASSWORD_INCORRECT
Password verification failed (incorrect password)
Definition packet.h:657
@ AUTH_FAIL_PASSWORD_REQUIRED
Server requires password but client didn't provide one.
Definition packet.h:655
@ AUTH_FAIL_CLIENT_KEY_REQUIRED
Server requires client key but client didn't provide one.
Definition packet.h:659
@ AUTH_FAIL_CLIENT_KEY_REJECTED
Client key not in whitelist (access denied)
Definition packet.h:661
@ AUTH_FAIL_SIGNATURE_INVALID
Client signature verification failed (invalid signature)
Definition packet.h:663
@ PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP
Server -> Client: {HMAC[32]} server proves knowledge (UNENCRYPTED)
Definition packet.h:327
Authentication failure packet structure.
Definition packet.h:674

References ASCIICHAT_OK, AUTH_FAIL_CLIENT_KEY_REJECTED, AUTH_FAIL_CLIENT_KEY_REQUIRED, AUTH_FAIL_PASSWORD_INCORRECT, AUTH_FAIL_PASSWORD_REQUIRED, AUTH_FAIL_SIGNATURE_INVALID, buffer_pool_free(), crypto_handshake_context_t::client_challenge_nonce, crypto_handshake_context_t::crypto_ctx, CRYPTO_HANDSHAKE_AUTHENTICATING, CRYPTO_HANDSHAKE_READY, crypto_verify_auth_response(), ERROR_CRYPTO_AUTH, ERROR_INVALID_STATE, ERROR_NETWORK, ERROR_NETWORK_PROTOCOL, crypto_context_t::hmac_size, log_info, PACKET_TYPE_CRYPTO_AUTH_FAILED, PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP, auth_failure_packet_t::reason_flags, receive_packet(), SET_ERRNO, and crypto_handshake_context_t::state.

Referenced by client_crypto_handshake().

◆ crypto_handshake_client_key_exchange()

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.

Parameters
ctxHandshake context (must be in CRYPTO_HANDSHAKE_INIT state)
client_socketSocket to send/receive on
Returns
ASCIICHAT_OK on success, error code on failure

Client processes server's KEY_EXCHANGE_INIT packet and responds with KEY_EXCHANGE_RESP. Supports both simple and authenticated formats. Verifies server signature if present.

Note
State transition: CRYPTO_HANDSHAKE_INIT -> CRYPTO_HANDSHAKE_KEY_EXCHANGE

Definition at line 29 of file lib/crypto/handshake/client.c.

29 {
30 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_INIT) {
31 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", (void *)ctx, ctx ? (int)ctx->state : -1);
32 }
33
34 // Receive server's KEY_EXCHANGE_INIT packet
35 packet_type_t packet_type;
36 uint8_t *payload = NULL;
37 size_t payload_len = 0;
38 int result = receive_packet(client_socket, &packet_type, (void **)&payload, &payload_len);
39 if (result != ASCIICHAT_OK) {
40 return SET_ERRNO(ERROR_NETWORK, "Failed to receive KEY_EXCHANGE_INIT packet");
41 }
42
43 // Verify packet type
44 if (packet_type != PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT) {
45 if (payload) {
46 buffer_pool_free(NULL, payload, payload_len);
47 }
48 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Expected KEY_EXCHANGE_INIT, got packet type %d", packet_type);
49 }
50
51 log_debug("CLIENT_KEY_EXCHANGE: Received packet with payload_len=%zu, kex_size=%u, auth_size=%u, sig_size=%u",
52 payload_len, ctx->crypto_ctx.public_key_size, ctx->crypto_ctx.auth_public_key_size,
53 ctx->crypto_ctx.signature_size);
54
55 // Check payload size - only authenticated format supported
56 // Authenticated: public_key_size + auth_public_key_size + signature_size bytes
57 size_t expected_auth_size =
58 ctx->crypto_ctx.public_key_size + ctx->crypto_ctx.auth_public_key_size + ctx->crypto_ctx.signature_size;
59
60 uint8_t *server_ephemeral_key;
61 // Use the crypto context's public key size to ensure compatibility
62 size_t key_size = sizeof(ctx->crypto_ctx.public_key);
63 server_ephemeral_key = SAFE_MALLOC(key_size, uint8_t *);
64 if (!server_ephemeral_key) {
65 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for server ephemeral key");
66 }
67 uint8_t *server_identity_key;
68 server_identity_key = SAFE_MALLOC(ctx->crypto_ctx.auth_public_key_size, uint8_t *);
69 uint8_t *server_signature;
70 server_signature = SAFE_MALLOC(ctx->crypto_ctx.signature_size, uint8_t *);
71
72 if (!server_identity_key || !server_signature) {
73 SAFE_FREE(server_ephemeral_key);
74 if (server_identity_key)
75 SAFE_FREE(server_identity_key);
76 if (server_signature)
77 SAFE_FREE(server_signature);
78 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for server identity key or signature");
79 }
80
81 // Validate packet size using session parameters
82 asciichat_error_t validation_result =
84 if (validation_result != ASCIICHAT_OK) {
85 if (payload) {
86 buffer_pool_free(NULL, payload, payload_len);
87 }
88 SAFE_FREE(server_ephemeral_key);
89 SAFE_FREE(server_identity_key);
90 SAFE_FREE(server_signature);
91 return validation_result;
92 }
93
94 // Check if server is using authentication (auth_public_key_size > 0 means
95 // authenticated format)
96 if (ctx->crypto_ctx.auth_public_key_size > 0 && payload_len == expected_auth_size) {
97 // Authenticated format:
98 // [ephemeral:public_key_size][identity:auth_public_key_size][signature:signature_size]
99 log_info("Received authenticated KEY_EXCHANGE_INIT (%zu bytes)", expected_auth_size);
100 memcpy(server_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
101 memcpy(server_identity_key, payload + ctx->crypto_ctx.public_key_size, ctx->crypto_ctx.auth_public_key_size);
102 memcpy(server_signature, payload + ctx->crypto_ctx.public_key_size + ctx->crypto_ctx.auth_public_key_size,
103 ctx->crypto_ctx.signature_size);
104
105 // Server is using client authentication
106 ctx->server_uses_client_auth = true;
107
108 // DEBUG: Print identity key received
109 char hex_id[HEX_STRING_SIZE_32];
110 for (int i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
111 safe_snprintf(hex_id + i * 2, 3, "%02x", server_identity_key[i]);
112 }
113 hex_id[HEX_STRING_SIZE_32 - 1] = '\0';
114 log_debug("Received identity key: %s", hex_id);
115
116 // DEBUG: Print ephemeral key and signature
117 char hex_eph[HEX_STRING_SIZE_32];
118 for (int i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
119 safe_snprintf(hex_eph + i * 2, 3, "%02x", server_ephemeral_key[i]);
120 }
121 hex_eph[HEX_STRING_SIZE_32 - 1] = '\0';
122 log_debug("Received ephemeral key: %s", hex_eph);
123
124 char hex_sig[HEX_STRING_SIZE_64];
125 for (int i = 0; i < ED25519_SIGNATURE_SIZE; i++) {
126 safe_snprintf(hex_sig + i * 2, 3, "%02x", server_signature[i]);
127 }
128 hex_sig[HEX_STRING_SIZE_64 - 1] = '\0';
129 log_debug("Received signature: %s", hex_sig);
130
131 // Verify signature: server identity signed the ephemeral key
132 log_debug("Verifying server's signature over ephemeral key (already logged above)");
133
134 // If client didn't specify --server-key, skip signature verification
135 // (client doesn't care about server identity verification)
136 if (!ctx->verify_server_key) {
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)");
139 } else {
140 // Extract GPG key ID from expected_server_key if it's a GPG key (gpg:KEYID format)
141 const char *gpg_key_id = NULL;
142 if (ctx->expected_server_key[0] != '\0' && strncmp(ctx->expected_server_key, "gpg:", 4) == 0) {
143 const char *key_id_start = ctx->expected_server_key + 4;
144 size_t key_id_len = strlen(key_id_start);
145 // Accept 8, 16, or 40 character GPG key IDs (short, long, or full fingerprint)
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);
149 }
150 }
151
152 if (ed25519_verify_signature(server_identity_key, server_ephemeral_key, ctx->crypto_ctx.public_key_size,
153 server_signature, gpg_key_id) != 0) {
154 if (payload) {
155 buffer_pool_free(NULL, payload, payload_len);
156 }
157 SAFE_FREE(server_ephemeral_key);
158 SAFE_FREE(server_identity_key);
159 SAFE_FREE(server_signature);
160 return SET_ERRNO(ERROR_CRYPTO, "Server signature verification FAILED - rejecting connection. "
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");
164 }
165 log_info("Server signature verified successfully");
166 }
167
168 // Verify server identity against expected key if --server-key is specified
169 if (ctx->verify_server_key && strlen(ctx->expected_server_key) > 0) {
170 // Parse ALL expected server keys (github:/gitlab: may have multiple keys)
171 public_key_t expected_keys[MAX_CLIENTS];
172 size_t num_expected_keys = 0;
173 if (parse_public_keys(ctx->expected_server_key, expected_keys, &num_expected_keys, MAX_CLIENTS) != 0 ||
174 num_expected_keys == 0) {
175 if (payload) {
176 buffer_pool_free(NULL, payload, payload_len);
177 }
178 SAFE_FREE(server_ephemeral_key);
179 SAFE_FREE(server_identity_key);
180 SAFE_FREE(server_signature);
181 return SET_ERRNO(ERROR_CONFIG,
182 "Failed to parse expected server key: %s. Check that "
183 "--server-key value is valid (ssh-ed25519 "
184 "format, github:username, or hex)",
185 ctx->expected_server_key);
186 }
187
188 // Compare server's IDENTITY key against ALL expected keys (match any one)
189 // This supports users with multiple SSH keys (e.g., different machines)
190 bool key_matched = false;
191 for (size_t i = 0; i < num_expected_keys; i++) {
192 if (sodium_memcmp(server_identity_key, expected_keys[i].key, ED25519_PUBLIC_KEY_SIZE) == 0) {
193 key_matched = true;
194 log_info("Server identity key matched expected key %zu/%zu", i + 1, num_expected_keys);
195 break;
196 }
197 }
198
199 if (!key_matched) {
200 if (payload) {
201 buffer_pool_free(NULL, payload, payload_len);
202 }
203 SAFE_FREE(server_ephemeral_key);
204 SAFE_FREE(server_identity_key);
205 SAFE_FREE(server_signature);
206 return SET_ERRNO(ERROR_CRYPTO,
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!",
211 ctx->expected_server_key, num_expected_keys);
212 }
213 log_info("Server identity key verified against --server-key (%zu key(s) checked)", num_expected_keys);
214 }
215
216 // Resolve server IP from socket if not already set
217 if (ctx->server_ip[0] == '\0') {
218 // Try to get server IP from socket
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)) ==
224 ASCIICHAT_OK) {
225 SAFE_STRNCPY(ctx->server_ip, ip_str, sizeof(ctx->server_ip) - 1);
226 if (server_addr.ss_family == AF_INET) {
227 struct sockaddr_in *addr_in = (struct sockaddr_in *)&server_addr;
228 ctx->server_port = NET_TO_HOST_U16(addr_in->sin_port);
229 } else if (server_addr.ss_family == AF_INET6) {
230 struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&server_addr;
231 ctx->server_port = NET_TO_HOST_U16(addr_in6->sin6_port);
232 }
233 }
234 } else {
235 log_warn("Failed to get server address from socket");
236 }
237 } else {
238 log_warn("Server IP already set: %s", ctx->server_ip);
239 }
240
241 // Check known_hosts for this server (if we have server IP and port)
242 // Check if known_hosts verification should be skipped
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) {
246 log_warn(
247 "Skipping known_hosts checking for authenticated connection (ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK=1)");
248 skip_known_hosts = true;
249 }
250#ifndef NDEBUG
251 // In debug builds, also skip for Claude Code (LLM automation can't do interactive prompts)
252 else if (platform_getenv("CLAUDECODE")) {
253 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build)");
254 skip_known_hosts = true;
255 }
256#endif
257
258 if (!skip_known_hosts && ctx->server_ip[0] != '\0' && ctx->server_port > 0) {
259 asciichat_error_t known_host_result = check_known_host(ctx->server_ip, ctx->server_port, server_identity_key);
260 if (known_host_result == ERROR_CRYPTO_VERIFICATION) {
261 // Key mismatch - MITM attack detected! Prompt user for confirmation
262 log_error("SECURITY: Server key does NOT match known_hosts entry!\n"
263 "This indicates a possible man-in-the-middle attack!");
264 uint8_t stored_key[ZERO_KEY_SIZE] = {0}; // We don't have the stored key easily
265 // accessible, use zeros for now
266 if (!display_mitm_warning(ctx->server_ip, ctx->server_port, stored_key, server_identity_key)) {
267 // User declined to continue - ABORT connection for security
268 if (payload) {
269 buffer_pool_free(NULL, payload, payload_len);
270 }
271 SAFE_FREE(server_ephemeral_key);
272 SAFE_FREE(server_identity_key);
273 SAFE_FREE(server_signature);
275 "SECURITY: Connection aborted - server key mismatch (possible MITM attack)");
276 }
277 // User accepted the risk - continue with connection
278 log_warn("SECURITY WARNING: User accepted MITM risk - continuing with connection");
279 } else if (known_host_result == ASCIICHAT_OK) {
280 // Unknown host (first connection) - prompt user to verify fingerprint
281 if (!prompt_unknown_host(ctx->server_ip, ctx->server_port, server_identity_key)) {
282 // User declined to add host - ABORT connection
283 if (payload) {
284 buffer_pool_free(NULL, payload, payload_len);
285 }
286 SAFE_FREE(server_ephemeral_key);
287 SAFE_FREE(server_identity_key);
288 SAFE_FREE(server_signature);
289 return SET_ERRNO(ERROR_CRYPTO, "User declined to verify unknown host");
290 }
291
292 // User accepted - add to known_hosts
293 if (add_known_host(ctx->server_ip, ctx->server_port, server_identity_key) != ASCIICHAT_OK) {
294 if (payload) {
295 buffer_pool_free(NULL, payload, payload_len);
296 }
297 SAFE_FREE(server_ephemeral_key);
298 SAFE_FREE(server_identity_key);
299 SAFE_FREE(server_signature);
300 return SET_ERRNO(ERROR_CONFIG,
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",
306 }
307 log_info("Server host added to known_hosts successfully");
308 } else if (known_host_result == 1) {
309 // Key matches - connection is secure!
310 log_info("Server host key verified from known_hosts - connection secure");
311 } else {
312 // Unexpected error code from check_known_host
313 if (payload) {
314 buffer_pool_free(NULL, payload, payload_len);
315 }
316 SAFE_FREE(server_ephemeral_key);
317 SAFE_FREE(server_identity_key);
318 SAFE_FREE(server_signature);
319 return SET_ERRNO(known_host_result, "SECURITY: known_hosts verification failed with error code %d",
320 known_host_result);
321 }
322 }
323 } else if (payload_len == ctx->crypto_ctx.public_key_size) {
324 // Simple format: just ephemeral key (no identity key)
325 log_info("Received simple KEY_EXCHANGE_INIT (%zu bytes) - server has no "
326 "identity key",
327 payload_len);
328 memcpy(server_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
329
330 // Clear identity key and signature for simple format
331 memset(server_identity_key, 0, ctx->crypto_ctx.auth_public_key_size);
332 memset(server_signature, 0, ctx->crypto_ctx.signature_size);
333
334 // Server is not using client authentication in simple mode
335 ctx->server_uses_client_auth = false;
336
337 log_debug("Received ephemeral key (simple format)");
338
339 // SECURITY: For servers without identity keys, we implement a different security model:
340 // 1. Verify IP address matches known_hosts entry
341 // 2. Always require user confirmation (no silent connections)
342 // 3. Store server fingerprint for future verification
343
344 if (ctx->server_ip[0] == '\0' || ctx->server_port <= 0) {
345 SAFE_FREE(server_ephemeral_key);
346 SAFE_FREE(server_identity_key);
347 SAFE_FREE(server_signature);
348 return SET_ERRNO(ERROR_CRYPTO, "Server IP or port not set, cannot check known_hosts");
349 }
350
351 // Check if this server was previously connected to (IP verification)
352 bool skip_known_hosts = false;
353 asciichat_error_t known_host_result = ASCIICHAT_OK;
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;
358 }
359#ifndef NDEBUG
360 // In debug builds, also skip for Claude Code (LLM automation can't do interactive prompts)
361 else if (platform_getenv("CLAUDECODE")) {
362 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build).");
363 skip_known_hosts = true;
364 }
365#endif
366 else {
367 known_host_result = check_known_host_no_identity(ctx->server_ip, ctx->server_port);
368 }
369
370 if (skip_known_hosts || known_host_result == 1) {
371 // Server IP is known and verified - allow connection without warnings
372 log_info("SECURITY: Server IP %s:%u is known (no-identity entry found) - connection verified", ctx->server_ip,
373 ctx->server_port);
374 } else if (known_host_result == ASCIICHAT_OK) {
375 // Server IP is unknown - require user confirmation
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",
379 ctx->server_ip, ctx->server_port);
380
381 if (!prompt_unknown_host_no_identity(ctx->server_ip, ctx->server_port)) {
382 if (payload) {
383 buffer_pool_free(NULL, payload, payload_len);
384 }
385 SAFE_FREE(server_ephemeral_key);
386 SAFE_FREE(server_identity_key);
387 SAFE_FREE(server_signature);
388 return SET_ERRNO(ERROR_CRYPTO, "User declined to connect to unknown server without identity key");
389 }
390
391 // User accepted - add to known_hosts as no-identity entry
392 // For servers without identity keys, pass zero key to indicate no-identity
393 uint8_t zero_key[ZERO_KEY_SIZE] = {0};
394 if (add_known_host(ctx->server_ip, ctx->server_port, zero_key) != ASCIICHAT_OK) {
395 if (payload) {
396 buffer_pool_free(NULL, payload, payload_len);
397 }
398 SAFE_FREE(server_ephemeral_key);
399 SAFE_FREE(server_identity_key);
400 SAFE_FREE(server_signature);
401 return SET_ERRNO(ERROR_CONFIG,
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",
407 }
408 log_info("Server host added to known_hosts successfully");
409 } else if (known_host_result == ERROR_CRYPTO_VERIFICATION) {
410 // Server previously had identity key but now has none - potential security issue
411 log_warn("SECURITY: Server previously had identity key but now has none - potential security issue");
412 if (payload) {
413 buffer_pool_free(NULL, payload, payload_len);
414 }
415 SAFE_FREE(server_ephemeral_key);
416 SAFE_FREE(server_identity_key);
417 SAFE_FREE(server_signature);
418 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Server key configuration changed - potential security issue");
419 } else {
420 // Other error checking known_hosts (e.g., ERROR_INVALID_PARAM)
421 if (payload) {
422 buffer_pool_free(NULL, payload, payload_len);
423 }
424 SAFE_FREE(server_ephemeral_key);
425 SAFE_FREE(server_identity_key);
426 SAFE_FREE(server_signature);
427 return SET_ERRNO(ERROR_CRYPTO, "Failed to verify server IP address");
428 }
429 } else {
430 if (payload) {
431 buffer_pool_free(NULL, payload, payload_len);
432 }
433 SAFE_FREE(server_ephemeral_key);
434 SAFE_FREE(server_identity_key);
435 SAFE_FREE(server_signature);
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",
441 payload_len, expected_auth_size, ctx->crypto_ctx.public_key_size);
442 // retry
443 }
444
445 // Set peer's public key (EPHEMERAL X25519) - this also derives the shared secret
446 crypto_result_t crypto_result = crypto_set_peer_public_key(&ctx->crypto_ctx, server_ephemeral_key);
447 if (payload) {
448 buffer_pool_free(NULL, payload, payload_len);
449 }
450 if (crypto_result != CRYPTO_OK) {
451 SAFE_FREE(server_ephemeral_key);
452 SAFE_FREE(server_identity_key);
453 SAFE_FREE(server_signature);
454 return SET_ERRNO(ERROR_CRYPTO, "Failed to set peer public key and derive shared secret: %s",
455 crypto_result_to_string(crypto_result));
456 }
457
458 // Determine if client has an identity key
459 bool client_has_identity_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
460
461 // Send authenticated response if server has identity key (auth_public_key_size > 0)
462 // OR if server requires client authentication (require_client_auth)
463 // Note: server_uses_client_auth is set when server has identity key, but we should
464 // send authenticated response when server has identity key regardless of client auth requirement
465 bool server_has_identity = (ctx->crypto_ctx.auth_public_key_size > 0 && ctx->crypto_ctx.signature_size > 0);
466 bool server_requires_auth = server_has_identity || ctx->require_client_auth;
467
468 if (server_requires_auth) {
469 // Send authenticated packet:
470 // [ephemeral:kex_size][identity:auth_size][signature:sig_size][gpg_key_id_len:1][gpg_key_id:0-16]
471 // Use Ed25519 sizes since client has Ed25519 key
472 size_t ed25519_pubkey_size = ED25519_PUBLIC_KEY_SIZE; // Ed25519 public key is always 32 bytes
473 size_t ed25519_sig_size = ED25519_SIGNATURE_SIZE; // Ed25519 signature is always 64 bytes
474 size_t response_size = ctx->crypto_ctx.public_key_size + ed25519_pubkey_size + ed25519_sig_size;
475
476 // Check if client has a GPG key ID to send
477 uint8_t gpg_key_id_len = 0;
478 if (ctx->client_gpg_key_id[0] != '\0') {
479 gpg_key_id_len = (uint8_t)strlen(ctx->client_gpg_key_id);
480 if (gpg_key_id_len > 40) {
481 gpg_key_id_len = 40; // Truncate to max length (full fingerprint)
482 }
483 response_size += 1 + gpg_key_id_len; // 1 byte for length + key ID
484 } else {
485 response_size += 1; // Just the length byte (0)
486 }
487
488 uint8_t *key_response = SAFE_MALLOC(response_size, uint8_t *);
489 size_t offset = 0;
490
491 // Copy ephemeral key
492 memcpy(key_response + offset, ctx->crypto_ctx.public_key,
493 ctx->crypto_ctx.public_key_size); // X25519 ephemeral for encryption
494 offset += ctx->crypto_ctx.public_key_size;
495
496 if (client_has_identity_key) {
497 // Client has identity key - send it with signature
498 memcpy(key_response + offset, ctx->client_private_key.public_key, ed25519_pubkey_size); // Ed25519 identity
499 offset += ed25519_pubkey_size;
500
501 // Sign ephemeral key with client identity key
502 if (ed25519_sign_message(&ctx->client_private_key, ctx->crypto_ctx.public_key, ctx->crypto_ctx.public_key_size,
503 key_response + offset) != 0) {
504 SAFE_FREE(key_response);
505 SAFE_FREE(server_ephemeral_key);
506 SAFE_FREE(server_identity_key);
507 SAFE_FREE(server_signature);
508 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign client ephemeral key");
509 }
510 offset += ed25519_sig_size;
511 } else {
512 // Client has no identity key - send null identity and null signature
513 memset(key_response + offset, 0, ed25519_pubkey_size); // Null identity
514 offset += ed25519_pubkey_size;
515 memset(key_response + offset, 0, ed25519_sig_size); // Null signature
516 offset += ed25519_sig_size;
517 }
518
519 // Append GPG key ID length
520 key_response[offset] = gpg_key_id_len;
521 offset += 1;
522
523 // Append GPG key ID if present
524 if (gpg_key_id_len > 0) {
525 memcpy(key_response + offset, ctx->client_gpg_key_id, gpg_key_id_len);
526 offset += gpg_key_id_len;
527 log_debug("Including client GPG key ID in KEY_EXCHANGE_RESPONSE: %.*s", gpg_key_id_len, ctx->client_gpg_key_id);
528 }
529
530 result = send_packet(client_socket, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, key_response, response_size);
531 if (result != 0) {
532 SAFE_FREE(key_response);
533 SAFE_FREE(server_ephemeral_key);
534 SAFE_FREE(server_identity_key);
535 SAFE_FREE(server_signature);
536 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_RESPONSE packet");
537 }
538
539 // Zero out the buffer before freeing
540 sodium_memzero(key_response, response_size);
541 SAFE_FREE(key_response);
542 } else {
543 // Send X25519 encryption key only to server (no identity key)
544 // Format: [X25519 pubkey (kex_size)] = kex_size bytes total
545 result = send_packet(client_socket, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, ctx->crypto_ctx.public_key,
546 ctx->crypto_ctx.public_key_size);
547 if (result != 0) {
548 SAFE_FREE(server_ephemeral_key);
549 SAFE_FREE(server_identity_key);
550 SAFE_FREE(server_signature);
551 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_RESPONSE packet");
552 }
553 }
554
556
557 // Free temporary buffers before successful return
558 SAFE_FREE(server_ephemeral_key);
559 SAFE_FREE(server_identity_key);
560 SAFE_FREE(server_signature);
561
562 return ASCIICHAT_OK;
563}
#define NET_TO_HOST_U16(val)
Definition endian.h:116
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
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.
Definition known_hosts.c:46
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.
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.
#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.
Definition known_hosts.c:77
#define HEX_STRING_SIZE_64
Hex string size for 64-byte values (128 hex chars + null terminator)
@ ERROR_CRYPTO_VERIFICATION
Definition error_codes.h:92
@ ERROR_MEMORY
Definition error_codes.h:53
@ ERROR_CONFIG
Definition error_codes.h:54
@ CRYPTO_HANDSHAKE_INIT
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.
Definition ssh_keys.c:1163
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)
Definition keys.c:187
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)
Definition ssh_keys.c:1067
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:23
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
int send_packet(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a basic packet without encryption.
Definition packet.c:754
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT
Server -> Client: {server_pubkey[32]} (UNENCRYPTED)
Definition packet.h:317
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP
Client -> Server: {client_pubkey[32]} (UNENCRYPTED)
Definition packet.h:319
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe version of snprintf that ensures null termination.
const char * platform_getenv(const char *name)
Get an environment variable value.
#define STR_ONE
String literal: "1" (one)
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.
Definition ip.c:205
Public key structure.
Definition key_types.h:69

References add_known_host(), ASCIICHAT_OK, crypto_context_t::auth_public_key_size, buffer_pool_free(), check_known_host(), check_known_host_no_identity(), crypto_handshake_context_t::client_gpg_key_id, crypto_handshake_context_t::client_private_key, crypto_handshake_context_t::crypto_ctx, CRYPTO_HANDSHAKE_INIT, CRYPTO_HANDSHAKE_KEY_EXCHANGE, crypto_handshake_validate_packet_size(), CRYPTO_OK, crypto_result_to_string(), crypto_set_peer_public_key(), display_mitm_warning(), ED25519_PUBLIC_KEY_SIZE, ed25519_sign_message(), ED25519_SIGNATURE_SIZE, ed25519_verify_signature(), ERROR_CONFIG, ERROR_CRYPTO, ERROR_CRYPTO_VERIFICATION, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_NETWORK, ERROR_NETWORK_PROTOCOL, crypto_handshake_context_t::expected_server_key, format_ip_address(), get_known_hosts_path(), HEX_STRING_SIZE_32, HEX_STRING_SIZE_64, KEY_TYPE_ED25519, log_debug, log_error, log_info, log_warn, MAX_CLIENTS, NET_TO_HOST_U16, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, parse_public_keys(), platform_getenv(), prompt_unknown_host(), prompt_unknown_host_no_identity(), crypto_context_t::public_key, private_key_t::public_key, crypto_context_t::public_key_size, receive_packet(), crypto_handshake_context_t::require_client_auth, SAFE_FREE, SAFE_MALLOC, safe_snprintf(), SAFE_STRNCPY, send_packet(), crypto_handshake_context_t::server_ip, crypto_handshake_context_t::server_port, crypto_handshake_context_t::server_uses_client_auth, SET_ERRNO, crypto_context_t::signature_size, crypto_handshake_context_t::state, STR_ONE, private_key_t::type, crypto_handshake_context_t::verify_server_key, and ZERO_KEY_SIZE.

Referenced by client_crypto_handshake().