- moved timeout handling responsibility from for nat tests from caller to the library
[oweals/gnunet.git] / src / cadet / gnunet-service-cadet_tunnel.c
index 2cbad799049d0ef2dc0ceccda3cf6da7d679cee0..6c4d949e3cf9e6f7c2f0661328cd91174daa2b55 100644 (file)
@@ -107,6 +107,12 @@ struct CadetTunnelKXCtx
    * When the rekey started. One minute after this the new key will be used.
    */
   struct GNUNET_TIME_Absolute rekey_start_time;
+
+  /**
+   * Task for delayed destruction of the Key eXchange context, to allow delayed
+   * messages with the old key to be decrypted successfully.
+   */
+  GNUNET_SCHEDULER_TaskIdentifier finish_task;
 };
 
 /**
@@ -536,6 +542,36 @@ check_ephemeral (struct CadetTunnel *t,
 }
 
 
+/**
+ * Calculate HMAC.
+ *
+ * @param t Tunnel to get keys from.
+ * @param plaintext Content to HMAC.
+ * @param size Size of @c plaintext.
+ * @param iv Initialization vector for the message.
+ * @param outgoing Is this an outgoing message that we encrypted?
+ * @param hmac Destination to store the HMAC.
+ */
+static void
+t_hmac (struct CadetTunnel *t, const void *plaintext, size_t size, uint32_t iv,
+        int outgoing, struct GNUNET_CADET_Hash *hmac)
+{
+  struct GNUNET_CRYPTO_AuthKey auth_key;
+  static const char ctx[] = "cadet authentication key";
+  struct GNUNET_CRYPTO_SymmetricSessionKey *key;
+  struct GNUNET_HashCode hash;
+
+  key = outgoing ? &t->e_key : &t->d_key;
+  GNUNET_CRYPTO_hmac_derive_key (&auth_key, key,
+                                 &iv, sizeof (iv),
+                                 key, sizeof (*key),
+                                 ctx, sizeof (ctx),
+                                 NULL);
+  GNUNET_CRYPTO_hmac (&auth_key, plaintext, size, &hash);
+  memcpy (hmac, &hash, sizeof (*hmac));
+}
+
+
 /**
  * Encrypt data with the tunnel key.
  *
@@ -544,19 +580,48 @@ check_ephemeral (struct CadetTunnel *t,
  * @param src Source of the plaintext. Can overlap with @c dst.
  * @param size Size of the plaintext.
  * @param iv Initialization Vector to use.
+ * @param force_newest_key Force the use of the newest key, otherwise
+ *                         CADET will use the old key when allowed.
+ *                         This can happen in the case when a KX is going on
+ *                         and the old one hasn't expired.
  */
 static int
-t_encrypt (struct CadetTunnel *t,
-           void *dst, const void *src,
-           size_t size, uint32_t iv)
+t_encrypt (struct CadetTunnel *t, void *dst, const void *src,
+           size_t size, uint32_t iv, int force_newest_key)
 {
   struct GNUNET_CRYPTO_SymmetricInitializationVector siv;
+  struct GNUNET_CRYPTO_SymmetricSessionKey *e_key;
   size_t out_size;
 
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  t_encrypt start\n");
-  GNUNET_CRYPTO_symmetric_derive_iv (&siv, &t->e_key, &iv, sizeof (iv), NULL);
+  if (GNUNET_NO == force_newest_key
+      && NULL != t->kx_ctx
+      && GNUNET_SCHEDULER_NO_TASK == t->kx_ctx->finish_task)
+  {
+    struct GNUNET_TIME_Relative age;
+
+    age = GNUNET_TIME_absolute_get_duration (t->kx_ctx->rekey_start_time);
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+         "  key exchange in progress, started %s ago\n",
+         GNUNET_STRINGS_relative_time_to_string (age, GNUNET_YES));
+    if (age.rel_value_us < GNUNET_TIME_UNIT_MINUTES.rel_value_us)
+    {
+      LOG (GNUNET_ERROR_TYPE_DEBUG, "  using old key\n");
+      e_key = &t->kx_ctx->e_key_old;
+    }
+    else
+    {
+      LOG (GNUNET_ERROR_TYPE_DEBUG, "  using new key\n");
+      e_key = &t->e_key;
+    }
+  }
+  else
+  {
+    e_key = &t->e_key;
+  }
+  GNUNET_CRYPTO_symmetric_derive_iv (&siv, e_key, &iv, sizeof (iv), NULL);
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  t_encrypt IV derived\n");
-  out_size = GNUNET_CRYPTO_symmetric_encrypt (src, size, &t->e_key, &siv, dst);
+  out_size = GNUNET_CRYPTO_symmetric_encrypt (src, size, e_key, &siv, dst);
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  t_encrypt end\n");
 
   return out_size;
