ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
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, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
 Client: Process server's public key and send our public key.
asciichat_error_t crypto_handshake_client_auth_response (crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
 Client: Process auth challenge and send response.
asciichat_error_t crypto_handshake_client_complete (crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
 Client: Wait for handshake complete confirmation.

Detailed Description

Client-side handshake functions.

Definition in file include/ascii-chat/crypto/handshake/client.h.

Function Documentation

◆ crypto_handshake_client_auth_response()

asciichat_error_t crypto_handshake_client_auth_response ( crypto_handshake_context_t * ctx,
acip_transport_t * transport,
packet_type_t packet_type,
const uint8_t * payload,
size_t payload_len )

Client: Process auth challenge and send response.

Parameters
ctxHandshake context (must be in CRYPTO_HANDSHAKE_KEY_EXCHANGE state)
transportACIP transport to send on
packet_typePacket type received (should be PACKET_TYPE_CRYPTO_AUTH_CHALLENGE or PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE)
payloadReceived packet payload
payload_lenLength of received payload
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
Packet must be received by ACIP handler before calling this function
State transition: CRYPTO_HANDSHAKE_KEY_EXCHANGE -> CRYPTO_HANDSHAKE_AUTHENTICATING

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

602 {
603 ASCII_OTEL_SCOPE(CRYPTO_HANDSHAKE_CLIENT_AUTH_RESPONSE);
604 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_KEY_EXCHANGE) {
605 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
606 }
607 if (!transport) {
608 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
609 }
610
611 // Note: Packet already received by ACIP handler
612 int result;
613
614 // If server sent HANDSHAKE_COMPLETE, authentication was skipped (client has
615 // no key)
616 if (packet_type == PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE) {
618 ctx->crypto_ctx.handshake_complete = true; // Mark crypto context as ready for rekeying
619 log_debug("Crypto handshake completed successfully (no authentication required)");
620 return ASCIICHAT_OK;
621 }
622
623 // If server sent AUTH_FAILED, client is not authorized
624 if (packet_type == PACKET_TYPE_CRYPTO_AUTH_FAILED) {
625 return SET_ERRNO(ERROR_CRYPTO, "Server rejected authentication - client key not authorized");
626 }
627
628 // Otherwise, verify packet type is AUTH_CHALLENGE
629 if (packet_type != PACKET_TYPE_CRYPTO_AUTH_CHALLENGE) {
631 "Expected AUTH_CHALLENGE, HANDSHAKE_COMPLETE, or AUTH_FAILED, "
632 "got packet type %d",
633 packet_type);
634 }
635
636 // Validate packet size using session parameters
637 asciichat_error_t validation_result =
639 if (validation_result != ASCIICHAT_OK) {
640 return validation_result;
641 }
642
643 // Parse auth requirement flags
644 uint8_t auth_flags = payload[0];
645
646 // Copy nonce to local buffer before freeing payload
647 // Use auth_challenge_size since that's what the server sent
648 // Note: auth_challenge_size is uint8_t (max 255), buffer is 256 bytes, so always sufficient
650 memcpy(nonce_buffer, payload + 1, ctx->crypto_ctx.auth_challenge_size);
651 const uint8_t *nonce = nonce_buffer;
652
653 log_debug("Server auth requirements: password=%s, client_key=%s",
654 (auth_flags & AUTH_REQUIRE_PASSWORD) ? "required" : "no",
655 (auth_flags & AUTH_REQUIRE_CLIENT_KEY) ? "required" : "no");
656
657 // Check if we can satisfy the server's authentication requirements
658 bool has_password = ctx->crypto_ctx.has_password;
659 bool has_client_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
660 bool password_required = (auth_flags & AUTH_REQUIRE_PASSWORD);
661 bool client_key_required = (auth_flags & AUTH_REQUIRE_CLIENT_KEY);
662
663 // Provide specific error messages based on what's missing
664 if (password_required && !has_password) {
665 if (client_key_required && !has_client_key) {
666 return SET_ERRNO(ERROR_CRYPTO, "Server requires both password and client key authentication. Please "
667 "provide --password and --key to authenticate");
668 }
669 // Prompt for password interactively
670 if (ctx->defer_password_prompt) {
672 return deferred_password_error("Server requires a password");
673 }
674 char prompted_password[PASSWORD_BUFFER_SIZE];
675 if (prompt_password("Server password required - please enter password:", prompted_password,
676 sizeof(prompted_password)) != 0) {
677 return SET_ERRNO(ERROR_CRYPTO, "Failed to read password");
678 }
679
680 // Derive password key from prompted password
681 log_debug("Deriving key from prompted password");
682 crypto_result_t crypto_result = crypto_derive_password_key(&ctx->crypto_ctx, prompted_password);
683 sodium_memzero(prompted_password, sizeof(prompted_password));
684
685 if (crypto_result != CRYPTO_OK) {
686 return SET_ERRNO(ERROR_CRYPTO, "Failed to derive password key: %s", crypto_result_to_string(crypto_result));
687 }
688
689 // Mark that password auth is now available
690 ctx->crypto_ctx.has_password = true;
691 has_password = true; // Update flag for logic below
692 }
693
694 // Authentication response priority:
695 // NOTE: Identity verification happens during KEY_EXCHANGE phase, not
696 // AUTH_RESPONSE!
697 // 1. If server requires password → MUST send HMAC (hmac_size bytes), error if no password
698 // 2. Else if server requires identity (whitelist) → MUST send Ed25519 signature (signature_size bytes), error if no
699 // key
700 // 3. Else if client has password → send HMAC (optional password auth)
701 // 4. Else if client has SSH key → send Ed25519 signature (optional identity
702 // proof)
703 // 5. Else → no authentication available
704
705 // Clean up payload before any early returns
706 if (password_required) {
707 // Server requires password - HIGHEST PRIORITY
708 // (Identity was already verified in KEY_EXCHANGE phase if whitelist is
709 // enabled)
710 if (!has_password) {
711 return SET_ERRNO(ERROR_CRYPTO, "Server requires password authentication\n"
712 "Please provide --password for this server");
713 }
714
715 result = send_password_auth_response(ctx, transport, nonce, "required password");
716 if (result != ASCIICHAT_OK) {
717 SET_ERRNO(ERROR_NETWORK, "Failed to send password auth response");
718 return result;
719 }
720 } else if (client_key_required) {
721 // Server requires client key (whitelist) - SECOND PRIORITY
722 if (!has_client_key) {
723 return SET_ERRNO(ERROR_CRYPTO, "Server requires client key authentication (whitelist)\n"
724 "Please provide --key with your authorized Ed25519 key");
725 }
726
727 result = send_key_auth_response(ctx, transport, nonce, "required client key");
728 if (result != ASCIICHAT_OK) {
729 SET_ERRNO(ERROR_NETWORK, "Failed to send key auth response");
730 return result;
731 }
732 } else if (has_password) {
733 // No server requirements, but client has password → send HMAC + client
734 // nonce (optional)
735 result = send_password_auth_response(ctx, transport, nonce, "optional password");
736 if (result != ASCIICHAT_OK) {
737 SET_ERRNO(ERROR_NETWORK, "Failed to send password auth response");
738 return result;
739 }
740 } else if (has_client_key) {
741 // No server requirements, but client has SSH key → send Ed25519 signature +
742 // client nonce (optional)
743 result = send_key_auth_response(ctx, transport, nonce, "optional identity");
744 if (result != ASCIICHAT_OK) {
745 SET_ERRNO(ERROR_NETWORK, "Failed to send key auth response");
746 return result;
747 }
748 } else {
749 // No authentication method available
750 // Continue without authentication (server will decide if this is
751 // acceptable)
752 log_debug("No authentication credentials provided - continuing without "
753 "authentication");
754 }
755
757
758 return ASCIICHAT_OK;
759}
#define SET_ERRNO(code,...)
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.
@ CRYPTO_HANDSHAKE_AUTHENTICATING
@ CRYPTO_HANDSHAKE_KEY_EXCHANGE
@ CRYPTO_HANDSHAKE_READY
#define AUTH_REQUIRE_PASSWORD
Server requires password authentication.
#define AUTH_REQUIRE_CLIENT_KEY
Server requires client key authentication (whitelist).
unsigned char uint8_t
Definition common.h:60
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.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:72
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:76
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_CRYPTO
Definition error_codes.h:93
@ ERROR_INVALID_PARAM
key_type_t type
Definition key_types.h:98
@ KEY_TYPE_ED25519
Definition key_types.h:54
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
@ AUTH_FAIL_PASSWORD_REQUIRED
Server requires password but client didn't provide one.
Definition packet.h:803
@ PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE
Server -> Client: "encryption ready" (UNENCRYPTED).
Definition packet.h:338
@ PACKET_TYPE_CRYPTO_AUTH_FAILED
Server -> Client: "authentication failed" (UNENCRYPTED).
Definition packet.h:334
@ PACKET_TYPE_CRYPTO_AUTH_CHALLENGE
Server -> Client: {nonce[32]} (UNENCRYPTED).
Definition packet.h:330
int prompt_password(const char *prompt, char *password, size_t max_len)
Prompt the user for a password with secure input.
Definition password.c:15
#define HANDSHAKE_NONCE_PACKET_CAPACITY
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
#define PASSWORD_BUFFER_SIZE
Password input buffer size (256 bytes).
Definition sizes.h:158
crypto_handshake_state_t state

