updates.
[silc.git] / lib / silcclient / protocol.c
index b1b9099ab1beebfbe46b5aa0b471b247ea03726a..a21ea9c45975a53b436e88bbe7174bfe854521fa 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
 
-  Copyright (C) 1997 - 2000 Pekka Riikonen
+  Copyright (C) 1997 - 2001 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
 /* $Id$ */
 
 #include "clientlibincludes.h"
+#include "client_internal.h"
 
 SILC_TASK_CALLBACK(silc_client_protocol_connection_auth);
 SILC_TASK_CALLBACK(silc_client_protocol_key_exchange);
+SILC_TASK_CALLBACK(silc_client_protocol_rekey);
 
 extern char *silc_version_string;
 
@@ -35,10 +37,10 @@ extern char *silc_version_string;
 
 /* Function that is called when SKE protocol sends packets to network. */
 
-static void silc_client_protocol_ke_send_packet(SilcSKE ske,
-                                               SilcBuffer packet,
-                                               SilcPacketType type,
-                                               void *context)
+void silc_client_protocol_ke_send_packet(SilcSKE ske,
+                                        SilcBuffer packet,
+                                        SilcPacketType type,
+                                        void *context)
 {
   SilcProtocol protocol = (SilcProtocol)context;
   SilcClientKEInternalContext *ctx = 
@@ -48,18 +50,16 @@ static void silc_client_protocol_ke_send_packet(SilcSKE ske,
   /* Send the packet immediately */
   silc_client_packet_send(client, ske->sock, type, NULL, 0, NULL, NULL,
                          packet->data, packet->len, TRUE);
-
 }
 
 /* Callback that is called when we have received KE2 payload from
    responder. We try to verify the public key now. */
 
-static SilcSKEStatus 
-silc_client_protocol_ke_verify_key(SilcSKE ske,
-                                  unsigned char *pk_data,
-                                  unsigned int pk_len,
-                                  SilcSKEPKType pk_type,
-                                  void *context)
+SilcSKEStatus silc_client_protocol_ke_verify_key(SilcSKE ske,
+                                                unsigned char *pk_data,
+                                                uint32 pk_len,
+                                                SilcSKEPKType pk_type,
+                                                void *context)
 {
   SilcProtocol protocol = (SilcProtocol)context;
   SilcClientKEInternalContext *ctx = 
@@ -68,8 +68,9 @@ silc_client_protocol_ke_verify_key(SilcSKE ske,
 
   SILC_LOG_DEBUG(("Start"));
 
-  /* Verify server key from user. */
-  if (!client->ops->verify_server_key(client, ctx->sock->user_data, 
+  /* Verify public key from user. */
+  if (!client->ops->verify_public_key(client, ctx->sock->user_data, 
+                                     ctx->sock->type,
                                      pk_data, pk_len, pk_type))
     return SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY;
 
@@ -78,15 +79,16 @@ silc_client_protocol_ke_verify_key(SilcSKE ske,
 
 /* Sets the negotiated key material into use for particular connection. */
 
-static void silc_client_protocol_ke_set_keys(SilcSKE ske,
-                                            SilcSocketConnection sock,
-                                            SilcSKEKeyMaterial *keymat,
-                                            SilcCipher cipher,
-                                            SilcPKCS pkcs,
-                                            SilcHash hash)
+void silc_client_protocol_ke_set_keys(SilcSKE ske,
+                                     SilcSocketConnection sock,
+                                     SilcSKEKeyMaterial *keymat,
+                                     SilcCipher cipher,
+                                     SilcPKCS pkcs,
+                                     SilcHash hash,
+                                     SilcHmac hmac,
+                                     SilcSKEDiffieHellmanGroup group)
 {
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcHash nhash;
 
   SILC_LOG_DEBUG(("Setting new keys into use"));
 
@@ -113,10 +115,52 @@ static void silc_client_protocol_ke_set_keys(SilcSKE ske,
                           ske->ke2_payload->pk_len);
 #endif
 
+  conn->rekey = silc_calloc(1, sizeof(*conn->rekey));
+  conn->rekey->send_enc_key = 
+    silc_calloc(keymat->enc_key_len / 8,
+               sizeof(*conn->rekey->send_enc_key));
+  memcpy(conn->rekey->send_enc_key, 
+        keymat->send_enc_key, keymat->enc_key_len / 8);
+  conn->rekey->enc_key_len = keymat->enc_key_len / 8;
+
+  if (ske->start_payload->flags & SILC_SKE_SP_FLAG_PFS)
+    conn->rekey->pfs = TRUE;
+  conn->rekey->ske_group = silc_ske_group_get_number(group);
+
   /* Save HMAC key to be used in the communication. */
-  silc_hash_alloc(hash->hash->name, &nhash);
-  silc_hmac_alloc(nhash, &conn->hmac);
+  silc_hmac_alloc(hmac->hmac->name, NULL, &conn->hmac);
   silc_hmac_set_key(conn->hmac, keymat->hmac_key, keymat->hmac_key_len);
+
+  /* Save the HASH function */
+  silc_hash_alloc(hash->hash->name, &conn->hash);
+}
+
+/* Checks the version string of the server. */
+
+SilcSKEStatus silc_ske_check_version(SilcSKE ske, unsigned char *version,
+                                    uint32 len)
+{
+  SilcClientConnection conn = (SilcClientConnection)ske->sock->user_data;
+  SilcClient client = (SilcClient)ske->user_data;
+  SilcSKEStatus status = SILC_SKE_STATUS_OK;
+
+  /* Check for initial version string */
+  if (!strstr(version, "SILC-1.0-"))
+    status = SILC_SKE_STATUS_BAD_VERSION;
+
+  /* Check software version */
+
+  if (len < strlen(silc_version_string))
+    status = SILC_SKE_STATUS_BAD_VERSION;
+
+  /* XXX for now there is no other tests due to the abnormal version
+     string that is used */
+
+  if (status != SILC_SKE_STATUS_OK)
+    client->ops->say(client, conn, 
+                    "We don't support server version `%s'", version);
+
+  return status;
 }
 
 /* Performs key exchange protocol. This is used for both initiator
@@ -148,19 +192,15 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
       ske = silc_ske_alloc();
       ctx->ske = ske;
       ske->rng = client->rng;
+      ske->user_data = (void *)client;
       
       if (ctx->responder == TRUE) {
-#if 0
-       SilcBuffer start_payload;
-
-
        /* Start the key exchange by processing the received security
           properties packet from initiator. */
        status = silc_ske_responder_start(ske, ctx->rng, ctx->sock,
-                                         start_payload,
-                                         silc_client_protocol_ke_send_packet,
-                                         context);
-#endif
+                                         silc_version_string,
+                                         ctx->packet->buffer, TRUE,
+                                         NULL, NULL);
       } else {
        SilcSKEStartPayload *start_payload;
 
@@ -173,7 +213,7 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
           to the remote end. */
        status = silc_ske_initiator_start(ske, ctx->rng, ctx->sock,
                                          start_payload,
-                                         silc_client_protocol_ke_send_packet,
+                                         ctx->send_packet,
                                          context);
       }
 
@@ -188,8 +228,10 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
        return;
       }
 
-      /* Advance the state of the protocol. */
+      /* Advance protocol state and call the next state if we are responder */
       protocol->state++;
+      if (ctx->responder == TRUE)
+       protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 100000);
     }
     break;
   case 2:
@@ -198,19 +240,19 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
        * Phase 1 
        */
       if (ctx->responder == TRUE) {
-#if 0
+       /* Sends the selected security properties to the initiator. */
        status = 
          silc_ske_responder_phase_1(ctx->ske, 
                                     ctx->ske->start_payload,
-                                    silc_server_protocol_ke_send_packet,
+                                    ctx->send_packet,
                                     context);
-#endif
       } else {
        /* Call Phase-1 function. This processes the Key Exchange Start
           paylaod reply we just got from the responder. The callback
           function will receive the processed payload where we will
           save it. */
-       status = silc_ske_initiator_phase_1(ctx->ske, ctx->packet, NULL, NULL);
+       status = silc_ske_initiator_phase_1(ctx->ske, ctx->packet->buffer, 
+                                           NULL, NULL);
       }
 
       if (status != SILC_SKE_STATUS_OK) {
@@ -224,9 +266,10 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
        return;
       }
 
-      /* Advance the state of the protocol and call the next state. */
+      /* Advance protocol state and call next state if we are initiator */
       protocol->state++;
-      protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0);
+      if (ctx->responder == FALSE)
+       protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 100000);
     }
     break;
   case 3:
