X-Git-Url: http://git.silcnet.org/gitweb/?a=blobdiff_plain;f=lib%2Fsilcclient%2Fprotocol.c;h=c16d9aa3545570e952a347d05f395b51358f7c85;hb=40f8443d8d3a6577336ee66d18e04d9ac4d956bb;hp=b1b9099ab1beebfbe46b5aa0b471b247ea03726a;hpb=aaf475b988510db3cbce709e5bd2f06e6cf103da;p=silc.git diff --git a/lib/silcclient/protocol.c b/lib/silcclient/protocol.c index b1b9099a..c16d9aa3 100644 --- a/lib/silcclient/protocol.c +++ b/lib/silcclient/protocol.c @@ -2,32 +2,29 @@ protocol.c - Author: Pekka Riikonen + Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2004 Pekka Riikonen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - + the Free Software Foundation; version 2 of the License. + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -/* - * Client side of the protocols. - */ /* $Id$ */ -#include "clientlibincludes.h" +#include "silc.h" +#include "silcclient.h" +#include "client_internal.h" SILC_TASK_CALLBACK(silc_client_protocol_connection_auth); SILC_TASK_CALLBACK(silc_client_protocol_key_exchange); - -extern char *silc_version_string; +SILC_TASK_CALLBACK(silc_client_protocol_rekey); /* * Key Exhange protocol functions @@ -35,88 +32,221 @@ extern char *silc_version_string; /* Function that is called when SKE protocol sends packets to network. */ -static void silc_client_protocol_ke_send_packet(SilcSKE ske, - SilcBuffer packet, - SilcPacketType type, - void *context) +void silc_client_protocol_ke_send_packet(SilcSKE ske, + SilcBuffer packet, + SilcPacketType type, + void *context) { SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = + SilcClientKEInternalContext *ctx = (SilcClientKEInternalContext *)protocol->context; SilcClient client = (SilcClient)ctx->client; /* Send the packet immediately */ silc_client_packet_send(client, ske->sock, type, NULL, 0, NULL, NULL, 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(SilcBool 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 +/* Callback that is called when we have received KE payload from responder. We try to verify the public key now. */ -static SilcSKEStatus -silc_client_protocol_ke_verify_key(SilcSKE ske, - unsigned char *pk_data, - unsigned int pk_len, - SilcSKEPKType pk_type, - void *context) +void silc_client_protocol_ke_verify_key(SilcSKE ske, + unsigned char *pk_data, + SilcUInt32 pk_len, + SilcSKEPKType pk_type, + void *context, + SilcSKEVerifyCbCompletion completion, + void *completion_context) { SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = + SilcClientKEInternalContext *ctx = (SilcClientKEInternalContext *)protocol->context; SilcClient client = (SilcClient)ctx->client; + VerifyKeyContext verify; SILC_LOG_DEBUG(("Start")); - /* Verify server key from user. */ - if (!client->ops->verify_server_key(client, ctx->sock->user_data, - 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->internal->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. */ -static void silc_client_protocol_ke_set_keys(SilcSKE ske, - SilcSocketConnection sock, - SilcSKEKeyMaterial *keymat, - SilcCipher cipher, - SilcPKCS pkcs, - SilcHash hash) +void silc_client_protocol_ke_set_keys(SilcSKE ske, + SilcSocketConnection sock, + SilcSKEKeyMaterial *keymat, + SilcCipher cipher, + SilcPKCS pkcs, + SilcHash hash, + SilcHmac hmac, + SilcSKEDiffieHellmanGroup group, + SilcBool is_responder) { SilcClientConnection conn = (SilcClientConnection)sock->user_data; - SilcHash nhash; + const char *cname = silc_cipher_get_name(cipher); SILC_LOG_DEBUG(("Setting new keys into use")); /* Allocate cipher to be used in the communication */ - silc_cipher_alloc(cipher->cipher->name, &conn->send_key); - silc_cipher_alloc(cipher->cipher->name, &conn->receive_key); - - conn->send_key->cipher->set_key(conn->send_key->context, - keymat->send_enc_key, - keymat->enc_key_len); - conn->send_key->set_iv(conn->send_key, keymat->send_iv); - conn->receive_key->cipher->set_key(conn->receive_key->context, - keymat->receive_enc_key, - keymat->enc_key_len); - conn->receive_key->set_iv(conn->receive_key, keymat->receive_iv); - - /* Allocate PKCS to be used */ -#if 0 - /* XXX Do we ever need to allocate PKCS for the connection?? - If yes, we need to change KE protocol to get the initiators - public key. */ - silc_pkcs_alloc(pkcs->pkcs->name, &conn->public_Key); - silc_pkcs_set_public_key(conn->public_key, ske->ke2_payload->pk_data, - ske->ke2_payload->pk_len); -#endif - - /* Save HMAC key to be used in the communication. */ - silc_hash_alloc(hash->hash->name, &nhash); - silc_hmac_alloc(nhash, &conn->hmac); - silc_hmac_set_key(conn->hmac, keymat->hmac_key, keymat->hmac_key_len); + silc_cipher_alloc((char *)cname, &conn->internal->send_key); + silc_cipher_alloc((char *)cname, &conn->internal->receive_key); + silc_hmac_alloc((char *)silc_hmac_get_name(hmac), NULL, + &conn->internal->hmac_send); + silc_hmac_alloc((char *)silc_hmac_get_name(hmac), NULL, + &conn->internal->hmac_receive); + + if (is_responder == TRUE) { + silc_cipher_set_key(conn->internal->send_key, keymat->receive_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(conn->internal->send_key, keymat->receive_iv); + silc_cipher_set_key(conn->internal->receive_key, keymat->send_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(conn->internal->receive_key, keymat->send_iv); + silc_hmac_set_key(conn->internal->hmac_send, keymat->receive_hmac_key, + keymat->hmac_key_len); + silc_hmac_set_key(conn->internal->hmac_receive, keymat->send_hmac_key, + keymat->hmac_key_len); + } else { + silc_cipher_set_key(conn->internal->send_key, keymat->send_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(conn->internal->send_key, keymat->send_iv); + silc_cipher_set_key(conn->internal->receive_key, keymat->receive_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(conn->internal->receive_key, keymat->receive_iv); + silc_hmac_set_key(conn->internal->hmac_send, keymat->send_hmac_key, + keymat->hmac_key_len); + silc_hmac_set_key(conn->internal->hmac_receive, keymat->receive_hmac_key, + keymat->hmac_key_len); + } + + /* Rekey stuff */ + conn->internal->rekey = silc_calloc(1, sizeof(*conn->internal->rekey)); + conn->internal->rekey->send_enc_key = silc_memdup(keymat->send_enc_key, + keymat->enc_key_len / 8); + conn->internal->rekey->enc_key_len = keymat->enc_key_len / 8; + + if (ske->start_payload->flags & SILC_SKE_SP_FLAG_PFS) + conn->internal->rekey->pfs = TRUE; + conn->internal->rekey->ske_group = silc_ske_group_get_number(group); + + /* Save the HASH function */ + silc_hash_alloc(silc_hash_get_name(hash), &conn->internal->hash); +} + +/* Checks the version string of the server. */ + +SilcSKEStatus silc_ske_check_version(SilcSKE ske, unsigned char *version, + SilcUInt32 len, void *context) +{ + SilcClientConnection conn = (SilcClientConnection)ske->sock->user_data; + SilcClient client = (SilcClient)ske->user_data; + SilcUInt32 l_protocol_version = 0, r_protocol_version = 0; + + if (!silc_parse_version_string(version, &r_protocol_version, NULL, NULL, + NULL, NULL)) { + client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, + "We don't support server version `%s'", + version); + return SILC_SKE_STATUS_BAD_VERSION; + } + + if (!silc_parse_version_string(client->internal->silc_client_version, + &l_protocol_version, NULL, NULL, + NULL, NULL)) { + client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, + "We don't support server version `%s'", + version); + return SILC_SKE_STATUS_BAD_VERSION; + } + + /* If remote is too new, don't connect */ + if (l_protocol_version < r_protocol_version) { + client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, + "We don't support server version `%s'", + version); + return SILC_SKE_STATUS_BAD_VERSION; + } + + ske->sock->version = r_protocol_version; + + return SILC_SKE_STATUS_OK; +} + +/* 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) { + /* Call failure client operation */ + client->internal->ops->failure(client, conn, protocol, + (void *)ske->status); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 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); + + /* 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++; + silc_protocol_execute(protocol, client->schedule, 0, 1); + } } /* Performs key exchange protocol. This is used for both initiator @@ -125,11 +255,11 @@ static void silc_client_protocol_ke_set_keys(SilcSKE ske, SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) { SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = + SilcClientKEInternalContext *ctx = (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")); @@ -145,38 +275,48 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) SilcSKE ske; /* Allocate Key Exchange object */ - ske = silc_ske_alloc(); - ctx->ske = ske; - ske->rng = client->rng; - - if (ctx->responder == TRUE) { -#if 0 - SilcBuffer start_payload; + ctx->ske = ske = silc_ske_alloc(client->rng, client); + silc_ske_set_callbacks(ske, ctx->send_packet, NULL, + ctx->verify, + silc_client_protocol_ke_continue, + silc_ske_check_version, + context); + + if (ctx->responder == TRUE) { + if (!ctx->packet) { + SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", + status)); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 0); + return; + } /* Start the key exchange by processing the received security properties packet from initiator. */ - status = silc_ske_responder_start(ske, ctx->rng, ctx->sock, - start_payload, - silc_client_protocol_ke_send_packet, - context); -#endif + status = + silc_ske_responder_start(ske, ctx->rng, ctx->sock, + client->internal->silc_client_version, + ctx->packet->buffer, TRUE); } else { SilcSKEStartPayload *start_payload; /* Assemble security properties. */ - silc_ske_assemble_security_properties(ske, SILC_SKE_SP_FLAG_NONE, - silc_version_string, - &start_payload); + silc_ske_assemble_security_properties( + ske, SILC_SKE_SP_FLAG_MUTUAL, + client->internal->silc_client_version, + &start_payload); /* Start the key exchange by sending our security properties to the remote end. */ status = silc_ske_initiator_start(ske, ctx->rng, ctx->sock, - start_payload, - silc_client_protocol_ke_send_packet, - context); + start_payload); } + /* 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)); @@ -184,33 +324,38 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) status)); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); + silc_protocol_execute(protocol, client->schedule, 0, 0); return; } - /* Advance the state of the protocol. */ + /* Advance protocol state and call the next state if we are responder */ protocol->state++; + if (ctx->responder == TRUE) + silc_protocol_execute(protocol, client->schedule, 0, 1); } break; case 2: { - /* - * Phase 1 + /* + * Phase 1 */ if (ctx->responder == TRUE) { -#if 0 - status = - silc_ske_responder_phase_1(ctx->ske, - ctx->ske->start_payload, - silc_server_protocol_ke_send_packet, - context); -#endif + /* Sends the selected security properties to the initiator. */ + status = silc_ske_responder_phase_1(ctx->ske); } else { + if (!ctx->packet) { + SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", + status)); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 0); + return; + } + /* Call Phase-1 function. This processes the Key Exchange Start paylaod reply we just got from the responder. The callback function will receive the processed payload where we will save it. */ - status = silc_ske_initiator_phase_1(ctx->ske, ctx->packet, NULL, NULL); + status = silc_ske_initiator_phase_1(ctx->ske, ctx->packet->buffer); } if (status != SILC_SKE_STATUS_OK) { @@ -220,39 +365,50 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) status)); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); + silc_protocol_execute(protocol, client->schedule, 0, 0); return; } - /* Advance the state of the protocol and call the next state. */ + /* Advance protocol state and call next state if we are initiator */ protocol->state++; - protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); + if (ctx->responder == FALSE) + silc_protocol_execute(protocol, client->schedule, 0, 1); } break; case 3: { - /* - * Phase 2 + /* + * Phase 2 */ if (ctx->responder == TRUE) { -#if 0 - status = - silc_ske_responder_phase_2(ctx->ske, - ctx->ske->start_payload, - silc_server_protocol_ke_send_packet, - context); -#endif + if (!ctx->packet) { + SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", + status)); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 0); + return; + } + + /* Process the received Key Exchange 1 Payload packet from + the initiator. This also creates our parts of the Diffie + 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); } else { /* Call the Phase-2 function. This creates Diffie Hellman key exchange parameters and sends our public part inside Key Exhange 1 Payload to the responder. */ - status = - silc_ske_initiator_phase_2(ctx->ske, - client->public_key, - silc_client_protocol_ke_send_packet, - context); + status = silc_ske_initiator_phase_2(ctx->ske, + client->public_key, + client->private_key, + SILC_SKE_PK_TYPE_SILC); + 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)); @@ -260,89 +416,133 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) status)); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); + silc_protocol_execute(protocol, client->schedule, 0, 0); return; } - - /* Advance the state of the protocol. */ - protocol->state++; } break; case 4: { - /* + /* * Finish protocol */ if (ctx->responder == TRUE) { - status = 0; -#if 0 - status = - silc_ske_responder_phase_2(ctx->ske, - ctx->ske->start_payload, - silc_server_protocol_ke_send_packet, - context); -#endif + /* This creates the key exchange material and sends our + public parts to the initiator inside Key Exchange 2 Payload. */ + status = + silc_ske_responder_finish(ctx->ske, + client->public_key, client->private_key, + SILC_SKE_PK_TYPE_SILC); + + /* End the protocol on the next round */ + protocol->state = SILC_PROTOCOL_STATE_END; } else { + if (!ctx->packet) { + SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", + status)); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 0); + return; + } + /* Finish the protocol. This verifies the Key Exchange 2 payload - sent by responder. */ - status = silc_ske_initiator_finish(ctx->ske, ctx->packet, - silc_client_protocol_ke_verify_key, - context, NULL, NULL); + 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); } - 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", - ctx->sock->hostname); + client->internal->ops->say( + client, conn, SILC_CLIENT_MESSAGE_AUDIT, + "Received unsupported server %s public key", + ctx->sock->hostname); } else { - client->ops->say(client, conn, + client->internal->ops->say( + client, conn, SILC_CLIENT_MESSAGE_AUDIT, "Error during key exchange protocol with server %s", ctx->sock->hostname); } protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); + silc_protocol_execute(protocol, client->schedule, 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. */ - silc_ske_end(ctx->ske, silc_client_protocol_ke_send_packet, context); - - /* End the protocol on the next round */ - protocol->state = SILC_PROTOCOL_STATE_END; } break; + case SILC_PROTOCOL_STATE_END: { - /* + /* * End protocol */ SilcSKEKeyMaterial *keymat; + int key_len = silc_cipher_get_key_len(ctx->ske->prop->cipher); + int hash_len = silc_hash_len(ctx->ske->prop->hash); /* Process the key material */ keymat = silc_calloc(1, sizeof(*keymat)); - silc_ske_process_key_material(ctx->ske, 16, (16 * 8), 16, keymat); + status = silc_ske_process_key_material(ctx->ske, 16, key_len, hash_len, + keymat); + if (status != SILC_SKE_STATUS_OK) { + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + silc_ske_free_key_material(keymat); + return; + } + ctx->keymat = keymat; - /* Take the negotiated keys into use. */ - silc_client_protocol_ke_set_keys(ctx->ske, ctx->sock, keymat, - ctx->ske->prop->cipher, - ctx->ske->prop->pkcs, - ctx->ske->prop->hash); + /* Send Ok to the other end if we are responder. If we are initiator + we have sent this already. */ + if (ctx->responder == TRUE) + silc_ske_end(ctx->ske); + + /* Unregister the timeout task since the protocol has ended. + This was the timeout task to be executed if the protocol is + not completed fast enough. */ + if (ctx->timeout_task) + silc_schedule_task_del(client->schedule, ctx->timeout_task); /* Protocol has ended, call the final callback */ if (protocol->final_callback) - protocol->execute_final(client->timeout_queue, 0, protocol, fd); + silc_protocol_execute_final(protocol, client->schedule); else silc_protocol_free(protocol); } break; + case SILC_PROTOCOL_STATE_ERROR: - + /* + * Error during protocol + */ + + /* Send abort notification */ + silc_ske_abort(ctx->ske, ctx->ske->status); + + /* On error the final callback is always called. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, client->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_FAILURE: + /* + * Received failure from remote. + */ + + /* Unregister the timeout task since the protocol has ended. + This was the timeout task to be executed if the protocol is + not completed fast enough. */ + if (ctx->timeout_task) + silc_schedule_task_del(client->schedule, ctx->timeout_task); + /* On error the final callback is always called. */ if (protocol->final_callback) - protocol->execute_final(client->timeout_queue, 0, protocol, fd); + silc_protocol_execute_final(protocol, client->schedule); else silc_protocol_free(protocol); break; @@ -355,10 +555,94 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) * Connection Authentication protocol functions */ +static int +silc_client_get_public_key_auth(SilcClient client, + SilcClientConnection conn, + unsigned char *auth_data, + SilcUInt32 *auth_data_len, + SilcSKE ske) +{ + int len; + SilcPKCS pkcs; + SilcBuffer auth; + + /* Use our default key */ + pkcs = client->pkcs; + + /* Make the authentication data. Protocol says it is HASH plus + KE Start Payload. */ + len = ske->hash_len + ske->start_payload_copy->len; + auth = silc_buffer_alloc(len); + silc_buffer_pull_tail(auth, len); + silc_buffer_format(auth, + SILC_STR_UI_XNSTRING(ske->hash, ske->hash_len), + SILC_STR_UI_XNSTRING(ske->start_payload_copy->data, + ske->start_payload_copy->len), + SILC_STR_END); + + if (silc_pkcs_sign_with_hash(pkcs, ske->prop->hash, auth->data, + auth->len, auth_data, auth_data_len)) { + silc_buffer_free(auth); + return TRUE; + } + + silc_buffer_free(auth); + 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, + SilcUInt32 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; + unsigned char *autf8 = NULL; + + SILC_LOG_DEBUG(("Sending authentication to server")); + + /* Passphrase must be UTF-8 encoded, if it isn't encode it */ + if (ctx->auth_meth == SILC_AUTH_PASSWORD && + !silc_utf8_valid(auth_data, auth_data_len)) { + payload_len = silc_utf8_encoded_len(auth_data, auth_data_len, + SILC_STRING_ASCII); + autf8 = silc_calloc(payload_len, sizeof(*autf8)); + auth_data_len = silc_utf8_encode(auth_data, auth_data_len, + SILC_STRING_ASCII, autf8, payload_len); + auth_data = autf8; + } + + 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); + silc_buffer_free(packet); + silc_free(autf8); + + /* Next state is end of protocol */ + protocol->state = SILC_PROTOCOL_STATE_END; +} + SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) { SilcProtocol protocol = (SilcProtocol)context; - SilcClientConnAuthInternalContext *ctx = + SilcClientConnAuthInternalContext *ctx = (SilcClientConnAuthInternalContext *)protocol->context; SilcClient client = (SilcClient)ctx->client; SilcClientConnection conn = ctx->sock->user_data; @@ -371,21 +655,20 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) switch(protocol->state) { case SILC_PROTOCOL_STATE_START: { - /* + /* * Start protocol. We send authentication data to the server * to be authenticated. */ - SilcBuffer packet; - int payload_len = 0; unsigned char *auth_data = NULL; - unsigned int auth_data_len = 0; + SilcUInt32 auth_data_len = 0; + unsigned char sign[2048 + 1]; switch(ctx->auth_meth) { - case SILC_PROTOCOL_CONN_AUTH_NONE: + case SILC_AUTH_NONE: /* No authentication required */ break; - case SILC_PROTOCOL_CONN_AUTH_PASSWORD: + case SILC_AUTH_PASSWORD: /* Password authentication */ if (ctx->auth_data && ctx->auth_data_len) { auth_data = ctx->auth_data; @@ -393,53 +676,44 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) break; } - 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->internal->ops->say( + client, conn, SILC_CLIENT_MESSAGE_INFO, + "Password authentication required by server %s", + ctx->sock->hostname); + client->internal->ops->ask_passphrase(client, conn, + silc_client_conn_auth_continue, + protocol); + return; break; - case SILC_PROTOCOL_CONN_AUTH_PUBLIC_KEY: - /* XXX */ + case SILC_AUTH_PUBLIC_KEY: + if (!ctx->auth_data) { + /* Public key authentication */ + silc_client_get_public_key_auth(client, conn, sign, &auth_data_len, + ctx->ske); + auth_data = sign; + } else { + auth_data = ctx->auth_data; + auth_data_len = ctx->auth_data_len; + } + break; } - 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; case SILC_PROTOCOL_STATE_END: { - /* + /* * End protocol. Nothing special to be done here. */ /* Protocol has ended, call the final callback */ if (protocol->final_callback) - protocol->execute_final(client->timeout_queue, 0, protocol, fd); + silc_protocol_execute_final(protocol, client->schedule); else silc_protocol_free(protocol); } @@ -447,27 +721,471 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) case SILC_PROTOCOL_STATE_ERROR: { - /* - * Error + /* + * Error. Send notify to remote. */ + unsigned char error[4]; + + SILC_PUT32_MSB(SILC_AUTH_FAILED, error); /* Error in protocol. Send FAILURE packet. Although I don't think this could ever happen on client side. */ silc_client_packet_send(client, ctx->sock, SILC_PACKET_FAILURE, - NULL, 0, NULL, NULL, NULL, 0, TRUE); + NULL, 0, NULL, NULL, error, 4, TRUE); /* On error the final callback is always called. */ if (protocol->final_callback) - protocol->execute_final(client->timeout_queue, 0, protocol, fd); + silc_protocol_execute_final(protocol, client->schedule); else silc_protocol_free(protocol); } + + case SILC_PROTOCOL_STATE_FAILURE: + /* + * Received failure from remote. + */ + + /* On error the final callback is always called. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, client->schedule); + else + silc_protocol_free(protocol); break; + case SILC_PROTOCOL_STATE_UNKNOWN: break; } } +/* + * Re-key protocol routines + */ + +/* Actually takes the new keys into use. */ + +static void +silc_client_protocol_rekey_validate(SilcClient client, + SilcClientRekeyInternalContext *ctx, + SilcSocketConnection sock, + SilcSKEKeyMaterial *keymat, + SilcBool send) +{ + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + + if (ctx->responder == TRUE) { + if (send) { + silc_cipher_set_key(conn->internal->send_key, keymat->receive_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(conn->internal->send_key, keymat->receive_iv); + silc_hmac_set_key(conn->internal->hmac_send, keymat->receive_hmac_key, + keymat->hmac_key_len); + } else { + silc_cipher_set_key(conn->internal->receive_key, keymat->send_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(conn->internal->receive_key, keymat->send_iv); + silc_hmac_set_key(conn->internal->hmac_receive, keymat->send_hmac_key, + keymat->hmac_key_len); + } + } else { + if (send) { + silc_cipher_set_key(conn->internal->send_key, keymat->send_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(conn->internal->send_key, keymat->send_iv); + silc_hmac_set_key(conn->internal->hmac_send, keymat->send_hmac_key, + keymat->hmac_key_len); + } else { + silc_cipher_set_key(conn->internal->receive_key, + keymat->receive_enc_key, keymat->enc_key_len); + silc_cipher_set_iv(conn->internal->receive_key, keymat->receive_iv); + silc_hmac_set_key(conn->internal->hmac_receive, + keymat->receive_hmac_key, keymat->hmac_key_len); + } + } + + /* Save the current sending encryption key */ + if (!send) { + memset(conn->internal->rekey->send_enc_key, 0, + conn->internal->rekey->enc_key_len); + silc_free(conn->internal->rekey->send_enc_key); + conn->internal->rekey->send_enc_key = silc_memdup(keymat->send_enc_key, + keymat->enc_key_len / 8); + conn->internal->rekey->enc_key_len = keymat->enc_key_len / 8; + } +} + +/* This function actually re-generates (when not using PFS) the keys and + takes them into use. */ + +static void +silc_client_protocol_rekey_generate(SilcClient client, + SilcClientRekeyInternalContext *ctx, + SilcBool send) +{ + SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data; + SilcSKEKeyMaterial *keymat; + SilcUInt32 key_len = silc_cipher_get_key_len(conn->internal->send_key); + SilcUInt32 hash_len = silc_hash_len(conn->internal->hash); + + SILC_LOG_DEBUG(("Generating new %s session keys (no PFS)", + send ? "sending" : "receiving")); + + /* Generate the new key */ + keymat = silc_calloc(1, sizeof(*keymat)); + silc_ske_process_key_material_data(conn->internal->rekey->send_enc_key, + conn->internal->rekey->enc_key_len, + 16, key_len, hash_len, + conn->internal->hash, keymat); + + /* Set the keys into use */ + silc_client_protocol_rekey_validate(client, ctx, ctx->sock, keymat, send); + + silc_ske_free_key_material(keymat); +} + +/* This function actually re-generates (with PFS) the keys and + takes them into use. */ + +static void +silc_client_protocol_rekey_generate_pfs(SilcClient client, + SilcClientRekeyInternalContext *ctx, + SilcBool send) +{ + SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data; + SilcSKEKeyMaterial *keymat; + SilcUInt32 key_len = silc_cipher_get_key_len(conn->internal->send_key); + SilcUInt32 hash_len = silc_hash_len(conn->internal->hash); + unsigned char *tmpbuf; + SilcUInt32 klen; + + SILC_LOG_DEBUG(("Generating new %s session keys (with PFS)", + send ? "sending" : "receiving")); + + /* Encode KEY to binary data */ + tmpbuf = silc_mp_mp2bin(ctx->ske->KEY, 0, &klen); + + /* Generate the new key */ + keymat = silc_calloc(1, sizeof(*keymat)); + silc_ske_process_key_material_data(tmpbuf, klen, 16, key_len, hash_len, + conn->internal->hash, keymat); + + /* Set the keys into use */ + silc_client_protocol_rekey_validate(client, ctx, ctx->sock, keymat, send); + + memset(tmpbuf, 0, klen); + silc_free(tmpbuf); + silc_ske_free_key_material(keymat); +} + +/* Packet sending callback. This function is provided as packet sending + routine to the Key Exchange functions. */ + +static void +silc_client_protocol_rekey_send_packet(SilcSKE ske, + SilcBuffer packet, + SilcPacketType type, + void *context) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcClientRekeyInternalContext *ctx = + (SilcClientRekeyInternalContext *)protocol->context; + SilcClient client = (SilcClient)ctx->client; + + /* Send the packet immediately */ + silc_client_packet_send(client, ctx->sock, type, NULL, 0, NULL, NULL, + packet->data, packet->len, FALSE); +} + +/* Performs re-key as defined in the SILC protocol specification. */ + +SILC_TASK_CALLBACK(silc_client_protocol_rekey) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcClientRekeyInternalContext *ctx = + (SilcClientRekeyInternalContext *)protocol->context; + SilcClient client = (SilcClient)ctx->client; + SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data; + SilcSKEStatus status; + + SILC_LOG_DEBUG(("Start")); + + if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN) + protocol->state = SILC_PROTOCOL_STATE_START; + + SILC_LOG_DEBUG(("State=%d", protocol->state)); + + switch(protocol->state) { + case SILC_PROTOCOL_STATE_START: + { + /* + * Start protocol. + */ + + if (ctx->responder == TRUE) { + /* + * We are receiving party + */ + + if (ctx->pfs == TRUE) { + /* + * Use Perfect Forward Secrecy, ie. negotiate the key material + * using the SKE protocol. + */ + + if (!ctx->packet) { + SILC_LOG_WARNING(("Error during Re-key")); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + return; + } + + if (ctx->packet->type != SILC_PACKET_KEY_EXCHANGE_1) { + /* Error in protocol */ + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + } + + ctx->ske = silc_ske_alloc(client->rng, client); + ctx->ske->prop = silc_calloc(1, sizeof(*ctx->ske->prop)); + silc_ske_group_get_by_number(conn->internal->rekey->ske_group, + &ctx->ske->prop->group); + + silc_ske_set_callbacks(ctx->ske, + silc_client_protocol_rekey_send_packet, + NULL, NULL, NULL, silc_ske_check_version, + context); + + status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer); + if (status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (type %d) during Re-key (PFS)", + status)); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + return; + } + + /* Advance the protocol state */ + protocol->state++; + silc_protocol_execute(protocol, client->schedule, 0, 0); + } else { + /* + * Do normal and simple re-key. + */ + + /* Send the REKEY_DONE to indicate we will take new keys into use */ + silc_client_packet_queue_purge(client, ctx->sock); + silc_client_packet_send(client, ctx->sock, + SILC_PACKET_REKEY_DONE, + NULL, 0, NULL, NULL, NULL, 0, FALSE); + + /* After we send REKEY_DONE we must set the sending encryption + key to the new key since all packets after this packet must + encrypted with the new key. */ + silc_client_protocol_rekey_generate(client, ctx, TRUE); + silc_client_packet_queue_purge(client, ctx->sock); + + /* The protocol ends in next stage. */ + protocol->state = SILC_PROTOCOL_STATE_END; + } + + } else { + /* + * We are the initiator of this protocol + */ + + /* Start the re-key by sending the REKEY packet */ + silc_client_packet_send(client, ctx->sock, SILC_PACKET_REKEY, + NULL, 0, NULL, NULL, NULL, 0, FALSE); + + if (ctx->pfs == TRUE) { + /* + * Use Perfect Forward Secrecy, ie. negotiate the key material + * using the SKE protocol. + */ + ctx->ske = silc_ske_alloc(client->rng, client); + ctx->ske->prop = silc_calloc(1, sizeof(*ctx->ske->prop)); + silc_ske_group_get_by_number(conn->internal->rekey->ske_group, + &ctx->ske->prop->group); + + silc_ske_set_callbacks(ctx->ske, + silc_client_protocol_rekey_send_packet, + NULL, NULL, NULL, silc_ske_check_version, + context); + + status = silc_ske_initiator_phase_2(ctx->ske, NULL, NULL, 0); + if (status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (type %d) during Re-key (PFS)", + status)); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + return; + } + + /* Advance the protocol state */ + protocol->state++; + } else { + /* + * Do normal and simple re-key. + */ + + /* Send the REKEY_DONE to indicate we will take new keys into use + now. */ + silc_client_packet_queue_purge(client, ctx->sock); + silc_client_packet_send(client, ctx->sock, + SILC_PACKET_REKEY_DONE, + NULL, 0, NULL, NULL, NULL, 0, FALSE); + + /* After we send REKEY_DONE we must set the sending encryption + key to the new key since all packets after this packet must + encrypted with the new key. */ + silc_client_protocol_rekey_generate(client, ctx, TRUE); + silc_client_packet_queue_purge(client, ctx->sock); + + /* The protocol ends in next stage. */ + protocol->state = SILC_PROTOCOL_STATE_END; + } + } + } + break; + + case 2: + /* + * Second state, used only when oding re-key with PFS. + */ + if (ctx->responder == TRUE) { + if (ctx->pfs == TRUE) { + /* + * Send our KE packe to the initiator now that we've processed + * the initiator's KE packet. + */ + status = silc_ske_responder_finish(ctx->ske, NULL, NULL, + SILC_SKE_PK_TYPE_SILC); + + if (status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (type %d) during Re-key (PFS)", + status)); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + return; + } + } + + } else { + if (ctx->pfs == TRUE) { + /* + * The packet type must be KE packet + */ + if (!ctx->packet) { + SILC_LOG_WARNING(("Error during Re-key")); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + return; + } + + if (ctx->packet->type != SILC_PACKET_KEY_EXCHANGE_2) { + /* Error in protocol */ + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + } + + status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer); + if (status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (type %d) during Re-key (PFS)", + status)); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + return; + } + } + } + + /* Send the REKEY_DONE to indicate we will take new keys into use + now. */ + silc_client_packet_queue_purge(client, ctx->sock); + silc_client_packet_send(client, ctx->sock, SILC_PACKET_REKEY_DONE, + NULL, 0, NULL, NULL, NULL, 0, FALSE); + + /* After we send REKEY_DONE we must set the sending encryption + key to the new key since all packets after this packet must + encrypted with the new key. */ + silc_client_protocol_rekey_generate_pfs(client, ctx, TRUE); + silc_client_packet_queue_purge(client, ctx->sock); + + /* The protocol ends in next stage. */ + protocol->state = SILC_PROTOCOL_STATE_END; + break; + + case SILC_PROTOCOL_STATE_END: + /* + * End protocol + */ + + if (!ctx->packet) { + SILC_LOG_WARNING(("Error during Re-key")); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 300000); + return; + } + + if (ctx->packet->type != SILC_PACKET_REKEY_DONE) { + /* Error in protocol */ + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, client->schedule, 0, 0); + } + + /* We received the REKEY_DONE packet and all packets after this is + encrypted with the new key so set the decryption key to the new key */ + if (ctx->pfs == TRUE) + silc_client_protocol_rekey_generate_pfs(client, ctx, FALSE); + else + silc_client_protocol_rekey_generate(client, ctx, FALSE); + silc_client_packet_queue_purge(client, ctx->sock); + + /* Protocol has ended, call the final callback */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, client->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_ERROR: + /* + * Error occured + */ + + if (ctx->pfs == TRUE) { + /* Send abort notification */ + silc_ske_abort(ctx->ske, ctx->ske->status); + } + + /* On error the final callback is always called. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, client->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_FAILURE: + /* + * We have received failure from remote + */ + + /* On error the final callback is always called. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, client->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_UNKNOWN: + break; + } + +} + /* Registers protocols used in client */ void silc_client_protocols_register(void) @@ -476,6 +1194,8 @@ void silc_client_protocols_register(void) silc_client_protocol_connection_auth); silc_protocol_register(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, silc_client_protocol_key_exchange); + silc_protocol_register(SILC_PROTOCOL_CLIENT_REKEY, + silc_client_protocol_rekey); } /* Unregisters protocols */ @@ -486,4 +1206,6 @@ void silc_client_protocols_unregister(void) silc_client_protocol_connection_auth); silc_protocol_unregister(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, silc_client_protocol_key_exchange); + silc_protocol_unregister(SILC_PROTOCOL_CLIENT_REKEY, + silc_client_protocol_rekey); }