References ASCII_OTEL_SCOPE, ASCIICHAT_OK, crypto_context_t::auth_challenge_size, AUTH_FAIL_PASSWORD_REQUIRED, AUTH_REQUIRE_CLIENT_KEY, AUTH_REQUIRE_PASSWORD, 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(), crypto_handshake_context_t::defer_password_prompt, ERROR_CRYPTO, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_NETWORK, ERROR_NETWORK_PROTOCOL, crypto_context_t::handshake_complete, HANDSHAKE_NONCE_PACKET_CAPACITY, crypto_context_t::has_password, KEY_TYPE_ED25519, log_debug, PACKET_TYPE_CRYPTO_AUTH_CHALLENGE, PACKET_TYPE_CRYPTO_AUTH_FAILED, PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE, PASSWORD_BUFFER_SIZE, crypto_handshake_context_t::password_failure, prompt_password(), SET_ERRNO, crypto_handshake_context_t::state, and private_key_t::type.

Referenced by acds_handshake_receive(), acip_security_receive(), and client_handle_auth_challenge().

◆ crypto_handshake_client_complete()

asciichat_error_t crypto_handshake_client_complete ( crypto_handshake_context_t * ctx,
acip_transport_t * transport,
packet_type_t packet_type,
const uint8_t * payload,
size_t payload_len )

Client: Wait for handshake complete confirmation.

Parameters
ctxHandshake context (must be in CRYPTO_HANDSHAKE_AUTHENTICATING state)
transportACIP transport (unused, for consistency)
packet_typePacket type received (should be PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP or PACKET_TYPE_CRYPTO_AUTH_FAILED)
payloadReceived packet payload
payload_lenLength of received payload
Returns
ASCIICHAT_OK on success, error code on failure

Client processes server's SERVER_AUTH_RESP or AUTH_FAILED packet. After receiving, handshake is complete and encryption is ready.