@@ -235,22 +278,20 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
        * Phase 2 
        */
       if (ctx->responder == TRUE) {
-#if 0
-       status = 
-         silc_ske_responder_phase_2(ctx->ske, 
-                                    ctx->ske->start_payload,
-                                    silc_server_protocol_ke_send_packet,
-                                    context);
-#endif
+       /* Process the received Key Exchange 1 Payload packet from
+          the initiator. This also creates our parts of the Diffie
+          Hellman algorithm. */
+       status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer, 
+                                           ctx->verify, context, NULL, NULL);
       } else {
        /* Call the Phase-2 function. This creates Diffie Hellman
           key exchange parameters and sends our public part inside
           Key Exhange 1 Payload to the responder. */
-       status = 
-         silc_ske_initiator_phase_2(ctx->ske,
-                                    client->public_key,
-                                    silc_client_protocol_ke_send_packet,
-                                    context);
+       status = silc_ske_initiator_phase_2(ctx->ske,
+                                           client->public_key,
+                                           client->private_key,
+                                           ctx->send_packet,
+                                           context);
       }
 
       if (status != SILC_SKE_STATUS_OK) {
@@ -264,8 +305,10 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
        return;
       }
 
-      /* Advance the state of the protocol. */
+      /* Advance protocol state and call the next state if we are responder */
       protocol->state++;
