--- /dev/null
+From c78aa91005b7b9542d595dc32d8c8fe020d2257d Mon Sep 17 00:00:00 2001
+From: Sachin Agrawal <sachin.agrawal@intel.com>
+Date: Wed, 21 Jan 2015 08:55:00 -0800
+Subject: [PATCH 1/1] Added support in tinyDTLS to support rehandshake
+
+As per RFC 6347 section 4.2.8, DTLS Server should support requests
+from clients who have silently abandoned the existing association
+and initiated a new handshake request by sending a ClientHello.
+Code is updated to detect this scenario and delete the old
+association when client successfully responds to HelloVerifyRequest.
+
+
+Change-Id: I6e256921215c1a22e9e5013499c4dfd98659f8cc
+Signed-off-by: Sachin Agrawal <sachin.agrawal@intel.com>
+---
+ extlibs/tinydtls/dtls.c | 74 +++++++++++++++++++++++++++++++++++++++++++----
+ 1 file changed, 68 insertions(+), 6 deletions(-)
+
+diff --git a/extlibs/tinydtls/dtls.c b/extlibs/tinydtls/dtls.c
+index 779e701..111a65d 100644
+--- a/extlibs/tinydtls/dtls.c
++++ b/extlibs/tinydtls/dtls.c
+@@ -529,6 +529,37 @@ known_cipher(dtls_context_t *ctx, dtls_cipher_t code, int is_client) {
+ (ecdsa && is_tls_ecdhe_ecdsa_with_aes_128_ccm_8(code));
+ }
+
++/**
++ * This method detects if we already have a established DTLS session with
++ * peer and the peer is attempting to perform a fresh handshake by sending
++ * messages with epoch = 0. This is to handle situations mentioned in
++ * RFC 6347 - section 4.2.8.
++ *
++ * @param msg The packet received from Client
++ * @param msglen Packet length
++ * @param peer peer who is the sender for this packet
++ * @return @c 1 if this is a rehandshake attempt by
++ * client
++ */
++static int
++hs_attempt_with_existing_peer(uint8_t *msg, size_t msglen,
++ dtls_peer_t *peer)
++{
++ if ((peer) && (peer->state == DTLS_STATE_CONNECTED)) {
++ if (msg[0] == DTLS_CT_HANDSHAKE) {
++ uint16_t msg_epoch = dtls_uint16_to_int(DTLS_RECORD_HEADER(msg)->epoch);
++ if (msg_epoch == 0) {
++ dtls_handshake_header_t * hs_header = DTLS_HANDSHAKE_HEADER(msg + DTLS_RH_LENGTH);
++ if (hs_header->msg_type == DTLS_HT_CLIENT_HELLO ||
++ hs_header->msg_type == DTLS_HT_HELLO_REQUEST) {
++ return 1;
++ }
++ }
++ }
++ }
++ return 0;
++}
++
+ /** Dump out the cipher keys and IVs used for the symetric cipher. */
+ static void dtls_debug_keyblock(dtls_security_parameters_t *config)
+ {
+@@ -1540,6 +1571,7 @@ static int
+ dtls_verify_peer(dtls_context_t *ctx,
+ dtls_peer_t *peer,
+ session_t *session,
++ const dtls_state_t state,
+ uint8 *data, size_t data_length)
+ {
+ uint8 buf[DTLS_HV_LENGTH + DTLS_COOKIE_LENGTH];
+@@ -1595,9 +1627,11 @@ dtls_verify_peer(dtls_context_t *ctx,
+
+ /* TODO use the same record sequence number as in the ClientHello,
+ see 4.2.1. Denial-of-Service Countermeasures */
+- err = dtls_send_handshake_msg_hash(ctx, peer, session,
+- DTLS_HT_HELLO_VERIFY_REQUEST,
+- buf, p - buf, 0);
++ err = dtls_send_handshake_msg_hash(ctx,
++ state == DTLS_STATE_CONNECTED ? peer : NULL,
++ session,
++ DTLS_HT_HELLO_VERIFY_REQUEST,
++ buf, p - buf, 0);
+ if (err < 0) {
+ dtls_warn("cannot send HelloVerify request\n");
+ }
+@@ -3209,7 +3243,7 @@ handle_handshake_msg(dtls_context_t *ctx, dtls_peer_t *peer, session_t *session,
+
+ case DTLS_HT_CLIENT_HELLO:
+
+- if ((peer && state != DTLS_STATE_CONNECTED) ||
++ if ((peer && state != DTLS_STATE_CONNECTED && state != DTLS_STATE_WAIT_CLIENTHELLO) ||
+ (!peer && state != DTLS_STATE_WAIT_CLIENTHELLO)) {
+ return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE);
+ }
+@@ -3223,7 +3257,7 @@ handle_handshake_msg(dtls_context_t *ctx, dtls_peer_t *peer, session_t *session,
+ Anything else will be rejected. Fragementation is not allowed
+ here as it would require peer state as well.
+ */
+- err = dtls_verify_peer(ctx, peer, session, data, data_length);
++ err = dtls_verify_peer(ctx, peer, session, state, data, data_length);
+ if (err < 0) {
+ dtls_warn("error in dtls_verify_peer err: %i\n", err);
+ return err;
+@@ -3236,7 +3270,23 @@ handle_handshake_msg(dtls_context_t *ctx, dtls_peer_t *peer, session_t *session,
+
+ /* At this point, we have a good relationship with this peer. This
+ * state is left for re-negotiation of key material. */
++ /* As per RFC 6347 - section 4.2.8 if this is an attempt to
++ * rehandshake, we can delete the existing key material
++ * as the client has demonstrated reachibility by completing
++ * the cookie exchange */
++ if (peer && state == DTLS_STATE_WAIT_CLIENTHELLO) {
++ dtls_debug("removing the peer\n");
++#ifndef WITH_CONTIKI
++ HASH_DEL_PEER(ctx->peers, peer);
++#else /* WITH_CONTIKI */
++ list_remove(ctx->peers, peer);
++#endif /* WITH_CONTIKI */
++
++ dtls_free_peer(peer);
++ peer = NULL;
++ }
+ if (!peer) {
++ dtls_debug("creating new peer\n");
+ dtls_security_parameters_t *security;
+
+ /* msg contains a Client Hello with a valid cookie, so we can
+@@ -3594,6 +3644,7 @@ dtls_handle_message(dtls_context_t *ctx,
+ int data_length; /* length of decrypted payload
+ (without MAC and padding) */
+ int err;
++ int bypass_epoch_check = 0;
+
+ /* check if we have DTLS state for addr/port/ifindex */
+ peer = dtls_get_peer(ctx, session);
+@@ -3613,6 +3664,15 @@ dtls_handle_message(dtls_context_t *ctx,
+ if (peer) {
+ data_length = decrypt_verify(peer, msg, rlen, &data);
+ if (data_length < 0) {
++ if (hs_attempt_with_existing_peer(msg, rlen, peer)) {
++ data = msg + DTLS_RH_LENGTH;
++ data_length = rlen - DTLS_RH_LENGTH;
++ state = DTLS_STATE_WAIT_CLIENTHELLO;
++ role = DTLS_SERVER;
++ /* Bypass epoch check as the epoch for incoming msg is 0
++ and expected epoch MAY be different */
++ bypass_epoch_check = 1;
++ } else {
+ int err = dtls_alert_fatal_create(DTLS_ALERT_DECRYPT_ERROR);
+ dtls_info("decrypt_verify() failed\n");
+ if (peer->state < DTLS_STATE_CONNECTED) {
+@@ -3623,8 +3683,10 @@ dtls_handle_message(dtls_context_t *ctx,
+ }
+ return err;
+ }
++ } else {
+ role = peer->role;
+ state = peer->state;
++ }
+ } else {
+ /* is_record() ensures that msg contains at least a record header */
+ data = msg + DTLS_RH_LENGTH;
+@@ -3677,7 +3739,7 @@ dtls_handle_message(dtls_context_t *ctx,
+ /* Handshake messages other than Finish must use the current
+ * epoch, Finish has epoch + 1. */
+
+- if (peer) {
++ if (peer && !bypass_epoch_check) {
+ uint16_t expected_epoch = dtls_security_params(peer)->epoch;
+ uint16_t msg_epoch =
+ dtls_uint16_to_int(DTLS_RECORD_HEADER(msg)->epoch);
+--
+1.7.9.5
+
(ecdsa && is_tls_ecdhe_ecdsa_with_aes_128_ccm_8(code));
}
+/**
+ * This method detects if we already have a established DTLS session with
+ * peer and the peer is attempting to perform a fresh handshake by sending
+ * messages with epoch = 0. This is to handle situations mentioned in
+ * RFC 6347 - section 4.2.8.
+ *
+ * @param msg The packet received from Client
+ * @param msglen Packet length
+ * @param peer peer who is the sender for this packet
+ * @return @c 1 if this is a rehandshake attempt by
+ * client
+ */
+static int
+hs_attempt_with_existing_peer(uint8_t *msg, size_t msglen,
+ dtls_peer_t *peer)
+{
+ if ((peer) && (peer->state == DTLS_STATE_CONNECTED)) {
+ if (msg[0] == DTLS_CT_HANDSHAKE) {
+ uint16_t msg_epoch = dtls_uint16_to_int(DTLS_RECORD_HEADER(msg)->epoch);
+ if (msg_epoch == 0) {
+ dtls_handshake_header_t * hs_header = DTLS_HANDSHAKE_HEADER(msg + DTLS_RH_LENGTH);
+ if (hs_header->msg_type == DTLS_HT_CLIENT_HELLO ||
+ hs_header->msg_type == DTLS_HT_HELLO_REQUEST) {
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
/** Dump out the cipher keys and IVs used for the symetric cipher. */
static void dtls_debug_keyblock(dtls_security_parameters_t *config)
{
dtls_verify_peer(dtls_context_t *ctx,
dtls_peer_t *peer,
session_t *session,
+ const dtls_state_t state,
uint8 *data, size_t data_length)
{
uint8 buf[DTLS_HV_LENGTH + DTLS_COOKIE_LENGTH];
/* TODO use the same record sequence number as in the ClientHello,
see 4.2.1. Denial-of-Service Countermeasures */
- err = dtls_send_handshake_msg_hash(ctx, peer, session,
- DTLS_HT_HELLO_VERIFY_REQUEST,
- buf, p - buf, 0);
+ err = dtls_send_handshake_msg_hash(ctx,
+ state == DTLS_STATE_CONNECTED ? peer : NULL,
+ session,
+ DTLS_HT_HELLO_VERIFY_REQUEST,
+ buf, p - buf, 0);
if (err < 0) {
dtls_warn("cannot send HelloVerify request\n");
}
case DTLS_HT_CLIENT_HELLO:
- if ((peer && state != DTLS_STATE_CONNECTED) ||
+ if ((peer && state != DTLS_STATE_CONNECTED && state != DTLS_STATE_WAIT_CLIENTHELLO) ||
(!peer && state != DTLS_STATE_WAIT_CLIENTHELLO)) {
return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE);
}
Anything else will be rejected. Fragementation is not allowed
here as it would require peer state as well.
*/
- err = dtls_verify_peer(ctx, peer, session, data, data_length);
+ err = dtls_verify_peer(ctx, peer, session, state, data, data_length);
if (err < 0) {
dtls_warn("error in dtls_verify_peer err: %i\n", err);
return err;
/* At this point, we have a good relationship with this peer. This
* state is left for re-negotiation of key material. */
+ /* As per RFC 6347 - section 4.2.8 if this is an attempt to
+ * rehandshake, we can delete the existing key material
+ * as the client has demonstrated reachibility by completing
+ * the cookie exchange */
+ if (peer && state == DTLS_STATE_WAIT_CLIENTHELLO) {
+ dtls_debug("removing the peer\n");
+#ifndef WITH_CONTIKI
+ HASH_DEL_PEER(ctx->peers, peer);
+#else /* WITH_CONTIKI */
+ list_remove(ctx->peers, peer);
+#endif /* WITH_CONTIKI */
+
+ dtls_free_peer(peer);
+ peer = NULL;
+ }
if (!peer) {
+ dtls_debug("creating new peer\n");
dtls_security_parameters_t *security;
/* msg contains a Client Hello with a valid cookie, so we can
int data_length; /* length of decrypted payload
(without MAC and padding) */
int err;
+ int bypass_epoch_check = 0;
/* check if we have DTLS state for addr/port/ifindex */
peer = dtls_get_peer(ctx, session);
if (peer) {
data_length = decrypt_verify(peer, msg, rlen, &data);
if (data_length < 0) {
+ if (hs_attempt_with_existing_peer(msg, rlen, peer)) {
+ data = msg + DTLS_RH_LENGTH;
+ data_length = rlen - DTLS_RH_LENGTH;
+ state = DTLS_STATE_WAIT_CLIENTHELLO;
+ role = DTLS_SERVER;
+ /* Bypass epoch check as the epoch for incoming msg is 0
+ and expected epoch MAY be different */
+ bypass_epoch_check = 1;
+ } else {
int err = dtls_alert_fatal_create(DTLS_ALERT_DECRYPT_ERROR);
dtls_info("decrypt_verify() failed\n");
if (peer->state < DTLS_STATE_CONNECTED) {
}
return err;
}
+ } else {
role = peer->role;
state = peer->state;
+ }
} else {
/* is_record() ensures that msg contains at least a record header */
data = msg + DTLS_RH_LENGTH;
/* Handshake messages other than Finish must use the current
* epoch, Finish has epoch + 1. */
- if (peer) {
+ if (peer && !bypass_epoch_check) {
uint16_t expected_epoch = dtls_security_params(peer)->epoch;
uint16_t msg_epoch =
dtls_uint16_to_int(DTLS_RECORD_HEADER(msg)->epoch);