silcclient: auto-negotiation of private message key using SKE over SILCnet
[silc.git] / lib / silcclient / client_prvmsg.c
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