From 62af60991cdf72c0023b5ab45e8670dfbdc7f5dd Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Sun, 12 Jul 2020 22:17:12 +0200 Subject: tomsg_clientlib: Proper hostkey checking --- ssh/client.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- ssh/ssh_client.c | 6 +++--- ssh/sshnc.h | 2 +- ssh/tomsg_clientlib.c | 17 ++++++++--------- ssh/tomsg_clientlib.h | 12 ++++++++++++ 5 files changed, 71 insertions(+), 16 deletions(-) diff --git a/ssh/client.c b/ssh/client.c index 64f8f57..80e14a4 100644 --- a/ssh/client.c +++ b/ssh/client.c @@ -83,6 +83,38 @@ static void splice_scanned(struct readbuffer *rb) { rb->newline_cursor = 0; } +static bool prompt_yn(struct readbuffer *rb, const char *text) { + printf("%s [y/n] ", text); + fflush(stdout); + + while (true) { + struct string_view line = extract_line(rb); + if (line.s == NULL) { + read_more_data(STDIN_FILENO, rb); + continue; + } + + splice_scanned(rb); + + if (line.len <= 3) { + char buf[4]; + memcpy(buf, line.s, line.len); + buf[line.len] = '\0'; + + if (strcmp(buf, "y") == 0 || strcmp(buf, "yes") == 0) { + return true; + } + + if (strcmp(buf, "n") == 0 || strcmp(buf, "no") == 0) { + return false; + } + } + + printf("Please answer with 'y', 'n', 'yes' or 'no'. [y/n] "); + fflush(stdout); + } +} + static struct string_view tokenise_greedy(struct string_view *line) { sv_skip_whitespace(line); return sv_tokenise_word(line); @@ -415,6 +447,17 @@ static void handle_event(struct state *state, const struct tomsg_event event) { } } +static bool hostkey_checker(const unsigned char *hash, size_t length, void *stdinbuf_) { + struct readbuffer *stdinbuf = stdinbuf_; + + const char *fingerprint = tomsg_print_hash(hash, length); + + printf("Server host key fingerprint: %s\n", fingerprint); + return prompt_yn(stdinbuf, + "Does this hash match the one given to you by the server administrator, or by a\n" + "member that you trust and is already connected to the server?"); +} + int main(int argc, char **argv) { if (argc != 2) { printf("Usage: %s \n", argv[0]); @@ -428,10 +471,13 @@ int main(int argc, char **argv) { return 1; } + struct readbuffer stdinbuf = readbuffer_new(); + printf("Connecting...\n"); struct tomsg_client *client; - enum tomsg_retval ret = tomsg_connect(hostname, port, &client); + enum tomsg_retval ret = tomsg_connect( + hostname, port, hostkey_checker, &stdinbuf, &client); if (ret != TOMSG_OK) { printf("Could not connect: %s\n", tomsg_strerror(ret)); return 1; @@ -439,8 +485,6 @@ int main(int argc, char **argv) { printf("Connected.\n"); - struct readbuffer stdinbuf = readbuffer_new(); - struct state state = (struct state){ .rooms = NULL, .num_rooms = 0, diff --git a/ssh/ssh_client.c b/ssh/ssh_client.c index 3e4a7c6..f09aaac 100644 --- a/ssh/ssh_client.c +++ b/ssh/ssh_client.c @@ -10,7 +10,7 @@ static bool prompt_yn(const char *text) { - printf("%s", text); + printf("%s [y/n] ", text); fflush(stdout); bool response; @@ -37,7 +37,7 @@ static bool prompt_yn(const char *text) { break; } - printf("Please answer with 'y', 'n', 'yes' or 'no'. [y/n]"); + printf("Please answer with 'y', 'n', 'yes' or 'no'. [y/n] "); fflush(stdout); } @@ -51,7 +51,7 @@ static bool hostkey_checker(const unsigned char *hash, size_t length, void *user bool response = prompt_yn( "Does this hash match the one given to you by the server administrator, or by a\n" - "member that you trust and is already connected to the server? [y/n] "); + "member that you trust and is already connected to the server?"); if (!response) { printf("Disconnecting.\n"); } diff --git a/ssh/sshnc.h b/ssh/sshnc.h index 1e82075..5ee742a 100644 --- a/ssh/sshnc.h +++ b/ssh/sshnc.h @@ -64,7 +64,7 @@ enum sshnc_retval sshnc_connect( const char *username, const char *subsystem, sshnc_hostkey_checker_t checker, - void *userdata, + void *userdata, // for checker struct sshnc_client **client // output ); diff --git a/ssh/tomsg_clientlib.c b/ssh/tomsg_clientlib.c index 9898b66..80a9bf3 100644 --- a/ssh/tomsg_clientlib.c +++ b/ssh/tomsg_clientlib.c @@ -34,13 +34,6 @@ struct tomsg_client { static size_t min_size_t(size_t a, size_t b) { return a < b ? a : b; } -static bool hostkey_checker(const unsigned char *hash, size_t length, void *userdata) { - (void)userdata; - static const char *preload = "SHA256:ppz/McaESpOQy0O3kbaIi1LPZ37/YtrdC+y9102Y0+I"; - const char *fingerprint = sshnc_print_hash(hash, length); - return strcmp(fingerprint, preload) == 0; -} - static bool hasspacelf(const char *string) { for (size_t i = 0; string[i]; i++) if (string[i] == ' ' || string[i] == '\n') return true; @@ -139,6 +132,10 @@ static void splice_scanned_buffer_part(struct tomsg_client *client) { client->buffer_newline_cursor = 0; } +const char* tomsg_print_hash(const unsigned char *hash, size_t length) { + return sshnc_print_hash(hash, length); +} + const char* tomsg_strerror(enum tomsg_retval code) { switch (code) { case TOMSG_OK: return "Success"; @@ -190,13 +187,15 @@ static enum tomsg_retval version_negotiation(struct tomsg_client *client) { } enum tomsg_retval tomsg_connect( - const char *hostname, int port, struct tomsg_client **clientp) { + const char *hostname, int port, + tomsg_hostkey_checker_t checker, void *userdata, + struct tomsg_client **clientp) { // In case we throw an error along the way *clientp = NULL; struct sshnc_client *conn; enum sshnc_retval ret = sshnc_connect( - hostname, port, "tomsg", "tomsg", hostkey_checker, NULL, &conn); + hostname, port, "tomsg", "tomsg", checker, userdata, &conn); if (ret == SSHNC_ERR_CONNECT) return TOMSG_ERR_CONNECT; if (ret != SSHNC_OK) return TOMSG_ERR_TRANSPORT; diff --git a/ssh/tomsg_clientlib.h b/ssh/tomsg_clientlib.h index 58ad4d4..03f1fcf 100644 --- a/ssh/tomsg_clientlib.h +++ b/ssh/tomsg_clientlib.h @@ -30,6 +30,16 @@ enum tomsg_retval { TOMSG_ERR_MEMORY, // Error allocating memory }; +// Should return 'true' if the key is trusted, 'false' otherwise. The hash is +// sha256 in byte form, not yet encoded in hexadecimal or similar. The +// 'userdata' pointer comes from the 'tomsg_connect' invocation. +typedef bool (*tomsg_hostkey_checker_t)( + const unsigned char *hash, size_t length, void *userdata); + +// Convenience function to convert a hash to a human-readable form. Returns a +// reference to an internal static buffer. +const char* tomsg_print_hash(const unsigned char *hash, size_t length); + // Returns reference to internal static buffer. const char* tomsg_strerror(enum tomsg_retval code); @@ -37,6 +47,8 @@ const char* tomsg_strerror(enum tomsg_retval code); // TOMSG_OK. On error, stores NULL in 'client' and returns an error code. enum tomsg_retval tomsg_connect( const char *hostname, int port, + tomsg_hostkey_checker_t checker, + void *userdata, // for checker struct tomsg_client **client // output ); -- cgit v1.2.3-54-g00ecf