+      if (ctx->responder == TRUE)
+       protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 100000);
     }
     break;
   case 4:
@@ -274,20 +317,20 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
        * Finish protocol
        */
       if (ctx->responder == TRUE) {
-       status = 0;
-#if 0
+       /* This creates the key exchange material and sends our
+          public parts to the initiator inside Key Exchange 2 Payload. */
        status = 
-         silc_ske_responder_phase_2(ctx->ske, 
-                                    ctx->ske->start_payload,
-                                    silc_server_protocol_ke_send_packet,
-                                    context);
-#endif
+         silc_ske_responder_finish(ctx->ske, 
+                                   client->public_key, client->private_key,
+                                   SILC_SKE_PK_TYPE_SILC,
+                                   ctx->send_packet,
+                                   context);
+       status = 0;
       } else {
        /* Finish the protocol. This verifies the Key Exchange 2 payload
           sent by responder. */
-       status = silc_ske_initiator_finish(ctx->ske, ctx->packet,
-                                          silc_client_protocol_ke_verify_key,
-                                          context, NULL, NULL);
+       status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer,
+                                          ctx->verify, context, NULL, NULL);
       }
 
       if (status != SILC_SKE_STATUS_OK) {
@@ -308,28 +351,45 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
       
       /* Send Ok to the other end. We will end the protocol as server
         sends Ok to us when we will take the new keys into use. */
-      silc_ske_end(ctx->ske, silc_client_protocol_ke_send_packet, context);
+      if (ctx->responder == FALSE)
+       silc_ske_end(ctx->ske, ctx->send_packet, context);
       
       /* End the protocol on the next round */
       protocol->state = SILC_PROTOCOL_STATE_END;
     }
     break;
+
   case SILC_PROTOCOL_STATE_END:
     {
       /* 
        * End protocol
        */
       SilcSKEKeyMaterial *keymat;
+      int key_len = silc_cipher_get_key_len(ctx->ske->prop->cipher);
+      int hash_len = ctx->ske->prop->hash->hash->hash_len;
 
       /* Process the key material */
       keymat = silc_calloc(1, sizeof(*keymat));
-      silc_ske_process_key_material(ctx->ske, 16, (16 * 8), 16, keymat);
+      status = silc_ske_process_key_material(ctx->ske, 16, key_len, hash_len,
+                                            keymat);
+      if (status != SILC_SKE_STATUS_OK) {
+       protocol->state = SILC_PROTOCOL_STATE_ERROR;
+       protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 300000);
+       silc_ske_free_key_material(keymat);
+       return;
+      }
+      ctx->keymat = keymat;
+
+      /* Send Ok to the other end if we are responder. If we are initiator
+        we have sent this already. */
+      if (ctx->responder == TRUE)
+       silc_ske_end(ctx->ske, ctx->send_packet, context);
 
