updates.
[silc.git] / lib / silcclient / client_channel.c
index c12532ca6d68af0ca77977354cd045bf1b5b4afe..bec415ab8ff218d60e7916e138ea1e631f8a4a34 100644 (file)
@@ -35,6 +35,7 @@
 void silc_client_send_channel_message(SilcClient client, 
                                      SilcClientConnection conn,
                                      SilcChannelEntry channel,
+                                     SilcChannelPrivateKey key,
                                      unsigned char *data, 
                                      unsigned int data_len, 
                                      int force_send)
@@ -50,14 +51,46 @@ void silc_client_send_channel_message(SilcClient client,
 
   SILC_LOG_DEBUG(("Sending packet to channel"));
 
-  if (!channel || !channel->key || !channel->hmac) {
+  if (!channel || !channel->hmac || 
+      (!channel->channel_key && !key && !channel->private_keys)) {
     client->ops->say(client, conn, 
                     "Cannot talk to channel: key does not exist");
     return;
   }
 
+  /* Take the key to be used */
+  if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) {
+    if (key) {
+      /* Use key application specified */
+      cipher = key->cipher;
+      hmac = key->hmac;
+    } else if (channel->curr_key) {
+      /* Use current private key */
+      cipher = channel->curr_key->cipher;
+      hmac = channel->curr_key->hmac;
+    } else if (!channel->curr_key && channel->private_keys) {
+      /* Use just some private key since we don't know what to use 
+        and private keys are set. */
+      silc_dlist_start(channel->private_keys);
+      key = silc_dlist_get(channel->private_keys);
+      cipher = key->cipher;
+      hmac = key->hmac;
+      
+      /* Use this key as current private key */
+      channel->curr_key = key;
+    } else {
+      /* Use normal channel key generated by the server */
+      cipher = channel->channel_key;
+      hmac = channel->hmac;
+    }
+  } else {
+    /* Use normal channel key generated by the server */
+    cipher = channel->channel_key;
+    hmac = channel->hmac;
+  }
+
   /* Generate IV */
-  iv_len = silc_cipher_get_block_len(channel->channel_key);
+  iv_len = silc_cipher_get_block_len(cipher);
   if (channel->iv[0] == '\0')
     for (i = 0; i < iv_len; i++) channel->iv[i] = 
                                   silc_rng_get_byte(client->rng);
@@ -65,9 +98,8 @@ void silc_client_send_channel_message(SilcClient client,
     silc_hash_make(client->md5hash, channel->iv, iv_len, channel->iv);
 
   /* Encode the channel payload. This also encrypts the message payload. */
-  payload = silc_channel_payload_encode(data_len, data, iv_len, 
-                                       channel->iv, channel->channel_key,
-                                       channel->hmac, client->rng);
+  payload = silc_channel_message_payload_encode(0, data_len, data, iv_len, 
+                                               channel->iv, cipher, hmac);
 
   /* Get data used in packet header encryption, keys and stuff. */
   cipher = conn->send_key;
@@ -132,7 +164,7 @@ void silc_client_channel_message(SilcClient client,
 {
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
   SilcBuffer buffer = packet->buffer;
-  SilcChannelPayload payload = NULL;
+  SilcChannelMessagePayload payload = NULL;
   SilcChannelID *id = NULL;
   SilcChannelEntry channel;
   SilcChannelUser chu;
@@ -162,13 +194,33 @@ void silc_client_channel_message(SilcClient client,
 
   channel = (SilcChannelEntry)id_cache->context;
 
-  /* Parse the channel message payload. This also decrypts the payload */
-  payload = silc_channel_payload_parse(buffer, channel->channel_key,
-                                      channel->hmac);
-  if (!payload)
+  /* 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,
+                                                channel->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,
+                                                  entry->hmac);
+      if (payload)
+       break;
+    }
+    if (entry == SILC_LIST_END)
+      goto out;
+  } else {
     goto out;
+  }
 
-  message = silc_channel_get_data(payload, NULL);
+  message = silc_channel_message_get_data(payload, NULL);
 
   /* Find client entry */
   silc_list_start(channel->clients);
@@ -189,7 +241,7 @@ void silc_client_channel_message(SilcClient client,
   if (client_id)
     silc_free(client_id);
   if (payload)
-    silc_channel_payload_free(payload);
+    silc_channel_message_payload_free(payload);
 }
 
 /* Saves channel key from encoded `key_payload'. This is used when we
@@ -285,7 +337,10 @@ void silc_client_receive_channel_key(SilcClient client,
    encrypted using that key. All clients on the channel must also know the
    key in order to decrypt the messages. However, it is possible to have
    several private keys per one channel. In this case only some of the
-   clients on the channel may now the one key and only some the other key.
+   clients on the channel may know the one key and only some the other key.
+
+   if `cipher' and/or `hmac' is NULL then default values will be used 
+   (aes-256-cbc for cipher and hmac-sha1-96 for hmac).
 
    The private key for channel is optional. If it is not set then the
    channel messages are encrypted using the channel key generated by the
@@ -309,9 +364,63 @@ int silc_client_add_channel_private_key(SilcClient client,
                                        SilcClientConnection conn,
                                        SilcChannelEntry channel,
                                        char *cipher,
+                                       char *hmac,
                                        unsigned char *key,
                                        unsigned int key_len)
 {
+  SilcChannelPrivateKey entry;
+  unsigned char hash[32];
+
+  if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY))
+    return FALSE;
+
+  if (!cipher)
+    cipher = "aes-256-cbc";
+  if (!hmac)
+    hmac = "hmac-sha1-96";
+
+  if (!silc_cipher_is_supported(cipher))
+    return FALSE;
+
+  if (!silc_hmac_is_supported(hmac))
+    return FALSE;
+
+  /* Remove the current key, if it exists. */
+  if (channel->channel_key) {
+    silc_cipher_free(channel->channel_key);
+    memset(channel->key, 0, channel->key_len / 8);
+    silc_free(channel->key);
+    channel->channel_key = NULL;
+    channel->key = NULL;
+    channel->key_len = 0;
+  }
+  if (channel->hmac)
+    silc_hmac_free(channel->hmac);
+
+  if (!channel->private_keys)
+    channel->private_keys = silc_dlist_init();
+
+  /* Save the key */
+  entry = silc_calloc(1, sizeof(*entry));
+  entry->key = silc_calloc(key_len, sizeof(*entry->key));
+  memcpy(entry->key, key, key_len);
+  entry->key_len = key_len;
+
+  /* Allocate the cipher and set the key*/
+  silc_cipher_alloc(cipher, &entry->cipher);
+  silc_cipher_set_key(entry->cipher, key, key_len * 8);
+
+  /* Generate HMAC key from the channel key data and set it */
+  silc_hmac_alloc(hmac, NULL, &entry->hmac);
+  silc_hash_make(entry->hmac->hash, key, key_len, hash);
+  silc_hmac_set_key(entry->hmac, hash, silc_hash_len(entry->hmac->hash));
+  memset(hash, 0, sizeof(hash));
+
+  /* Add to the private keys list */
+  silc_dlist_add(channel->private_keys, entry);
+
+  if (!channel->curr_key)
+    channel->curr_key = entry;
 
   return TRUE;
 }
@@ -324,6 +433,25 @@ int silc_client_del_channel_private_keys(SilcClient client,
                                         SilcClientConnection conn,
                                         SilcChannelEntry channel)
 {
+  SilcChannelPrivateKey entry;
+
+  if (!channel->private_keys)
+    return FALSE;
+
+  silc_dlist_start(channel->private_keys);
+  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
+    silc_dlist_del(channel->private_keys, entry);
+    memset(entry->key, 0, entry->key_len);
+    silc_free(entry->key);
+    silc_cipher_free(entry->cipher);
+    silc_hmac_free(entry->hmac);
+    silc_free(entry);
+  }
+
+  channel->curr_key = NULL;
+
+  silc_dlist_uninit(channel->private_keys);
+  channel->private_keys = NULL;
 
   return TRUE;
 }
@@ -339,8 +467,34 @@ int silc_client_del_channel_private_key(SilcClient client,
                                        SilcChannelEntry channel,
                                        SilcChannelPrivateKey key)
 {
+  SilcChannelPrivateKey entry;
+
+  if (!channel->private_keys)
+    return FALSE;
+
+  silc_dlist_start(channel->private_keys);
+  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
+    if (entry == key) {
+      if (channel->curr_key == entry)
+       channel->curr_key = NULL;
+
+      silc_dlist_del(channel->private_keys, entry);
+      memset(entry->key, 0, entry->key_len);
+      silc_free(entry->key);
+      silc_cipher_free(entry->cipher);
+      silc_hmac_free(entry->hmac);
+      silc_free(entry);
+
+      if (silc_dlist_count(channel->private_keys) == 0) {
+       silc_dlist_uninit(channel->private_keys);
+       channel->private_keys = NULL;
+      }
+
+      return TRUE;
+    }
+  }
 
-  return TRUE;
+  return FALSE;
 }
 
 /* Returns array (pointers) of private keys associated to the `channel'.
@@ -355,8 +509,23 @@ silc_client_list_channel_private_keys(SilcClient client,
                                      SilcChannelEntry channel,
                                      unsigned int *key_count)
 {
+  SilcChannelPrivateKey *keys = NULL, entry;
+  unsigned int count = 0;
 
-  return NULL;
+  if (!channel->private_keys)
+    return NULL;
+
+  silc_dlist_start(channel->private_keys);
+  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
+    keys = silc_realloc(keys, sizeof(*keys) * (count + 1));
+    keys[count] = entry;
+    count++;
+  }
+
+  if (key_count)
+    *key_count = count;
+
+  return keys;
 }
 
 /* Frees the SilcChannelPrivateKey array. */
@@ -364,5 +533,5 @@ silc_client_list_channel_private_keys(SilcClient client,
 void silc_client_free_channel_private_keys(SilcChannelPrivateKey *keys,
                                           unsigned int key_count)
 {
-
+  silc_free(keys);
 }