Author: Pekka Riikonen <priikone@silcnet.org>
- Copyright (C) 1997 - 2004 Pekka Riikonen
+ Copyright (C) 1997 - 2014 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
*/
/* $Id$ */
-/* This file includes the private message sending and receiving routines
- and private message key handling routines. */
#include "silc.h"
#include "silcclient.h"
#include "client_internal.h"
-/* Sends private message to remote client. If private message key has
- not been set with this client then the message will be encrypted using
- normal session keys. Private messages are special packets in SILC
- network hence we need this own function for them. This is similiar
- to silc_client_packet_send_to_channel except that we send private
- message. The `data' is the private message. If the `force_send' is
- TRUE the packet is sent immediately. */
+/************************** Private Message Send ****************************/
+
+/* Sends private message to remote client. */
SilcBool silc_client_send_private_message(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry client_entry,
- SilcMessageFlags flags,
- unsigned char *data,
- SilcUInt32 data_len,
- SilcBool force_send)
+ SilcClientConnection conn,
+ SilcClientEntry client_entry,
+ SilcMessageFlags flags,
+ SilcHash hash,
+ unsigned char *data,
+ SilcUInt32 data_len)
{
- SilcSocketConnection sock;
SilcBuffer buffer;
- SilcPacketContext packetdata;
- const SilcBufferStruct packet;
- SilcCipher cipher;
- SilcHmac hmac;
- int block_len;
- SilcBool ret = FALSE;
-
- assert(client && conn && client_entry);
- sock = conn->sock;
- SILC_LOG_DEBUG(("Sending private message"));
+ SilcBool ret;
+ SilcID sid, rid;
- /* Encode private message payload */
- buffer = silc_message_payload_encode(flags, data, data_len,
- !client_entry->send_key ? FALSE :
- !client_entry->generated,
- TRUE, client_entry->send_key,
- client_entry->hmac_send,
- client->rng, NULL, client->private_key,
- client->sha1hash);
- if (!buffer) {
- SILC_LOG_ERROR(("Error encoding private message"));
+ if (silc_unlikely(!client || !conn || !client_entry))
+ return FALSE;
+ if (silc_unlikely(flags & SILC_MESSAGE_FLAG_SIGNED && !hash))
+ return FALSE;
+ if (silc_unlikely(conn->internal->disconnected))
return FALSE;
- }
- /* If we don't have private message specific key then private messages
- are just as any normal packet thus call normal packet sending. If
- the key exist then the encryption process is a bit different and
- will be done in the rest of this function. */
- if (!client_entry->send_key) {
- silc_client_packet_send(client, sock, SILC_PACKET_PRIVATE_MESSAGE,
- client_entry->id, SILC_ID_CLIENT, NULL, NULL,
- buffer->data, buffer->len, force_send);
- ret = TRUE;
- goto out;
- }
+ SILC_LOG_DEBUG(("Sending private message"));
- /* We have private message specific key */
-
- /* Get data used in the encryption */
- cipher = conn->internal->send_key;
- hmac = conn->internal->hmac_send;
- block_len = silc_cipher_get_block_len(cipher);
-
- /* Set the packet context pointers. */
- data = buffer->data;
- data_len = buffer->len;
- packetdata.flags = SILC_PACKET_FLAG_PRIVMSG_KEY;
- packetdata.type = SILC_PACKET_PRIVATE_MESSAGE;
- packetdata.src_id = conn->local_id_data;
- packetdata.src_id_len = silc_id_get_len(conn->local_id, SILC_ID_CLIENT);
- packetdata.src_id_type = SILC_ID_CLIENT;
- packetdata.dst_id = silc_id_id2str(client_entry->id, SILC_ID_CLIENT);
- packetdata.dst_id_len = silc_id_get_len(client_entry->id, SILC_ID_CLIENT);
- packetdata.dst_id_type = SILC_ID_CLIENT;
- data_len = SILC_PACKET_DATALEN(data_len, SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len +
- packetdata.dst_id_len);
- packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len + packetdata.dst_id_len;
- SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len +
- packetdata.dst_id_len), block_len, packetdata.padlen);
-
- /* Create the outgoing packet */
- if (!silc_packet_assemble(&packetdata, client->rng, cipher, hmac, sock,
- data, data_len, (const SilcBuffer)&packet)) {
- SILC_LOG_ERROR(("Error assembling packet"));
- goto out;
+ /* Auto-negotiate private message key (AKE) if there is no key or
+ it's time to rekey. */
+ if (!client->internal->params->dont_autoneg_prvmsg_keys &&
+ !client_entry->internal.no_ake && client_entry != conn->local_entry &&
+ (!client_entry->internal.send_key ||
+ (client_entry->internal.ake_rekey <= silc_time() ||
+ client_entry->internal.ake_generation !=
+ conn->internal->ake_generation))) {
+ return silc_client_autoneg_private_message_key(
+ client, conn, client_entry, NULL,
+ flags, hash, data, data_len);
}
- /* Encrypt the header and padding of the packet. */
- silc_packet_encrypt(cipher, hmac, conn->internal->psn_send++,
- (SilcBuffer)&packet, SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len + packetdata.dst_id_len +
- packetdata.padlen);
+ sid.type = SILC_ID_CLIENT;
+ sid.u.client_id = *conn->local_id;
+ rid.type = SILC_ID_CLIENT;
+ rid.u.client_id = client_entry->id;
- SILC_LOG_HEXDUMP(("Private message packet, len %d", packet.len),
- packet.data, packet.len);
-
- /* Now actually send the packet */
- silc_client_packet_send_real(client, sock, force_send);
-
- /* Check for mandatory rekey */
- if (conn->internal->psn_send == SILC_CLIENT_REKEY_THRESHOLD)
- silc_schedule_task_add(client->schedule, sock->sock,
- silc_client_rekey_callback, sock, 0, 1,
- SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-
- silc_free(packetdata.dst_id);
+ /* Encode private message payload */
+ buffer =
+ silc_message_payload_encode(flags, data, data_len,
+ (!client_entry->internal.send_key ? FALSE :
+ !client_entry->internal.generated),
+ TRUE, client_entry->internal.send_key,
+ client_entry->internal.hmac_send,
+ client->rng, NULL, conn->private_key,
+ hash, &sid, &rid, NULL);
+ if (silc_unlikely(!buffer)) {
+ SILC_LOG_ERROR(("Error encoding private message"));
+ return FALSE;
+ }
- ret = TRUE;
+ /* Send the private message packet */
+ ret = silc_packet_send_ext(conn->stream, SILC_PACKET_PRIVATE_MESSAGE,
+ client_entry->internal.send_key ?
+ SILC_PACKET_FLAG_PRIVMSG_KEY : 0,
+ 0, NULL, SILC_ID_CLIENT, &client_entry->id,
+ silc_buffer_datalen(buffer), NULL, NULL);
- out:
silc_buffer_free(buffer);
-
return ret;
}
-static void silc_client_private_message_cb(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry *clients,
- SilcUInt32 clients_count,
- void *context)
-{
- SilcPacketContext *packet = (SilcPacketContext *)context;
+/************************* Private Message Receive **************************/
- if (!clients) {
- silc_packet_context_free(packet);
- return;
- }
+/* Client resolving callback. Continues with the private message processing */
+
+static void silc_client_private_message_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcStatus status,
+ SilcDList clients,
+ void *context)
+{
+ /* If no client found, ignore the private message, a silent error */
+ if (!clients)
+ silc_fsm_next(context, silc_client_private_message_error);
- silc_client_private_message(client, conn->sock, packet);
- silc_packet_context_free(packet);
+ /* Continue processing the private message packet */
+ SILC_FSM_CALL_CONTINUE(context);
}
-/* Private message received. This processes the private message and
- finally displays it on the screen. */
+/* Private message received. */
-void silc_client_private_message(SilcClient client,
- SilcSocketConnection sock,
- SilcPacketContext *packet)
+SILC_FSM_STATE(silc_client_private_message)
{
- SilcClientConnection conn = (SilcClientConnection)sock->user_data;
+ SilcClientConnection conn = fsm_context;
+ SilcClient client = conn->client;
+ SilcPacket packet = state_context;
SilcMessagePayload payload = NULL;
- SilcClientID *remote_id = NULL;
- SilcClientEntry remote_client;
+ SilcClientID remote_id;
+ SilcClientEntry remote_client = NULL;
SilcMessageFlags flags;
unsigned char *message;
SilcUInt32 message_len;
- SilcCipher cipher = NULL;
- SilcHmac hmac = NULL;
- if (packet->src_id_type != SILC_ID_CLIENT)
- goto out;
+ SILC_LOG_DEBUG(("Received private message"));
- remote_id = silc_id_str2id(packet->src_id, packet->src_id_len,
- SILC_ID_CLIENT);
- if (!remote_id)
- goto out;
+ if (silc_unlikely(packet->src_id_type != SILC_ID_CLIENT)) {
+ /** Invalid packet */
+ silc_fsm_next(fsm, silc_client_private_message_error);
+ return 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) {
- if (remote_client) {
- if (remote_client->status & SILC_CLIENT_STATUS_RESOLVING) {
- remote_client->status &= ~SILC_CLIENT_STATUS_RESOLVING;
- goto out;
- }
- remote_client->status |= SILC_CLIENT_STATUS_RESOLVING;
- remote_client->resolve_cmd_ident = conn->cmd_ident + 1;
- }
+ if (silc_unlikely(!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_private_message_error);
+ return SILC_FSM_CONTINUE;
+ }
- /* Resolve the client info */
- silc_client_get_client_by_id_resolve(client, conn, remote_id, NULL,
- silc_client_private_message_cb,
- silc_packet_context_dup(packet));
- return;
+ /* 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_private_message_resolved,
+ fsm));
+ /* NOT REACHED */
}
- cipher = remote_client->receive_key;
- hmac = remote_client->hmac_receive;
- if (packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY && !cipher && !hmac) {
- silc_free(remote_id);
- return;
+ if (silc_unlikely(packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY &&
+ !remote_client->internal.receive_key &&
+ !remote_client->internal.hmac_receive)) {
+#if 1
+ /* Kludge to check if the message has SKE packet inside, and then start
+ key exchange protocol. Remove this once AKE support is everywhere. */
+ payload = silc_message_payload_parse(silc_buffer_datalen(&packet->buffer),
+ TRUE, FALSE, NULL, NULL,
+ packet->src_id, packet->src_id_len,
+ packet->dst_id, packet->dst_id_len,
+ NULL, FALSE, NULL);
+ if (!payload)
+ goto out;
+
+ flags = silc_message_get_flags(payload);
+ if (flags & SILC_MESSAGE_FLAG_PACKET &&
+ silc_client_autoneg_private_message_key(client, conn, remote_client,
+ packet, 0, NULL, NULL, 0))
+ packet = NULL;
+#endif /* 0 */
+ goto out;
}
/* Parse the payload and decrypt it also if private message key is set */
- payload = silc_message_payload_parse(packet->buffer->data,
- packet->buffer->len, TRUE,
- !remote_client->generated,
- cipher, hmac);
- if (!payload) {
- silc_free(remote_id);
- return;
+ payload =
+ silc_message_payload_parse(silc_buffer_datalen(&packet->buffer),
+ TRUE, !remote_client->internal.generated,
+ remote_client->internal.receive_key,
+ remote_client->internal.hmac_receive,
+ packet->src_id, packet->src_id_len,
+ packet->dst_id, packet->dst_id_len,
+ NULL, FALSE, NULL);
+ if (silc_unlikely(!payload)) {
+ /* Private message key is set but the sender may have removed it,
+ try to parse without it. */
+ if (remote_client->internal.receive_key) {
+ SILC_LOG_DEBUG(("Parse payload without using private message key"));
+ payload =
+ silc_message_payload_parse(silc_buffer_datalen(&packet->buffer),
+ TRUE, FALSE, NULL, NULL,
+ packet->src_id, packet->src_id_len,
+ packet->dst_id, packet->dst_id_len,
+ NULL, FALSE, NULL);
+ }
}
+ if (!payload)
+ goto out;
flags = silc_message_get_flags(payload);
- /* Pass the private message to application */
+ /* If message contains SILC packet, process the packet here */
+ if (flags & SILC_MESSAGE_FLAG_PACKET) {
+ if (silc_client_autoneg_private_message_key(client, conn, remote_client,
+ packet, 0, NULL, NULL, 0))
+ packet = NULL;
+ goto out;
+ }
+
message = silc_message_get_data(payload, &message_len);
+
+ /* Pass the private message to application */
client->internal->ops->private_message(client, conn, remote_client, payload,
flags, message, message_len);
/* See if we are away (gone). If we are away we will reply to the
sender with the set away message. */
- if (conn->internal->away && conn->internal->away->away &&
+ if (conn->internal->away_message &&
!(flags & SILC_MESSAGE_FLAG_NOREPLY)) {
/* If it's me, ignore */
- if (SILC_ID_CLIENT_COMPARE(remote_id, conn->local_id))
+ if (SILC_ID_CLIENT_COMPARE(&remote_id, conn->local_id))
goto out;
/* Send the away message */
silc_client_send_private_message(client, conn, remote_client,
SILC_MESSAGE_FLAG_AUTOREPLY |
- SILC_MESSAGE_FLAG_NOREPLY,
- conn->internal->away->away,
- strlen(conn->internal->away->away), TRUE);
+ SILC_MESSAGE_FLAG_NOREPLY, NULL,
+ conn->internal->away_message,
+ strlen(conn->internal->away_message));
}
out:
+ /** Packet processed */
+ if (packet)
+ silc_packet_free(packet);
+ silc_client_unref_client(client, conn, remote_client);
if (payload)
silc_message_payload_free(payload);
- silc_free(remote_id);
+ return SILC_FSM_FINISH;
+}
+
+/* Private message error. */
+
+SILC_FSM_STATE(silc_client_private_message_error)
+{
+ SilcPacket packet = state_context;
+ silc_packet_free(packet);
+ return SILC_FSM_FINISH;
+}
+
+/* Initialize private message waiter for the `conn' connection. */
+
+SilcBool silc_client_private_message_wait_init(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry)
+{
+ SilcID id;
+
+ if (client_entry->internal.prv_waiter)
+ return TRUE;
+
+ /* We want SILC_PACKET_PRIVATE_MESSAGE packets from this source ID. */
+ id.type = SILC_ID_CLIENT;
+ id.u.client_id = client_entry->id;
+
+ client_entry->internal.prv_waiter =
+ silc_packet_wait_init(conn->stream, &id, SILC_PACKET_PRIVATE_MESSAGE, -1);
+ if (!client_entry->internal.prv_waiter)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Uninitializes private message waiter. */
+
+void silc_client_private_message_wait_uninit(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry)
+{
+ if (!client_entry->internal.prv_waiter)
+ return;
+ silc_packet_wait_uninit(client_entry->internal.prv_waiter, conn->stream);
+ client_entry->internal.prv_waiter = NULL;
+}
+
+/* Blocks the calling process or thread until private message has been
+ received from the specified client. */
+
+SilcBool silc_client_private_message_wait(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry,
+ SilcMessagePayload *payload)
+{
+ SilcPacket packet;
+
+ if (!client_entry->internal.prv_waiter)
+ return FALSE;
+
+ /* Block until private message arrives */
+ do {
+ if ((silc_packet_wait(client_entry->internal.prv_waiter, 0, &packet)) < 0)
+ return FALSE;
+
+ /* Parse the payload and decrypt it also if private message key is set */
+ *payload =
+ silc_message_payload_parse(silc_buffer_data(&packet->buffer),
+ silc_buffer_len(&packet->buffer),
+ TRUE, !client_entry->internal.generated,
+ client_entry->internal.receive_key,
+ client_entry->internal.hmac_receive,
+ packet->src_id, packet->src_id_len,
+ packet->dst_id, packet->dst_id_len,
+ NULL, FALSE, NULL);
+ if (!(*payload)) {
+ silc_packet_free(packet);
+ continue;
+ }
+
+ break;
+ } while (1);
+
+ silc_packet_free(packet);
+ return TRUE;
+}
+
+/*************************** Private Message Key ****************************/
+
+/* Sends private message key request. Sender of this packet is initiator
+ when setting the private message key. */
+
+static SilcBool
+silc_client_send_private_message_key_request(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry)
+{
+ const char *cipher, *hmac;
+
+ SILC_LOG_DEBUG(("Sending private message key request"));
+
+ cipher = silc_cipher_get_name(client_entry->internal.send_key);
+ hmac = silc_hmac_get_name(client_entry->internal.hmac_send);
+
+ /* Send the packet */
+ return silc_packet_send_va_ext(conn->stream,
+ SILC_PACKET_PRIVATE_MESSAGE_KEY,
+ 0, 0, NULL, SILC_ID_CLIENT,
+ &client_entry->id, NULL, NULL,
+ SILC_STR_UI_SHORT(strlen(cipher)),
+ SILC_STR_DATA(cipher, strlen(cipher)),
+ SILC_STR_UI_SHORT(strlen(hmac)),
+ SILC_STR_DATA(hmac, strlen(hmac)),
+ SILC_STR_END);
}
-/* Function that actually employes the received private message key */
+/* Client resolving callback. Here we simply mark that we are the responder
+ side of this private message key request. */
static void silc_client_private_message_key_cb(SilcClient client,
SilcClientConnection conn,
- SilcClientEntry *clients,
- SilcUInt32 clients_count,
+ SilcStatus status,
+ SilcDList clients,
void *context)
{
- SilcPacketContext *packet = (SilcPacketContext *)context;
- unsigned char *key;
- SilcUInt16 key_len;
+ SilcFSMThread thread = context;
+ SilcPacket packet = silc_fsm_get_state_context(thread);
unsigned char *cipher = NULL, *hmac = NULL;
+ SilcClientEntry client_entry;
int ret;
- if (!clients)
- goto out;
+ if (!clients) {
+ silc_packet_free(packet);
+ silc_fsm_finish(thread);
+ return;
+ }
/* Parse the private message key payload */
- ret = silc_buffer_unformat(packet->buffer,
- SILC_STR_UI16_NSTRING(&key, &key_len),
+ ret = silc_buffer_unformat(&packet->buffer,
SILC_STR_UI16_STRING_ALLOC(&cipher),
SILC_STR_UI16_STRING_ALLOC(&hmac),
SILC_STR_END);
if (!ret)
goto out;
- if (key_len > packet->buffer->len)
- goto out;
-
/* Mark that we are responder */
- clients[0]->prv_resp = TRUE;
+ client_entry = silc_dlist_get(clients);
+ if (client_entry)
+ client_entry->internal.prv_resp = TRUE;
+
+ /* XXX we should notify application that remote wants to set up the
+ static key. And we should tell if we already have key with remote.
+ Application should return status telling whether to delete the key
+ or not. */
out:
silc_free(cipher);
silc_free(hmac);
- silc_packet_context_free(packet);
+ silc_packet_free(packet);
+ silc_fsm_finish(thread);
}
/* Processes incoming Private Message Key payload to indicate that the
sender whishes to set up a static private message key. */
-void silc_client_private_message_key(SilcClient client,
- SilcSocketConnection sock,
- SilcPacketContext *packet)
+SILC_FSM_STATE(silc_client_private_message_key)
{
- SilcClientID *remote_id;
-
- if (packet->src_id_type != SILC_ID_CLIENT)
- return;
+ SilcClientConnection conn = fsm_context;
+ SilcClient client = conn->client;
+ SilcPacket packet = state_context;
+ SilcClientID remote_id;
+
+ if (packet->src_id_type != SILC_ID_CLIENT) {
+ silc_packet_free(packet);
+ return SILC_FSM_FINISH;
+ }
- remote_id = silc_id_str2id(packet->src_id, packet->src_id_len,
- SILC_ID_CLIENT);
- if (!remote_id)
- return;
+ if (!silc_id_str2id(packet->src_id, packet->src_id_len, SILC_ID_CLIENT,
+ &remote_id, sizeof(remote_id))) {
+ silc_packet_free(packet);
+ return SILC_FSM_FINISH;
+ }
- silc_client_get_client_by_id_resolve(client, sock->user_data, remote_id,
- NULL,
+ /* Always resolve the remote client. The actual packet is processed
+ in the resolving callback. */
+ SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
+ client, conn, &remote_id, NULL,
silc_client_private_message_key_cb,
- silc_packet_context_dup(packet));
- silc_free(remote_id);
+ fsm));
}
-/* Adds private message key to the client library. The key will be used to
- encrypt all private message between the client and the remote client
- indicated by the `client_entry'. If the `key' is NULL and the boolean
- value `generate_key' is TRUE the library will generate random key.
- The `key' maybe for example pre-shared-key, passphrase or similar.
- The `cipher' and `hmac' MAY be provided but SHOULD be NULL to assure
- that the requirements of the SILC protocol are met. The API, however,
- allows to allocate any cipher and HMAC.
-
- If `responder' is TRUE then the sending and receiving keys will be
- set according the client being the receiver of the private key. If
- FALSE the client is being the sender (or negotiator) of the private
- key.
-
- It is not necessary to set key for normal private message usage. If the
- key is not set then the private messages are encrypted using normal
- session keys. Setting the private key, however, increases the security.
-
- Returns FALSE if the key is already set for the `client_entry', TRUE
- otherwise. */
+/* Adds new private message key to `client_entry'. If we are setting this
+ before receiving request for it from `client_entry' we will send the
+ request to the client. Otherwise, we are responder side. */
SilcBool silc_client_add_private_message_key(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry client_entry,
- const char *cipher,
- const char *hmac,
- unsigned char *key,
- SilcUInt32 key_len,
- SilcBool generate_key,
- SilcBool responder)
+ SilcClientConnection conn,
+ SilcClientEntry client_entry,
+ const char *cipher,
+ const char *hmac,
+ unsigned char *key,
+ SilcUInt32 key_len)
{
- unsigned char private_key[32];
- SilcUInt32 len;
- int i;
- SilcSKEKeyMaterial *keymat;
+ SilcSKEKeyMaterial keymat;
+ SilcBool ret;
- assert(client && client_entry);
+ if (!client || !client_entry)
+ return FALSE;
/* Return FALSE if key already set */
- if (client_entry->send_key && client_entry->receive_key)
+ if (client_entry->internal.send_key && client_entry->internal.receive_key)
return FALSE;
if (!cipher)
if (!silc_hmac_is_supported(hmac))
return FALSE;
- /* Generate key if not provided */
- if (generate_key == TRUE) {
- len = 32;
- for (i = 0; i < len; i++)
- private_key[i] = silc_rng_get_byte_fast(client->rng);
- key = private_key;
- key_len = len;
- client_entry->generated = TRUE;
- }
-
/* Save the key */
- client_entry->key = silc_memdup(key, key_len);
- client_entry->key_len = key_len;
+ client_entry->internal.key = silc_memdup(key, key_len);
+ client_entry->internal.key_len = key_len;
/* Produce the key material as the protocol defines */
- keymat = silc_calloc(1, sizeof(*keymat));
- if (silc_ske_process_key_material_data(key, key_len, 16, 256, 16,
- client->sha1hash, keymat)
- != SILC_SKE_STATUS_OK)
+ keymat = silc_ske_process_key_material_data(key, key_len, 16, 256, 16,
+ conn->internal->sha1hash);
+ if (!keymat)
return FALSE;
- /* Allocate the cipher and HMAC */
- silc_cipher_alloc(cipher, &client_entry->send_key);
- silc_cipher_alloc(cipher, &client_entry->receive_key);
- silc_hmac_alloc(hmac, NULL, &client_entry->hmac_send);
- silc_hmac_alloc(hmac, NULL, &client_entry->hmac_receive);
-
- /* Set the keys */
- if (responder == TRUE) {
- silc_cipher_set_key(client_entry->send_key, keymat->receive_enc_key,
- keymat->enc_key_len);
- silc_cipher_set_iv(client_entry->send_key, keymat->receive_iv);
- silc_cipher_set_key(client_entry->receive_key, keymat->send_enc_key,
- keymat->enc_key_len);
- silc_cipher_set_iv(client_entry->receive_key, keymat->send_iv);
- silc_hmac_set_key(client_entry->hmac_send, keymat->receive_hmac_key,
- keymat->hmac_key_len);
- silc_hmac_set_key(client_entry->hmac_receive, keymat->send_hmac_key,
- keymat->hmac_key_len);
- } else {
- silc_cipher_set_key(client_entry->send_key, keymat->send_enc_key,
- keymat->enc_key_len);
- silc_cipher_set_iv(client_entry->send_key, keymat->send_iv);
- silc_cipher_set_key(client_entry->receive_key, keymat->receive_enc_key,
- keymat->enc_key_len);
- silc_cipher_set_iv(client_entry->receive_key, keymat->receive_iv);
- silc_hmac_set_key(client_entry->hmac_send, keymat->send_hmac_key,
- keymat->hmac_key_len);
- silc_hmac_set_key(client_entry->hmac_receive, keymat->receive_hmac_key,
- keymat->hmac_key_len);
- }
+ /* Set the key into use */
+ ret = silc_client_add_private_message_key_ske(client, conn, client_entry,
+ cipher, hmac, keymat);
+ client_entry->internal.generated = FALSE;
/* Free the key material */
silc_ske_free_key_material(keymat);
- return TRUE;
+ /* If we are setting the key without a request from the remote client,
+ we will send request to remote. */
+ if (!client_entry->internal.prv_resp)
+ silc_client_send_private_message_key_request(client, conn, client_entry);
+
+ return ret;
}
/* Same as above but takes the key material from the SKE key material
- structure. This structure is received if the application uses the
- silc_client_send_key_agreement to negotiate the key material. The
- `cipher' and `hmac' SHOULD be provided as it is negotiated also in
- the SKE protocol. */
+ structure. */
SilcBool silc_client_add_private_message_key_ske(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry client_entry,
- const char *cipher,
- const char *hmac,
- SilcSKEKeyMaterial *key,
- SilcBool responder)
+ SilcClientConnection conn,
+ SilcClientEntry client_entry,
+ const char *cipher,
+ const char *hmac,
+ SilcSKEKeyMaterial keymat)
{
- assert(client && client_entry);
+ if (!client || !client_entry)
+ return FALSE;
/* Return FALSE if key already set */
- if (client_entry->send_key && client_entry->receive_key)
+ if (client_entry->internal.send_key && client_entry->internal.receive_key)
return FALSE;
if (!cipher)
if (!silc_hmac_is_supported(hmac))
return FALSE;
- client_entry->generated = TRUE;
+ client_entry->internal.generated = TRUE;
+ client_entry->internal.no_ake = TRUE;
/* Allocate the cipher and HMAC */
- silc_cipher_alloc(cipher, &client_entry->send_key);
- silc_cipher_alloc(cipher, &client_entry->receive_key);
- silc_hmac_alloc(hmac, NULL, &client_entry->hmac_send);
- silc_hmac_alloc(hmac, NULL, &client_entry->hmac_receive);
+ if (!silc_cipher_alloc(cipher, &client_entry->internal.send_key))
+ return FALSE;
+ if (!silc_cipher_alloc(cipher, &client_entry->internal.receive_key))
+ return FALSE;
+ if (!silc_hmac_alloc(hmac, NULL, &client_entry->internal.hmac_send))
+ return FALSE;
+ if (!silc_hmac_alloc(hmac, NULL, &client_entry->internal.hmac_receive))
+ return FALSE;
/* Set the keys */
- if (responder == TRUE) {
- silc_cipher_set_key(client_entry->send_key, key->receive_enc_key,
- key->enc_key_len);
- silc_cipher_set_iv(client_entry->send_key, key->receive_iv);
- silc_cipher_set_key(client_entry->receive_key, key->send_enc_key,
- key->enc_key_len);
- silc_cipher_set_iv(client_entry->receive_key, key->send_iv);
- silc_hmac_set_key(client_entry->hmac_send, key->receive_hmac_key,
- key->hmac_key_len);
- silc_hmac_set_key(client_entry->hmac_receive, key->send_hmac_key,
- key->hmac_key_len);
+ if (client_entry->internal.prv_resp) {
+ silc_cipher_set_key(client_entry->internal.send_key,
+ keymat->receive_enc_key,
+ keymat->enc_key_len, TRUE);
+ silc_cipher_set_iv(client_entry->internal.send_key,
+ keymat->receive_iv);
+ silc_cipher_set_key(client_entry->internal.receive_key,
+ keymat->send_enc_key,
+ keymat->enc_key_len, FALSE);
+ silc_cipher_set_iv(client_entry->internal.receive_key, keymat->send_iv);
+ silc_hmac_set_key(client_entry->internal.hmac_send,
+ keymat->receive_hmac_key,
+ keymat->hmac_key_len);
+ silc_hmac_set_key(client_entry->internal.hmac_receive,
+ keymat->send_hmac_key,
+ keymat->hmac_key_len);
} else {
- silc_cipher_set_key(client_entry->send_key, key->send_enc_key,
- key->enc_key_len);
- silc_cipher_set_iv(client_entry->send_key, key->send_iv);
- silc_cipher_set_key(client_entry->receive_key, key->receive_enc_key,
- key->enc_key_len);
- silc_cipher_set_iv(client_entry->receive_key, key->receive_iv);
- silc_hmac_set_key(client_entry->hmac_send, key->send_hmac_key,
- key->hmac_key_len);
- silc_hmac_set_key(client_entry->hmac_receive, key->receive_hmac_key,
- key->hmac_key_len);
+ silc_cipher_set_key(client_entry->internal.send_key,
+ keymat->send_enc_key,
+ keymat->enc_key_len, TRUE);
+ silc_cipher_set_iv(client_entry->internal.send_key,
+ keymat->send_iv);
+ silc_cipher_set_key(client_entry->internal.receive_key,
+ keymat->receive_enc_key,
+ keymat->enc_key_len, FALSE);
+ silc_cipher_set_iv(client_entry->internal.receive_key, keymat->receive_iv);
+ silc_hmac_set_key(client_entry->internal.hmac_send,
+ keymat->send_hmac_key,
+ keymat->hmac_key_len);
+ silc_hmac_set_key(client_entry->internal.hmac_receive,
+ keymat->receive_hmac_key,
+ keymat->hmac_key_len);
}
return TRUE;
}
-/* Sends private message key indicator. The sender of this packet is
- going to be the initiator, if and when, the users set up a static
- private message key (not Key Agreement). */
-
-SilcBool silc_client_send_private_message_key_request(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry client_entry)
-{
- SilcSocketConnection sock;
- SilcBuffer buffer;
- int cipher_len, hmac_len;
- const char *cipher, *hmac;
-
- assert(client && conn && client_entry);
-
- sock = conn->sock;
- if (!client_entry->send_key || !client_entry->key)
- return FALSE;
-
- SILC_LOG_DEBUG(("Sending private message key indicator"));
-
- cipher = silc_cipher_get_name(client_entry->send_key);
- cipher_len = strlen(cipher);
- hmac = silc_hmac_get_name(client_entry->hmac_send);
- hmac_len = strlen(hmac);
-
- /* Create private message key payload */
- buffer = silc_buffer_alloc_size(4 + cipher_len + hmac_len);
- silc_buffer_format(buffer,
- SILC_STR_UI_SHORT(cipher_len),
- SILC_STR_UI_XNSTRING(cipher,
- cipher_len),
- SILC_STR_UI_SHORT(hmac_len),
- SILC_STR_UI_XNSTRING(hmac,
- hmac_len),
- SILC_STR_END);
-
- /* Send the packet */
- silc_client_packet_send(client, sock, SILC_PACKET_PRIVATE_MESSAGE_KEY,
- client_entry->id, SILC_ID_CLIENT, NULL, NULL,
- buffer->data, buffer->len, TRUE);
- silc_free(buffer);
-
- return TRUE;
-}
-
/* Removes the private message from the library. The key won't be used
after this to protect the private messages with the remote `client_entry'
client. Returns FALSE on error, TRUE otherwise. */
SilcBool silc_client_del_private_message_key(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry client_entry)
+ SilcClientConnection conn,
+ SilcClientEntry client_entry)
{
- assert(client && client_entry);
+ if (!client || !client_entry)
+ return FALSE;
- if (!client_entry->send_key && !client_entry->receive_key)
+ if (!client_entry->internal.send_key && !client_entry->internal.receive_key)
return FALSE;
- silc_cipher_free(client_entry->send_key);
- silc_cipher_free(client_entry->receive_key);
+ silc_cipher_free(client_entry->internal.send_key);
+ silc_cipher_free(client_entry->internal.receive_key);
- if (client_entry->key) {
- memset(client_entry->key, 0, client_entry->key_len);
- silc_free(client_entry->key);
+ if (client_entry->internal.key) {
+ memset(client_entry->internal.key, 0, client_entry->internal.key_len);
+ silc_free(client_entry->internal.key);
}
- client_entry->send_key = NULL;
- client_entry->receive_key = NULL;
- client_entry->key = NULL;
+ client_entry->internal.send_key = NULL;
+ client_entry->internal.receive_key = NULL;
+ client_entry->internal.key = NULL;
+ client_entry->internal.prv_resp = FALSE;
return TRUE;
}
{
SilcPrivateMessageKeys keys;
SilcUInt32 count = 0;
+ SilcList list;
SilcIDCacheEntry id_cache;
- SilcIDCacheList list;
SilcClientEntry entry;
- assert(client && conn);
-
- if (!silc_idcache_get_all(conn->internal->client_cache, &list))
+ if (!client || !conn)
return NULL;
- if (!silc_idcache_list_count(list)) {
- silc_idcache_list_free(list);
+ silc_mutex_lock(conn->internal->lock);
+ if (!silc_idcache_get_all(conn->internal->client_cache, &list)) {
+ silc_mutex_unlock(conn->internal->lock);
return NULL;
}
- keys = silc_calloc(silc_idcache_list_count(list), sizeof(*keys));
-
- silc_idcache_list_first(list, &id_cache);
- while (id_cache) {
- entry = (SilcClientEntry)id_cache->context;
+ keys = silc_calloc(silc_list_count(list), sizeof(*keys));
+ if (!keys) {
+ silc_mutex_unlock(conn->internal->lock);
+ return NULL;
+ }
- if (entry->send_key) {
+ silc_list_start(list);
+ while ((id_cache = silc_list_get(list))) {
+ entry = id_cache->context;
+ if (entry->internal.send_key) {
keys[count].client_entry = entry;
- keys[count].cipher = (char *)silc_cipher_get_name(entry->send_key);
- keys[count].key = entry->generated == FALSE ? entry->key : NULL;
- keys[count].key_len = entry->generated == FALSE ? entry->key_len : 0;
+ keys[count].cipher = (char *)silc_cipher_get_name(entry->internal.
+ send_key);
+ keys[count].key = (entry->internal.generated == FALSE ?
+ entry->internal.key : NULL);
+ keys[count].key_len = (entry->internal.generated == FALSE ?
+ entry->internal.key_len : 0);
count++;
}
-
- if (!silc_idcache_list_next(list, &id_cache))
- break;
}
+ silc_mutex_unlock(conn->internal->lock);
+
if (key_count)
*key_count = count;
silc_free(keys);
}
+/* Return private message key from the client entry. */
+
+SilcBool
+silc_client_private_message_key_is_set(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry)
+{
+ return client_entry->internal.send_key != NULL;
+}
+
+/********************* Private Message Key Autoneg (AKE) ********************/
+
+/* Private message key auto-negotiation context */
+struct SilcClientAutonegMessageKeyStruct {
+ SilcClientConnection conn; /* Connection to server */
+ SilcSKE ske; /* SKE with remote client */
+ SilcAsyncOperation ske_op; /* SKE operation */
+ SilcStream stream; /* PRIVATE_MESSAGE stream */
+ SilcPacketStream ske_stream; /* Packet stream for SKE (inside
+ the PRIVATE_MESSAGE stream) */
+ SilcDList messages; /* Message queue */
+ SilcHash hash; /* Initial message hash */
+ SilcPublicKey public_key; /* Remote client public key */
+ SilcVerifyKeyContext verify;
+ SilcSKEParamsStruct params;
+ SilcUInt32 generation; /* Starting AKE generation */
+};
+
+static SilcBool
+silc_client_autoneg_key_recv_ske(SilcPacketEngine engine,
+ SilcPacketStream stream,
+ SilcPacket packet,
+ void *callback_context,
+ void *stream_context);
+
+static const SilcPacketCallbacks autoneg_key_ske_cbs =
+{
+ silc_client_autoneg_key_recv_ske, NULL, NULL
+};
+
+/* Destroy auto-negotiation context */
+
+static void silc_client_autoneg_key_free(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry)
+{
+ SilcClientAutonegMessageKey ake = client_entry->internal.ake;
+ SilcBuffer m;
+
+ if (ake->ske_op)
+ silc_async_abort(ake->ske_op, NULL, NULL);
+
+ silc_ske_free(ake->ske);
+ silc_packet_stream_unlink(ake->ske_stream, &autoneg_key_ske_cbs, NULL);
+ silc_packet_stream_destroy(ake->ske_stream);
+ if (ake->hash)
+ silc_hash_free(ake->hash);
+
+ silc_dlist_start(ake->messages);
+ while ((m = silc_dlist_get(ake->messages)) != SILC_LIST_END) {
+ silc_dlist_del(ake->messages, m);
+ silc_buffer_free(m);
+ }
+ silc_dlist_uninit(ake->messages);
+
+ client_entry->internal.op = NULL;
+ client_entry->internal.ake = NULL;
+ silc_client_unref_client(client, conn, client_entry);
+
+ if (ake->verify)
+ ake->verify->aborted = TRUE;
+ else if (ake->public_key)
+ silc_pkcs_public_key_free(ake->public_key);
+
+ silc_free(ake);
+}
+
+/* Destroy auto-negotiation context */
+
+SILC_TASK_CALLBACK(silc_client_autoneg_key_finish)
+{
+ SilcClientEntry client_entry = context;
+ SilcClientAutonegMessageKey ake = client_entry->internal.ake;
+ SilcClientConnection conn = ake->conn;
+ SilcClient client = conn->client;
+
+ silc_client_autoneg_key_free(client, conn, client_entry);
+}
+
+/* Abort callback. This aborts the auto-negotiation and the SKE */
+
+static void
+silc_client_autoneg_key_abort(SilcAsyncOperation op, void *context)
+{
+ SilcClientEntry client_entry = context;
+ SilcClientAutonegMessageKey ake = client_entry->internal.ake;
+ SilcClientConnection conn = ake->conn;
+ SilcClient client = conn->client;
+
+ if (!ake)
+ return;
+
+ silc_client_autoneg_key_free(client, conn, client_entry);
+}
+
+/* SKE packet stream callback. Here we verify that the packets we got
+ from the private message are actually SKE packets for us. */
+
+static SilcBool
+silc_client_autoneg_key_recv_ske(SilcPacketEngine engine,
+ SilcPacketStream stream,
+ SilcPacket packet,
+ void *callback_context,
+ void *stream_context)
+{
+ SilcClientEntry client_entry = stream_context;
+ SilcClientAutonegMessageKey ake = client_entry->internal.ake;
+ SilcClientID remote_id;
+
+ SILC_LOG_DEBUG(("Packet %p type %d inside private message", packet,
+ packet->type));
+
+ /* Take only SKE packets, drop others, no support for anything else */
+ if (packet->type != SILC_PACKET_KEY_EXCHANGE &&
+ packet->type != SILC_PACKET_KEY_EXCHANGE_1 &&
+ packet->type != SILC_PACKET_KEY_EXCHANGE_2 &&
+ packet->type != SILC_PACKET_FAILURE)
+ goto drop;
+
+ /* Must be from client to client */
+ if (packet->dst_id_type != SILC_ID_CLIENT ||
+ packet->src_id_type != SILC_ID_CLIENT)
+ goto drop;
+
+ if (!silc_id_str2id(packet->src_id, packet->src_id_len, SILC_ID_CLIENT,
+ &remote_id, sizeof(remote_id)))
+ goto drop;
+
+ if (!SILC_ID_CLIENT_COMPARE(&client_entry->id, &remote_id)) {
+ /* The packet is not for this client, but it must be */
+ SILC_LOG_DEBUG(("Client ids do not match"));
+ goto drop;
+ }
+
+ /* Responder is started here if correct packet comes in */
+ if (!ake->ske_op) {
+ if (packet->type == SILC_PACKET_KEY_EXCHANGE) {
+ /* Ignore pre-set proposal */
+ if (ake->params.prop) {
+ silc_ske_group_free(ake->params.prop->group);
+ silc_cipher_free(ake->params.prop->cipher);
+ silc_hash_free(ake->params.prop->hash);
+ silc_hmac_free(ake->params.prop->hmac);
+ silc_pkcs_public_key_free(ake->params.prop->public_key);
+ silc_free(ake->params.prop);
+ ake->params.prop = NULL;
+ }
+ } else if (packet->type != SILC_PACKET_KEY_EXCHANGE_1) {
+ SILC_LOG_DEBUG(("Invalid SKE packet for responder"));
+ silc_async_abort(client_entry->internal.op, NULL, NULL);
+ goto drop;
+ }
+
+ ake->ske_op = silc_ske_responder(ake->ske, ake->ske_stream, &ake->params);
+ if (!ake->ske_op) {
+ silc_async_abort(client_entry->internal.op, NULL, NULL);
+ goto drop;
+ }
+
+ /* We have to re-inject the packet to SKE stream because SKE wasn't
+ listenning to these packets until silc_ske_responder() was called */
+ silc_packet_stream_inject(ake->ske_stream, packet);
+ return TRUE;
+ }
+
+ /* Packet is ok and is for us, let it pass to SKE */
+ SILC_LOG_DEBUG(("Pass packet %p type %d", packet, packet->type));
+ return FALSE;
+
+ drop:
+ silc_packet_free(packet);
+ return TRUE;
+}
+
+/* Coder callback for actually encoding/decoding the SKE packets inside
+ private messages. */
+
+static SilcBool silc_client_autoneg_key_coder(SilcStream stream,
+ SilcStreamStatus status,
+ SilcBuffer buffer,
+ void *context)
+{
+ SilcBool ret = FALSE;
+ SilcBuffer message;
+ SilcMessagePayload payload = NULL;
+ SilcMessageFlags flags;
+ unsigned char *msg;
+ SilcUInt32 message_len;
+
+ switch (status) {
+ case SILC_STREAM_CAN_READ:
+ /* Decode private message. We get all private messages here from
+ the remote client while we're doing SKE, so we must take the
+ correct messages. */
+ SILC_LOG_DEBUG(("Decode packet inside private message"));
+
+ payload = silc_message_payload_parse(silc_buffer_datalen(buffer),
+ TRUE, FALSE, NULL, NULL, NULL, 0,
+ NULL, 0, NULL, FALSE, NULL);
+ if (!payload) {
+ SILC_LOG_DEBUG(("Error decoding private message payload"));
+ goto out;
+ }
+
+ /* Ignore this message if it's not packet */
+ flags = silc_message_get_flags(payload);
+ if (!(flags & SILC_MESSAGE_FLAG_PACKET)) {
+ SILC_LOG_DEBUG(("Private message doesn't contain packet"));
+ silc_message_payload_free(payload);
+ goto out;
+ }
+
+ /* Take the packet */
+ ret = TRUE;
+
+ msg = silc_message_get_data(payload, &message_len);
+ silc_buffer_reset(buffer);
+ if (!silc_buffer_enlarge(buffer, message_len)) {
+ silc_message_payload_free(payload);
+ goto out;
+ }
+ silc_buffer_put(buffer, msg, message_len);
+
+ silc_message_payload_free(payload);
+ break;
+
+ case SILC_STREAM_CAN_WRITE:
+ /* Encode private message */
+ SILC_LOG_DEBUG(("Encode packet inside private message"));
+
+ ret = TRUE;
+
+ message =
+ silc_message_payload_encode(SILC_MESSAGE_FLAG_PACKET,
+ silc_buffer_datalen(buffer),
+ FALSE, TRUE, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (!message) {
+ SILC_LOG_DEBUG(("Error encoding private message payload"));
+ goto out;
+ }
+
+ silc_buffer_reset(buffer);
+ if (!silc_buffer_enlarge(buffer, silc_buffer_len(message)))
+ goto out;
+ silc_buffer_put(buffer, silc_buffer_datalen(message));
+
+ break;
+
+ default:
+ break;
+ }
+
+ out:
+ return ret;
+}
+
+/* Called after application has verified remote client's public key */
+
+static void
+silc_client_autoneg_key_verify_pubkey_cb(SilcBool success, void *context)
+{
+ SilcVerifyKeyContext verify = context;
+ SilcClientAutonegMessageKey ake = verify->context;
+
+ SILC_LOG_DEBUG(("Start, verify %p, ake %p", context, ake));
+
+ /* Call the completion callback back to the SKE */
+ if (!verify->aborted) {
+ ake->verify = NULL;
+ verify->completion(verify->ske, success ? SILC_SKE_STATUS_OK :
+ SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY,
+ verify->completion_context);
+ } else {
+ silc_pkcs_public_key_free(verify->public_key);
+ }
+
+ silc_free(verify);
+}
+
+/* Remote client's public key verification callback */
+
+static void
+silc_client_autoneg_key_verify_pubkey(SilcSKE ske,
+ SilcPublicKey public_key,
+ void *context,
+ SilcSKEVerifyCbCompletion completion,
+ void *completion_context)
+{
+ SilcClientEntry client_entry = context;
+ SilcClientAutonegMessageKey ake = client_entry->internal.ake;
+ SilcClientConnection conn = ake->conn;
+ SilcClient client = conn->client;
+ SilcVerifyKeyContext verify;
+
+ /* Use public key we cached earlier in AKE for direction verification */
+ if (client_entry->internal.send_key && client_entry->public_key &&
+ silc_pkcs_public_key_compare(public_key, client_entry->public_key)) {
+ SILC_LOG_DEBUG(("Client's cached public key matches"));
+ completion(ske, SILC_SKE_STATUS_OK, completion_context);
+ return;
+ }
+
+ /* If we provided repository for SKE and we got here the key was not
+ found from the repository. */
+ if (conn->internal->params.repository &&
+ !conn->internal->params.verify_notfound) {
+ completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY,
+ completion_context);
+ return;
+ }
+
+ SILC_LOG_DEBUG(("Verify remote client public key"));
+
+ ake->public_key = silc_pkcs_public_key_copy(public_key);
+ if (!ake->public_key) {
+ completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY,
+ completion_context);
+ return;
+ }
+
+ verify = silc_calloc(1, sizeof(*verify));
+ if (!verify) {
+ completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY,
+ completion_context);
+ return;
+ }
+ verify->public_key = ake->public_key;
+ verify->ske = ske;
+ verify->completion = completion;
+ verify->completion_context = completion_context;
+ verify->context = ake;
+ ake->verify = verify;
+
+ /* Verify public key in application */
+ client->internal->ops->verify_public_key(
+ client, conn,
+ SILC_CONN_CLIENT, ake->public_key,
+ silc_client_autoneg_key_verify_pubkey_cb,
+ verify);
+}
+
+/* Key exchange protocol completion callback */
+
+static void silc_client_autoneg_key_done(SilcSKE ske,
+ SilcSKEStatus status,
+ SilcSKESecurityProperties prop,
+ SilcSKEKeyMaterial keymat,
+ SilcSKERekeyMaterial rekey,
+ void *context)
+{
+ SilcClientEntry client_entry = context;
+ SilcClientAutonegMessageKey ake = client_entry->internal.ake;
+ SilcClientConnection conn = ake->conn;
+ SilcClient client = conn->client;
+ SilcBool initiator = !client_entry->internal.prv_resp;
+ SilcMessageFlags flags;
+ SilcBuffer m;
+
+ ake->ske_op = NULL;
+
+ conn->context_type = SILC_ID_CLIENT;
+ conn->client_entry = client_entry;
+
+ if (status != SILC_SKE_STATUS_OK) {
+ /* Key exchange failed */
+ SILC_LOG_DEBUG(("Error during key exchange: %s (%d)",
+ silc_ske_map_status(status), status));
+
+ if (initiator) {
+ if (status != SILC_SKE_STATUS_PROBE_TIMEOUT)
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+ "Cannot send private message to %s (%s)",
+ client_entry->nickname,
+ silc_ske_map_status(status));
+ else if (client_entry->mode & SILC_UMODE_DETACHED)
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+ "Cannot send private message to detached "
+ "client %s", client_entry->nickname);
+ } else if (status != SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY) {
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+ "Private message key exchange failed "
+ "with %s (%s)", client_entry->nickname,
+ silc_ske_map_status(status));
+ }
+
+ /* Errors that occur due to user not responding or deciding not to
+ trust the public key will not cause us to stop trying AKE next time.
+ Other errors disable AKE to allow communication with other means. */
+ if (initiator && status != SILC_SKE_STATUS_TIMEOUT &&
+ status != SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY &&
+ !(client_entry->mode & SILC_UMODE_DETACHED)) {
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_INFO,
+ "Cannot auto-negotiate key with %s, "
+ "messages will be protected with "
+ "session key", client_entry->nickname);
+
+ /* Don't try this again with this client */
+ client_entry->internal.no_ake = TRUE;
+ }
+ goto out;
+ }
+
+ /* Set the new private message key into use */
+ silc_client_del_private_message_key(client, conn, client_entry);
+ client_entry->internal.prv_resp = !initiator;
+ if (!silc_client_add_private_message_key_ske(
+ client, conn, client_entry,
+ silc_cipher_get_name(prop->cipher),
+ silc_hmac_get_name(prop->hmac),
+ keymat)) {
+ SILC_LOG_DEBUG(("Error adding private message key"));
+
+ client->internal->ops->say(client, conn,
+ SILC_CLIENT_MESSAGE_ERROR,
+ "Private message key exchange error: "
+ "cannot use keys");
+
+ /* Don't try this again with this client */
+ client_entry->internal.no_ake = TRUE;
+ goto out;
+ }
+
+ /* Save the public key to client entry */
+ if (!client_entry->public_key) {
+ client_entry->public_key = ake->public_key;
+ ake->public_key = NULL;
+ }
+
+ /* Rekey periodically */
+ client_entry->internal.ake_rekey = silc_time() + 300;
+ if (initiator)
+ client_entry->internal.ake_rekey -= 30;
+ client_entry->internal.ake_generation = conn->internal->ake_generation;
+ client_entry->internal.no_ake = FALSE;
+
+ SILC_LOG_DEBUG(("AKE completed as %s with %s, rekey in %u secs, "
+ "generation %u", initiator ? "initiator" : "responder",
+ client_entry->nickname, 300,
+ conn->internal->ake_generation));
+
+ /* Send queued messages */
+ silc_dlist_start(ake->messages);
+ while ((m = silc_dlist_get(ake->messages)) != SILC_LIST_END) {
+ SILC_GET16_MSB(flags, m->data - 2);
+ silc_client_send_private_message(client, conn, client_entry,
+ flags, ake->hash,
+ silc_buffer_datalen(m));
+ }
+
+ out:
+ conn->context_type = SILC_ID_NONE;
+ conn->client_entry = NULL;
+ silc_schedule_task_add_timeout(client->schedule,
+ silc_client_autoneg_key_finish,
+ client_entry, 0, 1);
+}
+
+/* Auto-negotiate private message key with the remote client using the
+ SKE protocol, which is tunneled through the SILC network inside private
+ messages shared between the us and the remote client.
+
+ This operation is naturally asynchronous and will involve exchanging
+ multiple messages back and forth. Despite this, we don't run this
+ operation in own FSM thread here, but instead will use the SKE library
+ to do the asynchronous operation which we can abort at any time in
+ case user disconnects.
+
+ Messages and packets we receive during this operation will be processed
+ in the normal connection thread. */
+
+SilcBool
+silc_client_autoneg_private_message_key(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry,
+ SilcPacket initiator_packet,
+ SilcMessageFlags flags,
+ SilcHash hash,
+ unsigned char *data,
+ SilcUInt32 data_len)
+{
+ SilcClientAutonegMessageKey ake;
+ SilcBool initiator = initiator_packet == NULL;
+ SilcBuffer m;
+
+ SILC_LOG_DEBUG(("Start private message AKE as %s with %s",
+ initiator ? "initiator" : "responder",
+ client_entry->nickname));
+
+ if (client_entry->internal.op) {
+ ake = client_entry->internal.ake;
+ if (ake && data) {
+ /* If generation has changed, we must abort this exchange and
+ start a new one. */
+ if (ake->generation != conn->internal->ake_generation) {
+ SILC_LOG_DEBUG(("Abort ongoing AKE and start new one"));
+ silc_async_abort(client_entry->internal.op, NULL, NULL);
+ } else {
+ SILC_LOG_DEBUG(("AKE is ongoing, queue the message"));
+
+ m = silc_buffer_alloc_size(data_len + 2);
+ if (!m)
+ return FALSE;
+ SILC_PUT16_MSB(flags, m->data);
+ silc_buffer_pull(m, 2);
+ silc_buffer_put(m, data, data_len);
+ silc_dlist_add(ake->messages, m);
+ return TRUE;
+ }
+ } else {
+ SILC_LOG_DEBUG(("Cannot start AKE, operation %p is ongoing",
+ client_entry->internal.op));
+ return FALSE;
+ }
+ }
+
+ ake = silc_calloc(1, sizeof(*ake));
+ if (!ake)
+ return FALSE;
+ ake->conn = conn;
+ ake->generation = conn->internal->ake_generation;
+
+ ake->messages = silc_dlist_init();
+ if (!ake->messages)
+ goto err;
+
+ /* Wrap our packet stream to a generic stream for the private messages
+ we are going to exchange. We send the packets with packet flag
+ SILC_PACKET_FLAG_PRIVMSG_KEY which is a lie, but is a way to get
+ clients which do not support this protocol to ignore these messages.
+ This kludge should be removed once support is everywhere and
+ responder should look only for the SILC_MESSAGE_FLAG_PACKET. */
+ ake->stream = silc_packet_stream_wrap(conn->stream,
+ SILC_PACKET_PRIVATE_MESSAGE,
+ SILC_PACKET_FLAG_PRIVMSG_KEY, FALSE,
+ SILC_ID_NONE, NULL,
+ SILC_ID_CLIENT, &client_entry->id,
+ silc_client_autoneg_key_coder,
+ client_entry);
+ if (!ake->stream)
+ goto err;
+
+ /* Create a new packet stream for the SKE library using the wrapped
+ stream as the underlaying stream, in effect creating a tunnel to
+ send SKE packets inside private message packets. */
+ ake->ske_stream = silc_packet_stream_create(client->internal->packet_engine,
+ conn->internal->schedule,
+ ake->stream);
+ if (!ake->ske_stream)
+ goto err;
+
+ silc_packet_set_context(ake->ske_stream, client_entry);
+ silc_packet_set_ids(ake->ske_stream, SILC_ID_CLIENT, conn->local_id,
+ SILC_ID_CLIENT, &client_entry->id);
+
+ /* Link to the new packet stream to intercept the packets before they
+ go to SKE library so that we can do additional checks and decide if
+ we really want to process the packets. */
+ if (!silc_packet_stream_link(ake->ske_stream, &autoneg_key_ske_cbs, NULL,
+ 1000001, SILC_PACKET_ANY, -1))
+ goto err;
+
+ /* Create SKE */
+ ake->ske = silc_ske_alloc(client->rng, conn->internal->schedule,
+ conn->internal->params.repository,
+ conn->public_key, conn->private_key,
+ client_entry);
+ if (!ake->ske)
+ goto err;
+
+ silc_ske_set_callbacks(ake->ske, silc_client_autoneg_key_verify_pubkey,
+ silc_client_autoneg_key_done, client_entry);
+ ake->params.version = client->internal->silc_client_version;
+ ake->params.probe_timeout_secs = 5;
+ ake->params.timeout_secs = 120;
+ ake->params.flags = SILC_SKE_SP_FLAG_MUTUAL | SILC_SKE_SP_FLAG_PFS;
+ ake->params.small_proposal = TRUE;
+ ake->params.no_acks = TRUE;
+
+ if (client_entry->internal.send_key &&
+ client_entry->internal.ake_generation == ake->generation) {
+ /* Security properties for rekey */
+ SilcSKESecurityProperties prop = silc_calloc(1, sizeof(*prop));
+ if (!prop)
+ goto err;
+ silc_cipher_alloc(silc_cipher_get_name(client_entry->internal.send_key),
+ &prop->cipher);
+ silc_hmac_alloc(silc_hmac_get_name(client_entry->internal.hmac_send),
+ NULL, &prop->hmac);
+ silc_hash_alloc(silc_hash_get_name(silc_hmac_get_hash(
+ client_entry->internal.hmac_send)), &prop->hash);
+ prop->public_key = silc_pkcs_public_key_copy(client_entry->public_key);
+ silc_ske_group_get_by_number(2, &prop->group);
+ prop->flags = ake->params.flags;
+ ake->params.prop = prop;
+ }
+
+ /* Start key exchange, responder is started in the packet callback */
+ if (initiator) {
+ ake->ske_op = silc_ske_initiator(ake->ske, ake->ske_stream, &ake->params,
+ NULL);
+ if (!ake->ske_op)
+ goto err;
+ }
+
+ /* Finally, set up the client entry */
+ client_entry->internal.op = silc_async_alloc(silc_client_autoneg_key_abort,
+ NULL, client_entry);
+ if (!client_entry->internal.op)
+ goto err;
+ client_entry->internal.ake = ake;
+ client_entry->internal.no_ake = FALSE;
+ client_entry->internal.prv_resp = !initiator;
+ silc_client_ref_client(client, conn, client_entry);
+
+ /* As responder reinject the packet to the new stream so it gets decoded
+ from the private message payload. */
+ if (initiator_packet)
+ silc_packet_stream_inject(conn->stream, initiator_packet);
+
+ /* Save the initial message, it will be sent after the key has been
+ negotiated. */
+ if (data && data_len) {
+ m = silc_buffer_alloc_size(data_len + 2);
+ if (m) {
+ SILC_PUT16_MSB(flags, m->data);
+ silc_buffer_pull(m, 2);
+ silc_buffer_put(m, data, data_len);
+ silc_dlist_add(ake->messages, m);
+ }
+ if (hash)
+ silc_hash_alloc(silc_hash_get_name(hash), &ake->hash);
+ }
+
+ return TRUE;
+
+ err:
+ if (ake->ske)
+ silc_ske_free(ake->ske);
+ if (ake->ske_stream) {
+ silc_packet_stream_unlink(ake->ske_stream, &autoneg_key_ske_cbs, NULL);
+ silc_packet_stream_destroy(ake->ske_stream);
+ } else if (ake->stream)
+ silc_stream_destroy(ake->stream);
+ silc_dlist_uninit(ake->messages);
+ silc_free(ake);
+ return FALSE;
+}
+
/* Sets away `message'. The away message may be set when the client's
mode is changed to SILC_UMODE_GONE and the client whishes to reply
to anyone who sends private message. The `message' will be sent
new one. If `message' is NULL the old away message is removed.
The sender may freely free the memory of the `message'. */
-void silc_client_set_away_message(SilcClient client,
- SilcClientConnection conn,
- char *message)
+SilcBool silc_client_set_away_message(SilcClient client,
+ SilcClientConnection conn,
+ char *message)
{
- assert(client && conn);
+ if (!client || !conn)
+ return FALSE;
- if (!message && conn->internal->away) {
- silc_free(conn->internal->away->away);
- silc_free(conn->internal->away);
- conn->internal->away = NULL;
+ if (!message) {
+ silc_free(conn->internal->away_message);
+ conn->internal->away_message = NULL;
+ return TRUE;
}
- if (message) {
- if (!conn->internal->away)
- conn->internal->away = silc_calloc(1, sizeof(*conn->internal->away));
- if (conn->internal->away->away)
- silc_free(conn->internal->away->away);
- conn->internal->away->away = strdup(message);
- }
+ if (conn->internal->away_message)
+ silc_free(conn->internal->away_message);
+
+ conn->internal->away_message = strdup(message);
+ if (!conn->internal->away_message)
+ return FALSE;
+
+ return TRUE;
}