Added support in tinyDTLS to support rehandshake
authorSachin Agrawal <sachin.agrawal@intel.com>
Wed, 21 Jan 2015 16:55:45 +0000 (08:55 -0800)
committerSudarshan Prasad <sudarshan.prasad@intel.com>
Fri, 6 Feb 2015 00:37:51 +0000 (00:37 +0000)
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>
Reviewed-on: https://gerrit.iotivity.org/gerrit/120
Tested-by: jenkins-iotivity <jenkins-iotivity@opendaylight.org>
Reviewed-by: Sashi Penta <sashi.kumar.penta@intel.com>
Reviewed-by: Sudarshan Prasad <sudarshan.prasad@intel.com>
(cherry picked from commit 6baf3d32af01b99cc56466e51b541a6be3a911f4)
Reviewed-on: https://gerrit.iotivity.org/gerrit/311

extlibs/tinydtls/0001-Added-support-in-tinyDTLS-to-support-rehandshake.patch [new file with mode: 0644]
extlibs/tinydtls/dtls.c

diff --git a/extlibs/tinydtls/0001-Added-support-in-tinyDTLS-to-support-rehandshake.patch b/extlibs/tinydtls/0001-Added-support-in-tinyDTLS-to-support-rehandshake.patch
new file mode 100644 (file)
index 0000000..fb9c71f
--- /dev/null
@@ -0,0 +1,172 @@
+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
+
index 9090f22..a87d7f1 100644 (file)
@@ -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);