-      /* Take the negotiated keys into use. */
-      silc_client_protocol_ke_set_keys(ctx->ske, ctx->sock, keymat,
-                                      ctx->ske->prop->cipher,
-                                      ctx->ske->prop->pkcs,
-                                      ctx->ske->prop->hash);
+      /* Unregister the timeout task since the protocol has ended. 
+        This was the timeout task to be executed if the protocol is
+        not completed fast enough. */
+      if (ctx->timeout_task)
+       silc_task_unregister(client->timeout_queue, ctx->timeout_task);
 
       /* Protocol has ended, call the final callback */
       if (protocol->final_callback)
@@ -338,8 +398,34 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
        silc_protocol_free(protocol);
     }
     break;
+
   case SILC_PROTOCOL_STATE_ERROR:
+    /*
+     * Error during protocol
+     */
     
+    /* Send abort notification */
+    silc_ske_abort(ctx->ske, ctx->ske->status, 
+                  ctx->send_packet, context);
+
+    /* On error the final callback is always called. */
+    if (protocol->final_callback)
+      protocol->execute_final(client->timeout_queue, 0, protocol, fd);
+    else
+      silc_protocol_free(protocol);
+    break;
+
+  case SILC_PROTOCOL_STATE_FAILURE:
+    /*
+     * Received failure from remote.
+     */
+
+    /* Unregister the timeout task since the protocol has ended. 
+       This was the timeout task to be executed if the protocol is
+       not completed fast enough. */
+    if (ctx->timeout_task)
+      silc_task_unregister(client->timeout_queue, ctx->timeout_task);
+
     /* On error the final callback is always called. */
     if (protocol->final_callback)
       protocol->execute_final(client->timeout_queue, 0, protocol, fd);
@@ -355,6 +441,53 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
  * Connection Authentication protocol functions
  */
 
+static int
+silc_client_get_public_key_auth(SilcClient client,
+                               char *filepath,
+                               unsigned char *auth_data,
+                               uint32 *auth_data_len,
+                               SilcSKE ske)
+{
+  int len;
+  SilcPKCS pkcs;
+  SilcBuffer auth;
+  SilcPublicKey pub_key;
+
+  if (!silc_pkcs_load_public_key(filepath,&pub_key, SILC_PKCS_FILE_PEM))
+    if (!silc_pkcs_load_public_key(filepath, &pub_key, SILC_PKCS_FILE_BIN))
+      return FALSE;
+
+  silc_pkcs_alloc(pub_key->name, &pkcs);
+  if (!silc_pkcs_public_key_set(pkcs, pub_key)) {
+    silc_pkcs_free(pkcs);
+    silc_pkcs_public_key_free(pub_key);
+    return FALSE;
+  }
+
+  /* Make the authentication data. Protocol says it is HASH plus
+     KE Start Payload. */
+  len = ske->hash_len + ske->start_payload_copy->len;
+  auth = silc_buffer_alloc(len);
+  silc_buffer_pull_tail(auth, len);
+  silc_buffer_format(auth,
+                    SILC_STR_UI_XNSTRING(ske->hash, ske->hash_len),
+                    SILC_STR_UI_XNSTRING(ske->start_payload_copy->data,
+                                         ske->start_payload_copy->len),
+                    SILC_STR_END);
+
+  if (silc_pkcs_sign(pkcs, auth->data, auth->len, auth_data, auth_data_len)) {
+    silc_pkcs_free(pkcs);
+    silc_buffer_free(auth);
+    silc_pkcs_public_key_free(pub_key);
+    return TRUE;
+  }
+
+  silc_pkcs_free(pkcs);
+  silc_buffer_free(auth);
+  silc_pkcs_public_key_free(pub_key);
+  return FALSE;
+}
+
 SILC_TASK_CALLBACK(silc_client_protocol_connection_auth)
 {
   SilcProtocol protocol = (SilcProtocol)context;
@@ -378,14 +511,14 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth)
       SilcBuffer packet;
       int payload_len = 0;
       unsigned char *auth_data = NULL;
