X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=blobdiff_plain;f=lib%2Fsilcclient%2Fclient_keyagr.c;h=a0c791d81cbe90fb316f904e8095e4941a095137;hp=3e9efd5fd9375df0773e79db6d5f8002d9216abe;hb=9905799a86c606304fd7df2cd401de1740a272a1;hpb=5d90b0684f07a8a51d5dff5cd108a328ec82ffa9 diff --git a/lib/silcclient/client_keyagr.c b/lib/silcclient/client_keyagr.c index 3e9efd5f..a0c791d8 100644 --- a/lib/silcclient/client_keyagr.c +++ b/lib/silcclient/client_keyagr.c @@ -2,315 +2,370 @@ client_keyagr.c - Author: Pekka Riikonen + Author: Pekka Riikonen - Copyright (C) 2001 Pekka Riikonen + Copyright (C) 2001 - 2006 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. */ -/* $Id$ */ -/* This file includes the Key Agreement packet processing and actual - key agreement routines. This file has nothing to do with the actual - connection key exchange protocol, it is implemented in the client.c - and in protocol.c. This file implements the client-to-client key - agreement as defined by the SILC protocol. */ - -#include "silcincludes.h" + +#include "silc.h" #include "silcclient.h" #include "client_internal.h" -SILC_TASK_CALLBACK(silc_client_key_agreement_final); -SILC_TASK_CALLBACK(silc_client_process_key_agreement); -SILC_TASK_CALLBACK(silc_client_key_agreement_timeout); -SILC_TASK_CALLBACK(silc_client_perform_key_agreement_start); +/************************** Types and definitions ***************************/ -/* Key agreement context */ +/* Key agreement context, used by responder */ struct SilcClientKeyAgreementStruct { - SilcClient client; - SilcClientConnection conn; - int fd; /* Listening/connection socket */ - SilcSocketConnection sock; /* Remote socket connection */ - SilcClientEntry client_entry; /* Destination client */ + SilcClient client; /* Client */ + SilcClientConnection conn; /* Server connection */ SilcKeyAgreementCallback completion; /* Key agreement completion */ void *context; /* User context */ - SilcTask timeout; /* Timeout task */ - SilcClientKEInternalContext *proto_ctx; /* Key Exchange protocol context */ + SilcClientConnectionParams params; /* Connection parameters */ + SilcPublicKey public_key; /* Responder public key */ + SilcPrivateKey private_key; /* Responder private key */ + SilcNetListener tcp_listener; /* TCP listener */ + SilcPacketStream udp_listener; /* UDP listener */ + SilcPacketStream stream; /* Remote connection (TCP or UDP) */ + SilcAsyncOperation op; /* SKE operation */ + SilcSKE ske; /* SKE */ }; -/* Packet sending function used by the SKE in the key agreement process. */ +/************************ Static utility functions **************************/ + +/* Destroyes key agreement session */ -static void silc_client_key_agreement_send_packet(SilcSKE ske, - SilcBuffer packet, - SilcPacketType type, - void *context) +static void silc_client_keyagr_free(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry) { - SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = - (SilcClientKEInternalContext *)protocol->context; - void *tmp; - - /* Send the packet immediately. We will assure that the packet is not - encrypted by setting the socket's user_data pointer to NULL. The - silc_client_packet_send would take the keys (wrong keys that is, - because user_data is the current SilcClientConnection) from it and - we cannot allow that. The packets are never encrypted when doing SKE - with another client. */ - tmp = ske->sock->user_data; - ske->sock->user_data = NULL; - silc_client_packet_send(ctx->client, ske->sock, type, NULL, 0, NULL, NULL, - packet->data, packet->len, TRUE); - ske->sock->user_data = tmp; + SilcClientKeyAgreement ke = client_entry->internal.ke; + + silc_schedule_task_del_by_context(conn->internal->schedule, client_entry); + + if (ke->op) + silc_async_abort(ke->op, NULL, NULL); + if (ke->ske) + silc_ske_free(ke->ske); + if (ke->tcp_listener) + silc_net_close_listener(ke->tcp_listener); + silc_packet_stream_destroy(ke->stream); + silc_packet_stream_destroy(ke->udp_listener); + + client_entry->internal.ke = NULL; + client_entry->internal.prv_resp = FALSE; + silc_client_unref_client(client, conn, client_entry); + + silc_free(ke); } -/* Timeout callback that is called to close the connection and free the - socket connection data. */ +/* Key agreement timeout callback */ -SILC_TASK_CALLBACK(silc_client_key_agreement_close) +SILC_TASK_CALLBACK(silc_client_keyagr_timeout) { - SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context; + SilcClientEntry client_entry = context; + SilcClientKeyAgreement ke = client_entry->internal.ke; - silc_schedule_unset_listen_fd(ke->client->schedule, ke->sock->sock); - silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd); - silc_net_close_connection(ke->sock->sock); - silc_net_close_connection(ke->fd); - silc_socket_free(ke->sock); - silc_free(ke); + SILC_LOG_DEBUG(("Key agreement %p timeout", ke)); + + ke->completion(ke->client, ke->conn, client_entry, + SILC_KEY_AGREEMENT_TIMEOUT, NULL, ke->context); + + silc_client_keyagr_free(ke->client, ke->conn, client_entry); } -/* This callback is called after the key agreement protocol has been - performed. This calls the final completion callback for the application. */ +/* Client resolving callback. Continues with the key agreement processing */ -SILC_TASK_CALLBACK(silc_client_key_agreement_final) +static void silc_client_keyagr_resolved(SilcClient client, + SilcClientConnection conn, + SilcStatus status, + SilcDList clients, + void *context) { - SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = - (SilcClientKEInternalContext *)protocol->context; - SilcClient client = (SilcClient)ctx->client; - SilcClientKeyAgreement ke = (SilcClientKeyAgreement)ctx->context; - - SILC_LOG_DEBUG(("Start")); - - if (protocol->state == SILC_PROTOCOL_STATE_ERROR || - protocol->state == SILC_PROTOCOL_STATE_FAILURE) { - /* Error occured during protocol */ - ke->client_entry->ke = NULL; - ke->completion(ke->client, ke->conn, ke->client_entry, - SILC_KEY_AGREEMENT_ERROR, NULL, ke->context); - silc_ske_free_key_material(ctx->keymat); - goto out; + /* If no client found, ignore the packet, a silent error */ + if (!clients) + silc_fsm_next(context, silc_client_key_agreement_error); + + /* Continue processing the packet */ + SILC_FSM_CALL_CONTINUE(context); +} + +/* Called after application has verified remote host's public key. Responder + function. */ + +static void silc_client_keyagr_verify_key_cb(SilcBool success, void *context) +{ + VerifyKeyContext verify = context; + + /* 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); +} + +/* Verify remote host's public key. Responder function. */ + +static void silc_client_keyagr_verify_key(SilcSKE ske, + SilcPublicKey public_key, + void *context, + SilcSKEVerifyCbCompletion completion, + void *completion_context) +{ + SilcClientEntry client_entry = context; + SilcClientKeyAgreement ke = client_entry->internal.ke; + SilcClientConnection conn = ke->conn; + SilcClient client = conn->client; + VerifyKeyContext verify; + + /* If we provided repository for SKE and we got here the key was not + found from the repository. */ + if (ke->params.repository && !ke->params.verify_notfound) { + completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY, + completion_context); + return; } - /* Pass the negotiated key material to the application. The application - is responsible of freeing the key material. */ - ke->client_entry->ke = NULL; - ke->completion(ke->client, ke->conn, ke->client_entry, - SILC_KEY_AGREEMENT_OK, ctx->keymat, ke->context); - - out: - silc_protocol_free(protocol); - if (ctx->ske) - silc_ske_free(ctx->ske); - if (ctx->dest_id) - silc_free(ctx->dest_id); - silc_schedule_task_del_by_fd(client->schedule, ke->fd); - silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd); - silc_net_close_connection(ke->fd); - if (ke->timeout) - silc_schedule_task_del(client->schedule, ke->timeout); - silc_client_del_socket(ke->client, ke->sock); - - silc_schedule_task_add(client->schedule, 0, - silc_client_key_agreement_close, - (void *)ke, 0, 1, - SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); - - silc_free(ctx); + SILC_LOG_DEBUG(("Verify remote public key")); + + verify = silc_calloc(1, sizeof(*verify)); + if (!verify) { + completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY, + completion_context); + return; + } + verify->ske = ske; + verify->completion = completion; + verify->completion_context = completion_context; + + /* Verify public key in application */ + client->internal->ops->verify_public_key(client, conn, + SILC_CONN_CLIENT, public_key, + silc_client_keyagr_verify_key_cb, + verify); } -/* Key agreement callback that is called when remote end has initiated - the key agreement protocol. This accepts the incoming TCP/IP connection - for the key agreement protocol. */ +/* Key exchange protocol completion callback. Responder function. */ -SILC_TASK_CALLBACK(silc_client_process_key_agreement) +static void silc_client_keyagr_completion(SilcSKE ske, + SilcSKEStatus status, + SilcSKESecurityProperties prop, + SilcSKEKeyMaterial keymat, + SilcSKERekeyMaterial rekey, + void *context) { - SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context; - SilcClient client = ke->client; + SilcClientEntry client_entry = context; + SilcClientKeyAgreement ke = client_entry->internal.ke; SilcClientConnection conn = ke->conn; - SilcSocketConnection newsocket; - SilcClientKEInternalContext *proto_ctx; - int sock; - - SILC_LOG_DEBUG(("Start")); - - sock = silc_net_accept_connection(ke->fd); - if (sock < 0) { - client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, - "Could not accept key agreement connection: ", - strerror(errno)); - ke->client_entry->ke = NULL; - ke->completion(ke->client, ke->conn, ke->client_entry, - SILC_KEY_AGREEMENT_ERROR, NULL, ke->context); - silc_schedule_task_del_by_fd(client->schedule, ke->fd); - silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd); - silc_net_close_connection(ke->fd); - if (ke->timeout) - silc_schedule_task_del(client->schedule, ke->timeout); - silc_free(ke); + SilcClient client = conn->client; + + if (status != SILC_SKE_STATUS_OK) { + /* Key exchange failed */ + ke->completion(client, conn, client_entry, + status == SILC_SKE_STATUS_TIMEOUT ? + SILC_KEY_AGREEMENT_TIMEOUT : + SILC_KEY_AGREEMENT_FAILURE, NULL, ke->context); + silc_client_keyagr_free(client, conn, client_entry); + return; + } + + /* Returns the negotiated key material to application. Key agreement + was successful. */ + ke->completion(client, conn, client_entry, SILC_KEY_AGREEMENT_OK, + keymat, ke->context); + + silc_client_keyagr_free(client, conn, client_entry); +} + +/* Starts key agreement as responder. */ + +static void silc_client_process_key_agreement(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry) +{ + SilcClientKeyAgreement ke = client_entry->internal.ke; + SilcSKEParamsStruct params; + + SILC_LOG_DEBUG(("Processing key agrement %p session", ke)); + + /* Allocate SKE */ + ke->ske = silc_ske_alloc(client->rng, conn->internal->schedule, + ke->params.repository, ke->public_key, + ke->private_key, client_entry); + if (!ke->ske) { + ke->completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY, + NULL, ke->context); + silc_client_keyagr_free(client, conn, client_entry); return; } - /* Set socket options */ - silc_net_set_socket_nonblock(sock); - silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); - - /* Create socket for this connection (it is of type UNKNOWN since this - really is not a real SILC connection. It is only for the key - agreement protocol). */ - silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, (void *)conn, &newsocket); - ke->sock = newsocket; - - /* Perform name and address lookups for the remote host. */ - silc_net_check_host_by_sock(sock, &newsocket->hostname, &newsocket->ip); - if (!newsocket->hostname && !newsocket->ip) { - client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, - "Could not resolve the remote IP or hostname"); - ke->client_entry->ke = NULL; - ke->completion(ke->client, ke->conn, ke->client_entry, - SILC_KEY_AGREEMENT_ERROR, NULL, ke->context); - silc_schedule_task_del_by_fd(client->schedule, ke->fd); - silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd); - silc_net_close_connection(ke->fd); - if (ke->timeout) - silc_schedule_task_del(client->schedule, ke->timeout); - silc_free(ke); + /* Set SKE parameters */ + params.version = client->internal->silc_client_version; + params.flags = SILC_SKE_SP_FLAG_MUTUAL; + if (ke->params.udp) { + params.flags |= SILC_SKE_SP_FLAG_IV_INCLUDED; + params.session_port = ke->params.local_port; + } + + silc_ske_set_callbacks(ke->ske, silc_client_keyagr_verify_key, + silc_client_keyagr_completion, client_entry); + + /* Start key exchange as responder */ + ke->op = silc_ske_responder(ke->ske, ke->stream, ¶ms); + if (!ke->op) { + ke->completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR, + NULL, ke->context); + silc_client_keyagr_free(client, conn, client_entry); + } +} + +/* TCP network listener callback. Accepts new key agreement connection. + Responder function. */ + +static void silc_client_tcp_accept(SilcNetStatus status, + SilcStream stream, + void *context) +{ + SilcClientEntry client_entry = context; + SilcClientKeyAgreement ke = client_entry->internal.ke; + + /* Create packet stream */ + ke->stream = silc_packet_stream_create(ke->client->internal->packet_engine, + ke->conn->internal->schedule, stream); + if (!ke->stream) { + silc_stream_destroy(stream); return; } - if (!newsocket->hostname) - newsocket->hostname = strdup(newsocket->ip); - newsocket->port = silc_net_get_remote_port(sock); - silc_client_add_socket(client, newsocket); - - /* Allocate internal context for key exchange protocol. This is - sent as context for the protocol. */ - proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); - proto_ctx->client = client; - proto_ctx->sock = silc_socket_dup(newsocket); - proto_ctx->rng = client->rng; - proto_ctx->responder = TRUE; - proto_ctx->context = context; - proto_ctx->send_packet = silc_client_key_agreement_send_packet; - proto_ctx->verify = silc_client_protocol_ke_verify_key; - ke->proto_ctx = proto_ctx; - - /* Prepare the connection for key exchange protocol. We allocate the - protocol but will not start it yet. The connector will be the - initiator of the protocol thus we will wait for initiation from - there before we start the protocol. */ - silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, - &newsocket->protocol, proto_ctx, - silc_client_key_agreement_final); - - /* Register the connection for network input and output. This sets - that scheduler will listen for incoming packets for this connection - and sets that outgoing packets may be sent to this connection as well. - However, this doesn't set the scheduler for outgoing traffic, it - will be set separately by calling SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT, - later when outgoing data is available. */ - context = (void *)client; - SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(sock); + + /* Process session */ + silc_client_process_key_agreement(ke->client, ke->conn, client_entry); } -/* Timeout occured during key agreement. This means that the key agreement - protocol was not completed in the specified timeout. We will call the - completion callback. */ +/* UDP network listener callback. Accepts new key agreement session. + Responder function. */ -SILC_TASK_CALLBACK(silc_client_key_agreement_timeout) +static SilcBool silc_client_udp_accept(SilcPacketEngine engine, + SilcPacketStream stream, + SilcPacket packet, + void *callback_context, + void *stream_context) { - SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context; + SilcClientEntry client_entry = callback_context; + SilcClientKeyAgreement ke = client_entry->internal.ke; + SilcUInt16 port; + const char *ip; + + /* We want only key exchange packet. Eat other packets so that default + packet callback doesn't get them. */ + if (packet->type != SILC_PACKET_KEY_EXCHANGE) { + silc_packet_free(packet); + return TRUE; + } - ke->client_entry->ke = NULL; - ke->completion(ke->client, ke->conn, ke->client_entry, - SILC_KEY_AGREEMENT_TIMEOUT, NULL, ke->context); + /* Create packet stream for this remote UDP session */ + if (!silc_packet_get_sender(packet, &ip, &port)) { + silc_packet_free(packet); + return TRUE; + } + ke->stream = silc_packet_stream_add_remote(stream, ip, port, packet); + if (!ke->stream) { + silc_packet_free(packet); + return TRUE; + } - if (ke->sock) { - silc_client_del_socket(ke->client, ke->sock); - silc_socket_free(ke->sock); + /* Process session */ + silc_client_process_key_agreement(ke->client, ke->conn, client_entry); + return TRUE; +} + +/* Client connect completion callback. Initiator function. */ + +static void silc_client_keyagr_perform_cb(SilcClient client, + SilcClientConnection conn, + SilcClientConnectionStatus status, + SilcStatus error, + const char *message, + void *context) +{ + SilcClientEntry client_entry = context; + SilcClientKeyAgreement ke = client_entry->internal.ke; + SilcSKEKeyMaterial keymat; + + ke->op = NULL; + + switch (status) { + case SILC_CLIENT_CONN_SUCCESS: + SILC_LOG_DEBUG(("Key agreement %p successful", ke)); + + keymat = silc_ske_get_key_material(conn->internal->ske); + ke->completion(ke->client, ke->conn, client_entry, SILC_KEY_AGREEMENT_OK, + keymat, ke->context); + break; + + case SILC_CLIENT_CONN_ERROR_TIMEOUT: + SILC_LOG_DEBUG(("Key agreement %p timeout", ke)); + ke->completion(ke->client, ke->conn, client_entry, + SILC_KEY_AGREEMENT_TIMEOUT, NULL, ke->context); + break; + + default: + SILC_LOG_DEBUG(("Key agreement %p error %d", ke, status)); + ke->completion(ke->client, ke->conn, client_entry, + SILC_KEY_AGREEMENT_FAILURE, NULL, ke->context); + break; } - if (ke->proto_ctx && ke->proto_ctx->ske) - silc_ske_free(ke->proto_ctx->ske); - ke->client_entry->ke = NULL; - if (ke->fd) - silc_schedule_task_del_by_fd(ke->client->schedule, ke->fd); - silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd); - silc_net_close_connection(ke->fd); - silc_free(ke); + + /* Close the created connection */ + if (conn) + silc_client_close_connection(ke->client, conn); + + silc_client_keyagr_free(ke->client, ke->conn, client_entry); } -/* Sends key agreement request to the remote client indicated by the - `client_entry'. If the caller provides the `hostname' and the `port' - arguments then the library will bind the client to that hostname and - that port for the key agreement protocol. It also sends the `hostname' - and the `port' in the key agreement packet to the remote client. This - would indicate that the remote client may initiate the key agreement - protocol to the `hostname' on the `port'. If port is zero then the - bound port is undefined (the operating system defines it). - - If the `hostname' and `port' is not provided then empty key agreement - packet is sent to the remote client. The remote client may reply with - the same packet including its hostname and port. If the library receives - the reply from the remote client the `key_agreement' client operation - callback will be called to verify whether the user wants to perform the - key agreement or not. - - NOTE: If the application provided the `hostname' and the `port' and the - remote side initiates the key agreement protocol it is not verified - from the user anymore whether the protocol should be executed or not. - By setting the `hostname' and `port' the user gives permission to - perform the protocol (we are responder in this case). - - NOTE: If the remote side decides not to initiate the key agreement - or decides not to reply with the key agreement packet then we cannot - perform the key agreement at all. If the key agreement protocol is - performed the `completion' callback with the `context' will be called. - If remote side decides to ignore the request the `completion' will be - called after the specified timeout, `timeout_secs'. - - NOTE: If the `hostname' and the `port' was not provided the `completion' - will not be called at all since this does nothing more than sending - a packet to the remote host. - - NOTE: There can be only one active key agreement for one client entry. - Before setting new one, the old one must be finished (it is finished - after calling the completion callback) or the function - silc_client_abort_key_agreement must be called. */ +/* Packet stream callbacks */ +static SilcPacketCallbacks silc_client_keyagr_stream_cb = +{ + silc_client_udp_accept, NULL, NULL +}; + +/*************************** Key Agreement API ******************************/ + +/* Sends key agreement packet to remote client. If IP addresses are provided + creates also listener for íncoming key agreement connection. Supports + both TCP and UDP transports. */ void silc_client_send_key_agreement(SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry, - const char *hostname, - const char *bindhost, - int port, - SilcUInt32 timeout_secs, + SilcClientConnectionParams *params, + SilcPublicKey public_key, + SilcPrivateKey private_key, SilcKeyAgreementCallback completion, void *context) { - SilcSocketConnection sock = conn->sock; SilcClientKeyAgreement ke = NULL; SilcBuffer buffer; + SilcUInt16 port = 0, protocol = 0; + char *local_ip = NULL; + SilcStream stream; + + SILC_LOG_DEBUG(("Sending key agreement")); if (!client_entry) return; + if (conn->internal->disconnected) + return; - if (client_entry->ke) { + if (client_entry->internal.ke) { completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ALREADY_STARTED, NULL, context); return; @@ -322,216 +377,143 @@ void silc_client_send_key_agreement(SilcClient client, return; } - /* Create the listener if hostname and port was provided. - * also, use bindhost if it was specified. - */ - - if (hostname) { + /* If local IP is provided, create listener */ + if (params && (params->local_ip || params->bind_ip)) { ke = silc_calloc(1, sizeof(*ke)); - - if (bindhost) - ke->fd = silc_net_create_server(port, bindhost); - else - ke->fd = silc_net_create_server(port, hostname); - - if (ke->fd < 0) { - client->internal->ops->say( - client, conn, SILC_CLIENT_MESSAGE_ERROR, - "Cannot create listener on %s on port %d: %s", - (bindhost) ? bindhost:hostname, port, strerror(errno)); - completion(client, conn, client_entry, SILC_KEY_AGREEMENT_FAILURE, + if (!ke) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY, NULL, context); - silc_free(ke); return; } + /* Create network listener */ + if (params->udp) { + /* UDP listener */ + stream = silc_net_udp_connect(params->bind_ip ? params->bind_ip : + params->local_ip, params->local_port, + NULL, 0, conn->internal->schedule); + ke->udp_listener = + silc_packet_stream_create(client->internal->packet_engine, + conn->internal->schedule, stream); + if (!ke->udp_listener) { + client->internal->ops->say( + client, conn, SILC_CLIENT_MESSAGE_ERROR, + "Cannot create UDP listener on %s on port %d: %s", + params->bind_ip ? params->bind_ip : + params->local_ip, params->local_port, strerror(errno)); + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR, + NULL, context); + if (stream) + silc_stream_destroy(stream); + silc_free(ke); + return; + } + silc_packet_stream_link(ke->udp_listener, + &silc_client_keyagr_stream_cb, + client_entry, 1000000, + SILC_PACKET_ANY, -1); + + port = params->local_port; + if (!port) { + /* Get listener port */ + SilcSocket sock; + silc_socket_stream_get_info(stream, &sock, NULL, NULL, NULL); + port = silc_net_get_local_port(sock); + } + } else { + /* TCP listener */ + ke->tcp_listener = + silc_net_tcp_create_listener(params->bind_ip ? + (const char **)¶ms->bind_ip : + (const char **)¶ms->local_ip, + 1, params->local_port, FALSE, FALSE, + conn->internal->schedule, + silc_client_tcp_accept, + client_entry); + if (!ke->tcp_listener) { + client->internal->ops->say( + client, conn, SILC_CLIENT_MESSAGE_ERROR, + "Cannot create listener on %s on port %d: %s", + params->bind_ip ? params->bind_ip : + params->local_ip, params->local_port, strerror(errno)); + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR, + NULL, context); + silc_free(ke); + return; + } + + port = params->local_port; + if (!port) { + /* Get listener port */ + SilcUInt16 *ports; + ports = silc_net_listener_get_port(ke->tcp_listener, NULL); + port = ports[0]; + silc_free(ports); + } + } + + local_ip = params->local_ip; + protocol = params->udp; + ke->client = client; ke->conn = conn; - ke->client_entry = client_entry; ke->completion = completion; ke->context = context; - - /* Add listener task to the scheduler. This task receives the key - negotiations. */ - silc_schedule_task_add(client->schedule, ke->fd, - silc_client_process_key_agreement, - (void *)ke, 0, 0, - SILC_TASK_FD, - SILC_TASK_PRI_NORMAL); - - /* Register a timeout task that will be executed if the connector - will not start the key exchange protocol within the specified - timeout. */ - ke->timeout = silc_schedule_task_add(client->schedule, 0, - silc_client_key_agreement_timeout, - (void *)ke, timeout_secs, 0, - SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); + ke->params = *params; + ke->public_key = public_key; + ke->private_key = private_key; + silc_client_ref_client(client, conn, client_entry); + client_entry->internal.ke = ke; + client_entry->internal.prv_resp = TRUE; } /* Encode the key agreement payload */ - buffer = silc_key_agreement_payload_encode(hostname, - !ke ? port : - silc_net_get_local_port(ke->fd)); + buffer = silc_key_agreement_payload_encode(local_ip, protocol, port); + if (!buffer) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY, + NULL, context); + silc_client_keyagr_free(client, conn, client_entry); + return; + } /* Send the key agreement packet to the client */ - silc_client_packet_send(client, sock, SILC_PACKET_KEY_AGREEMENT, - client_entry->id, SILC_ID_CLIENT, NULL, NULL, - buffer->data, buffer->len, FALSE); - silc_buffer_free(buffer); -} - -static int -silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx) -{ - int sock; - - /* Create connection to server asynchronously */ - sock = silc_net_create_connection_async(NULL, ctx->port, ctx->host); - if (sock < 0) - return -1; - - /* Register task that will receive the async connect and will - read the result. */ - ctx->task = silc_schedule_task_add(ctx->client->schedule, sock, - silc_client_perform_key_agreement_start, - (void *)ctx, 0, 0, - SILC_TASK_FD, - SILC_TASK_PRI_NORMAL); - silc_schedule_set_listen_fd(ctx->client->schedule, sock, SILC_TASK_WRITE); - - ctx->sock = sock; - - return sock; -} - -/* Routine used by silc_client_perform_key_agreement to create connection - to the remote client on specified port. */ - -static int -silc_client_connect_to_client(SilcClient client, - SilcClientConnection conn, int port, - char *host, void *context) -{ - SilcClientInternalConnectContext *ctx; - - /* Allocate internal context for connection process. This is - needed as we are doing async connecting. */ - ctx = silc_calloc(1, sizeof(*ctx)); - ctx->client = client; - ctx->conn = conn; - ctx->host = strdup(host); - ctx->port = port; - ctx->tries = 0; - ctx->context = context; - - /* Do the actual connecting process */ - return silc_client_connect_to_client_internal(ctx); -} - -/* Callback that is called after connection has been created. This actually - starts the key agreement protocol. This is initiator function. */ - -SILC_TASK_CALLBACK(silc_client_perform_key_agreement_start) -{ - SilcClientInternalConnectContext *ctx = - (SilcClientInternalConnectContext *)context; - SilcClient client = ctx->client; - SilcClientConnection conn = ctx->conn; - SilcClientKeyAgreement ke = (SilcClientKeyAgreement)ctx->context; - int opt, opt_len = sizeof(opt); - - SILC_LOG_DEBUG(("Start")); - - /* Check the socket status as it might be in error */ - silc_net_get_socket_opt(fd, SOL_SOCKET, SO_ERROR, &opt, &opt_len); - if (opt != 0) { - if (ctx->tries < 2) { - /* Connection failed but lets try again */ - client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, - "Could not connect to client %s: %s", - ctx->host, strerror(opt)); - client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, - "Connecting to port %d of client %s resumed", - ctx->port, ctx->host); - - /* Unregister old connection try */ - silc_schedule_unset_listen_fd(client->schedule, fd); - silc_net_close_connection(fd); - silc_schedule_task_del(client->schedule, ctx->task); - - /* Try again */ - silc_client_connect_to_client_internal(ctx); - ctx->tries++; - } else { - /* Connection failed and we won't try anymore */ - client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, - "Could not connect to client %s: %s", - ctx->host, strerror(opt)); - silc_schedule_unset_listen_fd(client->schedule, fd); - silc_net_close_connection(fd); - silc_schedule_task_del(client->schedule, ctx->task); - silc_free(ctx->host); - silc_free(ctx); - - /* Call the completion callback */ - ke->completion(ke->client, ke->conn, ke->client_entry, - SILC_KEY_AGREEMENT_FAILURE, NULL, ke->context); - silc_free(ke); - } + if (!silc_packet_send_ext(conn->stream, SILC_PACKET_KEY_AGREEMENT, 0, + 0, NULL, SILC_ID_CLIENT, &client_entry->id, + silc_buffer_datalen(buffer), NULL, NULL)) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR, + NULL, context); + silc_client_keyagr_free(client, conn, client_entry); + silc_buffer_free(buffer); return; } - silc_schedule_unset_listen_fd(client->schedule, fd); - silc_schedule_task_del(client->schedule, ctx->task); - - ke->fd = fd; + /* Add key agreement timeout task */ + if (params && params->timeout_secs) + silc_schedule_task_add_timeout(conn->internal->schedule, + silc_client_keyagr_timeout, + client_entry, params->timeout_secs, 0); - /* Now actually perform the key agreement protocol */ - silc_client_perform_key_agreement_fd(ke->client, ke->conn, - ke->client_entry, ke->fd, ctx->host, - ke->completion, ke->context); - silc_free(ke); - silc_free(ctx->host); - silc_free(ctx); + silc_buffer_free(buffer); } -/* Performs the actual key agreement protocol. Application may use this - to initiate the key agreement protocol. This can be called for example - after the application has received the `key_agreement' client operation, - and did not return TRUE from it. - - The `hostname' is the remote hostname (or IP address) and the `port' - is the remote port. The `completion' callback with the `context' will - be called after the key agreement protocol. - - NOTE: If the application returns TRUE in the `key_agreement' client - operation the library will automatically start the key agreement. In this - case the application must not call this function. However, application - may choose to just ignore the `key_agreement' client operation (and - merely just print information about it on the screen) and call this - function when the user whishes to do so (by, for example, giving some - specific command). Thus, the API provides both, automatic and manual - initiation of the key agreement. Calling this function is the manual - initiation and returning TRUE in the `key_agreement' client operation - is the automatic initiation. */ +/* Perform key agreement protocol as initiator. Conneects to remote host. */ void silc_client_perform_key_agreement(SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry, - char *hostname, - int port, + SilcClientConnectionParams *params, + SilcPublicKey public_key, + SilcPrivateKey private_key, + char *hostname, int port, SilcKeyAgreementCallback completion, void *context) { SilcClientKeyAgreement ke; - SILC_LOG_DEBUG(("Start")); + SILC_LOG_DEBUG(("Performing key agreement")); - if (!client_entry) - return; - - if (!hostname || !port) { - completion(client, conn, client_entry, SILC_KEY_AGREEMENT_FAILURE, + if (!client_entry || !hostname || !port) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR, NULL, context); return; } @@ -543,43 +525,55 @@ void silc_client_perform_key_agreement(SilcClient client, } ke = silc_calloc(1, sizeof(*ke)); + if (!ke) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY, + NULL, context); + return; + } ke->client = client; ke->conn = conn; - ke->client_entry = client_entry; ke->completion = completion; ke->context = context; - - /* Connect to the remote client */ - ke->fd = silc_client_connect_to_client(client, conn, port, hostname, ke); - if (ke->fd < 0) { - completion(client, conn, client_entry, SILC_KEY_AGREEMENT_FAILURE, + silc_client_ref_client(client, conn, client_entry); + client_entry->internal.ke = ke; + + if (params) + params->no_authentication = TRUE; + + /* Connect to the remote client. Performs key exchange automatically. */ + if (!silc_client_connect_to_client(client, params, public_key, + private_key, hostname, port, + silc_client_keyagr_perform_cb, + client_entry)) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR, NULL, context); - silc_free(ke); + silc_client_keyagr_free(client, conn, client_entry); return; } } -/* Same as above but application has created already the connection to - the remote host. The `sock' is the socket to the remote connection. - Application can use this function if it does not want the client library - to create the connection. */ - -void silc_client_perform_key_agreement_fd(SilcClient client, - SilcClientConnection conn, - SilcClientEntry client_entry, - int sock, - char *hostname, - SilcKeyAgreementCallback completion, - void *context) +/* Same as above but caller has created connection. */ + +void +silc_client_perform_key_agreement_stream(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry, + SilcClientConnectionParams *params, + SilcPublicKey public_key, + SilcPrivateKey private_key, + SilcStream stream, + SilcKeyAgreementCallback completion, + void *context) { SilcClientKeyAgreement ke; - SilcClientKEInternalContext *proto_ctx; - SilcProtocol protocol; - SILC_LOG_DEBUG(("Start")); + SILC_LOG_DEBUG(("Performing key agreement")); - if (!client_entry) + if (!client_entry || !stream) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR, + NULL, context); return; + } if (client_entry == conn->local_entry) { completion(client, conn, client_entry, SILC_KEY_AGREEMENT_SELF_DENIED, @@ -588,153 +582,129 @@ void silc_client_perform_key_agreement_fd(SilcClient client, } ke = silc_calloc(1, sizeof(*ke)); + if (!ke) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY, + NULL, context); + return; + } ke->client = client; ke->conn = conn; - ke->client_entry = client_entry; - ke->fd = sock; ke->completion = completion; ke->context = context; - - /* Allocate new socket connection object */ - silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, (void *)conn, &ke->sock); - silc_client_add_socket(client, ke->sock); - ke->sock->hostname = strdup(hostname); - ke->sock->port = silc_net_get_remote_port(sock); - - /* Allocate internal context for key exchange protocol. This is - sent as context for the protocol. */ - proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); - proto_ctx->client = client; - proto_ctx->sock = silc_socket_dup(ke->sock); - proto_ctx->rng = client->rng; - proto_ctx->responder = FALSE; - proto_ctx->context = ke; - proto_ctx->send_packet = silc_client_key_agreement_send_packet; - proto_ctx->verify = silc_client_protocol_ke_verify_key; - ke->proto_ctx = proto_ctx; - - /* Perform key exchange protocol. */ - silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, - &protocol, (void *)proto_ctx, - silc_client_key_agreement_final); - ke->sock->protocol = protocol; - - /* Register the connection for network input and output. This sets - that scheduler will listen for incoming packets for this connection - and sets that outgoing packets may be sent to this connection as well. - However, this doesn't set the scheduler for outgoing traffic, it will - be set separately by calling SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT, - later when outgoing data is available. */ - context = (void *)client; - SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(sock); - - /* Execute the protocol */ - silc_protocol_execute(protocol, client->schedule, 0, 0); + silc_client_ref_client(client, conn, client_entry); + client_entry->internal.ke = ke; + + if (params) + params->no_authentication = TRUE; + + /* Perform key exchange protocol */ + if (!silc_client_key_exchange(client, params, public_key, + private_key, stream, SILC_CONN_CLIENT, + silc_client_keyagr_perform_cb, + client_entry)) { + completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR, + NULL, context); + silc_client_keyagr_free(client, conn, client_entry); + return; + } } /* This function can be called to unbind the hostname and the port for - the key agreement protocol. However, this function has effect only + the key agreement protocol. However, this function has effect only before the key agreement protocol has been performed. After it has - been performed the library will automatically unbind the port. The - `client_entry' is the client to which we sent the key agreement + been performed the library will automatically unbind the port. The + `client_entry' is the client to which we sent the key agreement request. */ void silc_client_abort_key_agreement(SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry) { - if (!client_entry) + SilcClientKeyAgreement ke; + + if (!client_entry || !client_entry->internal.ke) return; - if (client_entry->ke) { - SilcClientKeyAgreement ke; + ke = client_entry->internal.ke; - if (client_entry->ke->sock) { - silc_client_del_socket(client_entry->ke->client, client_entry->ke->sock); - silc_socket_free(client_entry->ke->sock); - } - silc_schedule_task_del_by_fd(client->schedule, client_entry->ke->fd); - if (client_entry->ke->timeout) - silc_schedule_task_del(client->schedule, - client_entry->ke->timeout); - ke = client_entry->ke; - client_entry->ke = NULL; - ke->completion(client, conn, client_entry, - SILC_KEY_AGREEMENT_ABORTED, NULL, ke->context); - silc_free(ke); - } + SILC_LOG_DEBUG(("Abort key agreement %p")); + + ke->completion(client, conn, client_entry, + SILC_KEY_AGREEMENT_ABORTED, NULL, ke->context); + + silc_client_keyagr_free(client, conn, client_entry); } -/* Callback function that is called after we've resolved the client - information who sent us the key agreement packet from the server. - We actually call the key_agreement client operation now. */ +/* Key agreement packet received */ -static void -silc_client_key_agreement_resolve_cb(SilcClient client, - SilcClientConnection conn, - SilcClientEntry *clients, - SilcUInt32 clients_count, - void *context) +SILC_FSM_STATE(silc_client_key_agreement) { - SilcPacketContext *packet = (SilcPacketContext *)context; + SilcClientConnection conn = fsm_context; + SilcClient client = conn->client; + SilcPacket packet = state_context; + SilcClientID remote_id; + SilcClientEntry remote_client; SilcKeyAgreementPayload payload; - int ret; - SilcKeyAgreementCallback completion; - void *completion_context; - if (!clients) - goto out; + if (packet->src_id_type != SILC_ID_CLIENT) { + /** Invalid packet */ + silc_fsm_next(fsm, silc_client_key_agreement_error); + SILC_FSM_CONTINUE; + } + + if (!silc_id_str2id(packet->src_id, packet->src_id_len, SILC_ID_CLIENT, + &remote_id, sizeof(remote_id))) { + /** Invalid source ID */ + silc_fsm_next(fsm, silc_client_key_agreement_error); + SILC_FSM_CONTINUE; + } + + /* Check whether we know this client already */ + remote_client = silc_client_get_client_by_id(client, conn, &remote_id); + if (!remote_client || !remote_client->nickname[0]) { + /** Resolve client info */ + silc_client_unref_client(client, conn, remote_client); + SILC_FSM_CALL(silc_client_get_client_by_id_resolve( + client, conn, &remote_id, NULL, + silc_client_keyagr_resolved, fsm)); + /* NOT REACHED */ + } /* Parse the key agreement payload */ - payload = silc_key_agreement_payload_parse(packet->buffer->data, - packet->buffer->len); - if (!payload) - goto out; - - /* Call the key_agreement client operation */ - ret = client->internal->ops->key_agreement( - client, conn, clients[0], - silc_key_agreement_get_hostname(payload), - silc_key_agreement_get_port(payload), - &completion, &completion_context); + payload = silc_key_agreement_payload_parse(silc_buffer_data(&packet->buffer), + silc_buffer_len(&packet->buffer)); + if (!payload) { + /** Malformed Payload */ + SILC_LOG_DEBUG(("Malformed key agreement payload")); + silc_fsm_next(fsm, silc_client_key_agreement_error); + SILC_FSM_CONTINUE; + } + + /* If remote did not provide connection endpoint, we will assume that we + will provide it and will be responder. */ + if (!silc_key_agreement_get_hostname(payload)) + remote_client->internal.prv_resp = TRUE; + else + remote_client->internal.prv_resp = FALSE; - /* If the user returned TRUE then we'll start the key agreement right - here and right now. */ - if (ret == TRUE) - silc_client_perform_key_agreement(client, conn, clients[0], - silc_key_agreement_get_hostname(payload), - silc_key_agreement_get_port(payload), - completion, completion_context); + /* Notify application for key agreement request */ + client->internal->ops->key_agreement( + client, conn, remote_client, + silc_key_agreement_get_hostname(payload), + silc_key_agreement_get_protocol(payload), + silc_key_agreement_get_port(payload)); silc_key_agreement_payload_free(payload); - out: - silc_packet_context_free(packet); + silc_packet_free(packet); + SILC_FSM_FINISH; } -/* Received Key Agreement packet from remote client. Process the packet - and resolve the client information from the server before actually - letting the application know that we've received this packet. Then - call the key_agreement client operation and let the user decide - whether we perform the key agreement protocol now or not. */ +/* Key agreement packet processing error */ -void silc_client_key_agreement(SilcClient client, - SilcSocketConnection sock, - SilcPacketContext *packet) +SILC_FSM_STATE(silc_client_key_agreement_error) { - SilcClientID *remote_id; - - if (packet->src_id_type != SILC_ID_CLIENT) - return; - - remote_id = silc_id_str2id(packet->src_id, packet->src_id_len, - SILC_ID_CLIENT); - if (!remote_id) - return; - - silc_client_get_client_by_id_resolve(client, sock->user_data, remote_id, - NULL, - silc_client_key_agreement_resolve_cb, - silc_packet_context_dup(packet)); - silc_free(remote_id); + SilcPacket packet = state_context; + silc_packet_free(packet); + SILC_FSM_FINISH; }