silcclient: auto-negotiation of private message key using SKE over SILCnet
authorPekka Riikonen <priikone@silcnet.org>
Mon, 28 Apr 2014 19:59:28 +0000 (22:59 +0300)
committerPekka Riikonen <priikone@silcnet.org>
Mon, 28 Apr 2014 19:59:28 +0000 (22:59 +0300)
Previously in SILC private messages have been protected in normal mode
using the session key shared between the client and server and other
servers in the network.  This obviously has security implications if
the SILC servers cannot be trusted.

To overcome this issue silcclient library has offered user the ability to
use pre-shared key (or password) as the secret key to protect private
message, or to negotiate fresh key material using SKE peer-to-peer over
the internet (key agreement).

However, both of these feature have severe limitations.  The first one
requiring coordinated effort to somehow share the key or password and
the second requiring peer-to-peer connection which may not be possible
due to NAT and firewalls.

This commit adds a new private message protection method and takes it
into use as the default protection method.  The commits adds support
for automatic negotiation of the private message key using SKE but instead
of doing it peer-to-peer over the internet it is done client-to-client
over the SILC network itself.  This is accomplished by tunneling the
SKE protocol inside private message packets.  As SKE is safe over the
unprotected and untrusted internet it is safe also over the SILC network.

The end result of the auto-negotiation is a shared secret known only
to the two clients.  The SKE provides mutual authentication with digital
signatures to prevent man-in-the-middle attack.  The private messages
protected with this key can be read only by the two clients.  SILC servers
along the way cannot decrypt them.  The key is periodically re-keyed
(5 minutes or so) and it provides Perfect Forward Secrecy.

The auto-negotiation is enabled by default.  It can detect within seconds
if the remote client supports the method and if it doesn't it gives a
notification that the private message protection has been reverted back
to session keys.  Application can disable the feature, if wanted.

This feature does not require any changes to SILC servers.

lib/silcclient/client.h
lib/silcclient/client_internal.h
lib/silcclient/client_prvmsg.c
lib/silcclient/client_prvmsg.h
lib/silcclient/command_reply.c
lib/silcclient/silcclient.h
lib/silccore/silcmessage.h

index 019bd3c23f4be5e65c9c2dfdd13996378c170667..87441e53686520d339a80500e8016c3bb35c4d1a 100644 (file)
@@ -32,6 +32,7 @@ typedef struct SilcChannelEntryStruct *SilcChannelEntry;
 typedef struct SilcServerEntryStruct *SilcServerEntry;
 
 typedef struct SilcClientKeyAgreementStruct *SilcClientKeyAgreement;
+typedef struct SilcClientAutonegMessageKeyStruct *SilcClientAutonegMessageKey;
 typedef struct SilcClientFtpSessionStruct *SilcClientFtpSession;
 typedef struct SilcClientCommandReplyContextStruct
                                            *SilcClientCommandReplyContext;