@@ -564,20 +629,49 @@ t_encrypt (struct CadetTunnel *t,
 
 
 /**
- * Decrypt data with the tunnel key.
+ * Decrypt and verify data with the appropriate tunnel key.
+ *
+ * @param key Key to use.
+ * @param dst Destination for the plaintext.
+ * @param src Source of the encrypted data. Can overlap with @c dst.
+ * @param size Size of the encrypted data.
+ * @param iv Initialization Vector to use.
+ *
+ * @return Size of the decrypted data, -1 if an error was encountered.
+ */
+static int
+decrypt (const struct GNUNET_CRYPTO_SymmetricSessionKey *key,
+         void *dst, const void *src, size_t size, uint32_t iv)
+{
+  struct GNUNET_CRYPTO_SymmetricInitializationVector siv;
+  size_t out_size;
+
+  LOG (GNUNET_ERROR_TYPE_DEBUG, "  decrypt start\n");
+  LOG (GNUNET_ERROR_TYPE_DEBUG, "  decrypt iv\n");
+  GNUNET_CRYPTO_symmetric_derive_iv (&siv, key, &iv, sizeof (iv), NULL);
+  LOG (GNUNET_ERROR_TYPE_DEBUG, "  decrypt iv done\n");
+  out_size = GNUNET_CRYPTO_symmetric_decrypt (src, size, key, &siv, dst);
+  LOG (GNUNET_ERROR_TYPE_DEBUG, "  decrypt end\n");
+
+  return out_size;
+}
+
+
+/**
+ * Decrypt and verify data with the most recent tunnel key.
  *
  * @param t Tunnel whose key to use.
  * @param dst Destination for the plaintext.
  * @param src Source of the encrypted data. Can overlap with @c dst.
  * @param size Size of the encrypted data.
  * @param iv Initialization Vector to use.
+ *
+ * @return Size of the decrypted data, -1 if an error was encountered.
  */
 static int
-t_decrypt (struct CadetTunnel *t,
-           void *dst, const void *src,
+t_decrypt (struct CadetTunnel *t, void *dst, const void *src,
            size_t size, uint32_t iv)
 {
-  struct GNUNET_CRYPTO_SymmetricInitializationVector siv;
   struct GNUNET_CRYPTO_SymmetricSessionKey *key;
   size_t out_size;
 
@@ -586,30 +680,77 @@ t_decrypt (struct CadetTunnel *t,
   {
     key = &t->d_key;
   }
-  else if (NULL != t->kx_ctx)
-  {
-    key = &t->kx_ctx->d_key_old;
-  }
   else
   {
     GNUNET_STATISTICS_update (stats, "# non decryptable data", 1, GNUNET_NO);
-    LOG (GNUNET_ERROR_TYPE_DEBUG,
-         "WARNING got data on %s without a valid key\n",
+    LOG (GNUNET_ERROR_TYPE_WARNING,
+         "got data on %s without a valid key\n",
          GCT_2s (t));
     GCT_debug (t);
-    return 0;
+    return -1;
   }
 
-  LOG (GNUNET_ERROR_TYPE_DEBUG, "  t_decrypt iv\n");
-  GNUNET_CRYPTO_symmetric_derive_iv (&siv, key, &iv, sizeof (iv), NULL);
-  LOG (GNUNET_ERROR_TYPE_DEBUG, "  t_decrypt iv done\n");
-  out_size = GNUNET_CRYPTO_symmetric_decrypt (src, size, key, &siv, dst);
-  LOG (GNUNET_ERROR_TYPE_DEBUG, "  t_decrypt end\n");
+  out_size = decrypt (key, dst, src, size, iv);
 
   return out_size;
 }
 
 
+/**
+ * Decrypt and verify data with the appropriate tunnel key and verify that the
+ * data has not been altered since it was sent by the remote peer.
+ *
+ * @param t Tunnel whose key to use.
+ * @param dst Destination for the plaintext.
+ * @param src Source of the encrypted data. Can overlap with @c dst.
+ * @param size Size of the encrypted data.
+ * @param iv Initialization Vector to use.
+ * @param msg_hmac HMAC of the message, cannot be NULL.
+ *
+ * @return Size of the decrypted data, -1 if an error was encountered.
+ */
+static int
+t_decrypt_and_validate (struct CadetTunnel *t,
+                        void *dst, const void *src,
+                        size_t size, uint32_t iv,
+                        const struct GNUNET_CADET_Hash *msg_hmac)
+{
+  struct GNUNET_CRYPTO_SymmetricSessionKey *key;
+  struct GNUNET_CADET_Hash hmac;
+  int decrypted_size;
+
+  /* Try primary (newest) key */
+  key = &t->d_key;
+  decrypted_size = decrypt (key, dst, src, size, iv);
+  t_hmac (t, src, size, iv, GNUNET_NO, &hmac);
+  if (0 == memcmp (msg_hmac, &hmac, sizeof (hmac)))
+    return decrypted_size;
+
+  /* If no key exchange is going on, we just failed */
+  if (NULL == t->kx_ctx)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed checksum validation on tunnel %s with no KX\n",
+                GCT_2s (t));
+    GNUNET_STATISTICS_update (stats, "# wrong HMAC", 1, GNUNET_NO);
+    return -1;
+  }
+
+  /* Try secondary (from previous KX period) key */
+  key = &t->kx_ctx->d_key_old;
+  decrypted_size = decrypt (key, dst, src, size, iv);
+  t_hmac (t, src, size, iv, GNUNET_NO, &hmac);
+  if (0 == memcmp (msg_hmac, &hmac, sizeof (hmac)))
+    return decrypted_size;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+              "Failed checksum validation on tunnel %s with KX\n",
+              GCT_2s (t));
+  GNUNET_STATISTICS_update (stats, "# wrong HMAC", 1, GNUNET_NO);
+  return -1;
+}
+
+
 /**
  * Create key material by doing ECDH on the local and remote ephemeral keys.
  *
@@ -783,36 +924,6 @@ queue_data (struct CadetTunnel *t, const struct GNUNET_MessageHeader *msg)
 }
 
 
-/**
- * Calculate HMAC.
- *
- * @param t Tunnel to get keys from.
- * @param plaintext Content to HMAC.
- * @param size Size of @c plaintext.
- * @param iv Initialization vector for the message.
- * @param outgoing Is this an outgoing message that we encrypted?
- * @param hmac Destination to store the HMAC.
- */
-static void
-t_hmac (struct CadetTunnel *t, const void *plaintext, size_t size, uint32_t iv,
-        int outgoing, struct GNUNET_CADET_Hash *hmac)
-{
-  struct GNUNET_CRYPTO_AuthKey auth_key;
-  static const char ctx[] = "cadet authentication key";
-  struct GNUNET_CRYPTO_SymmetricSessionKey *key;
-  struct GNUNET_HashCode hash;
-
-  key = outgoing ? &t->e_key : &t->d_key;
-  GNUNET_CRYPTO_hmac_derive_key (&auth_key, key,
-                                 &iv, sizeof (iv),
-                                 key, sizeof (*key),
-                                 ctx, sizeof (ctx),
-                                 NULL);
-  GNUNET_CRYPTO_hmac (&auth_key, plaintext, size, &hash);
-  memcpy (hmac, &hash, sizeof (*hmac));
-}
-
-
 /**
  * Sends an already built message on a tunnel, encrypting it and
  * choosing the best connection.
@@ -870,7 +981,7 @@ send_prebuilt_message (const struct GNUNET_MessageHeader *message,
   msg = (struct GNUNET_CADET_Encrypted *) cbuf;
   msg->header.type = htons (GNUNET_MESSAGE_TYPE_CADET_ENCRYPTED);
   msg->iv = iv;
-  GNUNET_assert (t_encrypt (t, &msg[1], message, size, iv) == size);
+  GNUNET_assert (t_encrypt (t, &msg[1], message, size, iv, GNUNET_NO) == size);
   t_hmac (t, &msg[1], size, iv, GNUNET_YES, &msg->hmac);
   msg->header.size = htons (sizeof (struct GNUNET_CADET_Encrypted) + size);
 
@@ -1067,7 +1178,7 @@ send_kx (struct CadetTunnel *t,
 static void
 send_ephemeral (struct CadetTunnel *t)
 {
-  LOG (GNUNET_ERROR_TYPE_INFO, "=> EPHM for %s\n", GCT_2s (t));
+  LOG (GNUNET_ERROR_TYPE_INFO, "===> EPHM for %s\n", GCT_2s (t));
 
   kx_msg.sender_status = htonl (t->estate);
   send_kx (t, &kx_msg.header);
@@ -1083,7 +1194,7 @@ send_ping (struct CadetTunnel *t)
 {
   struct GNUNET_CADET_KX_Ping msg;
 
-  LOG (GNUNET_ERROR_TYPE_INFO, "=> PING for %s\n", GCT_2s (t));
+  LOG (GNUNET_ERROR_TYPE_INFO, "===> PING for %s\n", GCT_2s (t));
   msg.header.size = htons (sizeof (msg));
   msg.header.type = htons (GNUNET_MESSAGE_TYPE_CADET_KX_PING);
   msg.iv = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, UINT32_MAX);
@@ -1092,7 +1203,8 @@ send_ping (struct CadetTunnel *t)
 
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  sending %u\n", msg.nonce);
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  towards %s\n", GNUNET_i2s (&msg.target));
-  t_encrypt (t, &msg.target, &msg.target, ping_encryption_size(), msg.iv);
+  t_encrypt (t, &msg.target, &msg.target,
+             ping_encryption_size(), msg.iv, GNUNET_YES);
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  e sending %u\n", msg.nonce);
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  e towards %s\n", GNUNET_i2s (&msg.target));
 
@@ -1111,13 +1223,14 @@ send_pong (struct CadetTunnel *t, uint32_t challenge)
 {
   struct GNUNET_CADET_KX_Pong msg;
 
-  LOG (GNUNET_ERROR_TYPE_INFO, "=> PONG for %s\n", GCT_2s (t));
+  LOG (GNUNET_ERROR_TYPE_INFO, "===> PONG for %s\n", GCT_2s (t));
   msg.header.size = htons (sizeof (msg));
   msg.header.type = htons (GNUNET_MESSAGE_TYPE_CADET_KX_PONG);
   msg.iv = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, UINT32_MAX);
   msg.nonce = challenge;
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  sending %u\n", msg.nonce);
-  t_encrypt (t, &msg.nonce, &msg.nonce, sizeof (msg.nonce), msg.iv);
+  t_encrypt (t, &msg.nonce, &msg.nonce,
+             sizeof (msg.nonce), msg.iv, GNUNET_YES);
   LOG (GNUNET_ERROR_TYPE_DEBUG, "  e sending %u\n", msg.nonce);
 
   send_kx (t, &msg.header);
@@ -1500,7 +1613,6 @@ handle_ch_ack (struct CadetTunnel *t,
 }
 
 
-
 /**
  * Handle a channel destruction message.
  *
@@ -1603,6 +1715,23 @@ handle_ping (struct CadetTunnel *t,
 
   send_pong (t, res.nonce);
 }
+/**
+ * @brief Finish the Key eXchange and destory the old keys.
+ *
+ * @param cls Closure (Tunnel for which to finish the KX).
+ * @param tc Task context.
+ */
+static void
+finish_kx (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct CadetTunnel *t = cls;
+
+  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
+    return;
+
+  GNUNET_free (t->kx_ctx);
+  t->kx_ctx = NULL;
+}
 
 
 /**
@@ -1638,8 +1767,17 @@ handle_pong (struct CadetTunnel *t,
   }
   GNUNET_SCHEDULER_cancel (t->rekey_task);
   t->rekey_task = GNUNET_SCHEDULER_NO_TASK;
-  GNUNET_free (t->kx_ctx);
-  t->kx_ctx = NULL;
+
+  /* Don't free the old keys right away, but after a delay.
+   * Rationale: the KX could have happened over a very fast connection,
+   * with payload traffic still signed with the old key stuck in a slower
+   * connection.
+   */
+  if (GNUNET_SCHEDULER_NO_TASK == t->kx_ctx->finish_task)
+  {
+    t->kx_ctx->finish_task =
+      GNUNET_SCHEDULER_add_delayed(GNUNET_TIME_UNIT_MINUTES, finish_kx, t);
+  }
   GCT_change_estate (t, CADET_TUNNEL3_KEY_OK);
 }
 