Note
Packet must be received by ACIP handler before calling this function
State transition: CRYPTO_HANDSHAKE_AUTHENTICATING -> CRYPTO_HANDSHAKE_READY

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

763 {
764 ASCII_OTEL_SCOPE(CRYPTO_HANDSHAKE_CLIENT_COMPLETE);
765 // Accept both KEY_EXCHANGE and AUTHENTICATING states for simple mode compatibility
766 // In simple mode, server skips AUTH_CHALLENGE and sends HANDSHAKE_COMPLETE directly
768 SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", ctx, ctx ? ctx->state : -1);
769 return ERROR_INVALID_STATE;
770 }
771 if (!transport) {
772 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
773 }
774
775 // Note: Packet already received by ACIP handler
776 (void)transport; // Unused parameter (this function only receives, doesn't send)
777
778 // Check packet type
779 if (packet_type == PACKET_TYPE_CRYPTO_AUTH_FAILED) {
780 // Parse the auth failure packet to get specific reasons
781 if (payload_len >= sizeof(auth_failure_packet_t)) {
782 auth_failure_packet_t *failure = (auth_failure_packet_t *)payload;
784 if (ctx->defer_password_prompt && failure->reason_flags && !(failure->reason_flags & ~password_flags)) {
785 ctx->password_failure = failure->reason_flags;
786 return deferred_password_error("Server rejected password");
787 }
788 NOTICE(DANGER, "AUTHENTICATION REJECTED", "%s%s%s%s%sConnection rejected.",
789 failure->reason_flags & AUTH_FAIL_PASSWORD_INCORRECT ? "Incorrect password.\n" : "",
790 failure->reason_flags & AUTH_FAIL_PASSWORD_REQUIRED ? "Password required: use --password.\n" : "",
791 failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REQUIRED ? "Client identity required: use --key.\n" : "",
792 failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REJECTED ? "Client key is not authorized by the server.\n"
793 : "",
794 failure->reason_flags & AUTH_FAIL_SIGNATURE_INVALID ? "Client signature verification failed.\n" : "");
795 SET_ERRNO(ERROR_CRYPTO_AUTH, "Server rejected authentication:");
796
798 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Incorrect password");
799 }
801 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Server requires a password (use --password)");
802 }
804 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Server requires a whitelisted client key (use --key "
805 "with your SSH key)");
806 }
808 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Your client key is not in the server's whitelist");
809 }
811 SET_ERRNO(ERROR_CRYPTO_AUTH, " - Client signature verification failed");
812 }
813
814 // Provide helpful guidance
818 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Server requires BOTH correct password AND "
819 "whitelisted key");
820 } else if (failure->reason_flags & AUTH_FAIL_PASSWORD_INCORRECT) {
821 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Check your password and try again");
822 } else if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REQUIRED) {
823 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Provide your SSH key with --key ~/.ssh/id_ed25519");
824 } else if (failure->reason_flags & AUTH_FAIL_CLIENT_KEY_REJECTED) {
825 SET_ERRNO(ERROR_CRYPTO_AUTH, "Hint: Your key needs to be added to the server's whitelist");
826 }
827 }
828 } else {
829 NOTICE(DANGER, "AUTHENTICATION REJECTED",
830 "Server rejected authentication (no details provided).\nConnection rejected.");
831 SET_ERRNO(ERROR_CRYPTO_AUTH, "Server rejected authentication (no details provided)");
832 }
834 "Server authentication failed - incorrect HMAC"); // Special code for
835 // auth failure - do
836 // not retry
837 }
838
839 // Handle no-auth flow: server sends HANDSHAKE_COMPLETE directly
840 if (packet_type == PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE) {
842 ctx->crypto_ctx.handshake_complete = true;
843 log_info("Handshake complete (no authentication required)");
844 return ASCIICHAT_OK;
845 }
846
847 // Handle with-auth flow: server sends SERVER_AUTH_RESP after authentication
848 if (packet_type != PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP) {
850 "Expected HANDSHAKE_COMPLETE, SERVER_AUTH_RESPONSE, or AUTH_FAILED, got packet type %d",
851 packet_type);
852 }
853
854 // Verify server's HMAC for mutual authentication
855 // Use ctx->crypto_ctx.hmac_size (negotiated during handshake) rather than SERVER_AUTH_RESPONSE_SIZE constant
856 if (payload_len != ctx->crypto_ctx.hmac_size) {
857 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid SERVER_AUTH_RESPONSE size: %zu bytes (expected %u)", payload_len,
858 ctx->crypto_ctx.hmac_size);
859 }
860
861 // Verify server's HMAC (binds to DH shared_secret to prevent MITM)
863 NOTICE(
864 DANGER, "SERVER AUTHENTICATION FAILED",
865 "The server returned an incorrect HMAC. This may indicate a man-in-the-middle attack.\nConnection rejected.");
867 "Server authentication failed - incorrect HMAC"); // Authentication
868 // failure - do not
869 // retry
870 }
872 ctx->crypto_ctx.handshake_complete = true;
873 log_info("Server authentication successful - mutual authentication complete");
874
875 return ASCIICHAT_OK;
876}
@ ERROR_CRYPTO_AUTH
Definition error_codes.h:95
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
@ AUTH_FAIL_PASSWORD_INCORRECT
Password verification failed (incorrect password).
Definition packet.h:805
@ AUTH_FAIL_CLIENT_KEY_REQUIRED
Server requires client key but client didn't provide one.
Definition packet.h:807
@ AUTH_FAIL_CLIENT_KEY_REJECTED
Client key not in whitelist (access denied).
Definition packet.h:809
@ AUTH_FAIL_SIGNATURE_INVALID
Client signature verification failed (invalid signature).
Definition packet.h:811
@ PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP
Server -> Client: {HMAC[32]} server proves knowledge (UNENCRYPTED).
Definition packet.h:336
bool crypto_verify_auth_response(const crypto_context_t *ctx, const uint8_t nonce[AUTH_CHALLENGE_SIZE], const uint8_t expected_hmac[HMAC_SHA256_SIZE])
#define NOTICE(severity, title,...)
Definition notice.h:68
Authentication failure packet structure.
Definition packet.h:822
uint8_t reason_flags
Bitmask of auth_failure_reason_t values indicating failure causes.
Definition packet.h:824

