#include "ssh_client.h" #include "efuse_ecdsa.h" #include "sdkconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *TAG = "ssh_client"; /* ------------------------------------------------------------------------- * SSH blob / key helpers * ---------------------------------------------------------------------- */ /* Write a big-endian uint32 into buf. */ static void write_be32(uint8_t *buf, uint32_t val) { buf[0] = (val >> 24) & 0xFF; buf[1] = (val >> 16) & 0xFF; buf[2] = (val >> 8) & 0xFF; buf[3] = val & 0xFF; } /** * Format one ECDSA integer as an SSH mpint: * 4-byte big-endian length | optional 0x00 pad | value (leading zeros stripped) * * Returns number of bytes written to buf. */ static uint32_t write_mpint(uint8_t *buf, const uint8_t *val, uint32_t size) { uint32_t start = 0; while (start < (size - 1) && val[start] == 0x00) { start++; } bool pad = (val[start] & 0x80) != 0; uint32_t len = (size - start) + (pad ? 1 : 0); uint32_t off = 0; buf[off++] = (len >> 24) & 0xFF; buf[off++] = (len >> 16) & 0xFF; buf[off++] = (len >> 8) & 0xFF; buf[off++] = len & 0xFF; if (pad) { buf[off++] = 0x00; } memcpy(&buf[off], &val[start], size - start); return off + (size - start); } /** * Build a 104-byte SSH public key blob for ecdsa-sha2-nistp256: * [uint32 19] "ecdsa-sha2-nistp256" * [uint32 8] "nistp256" * [uint32 65] 0x04 || X(32) || Y(32) * * @param pub_x 32-byte X coordinate (big-endian). * @param pub_y 32-byte Y coordinate (big-endian). * @param out_blob Must point to a buffer of at least 104 bytes. */ static void build_pubkey_blob(const uint8_t *pub_x, const uint8_t *pub_y, uint8_t *out_blob) { uint32_t off = 0; /* Key type */ const char *key_type = "ecdsa-sha2-nistp256"; write_be32(&out_blob[off], 19); off += 4; memcpy(&out_blob[off], key_type, 19); off += 19; /* Curve name */ const char *curve = "nistp256"; write_be32(&out_blob[off], 8); off += 4; memcpy(&out_blob[off], curve, 8); off += 8; /* Uncompressed EC point: 0x04 || X || Y */ write_be32(&out_blob[off], 65); off += 4; out_blob[off++] = 0x04; memcpy(&out_blob[off], pub_x, 32); off += 32; memcpy(&out_blob[off], pub_y, 32); /* off += 32 — total = 104 */ } /* ------------------------------------------------------------------------- * libssh2 signing callback (eFuse ECDSA signs the hash) * ---------------------------------------------------------------------- */ static int sign_callback(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, const unsigned char *data, size_t data_len, void **abstract) { (void)session; (void)abstract; uint8_t digest[32]; uint8_t r_raw[32], s_raw[32]; /* Hash the challenge data */ mbedtls_sha256(data, data_len, digest, 0); /* Sign with the eFuse hardware ECDSA key */ if (!efuse_ecdsa_sign(digest, r_raw, s_raw)) { ESP_LOGE(TAG, "eFuse ECDSA signing failed!"); return -1; } /* Encode as [mpint R][mpint S] (no outer string wrapper) */ unsigned char *buf = (unsigned char *)malloc(80); if (!buf) return -1; uint32_t off = 0; off += write_mpint(&buf[off], r_raw, 32); /* R */ off += write_mpint(&buf[off], s_raw, 32); /* S */ *sig = buf; *sig_len = off; return 0; } /* ------------------------------------------------------------------------- * Public API * ---------------------------------------------------------------------- */ void ssh_print_public_key(void) { uint8_t pub_x[32], pub_y[32]; if (!efuse_ecdsa_get_pubkey(pub_x, pub_y)) { ESP_LOGE(TAG, "Failed to export public key from eFuse"); return; } uint8_t blob[104]; build_pubkey_blob(pub_x, pub_y, blob); size_t b64_len = 0; mbedtls_base64_encode(NULL, 0, &b64_len, blob, sizeof(blob)); unsigned char *b64 = (unsigned char *)malloc(b64_len + 1); if (!b64) { ESP_LOGE(TAG, "malloc failed for base64 buffer"); return; } mbedtls_base64_encode(b64, b64_len, &b64_len, blob, sizeof(blob)); b64[b64_len] = '\0'; printf("\n=== SSH Public Key (add to authorized_keys) ===\n"); printf("ecdsa-sha2-nistp256 %s keypitecc\n", b64); printf("================================================\n\n"); free(b64); } bool ssh_execute_command(const char *cmd) { if (!cmd) return false; /* --- Read public key blob ------------------------------------------ */ uint8_t pub_x[32], pub_y[32]; if (!efuse_ecdsa_get_pubkey(pub_x, pub_y)) { ESP_LOGE(TAG, "Failed to export public key from eFuse"); return false; } uint8_t pubkey_blob[104]; build_pubkey_blob(pub_x, pub_y, pubkey_blob); /* --- TCP connect ------------------------------------------------------- */ int rc; int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { ESP_LOGE(TAG, "socket() failed"); return false; } struct sockaddr_in sin = {}; sin.sin_family = AF_INET; sin.sin_port = htons(CONFIG_SSH_PORT); sin.sin_addr.s_addr = inet_addr(CONFIG_SSH_HOSTNAME); if (sin.sin_addr.s_addr == 0xFFFFFFFF) { struct hostent *hp = gethostbyname(CONFIG_SSH_HOSTNAME); if (!hp) { ESP_LOGE(TAG, "gethostbyname(%s) failed", CONFIG_SSH_HOSTNAME); close(sock); return false; } sin.sin_addr.s_addr = ((struct ip4_addr *)hp->h_addr)->addr; } if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) != 0) { ESP_LOGE(TAG, "connect to %s:%d failed", CONFIG_SSH_HOSTNAME, CONFIG_SSH_PORT); close(sock); return false; } ESP_LOGI(TAG, "TCP connected to %s:%d", CONFIG_SSH_HOSTNAME, CONFIG_SSH_PORT); /* --- libssh2 session -------------------------------------------------- */ rc = libssh2_init(0); if (rc) { ESP_LOGE(TAG, "libssh2_init failed: %d", rc); close(sock); return false; } LIBSSH2_SESSION *session = libssh2_session_init(); if (!session) { ESP_LOGE(TAG, "libssh2_session_init failed"); libssh2_exit(); close(sock); return false; } rc = libssh2_session_handshake(session, sock); if (rc) { ESP_LOGE(TAG, "SSH handshake failed: %d", rc); goto cleanup_session; } ESP_LOGI(TAG, "SSH handshake OK"); /* --- Authenticate with eFuse ECDSA hardware key ---------------------- */ void *abstract = NULL; rc = libssh2_userauth_publickey(session, CONFIG_SSH_USERNAME, pubkey_blob, sizeof(pubkey_blob), sign_callback, &abstract); if (rc != 0) { ESP_LOGE(TAG, "SSH public-key authentication failed: %d", rc); goto cleanup_session; } ESP_LOGI(TAG, "SSH authenticated as '%s'", CONFIG_SSH_USERNAME); /* --- Open channel and execute command --------------------------------- */ LIBSSH2_CHANNEL *channel = libssh2_channel_open_session(session); if (!channel) { ESP_LOGE(TAG, "Failed to open SSH channel"); goto cleanup_session; } ESP_LOGI(TAG, "Executing: %s", cmd); rc = libssh2_channel_exec(channel, cmd); if (rc != 0) { ESP_LOGE(TAG, "channel_exec failed: %d", rc); libssh2_channel_free(channel); goto cleanup_session; } /* --- Read output ------------------------------------------------------- */ char buf[256]; int bytes; ESP_LOGI(TAG, "--- command output ---"); while ((bytes = libssh2_channel_read(channel, buf, sizeof(buf) - 1)) > 0) { buf[bytes] = '\0'; printf("%s", buf); } ESP_LOGI(TAG, "--- end output ---"); /* --- Shutdown --------------------------------------------------------- */ libssh2_channel_close(channel); int exit_status = libssh2_channel_get_exit_status(channel); ESP_LOGI(TAG, "Command exited with status: %d", exit_status); libssh2_channel_free(channel); libssh2_session_disconnect(session, "Bye"); libssh2_session_free(session); libssh2_exit(); close(sock); return (exit_status == 0); cleanup_session: libssh2_session_disconnect(session, "Error"); libssh2_session_free(session); libssh2_exit(); close(sock); return false; }