Sun Mar 11 15:22:42 CET 2007 Jochen Eisinger <coffee@silcnet.org>
[silc.git] / lib / silcske / silcske.c
index 4a871aa881671059854b029431bd61be04353e4c..cf97428eeed0d291334d2db4c2c778d619035939 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2000 - 2006 Pekka Riikonen
+  Copyright (C) 2000 - 2007 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
@@ -55,6 +55,7 @@ SILC_FSM_STATE(silc_ske_st_responder_error);
 SILC_FSM_STATE(silc_ske_st_rekey_initiator_start);
 SILC_FSM_STATE(silc_ske_st_rekey_initiator_done);
 SILC_FSM_STATE(silc_ske_st_rekey_initiator_end);
+SILC_TASK_CALLBACK(silc_ske_packet_send_retry);
 
 SilcSKEKeyMaterial
 silc_ske_process_key_material(SilcSKE ske,
@@ -81,7 +82,8 @@ static SilcBool silc_ske_packet_receive(SilcPacketEngine engine,
   /* Clear retransmission */
   ske->retry_timer = SILC_SKE_RETRY_MIN;
   ske->retry_count = 0;
-  silc_schedule_task_del_by_context(ske->schedule, ske);
+  silc_schedule_task_del_by_callback(ske->schedule,
+                                    silc_ske_packet_send_retry);
 
   /* Signal for new packet */
   ske->packet = packet;
@@ -108,8 +110,10 @@ static SilcBool silc_ske_packet_receive(SilcPacketEngine engine,
       silc_fsm_next(&ske->fsm, silc_ske_st_initiator_failure);
   }
 
-  /* Handle rekey synchronously */
-  if (ske->rekeying)
+  /* Handle rekey and SUCCESS packets synchronously.  After SUCCESS packets
+     they keys are taken into use immediately, hence the synchronous
+     processing to get the keys in use as soon as possible. */
+  if (ske->rekeying || packet->type == SILC_PACKET_SUCCESS)
     silc_fsm_continue_sync(&ske->fsm);
   else
     silc_fsm_continue(&ske->fsm);
@@ -174,22 +178,13 @@ static void silc_ske_skr_callback(SilcSKR repository,
 
 static SilcSKEStatus silc_ske_check_version(SilcSKE ske)
 {
-  SilcUInt32 l_protocol_version = 0, r_protocol_version = 0;
   SilcUInt32 r_software_version = 0;
 
   if (!ske->remote_version || !ske->version)
     return SILC_SKE_STATUS_BAD_VERSION;
 
-  if (!silc_parse_version_string(ske->remote_version, &r_protocol_version,
-                                NULL, &r_software_version, NULL, NULL))
-    return SILC_SKE_STATUS_BAD_VERSION;
-
-  if (!silc_parse_version_string(ske->version, &l_protocol_version,
-                                NULL, NULL, NULL, NULL))
-    return SILC_SKE_STATUS_BAD_VERSION;
-
-  /* If remote is too new, don't connect */
-  if (l_protocol_version < r_protocol_version)
+  if (!silc_parse_version_string(ske->remote_version, NULL, NULL,
+                                &r_software_version, NULL, NULL))
     return SILC_SKE_STATUS_BAD_VERSION;
 
   /* Backwards compatibility checks */
@@ -806,8 +801,9 @@ silc_ske_make_rekey_material(SilcSKE ske, SilcSKEKeyMaterial keymat)
     return NULL;
 
   if (ske->prop) {
+    if (ske->prop->group)
+      rekey->ske_group = silc_ske_group_get_number(ske->prop->group);
     rekey->pfs = (ske->prop->flags & SILC_SKE_SP_FLAG_PFS ? TRUE : FALSE);
-    rekey->ske_group = silc_ske_group_get_number(ske->prop->group);
     hash = silc_hash_get_name(ske->prop->hash);
     rekey->hash = silc_memdup(hash, strlen(hash));
     if (!rekey->hash)
@@ -816,7 +812,7 @@ silc_ske_make_rekey_material(SilcSKE ske, SilcSKEKeyMaterial keymat)
 
   if (rekey->pfs == FALSE) {
     rekey->send_enc_key = silc_memdup(keymat->send_enc_key,
-                                     keymat->enc_key_len);
+                                     keymat->enc_key_len / 8);
     if (!rekey->send_enc_key) {
       silc_free(rekey);
       return NULL;
@@ -963,15 +959,11 @@ static SilcBool silc_ske_packet_send(SilcSKE ske,
   return ret;
 }
 
-/* SKE FSM destructor.  We call completion callback here.  All SKE
-   machines go here and call the completion.  Completion must not be called
-   from any other place. */
+/* Calls completion callback.  Completion is called always in this function
+   and must not be called anywhere else. */
 
-static void silc_ske_finished(SilcFSM fsm, void *fsm_context,
-                             void *destructor_context)
+static void silc_ske_completion(SilcSKE ske)
 {
-  SilcSKE ske = fsm_context;
-
   /* Call the completion callback */
   if (!ske->freed && !ske->aborted && ske->callbacks->completed) {
     if (ske->status != SILC_SKE_STATUS_OK)
@@ -981,12 +973,37 @@ static void silc_ske_finished(SilcFSM fsm, void *fsm_context,
       ske->callbacks->completed(ske, ske->status, ske->prop, ske->keymat,
                                ske->rekey, ske->callbacks->context);
   }
+}
+
+/* SKE FSM destructor. */
 
+static void silc_ske_finished(SilcFSM fsm, void *fsm_context,
+                             void *destructor_context)
+{
+  SilcSKE ske = fsm_context;
   ske->running = FALSE;
   if (ske->freed)
     silc_ske_free(ske);
 }
 
+/* Key exchange timeout task callback */
+
+SILC_TASK_CALLBACK(silc_ske_timeout)
+{
+  SilcSKE ske = context;
+
+  SILC_LOG_DEBUG(("Timeout"));
+
+  ske->packet = NULL;
+  ske->status = SILC_SKE_STATUS_TIMEOUT;
+  if (ske->responder)
+    silc_fsm_next(&ske->fsm, silc_ske_st_responder_failure);
+  else
+    silc_fsm_next(&ske->fsm, silc_ske_st_initiator_failure);
+
+  silc_fsm_continue_sync(&ske->fsm);
+}
+
 /******************************* Protocol API *******************************/
 
 /* Allocates new SKE object. */
@@ -1018,6 +1035,7 @@ SilcSKE silc_ske_alloc(SilcRng rng, SilcSchedule schedule,
   ske->public_key = public_key;
   ske->private_key = private_key;
   ske->retry_timer = SILC_SKE_RETRY_MIN;
+  ske->refcnt = 1;
 
   return ske;
 }
@@ -1033,9 +1051,24 @@ void silc_ske_free(SilcSKE ske)
 
   if (ske->running) {
     ske->freed = TRUE;
+
+    if (ske->aborted) {
+      /* If already aborted, destroy the session immediately */
+      ske->packet = NULL;
+      ske->status = SILC_SKE_STATUS_ERROR;
+      if (ske->responder)
+       silc_fsm_next(&ske->fsm, silc_ske_st_responder_failure);
+      else
+       silc_fsm_next(&ske->fsm, silc_ske_st_initiator_failure);
+      silc_fsm_continue_sync(&ske->fsm);
+    }
     return;
   }
 
+  ske->refcnt--;
+  if (ske->refcnt > 0)
+    return;
+
   /* Free start payload */
   if (ske->start_payload)
     silc_ske_payload_start_free(ske->start_payload);
@@ -1057,6 +1090,8 @@ void silc_ske_free(SilcSKE ske)
       silc_hash_free(ske->prop->hash);
     if (ske->prop->hmac)
       silc_hmac_free(ske->prop->hmac);
+    if (ske->prop->public_key)
+      silc_pkcs_public_key_free(ske->prop->public_key);
     silc_free(ske->prop);
   }
   if (ske->keymat)
@@ -1147,10 +1182,12 @@ SILC_FSM_STATE(silc_ske_st_initiator_start)
     return SILC_FSM_CONTINUE;
   }
 
-  /* XXX timeout */
+  /* Add key exchange timeout */
+  silc_schedule_task_add_timeout(ske->schedule, silc_ske_timeout,
+                                ske, ske->timeout, 0);
 
   /** Wait for responder proposal */
-  SILC_LOG_DEBUG(("Waiting for reponder proposal"));
+  SILC_LOG_DEBUG(("Waiting for responder proposal"));
   silc_fsm_next(fsm, silc_ske_st_initiator_phase1);
   return SILC_FSM_WAIT;
 }
@@ -1385,7 +1422,7 @@ SILC_FSM_STATE(silc_ske_st_initiator_phase2)
 
     /* Sign the hash value */
     if (!silc_pkcs_sign(ske->private_key, hash, hash_len, sign,
-                       sizeof(sign) - 1, &sign_len, NULL)) {
+                       sizeof(sign) - 1, &sign_len, FALSE, ske->prop->hash)) {
       /** Error computing signature */
       silc_mp_uninit(x);
       silc_free(x);
@@ -1688,6 +1725,9 @@ SILC_FSM_STATE(silc_ske_st_initiator_end)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
@@ -1707,6 +1747,9 @@ SILC_FSM_STATE(silc_ske_st_initiator_aborted)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
@@ -1732,6 +1775,9 @@ SILC_FSM_STATE(silc_ske_st_initiator_error)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
@@ -1755,16 +1801,18 @@ SILC_FSM_STATE(silc_ske_st_initiator_failure)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
 /* Starts the protocol as initiator */
 
-SilcAsyncOperation
-silc_ske_initiator(SilcSKE ske,
-                  SilcPacketStream stream,
-                  SilcSKEParams params,
-                  SilcSKEStartPayload start_payload)
+SilcAsyncOperation silc_ske_initiator(SilcSKE ske,
+                                     SilcPacketStream stream,
+                                     SilcSKEParams params,
+                                     SilcSKEStartPayload start_payload)
 {
   SILC_LOG_DEBUG(("Start SKE as initiator"));
 
@@ -1789,6 +1837,7 @@ silc_ske_initiator(SilcSKE ske,
       return NULL;
   }
 
+  ske->timeout = params->timeout_secs ? params->timeout_secs : 30;
   ske->start_payload = start_payload;
   ske->version = params->version;
   ske->running = TRUE;
@@ -1823,8 +1872,9 @@ SILC_FSM_STATE(silc_ske_st_responder_start)
     return SILC_FSM_CONTINUE;
   }
 
-  /* Start timeout */
-  /* XXX */
+  /* Add key exchange timeout */
+  silc_schedule_task_add_timeout(ske->schedule, silc_ske_timeout,
+                                ske, ske->timeout, 0);
 
   /** Wait for initiator */
   silc_fsm_next(fsm, silc_ske_st_responder_phase1);
@@ -2184,7 +2234,7 @@ SILC_FSM_STATE(silc_ske_st_responder_phase5)
 
     /* Sign the hash value */
     if (!silc_pkcs_sign(ske->private_key, hash, hash_len, sign,
-                       sizeof(sign) - 1, &sign_len, NULL)) {
+                       sizeof(sign) - 1, &sign_len, FALSE, ske->prop->hash)) {
       /** Error computing signature */
       status = SILC_SKE_STATUS_SIGNATURE_ERROR;
       silc_fsm_next(fsm, silc_ske_st_responder_error);
@@ -2261,6 +2311,9 @@ SILC_FSM_STATE(silc_ske_st_responder_end)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
@@ -2280,6 +2333,9 @@ SILC_FSM_STATE(silc_ske_st_responder_aborted)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
@@ -2302,6 +2358,9 @@ SILC_FSM_STATE(silc_ske_st_responder_failure)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
@@ -2324,15 +2383,17 @@ SILC_FSM_STATE(silc_ske_st_responder_error)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
 /* Starts the protocol as responder. */
 
-SilcAsyncOperation
-silc_ske_responder(SilcSKE ske,
-                  SilcPacketStream stream,
-                  SilcSKEParams params)
+SilcAsyncOperation silc_ske_responder(SilcSKE ske,
+                                     SilcPacketStream stream,
+                                     SilcSKEParams params)
 {
   SILC_LOG_DEBUG(("Start SKE as responder"));
 
@@ -2347,6 +2408,7 @@ silc_ske_responder(SilcSKE ske,
 
   ske->responder = TRUE;
   ske->flags = params->flags;
+  ske->timeout = params->timeout_secs ? params->timeout_secs : 30;
   if (ske->flags & SILC_SKE_SP_FLAG_IV_INCLUDED)
     ske->session_port = params->session_port;
   ske->version = strdup(params->version);
@@ -2385,7 +2447,9 @@ SILC_FSM_STATE(silc_ske_st_rekey_initiator_start)
     return SILC_FSM_CONTINUE;
   }
 
-  /* XXX timeout */
+  /* Add rekey exchange timeout */
+  silc_schedule_task_add_timeout(ske->schedule, silc_ske_timeout,
+                                ske, 30, 0);
 
   ske->prop = silc_calloc(1, sizeof(*ske->prop));
   if (!ske->prop) {
@@ -2550,8 +2614,13 @@ SILC_FSM_STATE(silc_ske_st_rekey_initiator_end)
 
   /* Generate new rekey material */
   rekey = silc_ske_make_rekey_material(ske, ske->keymat);
+  if (!rekey) {
+    /** No memory */
+    ske->status = SILC_SKE_STATUS_OUT_OF_MEMORY;
+    silc_fsm_next(fsm, silc_ske_st_initiator_error);
+    return SILC_FSM_CONTINUE;
+  }
   rekey->pfs = ske->rekey->pfs;
-  rekey->ske_group = ske->rekey->ske_group;
   ske->rekey = rekey;
 
   ske->prop->cipher = NULL;
@@ -2561,6 +2630,9 @@ SILC_FSM_STATE(silc_ske_st_rekey_initiator_end)
   silc_packet_stream_unlink(ske->stream, &silc_ske_stream_cbs, ske);
   silc_schedule_task_del_by_context(ske->schedule, ske);
 
+  /* Call completion */
+  silc_ske_completion(ske);
+
   return SILC_FSM_FINISH;
 }
 
@@ -2930,7 +3002,7 @@ void silc_ske_free_rekey_material(SilcSKERekeyMaterial rekey)
   if (!rekey)
     return;
   if (rekey->send_enc_key) {
-    memset(rekey->send_enc_key, 0, rekey->enc_key_len);
+    memset(rekey->send_enc_key, 0, rekey->enc_key_len / 8);
     silc_free(rekey->send_enc_key);
   }
   silc_free(rekey->hash);
@@ -2948,6 +3020,9 @@ SilcBool silc_ske_set_keys(SilcSKE ske,
                           SilcHmac *ret_hmac_receive,
                           SilcHash *ret_hash)
 {
+  unsigned char iv[32];
+  SilcBool iv_included = (prop->flags & SILC_SKE_SP_FLAG_IV_INCLUDED);
+
   /* Allocate ciphers to be used in the communication */
   if (ret_send_key) {
     if (!silc_cipher_alloc((char *)silc_cipher_get_name(prop->cipher),
@@ -2973,16 +3048,31 @@ SilcBool silc_ske_set_keys(SilcSKE ske,
   }
 
   /* Set key material */
+  memset(iv, 0, sizeof(iv));
   if (ske->responder) {
     if (ret_send_key) {
       silc_cipher_set_key(*ret_send_key, keymat->receive_enc_key,
-                         keymat->enc_key_len);
-      silc_cipher_set_iv(*ret_send_key, keymat->receive_iv);
+                         keymat->enc_key_len, TRUE);
+
+      if (silc_cipher_get_mode(*ret_send_key) == SILC_CIPHER_MODE_CTR) {
+        memcpy(iv, ske->hash, 4);
+        memcpy(iv + 4, keymat->receive_iv, iv_included ? 4 : 8);
+        silc_cipher_set_iv(*ret_send_key, iv);
+      } else {
+       silc_cipher_set_iv(*ret_send_key, keymat->receive_iv);
+      }
     }
     if (ret_receive_key) {
       silc_cipher_set_key(*ret_receive_key, keymat->send_enc_key,
-                         keymat->enc_key_len);
-      silc_cipher_set_iv(*ret_receive_key, keymat->send_iv);
+                         keymat->enc_key_len, FALSE);
+
+      if (silc_cipher_get_mode(*ret_receive_key) == SILC_CIPHER_MODE_CTR) {
+        memcpy(iv, ske->hash, 4);
+        memcpy(iv + 4, keymat->send_iv, iv_included ? 4 : 8);
+        silc_cipher_set_iv(*ret_receive_key, iv);
+      } else {
+       silc_cipher_set_iv(*ret_receive_key, keymat->send_iv);
+      }
     }
     if (ret_hmac_send)
       silc_hmac_set_key(*ret_hmac_send, keymat->receive_hmac_key,
@@ -2993,13 +3083,27 @@ SilcBool silc_ske_set_keys(SilcSKE ske,
   } else {
     if (ret_send_key) {
       silc_cipher_set_key(*ret_send_key, keymat->send_enc_key,
-                         keymat->enc_key_len);
-      silc_cipher_set_iv(*ret_send_key, keymat->send_iv);
+                         keymat->enc_key_len, TRUE);
+
+      if (silc_cipher_get_mode(*ret_send_key) == SILC_CIPHER_MODE_CTR) {
+        memcpy(iv, ske->hash, 4);
+        memcpy(iv + 4, keymat->send_iv, iv_included ? 4 : 8);
+       silc_cipher_set_iv(*ret_send_key, iv);
+      } else {
+       silc_cipher_set_iv(*ret_send_key, keymat->send_iv);
+      }
     }
     if (ret_receive_key) {
       silc_cipher_set_key(*ret_receive_key, keymat->receive_enc_key,
-                         keymat->enc_key_len);
-      silc_cipher_set_iv(*ret_receive_key, keymat->receive_iv);
+                         keymat->enc_key_len, FALSE);
+
+      if (silc_cipher_get_mode(*ret_receive_key) == SILC_CIPHER_MODE_CTR) {
+        memcpy(iv, ske->hash, 4);
+        memcpy(iv + 4, keymat->receive_iv, iv_included ? 4 : 8);
+       silc_cipher_set_iv(*ret_receive_key, iv);
+      } else {
+       silc_cipher_set_iv(*ret_receive_key, keymat->receive_iv);
+      }
     }
     if (ret_hmac_send)
       silc_hmac_set_key(*ret_hmac_send, keymat->send_hmac_key,
@@ -3040,6 +3144,7 @@ const char *silc_ske_status_string[] =
   "Bad payload length in packet",
   "Error computing signature",
   "System out of memory",
+  "Key exchange timeout",
 
   NULL
 };