From f7da4795eeebddac37434ba60c8c91b3f581c4c3 Mon Sep 17 00:00:00 2001 From: Sachin Agrawal Date: Wed, 21 Jan 2015 08:55:45 -0800 Subject: [PATCH] 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 Reviewed-on: https://gerrit.iotivity.org/gerrit/120 Tested-by: jenkins-iotivity Reviewed-by: Sashi Penta Reviewed-by: Sudarshan Prasad (cherry picked from commit 6baf3d32af01b99cc56466e51b541a6be3a911f4) Reviewed-on: https://gerrit.iotivity.org/gerrit/311 --- ...upport-in-tinyDTLS-to-support-rehandshake.patch | 172 +++++++++++++++++++++ extlibs/tinydtls/dtls.c | 74 ++++++++- 2 files changed, 240 insertions(+), 6 deletions(-) create mode 100644 extlibs/tinydtls/0001-Added-support-in-tinyDTLS-to-support-rehandshake.patch 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 index 0000000..fb9c71f --- /dev/null +++ b/extlibs/tinydtls/0001-Added-support-in-tinyDTLS-to-support-rehandshake.patch @@ -0,0 +1,172 @@ +From c78aa91005b7b9542d595dc32d8c8fe020d2257d Mon Sep 17 00:00:00 2001 +From: Sachin Agrawal +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 +--- + 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 + diff --git a/extlibs/tinydtls/dtls.c b/extlibs/tinydtls/dtls.c index 9090f22..a87d7f1 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); -- 2.7.4