@@ -54,6 +55,10 @@ typedef struct SilcClientEntryInternalStruct {
   SilcClientKeyAgreement ke;   /* Current key agreement context or NULL */
   SilcAsyncOperation op;       /* Asynchronous operation with this client */
 
+  SilcClientAutonegMessageKey ake; /* Current auto-negotiation context */
+  SilcInt64 ake_rekey;         /* Next private message key auto-negotation */
+  SilcUInt32 ake_generation;   /* current AKE rekey generation */
+
   SilcAtomic32 refcnt;         /* Reference counter */
   SilcAtomic32 deleted;                /* Flag indicating whether the client object is
                                   already scheduled for deletion */
@@ -65,6 +70,9 @@ typedef struct SilcClientEntryInternalStruct {
   unsigned int generated   : 1; /* TRUE if library generated `key' */
   unsigned int prv_resp    : 1; /* TRUE if we are responder when using
                                   private message keys. */
+  unsigned int no_ake      : 1;        /* TRUE if client doesn't support
+                                  auto-negotiation of private message key,
+                                  or it doesn't work. */
 } SilcClientEntryInternal;
 
 /* Internal channel entry context */
index 54582e20e3af80fb0b1143397209fb85c8dc5f63..dac40c41d41d6c13c7590dd24c7dd7fd2b8ce6ef 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2007 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
 typedef struct {
   SilcSKE ske;
   SilcSKEVerifyCbCompletion completion;
+  SilcPublicKey public_key;
   void *completion_context;
+  void *context;
+  SilcBool aborted;
 } *SilcVerifyKeyContext;
 
 /* Command and command reply context used to hold registered commands
@@ -150,6 +153,7 @@ struct SilcClientConnectionInternalStruct {
   SilcUInt8 retry_timer;                /* Packet retry timer */
   SilcClientConnectionStatus status;    /* Connection callback status */
   SilcStatus error;                     /* Connection callback error */
+  SilcUInt32 ake_generation;            /* next AKE rekey generation */
 
   /* Events */
   unsigned int connect            : 1;  /* Connect remote host */
index 5dd74141ceea07547d09b7fe95b737e7efb86afc..01d80c61125243091338826e65034555a43d32b6 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2007 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
@@ -47,8 +47,21 @@ SilcBool silc_client_send_private_message(SilcClient client,
 
   SILC_LOG_DEBUG(("Sending private message"));
 
+  /* 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);
+  }
+
   sid.type = SILC_ID_CLIENT;
-  sid.u.client_id = conn->local_entry->id;
+  sid.u.client_id = *conn->local_id;
   rid.type = SILC_ID_CLIENT;
   rid.u.client_id = client_entry->id;
 
@@ -139,8 +152,26 @@ SILC_FSM_STATE(silc_client_private_message)
 
   if (silc_unlikely(packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY &&
                    !remote_client->internal.receive_key &&
-                   !remote_client->internal.hmac_receive))
+                   !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 =
@@ -151,12 +182,35 @@ SILC_FSM_STATE(silc_client_private_message)
                               packet->src_id, packet->src_id_len,
                               packet->dst_id, packet->dst_id_len,
                               NULL, FALSE, NULL);
-  if (silc_unlikely(!payload))
+  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;
 
-  /* Pass the private message to application */
   flags = silc_message_get_flags(payload);
+
+  /* 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);
 
@@ -178,7 +232,8 @@ SILC_FSM_STATE(silc_client_private_message)
 
  out:
   /** Packet processed */
-  silc_packet_free(packet);
+  if (packet)
+    silc_packet_free(packet);
   silc_client_unref_client(client, conn, remote_client);
   if (payload)
     silc_message_payload_free(payload);
@@ -461,6 +516,7 @@ SilcBool silc_client_add_private_message_key_ske(SilcClient client,
     return FALSE;
 
   client_entry->internal.generated = TRUE;
+  client_entry->internal.no_ake = TRUE;
 
   /* Allocate the cipher and HMAC */
   if (!silc_cipher_alloc(cipher, &client_entry->internal.send_key))
@@ -616,6 +672,623 @@ silc_client_private_message_key_is_set(SilcClient client,
   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;
+  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;
+  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;
+  }
+
+  /* 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"));
+
+  /* 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;
+  SilcSKEParamsStruct params = {};
+  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);
+  params.version = client->internal->silc_client_version;
+  params.probe_timeout_secs = 5;
+  params.timeout_secs = 120;
+  params.flags = SILC_SKE_SP_FLAG_MUTUAL | SILC_SKE_SP_FLAG_PFS;
+  params.small_proposal = TRUE;
+  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 = params.flags;
+    params.prop = prop;
+  }
+
+  /* Start key exchange */
+  if (initiator)
+    ake->ske_op = silc_ske_initiator(ake->ske, ake->ske_stream, &params, NULL);
+  else
+    ake->ske_op = silc_ske_responder(ake->ske, ake->ske_stream, &params);
+  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, re-inject the initiator's private message back to the
+     stream so that the new SKE gets it. */
+  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
index 93e5532d114087fa12f1c256b44761bcfdbd0e05..7cc1bf9c25a5252a6c0a8630cff088e6ce76369a 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2006 Pekka Riikonen
+  Copyright (C) 2006 - 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
@@ -24,4 +24,14 @@ SILC_FSM_STATE(silc_client_private_message);
 SILC_FSM_STATE(silc_client_private_message_error);
 SILC_FSM_STATE(silc_client_private_message_key);
 
+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);
+
 #endif /* CLIENT_PRVMSG_H */
index 54b77757742ba6bca9711be3d5f91cb41eab72ba..45de171ef1d72b79c26f2524757f07b1025da66e 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2007 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
@@ -738,6 +738,12 @@ SILC_FSM_STATE(silc_client_command_reply_nick)
     goto out;
   }
 
+  /* All auto-generated private message keys must be rekeyed because
+     we changed nick and others may not know about it. */
+  conn->internal->ake_generation++;
+  SILC_LOG_DEBUG(("AKE keys will be rekeyed, generation %u",
+                 conn->internal->ake_generation));
+
   silc_rwlock_unlock(conn->local_entry->internal.lock);
 
   /* Notify application */
index 0d07b93867d7abb84e6775fae8cb35f11aa3a5ca..58e43f158c85230ee5f126acb31649d01f56329f 100644 (file)
@@ -720,6 +720,10 @@ typedef struct SilcClientParamsStruct {
      itself will need to handle that. */
   SilcBool dont_register_crypto_library;
 
+  /* If this is set to TRUE, the silcclient library will not automatically
+     negotiate private message keys using SKE over the SILC network but will
+     use normal session keys to protect private messages. */
+  SilcBool dont_autoneg_prvmsg_keys;
 } SilcClientParams;
 /***/
 
@@ -1177,7 +1181,7 @@ void silc_client_close_connection(SilcClient client,
  *
  *    Returns TRUE if the message was sent, and FALSE if error occurred or
  *    the sending is not allowed due to channel modes (like sending is
- *    blocked).  This function is thread safe and private messages can be
+ *    blocked).  This function is thread safe and channel messages can be
  *    sent from multiple threads.
  *
  ***/
index 99e9d988d94aedfcffbc6790938e396df602a298..9d6df25203ea832e7e37820e6c31dd8281de71de 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2007 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
@@ -79,7 +79,8 @@ typedef SilcUInt16 SilcMessageFlags;
 #define SILC_MESSAGE_FLAG_UTF8        0x0100     /* UTF-8 string */
 #define SILC_MESSAGE_FLAG_ACK         0x0200     /* ACK messages */
 #define SILC_MESSAGE_FLAG_STOP        0x0400      /* Stop indication */
-#define SILC_MESSAGE_FLAG_RESERVED    0x0800      /* to 0x1000 */
+#define SILC_MESSAGE_FLAG_PACKET      0x0800      /* Contains SILC packet */
+#define SILC_MESSAGE_FLAG_RESERVED    0x1000      /* to 0x1000 */
 #define SILC_MESSAGE_FLAG_PRIVATE     0x2000     /* to 0x8000 */
 /***/