-      unsigned int auth_data_len = 0;
+      uint32 auth_data_len = 0;
 
       switch(ctx->auth_meth) {
-      case SILC_PROTOCOL_CONN_AUTH_NONE:
+      case SILC_AUTH_NONE:
        /* No authentication required */
        break;
 
-      case SILC_PROTOCOL_CONN_AUTH_PASSWORD:
+      case SILC_AUTH_PASSWORD:
        /* Password authentication */
        if (ctx->auth_data && ctx->auth_data_len) {
          auth_data = ctx->auth_data;
@@ -400,9 +533,18 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth)
        auth_data_len = strlen(auth_data);
        break;
 
-      case SILC_PROTOCOL_CONN_AUTH_PUBLIC_KEY:
-       /* XXX */
-       break;
+      case SILC_AUTH_PUBLIC_KEY:
+       {
+         unsigned char sign[1024];
+
+         /* Public key authentication */
+         silc_client_get_public_key_auth(client, ctx->auth_data,
+                                         sign, &auth_data_len, 
+                                         ctx->ske);
+         auth_data = silc_calloc(auth_data_len, sizeof(*auth_data));
+         memcpy(auth_data, sign, auth_data_len);
+         break;
+       }
       }
 
       payload_len = 4 + auth_data_len;
@@ -448,13 +590,16 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth)
   case SILC_PROTOCOL_STATE_ERROR:
     {
       /* 
-       * Error
+       * Error. Send notify to remote.
        */
+      unsigned char error[4];
+
+      SILC_PUT32_MSB(SILC_AUTH_FAILED, error);
 
       /* Error in protocol. Send FAILURE packet. Although I don't think
         this could ever happen on client side. */
       silc_client_packet_send(client, ctx->sock, SILC_PACKET_FAILURE,
-                             NULL, 0, NULL, NULL, NULL, 0, TRUE);
+                             NULL, 0, NULL, NULL, error, 4, TRUE);
 
       /* On error the final callback is always called. */
       if (protocol->final_callback)
@@ -462,10 +607,397 @@ SILC_TASK_CALLBACK(silc_client_protocol_connection_auth)
       else
        silc_protocol_free(protocol);
     }
