From fbb6b73744494d8a908d9cfff2e4dbf882bf5ccf Mon Sep 17 00:00:00 2001 From: Pekka Riikonen Date: Sat, 26 May 2001 13:55:13 +0000 Subject: [PATCH] updates. --- CHANGES | 47 +++ TODO | 6 +- apps/irssi/src/silc/core/silc-channels.c | 2 +- apps/irssi/src/silc/core/silc-core.c | 259 +++++++++++++++-- apps/irssi/src/silc/core/silc-servers.c | 2 + apps/silc/client_ops.c | 350 +++++++++++----------- apps/silc/client_ops.h | 13 +- apps/silcd/protocol.c | 76 ++++- lib/silcclient/command.c | 102 ++++--- lib/silcclient/protocol.c | 229 +++++++++++---- lib/silcclient/protocol.h | 12 +- lib/silcclient/silcapi.h | 35 ++- lib/silcske/silcske.c | 354 +++++++++++++++++------ lib/silcske/silcske.h | 34 ++- lib/silcske/silcske_status.h | 2 + 15 files changed, 1107 insertions(+), 416 deletions(-) diff --git a/CHANGES b/CHANGES index be2ab6e5..df4adf93 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,50 @@ +Sat May 26 12:13:37 EEST 2001 Pekka Riikonen + + * Changed the ask_passphrase client operation to be ascynchronous. + It has now a completion callback and a context that the + application must call after it has got the passphrase from + the user. Affected files lib/silcclient/silcapi.h, + lib/silcclient/protocol.c, lib/silcclient/command.c and + silc/client_ops.c. + + Added SilcAskPassphrase callback that the application calls + to deliver the passphrase to the library. + + * Changed the SKE protocol's SilcSKEVerifyCb to be asynchronous. + The public key verification and especially a certificate + verification is asynchronous procedure. + + Added new SILC_SKE_STATUS_PENDING status to indicate the + request is pending and a callback will be called to finalize + the request. + + Added also SILC_SKE_STATUS_PUBLIC_KEY_NOT_PROVIDED status to + indicate that remote end did not send its public key (or + certificate), even though we require it. Added check for this + condition in the SKE. This was a security bug, now fixed. + + Defined new SilcSKEVerifyCbCompletion callback that is called + when the verification process is completed. + + The affected files lib/silcske/silcske_status.h and + lib/silcske/silcske.[ch]. + + * Changed the verify_public_key client operation to be async + as well. Defined SilcVerifyPublicKey callback that is used to + indicate the success of the public key verification process. + + Changed the server and client to use the new async client + operations. + + * Changed the Irssi SILC client's internal scheduler to be called + twice as many times as it used to be. As a result the client + should be a bit faster now. Affected file is + irssi/src/silc/core/silc-core.c. + + * Added support to Irssi SILC client of asynchronous public key + verification and passphrase inquiry. Affected file is + irssi/src/silc/core/silc-core.c. + Fri May 25 14:38:38 EEST 2001 Pekka Riikonen * Do not say "You have left channel %s" in client library. diff --git a/TODO b/TODO index b695701e..04e9e8d0 100644 --- a/TODO +++ b/TODO @@ -19,9 +19,9 @@ TODO/bugs In SILC Client Library TODO/bugs In SILC Server ======================== - o Save channel names in the ID Cache always as lowered characters, - though allow mixed case characters in the channel entry but the ID - cache does not handle loose data searching anymore. + o The SKE protocol in the server does not verify the remote hosts + (the router's) public key at all. All public keys are accepted without + verification - this obviously is not secure. o When server quits and all clients of that server are removed from all channels the channel keys are re-generated for all clients. This is diff --git a/apps/irssi/src/silc/core/silc-channels.c b/apps/irssi/src/silc/core/silc-channels.c index c00ec258..940d8712 100644 --- a/apps/irssi/src/silc/core/silc-channels.c +++ b/apps/irssi/src/silc/core/silc-channels.c @@ -337,7 +337,7 @@ static void event_cumode(SILC_SERVER_REC *server, va_list va) printtext(server, channel->channel_name, MSGLEVEL_MODES, "cumode/%s/%s [%s] by %s", destclient->nickname, - channel->channel_name, mode, client->nickname); + channel->channel_name, modestr, client->nickname); g_free(modestr); } diff --git a/apps/irssi/src/silc/core/silc-core.c b/apps/irssi/src/silc/core/silc-core.c index fed647f5..0ef7af68 100644 --- a/apps/irssi/src/silc/core/silc-core.c +++ b/apps/irssi/src/silc/core/silc-core.c @@ -37,6 +37,7 @@ #include "settings.h" #include "fe-common/core/printtext.h" #include "fe-common/core/fe-channels.h" +#include "fe-common/core/keyboard.h" /* Command line option variables */ static bool opt_create_keypair = FALSE; @@ -81,14 +82,18 @@ static void silc_command_reply(SilcClient client, SilcClientConnection conn, SilcCommandPayload cmd_payload, int success, SilcCommand command, SilcCommandStatus status, ...); - -static int silc_verify_public_key(SilcClient client, - SilcClientConnection conn, - SilcSocketType conn_type, - unsigned char *pk, uint32 pk_len, - SilcSKEPKType pk_type); -static unsigned char *silc_ask_passphrase(SilcClient client, - SilcClientConnection conn); +static void +silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context); +static void +silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context); +static void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context); static int silc_get_auth_method(SilcClient client, SilcClientConnection conn, char *hostname, uint16 port, @@ -788,8 +793,9 @@ silc_command_reply(SilcClient client, SilcClientConnection conn, pk = silc_pkcs_public_key_encode(public_key, &pk_len); if (id_type == SILC_ID_CLIENT) { - silc_verify_public_key(client, conn, SILC_SOCKET_TYPE_CLIENT, - pk, pk_len, SILC_SKE_PK_TYPE_SILC); + silc_verify_public_key_internal(client, conn, SILC_SOCKET_TYPE_CLIENT, + pk, pk_len, SILC_SKE_PK_TYPE_SILC, + NULL, NULL); } silc_free(pk); @@ -819,25 +825,234 @@ silc_command_reply(SilcClient client, SilcClientConnection conn, va_end(vp); } -/* Verifies received public key. If user decides to trust the key it is - saved as public server key for later use. If user does not trust the - key this returns FALSE. */ +/* Internal routine to verify public key. If the `completion' is provided + it will be called to indicate whether public was verified or not. */ -static int silc_verify_public_key(SilcClient client, - SilcClientConnection conn, - SilcSocketType conn_type, - unsigned char *pk, uint32 pk_len, - SilcSKEPKType pk_type) +typedef struct { + SilcClient client; + SilcClientConnection conn; + char *filename; + char *entity; + unsigned char *pk; + uint32 pk_len; + SilcSKEPKType pk_type; + SilcVerifyPublicKey completion; + void *context; +} *PublicKeyVerify; + +static void verify_public_key_completion(const char *line, void *context) { - return TRUE; + PublicKeyVerify verify = (PublicKeyVerify)context; + + if (line[0] == 'Y' || line[0] == 'y') { + /* Call the completion */ + if (verify->completion) + verify->completion(TRUE, verify->context); + + /* Save the key for future checking */ + silc_pkcs_save_public_key_data(verify->filename, verify->pk, + verify->pk_len, SILC_PKCS_FILE_PEM); + } else { + /* Call the completion */ + if (verify->completion) + verify->completion(FALSE, verify->context); + + silc_say(verify->client, + verify->conn, "Will not accept the %s key", verify->entity); + } + + silc_free(verify->filename); + silc_free(verify->entity); + silc_free(verify); +} + +static void +silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context) +{ + int i; + char file[256], filename[256], *fingerprint; + struct passwd *pw; + struct stat st; + char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER) ? + "server" : "client"); + PublicKeyVerify verify; + + if (pk_type != SILC_SKE_PK_TYPE_SILC) { + silc_say(client, conn, "We don't support %s public key type %d", + entity, pk_type); + if (completion) + completion(FALSE, context); + return; + } + + pw = getpwuid(getuid()); + if (!pw) { + if (completion) + completion(FALSE, context); + return; + } + + memset(filename, 0, sizeof(filename)); + memset(file, 0, sizeof(file)); + + if (conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER) { + snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, + conn->sock->hostname, conn->sock->port); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", + pw->pw_dir, entity, file); + } else { + /* Replace all whitespaces with `_'. */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + for (i = 0; i < strlen(fingerprint); i++) + if (fingerprint[i] == ' ') + fingerprint[i] = '_'; + + snprintf(file, sizeof(file) - 1, "%skey_%s.pub", entity, fingerprint); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", + pw->pw_dir, entity, file); + silc_free(fingerprint); + } + + /* Take fingerprint of the public key */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + + verify = silc_calloc(1, sizeof(*verify)); + verify->client = client; + verify->conn = conn; + verify->filename = strdup(filename); + verify->entity = strdup(entity); + verify->pk = pk; + verify->pk_len = pk_len; + verify->pk_type = pk_type; + verify->completion = completion; + verify->context = context; + + /* Check whether this key already exists */ + if (stat(filename, &st) < 0) { + /* Key does not exist, ask user to verify the key and save it */ + + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + + keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion, + "Would you like to accept the key (y/n)? ", 0, + verify); + silc_free(fingerprint); + return; + } else { + /* The key already exists, verify it. */ + SilcPublicKey public_key; + unsigned char *encpk; + uint32 encpk_len; + + /* Load the key file */ + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_PEM)) + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_BIN)) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "Could not load your local copy of the %s key", + entity); + keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion, + "Would you like to accept the key " + "anyway (y/n)? ", 0, + verify); + silc_free(fingerprint); + return; + } + + /* Encode the key data */ + encpk = silc_pkcs_public_key_encode(public_key, &encpk_len); + if (!encpk) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "Your local copy of the %s key is malformed", + entity); + keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion, + "Would you like to accept the key " + "anyway (y/n)? ", 0, + verify); + silc_free(fingerprint); + return; + } + + /* Compare the keys */ + if (memcmp(encpk, pk, encpk_len)) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "%s key does not match with your local copy", + entity); + silc_say(client, conn, + "It is possible that the key has expired or changed"); + silc_say(client, conn, "It is also possible that some one is performing " + "man-in-the-middle attack"); + + /* Ask user to verify the key and save it */ + keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion, + "Would you like to accept the key " + "anyway (y/n)? ", 0, + verify); + silc_free(fingerprint); + return; + } + + /* Local copy matched */ + if (completion) + completion(TRUE, context); + silc_free(fingerprint); + } +} + +/* Verifies received public key. The `conn_type' indicates which entity + (server, client etc.) has sent the public key. If user decides to trust + the key may be saved as trusted public key for later use. The + `completion' must be called after the public key has been verified. */ + +static void +silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context) +{ + silc_verify_public_key_internal(client, conn, conn_type, pk, + pk_len, pk_type, + completion, context); } /* Asks passphrase from user on the input line. */ -static unsigned char *silc_ask_passphrase(SilcClient client, - SilcClientConnection conn) +typedef struct { + SilcAskPassphrase completion; + void *context; +} *AskPassphrase; + +static void ask_passphrase_completion(const char *passphrase, void *context) { - return NULL; + AskPassphrase p = (AskPassphrase)context; + p->completion((unsigned char *)passphrase, + passphrase ? strlen(passphrase) : 0, p->context); + silc_free(p); +} + +static void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context) +{ + AskPassphrase p = silc_calloc(1, sizeof(*p)); + p->completion = completion; + p->context = context; + + keyboard_entry_redirect((SIGNAL_FUNC)ask_passphrase_completion, + "Passphrase: ", ENTRY_REDIRECT_FLAG_HIDDEN, p); } /* Find authentication method and authentication data by hostname and diff --git a/apps/irssi/src/silc/core/silc-servers.c b/apps/irssi/src/silc/core/silc-servers.c index 062b6049..8e908936 100644 --- a/apps/irssi/src/silc/core/silc-servers.c +++ b/apps/irssi/src/silc/core/silc-servers.c @@ -327,6 +327,8 @@ void silc_server_init(void) command_bind("getkey", MODULE_NAME, (SIGNAL_FUNC) command_self); command_set_options("connect", "+silcnet"); + + settings_add_bool("server", "skip_motd", FALSE); } void silc_server_deinit(void) diff --git a/apps/silc/client_ops.c b/apps/silc/client_ops.c index 62849ce1..33de05c6 100644 --- a/apps/silc/client_ops.c +++ b/apps/silc/client_ops.c @@ -20,6 +20,163 @@ #include "clientincludes.h" +static bool +silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type) +{ + int i; + char file[256], filename[256], *fingerprint; + struct passwd *pw; + struct stat st; + char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER) ? + "server" : "client"); + + if (pk_type != SILC_SKE_PK_TYPE_SILC) { + silc_say(client, conn, "We don't support %s public key type %d", + entity, pk_type); + return FALSE; + } + + pw = getpwuid(getuid()); + if (!pw) + return FALSE; + + memset(filename, 0, sizeof(filename)); + memset(file, 0, sizeof(file)); + + if (conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER) { + snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, + conn->sock->hostname, conn->sock->port); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", + pw->pw_dir, entity, file); + } else { + /* Replace all whitespaces with `_'. */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + for (i = 0; i < strlen(fingerprint); i++) + if (fingerprint[i] == ' ') + fingerprint[i] = '_'; + + snprintf(file, sizeof(file) - 1, "%skey_%s.pub", entity, fingerprint); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", + pw->pw_dir, entity, file); + silc_free(fingerprint); + } + + /* Take fingerprint of the public key */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + + /* Check whether this key already exists */ + if (stat(filename, &st) < 0) { + + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + + /* Ask user to verify the key and save it */ + if (silc_client_ask_yes_no(client, + "Would you like to accept the key (y/n)? ")) + { + /* Save the key for future checking */ + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + silc_free(fingerprint); + return TRUE; + } + } else { + /* The key already exists, verify it. */ + SilcPublicKey public_key; + unsigned char *encpk; + uint32 encpk_len; + + /* Load the key file */ + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_PEM)) + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_BIN)) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "Could not load your local copy of the %s key", + entity); + if (silc_client_ask_yes_no(client, + "Would you like to accept the key anyway (y/n)? ")) + { + /* Save the key for future checking */ + unlink(filename); + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + silc_free(fingerprint); + return TRUE; + } + + silc_free(fingerprint); + return FALSE; + } + + /* Encode the key data */ + encpk = silc_pkcs_public_key_encode(public_key, &encpk_len); + if (!encpk) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "Your local copy of the %s key is malformed", + entity); + if (silc_client_ask_yes_no(client, + "Would you like to accept the key anyway (y/n)? ")) + { + /* Save the key for future checking */ + unlink(filename); + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + silc_free(fingerprint); + return TRUE; + } + + silc_free(fingerprint); + return FALSE; + } + + if (memcmp(encpk, pk, encpk_len)) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "%s key does not match with your local copy", + entity); + silc_say(client, conn, + "It is possible that the key has expired or changed"); + silc_say(client, conn, "It is also possible that some one is performing " + "man-in-the-middle attack"); + + /* Ask user to verify the key and save it */ + if (silc_client_ask_yes_no(client, + "Would you like to accept the key anyway (y/n)? ")) + { + /* Save the key for future checking */ + unlink(filename); + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + silc_free(fingerprint); + return TRUE; + } + + silc_say(client, conn, "Will not accept the %s key", entity); + silc_free(fingerprint); + return FALSE; + } + + /* Local copy matched */ + silc_free(fingerprint); + return TRUE; + } + + silc_say(client, conn, "Will not accept the %s key", entity); + silc_free(fingerprint); + return FALSE; +} + /* Prints a message with three star (*) sign before the actual message on the current output window. This is used to print command outputs and error messages. */ @@ -905,8 +1062,9 @@ void silc_command_reply(SilcClient client, SilcClientConnection conn, pk = silc_pkcs_public_key_encode(public_key, &pk_len); if (id_type == SILC_ID_CLIENT) { - silc_verify_public_key(client, conn, SILC_SOCKET_TYPE_CLIENT, - pk, pk_len, SILC_SKE_PK_TYPE_SILC); + silc_verify_public_key_internal(client, conn, + SILC_SOCKET_TYPE_CLIENT, + pk, pk_len, SILC_SKE_PK_TYPE_SILC); } silc_free(pk); @@ -963,12 +1121,11 @@ void silc_disconnect(SilcClient client, SilcClientConnection conn) /* Asks passphrase from user on the input line. */ -unsigned char *silc_ask_passphrase(SilcClient client, - SilcClientConnection conn) +void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context) { SilcClientInternal app = (SilcClientInternal)conn->client->application; char pass1[256], pass2[256]; - char *ret; int try = 3; while(try) { @@ -997,180 +1154,33 @@ unsigned char *silc_ask_passphrase(SilcClient client, try--; } - ret = silc_calloc(strlen(pass1), sizeof(char)); - memcpy(ret, pass1, strlen(pass1)); - - memset(pass1, 0, sizeof(pass1)); - memset(pass2, 0, sizeof(pass2)); - wattroff(app->screen->input_win, A_INVIS); silc_screen_input_reset(app->screen); - return ret; + /* Deliver the passphrase to the library */ + completion(pass1, strlen(pass1), context); + + memset(pass1, 0, sizeof(pass1)); + memset(pass2, 0, sizeof(pass2)); } -/* Verifies received public key. If user decides to trust the key it is - saved as public server key for later use. If user does not trust the - key this returns FALSE. */ +/* Verifies received public key. The `conn_type' indicates which entity + (server, client etc.) has sent the public key. If user decides to trust + the key may be saved as trusted public key for later use. The + `completion' must be called after the public key has been verified. */ -int silc_verify_public_key(SilcClient client, - SilcClientConnection conn, - SilcSocketType conn_type, - unsigned char *pk, uint32 pk_len, - SilcSKEPKType pk_type) +void silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context) { - int i; - char filename[256]; - char file[256]; - char *fingerprint; - struct passwd *pw; - struct stat st; - char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER || - conn_type == SILC_SOCKET_TYPE_ROUTER) ? - "server" : "client"); - - if (pk_type != SILC_SKE_PK_TYPE_SILC) { - silc_say(client, conn, "We don't support %s public key type %d", - entity, pk_type); - return FALSE; - } - - pw = getpwuid(getuid()); - if (!pw) - return FALSE; - - memset(filename, 0, sizeof(filename)); - memset(file, 0, sizeof(file)); - - if (conn_type == SILC_SOCKET_TYPE_SERVER || - conn_type == SILC_SOCKET_TYPE_ROUTER) { - snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, - conn->sock->hostname, conn->sock->port); - snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", - pw->pw_dir, entity, file); - } else { - /* Replace all whitespaces with `_'. */ - fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); - for (i = 0; i < strlen(fingerprint); i++) - if (fingerprint[i] == ' ') - fingerprint[i] = '_'; - - snprintf(file, sizeof(file) - 1, "%skey_%s.pub", entity, fingerprint); - snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", - pw->pw_dir, entity, file); - silc_free(fingerprint); - } - - /* Take fingerprint of the public key */ - fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); - - /* Check whether this key already exists */ - if (stat(filename, &st) < 0) { - - silc_say(client, conn, "Received %s public key", entity); - silc_say(client, conn, "Fingerprint for the %s key is", entity); - silc_say(client, conn, "%s", fingerprint); - - /* Ask user to verify the key and save it */ - if (silc_client_ask_yes_no(client, - "Would you like to accept the key (y/n)? ")) - { - /* Save the key for future checking */ - silc_pkcs_save_public_key_data(filename, pk, pk_len, - SILC_PKCS_FILE_PEM); - silc_free(fingerprint); - return TRUE; - } - } else { - /* The key already exists, verify it. */ - SilcPublicKey public_key; - unsigned char *encpk; - uint32 encpk_len; - - /* Load the key file */ - if (!silc_pkcs_load_public_key(filename, &public_key, - SILC_PKCS_FILE_PEM)) - if (!silc_pkcs_load_public_key(filename, &public_key, - SILC_PKCS_FILE_BIN)) { - silc_say(client, conn, "Received %s public key", entity); - silc_say(client, conn, "Fingerprint for the %s key is", entity); - silc_say(client, conn, "%s", fingerprint); - silc_say(client, conn, "Could not load your local copy of the %s key", - entity); - if (silc_client_ask_yes_no(client, - "Would you like to accept the key anyway (y/n)? ")) - { - /* Save the key for future checking */ - unlink(filename); - silc_pkcs_save_public_key_data(filename, pk, pk_len, - SILC_PKCS_FILE_PEM); - silc_free(fingerprint); - return TRUE; - } - - silc_free(fingerprint); - return FALSE; - } - - /* Encode the key data */ - encpk = silc_pkcs_public_key_encode(public_key, &encpk_len); - if (!encpk) { - silc_say(client, conn, "Received %s public key", entity); - silc_say(client, conn, "Fingerprint for the %s key is", entity); - silc_say(client, conn, "%s", fingerprint); - silc_say(client, conn, "Your local copy of the %s key is malformed", - entity); - if (silc_client_ask_yes_no(client, - "Would you like to accept the key anyway (y/n)? ")) - { - /* Save the key for future checking */ - unlink(filename); - silc_pkcs_save_public_key_data(filename, pk, pk_len, - SILC_PKCS_FILE_PEM); - silc_free(fingerprint); - return TRUE; - } - - silc_free(fingerprint); - return FALSE; - } - - if (memcmp(encpk, pk, encpk_len)) { - silc_say(client, conn, "Received %s public key", entity); - silc_say(client, conn, "Fingerprint for the %s key is", entity); - silc_say(client, conn, "%s", fingerprint); - silc_say(client, conn, "%s key does not match with your local copy", - entity); - silc_say(client, conn, - "It is possible that the key has expired or changed"); - silc_say(client, conn, "It is also possible that some one is performing " - "man-in-the-middle attack"); - - /* Ask user to verify the key and save it */ - if (silc_client_ask_yes_no(client, - "Would you like to accept the key anyway (y/n)? ")) - { - /* Save the key for future checking */ - unlink(filename); - silc_pkcs_save_public_key_data(filename, pk, pk_len, - SILC_PKCS_FILE_PEM); - silc_free(fingerprint); - return TRUE; - } - - silc_say(client, conn, "Will not accept the %s key", entity); - silc_free(fingerprint); - return FALSE; - } - - /* Local copy matched */ - silc_free(fingerprint); - return TRUE; + if (silc_verify_public_key_internal(client, conn, conn_type, pk, + pk_len, pk_type)) { + completion(TRUE, context); + return; } - silc_say(client, conn, "Will not accept the %s key", entity); - silc_free(fingerprint); - return FALSE; + completion(FALSE, context); } /* Find authentication method and authentication data by hostname and diff --git a/apps/silc/client_ops.h b/apps/silc/client_ops.h index ebcd0e31..e803af4d 100644 --- a/apps/silc/client_ops.h +++ b/apps/silc/client_ops.h @@ -39,13 +39,12 @@ void silc_command_reply(SilcClient client, SilcClientConnection conn, SilcCommand command, SilcCommandStatus status, ...); void silc_connect(SilcClient client, SilcClientConnection conn, int success); void silc_disconnect(SilcClient client, SilcClientConnection conn); -unsigned char *silc_ask_passphrase(SilcClient client, - SilcClientConnection conn); -int silc_verify_public_key(SilcClient client, - SilcClientConnection conn, - SilcSocketType conn_type, - unsigned char *pk, uint32 pk_len, - SilcSKEPKType pk_type); +void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context); +void silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context); int silc_get_auth_method(SilcClient client, SilcClientConnection conn, char *hostname, uint16 port, SilcProtocolAuthMeth *auth_meth, diff --git a/apps/silcd/protocol.c b/apps/silcd/protocol.c index 6069caaf..dbda9d84 100644 --- a/apps/silcd/protocol.c +++ b/apps/silcd/protocol.c @@ -197,6 +197,42 @@ SilcSKEStatus silc_ske_check_version(SilcSKE ske, unsigned char *version, return status; } +/* Callback that is called by the SKE to indicate that it is safe to + continue the execution of the protocol. This is used only if we are + initiator. Is given as argument to the silc_ske_initiator_finish + function. This is called due to the fact that the public key verification + process is asynchronous and we must not continue the protocl until + the public key has been verified and this callback is called. */ + +static void silc_server_protocol_ke_finish(SilcSKE ske, void *context) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerKEInternalContext *ctx = + (SilcServerKEInternalContext *)protocol->context; + SilcServer server = (SilcServer)ctx->server; + + SILC_LOG_DEBUG(("Start")); + + if (ske->status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", + ske->status)); + SILC_LOG_DEBUG(("Error (type %d) during Key Exchange protocol", + ske->status)); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + protocol->execute(server->timeout_queue, 0, protocol, 0, 0, 300000); + return; + } + + /* Send Ok to the other end. We will end the protocol as responder + sends Ok to us when we will take the new keys into use. */ + if (ctx->responder == FALSE) + silc_ske_end(ctx->ske, silc_server_protocol_ke_send_packet, context); + + /* End the protocol on the next round */ + protocol->state = SILC_PROTOCOL_STATE_END; +} + /* Performs key exchange protocol. This is used for both initiator and responder key exchange. This is performed always when accepting new connection to the server. This may be called recursively. */ @@ -207,7 +243,7 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) SilcServerKEInternalContext *ctx = (SilcServerKEInternalContext *)protocol->context; SilcServer server = (SilcServer)ctx->server; - SilcSKEStatus status = 0; + SilcSKEStatus status = SILC_SKE_STATUS_OK; SILC_LOG_DEBUG(("Start")); @@ -252,6 +288,10 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) context); } + /* Return now if the procedure is pending. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", status)); @@ -290,6 +330,10 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) NULL, NULL); } + /* Return now if the procedure is pending. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", status)); @@ -316,6 +360,12 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) /* Process the received Key Exchange 1 Payload packet from the initiator. This also creates our parts of the Diffie Hellman algorithm. */ + /* XXX TODO: If mutual authentication flag is set then the + verify_key callback should be set to verify the remote ends + public key!! */ + /* XXX TODO: when the verify_key is set then the `callback' + must be set as well as the verify_key is asynchronous + (take a look to silc_ske_initiator_finish for example. */ status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer, NULL, NULL, NULL, NULL); } else { @@ -330,6 +380,10 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) context); } + /* Return now if the procedure is pending. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", status)); @@ -361,13 +415,23 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) SILC_SKE_PK_TYPE_SILC, silc_server_protocol_ke_send_packet, context); + + /* End the protocol on the next round */ + protocol->state = SILC_PROTOCOL_STATE_END; } else { /* Finish the protocol. This verifies the Key Exchange 2 payload sent by responder. */ + /* XXX TODO: the verify_key callback is not set!!! */ status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer, - NULL, NULL, NULL, NULL); + NULL, NULL, + silc_server_protocol_ke_finish, + context); } + /* Return now if the procedure is pending. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", status)); @@ -378,14 +442,6 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 300000); return; } - - /* Send Ok to the other end. We will end the protocol as responder - sends Ok to us when we will take the new keys into use. */ - if (ctx->responder == FALSE) - silc_ske_end(ctx->ske, silc_server_protocol_ke_send_packet, context); - - /* End the protocol on the next round */ - protocol->state = SILC_PROTOCOL_STATE_END; } break; diff --git a/lib/silcclient/command.c b/lib/silcclient/command.c index b3d8e4f6..ac2324cb 100644 --- a/lib/silcclient/command.c +++ b/lib/silcclient/command.c @@ -1578,15 +1578,44 @@ SILC_CLIENT_CMD_FUNC(kick) silc_client_command_free(cmd); } +static void silc_client_command_oper_send(unsigned char *data, + uint32 data_len, void *context) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClientConnection conn = cmd->conn; + SilcBuffer buffer, auth; + + if (cmd->argc == 3) { + /* Pulic key auth XXX TODO */ + auth = NULL; + } else { + /* Encode the authentication payload */ + auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0, + data, data_len); + } + + buffer = silc_command_payload_encode_va(SILC_COMMAND_OPER, 0, 2, + 1, cmd->argv[1], + strlen(cmd->argv[1]), + 2, auth->data, auth->len); + silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, + 0, NULL, NULL, buffer->data, buffer->len, TRUE); + + silc_buffer_free(buffer); + silc_buffer_free(auth); + + /* Notify application */ + COMMAND; +} + /* OPER command. Used to obtain server operator privileges. */ SILC_CLIENT_CMD_FUNC(oper) { SilcClientCommandContext cmd = (SilcClientCommandContext)context; SilcClientConnection conn = cmd->conn; - SilcBuffer buffer; unsigned char *auth_data; - SilcBuffer auth; + uint32 auth_data_len = 0; if (!cmd->conn) { SILC_NOT_CONNECTED(cmd->client, cmd->conn); @@ -1608,19 +1637,41 @@ SILC_CLIENT_CMD_FUNC(oper) goto out; } else { /* Get passphrase */ + cmd->client->ops->ask_passphrase(cmd->client, conn, + silc_client_command_oper_send, + context); + return; + } - auth_data = cmd->client->ops->ask_passphrase(cmd->client, conn); - if (!auth_data) { - COMMAND_ERROR; - goto out; - } + silc_client_command_oper_send(auth_data, auth_data_len, context); + + memset(auth_data, 0, auth_data_len); + silc_free(auth_data); + + /* Notify application */ + COMMAND; + + out: + silc_client_command_free(cmd); +} + +static void silc_client_command_silcoper_send(unsigned char *data, + uint32 data_len, void *context) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClientConnection conn = cmd->conn; + SilcBuffer buffer, auth; + if (cmd->argc == 3) { + /* Pulic key auth XXX TODO */ + auth = NULL; + } else { /* Encode the authentication payload */ auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0, - auth_data, strlen(auth_data)); + data, data_len); } - buffer = silc_command_payload_encode_va(SILC_COMMAND_OPER, 0, 2, + buffer = silc_command_payload_encode_va(SILC_COMMAND_SILCOPER, 0, 2, 1, cmd->argv[1], strlen(cmd->argv[1]), 2, auth->data, auth->len); @@ -1629,14 +1680,9 @@ SILC_CLIENT_CMD_FUNC(oper) silc_buffer_free(buffer); silc_buffer_free(auth); - memset(auth_data, 0, strlen(auth_data)); - silc_free(auth_data); /* Notify application */ COMMAND; - - out: - silc_client_command_free(cmd); } /* SILCOPER command. Used to obtain router operator privileges. */ @@ -1645,9 +1691,8 @@ SILC_CLIENT_CMD_FUNC(silcoper) { SilcClientCommandContext cmd = (SilcClientCommandContext)context; SilcClientConnection conn = cmd->conn; - SilcBuffer buffer; unsigned char *auth_data; - SilcBuffer auth; + uint32 auth_data_len = 0; if (!cmd->conn) { SILC_NOT_CONNECTED(cmd->client, cmd->conn); @@ -1669,28 +1714,15 @@ SILC_CLIENT_CMD_FUNC(silcoper) goto out; } else { /* Get passphrase */ - - auth_data = cmd->client->ops->ask_passphrase(cmd->client, conn); - if (!auth_data) { - COMMAND_ERROR; - goto out; - } - - /* Encode the authentication payload */ - auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0, - auth_data, strlen(auth_data)); + cmd->client->ops->ask_passphrase(cmd->client, conn, + silc_client_command_silcoper_send, + context); + return; } - buffer = silc_command_payload_encode_va(SILC_COMMAND_SILCOPER, 0, 2, - 1, cmd->argv[1], - strlen(cmd->argv[1]), - 2, auth->data, auth->len); - silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, - 0, NULL, NULL, buffer->data, buffer->len, TRUE); + silc_client_command_silcoper_send(auth_data, auth_data_len, context); - silc_buffer_free(buffer); - silc_buffer_free(auth); - memset(auth_data, 0, strlen(auth_data)); + memset(auth_data, 0, auth_data_len); silc_free(auth_data); /* Notify application */ diff --git a/lib/silcclient/protocol.c b/lib/silcclient/protocol.c index d4cfae97..f82bbbeb 100644 --- a/lib/silcclient/protocol.c +++ b/lib/silcclient/protocol.c @@ -52,29 +52,57 @@ void silc_client_protocol_ke_send_packet(SilcSKE ske, packet->data, packet->len, TRUE); } +/* Public key verification callback. Called by the application. */ + +typedef struct { + SilcSKE ske; + SilcSKEVerifyCbCompletion completion; + void *completion_context; +} *VerifyKeyContext; + +static void silc_client_verify_key_cb(bool success, void *context) +{ + VerifyKeyContext verify = (VerifyKeyContext)context; + + SILC_LOG_DEBUG(("Start")); + + /* Call the completion callback back to the SKE */ + verify->completion(verify->ske, success ? SILC_SKE_STATUS_OK : + SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY, + verify->completion_context); + + silc_free(verify); +} + /* Callback that is called when we have received KE2 payload from responder. We try to verify the public key now. */ -SilcSKEStatus silc_client_protocol_ke_verify_key(SilcSKE ske, - unsigned char *pk_data, - uint32 pk_len, - SilcSKEPKType pk_type, - void *context) +void silc_client_protocol_ke_verify_key(SilcSKE ske, + unsigned char *pk_data, + uint32 pk_len, + SilcSKEPKType pk_type, + void *context, + SilcSKEVerifyCbCompletion completion, + void *completion_context) { SilcProtocol protocol = (SilcProtocol)context; SilcClientKEInternalContext *ctx = (SilcClientKEInternalContext *)protocol->context; SilcClient client = (SilcClient)ctx->client; + VerifyKeyContext verify; SILC_LOG_DEBUG(("Start")); - /* Verify public key from user. */ - if (!client->ops->verify_public_key(client, ctx->sock->user_data, - ctx->sock->type, - pk_data, pk_len, pk_type)) - return SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY; + verify = silc_calloc(1, sizeof(*verify)); + verify->ske = ske; + verify->completion = completion; + verify->completion_context = completion_context; - return SILC_SKE_STATUS_OK; + /* Verify public key from user. */ + client->ops->verify_public_key(client, ctx->sock->user_data, + ctx->sock->type, + pk_data, pk_len, pk_type, + silc_client_verify_key_cb, verify); } /* Sets the negotiated key material into use for particular connection. */ @@ -193,6 +221,64 @@ SilcSKEStatus silc_ske_check_version(SilcSKE ske, unsigned char *version, return status; } +/* Callback that is called by the SKE to indicate that it is safe to + continue the execution of the protocol. Is given as argument to the + silc_ske_initiator_finish or silc_ske_responder_phase_2 functions. + This is called due to the fact that the public key verification + process is asynchronous and we must not continue the protocl until + the public key has been verified and this callback is called. */ + +static void silc_client_protocol_ke_continue(SilcSKE ske, + void *context) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcClientKEInternalContext *ctx = + (SilcClientKEInternalContext *)protocol->context; + SilcClient client = (SilcClient)ctx->client; + SilcClientConnection conn = ctx->sock->user_data; + + SILC_LOG_DEBUG(("Start")); + + if (ske->status != SILC_SKE_STATUS_OK) { + if (ske->status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY) { + client->ops->say(client, conn, + "Received unsupported server %s public key", + ctx->sock->hostname); + } else if (ske->status == SILC_SKE_STATUS_PUBLIC_KEY_NOT_PROVIDED) { + client->ops->say(client, conn, + "Remote host did not send its public key, even though " + "it must send it"); + } else { + client->ops->say(client, conn, + "Error during key exchange protocol with server %s", + ctx->sock->hostname); + } + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + protocol->execute(client->timeout_queue, 0, protocol, 0, 0, 0); + return; + } + + /* Send Ok to the other end. We will end the protocol as server + sends Ok to us when we will take the new keys into use. Do this + if we are initiator. This is happens when this callback was sent + to silc_ske_initiator_finish function. */ + if (ctx->responder == FALSE) { + silc_ske_end(ctx->ske, ctx->send_packet, context); + + /* End the protocol on the next round */ + protocol->state = SILC_PROTOCOL_STATE_END; + } + + /* Advance protocol state and call the next state if we are responder. + This happens when this callback was sent to silc_ske_responder_phase_2 + function. */ + if (ctx->responder == TRUE) { + protocol->state++; + protocol->execute(client->timeout_queue, 0, protocol, 0, 0, 100000); + } +} + /* Performs key exchange protocol. This is used for both initiator and responder key exchange. This may be called recursively. */ @@ -203,7 +289,7 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) (SilcClientKEInternalContext *)protocol->context; SilcClient client = (SilcClient)ctx->client; SilcClientConnection conn = ctx->sock->user_data; - SilcSKEStatus status = 0; + SilcSKEStatus status = SILC_SKE_STATUS_OK; SILC_LOG_DEBUG(("Start")); @@ -247,6 +333,10 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) context); } + /* Return now if the procedure is pending */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", status)); @@ -310,9 +400,12 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) if (ctx->responder == TRUE) { /* Process the received Key Exchange 1 Payload packet from the initiator. This also creates our parts of the Diffie - Hellman algorithm. */ + Hellman algorithm. The silc_client_protocol_ke_continue will + be called after the public key has been verified. */ status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer, - ctx->verify, context, NULL, NULL); + ctx->verify, context, + silc_client_protocol_ke_continue, + context); } else { /* Call the Phase-2 function. This creates Diffie Hellman key exchange parameters and sends our public part inside @@ -322,8 +415,13 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) client->private_key, ctx->send_packet, context); + protocol->state++; } + /* Return now if the procedure is pending */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", status)); @@ -334,11 +432,6 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); return; } - - /* Advance protocol state and call the next state if we are responder */ - protocol->state++; - if (ctx->responder == TRUE) - protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 100000); } break; case 4: @@ -355,16 +448,24 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) SILC_SKE_PK_TYPE_SILC, ctx->send_packet, context); - status = 0; + + /* End the protocol on the next round */ + protocol->state = SILC_PROTOCOL_STATE_END; } else { /* Finish the protocol. This verifies the Key Exchange 2 payload - sent by responder. */ + sent by responder. The silc_client_protocol_ke_continue will + be called after the public key has been verified. */ status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer, - ctx->verify, context, NULL, NULL); + ctx->verify, context, + silc_client_protocol_ke_continue, + context); } - if (status != SILC_SKE_STATUS_OK) { + /* Return now if the procedure is pending */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY) { client->ops->say(client, conn, "Received unsupported server %s public key", @@ -378,14 +479,6 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); return; } - - /* Send Ok to the other end. We will end the protocol as server - sends Ok to us when we will take the new keys into use. */ - if (ctx->responder == FALSE) - silc_ske_end(ctx->ske, ctx->send_packet, context); - - /* End the protocol on the next round */ - protocol->state = SILC_PROTOCOL_STATE_END; } break; @@ -518,6 +611,47 @@ silc_client_get_public_key_auth(SilcClient client, return FALSE; } +/* Continues the connection authentication protocol. This funtion may + be called directly or used as SilcAskPassphrase callback. */ + +static void +silc_client_conn_auth_continue(unsigned char *auth_data, + uint32 auth_data_len, void *context) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcClientConnAuthInternalContext *ctx = + (SilcClientConnAuthInternalContext *)protocol->context; + SilcClient client = (SilcClient)ctx->client; + SilcBuffer packet; + int payload_len = 0; + + SILC_LOG_DEBUG(("Start")); + + payload_len = 4 + auth_data_len; + packet = silc_buffer_alloc(payload_len); + silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); + silc_buffer_format(packet, + SILC_STR_UI_SHORT(payload_len), + SILC_STR_UI_SHORT(SILC_SOCKET_TYPE_CLIENT), + SILC_STR_UI_XNSTRING(auth_data, auth_data_len), + SILC_STR_END); + + /* Send the packet to server */ + silc_client_packet_send(client, ctx->sock, + SILC_PACKET_CONNECTION_AUTH, + NULL, 0, NULL, NULL, + packet->data, packet->len, TRUE); + + if (auth_data) { + memset(auth_data, 0, auth_data_len); + silc_free(auth_data); + } + silc_buffer_free(packet); + + /* Next state is end of protocol */ + protocol->state = SILC_PROTOCOL_STATE_END; +} + SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) { SilcProtocol protocol = (SilcProtocol)context; @@ -538,8 +672,6 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) * Start protocol. We send authentication data to the server * to be authenticated. */ - SilcBuffer packet; - int payload_len = 0; unsigned char *auth_data = NULL; uint32 auth_data_len = 0; @@ -559,8 +691,10 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) client->ops->say(client, conn, "Password authentication required by server %s", ctx->sock->hostname); - auth_data = client->ops->ask_passphrase(client, conn); - auth_data_len = strlen(auth_data); + client->ops->ask_passphrase(client, conn, + silc_client_conn_auth_continue, + protocol); + return; break; case SILC_AUTH_PUBLIC_KEY: @@ -577,29 +711,8 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) } } - payload_len = 4 + auth_data_len; - packet = silc_buffer_alloc(payload_len); - silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); - silc_buffer_format(packet, - SILC_STR_UI_SHORT(payload_len), - SILC_STR_UI_SHORT(SILC_SOCKET_TYPE_CLIENT), - SILC_STR_UI_XNSTRING(auth_data, auth_data_len), - SILC_STR_END); - - /* Send the packet to server */ - silc_client_packet_send(client, ctx->sock, - SILC_PACKET_CONNECTION_AUTH, - NULL, 0, NULL, NULL, - packet->data, packet->len, TRUE); - - if (auth_data) { - memset(auth_data, 0, auth_data_len); - silc_free(auth_data); - } - silc_buffer_free(packet); - - /* Next state is end of protocol */ - protocol->state = SILC_PROTOCOL_STATE_END; + silc_client_conn_auth_continue(auth_data, + auth_data_len, protocol); } break; diff --git a/lib/silcclient/protocol.h b/lib/silcclient/protocol.h index e55f4c66..b74bd6f7 100644 --- a/lib/silcclient/protocol.h +++ b/lib/silcclient/protocol.h @@ -91,11 +91,13 @@ void silc_client_protocol_ke_send_packet(SilcSKE ske, SilcBuffer packet, SilcPacketType type, void *context); -SilcSKEStatus silc_client_protocol_ke_verify_key(SilcSKE ske, - unsigned char *pk_data, - uint32 pk_len, - SilcSKEPKType pk_type, - void *context); +void silc_client_protocol_ke_verify_key(SilcSKE ske, + unsigned char *pk_data, + uint32 pk_len, + SilcSKEPKType pk_type, + void *context, + SilcSKEVerifyCbCompletion completion, + void *completion_context); void silc_client_protocol_ke_set_keys(SilcSKE ske, SilcSocketConnection sock, SilcSKEKeyMaterial *keymat, diff --git a/lib/silcclient/silcapi.h b/lib/silcclient/silcapi.h index 8b0462fc..0c2b85f0 100644 --- a/lib/silcclient/silcapi.h +++ b/lib/silcclient/silcapi.h @@ -84,6 +84,18 @@ typedef struct { ******************************************************************************/ +/* Ask passphrase callback. This is called by the application when the + library calls `ask_passphrase' client operation. The callback delivers + the passphrase to the library. */ +typedef void (*SilcAskPassphrase)(unsigned char *passphrase, + uint32 passphrase_len, + void *context); + +/* Public key (or certificate) verification callback. This is called + by the application to indicate that the public key verification was + either success or failure. */ +typedef void (*SilcVerifyPublicKey)(bool success, void *context); + /* SILC Client Operations. These must be implemented by the application. */ typedef struct { /* Message sent to the application by library. `conn' associates the @@ -166,17 +178,18 @@ typedef struct { /* Verifies received public key. The `conn_type' indicates which entity (server, client etc.) has sent the public key. If user decides to trust - the key may be saved as trusted public key for later use. If user does - not trust the key this returns FALSE. If everything is Ok this returns - TRUE. */ - int (*verify_public_key)(SilcClient client, SilcClientConnection conn, - SilcSocketType conn_type, unsigned char *pk, - uint32 pk_len, SilcSKEPKType pk_type); - - /* Ask (interact, that is) a passphrase from user. Returns the passphrase - or NULL on error. */ - unsigned char *(*ask_passphrase)(SilcClient client, - SilcClientConnection conn); + the key may be saved as trusted public key for later use. The + `completion' must be called after the public key has been verified. */ + void (*verify_public_key)(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context); + + /* Ask (interact, that is) a passphrase from user. The passphrase is + returned to the library by calling the `completion' callback with + the `context'. */ + void (*ask_passphrase)(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context); /* Notifies application that failure packet was received. This is called if there is some protocol active in the client. The `protocol' is the diff --git a/lib/silcske/silcske.c b/lib/silcske/silcske.c index 8bd412a7..694c2ac8 100644 --- a/lib/silcske/silcske.c +++ b/lib/silcske/silcske.c @@ -323,60 +323,51 @@ SilcSKEStatus silc_ske_initiator_phase_2(SilcSKE ske, return status; } -/* Receives Key Exchange Payload from responder consisting responders - public key, f, and signature. This function verifies the public key, - computes the secret shared key and verifies the signature. */ +/* An initiator finish final callback that is called to indicate that + the SKE protocol may continue. */ -SilcSKEStatus silc_ske_initiator_finish(SilcSKE ske, - SilcBuffer ke_payload, - SilcSKEVerifyCb verify_key, - void *verify_context, - SilcSKECb callback, - void *context) +typedef struct { + SilcSKECb callback; + void *context; +} *SKEInitiatorFinish; + +static void silc_ske_initiator_finish_final(SilcSKE ske, + SilcSKEStatus status, + void *context) { - SilcSKEStatus status = SILC_SKE_STATUS_OK; - SilcSKEKEPayload *payload; - SilcPublicKey public_key = NULL; - SilcInt *KEY; + SKEInitiatorFinish finish = (SKEInitiatorFinish)context; + SilcSKEKEPayload *payload = ske->ke2_payload; unsigned char hash[32]; uint32 hash_len; + SilcPublicKey public_key = NULL; - SILC_LOG_DEBUG(("Start")); + /* If the caller returns PENDING status SKE library will assume that + the caller will re-call this callback when it is not anymore in + PENDING status. */ + if (status == SILC_SKE_STATUS_PENDING) + return; - /* Decode the payload */ - status = silc_ske_payload_ke_decode(ske, ke_payload, &payload); + /* If the status is an error then the public key that was verified + by the caller is not authentic. */ if (status != SILC_SKE_STATUS_OK) { ske->status = status; - return status; + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); + return; } - ske->ke2_payload = payload; - - SILC_LOG_DEBUG(("Computing KEY = f ^ x mod p")); - /* Compute the shared secret key */ - KEY = silc_calloc(1, sizeof(*KEY)); - silc_mp_init(KEY); - silc_mp_powm(KEY, &payload->x, ske->x, &ske->prop->group->group); - ske->KEY = KEY; - - if (payload->pk_data) { - SILC_LOG_DEBUG(("Verifying public key")); - - if (!silc_pkcs_public_key_decode(payload->pk_data, payload->pk_len, - &public_key)) { - status = SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY; - goto err; - } + /* Decode the public key */ + if (!silc_pkcs_public_key_decode(payload->pk_data, payload->pk_len, + &public_key)) { + status = SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY; + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); + return; } - if (verify_key) { - status = (*verify_key)(ske, payload->pk_data, payload->pk_len, - payload->pk_type, verify_context); - if (status != SILC_SKE_STATUS_OK) - goto err; - - SILC_LOG_DEBUG(("Public key is authentic")); - } + SILC_LOG_DEBUG(("Public key is authentic")); if (payload->pk_data) { /* Compute the hash value */ @@ -408,11 +399,14 @@ SilcSKEStatus silc_ske_initiator_finish(SilcSKE ske, memset(hash, 'F', hash_len); } - /* Call the callback. */ - if (callback) - (*callback)(ske, context); + ske->status = SILC_SKE_STATUS_OK; - return status; + /* Call the callback. The caller may now continue the SKE protocol. */ + if (finish->callback) + finish->callback(ske, finish->context); + + silc_free(finish); + return; err: memset(hash, 'F', sizeof(hash)); @@ -432,6 +426,102 @@ SilcSKEStatus silc_ske_initiator_finish(SilcSKE ske, ske->hash = NULL; } + if (status == SILC_SKE_STATUS_OK) + ske->status = SILC_SKE_STATUS_ERROR; + + ske->status = status; + + /* Call the callback. */ + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); +} + +/* Receives Key Exchange Payload from responder consisting responders + public key, f, and signature. This function verifies the public key, + computes the secret shared key and verifies the signature. + + The `callback' will be called to indicate that the caller may + continue with the SKE protocol. The caller must not continue + before the SKE libary has called that callback. If this function + returns an error the callback will not be called. It is called + if this function return SILC_SKE_STATUS_OK or SILC_SKE_STATUS_PENDING. + However, note that when the library calls the callback the ske->status + may be error. + + This calls the `verify_key' callback to verify the received public + key or certificate. If the `verify_key' is provided then the remote + must send public key and it is considered to be an error if remote + does not send its public key. If caller is performing a re-key with + SKE then the `verify_key' is usually not provided when it is not also + required for the remote to send its public key. */ + +SilcSKEStatus silc_ske_initiator_finish(SilcSKE ske, + SilcBuffer ke_payload, + SilcSKEVerifyCb verify_key, + void *verify_context, + SilcSKECb callback, + void *context) +{ + SilcSKEStatus status = SILC_SKE_STATUS_OK; + SilcSKEKEPayload *payload; + SilcInt *KEY; + SKEInitiatorFinish finish; + + SILC_LOG_DEBUG(("Start")); + + /* Decode the payload */ + status = silc_ske_payload_ke_decode(ske, ke_payload, &payload); + if (status != SILC_SKE_STATUS_OK) { + ske->status = status; + return status; + } + ske->ke2_payload = payload; + + if (!payload->pk_data && verify_key) { + SILC_LOG_DEBUG(("Remote end did not send its public key (or certificate), " + "even though we require it")); + ske->status = SILC_SKE_STATUS_PUBLIC_KEY_NOT_PROVIDED; + goto err; + } + + SILC_LOG_DEBUG(("Computing KEY = f ^ x mod p")); + + /* Compute the shared secret key */ + KEY = silc_calloc(1, sizeof(*KEY)); + silc_mp_init(KEY); + silc_mp_powm(KEY, &payload->x, ske->x, &ske->prop->group->group); + ske->KEY = KEY; + + finish = silc_calloc(1, sizeof(*finish)); + finish->callback = callback; + finish->context = context; + + if (verify_key) { + SILC_LOG_DEBUG(("Verifying public key")); + + (*verify_key)(ske, payload->pk_data, payload->pk_len, + payload->pk_type, verify_context, + silc_ske_initiator_finish_final, finish); + + /* We will continue to the final state after the public key has + been verified by the caller. */ + return SILC_SKE_STATUS_PENDING; + } + + /* Continue to final state */ + silc_ske_initiator_finish_final(ske, SILC_SKE_STATUS_OK, finish); + + return SILC_SKE_STATUS_OK; + + err: + silc_ske_payload_ke_free(payload); + ske->ke2_payload = NULL; + + silc_mp_clear(ske->KEY); + silc_free(ske->KEY); + ske->KEY = NULL; + if (status == SILC_SKE_STATUS_OK) return SILC_SKE_STATUS_ERROR; @@ -590,65 +680,70 @@ SilcSKEStatus silc_ske_responder_phase_1(SilcSKE ske, return status; } -/* This function receives the Key Exchange Payload from the initiator. - This also performs the mutual authentication if required. Then, this - function first generated a random number x, such that 1 < x < q - and computes f = g ^ x mod p. This then puts the result f to a Key - Exchange Payload. */ +/* An responder phase 2 final callback that is called to indicate that + the SKE protocol may continue. */ -SilcSKEStatus silc_ske_responder_phase_2(SilcSKE ske, - SilcBuffer ke_payload, - SilcSKEVerifyCb verify_key, - void *verify_context, - SilcSKECb callback, - void *context) +typedef struct { + SilcSKECb callback; + void *context; +} *SKEResponderPhaseII; + +static void silc_ske_responder_phase2_final(SilcSKE ske, + SilcSKEStatus status, + void *context) { - SilcSKEStatus status = SILC_SKE_STATUS_OK; + SKEResponderPhaseII finish = (SKEResponderPhaseII)context; SilcSKEKEPayload *recv_payload, *send_payload; SilcInt *x, f; - SILC_LOG_DEBUG(("Start")); + recv_payload = ske->ke1_payload; - /* Decode Key Exchange Payload */ - status = silc_ske_payload_ke_decode(ske, ke_payload, &recv_payload); + /* If the caller returns PENDING status SKE library will assume that + the caller will re-call this callback when it is not anymore in + PENDING status. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + + /* If the status is an error then the public key that was verified + by the caller is not authentic. */ if (status != SILC_SKE_STATUS_OK) { ske->status = status; - return status; + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); + return; } - ske->ke1_payload = recv_payload; - - /* Verify the received public key and verify the signature if we are - doing mutual authentication. */ + /* The public key verification was performed only if the Mutual + Authentication flag is set. */ if (ske->start_payload && ske->start_payload->flags & SILC_SKE_SP_FLAG_MUTUAL) { SilcPublicKey public_key = NULL; unsigned char hash[32]; uint32 hash_len; - SILC_LOG_DEBUG(("We are doing mutual authentication")); - SILC_LOG_DEBUG(("Verifying public key")); - + /* Decode the public key */ if (!silc_pkcs_public_key_decode(recv_payload->pk_data, recv_payload->pk_len, &public_key)) { - status = SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY; - return status; - } - - if (verify_key) { - status = (*verify_key)(ske, recv_payload->pk_data, recv_payload->pk_len, - recv_payload->pk_type, verify_context); - if (status != SILC_SKE_STATUS_OK) - return status; + ske->status = SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY; + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); + return; } SILC_LOG_DEBUG(("Public key is authentic")); /* Compute the hash value */ status = silc_ske_make_hash(ske, hash, &hash_len, TRUE); - if (status != SILC_SKE_STATUS_OK) - return status; + if (status != SILC_SKE_STATUS_OK) { + ske->status = status; + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); + return; + } SILC_LOG_DEBUG(("Verifying signature (HASH_i)")); @@ -660,8 +755,11 @@ SilcSKEStatus silc_ske_responder_phase_2(SilcSKE ske, SILC_LOG_DEBUG(("Signature don't match")); - status = SILC_SKE_STATUS_INCORRECT_SIGNATURE; - return status; + ske->status = SILC_SKE_STATUS_INCORRECT_SIGNATURE; + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); + return; } SILC_LOG_DEBUG(("Signature is Ok")); @@ -680,7 +778,11 @@ SilcSKEStatus silc_ske_responder_phase_2(SilcSKE ske, if (status != SILC_SKE_STATUS_OK) { silc_mp_clear(x); silc_free(x); - return status; + ske->status = status; + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); + return; } SILC_LOG_DEBUG(("Computing f = g ^ x mod p")); @@ -696,11 +798,89 @@ SilcSKEStatus silc_ske_responder_phase_2(SilcSKE ske, ske->x = x; ske->ke2_payload = send_payload; - /* Call the callback. */ - if (callback) - (*callback)(ske, context); + /* Call the callback. The caller may now continue with the SKE protocol. */ + ske->status = SILC_SKE_STATUS_OK; + if (finish->callback) + finish->callback(ske, finish->context); + silc_free(finish); +} - return status; +/* This function receives the Key Exchange Payload from the initiator. + This also performs the mutual authentication if required. Then, this + function first generated a random number x, such that 1 < x < q + and computes f = g ^ x mod p. This then puts the result f to a Key + Exchange Payload. + + The `callback' will be called to indicate that the caller may + continue with the SKE protocol. The caller must not continue + before the SKE libary has called that callback. If this function + returns an error the callback will not be called. It is called + if this function return SILC_SKE_STATUS_OK or SILC_SKE_STATUS_PENDING. + However, note that when the library calls the callback the ske->status + may be error. + + This calls the `verify_key' callback to verify the received public + key or certificate if the Mutual Authentication flag is set. If the + `verify_key' is provided then the remote must send public key and it + is considered to be an error if remote does not send its public key. */ + +SilcSKEStatus silc_ske_responder_phase_2(SilcSKE ske, + SilcBuffer ke_payload, + SilcSKEVerifyCb verify_key, + void *verify_context, + SilcSKECb callback, + void *context) +{ + SilcSKEStatus status = SILC_SKE_STATUS_OK; + SilcSKEKEPayload *recv_payload; + SKEResponderPhaseII finish; + + SILC_LOG_DEBUG(("Start")); + + /* Decode Key Exchange Payload */ + status = silc_ske_payload_ke_decode(ske, ke_payload, &recv_payload); + if (status != SILC_SKE_STATUS_OK) { + ske->status = status; + return status; + } + + ske->ke1_payload = recv_payload; + + finish = silc_calloc(1, sizeof(*finish)); + finish->callback = callback; + finish->context = context; + + /* Verify the received public key and verify the signature if we are + doing mutual authentication. */ + if (ske->start_payload && + ske->start_payload->flags & SILC_SKE_SP_FLAG_MUTUAL) { + + SILC_LOG_DEBUG(("We are doing mutual authentication")); + + if (!recv_payload->pk_data && verify_key) { + SILC_LOG_DEBUG(("Remote end did not send its public key (or " + "certificate), even though we require it")); + ske->status = SILC_SKE_STATUS_PUBLIC_KEY_NOT_PROVIDED; + return status; + } + + if (verify_key) { + SILC_LOG_DEBUG(("Verifying public key")); + + (*verify_key)(ske, recv_payload->pk_data, recv_payload->pk_len, + recv_payload->pk_type, verify_context, + silc_ske_responder_phase2_final, finish); + + /* We will continue to the final state after the public key has + been verified by the caller. */ + return SILC_SKE_STATUS_PENDING; + } + } + + /* Continue to final state */ + silc_ske_responder_phase2_final(ske, SILC_SKE_STATUS_OK, finish); + + return SILC_SKE_STATUS_OK; } /* This functions generates the secret key KEY = e ^ x mod p, and, a hash diff --git a/lib/silcske/silcske.h b/lib/silcske/silcske.h index 6774ea7f..1828d10f 100644 --- a/lib/silcske/silcske.h +++ b/lib/silcske/silcske.h @@ -46,15 +46,35 @@ typedef void (*SilcSKESendPacketCb)(SilcSKE ske, SilcBuffer packet, /* Generic SKE callback function. This is called in various SKE routines. The SilcSKE object sent as argument provides all the data - callers routine might need (payloads etc). */ + callers routine might need (payloads etc). This is usually called + to indicate that the application may continue the execution of the + SKE protocol. The application should check the ske->status in this + callback function. */ typedef void (*SilcSKECb)(SilcSKE ske, void *context); -/* Callback function used to verify the received public key. */ -typedef SilcSKEStatus (*SilcSKEVerifyCb)(SilcSKE ske, - unsigned char *pk_data, - uint32 pk_len, - SilcSKEPKType pk_type, - void *context); +/* Completion callback that will be called when the public key + has been verified. The `status' will indicate whether the public + key were trusted or not. If the `status' is PENDING then the status + is not considered to be available at this moment. In this case the + SKE libary will assume that the caller will call this callback again + when the status is available. */ +typedef void (*SilcSKEVerifyCbCompletion)(SilcSKE ske, + SilcSKEStatus status, + void *context); + +/* Callback function used to verify the received public key or certificate. + The verification process is most likely asynchronous. That's why the + application must call the `completion' callback when the verification + process has been completed. The library then calls the user callback + (SilcSKECb), if it was provided for the function that takes this callback + function as argument, to indicate that the SKE protocol may continue. */ +typedef void (*SilcSKEVerifyCb)(SilcSKE ske, + unsigned char *pk_data, + uint32 pk_len, + SilcSKEPKType pk_type, + void *context, + SilcSKEVerifyCbCompletion completion, + void *completion_context); /* Context passed to key material processing function. The function returns the processed key material into this structure. */ diff --git a/lib/silcske/silcske_status.h b/lib/silcske/silcske_status.h index f6ef9212..c2f85c9c 100644 --- a/lib/silcske/silcske_status.h +++ b/lib/silcske/silcske_status.h @@ -36,6 +36,8 @@ typedef enum { SILC_SKE_STATUS_INCORRECT_SIGNATURE = 9, SILC_SKE_STATUS_BAD_VERSION = 10, + SILC_SKE_STATUS_PENDING, + SILC_SKE_STATUS_PUBLIC_KEY_NOT_PROVIDED, SILC_SKE_STATUS_KEY_EXCHANGE_NOT_ACTIVE, SILC_SKE_STATUS_BAD_RESERVED_FIELD, SILC_SKE_STATUS_BAD_PAYLOAD_LENGTH, -- 2.24.0