* 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;
};
/**
}
+/**
+ * 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.
*
* @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");
- if (NULL != t->kx_ctx)
+ if (GNUNET_NO == force_newest_key
+ && NULL != t->kx_ctx
+ && GNUNET_SCHEDULER_NO_TASK == t->kx_ctx->finish_task)
{
struct GNUNET_TIME_Relative age;
/**
- * 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;
{
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.
*
}
-/**
- * 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.
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);
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);
{
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);
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));
{
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);
}
-
/**
* Handle a channel destruction message.
*
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;
+}
/**
}
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);
}
{
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)
{
{
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);
}