+
+  case SILC_PROTOCOL_STATE_FAILURE:
+    /*
+     * Received failure from remote.
+     */
+
+    /* On error the final callback is always called. */
+    if (protocol->final_callback)
+      protocol->execute_final(client->timeout_queue, 0, protocol, fd);
+    else
+      silc_protocol_free(protocol);
+    break;
+
+  case SILC_PROTOCOL_STATE_UNKNOWN:
+    break;
+  }
+}
+
+/*
+ * Re-key protocol routines
+ */
+
+/* Actually takes the new keys into use. */
+
+static void 
+silc_client_protocol_rekey_validate(SilcClient client,
+                                   SilcClientRekeyInternalContext *ctx,
+                                   SilcSocketConnection sock,
+                                   SilcSKEKeyMaterial *keymat)
+{
+  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
+
+  if (ctx->responder == TRUE) {
+    silc_cipher_set_key(conn->send_key, keymat->receive_enc_key, 
+                       keymat->enc_key_len);
+    silc_cipher_set_iv(conn->send_key, keymat->receive_iv);
+    silc_cipher_set_key(conn->receive_key, keymat->send_enc_key, 
+                       keymat->enc_key_len);
+    silc_cipher_set_iv(conn->receive_key, keymat->send_iv);
+  } else {
+    silc_cipher_set_key(conn->send_key, keymat->send_enc_key, 
+                       keymat->enc_key_len);
+    silc_cipher_set_iv(conn->send_key, keymat->send_iv);
+    silc_cipher_set_key(conn->receive_key, keymat->receive_enc_key, 
+                       keymat->enc_key_len);
+    silc_cipher_set_iv(conn->receive_key, keymat->receive_iv);
+  }
+
+  silc_hmac_set_key(conn->hmac, keymat->hmac_key, keymat->hmac_key_len);
+
+  /* Save the current sending encryption key */
+  memset(conn->rekey->send_enc_key, 0, conn->rekey->enc_key_len);
+  silc_free(conn->rekey->send_enc_key);
+  conn->rekey->send_enc_key = 
+    silc_calloc(keymat->enc_key_len / 8,
+               sizeof(*conn->rekey->send_enc_key));
+  memcpy(conn->rekey->send_enc_key, keymat->send_enc_key, 
+        keymat->enc_key_len / 8);
+  conn->rekey->enc_key_len = keymat->enc_key_len / 8;
+}
+
+/* This function actually re-generates (when not using PFS) the keys and
+   takes them into use. */
+
+void silc_client_protocol_rekey_generate(SilcClient client,
+                                        SilcClientRekeyInternalContext *ctx)
+{
+  SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data;
+  SilcSKEKeyMaterial *keymat;
+  uint32 key_len = silc_cipher_get_key_len(conn->send_key);
+  uint32 hash_len = conn->hash->hash->hash_len;
+
+  SILC_LOG_DEBUG(("Generating new session keys (no PFS)"));
+
+  /* Generate the new key */
+  keymat = silc_calloc(1, sizeof(*keymat));
+  silc_ske_process_key_material_data(conn->rekey->send_enc_key,
+                                    conn->rekey->enc_key_len,
+                                    16, key_len, hash_len, 
+                                    conn->hash, keymat);
+
+  /* Set the keys into use */
+  silc_client_protocol_rekey_validate(client, ctx, ctx->sock, keymat);
+
+  silc_ske_free_key_material(keymat);
+}
+
+/* This function actually re-generates (with PFS) the keys and
+   takes them into use. */
+
+void 
+silc_client_protocol_rekey_generate_pfs(SilcClient client,
+                                       SilcClientRekeyInternalContext *ctx)
+{
+  SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data;
+  SilcSKEKeyMaterial *keymat;
+  uint32 key_len = silc_cipher_get_key_len(conn->send_key);
+  uint32 hash_len = conn->hash->hash->hash_len;
+  unsigned char *tmpbuf;
+  uint32 klen;
+
+  SILC_LOG_DEBUG(("Generating new session keys (with PFS)"));
+
+  /* Encode KEY to binary data */
+  tmpbuf = silc_mp_mp2bin(ctx->ske->KEY, 0, &klen);
+
+  /* Generate the new key */
+  keymat = silc_calloc(1, sizeof(*keymat));
+  silc_ske_process_key_material_data(tmpbuf, klen, 16, key_len, hash_len, 
+                                    conn->hash, keymat);
+
+  /* Set the keys into use */
+  silc_client_protocol_rekey_validate(client, ctx, ctx->sock, keymat);
+
+  memset(tmpbuf, 0, klen);
+  silc_free(tmpbuf);
+  silc_ske_free_key_material(keymat);
+}
+
+/* Packet sending callback. This function is provided as packet sending
+   routine to the Key Exchange functions. */
+
+static void 
+silc_client_protocol_rekey_send_packet(SilcSKE ske,
+                                      SilcBuffer packet,
+                                      SilcPacketType type,
+                                      void *context)
+{
+  SilcProtocol protocol = (SilcProtocol)context;
+  SilcClientRekeyInternalContext *ctx = 
+    (SilcClientRekeyInternalContext *)protocol->context;
+  SilcClient client = (SilcClient)ctx->client;
+
+  /* Send the packet immediately */
+  silc_client_packet_send(client, ctx->sock, type, NULL, 0, NULL, NULL,
+                         packet->data, packet->len, TRUE);
+}
+
+/* Performs re-key as defined in the SILC protocol specification. */
+
+SILC_TASK_CALLBACK(silc_client_protocol_rekey)
+{
+  SilcProtocol protocol = (SilcProtocol)context;
+  SilcClientRekeyInternalContext *ctx = 
+    (SilcClientRekeyInternalContext *)protocol->context;
+  SilcClient client = (SilcClient)ctx->client;
+  SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data;
+  SilcSKEStatus status;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN)
+    protocol->state = SILC_PROTOCOL_STATE_START;
+
+  SILC_LOG_DEBUG(("State=%d", protocol->state));
+
+  switch(protocol->state) {
+  case SILC_PROTOCOL_STATE_START:
+    {
+      /* 
+       * Start protocol.
+       */
+
+      if (ctx->responder == TRUE) {
+       /*
+        * We are receiving party
+        */
+
+       if (ctx->pfs == TRUE) {
+         /* 
+          * Use Perfect Forward Secrecy, ie. negotiate the key material
+          * using the SKE protocol.
+          */
+
+         if (ctx->packet->type != SILC_PACKET_KEY_EXCHANGE_1) {
+           /* Error in protocol */
+           protocol->state = SILC_PROTOCOL_STATE_ERROR;
+           protocol->execute(client->timeout_queue, 0, protocol, fd, 
+                             0, 300000);
+         }
+
+         ctx->ske = silc_ske_alloc();
+         ctx->ske->rng = client->rng;
+         ctx->ske->prop = silc_calloc(1, sizeof(*ctx->ske->prop));
+         silc_ske_get_group_by_number(conn->rekey->ske_group,
+                                      &ctx->ske->prop->group);
+
+         status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer,
+                                             NULL, NULL, NULL, NULL);
+         if (status != SILC_SKE_STATUS_OK) {
+           SILC_LOG_WARNING(("Error (type %d) during Re-key (PFS)",
+                             status));
+           
+           protocol->state = SILC_PROTOCOL_STATE_ERROR;
+           protocol->execute(client->timeout_queue, 0, 
+                             protocol, fd, 0, 300000);
+           return;
+         }
+
+         /* Advance the protocol state */
+         protocol->state++;
+         protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0);
+       } else {
+         /*
+          * Do normal and simple re-key.
+          */
+
+         /* Send the REKEY_DONE to indicate we will take new keys into use */
+         silc_client_packet_send(client, ctx->sock, 
+                                 SILC_PACKET_REKEY_DONE, 
+                                 NULL, 0, NULL, NULL, NULL, 0, TRUE);
+
+         /* The protocol ends in next stage. */
+         protocol->state = SILC_PROTOCOL_STATE_END;
+       }
+      
+      } else {
+       /*
+        * We are the initiator of this protocol
+        */
+
+       /* Start the re-key by sending the REKEY packet */
+       silc_client_packet_send(client, ctx->sock, SILC_PACKET_REKEY, 
+                               NULL, 0, NULL, NULL, NULL, 0, TRUE);
+
+       if (ctx->pfs == TRUE) {
+         /* 
+          * Use Perfect Forward Secrecy, ie. negotiate the key material
+          * using the SKE protocol.
+          */
+         ctx->ske = silc_ske_alloc();
+         ctx->ske->rng = client->rng;
+         ctx->ske->prop = silc_calloc(1, sizeof(*ctx->ske->prop));
+         silc_ske_get_group_by_number(conn->rekey->ske_group,
+                                      &ctx->ske->prop->group);
+
+         status = 
+           silc_ske_initiator_phase_2(ctx->ske, NULL, NULL,
+                                      silc_client_protocol_rekey_send_packet,
+                                      context);
+
+         if (status != SILC_SKE_STATUS_OK) {
+           SILC_LOG_WARNING(("Error (type %d) during Re-key (PFS)",
+                             status));
+           
+           protocol->state = SILC_PROTOCOL_STATE_ERROR;
+           protocol->execute(client->timeout_queue, 0, 
+                             protocol, fd, 0, 300000);
+           return;
+         }
+
+         /* Advance the protocol state */
+         protocol->state++;
+       } else {
+         /*
+          * Do normal and simple re-key.
+          */
+
+         /* The protocol ends in next stage. We have sent the REKEY packet
+            and now we just wait that the responder send REKEY_DONE and
+            the we'll generate the new key, simple. */
+         protocol->state = SILC_PROTOCOL_STATE_END;
+       }
+      }
+    }
+    break;
+
+  case 2:
+    /*
+     * Second state, used only when oding re-key with PFS.
+     */
+    if (ctx->responder == TRUE) {
+      if (ctx->pfs == TRUE) {
+       /*
+        * Send our KE packe to the initiator now that we've processed
+        * the initiator's KE packet.
+        */
+       status = 
+         silc_ske_responder_finish(ctx->ske, NULL, NULL, 
+                                   SILC_SKE_PK_TYPE_SILC,
+                                   silc_client_protocol_rekey_send_packet,
+                                   context);
+
+         if (status != SILC_SKE_STATUS_OK) {
+           SILC_LOG_WARNING(("Error (type %d) during Re-key (PFS)",
+                             status));
+           
+           protocol->state = SILC_PROTOCOL_STATE_ERROR;
+           protocol->execute(client->timeout_queue, 0, 
+                             protocol, fd, 0, 300000);
+           return;
+         }
+      }
+
+    } else {
+      if (ctx->pfs == TRUE) {
+       /*
+        * The packet type must be KE packet
+        */
+       if (ctx->packet->type != SILC_PACKET_KEY_EXCHANGE_2) {
+         /* Error in protocol */
+         protocol->state = SILC_PROTOCOL_STATE_ERROR;
+         protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 300000);
+       }
+       
+       status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer,
+                                          NULL, NULL, NULL, NULL);
+       if (status != SILC_SKE_STATUS_OK) {
+         SILC_LOG_WARNING(("Error (type %d) during Re-key (PFS)",
+                           status));
+         
+         protocol->state = SILC_PROTOCOL_STATE_ERROR;
+         protocol->execute(client->timeout_queue, 0, 
+                           protocol, fd, 0, 300000);
+         return;
+       }
+      }
+    }
+
+    /* Send the REKEY_DONE to indicate we will take new keys into use 
+       now. */ 
+    silc_client_packet_send(client, ctx->sock, SILC_PACKET_REKEY_DONE, 
+                           NULL, 0, NULL, NULL, NULL, 0, TRUE);
+    
+    /* The protocol ends in next stage. */
+    protocol->state = SILC_PROTOCOL_STATE_END;
     break;