References ASCII_OTEL_SCOPE, ASCIICHAT_OK, AUTH_FAIL_CLIENT_KEY_REJECTED, AUTH_FAIL_CLIENT_KEY_REQUIRED, AUTH_FAIL_PASSWORD_INCORRECT, AUTH_FAIL_PASSWORD_REQUIRED, AUTH_FAIL_SIGNATURE_INVALID, crypto_handshake_context_t::client_challenge_nonce, crypto_handshake_context_t::crypto_ctx, CRYPTO_HANDSHAKE_AUTHENTICATING, CRYPTO_HANDSHAKE_KEY_EXCHANGE, CRYPTO_HANDSHAKE_READY, crypto_verify_auth_response(), crypto_handshake_context_t::defer_password_prompt, ERROR_CRYPTO_AUTH, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_NETWORK_PROTOCOL, crypto_context_t::handshake_complete, crypto_context_t::hmac_size, log_info, NOTICE, PACKET_TYPE_CRYPTO_AUTH_FAILED, PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE, PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP, crypto_handshake_context_t::password_failure, auth_failure_packet_t::reason_flags, SET_ERRNO, and crypto_handshake_context_t::state.

Referenced by acds_handshake_receive(), acip_security_receive(), and client_handle_handshake_complete().

◆ crypto_handshake_client_key_exchange()

asciichat_error_t crypto_handshake_client_key_exchange ( crypto_handshake_context_t * ctx,
acip_transport_t * transport,
packet_type_t packet_type,
const uint8_t * payload,
size_t payload_len )

Client: Process server's public key and send our public key.

Parameters
ctxHandshake context (must be in CRYPTO_HANDSHAKE_INIT state)
transportACIP transport to send on
packet_typePacket type received (should be PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT)
payloadReceived packet payload
payload_lenLength of received payload
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
Packet must be received by ACIP handler before calling this function
State transition: CRYPTO_HANDSHAKE_INIT -> CRYPTO_HANDSHAKE_KEY_EXCHANGE

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

