channel key receiving and setting, and channel private key handling
routines. */
-#include "clientlibincludes.h"
+#include "silcincludes.h"
+#include "silcclient.h"
#include "client_internal.h"
/* Sends packet to the `channel'. Packet to channel is always encrypted
SilcChannelPrivateKey key,
SilcMessageFlags flags,
unsigned char *data,
- uint32 data_len,
+ SilcUInt32 data_len,
int force_send)
{
int i;
SilcSocketConnection sock = conn->sock;
SilcBuffer payload;
SilcPacketContext packetdata;
+ const SilcBufferStruct packet;
SilcCipher cipher;
SilcHmac hmac;
unsigned char *id_string;
- uint32 iv_len;
+ SilcUInt32 iv_len;
+ int block_len;
+ SilcChannelUser chu;
SILC_LOG_DEBUG(("Sending packet to channel"));
+ chu = silc_client_on_channel(channel, conn->local_entry);
+ if (!chu) {
+ SILC_LOG_ERROR(("Cannot send message to channel we are not joined"));
+ return;
+ }
+
+ /* Check if it is allowed to send messages to this channel by us. */
+ if (channel->mode & SILC_CHANNEL_MODE_SILENCE_USERS && !chu->mode)
+ return;
+ if (channel->mode & SILC_CHANNEL_MODE_SILENCE_OPERS &&
+ chu->mode & SILC_CHANNEL_UMODE_CHANOP &&
+ !(chu->mode & SILC_CHANNEL_UMODE_CHANFO))
+ return;
+ if (chu->mode & SILC_CHANNEL_UMODE_QUIET)
+ return;
+
/* Take the key to be used */
if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) {
if (key) {
if (!cipher || !hmac)
return;
+ block_len = silc_cipher_get_block_len(cipher);
+
/* Generate IV */
iv_len = silc_cipher_get_block_len(cipher);
if (channel->iv[0] == '\0')
/* Encode the channel payload. This also encrypts the message payload. */
payload = silc_channel_message_payload_encode(flags, data_len, data, iv_len,
- channel->iv, cipher, hmac);
+ channel->iv, cipher, hmac,
+ client->rng);
/* Get data used in packet header encryption, keys and stuff. */
cipher = conn->send_key;
/* Set the packet context pointers. The destination ID is always
the Channel ID of the channel. Server and router will handle the
distribution of the packet. */
+ data = payload->data;
+ data_len = payload->len;
packetdata.flags = 0;
packetdata.type = SILC_PACKET_CHANNEL_MESSAGE;
packetdata.src_id = conn->local_id_data;
packetdata.dst_id = id_string;
packetdata.dst_id_len = silc_id_get_len(channel->id, SILC_ID_CHANNEL);
packetdata.dst_id_type = SILC_ID_CHANNEL;
- packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN +
+ 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;
packetdata.padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN +
packetdata.src_id_len +
- packetdata.dst_id_len));
-
- /* Prepare outgoing data buffer for packet sending */
- silc_packet_send_prepare(sock,
- SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len +
- packetdata.dst_id_len,
- packetdata.padlen,
- payload->len);
-
- packetdata.buffer = sock->outbuf;
-
- /* Put the channel message payload to the outgoing data buffer */
- silc_buffer_put(sock->outbuf, payload->data, payload->len);
+ packetdata.dst_id_len), block_len);
/* Create the outgoing packet */
- silc_packet_assemble(&packetdata);
+ if (!silc_packet_assemble(&packetdata, client->rng, cipher, hmac, sock,
+ data, data_len, (const SilcBuffer)&packet)) {
+ SILC_LOG_ERROR(("Error assembling packet"));
+ goto out;
+ }
/* Encrypt the header and padding of the packet. This is encrypted
with normal session key shared with our server. */
- silc_packet_encrypt(cipher, hmac, sock->outbuf, SILC_PACKET_HEADER_LEN +
+ silc_packet_encrypt(cipher, hmac, conn->psn_send++,
+ (SilcBuffer)&packet, SILC_PACKET_HEADER_LEN +
packetdata.src_id_len + packetdata.dst_id_len +
packetdata.padlen);
- SILC_LOG_HEXDUMP(("Packet to channel, len %d", sock->outbuf->len),
- sock->outbuf->data, sock->outbuf->len);
+ SILC_LOG_HEXDUMP(("Packet to channel, len %d", packet.len),
+ packet.data, packet.len);
/* Now actually send the packet */
- silc_client_packet_send_real(client, sock, force_send, FALSE);
+ silc_client_packet_send_real(client, sock, force_send);
+
+ out:
silc_buffer_free(payload);
silc_free(id_string);
}
+typedef struct {
+ SilcChannelMessagePayload payload;
+ SilcChannelID *channel_id;
+} *SilcChannelClientResolve;
+
+static void silc_client_channel_message_cb(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count,
+ void *context)
+{
+ SilcChannelClientResolve res = (SilcChannelClientResolve)context;
+
+ if (clients_count == 1) {
+ SilcChannelEntry channel;
+ unsigned char *message;
+ SilcUInt32 message_len;
+
+ channel = silc_client_get_channel_by_id(client, conn, res->channel_id);
+ if (!channel)
+ goto out;
+
+ /* If this client is not on channel, add it there since it clearly
+ is there. */
+ if (!silc_client_on_channel(channel, clients[0])) {
+ SilcChannelUser chu = silc_calloc(1, sizeof(*chu));
+ chu->client = clients[0];
+ chu->channel = channel;
+ silc_hash_table_add(channel->user_list, clients[0], chu);
+ silc_hash_table_add(clients[0]->channels, channel, chu);
+ }
+
+ message = silc_channel_message_get_data(res->payload, &message_len);
+
+ /* Pass the message to application */
+ client->internal->ops->channel_message(
+ client, conn, clients[0], channel,
+ silc_channel_message_get_flags(res->payload),
+ message, message_len);
+ }
+
+ out:
+ silc_channel_message_payload_free(res->payload);
+ silc_free(res->channel_id);
+ silc_free(res);
+}
+
/* Process received message to a channel (or from a channel, really). This
decrypts the channel message with channel specific key and parses the
channel payload. Finally it displays the message on the screen. */
SilcChannelMessagePayload payload = NULL;
SilcChannelID *id = NULL;
SilcChannelEntry channel;
- SilcChannelUser chu;
- SilcIDCacheEntry id_cache = NULL;
+ SilcClientEntry client_entry;
SilcClientID *client_id = NULL;
- int found = FALSE;
unsigned char *message;
+ SilcUInt32 message_len;
SILC_LOG_DEBUG(("Start"));
goto out;
/* Find the channel entry from channels on this connection */
- if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id, &id_cache))
+ channel = silc_client_get_channel_by_id(client, conn, id);
+ if (!channel)
goto out;
- channel = (SilcChannelEntry)id_cache->context;
-
/* If there is no channel private key then just decrypt the message
with the channel key. If private keys are set then just go through
all private keys and check what decrypts correctly. */
if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
/* Parse the channel message payload. This also decrypts the payload */
- payload = silc_channel_message_payload_parse(buffer, channel->channel_key,
+ payload = silc_channel_message_payload_parse(buffer->data, buffer->len,
+ channel->channel_key,
channel->hmac);
- if (!payload)
- goto out;
+
+ /* If decryption failed and we have just performed channel key rekey
+ we will use the old key in decryption. If that fails too then we
+ cannot do more and will drop the packet. */
+ if (!payload) {
+ if (!channel->old_channel_key) {
+ goto out;
+ }
+
+ payload = silc_channel_message_payload_parse(buffer->data, buffer->len,
+ channel->old_channel_key,
+ channel->old_hmac);
+ if (!payload) {
+ goto out;
+ }
+ }
} else if (channel->private_keys) {
SilcChannelPrivateKey entry;
silc_dlist_start(channel->private_keys);
while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
/* Parse the channel message payload. This also decrypts the payload */
- payload = silc_channel_message_payload_parse(buffer, entry->cipher,
+ payload = silc_channel_message_payload_parse(buffer->data, buffer->len,
+ entry->cipher,
entry->hmac);
if (payload)
break;
goto out;
}
- message = silc_channel_message_get_data(payload, NULL);
-
/* Find client entry */
- silc_list_start(channel->clients);
- while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
- if (SILC_ID_CLIENT_COMPARE(chu->client->id, client_id)) {
- found = TRUE;
- break;
- }
+ client_entry = silc_client_get_client_by_id(client, conn, client_id);
+ if (!client_entry || !client_entry->nickname ||
+ !silc_client_on_channel(channel, client_entry)) {
+ /* Resolve the client info */
+ SilcChannelClientResolve res = silc_calloc(1, sizeof(*res));
+ res->payload = payload;
+ res->channel_id = id;
+ silc_client_get_client_by_id_resolve(client, conn, client_id, NULL,
+ silc_client_channel_message_cb,
+ res);
+ payload = NULL;
+ id = NULL;
+ goto out;
}
+ message = silc_channel_message_get_data(payload, &message_len);
+
/* Pass the message to application */
- client->ops->channel_message(client, conn, found ? chu->client : NULL,
- channel,
- silc_channel_message_get_flags(payload),
- message);
+ client->internal->ops->channel_message(
+ client, conn, client_entry, channel,
+ silc_channel_message_get_flags(payload),
+ message, message_len);
out:
- if (id)
- silc_free(id);
- if (client_id)
- silc_free(client_id);
+ silc_free(id);
+ silc_free(client_id);
if (payload)
silc_channel_message_payload_free(payload);
}
+/* Timeout callback that is called after a short period of time after the
+ new channel key has been created. This removes the old channel key all
+ together. */
+
+SILC_TASK_CALLBACK(silc_client_save_channel_key_rekey)
+{
+ SilcChannelEntry channel = (SilcChannelEntry)context;
+
+ if (channel->old_channel_key)
+ silc_cipher_free(channel->old_channel_key);
+ if (channel->old_hmac)
+ silc_hmac_free(channel->old_hmac);
+ channel->old_channel_key = NULL;
+ channel->old_hmac = NULL;
+ channel->rekey_task = NULL;
+}
+
/* Saves channel key from encoded `key_payload'. This is used when we
receive Channel Key Payload and when we are processing JOIN command
reply. */
-void silc_client_save_channel_key(SilcClientConnection conn,
+void silc_client_save_channel_key(SilcClient client,
+ SilcClientConnection conn,
SilcBuffer key_payload,
SilcChannelEntry channel)
{
- unsigned char *id_string, *key, *cipher, hash[32];
- uint32 tmp_len;
+ unsigned char *id_string, *key, *cipher, *hmac, hash[32];
+ SilcUInt32 tmp_len;
SilcChannelID *id;
- SilcIDCacheEntry id_cache = NULL;
SilcChannelKeyPayload payload;
- payload = silc_channel_key_payload_parse(key_payload);
+ payload = silc_channel_key_payload_parse(key_payload->data,
+ key_payload->len);
if (!payload)
return;
/* Find channel. */
if (!channel) {
- if (!silc_idcache_find_by_id_one(conn->channel_cache,
- (void *)id, &id_cache))
+ channel = silc_client_get_channel_by_id(client, conn, id);
+ if (!channel)
goto out;
-
- /* Get channel entry */
- channel = (SilcChannelEntry)id_cache->context;
}
+ hmac = (channel->hmac ? (char *)silc_hmac_get_name(channel->hmac) :
+ SILC_DEFAULT_HMAC);
+
+ /* Save the old key for a short period of time so that we can decrypt
+ channel message even after the rekey if some client would be sending
+ messages with the old key after the rekey. */
+ if (channel->old_channel_key)
+ silc_cipher_free(channel->old_channel_key);
+ if (channel->old_hmac)
+ silc_hmac_free(channel->old_hmac);
+ if (channel->rekey_task)
+ silc_schedule_task_del(client->schedule, channel->rekey_task);
+ channel->old_channel_key = channel->channel_key;
+ channel->old_hmac = channel->hmac;
+ channel->rekey_task =
+ silc_schedule_task_add(client->schedule, 0,
+ silc_client_save_channel_key_rekey, channel,
+ 10, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+
+ /* Free the old channel key data */
+ silc_free(channel->key);
+
/* Save the key */
key = silc_channel_key_get_key(payload, &tmp_len);
cipher = silc_channel_key_get_cipher(payload, NULL);
channel->key_len = tmp_len * 8;
- channel->key = silc_calloc(tmp_len, sizeof(*channel->key));
- memcpy(channel->key, key, tmp_len);
+ channel->key = silc_memdup(key, tmp_len);
if (!silc_cipher_alloc(cipher, &channel->channel_key)) {
- conn->client->ops->say(conn->client, conn,
- "Cannot talk to channel: unsupported cipher %s", cipher);
+ client->internal->ops->say(
+ conn->client, conn,
+ SILC_CLIENT_MESSAGE_AUDIT,
+ "Cannot talk to channel: unsupported cipher %s",
+ cipher);
goto out;
}
silc_cipher_set_key(channel->channel_key, key, channel->key_len);
/* Generate HMAC key from the channel key data and set it */
- if (!channel->hmac)
- silc_hmac_alloc("hmac-sha1-96", NULL, &channel->hmac);
- silc_hash_make(channel->hmac->hash, key, tmp_len, hash);
- silc_hmac_set_key(channel->hmac, hash, silc_hash_len(channel->hmac->hash));
+ silc_hmac_alloc(hmac, NULL, &channel->hmac);
+ silc_hash_make(silc_hmac_get_hash(channel->hmac), key, tmp_len, hash);
+ silc_hmac_set_key(channel->hmac, hash,
+ silc_hash_len(silc_hmac_get_hash(channel->hmac)));
memset(hash, 0, sizeof(hash));
out:
SILC_LOG_DEBUG(("Received key for channel"));
/* Save the key */
- silc_client_save_channel_key(sock->user_data, packet, NULL);
+ silc_client_save_channel_key(client, sock->user_data, packet, NULL);
}
/* Adds private key for channel. This may be set only if the channel's mode
int silc_client_add_channel_private_key(SilcClient client,
SilcClientConnection conn,
SilcChannelEntry channel,
+ const char *name,
char *cipher,
char *hmac,
unsigned char *key,
- uint32 key_len)
+ SilcUInt32 key_len)
{
SilcChannelPrivateKey entry;
unsigned char hash[32];
return FALSE;
if (!cipher)
- cipher = "aes-256-cbc";
+ cipher = SILC_DEFAULT_CIPHER;
if (!hmac)
- hmac = "hmac-sha1-96";
+ hmac = SILC_DEFAULT_HMAC;
if (!silc_cipher_is_supported(cipher))
return FALSE;
/* Save the key */
entry = silc_calloc(1, sizeof(*entry));
- entry->key = silc_calloc(keymat->enc_key_len / 8, sizeof(*entry->key));
- memcpy(entry->key, keymat->send_enc_key, keymat->enc_key_len / 8);
+ entry->name = name ? strdup(name) : NULL;
+ entry->key = silc_memdup(keymat->send_enc_key, keymat->enc_key_len / 8);
entry->key_len = keymat->enc_key_len / 8;
/* Allocate the cipher and set the key*/
/* Generate HMAC key from the channel key data and set it */
silc_hmac_alloc(hmac, NULL, &entry->hmac);
- silc_hash_make(entry->hmac->hash, entry->key, entry->key_len, hash);
- silc_hmac_set_key(entry->hmac, hash, silc_hash_len(entry->hmac->hash));
+ silc_hash_make(silc_hmac_get_hash(entry->hmac), entry->key,
+ entry->key_len, hash);
+ silc_hmac_set_key(entry->hmac, hash,
+ silc_hash_len(silc_hmac_get_hash(entry->hmac)));
memset(hash, 0, sizeof(hash));
/* Add to the private keys list */
silc_dlist_del(channel->private_keys, entry);
memset(entry->key, 0, entry->key_len);
silc_free(entry->key);
+ silc_free(entry->name);
silc_cipher_free(entry->cipher);
silc_hmac_free(entry->hmac);
silc_free(entry);
silc_dlist_del(channel->private_keys, entry);
memset(entry->key, 0, entry->key_len);
silc_free(entry->key);
+ silc_free(entry->name);
silc_cipher_free(entry->cipher);
silc_hmac_free(entry->hmac);
silc_free(entry);
silc_client_list_channel_private_keys(SilcClient client,
SilcClientConnection conn,
SilcChannelEntry channel,
- uint32 *key_count)
+ SilcUInt32 *key_count)
{
SilcChannelPrivateKey *keys = NULL, entry;
- uint32 count = 0;
+ SilcUInt32 count = 0;
if (!channel->private_keys)
return NULL;
/* Frees the SilcChannelPrivateKey array. */
void silc_client_free_channel_private_keys(SilcChannelPrivateKey *keys,
- uint32 key_count)
+ SilcUInt32 key_count)
{
silc_free(keys);
}
+
+/* Sets the `key' to be used as current channel private key on the
+ `channel'. Packet sent after calling this function will be secured
+ with `key'. */
+
+void silc_client_current_channel_private_key(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel,
+ SilcChannelPrivateKey key)
+{
+ channel->curr_key = key;
+}
+
+/* Returns the SilcChannelUser entry if the `client_entry' is joined on the
+ channel indicated by the `channel'. NULL if client is not joined on
+ the channel. */
+
+SilcChannelUser silc_client_on_channel(SilcChannelEntry channel,
+ SilcClientEntry client_entry)
+{
+ SilcChannelUser chu;
+
+ if (silc_hash_table_find(channel->user_list, client_entry, NULL,
+ (void *)&chu))
+ return chu;
+
+ return NULL;
+}