@@ -1729,23 +1867,14 @@ GCT_handle_encrypted (struct CadetTunnel *t,
 {
   size_t size = ntohs (msg->header.size);
   size_t payload_size = size - sizeof (struct GNUNET_CADET_Encrypted);
-  size_t decrypted_size;
+  int decrypted_size;
   char cbuf [payload_size];
   struct GNUNET_MessageHeader *msgh;
   unsigned int off;
-  struct GNUNET_CADET_Hash hmac;
 
-  decrypted_size = t_decrypt (t, cbuf, &msg[1], payload_size, msg->iv);
-  t_hmac (t, &msg[1], payload_size, msg->iv, GNUNET_NO, &hmac);
-  if (0 != memcmp (&hmac, &msg->hmac, sizeof (hmac)))
-  {
-    /* checksum failed */
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed checksum validation for a message on tunnel `%s'\n",
-                GCT_2s (t));
-    GNUNET_STATISTICS_update (stats, "# wrong HMAC", 1, GNUNET_NO);
-    return;
-  }
+  decrypted_size = t_decrypt_and_validate (t, cbuf, &msg[1], payload_size,
+                                           msg->iv, &msg->hmac);
+
   off = 0;
   while (off < decrypted_size)
   {
@@ -2305,12 +2434,13 @@ GCT_destroy (struct CadetTunnel *t)
   {
     GNUNET_SCHEDULER_cancel (t->rekey_task);
     t->rekey_task = GNUNET_SCHEDULER_NO_TASK;
-    if (NULL != t->kx_ctx)
-      GNUNET_free (t->kx_ctx);
-    else
-      GNUNET_break (0);
   }
-
+  if (NULL != t->kx_ctx)
+  {
+    if (GNUNET_SCHEDULER_NO_TASK != t->kx_ctx->finish_task)
+      GNUNET_SCHEDULER_cancel (t->kx_ctx->finish_task);
+    GNUNET_free (t->kx_ctx);
+  }
   GNUNET_free (t);
 }