49 {
50 ASCII_OTEL_SCOPE(CRYPTO_HANDSHAKE_CLIENT_KEY_EXCHANGE);
51 if (!ctx || ctx->state != CRYPTO_HANDSHAKE_INIT) {
52 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid state: ctx=%p, state=%d", (void *)ctx, ctx ? (int)ctx->state : -1);
53 }
54 if (!transport) {
55 return SET_ERRNO(ERROR_INVALID_PARAM, "transport is NULL");
56 }
57
58 // Note: Packet already received by ACIP handler
59 int result;
60
61 // Verify packet type
62 if (packet_type != PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT) {
63 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Expected KEY_EXCHANGE_INIT, got packet type %d", packet_type);
64 }
65
66 log_debug("CLIENT_KEY_EXCHANGE: Received packet with payload_len=%zu, kex_size=%u, auth_size=%u, sig_size=%u",
69
70 // Check payload size - only authenticated format supported
71 // Authenticated: public_key_size + auth_public_key_size + signature_size bytes
72 size_t expected_auth_size =
74
75 // Use the crypto context's public key size to ensure compatibility.
76 size_t key_size = sizeof(ctx->crypto_ctx.public_key);
77 SAFE_MALLOC_DEFER_FREE(server_ephemeral_key, key_size, uint8_t *);
78 if (!server_ephemeral_key) {
79 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for server ephemeral key");
80 }
83
84 if (!server_identity_key || !server_signature) {
85 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for server identity key or signature");
86 }
87
88 // Validate packet size using session parameters
89 asciichat_error_t validation_result =
91 if (validation_result != ASCIICHAT_OK) {
92 return validation_result;
93 }
94
95 // Check if server is using authenticated format (includes signature)
96 // Must have signature_size > 0 to distinguish from simple ephemeral-only format
97 // This covers both cases:
98 // 1. With identity key: ephemeral + identity + signature
99 // 2. Without identity key but with signature: ephemeral + 0 + signature
100 if (ctx->crypto_ctx.signature_size > 0 && payload_len == expected_auth_size) {
101 // Authenticated format:
102 // [ephemeral:public_key_size][identity:auth_public_key_size][signature:signature_size]
103 log_debug("Received authenticated KEY_EXCHANGE_INIT (%zu bytes)", expected_auth_size);
104 memcpy(server_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
105 memcpy(server_identity_key, payload + ctx->crypto_ctx.public_key_size, ctx->crypto_ctx.auth_public_key_size);
106 memcpy(server_signature, payload + ctx->crypto_ctx.public_key_size + ctx->crypto_ctx.auth_public_key_size,
108
109 // Server is using client authentication
110 ctx->server_uses_client_auth = true;
111
112 // DEBUG: Print identity key received
113 char hex_id[HEX_STRING_SIZE_32];
114 for (int i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
115 safe_snprintf(hex_id + i * 2, 3, "%02x", server_identity_key[i]);
116 }
117 hex_id[HEX_STRING_SIZE_32 - 1] = '\0';
118 log_debug("Received identity key: %s", hex_id);
119
120 // DEBUG: Print ephemeral key and signature
121 char hex_eph[HEX_STRING_SIZE_32];
122 for (int i = 0; i < ED25519_PUBLIC_KEY_SIZE; i++) {
123 safe_snprintf(hex_eph + i * 2, 3, "%02x", server_ephemeral_key[i]);
124 }
125 hex_eph[HEX_STRING_SIZE_32 - 1] = '\0';
126 log_debug("Received ephemeral key: %s", hex_eph);
127
128 char hex_sig[HEX_STRING_SIZE_64];
129 for (int i = 0; i < ED25519_SIGNATURE_SIZE; i++) {
130 safe_snprintf(hex_sig + i * 2, 3, "%02x", server_signature[i]);
131 }
132 hex_sig[HEX_STRING_SIZE_64 - 1] = '\0';
133 log_debug("Received signature: %s", hex_sig);
134
135 // Verify signature: server identity signed the ephemeral key
136 log_debug("Verifying server's signature over ephemeral key (already logged above)");
137
138 // If client didn't specify --server-key, skip signature verification
139 // (client doesn't care about server identity verification)
140 if (!ctx->verify_server_key) {
141 log_info("Skipping server signature verification (no --server-key specified)");
142 NOTICE(DANGER, "UNVERIFIED SERVER IDENTITY",
143 "Connection is encrypted but server identity is NOT verified (vulnerable to MITM)");
144 } else {
145 // Extract GPG key ID from expected_server_key if it's a GPG key (gpg:KEYID format)
146 const char *gpg_key_id = NULL;
147 if (ctx->expected_server_key[0] != '\0' && strncmp(ctx->expected_server_key, "gpg:", 4) == 0) {
148 const char *key_id_start = ctx->expected_server_key + 4;
149 size_t key_id_len = strlen(key_id_start);
150 // Accept 8, 16, or 40 character GPG key IDs (short, long, or full fingerprint)
151 if (key_id_len == 8 || key_id_len == 16 || key_id_len == 40) {
152 gpg_key_id = key_id_start;
153 log_debug("Using GPG key ID from --server-key for verification: %s", gpg_key_id);
154 }
155 }
156
157 if (ed25519_verify_signature(server_identity_key, server_ephemeral_key, ctx->crypto_ctx.public_key_size,
158 server_signature, gpg_key_id) != 0) {
159 NOTICE(DANGER, "SERVER SIGNATURE INVALID",
160 "The server identity signature could not be verified.\n"
161 "Possible man-in-the-middle attack. Connection rejected.");
162 return SET_ERRNO(ERROR_CRYPTO, "Server signature verification FAILED - rejecting connection. "
163 "This indicates: Server's identity key does not "
164 "match its ephemeral key, Potential man-in-the-middle attack, "
165 "Corrupted or malicious server");
166 }
167 log_debug("Server signature verified successfully");
168 }
169
170 // Verify server identity against a browser-provided raw key before falling
171 // back to the native file, URL, and key-list parser.
173 if (sodium_memcmp(server_identity_key, ctx->expected_server_key_bytes, ED25519_PUBLIC_KEY_SIZE) != 0) {
174 NOTICE(DANGER, "SERVER IDENTITY KEY MISMATCH",
175 "The server key does not match the browser verification key.\nConnection rejected.");
177 "Server identity key does not match the active browser verification key");
178 }
179 log_info("Server identity key verified against active browser key");
180 } else if (ctx->verify_server_key && strlen(ctx->expected_server_key) > 0) {
181 // Parse ALL expected server keys (github:/gitlab: may have multiple keys)
182 public_key_t expected_keys[MAX_CLIENTS];
183 size_t num_expected_keys = 0;
184 if (parse_public_keys(ctx->expected_server_key, expected_keys, &num_expected_keys, MAX_CLIENTS) != 0 ||
185 num_expected_keys == 0) {
186 return SET_ERRNO(ERROR_CONFIG,
187 "Failed to parse expected server key: %s. Check that "
188 "--server-key value is valid (ssh-ed25519 "
189 "format, github:username, or hex)",
191 }
192
193 // Compare server's IDENTITY key against ALL expected keys (match any one)
194 // This supports users with multiple SSH keys (e.g., different machines)
195 bool key_matched = false;
196 for (size_t i = 0; i < num_expected_keys; i++) {
197 if (sodium_memcmp(server_identity_key, expected_keys[i].key, ED25519_PUBLIC_KEY_SIZE) == 0) {
198 key_matched = true;
199 log_debug("Server identity key matched expected key %zu/%zu", i + 1, num_expected_keys);
200 break;
201 }
202 }
203
204 if (!key_matched) {
205 NOTICE(DANGER, "SERVER IDENTITY KEY MISMATCH",
206 "The server key does not match --server-key: %s.\n"
207 "Possible man-in-the-middle attack. Do not connect until the server identity is verified.",
209 return SET_ERRNO(ERROR_CRYPTO,
210 "Server identity key mismatch - potential MITM attack! "
211 "Expected key(s) from: %s (checked %zu keys), Server presented a different key "
212 "than specified with --server-key, DO NOT CONNECT to this "
213 "server - likely man-in-the-middle attack!",
214 ctx->expected_server_key, num_expected_keys);
215 }
216 log_info("Server identity key verified against --server-key (%zu key(s) checked)", num_expected_keys);
217 }
218
219 // Note: Server IP resolution now handled by caller (TCP transport layer)
220 // For WebSocket/WebRTC transports, server_ip should be set by the transport
221 // before handshake begins. TCP clients will use the legacy wrapper which
222 // handles this.
223 if (ctx->server_ip[0] == '\0') {
224 log_debug("Server IP not set - skipping known_hosts verification (non-TCP transport)");
225 } else {
226 log_debug("Server IP already set: %s", ctx->server_ip);
227 }
228
229 // Check known_hosts for this server (if we have server IP and port)
230 // Check if known_hosts verification should be skipped
231 bool skip_known_hosts = false;
232 const char *env_skip = platform_getenv("ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK");
233 if (env_skip && strcmp(env_skip, STR_ONE) == 0) {
234 log_warn(
235 "Skipping known_hosts checking for authenticated connection (ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK=1)");
236 skip_known_hosts = true;
237 }
238#ifndef NDEBUG
239 // In debug builds, also skip for Claude Code (LLM automation can't do interactive prompts)
240 else if (platform_getenv("CLAUDECODE")) {
241 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build)");
242 skip_known_hosts = true;
243 }
244#endif
245
246 if (!ctx->addressless_transport && !skip_known_hosts && ctx->server_ip[0] != '\0' && ctx->server_port > 0) {
248 bool has_stored_key = false;
249 asciichat_error_t known_host_result =
250 check_known_host_with_key(ctx->server_ip, ctx->server_port, server_identity_key, stored_key, &has_stored_key);
251 if (known_host_result == ERROR_CRYPTO_VERIFICATION) {
252 // Key mismatch - MITM attack detected! Prompt user for confirmation
253 log_error("SECURITY: Server key does NOT match known_hosts entry!\n"
254 "This indicates a possible man-in-the-middle attack!");
255 if (!display_mitm_warning(ctx->server_ip, ctx->server_port, has_stored_key ? stored_key : NULL,
256 server_identity_key)) {
257 // User declined to continue - ABORT connection for security
258
260 "SECURITY: Connection aborted - server key mismatch (possible MITM attack)");
261 }
262 // User accepted the risk - continue with connection
263 NOTICE(DANGER, "HOST KEY CHANGE ACCEPTED",
264 "SECURITY WARNING: User accepted MITM risk - continuing with connection");
265 } else if (known_host_result == ASCIICHAT_OK) {
266 // Unknown host (first connection) - prompt user to verify fingerprint
267 if (!prompt_unknown_host(ctx->server_ip, ctx->server_port, server_identity_key)) {
268 // User declined to add host - ABORT connection
269
270 return SET_ERRNO(ERROR_CRYPTO, "User declined to verify unknown host");
271 }
272
273 // User accepted - add to known_hosts
274 if (add_known_host(ctx->server_ip, ctx->server_port, server_identity_key) != ASCIICHAT_OK) {
275 return SET_ERRNO(ERROR_CONFIG,
276 "CRITICAL SECURITY ERROR: Failed to create known_hosts "
277 "file! This is a security vulnerability - the "
278 "program cannot track known hosts. Please check file "
279 "permissions and ensure the program can write to: %s",
281 }
282 log_debug("Server host added to known_hosts successfully");
283 } else if (known_host_result == 1) {
284 // Key matches - connection is secure!
285 log_info("Server host key verified from known_hosts - connection secure");
286 } else {
287 // Unexpected error code from check_known_host
288
289 return SET_ERRNO(known_host_result, "SECURITY: known_hosts verification failed with error code %d",
290 known_host_result);
291 }
292 }
293 public_key_t identity = {.type = KEY_TYPE_ED25519};
294 memcpy(identity.key, server_identity_key, sizeof(identity.key));
296 ctx->verify_server_key ? "VERIFIED SERVER PUBLIC IDENTITY" : "UNVERIFIED SERVER PUBLIC IDENTITY", &identity);
297 } else if (payload_len == ctx->crypto_ctx.public_key_size) {
298 if (ctx->verify_server_key) {
299 return SET_ERRNO(ERROR_CRYPTO_AUTH, "Server omitted the required identity key");
300 }
301 // Simple format: just ephemeral key (no identity key)
302 log_debug("Received simple KEY_EXCHANGE_INIT (%zu bytes) - server has no "
303 "identity key",
304 payload_len);
305 memcpy(server_ephemeral_key, payload, ctx->crypto_ctx.public_key_size);
306
307 // Clear identity key and signature for simple format
308 memset(server_identity_key, 0, ctx->crypto_ctx.auth_public_key_size);
309 memset(server_signature, 0, ctx->crypto_ctx.signature_size);
310
311 // Server is not using client authentication in simple mode
312 ctx->server_uses_client_auth = false;
313
314 log_debug("Received ephemeral key (simple format)");
315
316 if (!ctx->addressless_transport) {
317 // SECURITY: For servers without identity keys, we implement a different security model:
318 // 1. Verify IP address matches known_hosts entry
319 // 2. Always require user confirmation (no silent connections)
320 // 3. Store server fingerprint for future verification
321
322 if (ctx->server_ip[0] == '\0' || ctx->server_port <= 0) {
323 return SET_ERRNO(ERROR_CRYPTO, "Server IP or port not set, cannot check known_hosts");
324 }
325
326 // Check if this server was previously connected to (IP verification)
327 bool skip_known_hosts = false;
328 asciichat_error_t known_host_result = ASCIICHAT_OK;
329 const char *env_skip_known_hosts_checking = platform_getenv("ASCII_CHAT_INSECURE_NO_HOST_IDENTITY_CHECK");
330 if (env_skip_known_hosts_checking && strcmp(env_skip_known_hosts_checking, STR_ONE) == 0) {
331 log_warn("Skipping known_hosts checking. This is a security vulnerability.");
332 skip_known_hosts = true;
333 }
334#ifndef NDEBUG
335 // In debug builds, also skip for Claude Code (LLM automation can't do interactive prompts)
336 else if (platform_getenv("CLAUDECODE")) {
337 log_warn("Skipping known_hosts checking (CLAUDECODE set in debug build).");
338 skip_known_hosts = true;
339 }
340#endif
341 else {
342 known_host_result = check_known_host_no_identity(ctx->server_ip, ctx->server_port);
343 }
344
345 if (skip_known_hosts || known_host_result == 1) {
346 // Server IP is known and verified - allow connection without warnings
347 log_info("SECURITY: Server IP %s:%u is known (no-identity entry found) - connection verified", ctx->server_ip,
348 ctx->server_port);
349 } else if (known_host_result == ASCIICHAT_OK) {
350 // Server IP is unknown - require user confirmation
351 NOTICE(DANGER, "UNVERIFIED SERVER IDENTITY",
352 "SECURITY: Unknown server IP %s:%u with no identity key\n"
353 "This connection is vulnerable to man-in-the-middle attacks\n"
354 "Anyone can intercept your connection and read your data",
355 ctx->server_ip, ctx->server_port);
356
358 return SET_ERRNO(ERROR_CRYPTO, "User declined to connect to unknown server without identity key");
359 }
360
361 // User accepted - add to known_hosts as no-identity entry
362 // For servers without identity keys, pass zero key to indicate no-identity
363 uint8_t zero_key[ZERO_KEY_SIZE] = {0};
364 if (add_known_host(ctx->server_ip, ctx->server_port, zero_key) != ASCIICHAT_OK) {
365 return SET_ERRNO(ERROR_CONFIG,
366 "CRITICAL SECURITY ERROR: Failed to create known_hosts "
367 "file! This is a security vulnerability - the "
368 "program cannot track known hosts. Please check file "
369 "permissions and ensure the program can write to: %s",
371 }
372 log_debug("Server host added to known_hosts successfully");
373 } else if (known_host_result == ERROR_CRYPTO_VERIFICATION) {
374 // Server previously had identity key but now has none - potential security issue
375 NOTICE(DANGER, "SERVER IDENTITY REMOVED",
376 "SECURITY: Server previously had identity key but now has none - potential security issue");
377
378 return SET_ERRNO(ERROR_CRYPTO_VERIFICATION, "Server key configuration changed - potential security issue");
379 } else {
380 // Other error checking known_hosts (e.g., ERROR_INVALID_PARAM)
381
382 return SET_ERRNO(ERROR_CRYPTO, "Failed to verify server IP address");
383 }
384 }
385 } else {
387 "Invalid KEY_EXCHANGE_INIT size: %zu bytes (expected %zu or "
388 "%zu). This indicates: Protocol violation "
389 "or incompatible server version, Potential man-in-the-middle "
390 "attack, Network corruption",
391 payload_len, expected_auth_size, ctx->crypto_ctx.public_key_size);
392 // retry
393 }
394
395 // Set peer's public key (EPHEMERAL X25519) - this also derives the shared secret
396 crypto_result_t crypto_result = crypto_set_peer_public_key(&ctx->crypto_ctx, server_ephemeral_key);
397 if (crypto_result != CRYPTO_OK) {
398 return SET_ERRNO(ERROR_CRYPTO, "Failed to set peer public key and derive shared secret: %s",
399 crypto_result_to_string(crypto_result));
400 }
401
402 // Determine if client has an identity key
403 bool client_has_identity_key = (ctx->client_private_key.type == KEY_TYPE_ED25519);
404
405 // Send authenticated response if server has identity key (auth_public_key_size > 0)
406 // OR if server requires client authentication (require_client_auth)
407 // Note: server_uses_client_auth is set when server has identity key, but we should
408 // send authenticated response when server has identity key regardless of client auth requirement
409 bool server_has_identity = (ctx->crypto_ctx.auth_public_key_size > 0 && ctx->crypto_ctx.signature_size > 0);
410 bool server_requires_auth = server_has_identity || ctx->require_client_auth;
411
412 if (server_requires_auth || client_has_identity_key) {
413 // Send authenticated packet:
414 // [ephemeral:kex_size][identity:auth_size][signature:sig_size][gpg_key_id_len:1][gpg_key_id:0-16]
415 // Use Ed25519 sizes since client has Ed25519 key
416 size_t ed25519_pubkey_size = ED25519_PUBLIC_KEY_SIZE; // Ed25519 public key is always 32 bytes
417 size_t ed25519_sig_size = ED25519_SIGNATURE_SIZE; // Ed25519 signature is always 64 bytes
418 size_t response_size = ctx->crypto_ctx.public_key_size + ed25519_pubkey_size + ed25519_sig_size;
419
420 // Check if client has a GPG key ID to send
421 uint8_t gpg_key_id_len = 0;
422 if (ctx->client_gpg_key_id[0] != '\0') {
423 gpg_key_id_len = (uint8_t)strlen(ctx->client_gpg_key_id);
424 if (gpg_key_id_len > 40) {
425 gpg_key_id_len = 40; // Truncate to max length (full fingerprint)
426 }
427 response_size += 1 + gpg_key_id_len; // 1 byte for length + key ID
428 } else {
429 response_size += 1; // Just the length byte (0)
430 }
431
432 SAFE_MALLOC_DEFER_FREE(key_response, response_size, uint8_t *);
433 defer(sodium_memzero(key_response, response_size));
434 size_t offset = 0;
435
436 // Copy ephemeral key
437 memcpy(key_response + offset, ctx->crypto_ctx.public_key,
438 ctx->crypto_ctx.public_key_size); // X25519 ephemeral for encryption
439 offset += ctx->crypto_ctx.public_key_size;
440
441 if (client_has_identity_key) {
442 // Client has identity key - send it with signature
443 memcpy(key_response + offset, ctx->client_private_key.public_key, ed25519_pubkey_size); // Ed25519 identity
444 offset += ed25519_pubkey_size;
445
446 // Sign ephemeral key with client identity key
448 key_response + offset) != 0) {
449 return SET_ERRNO(ERROR_CRYPTO, "Failed to sign client ephemeral key");
450 }
451 offset += ed25519_sig_size;
452 } else {
453 // Client has no identity key - send null identity and null signature
454 memset(key_response + offset, 0, ed25519_pubkey_size); // Null identity
455 offset += ed25519_pubkey_size;
456 memset(key_response + offset, 0, ed25519_sig_size); // Null signature
457 offset += ed25519_sig_size;
458 }
459
460 // Append GPG key ID length
461 key_response[offset] = gpg_key_id_len;
462 offset += 1;
463
464 // Append GPG key ID if present
465 if (gpg_key_id_len > 0) {
466 memcpy(key_response + offset, ctx->client_gpg_key_id, gpg_key_id_len);
467 offset += gpg_key_id_len;
468 log_debug("Including client GPG key ID in KEY_EXCHANGE_RESPONSE: %.*s", gpg_key_id_len, ctx->client_gpg_key_id);
469 }
470
471 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, key_response, response_size, 0);
472 if (result != 0) {
473 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_RESPONSE packet");
474 }
475
476 } else {
477 // Send X25519 encryption key only to server (no identity key)
478 // Format: [X25519 pubkey (kex_size)] = kex_size bytes total
481 if (result != 0) {
482 return SET_ERRNO(ERROR_NETWORK, "Failed to send KEY_EXCHANGE_RESPONSE packet");
483 }
484 }
485
487
488 return ASCIICHAT_OK;
489}
@ CRYPTO_HANDSHAKE_INIT
#define SAFE_MALLOC_DEFER_FREE(name, size, cast)
Definition common.h:386
const char * get_known_hosts_path(void)
Get the path to the known_hosts file.
Definition known_hosts.c:57
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).
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,...
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).
@ ERROR_CRYPTO_VERIFICATION
Definition error_codes.h:97
@ ERROR_MEMORY
Definition error_codes.h:51
@ ERROR_CONFIG
Definition error_codes.h:52
uint8_t key[CRYPTO_PUBLIC_KEY_SIZE]
Definition key_types.h:74
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:411
uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]
Definition key_types.h:105
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-128).
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT
Server -> Client: {server_pubkey[32]} (UNENCRYPTED).
Definition packet.h:326
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP
Client -> Server: {client_pubkey[32]} (UNENCRYPTED).
Definition packet.h:328
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
const char * platform_getenv(const char *name)
Get an environment variable value.
#define STR_ONE
String literal: "1" (one).
bool prompt_unknown_host(const char *server_ip, uint16_t port, const uint8_t server_key[ED25519_PUBLIC_KEY_SIZE])
bool display_mitm_warning(const char *server_ip, uint16_t port, const uint8_t expected_key[ED25519_PUBLIC_KEY_SIZE], const uint8_t received_key[ED25519_PUBLIC_KEY_SIZE])
asciichat_error_t check_known_host_with_key(const char *server_ip, uint16_t port, const uint8_t server_key[ED25519_PUBLIC_KEY_SIZE], uint8_t expected_key[ED25519_PUBLIC_KEY_SIZE], bool *has_expected_key)
Definition known_hosts.c:91
asciichat_error_t add_known_host(const char *server_ip, uint16_t port, const uint8_t server_key[ED25519_PUBLIC_KEY_SIZE])
void key_identity_announce(const char *label, const public_key_t *key)
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, uint32_t client_id)
Definition send.c:190
#define ED25519_SIGNATURE_SIZE
Ed25519 signature size in bytes.
Definition sizes.h:37
#define ED25519_PUBLIC_KEY_SIZE
Ed25519 public key size in bytes.
Definition sizes.h:33
#define HEX_STRING_SIZE_32
Hex string size for 32-byte values (64 hex chars + null terminator).
Definition sizes.h:154
#define ZERO_KEY_SIZE
Zero key array size (32 bytes, used for no-identity entries).
Definition sizes.h:160
#define HEX_STRING_SIZE_64
Hex string size for 64-byte values (128 hex chars + null terminator).
Definition sizes.h:156
asciichat_error_t ed25519_sign_message(const private_key_t *key, const uint8_t *message, size_t message_len, uint8_t signature[ED25519_SIGNATURE_SIZE])
Definition ssh_keys.c:1133
asciichat_error_t ed25519_verify_signature(const uint8_t public_key[ED25519_PUBLIC_KEY_SIZE], const uint8_t *message, size_t message_len, const uint8_t signature[ED25519_SIGNATURE_SIZE], const char *gpg_key_id)
Definition ssh_keys.c:1235
The normalized public identity used by ascii-chat's crypto and key-management code.
Definition key_types.h:72

References add_known_host(), crypto_handshake_context_t::addressless_transport, ASCII_OTEL_SCOPE, ASCIICHAT_OK, crypto_context_t::auth_public_key_size, check_known_host_no_identity(), check_known_host_with_key(), 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_AUTH, ERROR_CRYPTO_VERIFICATION, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_NETWORK, ERROR_NETWORK_PROTOCOL, crypto_handshake_context_t::expected_server_key, crypto_handshake_context_t::expected_server_key_bytes, crypto_handshake_context_t::expected_server_key_bytes_set, get_known_hosts_path(), HEX_STRING_SIZE_32, HEX_STRING_SIZE_64, public_key_t::key, key_identity_announce(), KEY_TYPE_ED25519, log_debug, log_error, log_info, log_warn, MAX_CLIENTS, NOTICE, packet_send_via_transport(), 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, crypto_handshake_context_t::require_client_auth, SAFE_MALLOC_DEFER_FREE, safe_snprintf(), 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 acds_handshake_receive(), acip_security_receive(), and client_handle_key_exchange_init().