+
+  case SILC_PROTOCOL_STATE_END:
+    /* 
+     * End protocol
+     */
+
+    if (ctx->packet->type != SILC_PACKET_REKEY_DONE) {
+      /* Error in protocol */
+      protocol->state = SILC_PROTOCOL_STATE_ERROR;
+      protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0);
+    }
+
+    if (ctx->responder == FALSE) {
+      if (ctx->pfs == FALSE) {
+       /* Send the REKEY_DONE to indicate we will take new keys into use 
+          now. */ 
+       silc_client_packet_send(client, ctx->sock, 
+                               SILC_PACKET_REKEY_DONE, 
+                               NULL, 0, NULL, NULL, NULL, 0, TRUE);
+      }
+    }
+
+    /* Protocol has ended, call the final callback */
+    if (protocol->final_callback)
+      protocol->execute_final(client->timeout_queue, 0, protocol, fd);
+    else
+      silc_protocol_free(protocol);
+    break;
+
+  case SILC_PROTOCOL_STATE_ERROR:
+    /*
+     * Error occured
+     */
+
+    if (ctx->pfs == TRUE) {
+      /* Send abort notification */
+      silc_ske_abort(ctx->ske, ctx->ske->status, 
+                    silc_client_protocol_ke_send_packet,
+                    context);
+    }
+
+    /* On error the final callback is always called. */
+    if (protocol->final_callback)
+      protocol->execute_final(client->timeout_queue, 0, protocol, fd);
+    else
+      silc_protocol_free(protocol);
+    break;
+
+  case SILC_PROTOCOL_STATE_FAILURE:
+    /*
+     * We have received failure from remote
+     */
+
+    /* On error the final callback is always called. */
+    if (protocol->final_callback)
+      protocol->execute_final(client->timeout_queue, 0, protocol, fd);
+    else
+      silc_protocol_free(protocol);
+    break;
+
   case SILC_PROTOCOL_STATE_UNKNOWN:
     break;
   }
+
 }
 
 /* Registers protocols used in client */
@@ -476,6 +1008,8 @@ void silc_client_protocols_register(void)
                         silc_client_protocol_connection_auth);
   silc_protocol_register(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE,
                         silc_client_protocol_key_exchange);
+  silc_protocol_register(SILC_PROTOCOL_CLIENT_REKEY,
+                        silc_client_protocol_rekey);
 }
 
 /* Unregisters protocols */
@@ -486,4 +1020,6 @@ void silc_client_protocols_unregister(void)
                           silc_client_protocol_connection_auth);
   silc_protocol_unregister(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE,
                           silc_client_protocol_key_exchange);
+  silc_protocol_unregister(SILC_PROTOCOL_CLIENT_REKEY,
+                          silc_client_protocol_rekey);
 }