Version 0.5.0 of tinydtls
Dependents: tinydtls_test_cellular tinydtls_test_ethernet tiny-dtls
Diff: dtls.c
- Revision:
- 0:ff9ebe0cf0e9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dtls.c Fri Oct 18 13:18:30 2013 +0000 @@ -0,0 +1,3716 @@ +/* dtls -- a very basic DTLS implementation + * + * Copyright (C) 2011--2012 Olaf Bergmann <bergmann@tzi.org> + * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" +#include "dtls_time.h" + +#define __DEBUG__ 0 +#ifndef __MODULE__ +#define __MODULE__ "dtls.c" +#endif + +#include "dbg.h" + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_ASSERT_H +#include <assert.h> +#endif +#ifndef WITH_CONTIKI +#include <stdlib.h> +#include "uthash.h" +#else /* WITH_CONTIKI */ +# ifndef NDEBUG +# define DEBUG DEBUG_PRINT +# include "net/uip-debug.h" +# endif /* NDEBUG */ +#endif /* WITH_CONTIKI */ + +#include "debug.h" +#include "numeric.h" +#include "netq.h" +#include "dtls.h" + +#ifdef WITH_SHA256 +# include "sha2/sha2.h" +#endif + +#define dtls_set_version(H,V) dtls_int_to_uint16(&(H)->version, (V)) +#define dtls_set_content_type(H,V) ((H)->content_type = (V) & 0xff) +#define dtls_set_length(H,V) ((H)->length = (V)) + +#define dtls_get_content_type(H) ((H)->content_type & 0xff) +#define dtls_get_version(H) dtls_uint16_to_int(&(H)->version) +#define dtls_get_epoch(H) dtls_uint16_to_int(&(H)->epoch) +#define dtls_get_sequence_number(H) dtls_uint48_to_ulong(&(H)->sequence_number) +#define dtls_get_fragment_length(H) dtls_uint24_to_int(&(H)->fragment_length) + +#ifndef WITH_CONTIKI +#define HASH_FIND_PEER(head,sess,out) \ + HASH_FIND(hh,head,sess,sizeof(session_t),out) +#define HASH_ADD_PEER(head,sess,add) \ + HASH_ADD(hh,head,sess,sizeof(session_t),add) +#define HASH_DEL_PEER(head,delptr) \ + HASH_DELETE(hh,head,delptr) +#endif /* WITH_CONTIKI */ + +#define DTLS_RH_LENGTH sizeof(dtls_record_header_t) +#define DTLS_HS_LENGTH sizeof(dtls_handshake_header_t) +#define DTLS_CH_LENGTH sizeof(dtls_client_hello_t) /* no variable length fields! */ +#define DTLS_COOKIE_LENGTH_MAX 32 +#define DTLS_CH_LENGTH_MAX sizeof(dtls_client_hello_t) + DTLS_COOKIE_LENGTH_MAX + 12 + 26 +#define DTLS_HV_LENGTH sizeof(dtls_hello_verify_t) +#define DTLS_SH_LENGTH (2 + DTLS_RANDOM_LENGTH + 1 + 2 + 1) +#define DTLS_CE_LENGTH (3 + 3 + 27 + DTLS_EC_KEY_SIZE + DTLS_EC_KEY_SIZE) +#define DTLS_SKEXEC_LENGTH (1 + 2 + 1 + 1 + DTLS_EC_KEY_SIZE + DTLS_EC_KEY_SIZE + 2 + 70) +#define DTLS_SKEXECPSK_LENGTH_MIN 2 +#define DTLS_SKEXECPSK_LENGTH_MAX 2 + DTLS_PSK_MAX_CLIENT_IDENTITY_LEN +#define DTLS_CKXPSK_LENGTH_MIN 2 +#define DTLS_CKXEC_LENGTH (1 + 1 + DTLS_EC_KEY_SIZE + DTLS_EC_KEY_SIZE) +#define DTLS_CV_LENGTH (1 + 1 + 2 + 1 + 1 + 1 + 1 + DTLS_EC_KEY_SIZE + 1 + 1 + DTLS_EC_KEY_SIZE) +#define DTLS_FIN_LENGTH 12 + +#define HS_HDR_LENGTH DTLS_RH_LENGTH + DTLS_HS_LENGTH +#define HV_HDR_LENGTH HS_HDR_LENGTH + DTLS_HV_LENGTH + +#define HIGH(V) (((V) >> 8) & 0xff) +#define LOW(V) ((V) & 0xff) + +#define DTLS_RECORD_HEADER(M) ((dtls_record_header_t *)(M)) +#define DTLS_HANDSHAKE_HEADER(M) ((dtls_handshake_header_t *)(M)) + +#define HANDSHAKE(M) ((dtls_handshake_header_t *)((M) + DTLS_RH_LENGTH)) +#define CLIENTHELLO(M) ((dtls_client_hello_t *)((M) + HS_HDR_LENGTH)) + +/* The length check here should work because dtls_*_to_int() works on + * unsigned char. Otherwise, broken messages could cause severe + * trouble. Note that this macro jumps out of the current program flow + * when the message is too short. Beware! + */ +#define SKIP_VAR_FIELD(P,L,T) { \ + if (L < dtls_ ## T ## _to_int(P) + sizeof(T)) \ + goto error; \ + L -= dtls_ ## T ## _to_int(P) + sizeof(T); \ + P += dtls_ ## T ## _to_int(P) + sizeof(T); \ + } + +/* some constants for the PRF */ +#define PRF_LABEL(Label) prf_label_##Label +#define PRF_LABEL_SIZE(Label) (sizeof(PRF_LABEL(Label)) - 1) + +static const unsigned char prf_label_master[] = "master secret"; +static const unsigned char prf_label_key[] = "key expansion"; +static const unsigned char prf_label_client[] = "client"; +static const unsigned char prf_label_server[] = "server"; +static const unsigned char prf_label_finished[] = " finished"; + +/* first part of Raw public key, the is the start of the Subject Public Key */ +static const unsigned char cert_asn1_header[] = { + 0x30, 0x59, /* SEQUENCE, length 89 bytes */ + 0x30, 0x13, /* SEQUENCE, length 19 bytes */ + 0x06, 0x07, /* OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1) */ + 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, + 0x06, 0x08, /* OBJECT IDENTIFIER prime256v1 (1 2 840 10045 3 1 7) */ + 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, + 0x03, 0x42, 0x00, /* BIT STRING, length 66 bytes, 0 bits unused */ + 0x04 /* uncompressed, followed by the r und s values of the public key */ +}; + +static dtls_context_t the_dtls_context; + +#ifdef WITH_CONTIKI +PROCESS(dtls_retransmit_process, "DTLS retransmit process"); +#endif + +void +dtls_init() { + dtls_clock_init(); + netq_init(); + crypto_init(); + peer_init(); +} + +/* Calls cb_alert() with given arguments if defined, otherwise an + * error message is logged and the result is -1. This is just an + * internal helper. + */ +#define CALL(Context, which, ...) \ + ((Context)->h && (Context)->h->which \ + ? (Context)->h->which((Context), ##__VA_ARGS__) \ + : -1) + +/** + * Sends the fragment of length \p buflen given in \p buf to the + * specified \p peer. The data will be MAC-protected and encrypted + * according to the selected cipher and split into one or more DTLS + * records of the specified \p type. This function returns the number + * of bytes that were sent, or \c -1 if an error occurred. + * + * \param ctx The DTLS context to use. + * \param peer The remote peer. + * \param type The content type of the record. + * \param buf The data to send. + * \param buflen The actual length of \p buf. + * \return Less than zero on error, the number of bytes written otherwise. + */ +static int dtls_send(dtls_context_t *ctx, dtls_peer_t *peer, unsigned char type, + uint8 *buf, size_t buflen); + +/** + * Stops ongoing retransmissions of handshake messages for @p peer. + */ +static void dtls_stop_retransmission(dtls_context_t *context, dtls_peer_t *peer); + +dtls_peer_t * +dtls_get_peer(const dtls_context_t *ctx, const session_t *session) { + dtls_peer_t *p = NULL; + +#ifndef WITH_CONTIKI + HASH_FIND_PEER(ctx->peers, session, p); +#else /* WITH_CONTIKI */ + for (p = list_head(ctx->peers); p; p = list_item_next(p)) + if (dtls_session_equals(&p->session, session)) + return p; +#endif /* WITH_CONTIKI */ + + return p; +} + +static void +dtls_add_peer(dtls_context_t *ctx, dtls_peer_t *peer) { +#ifndef WITH_CONTIKI + HASH_ADD_PEER(ctx->peers, session, peer); +#else /* WITH_CONTIKI */ + list_add(ctx->peers, peer); +#endif /* WITH_CONTIKI */ +} + +int +dtls_write(struct dtls_context_t *ctx, + session_t *dst, uint8 *buf, size_t len) { + + dtls_peer_t *peer = dtls_get_peer(ctx, dst); + + /* Check if peer connection already exists */ + if (!peer) { /* no ==> create one */ + int res; + + /* dtls_connect() returns a value greater than zero if a new + * connection attempt is made, 0 for session reuse. */ + res = dtls_connect(ctx, dst); + + return (res >= 0) ? 0 : res; + } else { /* a session exists, check if it is in state connected */ + + if (peer->state != DTLS_STATE_CONNECTED) { + return 0; + } else { + return dtls_send(ctx, peer, DTLS_CT_APPLICATION_DATA, buf, len); + } + } +} + +static int +dtls_get_cookie(uint8 *msg, int msglen, uint8 **cookie) { + /* To access the cookie, we have to determine the session id's + * length and skip the whole thing. */ + if (msglen < DTLS_HS_LENGTH + DTLS_CH_LENGTH + sizeof(uint8)) + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + + if (dtls_uint16_to_int(msg + DTLS_HS_LENGTH) != DTLS_VERSION) + return dtls_alert_fatal_create(DTLS_ALERT_PROTOCOL_VERSION); + + msglen -= DTLS_HS_LENGTH + DTLS_CH_LENGTH; + msg += DTLS_HS_LENGTH + DTLS_CH_LENGTH; + + SKIP_VAR_FIELD(msg, msglen, uint8); /* skip session id */ + + if (msglen < (*msg & 0xff) + sizeof(uint8)) + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + + *cookie = msg + sizeof(uint8); + return dtls_uint8_to_int(msg); + + error: + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); +} + +static int +dtls_create_cookie(dtls_context_t *ctx, + session_t *session, + uint8 *msg, int msglen, + uint8 *cookie, int *clen) { + unsigned char buf[DTLS_HMAC_MAX]; + size_t len, e; + + /* create cookie with HMAC-SHA256 over: + * - SECRET + * - session parameters (only IP address?) + * - client version + * - random gmt and bytes + * - session id + * - cipher_suites + * - compression method + */ + + /* We use our own buffer as hmac_context instead of a dynamic buffer + * created by dtls_hmac_new() to separate storage space for cookie + * creation from storage that is used in real sessions. Note that + * the buffer size must fit with the default hash algorithm (see + * implementation of dtls_hmac_context_new()). */ + + dtls_hmac_context_t hmac_context; + dtls_hmac_init(&hmac_context, ctx->cookie_secret, DTLS_COOKIE_SECRET_LENGTH); + + dtls_hmac_update(&hmac_context, + (unsigned char *)&session->addr, session->size); + + /* feed in the beginning of the Client Hello up to and including the + session id */ + e = sizeof(dtls_client_hello_t); + e += (*(msg + DTLS_HS_LENGTH + e) & 0xff) + sizeof(uint8); + if (e + DTLS_HS_LENGTH > msglen) + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + + dtls_hmac_update(&hmac_context, msg + DTLS_HS_LENGTH, e); + + /* skip cookie bytes and length byte */ + e += *(uint8 *)(msg + DTLS_HS_LENGTH + e) & 0xff; + e += sizeof(uint8); + if (e + DTLS_HS_LENGTH > msglen) + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + + dtls_hmac_update(&hmac_context, + msg + DTLS_HS_LENGTH + e, + dtls_get_fragment_length(DTLS_HANDSHAKE_HEADER(msg)) - e); + + len = dtls_hmac_finalize(&hmac_context, buf); + + if (len < *clen) { + memset(cookie + len, 0, *clen - len); + *clen = len; + } + + memcpy(cookie, buf, *clen); + return 0; +} + +#ifdef DTLS_CHECK_CONTENTTYPE +/* used to check if a received datagram contains a DTLS message */ +static char const content_types[] = { + DTLS_CT_CHANGE_CIPHER_SPEC, + DTLS_CT_ALERT, + DTLS_CT_HANDSHAKE, + DTLS_CT_APPLICATION_DATA, + 0 /* end marker */ +}; +#endif + +/** + * Checks if \p msg points to a valid DTLS record. If + * + */ +static unsigned int +is_record(uint8 *msg, int msglen) { + DBG("is_record"); + unsigned int rlen = 0; + + if (msglen >= DTLS_RH_LENGTH /* FIXME allow empty records? */ +#ifdef DTLS_CHECK_CONTENTTYPE + && strchr(content_types, msg[0]) +#endif + && msg[1] == HIGH(DTLS_VERSION) + && msg[2] == LOW(DTLS_VERSION)) + { + rlen = DTLS_RH_LENGTH + + dtls_uint16_to_int(DTLS_RECORD_HEADER(msg)->length); + + /* we do not accept wrong length field in record header */ + if (rlen > msglen) + rlen = 0; + } + return rlen; +} + +/** + * Initializes \p buf as record header. The caller must ensure that \p + * buf is capable of holding at least \c sizeof(dtls_record_header_t) + * bytes. Increments sequence number counter of \p peer. + * \return pointer to the next byte after the written header + */ +static inline uint8 * +dtls_set_record_header(uint8 type, dtls_peer_t *peer, uint8 *buf) { + + dtls_int_to_uint8(buf, type); + buf += sizeof(uint8); + + dtls_int_to_uint16(buf, DTLS_VERSION); + buf += sizeof(uint16); + + if (peer) { + memcpy(buf, &peer->epoch, sizeof(uint16) + sizeof(uint48)); + + /* increment record sequence counter by 1 */ + inc_uint(uint48, peer->rseq); + } else { + memset(buf, 0, sizeof(uint16) + sizeof(uint48)); + } + + buf += sizeof(uint16) + sizeof(uint48); + + memset(buf, 0, sizeof(uint16)); + return buf + sizeof(uint16); +} + +/** + * Initializes \p buf as handshake header. The caller must ensure that \p + * buf is capable of holding at least \c sizeof(dtls_handshake_header_t) + * bytes. Increments message sequence number counter of \p peer. + * \return pointer to the next byte after \p buf + */ +static inline uint8 * +dtls_set_handshake_header(uint8 type, dtls_peer_t *peer, + int length, + int frag_offset, int frag_length, + uint8 *buf) { + + dtls_int_to_uint8(buf, type); + buf += sizeof(uint8); + + dtls_int_to_uint24(buf, length); + buf += sizeof(uint24); + + if (peer) { + /* increment handshake message sequence counter by 1 */ + inc_uint(uint16, peer->hs_state.mseq); + + /* and copy the result to buf */ + memcpy(buf, &peer->hs_state.mseq, sizeof(uint16)); + } else { + memset(buf, 0, sizeof(uint16)); + } + buf += sizeof(uint16); + + dtls_int_to_uint24(buf, frag_offset); + buf += sizeof(uint24); + + dtls_int_to_uint24(buf, frag_length); + buf += sizeof(uint24); + + return buf; +} + +/** only one compression method is currently defined */ +static uint8 compression_methods[] = { + TLS_COMPRESSION_NULL +}; + +static inline int is_psk_supported(dtls_context_t *ctx){ + return ctx && ctx->h && ctx->h->get_psk_key; +} + +static inline int is_ecdsa_supported(dtls_context_t *ctx, int is_client){ + return ctx && ctx->h && ((!is_client && ctx->h->get_ecdsa_key) || + (is_client && ctx->h->verify_ecdsa_key)); +} + +/** + * Returns @c 1 if @p code is a cipher suite other than @c + * TLS_NULL_WITH_NULL_NULL that we recognize. + * + * @param ctx The current DTLS context + * @param code The cipher suite identifier to check + * @param is_client 1 for a dtls client, 0 for server + * @return @c 1 iff @p code is recognized, + */ +static int +known_cipher(dtls_context_t *ctx, dtls_cipher_t code, int is_client) { + int psk; + int ecdsa; + + psk = is_psk_supported(ctx); + ecdsa = is_ecdsa_supported(ctx, is_client); + return (psk && code == TLS_PSK_WITH_AES_128_CCM_8) || + (ecdsa && code == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); +} + +static void dtls_debug_keyblock(dtls_security_parameters_t *config) +{ + DBG("key_block (%d bytes):", dtls_kb_size(config, peer->role)); + dtls_dsrv_hexdump_log(LOG_DEBUG, " client_MAC_secret", + dtls_kb_client_mac_secret(config, peer->role), + dtls_kb_mac_secret_size(config, peer->role), 0); + + dtls_dsrv_hexdump_log(LOG_DEBUG, " server_MAC_secret", + dtls_kb_server_mac_secret(config, peer->role), + dtls_kb_mac_secret_size(config, peer->role), 0); + + dtls_dsrv_hexdump_log(LOG_DEBUG, " client_write_key", + dtls_kb_client_write_key(config, peer->role), + dtls_kb_key_size(config, peer->role), 0); + + dtls_dsrv_hexdump_log(LOG_DEBUG, " server_write_key", + dtls_kb_server_write_key(config, peer->role), + dtls_kb_key_size(config, peer->role), 0); + + dtls_dsrv_hexdump_log(LOG_DEBUG, " client_IV", + dtls_kb_client_iv(config, peer->role), + dtls_kb_iv_size(config, peer->role), 0); + + dtls_dsrv_hexdump_log(LOG_DEBUG, " server_IV", + dtls_kb_server_iv(config, peer->role), + dtls_kb_iv_size(config, peer->role), 0); +} + +static int +calculate_key_block(dtls_context_t *ctx, + dtls_handshake_parameters_t *handshake, + dtls_security_parameters_t *security, + session_t *session) { + unsigned char *pre_master_secret; + size_t pre_master_len = 0; + pre_master_secret = security->key_block; + uint8 master_secret[DTLS_MASTER_SECRET_LENGTH]; + int err; + + switch (handshake->cipher) { + case TLS_PSK_WITH_AES_128_CCM_8: { + const dtls_psk_key_t *psk; + + err = CALL(ctx, get_psk_key, session, handshake->keyx.psk.identity, + handshake->keyx.psk.id_length, &psk); + if (err < 0) { + DBG("no psk key for session available"); + return err; + } + /* Temporarily use the key_block storage space for the pre master secret. */ + pre_master_len = dtls_psk_pre_master_secret(psk->key, psk->key_length, + pre_master_secret); + + dtls_dsrv_hexdump_log(LOG_DEBUG, "psk", psk->key, psk->key_length, 1); + + break; + } + case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: { + pre_master_len = dtls_ecdh_pre_master_secret(handshake->keyx.ecdsa.own_eph_priv, + handshake->keyx.ecdsa.other_eph_pub_x, + handshake->keyx.ecdsa.other_eph_pub_y, + sizeof(handshake->keyx.ecdsa.own_eph_priv), + pre_master_secret); + break; + } + default: + DBG("calculate_key_block: unknown cipher"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + dtls_dsrv_hexdump_log(LOG_DEBUG, "client_random", handshake->tmp.random.client, + DTLS_RANDOM_LENGTH, 0); + dtls_dsrv_hexdump_log(LOG_DEBUG, "server_random", handshake->tmp.random.server, + DTLS_RANDOM_LENGTH, 0); + dtls_dsrv_hexdump_log(LOG_DEBUG, "pre_master_secret", pre_master_secret, + pre_master_len, 0); + + dtls_prf(pre_master_secret, pre_master_len, + PRF_LABEL(master), PRF_LABEL_SIZE(master), + handshake->tmp.random.client, DTLS_RANDOM_LENGTH, + handshake->tmp.random.server, DTLS_RANDOM_LENGTH, + master_secret, + DTLS_MASTER_SECRET_LENGTH); + + dtls_dsrv_hexdump_log(LOG_DEBUG, "master_secret", master_secret, + DTLS_MASTER_SECRET_LENGTH, 0); + + /* create key_block from master_secret + * key_block = PRF(master_secret, + "key expansion" + tmp.random.server + tmp.random.client) */ + + dtls_prf(master_secret, + DTLS_MASTER_SECRET_LENGTH, + PRF_LABEL(key), PRF_LABEL_SIZE(key), + handshake->tmp.random.server, DTLS_RANDOM_LENGTH, + handshake->tmp.random.client, DTLS_RANDOM_LENGTH, + security->key_block, + dtls_kb_size(security, peer->role)); + + memcpy(handshake->tmp.master_secret, master_secret, DTLS_MASTER_SECRET_LENGTH); + dtls_debug_keyblock(security); + return 0; +} + +/** + * Releases the storage allocated for read_cipher and write_cipher and + * sets both fields to NULL. + */ +static void +invalidate_ciphers(dtls_security_parameters_t *config) { + if (config->read_cipher) { + dtls_cipher_free(config->read_cipher); + config->read_cipher = NULL; + } + + if (config->write_cipher) { + dtls_cipher_free(config->write_cipher); + config->write_cipher = NULL; + } +} + +static int +init_cipher(dtls_handshake_parameters_t *handshake, dtls_security_parameters_t *config, dtls_peer_type role) +{ + /* set crypto context for TLS_PSK_WITH_AES_128_CCM_8 */ + dtls_cipher_free(config->read_cipher); + + assert(handshake->cipher != TLS_NULL_WITH_NULL_NULL); + config->read_cipher = dtls_cipher_new(handshake->cipher, + dtls_kb_remote_write_key(config, role), + dtls_kb_key_size(config, role)); + + if (!config->read_cipher) { + WARN("cannot create read cipher"); + goto error; + } + + dtls_cipher_free(config->write_cipher); + + config->write_cipher = dtls_cipher_new(handshake->cipher, + dtls_kb_local_write_key(config, role), + dtls_kb_key_size(config, role)); + + if (!config->write_cipher) { + WARN("cannot create write cipher"); + goto error; + } + + config->cipher = handshake->cipher; + config->compression = handshake->compression; + + return 0; +error: + + invalidate_ciphers(config); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); +} + +/* TODO: add a generic method which iterates over a list and searches for a specific key */ +static int verify_ext_eliptic_curves(uint8 *data, size_t data_length) { + int i, curve_name; + + /* length of curve list */ + i = dtls_uint16_to_int(data); + data += sizeof(uint16); + if (i + sizeof(uint16) != data_length) { + WARN("the list of the supported elliptic curves should be tls extension length - 2"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + + for (i = data_length - sizeof(uint16); i > 0; i -= sizeof(uint16)) { + /* check if this curve is supported */ + curve_name = dtls_uint16_to_int(data); + data += sizeof(uint16); + + if (curve_name == TLS_EXT_ELLIPTIC_CURVES_SECP256R1) + return 0; + } + + WARN("no supported elliptic curve found"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); +} + +static int verify_ext_cert_type(uint8 *data, size_t data_length) { + int i, cert_type; + + /* length of cert type list */ + i = dtls_uint8_to_int(data); + data += sizeof(uint8); + if (i + sizeof(uint8) != data_length) { + WARN("the list of the supported certificate types should be tls extension length - 1"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + + for (i = data_length - sizeof(uint8); i > 0; i -= sizeof(uint8)) { + /* check if this cert type is supported */ + cert_type = dtls_uint8_to_int(data); + data += sizeof(uint8); + + if (cert_type == TLS_CERT_TYPE_OOB) + return 0; + } + + WARN("no supported certificate type found"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); +} + +static int verify_ext_ec_point_formats(uint8 *data, size_t data_length) { + int i, cert_type; + + /* length of ec_point_formats list */ + i = dtls_uint8_to_int(data); + data += sizeof(uint8); + if (i + sizeof(uint8) != data_length) { + WARN("the list of the supported ec_point_formats should be tls extension length - 1"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + + for (i = data_length - sizeof(uint8); i > 0; i -= sizeof(uint8)) { + /* check if this ec_point_format is supported */ + cert_type = dtls_uint8_to_int(data); + data += sizeof(uint8); + + if (cert_type == TLS_EXT_EC_POINT_FORMATS_UNCOMPRESSED) + return 0; + } + + WARN("no supported ec_point_format found"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); +} + +static int +dtls_check_tls_extension(dtls_peer_t *peer, + uint8 *data, size_t data_length, int client_hello) +{ + int i, j; + int ext_elliptic_curve = 0; + int ext_client_cert_type = 0; + int ext_server_cert_type = 0; + int ext_ec_point_formats = 0; + dtls_handshake_parameters_t *handshake = &peer->handshake_params; + + if (data_length < sizeof(uint16)) { + /* no tls extensions specified */ + if (handshake->cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) { + goto error; + } + return 0; + } + + /* get the length of the tls extension list */ + j = dtls_uint16_to_int(data); + data += sizeof(uint16); + data_length -= sizeof(uint16); + + if (data_length < j) + goto error; + + /* check for TLS extensions needed for this cipher */ + while (data_length) { + if (data_length < sizeof(uint16) * 2) + goto error; + + /* get the tls extension type */ + i = dtls_uint16_to_int(data); + data += sizeof(uint16); + data_length -= sizeof(uint16); + + /* get the length of the tls extension */ + j = dtls_uint16_to_int(data); + data += sizeof(uint16); + data_length -= sizeof(uint16); + + if (data_length < j) + goto error; + + switch (i) { + case TLS_EXT_ELLIPTIC_CURVES: + ext_elliptic_curve = 1; + if (verify_ext_eliptic_curves(data, j)) + goto error; + break; + case TLS_EXT_CLIENT_CERIFICATE_TYPE: + ext_client_cert_type = 1; + if (client_hello) { + if (verify_ext_cert_type(data, j)) + goto error; + } else { + if (dtls_uint8_to_int(data) != TLS_CERT_TYPE_OOB) + goto error; + } + break; + case TLS_EXT_SERVER_CERIFICATE_TYPE: + ext_server_cert_type = 1; + if (client_hello) { + if (verify_ext_cert_type(data, j)) + goto error; + } else { + if (dtls_uint8_to_int(data) != TLS_CERT_TYPE_OOB) + goto error; + } + break; + case TLS_EXT_EC_POINT_FORMATS: + ext_ec_point_formats = 1; + if (verify_ext_ec_point_formats(data, j)) + goto error; + break; + default: + WARN("unsupported tls extension: %i", i); + break; + } + data += j; + data_length -= j; + } + if (handshake->cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) { + if (!ext_elliptic_curve && !ext_client_cert_type && !ext_server_cert_type + && !ext_ec_point_formats) { + WARN("not all required tls extensions found in client hello"); + goto error; + } + } + return 0; + +error: + if (client_hello && peer->state == DTLS_STATE_CONNECTED) { + return dtls_alert_create(DTLS_ALERT_LEVEL_WARNING, DTLS_ALERT_NO_RENEGOTIATION); + } else { + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } +} + +/** + * Updates the security parameters of given \p peer. As this must be + * done before the new configuration is activated, it changes the + * OTHER_CONFIG only. When the ClientHello handshake message in \p + * data does not contain a cipher suite or compression method, it is + * copied from the CURRENT_CONFIG. + * + * \param ctx The current DTLS context. + * \param peer The remote peer whose security parameters are about to change. + * \param data The handshake message with a ClientHello. + * \param data_length The actual size of \p data. + * \return \c 0 if an error occurred, \c 1 otherwise. + */ +static int +dtls_update_parameters(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) { + int i, j; + int ok; + dtls_handshake_parameters_t *config = &peer->handshake_params; + dtls_security_parameters_t *security = &peer->security_params; + + assert(config); + assert(data_length > DTLS_HS_LENGTH + DTLS_CH_LENGTH); + + /* DBG("dtls_update_parameters: msglen is %d", data_length); */ + + /* skip the handshake header and client version information */ + data += DTLS_HS_LENGTH + sizeof(uint16); + data_length -= DTLS_HS_LENGTH + sizeof(uint16); + + /* store client random in config + * FIXME: if we send the ServerHello here, we do not need to store + * the client's random bytes */ + memcpy(config->tmp.random.client, data, DTLS_RANDOM_LENGTH); + data += DTLS_RANDOM_LENGTH; + data_length -= DTLS_RANDOM_LENGTH; + + /* Caution: SKIP_VAR_FIELD may jump to error: */ + SKIP_VAR_FIELD(data, data_length, uint8); /* skip session id */ + SKIP_VAR_FIELD(data, data_length, uint8); /* skip cookie */ + + i = dtls_uint16_to_int(data); + if (data_length < i + sizeof(uint16)) { + /* Looks like we do not have a cipher nor compression. This is ok + * for renegotiation, but not for the initial handshake. */ + + if (security->cipher == TLS_NULL_WITH_NULL_NULL) + goto error; + + config->cipher = security->cipher; + config->compression = security->compression; + + return 0; + } + + data += sizeof(uint16); + data_length -= sizeof(uint16) + i; + + ok = 0; + while (i && !ok) { + config->cipher = dtls_uint16_to_int(data); + ok = known_cipher(ctx, config->cipher, 0); + i -= sizeof(uint16); + data += sizeof(uint16); + } + + /* skip remaining ciphers */ + data += i; + + if (!ok) { + /* reset config cipher to a well-defined value */ + config->cipher = TLS_NULL_WITH_NULL_NULL; + goto error; + } + + if (data_length < sizeof(uint8)) { + /* no compression specified, take the current compression method */ + config->compression = security->compression; + goto error; + } + + i = dtls_uint8_to_int(data); + if (data_length < i + sizeof(uint8)) + goto error; + + data += sizeof(uint8); + data_length -= sizeof(uint8) + i; + + ok = 0; + while (i && !ok) { + for (j = 0; j < sizeof(compression_methods) / sizeof(uint8); ++j) + if (dtls_uint8_to_int(data) == compression_methods[j]) { + config->compression = compression_methods[j]; + ok = 1; + } + i -= sizeof(uint8); + data += sizeof(uint8); + } + + if (!ok) { + /* reset config cipher to a well-defined value */ + goto error; + } + + return dtls_check_tls_extension(peer, data, data_length, 1); + error: + WARN("ClientHello too short (%d bytes)", data_length); + if (peer->state == DTLS_STATE_CONNECTED) { + return dtls_alert_create(DTLS_ALERT_LEVEL_WARNING, DTLS_ALERT_NO_RENEGOTIATION); + } else { + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } +} + +static inline int +check_client_keyexchange(dtls_context_t *ctx, + dtls_handshake_parameters_t *handshake, + uint8 *data, size_t length) { + + if (handshake->cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) { + + if (length < DTLS_HS_LENGTH + DTLS_CKXEC_LENGTH) { + DBG("The client key exchange is too short"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += DTLS_HS_LENGTH; + + if (dtls_uint8_to_int(data) != 65) { + DBG("expected 65 bytes long public point"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += sizeof(uint8); + + if (dtls_uint8_to_int(data) != 4) { + DBG("expected uncompressed public point"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += sizeof(uint8); + + memcpy(handshake->keyx.ecdsa.other_eph_pub_x, data, + sizeof(handshake->keyx.ecdsa.other_eph_pub_x)); + data += sizeof(handshake->keyx.ecdsa.other_eph_pub_x); + + memcpy(handshake->keyx.ecdsa.other_eph_pub_y, data, + sizeof(handshake->keyx.ecdsa.other_eph_pub_y)); + data += sizeof(handshake->keyx.ecdsa.other_eph_pub_y); + } else { + int id_length; + + if (length < DTLS_HS_LENGTH + DTLS_CKXPSK_LENGTH_MIN) { + DBG("The client key exchange is too short"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += DTLS_HS_LENGTH; + + id_length = dtls_uint16_to_int(data); + data += sizeof(uint16); + + if (DTLS_HS_LENGTH + DTLS_CKXPSK_LENGTH_MIN + id_length != length) { + DBG("The identity has a wrong length"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + + if (id_length > DTLS_PSK_MAX_CLIENT_IDENTITY_LEN) { + WARN("please use a smaller client identity"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + handshake->keyx.psk.id_length = id_length; + memcpy(handshake->keyx.psk.identity, data, id_length); + } + return 0; +} + +static inline void +update_hs_hash(dtls_peer_t *peer, uint8 *data, size_t length) { + dtls_dsrv_hexdump_log(LOG_DEBUG, "add MAC data", data, length, 0); + dtls_hash_update(&peer->hs_state.hs_hash, data, length); +} + +static void +copy_hs_hash(dtls_peer_t *peer, dtls_hash_ctx *hs_hash) { + memcpy(hs_hash, &peer->hs_state.hs_hash, sizeof(peer->hs_state.hs_hash)); +} + +static inline size_t +finalize_hs_hash(dtls_peer_t *peer, uint8 *buf) { + return dtls_hash_finalize(buf, &peer->hs_state.hs_hash); +} + +static inline void +clear_hs_hash(dtls_peer_t *peer) { + assert(peer); + DBG("clear MAC"); + dtls_hash_init(&peer->hs_state.hs_hash); +} + +/** + *Checks if \p record + \p data contain a Finished message with valid + * verify_data. + * + * \param ctx The current DTLS context. + * \param peer The remote peer of the security association. + * \param record The message record header. + * \param rlen The actual length of \p record. + * \param data The cleartext payload of the message. + * \param data_length Actual length of \p data. + * \return \c 1 if the Finished message is valid, \c 0 otherwise. + */ +static int +check_finished(dtls_context_t *ctx, dtls_peer_t *peer, + uint8 *record, uint8 *data, size_t data_length) { + + DBG("check_finished"); + size_t digest_length, label_size; + const unsigned char *label; + unsigned char buf[DTLS_HMAC_MAX]; + + if (data_length < DTLS_HS_LENGTH + DTLS_FIN_LENGTH) + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + + /* Use a union here to ensure that sufficient stack space is + * reserved. As statebuf and verify_data are not used at the same + * time, we can re-use the storage safely. + */ + union { + unsigned char statebuf[DTLS_HASH_CTX_SIZE]; + unsigned char verify_data[DTLS_FIN_LENGTH]; + } b; + + /* temporarily store hash status for roll-back after finalize */ + memcpy(b.statebuf, &peer->hs_state.hs_hash, DTLS_HASH_CTX_SIZE); + + digest_length = finalize_hs_hash(peer, buf); + /* clear_hash(); */ + + /* restore hash status */ + memcpy(&peer->hs_state.hs_hash, b.statebuf, DTLS_HASH_CTX_SIZE); + + if (peer->role == DTLS_CLIENT) { + label = PRF_LABEL(server); + label_size = PRF_LABEL_SIZE(server); + } else { /* client */ + label = PRF_LABEL(client); + label_size = PRF_LABEL_SIZE(client); + } + + dtls_prf(peer->handshake_params.tmp.master_secret, + DTLS_MASTER_SECRET_LENGTH, + label, label_size, + PRF_LABEL(finished), PRF_LABEL_SIZE(finished), + buf, digest_length, + b.verify_data, sizeof(b.verify_data)); + + dtls_dsrv_hexdump_log(LOG_DEBUG, "d:", data + DTLS_HS_LENGTH, sizeof(b.verify_data), 0); + dtls_dsrv_hexdump_log(LOG_DEBUG, "v:", b.verify_data, sizeof(b.verify_data), 0); + return memcmp(data + DTLS_HS_LENGTH, b.verify_data, sizeof(b.verify_data)); +} + +/** + * Prepares the payload given in \p data for sending with + * dtls_send(). The \p data is encrypted and compressed according to + * the current security parameters of \p peer. The result of this + * operation is put into \p sendbuf with a prepended record header of + * type \p type ready for sending. As some cipher suites add a MAC + * before encryption, \p data must be large enough to hold this data + * as well (usually \c dtls_kb_digest_size(CURRENT_CONFIG(peer)). + * + * \param peer The remote peer the packet will be sent to. + * \param type The content type of this record. + * \param data The payload to send. + * \param data_length The size of \p data. + * \param sendbuf The output buffer where the encrypted record + * will be placed. + * \param rlen This parameter must be initialized with the + * maximum size of \p sendbuf and will be updated + * to hold the actual size of the stored packet + * on success. On error, the value of \p rlen is + * undefined. + * \return Less than zero on error, or greater than zero success. + */ +static int +dtls_prepare_record(dtls_peer_t *peer, + unsigned char type, + uint8 *data_array[], size_t data_len_array[], + size_t data_array_len, + uint8 *sendbuf, size_t *rlen) { + DBG("dtls_prepare_record"); + + uint8 *p, *start; + int res; + int i; + dtls_security_parameters_t *security = &peer->security_params; + + p = dtls_set_record_header(type, peer, sendbuf); + start = p; + + if (!peer || security->cipher == TLS_NULL_WITH_NULL_NULL) { + /* no cipher suite */ + + res = 0; + for (i = 0; i < data_array_len; i++) { + /* check the minimum that we need for packets that are not encrypted */ + if (*rlen < (p - start) + data_len_array[i]) { + DBG("dtls_prepare_record: send buffer too small"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + memcpy(p, data_array[i], data_len_array[i]); + p += data_len_array[i]; + res += data_len_array[i]; + } + } else { /* TLS_PSK_WITH_AES_128_CCM_8 */ + dtls_cipher_context_t *cipher_context; + + /** + * length of additional_data for the AEAD cipher which consists of + * seq_num(2+6) + type(1) + version(2) + length(2) + */ +#define A_DATA_LEN 13 + unsigned char nonce[DTLS_CCM_BLOCKSIZE]; + unsigned char A_DATA[A_DATA_LEN]; + + if (security->cipher == TLS_PSK_WITH_AES_128_CCM_8) { + DBG("dtls_prepare_record(): encrypt using TLS_PSK_WITH_AES_128_CCM_8"); + } else if (security->cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) { + DBG("dtls_prepare_record(): encrypt using TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"); + } else { + DBG("dtls_prepare_record(): encrypt using unknown cipher"); + } + + /* set nonce + from http://tools.ietf.org/html/draft-mcgrew-tls-aes-ccm-03: + struct { + case client: + uint32 client_write_IV; // low order 32-bits + case server: + uint32 server_write_IV; // low order 32-bits + uint64 seq_num; + } CCMNonce. + + In DTLS, the 64-bit seq_num is the 16-bit epoch concatenated with the + 48-bit seq_num. + */ + + memcpy(p, &DTLS_RECORD_HEADER(sendbuf)->epoch, 8); + p += 8; + res = 8; + + for (i = 0; i < data_array_len; i++) { + /* check the minimum that we need for packets that are not encrypted */ + if (*rlen < res + data_len_array[i]) { + DBG("dtls_prepare_record: send buffer too small"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + memcpy(p, data_array[i], data_len_array[i]); + p += data_len_array[i]; + res += data_len_array[i]; + } + + memset(nonce, 0, DTLS_CCM_BLOCKSIZE); + memcpy(nonce, dtls_kb_local_iv(security, peer->role), + dtls_kb_iv_size(security, peer->role)); + memcpy(nonce + dtls_kb_iv_size(security, peer->role), start, 8); /* epoch + seq_num */ + + cipher_context = security->write_cipher; + + if (!cipher_context) { + WARN("no write_cipher available!"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + dtls_dsrv_hexdump_log(LOG_DEBUG, "nonce:", nonce, DTLS_CCM_BLOCKSIZE, 0); + dtls_dsrv_hexdump_log(LOG_DEBUG, "key:", + dtls_kb_local_write_key(security, peer->role), + dtls_kb_key_size(security, peer->role), 0); + + /* re-use N to create additional data according to RFC 5246, Section 6.2.3.3: + * + * additional_data = seq_num + TLSCompressed.type + + * TLSCompressed.version + TLSCompressed.length; + */ + memcpy(A_DATA, &DTLS_RECORD_HEADER(sendbuf)->epoch, 8); /* epoch and seq_num */ + memcpy(A_DATA + 8, &DTLS_RECORD_HEADER(sendbuf)->content_type, 3); /* type and version */ + dtls_int_to_uint16(A_DATA + 11, res - 8); /* length */ + + res = dtls_encrypt(cipher_context, start + 8, res - 8, start + 8, nonce, + A_DATA, A_DATA_LEN); + + if (res < 0) + return res; + + res += 8; /* increment res by size of nonce_explicit */ + dtls_dsrv_hexdump_log(LOG_DEBUG, "message:", start, res, 0); + } + + /* fix length of fragment in sendbuf */ + dtls_int_to_uint16(sendbuf + 11, res); + + *rlen = DTLS_RH_LENGTH + res; + return 0; +} + +static int +dtls_send_handshake_msg_hash(dtls_context_t *ctx, + dtls_peer_t *peer, + session_t *session, + uint8 header_type, + uint8 *data, size_t data_length, + int add_hash) +{ + DBG("dtls_send_handshake_msg_hash"); + uint8 buf[DTLS_HS_LENGTH]; + uint8 *data_array[2]; + size_t data_len_array[2]; + int i = 0; + uint8 sendbuf[DTLS_MAX_BUF]; + size_t len = sizeof(sendbuf); + + dtls_set_handshake_header(header_type, (add_hash) ? peer : NULL, data_length, 0, + data_length, buf); + + if (add_hash) { + update_hs_hash(peer, buf, sizeof(buf)); + } + data_array[i] = buf; + data_len_array[i] = sizeof(buf); + i++; + + if (data != NULL) { + if (add_hash) { + update_hs_hash(peer, data, data_length); + } + data_array[i] = data; + data_len_array[i] = data_length; + i++; + } + i = dtls_prepare_record(peer, DTLS_CT_HANDSHAKE, data_array, data_len_array, + i, sendbuf, &len); + if (i < 0) { + return i; + } + + return CALL(ctx, write, session, sendbuf, len); +} + +static int +dtls_send_handshake_msg(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 header_type, + uint8 *data, size_t data_length) +{ + return dtls_send_handshake_msg_hash(ctx, peer, &peer->session, + header_type, data, data_length, 1); +} + +/** + * Returns true if the message @p Data is a handshake message that + * must be included in the calculation of verify_data in the Finished + * message. + * + * @param Type The message type. Only handshake messages but the initial + * Client Hello and Hello Verify Request are included in the hash, + * @param Data The PDU to examine. + * @param Length The length of @p Data. + * + * @return @c 1 if @p Data must be included in hash, @c 0 otherwise. + * + * @hideinitializer + */ +#define MUST_HASH(Type, Data, Length) \ + ((Type) == DTLS_CT_HANDSHAKE && \ + ((Data) != NULL) && ((Length) > 0) && \ + ((Data)[0] != DTLS_HT_HELLO_VERIFY_REQUEST) && \ + ((Data)[0] != DTLS_HT_CLIENT_HELLO || \ + ((Length) >= HS_HDR_LENGTH && \ + (dtls_uint16_to_int(DTLS_RECORD_HEADER(Data)->epoch > 0) || \ + (dtls_uint16_to_int(HANDSHAKE(Data)->message_seq) > 0))))) + +/** + * Sends the data passed in @p buf as a DTLS record of type @p type to + * the given peer. The data will be encrypted and compressed according + * to the security parameters for @p peer. + * + * @param ctx The DTLS context in effect. + * @param peer The remote party where the packet is sent. + * @param type The content type of this record. + * @param buf The data to send. + * @param buflen The number of bytes to send from @p buf. + * @return Less than zero in case of an error or the number of + * bytes that have been sent otherwise. + */ +static int +dtls_send(dtls_context_t *ctx, dtls_peer_t *peer, + unsigned char type, + uint8 *buf, size_t buflen) { + + /* We cannot use ctx->sendbuf here as it is reserved for collecting + * the input for this function, i.e. buf == ctx->sendbuf. + * + * TODO: check if we can use the receive buf here. This would mean + * that we might not be able to handle multiple records stuffed in + * one UDP datagram */ + unsigned char sendbuf[DTLS_MAX_BUF]; + size_t len = sizeof(sendbuf); + int res; + + res = dtls_prepare_record(peer, type, &buf, &buflen, 1, sendbuf, &len); + + if (res < 0) + return res; + + /* if (peer && MUST_HASH(peer, type, buf, buflen)) */ + /* update_hs_hash(peer, buf, buflen); */ + + dtls_dsrv_hexdump_log(LOG_DEBUG, "send header", sendbuf, + sizeof(dtls_record_header_t), 1); + dtls_dsrv_hexdump_log(LOG_DEBUG, "send unencrypted", buf, buflen, 1); + + if (type == DTLS_CT_HANDSHAKE && buf[0] != DTLS_HT_HELLO_VERIFY_REQUEST) { + /* copy handshake messages other than HelloVerify into retransmit buffer */ + netq_t *n = netq_node_new(); + if (n) { + dtls_tick_t now; + dtls_ticks(&now); + n->t = now + 2 * CLOCK_SECOND; + n->retransmit_cnt = 0; + n->timeout = 2 * CLOCK_SECOND; + n->peer = peer; + n->length = buflen; + memcpy(n->data, buf, buflen); + + if (!netq_insert_node((netq_t **)ctx->sendqueue, n)) { + WARN("cannot add packet to retransmit buffer"); + netq_node_free(n); +#ifdef WITH_CONTIKI + } else { + /* must set timer within the context of the retransmit process */ + PROCESS_CONTEXT_BEGIN(&dtls_retransmit_process); + etimer_set(&ctx->retransmit_timer, n->timeout); + PROCESS_CONTEXT_END(&dtls_retransmit_process); +#else /* WITH_CONTIKI */ + DBG("copied to sendqueue"); +#endif /* WITH_CONTIKI */ + } + } else + WARN("retransmit buffer full"); + } + + /* FIXME: copy to peer's sendqueue (after fragmentation if + * necessary) and initialize retransmit timer */ + res = CALL(ctx, write, &peer->session, sendbuf, len); + + /* Guess number of bytes application data actually sent: + * dtls_prepare_record() tells us in len the number of bytes to + * send, res will contain the bytes actually sent. */ + return res <= 0 ? res : buflen - (len - res); +} + +static inline int +dtls_alert(dtls_context_t *ctx, dtls_peer_t *peer, dtls_alert_level_t level, + dtls_alert_t description) { + uint8_t msg[] = { level, description }; + + dtls_send(ctx, peer, DTLS_CT_ALERT, msg, sizeof(msg)); + return 0; +} + +int +dtls_close(dtls_context_t *ctx, const session_t *remote) { + int res = -1; + dtls_peer_t *peer; + + peer = dtls_get_peer(ctx, remote); + + if (peer) { + res = dtls_alert(ctx, peer, DTLS_ALERT_LEVEL_FATAL, DTLS_ALERT_CLOSE_NOTIFY); + /* indicate tear down */ + peer->state = DTLS_STATE_CLOSING; + } + return res; +} + +static void dtls_destory_peer(dtls_context_t *ctx, dtls_peer_t *peer, int unlink) +{ + if (peer->state != DTLS_STATE_CLOSED) + dtls_close(ctx, &peer->session); + if (unlink) { +#ifndef WITH_CONTIKI + HASH_DEL_PEER(ctx->peers, peer); +#else /* WITH_CONTIKI */ + list_remove(ctx->peers, peer); + +#ifndef NDEBUG + PRINTF("removed peer ["); + PRINT6ADDR(&peer->session.addr); + PRINTF("]:%d", uip_ntohs(peer->session.port)); +#endif +#endif /* WITH_CONTIKI */ + } + dtls_free_peer(peer); +} + +/** + * Checks a received Client Hello message for a valid cookie. When the + * Client Hello contains no cookie, the function fails and a Hello + * Verify Request is sent to the peer (using the write callback function + * registered with \p ctx). The return value is \c -1 on error, \c 0 when + * undecided, and \c 1 if the Client Hello was good. + * + * \param ctx The DTLS context. + * \param peer The remote party we are talking to, if any. + * \param session Transport address of the remote peer. + * \param msg The received datagram. + * \param msglen Length of \p msg. + * \return \c 1 if msg is a Client Hello with a valid cookie, \c 0 or + * \c -1 otherwise. + */ +static int +dtls_verify_peer(dtls_context_t *ctx, + dtls_peer_t *peer, + session_t *session, + uint8 *record, + uint8 *data, size_t data_length) +{ + +DBG("dtls_verify_peer"); + + uint8 buf[DTLS_HV_LENGTH + DTLS_COOKIE_LENGTH]; + uint8 *p = buf; + int len = DTLS_COOKIE_LENGTH; + uint8 *cookie; + int err; +#undef mycookie +#define mycookie (buf + DTLS_HV_LENGTH) + + /* Store cookie where we can reuse it for the HelloVerify request. */ + err = dtls_create_cookie(ctx, session, data, data_length, mycookie, &len); + if (err < 0) + return err; + + dtls_dsrv_hexdump_log(LOG_DEBUG, "create cookie", mycookie, len, 0); + + assert(len == DTLS_COOKIE_LENGTH); + + /* Perform cookie check. */ + len = dtls_get_cookie(data, data_length, &cookie); + if (len < 0) { + WARN("error while fetching the cookie, err: %i", err); + return err; + } + + dtls_dsrv_hexdump_log(LOG_DEBUG, "compare with cookie", cookie, len, 0); + + /* check if cookies match */ + if (len == DTLS_COOKIE_LENGTH && memcmp(cookie, mycookie, len) == 0) { + DBG("found matching cookie"); + return 0; + } + + if (len > 0) { + dtls_dsrv_hexdump_log(LOG_DEBUG, "invalid cookie", cookie, len, 0); + } else { + DBG("cookie len is 0!"); + } + + /* ClientHello did not contain any valid cookie, hence we send a + * HelloVerify request. */ + + dtls_int_to_uint16(p, DTLS_VERSION); + p += sizeof(uint16); + + dtls_int_to_uint8(p, DTLS_COOKIE_LENGTH); + p += sizeof(uint8); + + assert(p == mycookie); + + p += DTLS_COOKIE_LENGTH; + + err = dtls_send_handshake_msg_hash(ctx, peer, session, + DTLS_HT_HELLO_VERIFY_REQUEST, + buf, p - buf, 0); + if (err < 0) { + WARN("cannot send HelloVerify request"); + } + return err; /* HelloVerify is sent, now we cannot do anything but wait */ + +#undef mycookie +} + +static int +check_client_certificate_verify(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) +{ + DBG("check_client_certificate_verify"); + + dtls_handshake_parameters_t *config = &peer->handshake_params; + int i; + unsigned char *result_r; + unsigned char *result_s; + dtls_hash_ctx hs_hash; + unsigned char sha256hash[DTLS_HMAC_DIGEST_SIZE]; + + assert(config->cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); + + data += DTLS_HS_LENGTH; + + if (data_length < DTLS_HS_LENGTH + DTLS_CV_LENGTH) { + DBG("the package length does not match the expected"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + + if (dtls_uint8_to_int(data) != TLS_EXT_SIG_HASH_ALGO_SHA256) { + DBG("only sha256 is supported in certificate verify"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + if (dtls_uint8_to_int(data) != TLS_EXT_SIG_HASH_ALGO_ECDSA) { + DBG("only ecdsa signature is supported in client verify"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + if (data_length < dtls_uint16_to_int(data)) { + DBG("signature length wrong"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint16); + data_length -= sizeof(uint16); + + if (dtls_uint8_to_int(data) != 0x30) { + DBG("wrong ASN.1 struct, expected SEQUENCE"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + if (data_length < dtls_uint8_to_int(data)) { + DBG("signature length wrong"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + if (dtls_uint8_to_int(data) != 0x02) { + DBG("wrong ASN.1 struct, expected Integer"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + i = dtls_uint8_to_int(data); + data += sizeof(uint8); + data_length -= sizeof(uint8); + + /* Sometimes these values have a leeding 0 byte */ + result_r = data + i - DTLS_EC_KEY_SIZE; + + data += i; + data_length -= i; + + if (dtls_uint8_to_int(data) != 0x02) { + DBG("wrong ASN.1 struct, expected Integer"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + i = dtls_uint8_to_int(data); + data += sizeof(uint8); + data_length -= sizeof(uint8); + + /* Sometimes these values have a leeding 0 byte */ + result_s = data + i - DTLS_EC_KEY_SIZE; + + data += i; + data_length -= i; + + copy_hs_hash(peer, &hs_hash); + + dtls_hash_finalize(sha256hash, &hs_hash); + + i = dtls_ecdsa_verify_sig_hash(config->keyx.ecdsa.other_pub_x, config->keyx.ecdsa.other_pub_y, + sizeof(config->keyx.ecdsa.other_pub_x), + sha256hash, sizeof(sha256hash), + result_r, result_s); + + if (i < 0) { + DBG("wrong signature err: %i", i); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + return 0; +} + +static int +dtls_send_server_hello(dtls_context_t *ctx, dtls_peer_t *peer) +{ + /* Ensure that the largest message to create fits in our source + * buffer. (The size of the destination buffer is checked by the + * encoding function, so we do not need to guess.) */ + uint8 buf[DTLS_SH_LENGTH + 2 + 5 + 5 + 8 + 6]; + uint8 *p; + int ecdsa; + uint8 extension_size; + dtls_handshake_parameters_t *handshake = &peer->handshake_params; + dtls_tick_t now; + + ecdsa = handshake->cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; + + extension_size = (ecdsa) ? 2 + 5 + 5 + 8 + 6 : 0; + + /* Handshake header */ + p = buf; + + /* ServerHello */ + dtls_int_to_uint16(p, DTLS_VERSION); + p += sizeof(uint16); + + /* Set server random: First 4 bytes are the server's Unix timestamp, + * followed by 28 bytes of generate random data. */ + dtls_ticks(&now); + dtls_int_to_uint32(handshake->tmp.random.server, now / CLOCK_SECOND); + prng(handshake->tmp.random.server + 4, 28); + + memcpy(p, handshake->tmp.random.server, DTLS_RANDOM_LENGTH); + p += DTLS_RANDOM_LENGTH; + + *p++ = 0; /* no session id */ + + if (handshake->cipher != TLS_NULL_WITH_NULL_NULL) { + /* selected cipher suite */ + dtls_int_to_uint16(p, handshake->cipher); + p += sizeof(uint16); + + /* selected compression method */ + if (handshake->compression >= 0) + *p++ = compression_methods[handshake->compression]; + } + + if (extension_size) { + /* length of the extensions */ + dtls_int_to_uint16(p, extension_size - 2); + p += sizeof(uint16); + } + + if (ecdsa) { + /* client certificate type extension */ + dtls_int_to_uint16(p, TLS_EXT_CLIENT_CERIFICATE_TYPE); + p += sizeof(uint16); + + /* length of this extension type */ + dtls_int_to_uint16(p, 1); + p += sizeof(uint16); + + dtls_int_to_uint8(p, TLS_CERT_TYPE_OOB); + p += sizeof(uint8); + + /* client certificate type extension */ + dtls_int_to_uint16(p, TLS_EXT_SERVER_CERIFICATE_TYPE); + p += sizeof(uint16); + + /* length of this extension type */ + dtls_int_to_uint16(p, 1); + p += sizeof(uint16); + + dtls_int_to_uint8(p, TLS_CERT_TYPE_OOB); + p += sizeof(uint8); + + /* elliptic_curves */ + dtls_int_to_uint16(p, TLS_EXT_ELLIPTIC_CURVES); + p += sizeof(uint16); + + /* length of this extension type */ + dtls_int_to_uint16(p, 4); + p += sizeof(uint16); + + /* length of the list */ + dtls_int_to_uint16(p, 2); + p += sizeof(uint16); + + dtls_int_to_uint16(p, TLS_EXT_ELLIPTIC_CURVES_SECP256R1); + p += sizeof(uint16); + + /* ec_point_formats */ + dtls_int_to_uint16(p, TLS_EXT_EC_POINT_FORMATS); + p += sizeof(uint16); + + /* length of this extension type */ + dtls_int_to_uint16(p, 2); + p += sizeof(uint16); + + /* number of supported formats */ + dtls_int_to_uint8(p, 1); + p += sizeof(uint8); + + dtls_int_to_uint8(p, TLS_EXT_EC_POINT_FORMATS_UNCOMPRESSED); + p += sizeof(uint8); + } + + assert(p - buf <= sizeof(buf)); + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_SERVER_HELLO, + buf, p - buf); +} + +static int +dtls_send_certificate_ecdsa(dtls_context_t *ctx, dtls_peer_t *peer, + const dtls_ecdsa_key_t *key) +{ + DBG("dtls_send_certificate_ecdsa"); + uint8 buf[DTLS_CE_LENGTH]; + uint8 *p; + + /* Certificate + * + * Start message construction at beginning of buffer. */ + p = buf; + + dtls_int_to_uint24(p, 94); + p += sizeof(uint24); + + dtls_int_to_uint24(p, 91); + p += sizeof(uint24); + + memcpy(p, &cert_asn1_header, sizeof(cert_asn1_header)); + p += sizeof(cert_asn1_header); + + memcpy(p, key->pub_key_x, DTLS_EC_KEY_SIZE); + p += DTLS_EC_KEY_SIZE; + + memcpy(p, key->pub_key_y, DTLS_EC_KEY_SIZE); + p += DTLS_EC_KEY_SIZE; + + assert(p - buf <= sizeof(buf)); + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_CERTIFICATE, + buf, p - buf); +} + +static uint8 * +dtls_add_ecdsa_signature_elem(uint8 *p, uint32_t *point_r, uint32_t *point_s) +{ + int len_r; + int len_s; + +#define R_KEY_OFFSET (2 + 1 + 1 + 1 + 1) +#define S_KEY_OFFSET(len_s) (R_KEY_OFFSET + (len_s) + 1 + 1) + /* store the pointer to the r component of the signature and make space */ + len_r = dtls_ec_key_from_uint32_asn1(point_r, DTLS_EC_KEY_SIZE, p + R_KEY_OFFSET); + len_s = dtls_ec_key_from_uint32_asn1(point_s, DTLS_EC_KEY_SIZE, p + S_KEY_OFFSET(len_r)); + +#undef R_KEY_OFFSET +#undef S_KEY_OFFSET + + /* length of signature */ + dtls_int_to_uint16(p, len_r + len_s + 2 + 2 + 2); + p += sizeof(uint16); + + /* ASN.1 SEQUENCE */ + dtls_int_to_uint8(p, 0x30); + p += sizeof(uint8); + + dtls_int_to_uint8(p, len_r + len_s + 2 + 2); + p += sizeof(uint8); + + /* ASN.1 Integer r */ + dtls_int_to_uint8(p, 0x02); + p += sizeof(uint8); + + dtls_int_to_uint8(p, len_r); + p += sizeof(uint8); + + /* the pint r was added here */ + p += len_r; + + /* ASN.1 Integer s */ + dtls_int_to_uint8(p, 0x02); + p += sizeof(uint8); + + dtls_int_to_uint8(p, len_s); + p += sizeof(uint8); + + /* the pint s was added here */ + p += len_s; + + return p; +} + +static int +dtls_send_server_key_exchange_ecdh(dtls_context_t *ctx, dtls_peer_t *peer, + const dtls_ecdsa_key_t *key) +{ + /* The ASN.1 Integer representation of an 32 byte unsigned int could be + * 33 bytes long add space for that */ + uint8 buf[DTLS_SKEXEC_LENGTH + 2]; + uint8 *p; + uint8 *key_params; + uint8 *ephemeral_pub_x; + uint8 *ephemeral_pub_y; + uint32_t point_r[9]; + uint32_t point_s[9]; + dtls_handshake_parameters_t *config = &peer->handshake_params; + + /* ServerKeyExchange + * + * Start message construction at beginning of buffer. */ + p = buf; + + key_params = p; + /* ECCurveType curve_type: named_curve */ + dtls_int_to_uint8(p, 3); + p += sizeof(uint8); + + /* NamedCurve namedcurve: secp256r1 */ + dtls_int_to_uint16(p, 23); + p += sizeof(uint16); + + dtls_int_to_uint8(p, 1 + 2 * DTLS_EC_KEY_SIZE); + p += sizeof(uint8); + + /* This should be an uncompressed point, but I do not have access to the sepc. */ + dtls_int_to_uint8(p, 4); + p += sizeof(uint8); + + /* store the pointer to the x component of the pub key and make space */ + ephemeral_pub_x = p; + p += DTLS_EC_KEY_SIZE; + + /* store the pointer to the y component of the pub key and make space */ + ephemeral_pub_y = p; + p += DTLS_EC_KEY_SIZE; + + dtls_ecdsa_generate_key(config->keyx.ecdsa.own_eph_priv, + ephemeral_pub_x, ephemeral_pub_y, + DTLS_EC_KEY_SIZE); + + /* sign the ephemeral and its paramaters */ + dtls_ecdsa_create_sig(key->priv_key, DTLS_EC_KEY_SIZE, + config->tmp.random.client, DTLS_RANDOM_LENGTH, + config->tmp.random.server, DTLS_RANDOM_LENGTH, + key_params, p - key_params, + point_r, point_s); + + p = dtls_add_ecdsa_signature_elem(p, point_r, point_s); + + assert(p - buf <= sizeof(buf)); + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_SERVER_KEY_EXCHANGE, + buf, p - buf); +} + +static int +dtls_send_server_key_exchange_psk(dtls_context_t *ctx, dtls_peer_t *peer, + const dtls_psk_key_t *key) +{ + uint8 buf[DTLS_SKEXECPSK_LENGTH_MAX]; + uint8 *p; + + p = buf; + + if (key->id_length > DTLS_PSK_MAX_CLIENT_IDENTITY_LEN) { + WARN("psk identity hint is too long"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + dtls_int_to_uint16(p, key->id_length); + p += sizeof(uint16); + + memcpy(p, key->id, key->id_length); + p += key->id_length; + + assert(p - buf <= sizeof(buf)); + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_SERVER_KEY_EXCHANGE, + buf, p - buf); +} + +static int +dtls_send_server_certificate_request(dtls_context_t *ctx, dtls_peer_t *peer) +{ + uint8 buf[8]; + uint8 *p; + + /* ServerHelloDone + * + * Start message construction at beginning of buffer. */ + p = buf; + + /* certificate_types */ + dtls_int_to_uint8(p, 1); + p += sizeof(uint8); + + /* ecdsa_sign */ + dtls_int_to_uint8(p, 64); + p += sizeof(uint8); + + /* supported_signature_algorithms */ + dtls_int_to_uint16(p, 2); + p += sizeof(uint16); + + /* sha256 */ + dtls_int_to_uint8(p, TLS_EXT_SIG_HASH_ALGO_SHA256); + p += sizeof(uint8); + + /* ecdsa */ + dtls_int_to_uint8(p, TLS_EXT_SIG_HASH_ALGO_ECDSA); + p += sizeof(uint8); + + /* certificate_authoritiess */ + dtls_int_to_uint16(p, 0); + p += sizeof(uint16); + + assert(p - buf <= sizeof(buf)); + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_CERTIFICATE_REQUEST, + buf, p - buf); +} + +static int +dtls_send_server_hello_done(dtls_context_t *ctx, dtls_peer_t *peer) +{ + + /* ServerHelloDone + * + * Start message construction at beginning of buffer. */ + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_SERVER_HELLO_DONE, + NULL, 0); +} + +static int +dtls_send_server_hello_msgs(dtls_context_t *ctx, dtls_peer_t *peer) +{ + int res; + + res = dtls_send_server_hello(ctx, peer); + + if (res < 0) { + DBG("dtls_server_hello: cannot prepare ServerHello record"); + return res; + } + + if (peer->handshake_params.cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) { + const dtls_ecdsa_key_t *ecdsa_key; + + res = CALL(ctx, get_ecdsa_key, &peer->session, &ecdsa_key); + if (res < 0) { + DBG("no ecdsa certificate to send in certificate"); + return res; + } + + res = dtls_send_certificate_ecdsa(ctx, peer, ecdsa_key); + + if (res < 0) { + DBG("dtls_server_hello: cannot prepare Certificate record"); + return res; + } + + res = dtls_send_server_key_exchange_ecdh(ctx, peer, ecdsa_key); + + if (res < 0) { + DBG("dtls_server_hello: cannot prepare Server Key Exchange record"); + return res; + } + + if (peer->handshake_params.cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 && + ctx && ctx->h && ctx->h->verify_ecdsa_key) { + res = dtls_send_server_certificate_request(ctx, peer); + + if (res < 0) { + DBG("dtls_server_hello: cannot prepare certificate Request record"); + return res; + } + } + } else if (peer->handshake_params.cipher == TLS_PSK_WITH_AES_128_CCM_8) { + const dtls_psk_key_t *psk; + + res = CALL(ctx, get_psk_key, &peer->session, NULL, 0, &psk); + if (res < 0) { + DBG("no psk or identity found for this session"); + return res; + } + + if (psk->id_length) { + res = dtls_send_server_key_exchange_psk(ctx, peer, psk); + + if (res < 0) { + DBG("dtls_server_key_exchange_psk: cannot send server key exchange record"); + return res; + } + } + } + + res = dtls_send_server_hello_done(ctx, peer); + + if (res < 0) { + DBG("dtls_server_hello: cannot prepare ServerHelloDone record"); + return res; + } + return 0; +} + +static inline int +dtls_send_ccs(dtls_context_t *ctx, dtls_peer_t *peer) { + uint8 buf[1]; + buf[0] = 1; + + return dtls_send(ctx, peer, DTLS_CT_CHANGE_CIPHER_SPEC, buf, 1); +} + + +static int +dtls_send_client_key_exchange(dtls_context_t *ctx, dtls_peer_t *peer) +{ + uint8 buf[DTLS_CKXEC_LENGTH]; + uint8 *p; + int err; + dtls_handshake_parameters_t *handshake = &peer->handshake_params; + + p = buf; + + switch (handshake->cipher) { + case TLS_PSK_WITH_AES_128_CCM_8: { + const dtls_psk_key_t *psk; + + err = CALL(ctx, get_psk_key, &peer->session, handshake->keyx.psk.identity, + handshake->keyx.psk.id_length, &psk); + if (err < 0) { + DBG("no psk key to send in kx"); + return err; + } + + if (psk->id_length + sizeof(uint16) > DTLS_CKXEC_LENGTH) { + WARN("the psk identity is too long"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + dtls_int_to_uint16(p, psk->id_length); + p += sizeof(uint16); + + memcpy(p, psk->id, psk->id_length); + p += psk->id_length; + + break; + } + case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: { + uint8 *ephemeral_pub_x; + uint8 *ephemeral_pub_y; + + dtls_int_to_uint8(p, 1 + 2 * DTLS_EC_KEY_SIZE); + p += sizeof(uint8); + + /* This should be an uncompressed point, but I do not have access to the sepc. */ + dtls_int_to_uint8(p, 4); + p += sizeof(uint8); + + ephemeral_pub_x = p; + p += DTLS_EC_KEY_SIZE; + ephemeral_pub_y = p; + p += DTLS_EC_KEY_SIZE; + + dtls_ecdsa_generate_key(peer->handshake_params.keyx.ecdsa.own_eph_priv, + ephemeral_pub_x, ephemeral_pub_y, + DTLS_EC_KEY_SIZE); + + break; + } + default: + DBG("cipher not supported"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + assert(p - buf <= sizeof(buf)); + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_CLIENT_KEY_EXCHANGE, + buf, p - buf); +} + +static int +dtls_send_certificate_verify_ecdh(dtls_context_t *ctx, dtls_peer_t *peer, + const dtls_ecdsa_key_t *key) +{ + /* The ASN.1 Integer representation of an 32 byte unsigned int could be + * 33 bytes long add space for that */ + uint8 buf[DTLS_CV_LENGTH + 2]; + uint8 *p; + uint32_t point_r[9]; + uint32_t point_s[9]; + dtls_hash_ctx hs_hash; + unsigned char sha256hash[DTLS_HMAC_DIGEST_SIZE]; + + /* ServerKeyExchange + * + * Start message construction at beginning of buffer. */ + p = buf; + + /* sha256 */ + dtls_int_to_uint8(p, TLS_EXT_SIG_HASH_ALGO_SHA256); + p += sizeof(uint8); + + /* ecdsa */ + dtls_int_to_uint8(p, TLS_EXT_SIG_HASH_ALGO_ECDSA); + p += sizeof(uint8); + + copy_hs_hash(peer, &hs_hash); + + dtls_hash_finalize(sha256hash, &hs_hash); + + /* sign the ephemeral and its paramaters */ + dtls_ecdsa_create_sig_hash(key->priv_key, DTLS_EC_KEY_SIZE, + sha256hash, sizeof(sha256hash), + point_r, point_s); + + p = dtls_add_ecdsa_signature_elem(p, point_r, point_s); + + assert(p - buf <= sizeof(buf)); + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_CERTIFICATE_VERIFY, + buf, p - buf); +} + +static int +dtls_send_finished(dtls_context_t *ctx, dtls_peer_t *peer, + const unsigned char *label, size_t labellen) +{ + int length; + uint8 hash[DTLS_HMAC_MAX]; + uint8 buf[DTLS_FIN_LENGTH]; + dtls_hash_ctx hs_hash; + uint8 *p = buf; + + copy_hs_hash(peer, &hs_hash); + + length = dtls_hash_finalize(hash, &hs_hash); + + dtls_prf(peer->handshake_params.tmp.master_secret, + DTLS_MASTER_SECRET_LENGTH, + label, labellen, + PRF_LABEL(finished), PRF_LABEL_SIZE(finished), + hash, length, + p, DTLS_FIN_LENGTH); + + dtls_dsrv_hexdump_log(LOG_DEBUG, "server finished MAC", p, DTLS_FIN_LENGTH, 0); + + p += DTLS_FIN_LENGTH; + + assert(p - buf <= sizeof(buf)); + + return dtls_send_handshake_msg(ctx, peer, DTLS_HT_FINISHED, + buf, p - buf); +} + +static int +dtls_send_client_hello(dtls_context_t *ctx, dtls_peer_t *peer, + uint8 cookie[], size_t cookie_length) { + DBG("dtls_send_client_hello"); + uint8 buf[DTLS_CH_LENGTH_MAX]; + uint8 *p = buf; + uint8_t cipher_size; + uint8_t extension_size; + int psk; + int ecdsa; + dtls_handshake_parameters_t *handshake = &peer->handshake_params; + dtls_tick_t now; + + psk = is_psk_supported(ctx); + ecdsa = is_ecdsa_supported(ctx, 1); + + cipher_size = 2 + ((ecdsa) ? 2 : 0) + ((psk) ? 2 : 0); + extension_size = (ecdsa) ? 2 + 6 + 6 + 8 + 6: 0; + + if (cipher_size == 0) { + DBG("no cipher callbacks implemented"); + } + + dtls_int_to_uint16(p, DTLS_VERSION); + p += sizeof(uint16); + + if (cookie_length > DTLS_COOKIE_LENGTH_MAX) { + WARN("the cookie is too long"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + + if (cookie_length == 0) { + /* Set client random: First 4 bytes are the client's Unix timestamp, + * followed by 28 bytes of generate random data. */ + dtls_ticks(&now); + dtls_int_to_uint32(&handshake->tmp.random.client, now / CLOCK_SECOND); + prng(handshake->tmp.random.client + sizeof(uint32), + DTLS_RANDOM_LENGTH - sizeof(uint32)); + } + /* we must use the same Client Random as for the previous request */ + memcpy(p, handshake->tmp.random.client, DTLS_RANDOM_LENGTH); + p += DTLS_RANDOM_LENGTH; + + /* session id (length 0) */ + dtls_int_to_uint8(p, 0); + p += sizeof(uint8); + + /* cookie */ + dtls_int_to_uint8(p, cookie_length); + p += sizeof(uint8); + if (cookie_length != 0) { + memcpy(p, cookie, cookie_length); + p += cookie_length; + } + + /* add known cipher(s) */ + dtls_int_to_uint16(p, cipher_size - 2); + p += sizeof(uint16); + + if (ecdsa) { + dtls_int_to_uint16(p, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); + p += sizeof(uint16); + } + if (psk) { + dtls_int_to_uint16(p, TLS_PSK_WITH_AES_128_CCM_8); + p += sizeof(uint16); + } + + /* compression method */ + dtls_int_to_uint8(p, 1); + p += sizeof(uint8); + + dtls_int_to_uint8(p, TLS_COMPRESSION_NULL); + p += sizeof(uint8); + + if (extension_size) { + /* length of the extensions */ + dtls_int_to_uint16(p, extension_size - 2); + p += sizeof(uint16); + } + + if (ecdsa) { + /* client certificate type extension */ + dtls_int_to_uint16(p, TLS_EXT_CLIENT_CERIFICATE_TYPE); + p += sizeof(uint16); + + /* length of this extension type */ + dtls_int_to_uint16(p, 2); + p += sizeof(uint16); + + /* length of the list */ + dtls_int_to_uint8(p, 1); + p += sizeof(uint8); + + dtls_int_to_uint8(p, TLS_CERT_TYPE_OOB); + p += sizeof(uint8); + + /* client certificate type extension */ + dtls_int_to_uint16(p, TLS_EXT_SERVER_CERIFICATE_TYPE); + p += sizeof(uint16); + + /* length of this extension type */ + dtls_int_to_uint16(p, 2); + p += sizeof(uint16); + + /* length of the list */ + dtls_int_to_uint8(p, 1); + p += sizeof(uint8); + + dtls_int_to_uint8(p, TLS_CERT_TYPE_OOB); + p += sizeof(uint8); + + /* elliptic_curves */ + dtls_int_to_uint16(p, TLS_EXT_ELLIPTIC_CURVES); + p += sizeof(uint16); + + /* length of this extension type */ + dtls_int_to_uint16(p, 4); + p += sizeof(uint16); + + /* length of the list */ + dtls_int_to_uint16(p, 2); + p += sizeof(uint16); + + dtls_int_to_uint16(p, TLS_EXT_ELLIPTIC_CURVES_SECP256R1); + p += sizeof(uint16); + + /* ec_point_formats */ + dtls_int_to_uint16(p, TLS_EXT_EC_POINT_FORMATS); + p += sizeof(uint16); + + /* length of this extension type */ + dtls_int_to_uint16(p, 2); + p += sizeof(uint16); + + /* number of supported formats */ + dtls_int_to_uint8(p, 1); + p += sizeof(uint8); + + dtls_int_to_uint8(p, TLS_EXT_EC_POINT_FORMATS_UNCOMPRESSED); + p += sizeof(uint8); + } + + assert(p - buf <= sizeof(buf)); + + if (cookie_length != 0) + clear_hs_hash(peer); + + return dtls_send_handshake_msg_hash(ctx, peer, &peer->session, + DTLS_HT_CLIENT_HELLO, + buf, p - buf, cookie_length != 0); +} + +static int +check_server_hello(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) +{ + dtls_handshake_parameters_t *handshake = &peer->handshake_params; + + /* This function is called when we expect a ServerHello (i.e. we + * have sent a ClientHello). We might instead receive a HelloVerify + * request containing a cookie. If so, we must repeat the + * ClientHello with the given Cookie. + */ + if (data_length < DTLS_HS_LENGTH + DTLS_HS_LENGTH) + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + + update_hs_hash(peer, data, data_length); + + /* FIXME: check data_length before accessing fields */ + + /* Get the server's random data and store selected cipher suite + * and compression method (like dtls_update_parameters(). + * Then calculate master secret and wait for ServerHelloDone. When received, + * send ClientKeyExchange (?) and ChangeCipherSpec + ClientFinished. */ + + /* check server version */ + data += DTLS_HS_LENGTH; + data_length -= DTLS_HS_LENGTH; + + if (dtls_uint16_to_int(data) != DTLS_VERSION) { + DBG("unknown DTLS version"); + return dtls_alert_fatal_create(DTLS_ALERT_PROTOCOL_VERSION); + } + + data += sizeof(uint16); /* skip version field */ + data_length -= sizeof(uint16); + + /* store server random data */ + memcpy(handshake->tmp.random.server, data, DTLS_RANDOM_LENGTH); + /* skip server random */ + data += DTLS_RANDOM_LENGTH; + data_length -= DTLS_RANDOM_LENGTH; + + SKIP_VAR_FIELD(data, data_length, uint8); /* skip session id */ + + /* Check cipher suite. As we offer all we have, it is sufficient + * to check if the cipher suite selected by the server is in our + * list of known cipher suites. Subsets are not supported. */ + handshake->cipher = dtls_uint16_to_int(data); + if (!known_cipher(ctx, handshake->cipher, 1)) { + DBG("unsupported cipher 0x%02x 0x%02x", + data[0], data[1]); + return dtls_alert_fatal_create(DTLS_ALERT_INSUFFICIENT_SECURITY); + } + data += sizeof(uint16); + data_length -= sizeof(uint16); + + /* Check if NULL compression was selected. We do not know any other. */ + if (dtls_uint8_to_int(data) != TLS_COMPRESSION_NULL) { + DBG("unsupported compression method 0x%02x", data[0]); + return dtls_alert_fatal_create(DTLS_ALERT_INSUFFICIENT_SECURITY); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + return dtls_check_tls_extension(peer, data, data_length, 0); + +error: + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); +} + +static int +check_server_hello_verify_request(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) +{ + DBG("check_server_hello_verify_request"); + dtls_hello_verify_t *hv; + int res; + + if (data_length < DTLS_HS_LENGTH + DTLS_HV_LENGTH) + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + + hv = (dtls_hello_verify_t *)(data + DTLS_HS_LENGTH); + + res = dtls_send_client_hello(ctx, peer, hv->cookie, hv->cookie_length); + + if (res < 0) + WARN("cannot send ClientHello"); + + return res; +} + +static int +check_server_certificate(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) +{ + DBG("check_server_certificate"); + int err; + dtls_handshake_parameters_t *config = &peer->handshake_params; + + update_hs_hash(peer, data, data_length); + + assert(config->cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); + + data += DTLS_HS_LENGTH; + + if (dtls_uint24_to_int(data) != 94) { + DBG("expect length of 94 bytes for server certificate message"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint24); + + if (dtls_uint24_to_int(data) != 91) { + DBG("expect length of 91 bytes for certificate"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint24); + + if (memcmp(data, cert_asn1_header, sizeof(cert_asn1_header))) { + DBG("got an unexpected Subject public key format"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(cert_asn1_header); + + memcpy(config->keyx.ecdsa.other_pub_x, data, + sizeof(config->keyx.ecdsa.other_pub_x)); + data += sizeof(config->keyx.ecdsa.other_pub_x); + + memcpy(config->keyx.ecdsa.other_pub_y, data, + sizeof(config->keyx.ecdsa.other_pub_y)); + data += sizeof(config->keyx.ecdsa.other_pub_y); + + err = CALL(ctx, verify_ecdsa_key, &peer->session, + config->keyx.ecdsa.other_pub_x, + config->keyx.ecdsa.other_pub_y, + sizeof(config->keyx.ecdsa.other_pub_x)); + if (err < 0) { + WARN("The certificate was not accepted"); + return err; + } + + return 0; +} + +static int +check_server_key_exchange_ecdsa(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) +{ + DBG("check_server_key_exchange_ecdsa"); + + dtls_handshake_parameters_t *config = &peer->handshake_params; + int i; + unsigned char *result_r; + unsigned char *result_s; + unsigned char *key_params; + + update_hs_hash(peer, data, data_length); + + assert(config->cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); + + data += DTLS_HS_LENGTH; + + if (data_length < DTLS_HS_LENGTH + DTLS_SKEXEC_LENGTH) { + DBG("the package length does not match the expected"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + key_params = data; + + if (dtls_uint8_to_int(data) != 3) { + DBG("Only named curves supported"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + if (dtls_uint16_to_int(data) != 23) { + DBG("secp256r1 supported"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += sizeof(uint16); + data_length -= sizeof(uint16); + + if (dtls_uint8_to_int(data) != 65) { + DBG("expected 65 bytes long public point"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + if (dtls_uint8_to_int(data) != 4) { + DBG("expected uncompressed public point"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + memcpy(config->keyx.ecdsa.other_eph_pub_x, data, sizeof(config->keyx.ecdsa.other_eph_pub_y)); + data += sizeof(config->keyx.ecdsa.other_eph_pub_y); + data_length -= sizeof(config->keyx.ecdsa.other_eph_pub_y); + + memcpy(config->keyx.ecdsa.other_eph_pub_y, data, sizeof(config->keyx.ecdsa.other_eph_pub_y)); + data += sizeof(config->keyx.ecdsa.other_eph_pub_y); + data_length -= sizeof(config->keyx.ecdsa.other_eph_pub_y); + + + if (data_length < dtls_uint16_to_int(data)) { + DBG("signature length wrong"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint16); + data_length -= sizeof(uint16); + + if (dtls_uint8_to_int(data) != 0x30) { + DBG("wrong ASN.1 struct, expected SEQUENCE"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + if (data_length < dtls_uint8_to_int(data)) { + DBG("signature length wrong"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + if (dtls_uint8_to_int(data) != 0x02) { + DBG("wrong ASN.1 struct, expected Integer"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + i = dtls_uint8_to_int(data); + data += sizeof(uint8); + data_length -= sizeof(uint8); + + /* Sometimes these values have a leeding 0 byte */ + result_r = data + i - DTLS_EC_KEY_SIZE; + + data += i; + data_length -= i; + + if (dtls_uint8_to_int(data) != 0x02) { + DBG("wrong ASN.1 struct, expected Integer"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + data += sizeof(uint8); + data_length -= sizeof(uint8); + + i = dtls_uint8_to_int(data); + data += sizeof(uint8); + data_length -= sizeof(uint8); + + /* Sometimes these values have a leeding 0 byte */ + result_s = data + i - DTLS_EC_KEY_SIZE; + + data += i; + data_length -= i; + + i = dtls_ecdsa_verify_sig(config->keyx.ecdsa.other_pub_x, config->keyx.ecdsa.other_pub_y, + sizeof(config->keyx.ecdsa.other_pub_x), + config->tmp.random.client, DTLS_RANDOM_LENGTH, + config->tmp.random.server, DTLS_RANDOM_LENGTH, + key_params, + 1 + 2 + 1 + 1 + (2 * DTLS_EC_KEY_SIZE), + result_r, result_s); + + if (i < 0) { + DBG("wrong signature"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + return 0; +} + +static int +check_server_key_exchange_psk(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) +{ + DBG("check_server_key_exchange_psk"); + dtls_handshake_parameters_t *config = &peer->handshake_params; + int len; + + update_hs_hash(peer, data, data_length); + + assert(config->cipher == TLS_PSK_WITH_AES_128_CCM_8); + + data += DTLS_HS_LENGTH; + + if (data_length < DTLS_HS_LENGTH + DTLS_SKEXECPSK_LENGTH_MIN) { + DBG("the package length does not match the expected"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + + len = dtls_uint16_to_int(data); + data += sizeof(uint16); + + if (len != data_length - DTLS_HS_LENGTH - sizeof(uint16)) { + WARN("the length of the server identity hint is worng"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + + if (len > DTLS_PSK_MAX_CLIENT_IDENTITY_LEN) { + WARN("please use a smaller server identity hint"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + + config->keyx.psk.id_length = len; + memcpy(config->keyx.psk.identity, data, len); + return 0; +} + +static int +check_certificate_request(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) +{ + DBG("check_certificate_request"); + int i; + int auth_alg; + int sig_alg; + int hash_alg; + + update_hs_hash(peer, data, data_length); + + assert(peer->handshake_params.cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); + + data += DTLS_HS_LENGTH; + + if (data_length < DTLS_HS_LENGTH + 5) { + DBG("the package length does not match the expected"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + + i = dtls_uint8_to_int(data); + data += sizeof(uint8); + if (i + 1 > data_length) { + DBG("the cerfificate types are too long"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + + auth_alg = 0; + for (; i > 0 ; i -= sizeof(uint8)) { + if (dtls_uint8_to_int(data) == 64 && auth_alg == 0) + auth_alg = dtls_uint8_to_int(data); + data += sizeof(uint8); + } + + if (auth_alg != 64) { + DBG("the request authentication algorithem is not supproted"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + + i = dtls_uint16_to_int(data); + data += sizeof(uint16); + if (i + 1 > data_length) { + DBG("the signature and hash algorithm list is too long"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + + hash_alg = 0; + sig_alg = 0; + for (; i > 0 ; i -= sizeof(uint16)) { + int current_hash_alg; + int current_sig_alg; + + current_hash_alg = dtls_uint8_to_int(data); + data += sizeof(uint8); + current_sig_alg = dtls_uint8_to_int(data); + data += sizeof(uint8); + + if (current_hash_alg == 4 && hash_alg == 0 && + current_sig_alg == 3 && sig_alg == 0) { + hash_alg = current_hash_alg; + sig_alg = current_sig_alg; + } + } + + if (hash_alg != 4 || sig_alg != 3) { + DBG("no supported hash and signature algorithem"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + + /* common names are ignored */ + + peer->handshake_params.do_client_auth = 1; + return 0; +} + +static int +check_server_hellodone(dtls_context_t *ctx, + dtls_peer_t *peer, + uint8 *data, size_t data_length) +{ + int res; + const dtls_ecdsa_key_t *ecdsa_key; + dtls_handshake_parameters_t *handshake = &peer->handshake_params; + dtls_security_parameters_t *security = &peer->security_params; + DBG("check_server_hellodone"); + /* calculate master key, send CCS */ + + update_hs_hash(peer, data, data_length); + + if (handshake->do_client_auth) { + + res = CALL(ctx, get_ecdsa_key, &peer->session, &ecdsa_key); + if (res < 0) { + DBG("no ecdsa certificate to send in certificate"); + return res; + } + + res = dtls_send_certificate_ecdsa(ctx, peer, ecdsa_key); + + if (res < 0) { + DBG("dtls_server_hello: cannot prepare Certificate record"); + return res; + } + } + + /* send ClientKeyExchange */ + res = dtls_send_client_key_exchange(ctx, peer); + + if (res < 0) { + DBG("cannot send KeyExchange message"); + return res; + } + + if (handshake->do_client_auth) { + + res = dtls_send_certificate_verify_ecdh(ctx, peer, ecdsa_key); + + if (res < 0) { + DBG("dtls_server_hello: cannot prepare Certificate record"); + return res; + } + } + + /* and switch cipher suite */ + res = dtls_send_ccs(ctx, peer); + if (res < 0) { + DBG("cannot send CCS message"); + return res; + } + + res = calculate_key_block(ctx, handshake, security, + &peer->session); + if (res < 0) { + return res; + } + + res = init_cipher(handshake, security, peer->role); + if (res < 0) { + return res; + } + + inc_uint(uint16, peer->epoch); + memset(peer->rseq, 0, sizeof(peer->rseq)); + + dtls_debug_keyblock(security); + + /* Client Finished */ + DBG("send Finished"); + return dtls_send_finished(ctx, peer, PRF_LABEL(client), PRF_LABEL_SIZE(client)); +} + +static int +decrypt_verify(dtls_peer_t *peer, + uint8 *packet, size_t length, + uint8 **cleartext, size_t *clen) { + int ok = 0; + dtls_security_parameters_t *security = &peer->security_params; + + *cleartext = (uint8 *)packet + sizeof(dtls_record_header_t); + *clen = length - sizeof(dtls_record_header_t); + + if (security->cipher == TLS_NULL_WITH_NULL_NULL) { + /* no cipher suite selected */ + return 1; + } else { /* TLS_PSK_WITH_AES_128_CCM_8 */ + dtls_cipher_context_t *cipher_context; + /** + * length of additional_data for the AEAD cipher which consists of + * seq_num(2+6) + type(1) + version(2) + length(2) + */ +#define A_DATA_LEN 13 + unsigned char nonce[DTLS_CCM_BLOCKSIZE]; + unsigned char A_DATA[A_DATA_LEN]; + long int len; + + + if (*clen < 16) /* need at least IV and MAC */ + return -1; + + memset(nonce, 0, DTLS_CCM_BLOCKSIZE); + memcpy(nonce, dtls_kb_remote_iv(security, peer->role), + dtls_kb_iv_size(security, peer->role)); + + /* read epoch and seq_num from message */ + memcpy(nonce + dtls_kb_iv_size(security, peer->role), *cleartext, 8); + *cleartext += 8; + *clen -= 8; + + cipher_context = security->read_cipher; + + if (!cipher_context) { + WARN("no read_cipher available!"); + return 0; + } + + dtls_dsrv_hexdump_log(LOG_DEBUG, "nonce", nonce, DTLS_CCM_BLOCKSIZE, 0); + dtls_dsrv_hexdump_log(LOG_DEBUG, "key", + dtls_kb_remote_write_key(security, peer->role), + dtls_kb_key_size(security, peer->role), 0); + dtls_dsrv_hexdump_log(LOG_DEBUG, "ciphertext", *cleartext, *clen, 0); + + /* re-use N to create additional data according to RFC 5246, Section 6.2.3.3: + * + * additional_data = seq_num + TLSCompressed.type + + * TLSCompressed.version + TLSCompressed.length; + */ + memcpy(A_DATA, &DTLS_RECORD_HEADER(packet)->epoch, 8); /* epoch and seq_num */ + memcpy(A_DATA + 8, &DTLS_RECORD_HEADER(packet)->content_type, 3); /* type and version */ + dtls_int_to_uint16(A_DATA + 11, *clen - 8); /* length without nonce_explicit */ + + len = dtls_decrypt(cipher_context, *cleartext, *clen, *cleartext, nonce, + A_DATA, A_DATA_LEN); + + ok = len >= 0; + if (!ok) + WARN("decryption failed"); + else { +#ifndef NDEBUG + printf("decrypt_verify(): found %ld bytes cleartext", len); +#endif + *clen = len; + } + dtls_dsrv_hexdump_log(LOG_DEBUG, "cleartext", *cleartext, *clen, 0); + } + + return ok; +} + +static int +dtls_send_hello_request(dtls_context_t *ctx, dtls_peer_t *peer) +{ + return dtls_send_handshake_msg_hash(ctx, peer, &peer->session, + DTLS_HT_HELLO_REQUEST, + NULL, 0, 0); +} + +int +dtls_renegotiate(dtls_context_t *ctx, const session_t *dst) +{ + dtls_peer_t *peer = NULL; + int err; + + peer = dtls_get_peer(ctx, dst); + + if (!peer) { + return -1; + } + if (peer->state != DTLS_STATE_CONNECTED) + return -1; + + if (peer->role == DTLS_CLIENT) { + /* send ClientHello with empty Cookie */ + err = dtls_send_client_hello(ctx, peer, NULL, 0); + if (err < 0) + WARN("cannot send ClientHello"); + else + peer->state = DTLS_STATE_CLIENTHELLO; + return err; + } else if (peer->role == DTLS_SERVER) { + return dtls_send_hello_request(ctx, peer); + } + + return -1; +} + +static int +handle_handshake(dtls_context_t *ctx, dtls_peer_t *peer, session_t *session, + const dtls_peer_type role, const dtls_state_t state, + uint8 *record_header, uint8 *data, size_t data_length) { + + int err = 0; + + if (data_length < DTLS_HS_LENGTH) { + WARN("handshake message too short"); + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + } + + if (!peer && data[0] != DTLS_HT_CLIENT_HELLO) { + WARN("If there is no peer only ClientHello is allowed"); + return dtls_alert_fatal_create(DTLS_ALERT_HANDSHAKE_FAILURE); + } + /* The following switch construct handles the given message with + * respect to the current internal state for this peer. In case of + * error, it is left with return 0. */ + + switch (data[0]) { + + /************************************************************************ + * Client states + ************************************************************************/ + case DTLS_HT_HELLO_VERIFY_REQUEST: + + DBG("DTLS_HT_HELLO_VERIFY_REQUEST"); + if (state != DTLS_STATE_CLIENTHELLO) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + err = check_server_hello_verify_request(ctx, peer, data, data_length); + if (err < 0) { + WARN("error in check_server_hello_verify_request err: %i", err); + return err; + } + + break; + case DTLS_HT_SERVER_HELLO: + + DBG("DTLS_HT_SERVER_HELLO"); + if (state != DTLS_STATE_CLIENTHELLO) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + err = check_server_hello(ctx, peer, data, data_length); + if (err < 0) { + WARN("error in check_server_hello err: %i", err); + return err; + } + if (peer->handshake_params.cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) + peer->state = DTLS_STATE_WAIT_SERVERCERTIFICATE; + else + peer->state = DTLS_STATE_WAIT_SERVERHELLODONE; + /* update_hs_hash(peer, data, data_length); */ + + break; + + case DTLS_HT_CERTIFICATE: + DBG("DTLS_HT_CERTIFICATE"); + + if ((role == DTLS_CLIENT && state != DTLS_STATE_WAIT_SERVERCERTIFICATE) || + (role == DTLS_SERVER && state != DTLS_STATE_WAIT_CLIENTCERTIFICATE)) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + err = check_server_certificate(ctx, peer, data, data_length); + if (err < 0) { + WARN("error in check_server_certificate err: %i", err); + return err; + } + if (role == DTLS_CLIENT) { + peer->state = DTLS_STATE_WAIT_SERVERKEYEXCHANGE; + } else if (role == DTLS_SERVER){ + peer->state = DTLS_STATE_WAIT_CLIENTKEYEXCHANGE; + } + /* update_hs_hash(peer, data, data_length); */ + + break; + + case DTLS_HT_SERVER_KEY_EXCHANGE: + + DBG("DTLS_HT_SERVER_KEY_EXCHANGE"); + + if (peer->handshake_params.cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) { + DBG("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"); + if (state != DTLS_STATE_WAIT_SERVERKEYEXCHANGE) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + err = check_server_key_exchange_ecdsa(ctx, peer, data, data_length); + } else if (peer->handshake_params.cipher == TLS_PSK_WITH_AES_128_CCM_8) { + DBG("TLS_PSK_WITH_AES_128_CCM_8"); + if (state != DTLS_STATE_WAIT_SERVERHELLODONE) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + err = check_server_key_exchange_psk(ctx, peer, data, data_length); + } else { + assert(0); + } + + if (err < 0) { + WARN("error in check_server_key_exchange err: %i", err); + return err; + } + peer->state = DTLS_STATE_WAIT_SERVERHELLODONE; + /* update_hs_hash(peer, data, data_length); */ + DBG("Set state to DTLS_STATE_WAIT_SERVERHELLODONE"); + break; + + case DTLS_HT_SERVER_HELLO_DONE: + + DBG("DTLS_HT_SERVER_HELLO_DONE"); + if (state != DTLS_STATE_WAIT_SERVERHELLODONE) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + err = check_server_hellodone(ctx, peer, data, data_length); + if (err < 0) { + WARN("error in check_server_hellodone err: %i", err); + return err; + } + peer->state = DTLS_STATE_WAIT_SERVERFINISHED; + /* update_hs_hash(peer, data, data_length); */ + + break; + + case DTLS_HT_CERTIFICATE_REQUEST: + + DBG("DTLS_HT_CERTIFICATE_REQUEST"); + if (state != DTLS_STATE_WAIT_SERVERHELLODONE) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + err = check_certificate_request(ctx, peer, data, data_length); + if (err < 0) { + WARN("error in check_certificate_request err: %i", err); + return err; + } + + break; + + case DTLS_HT_FINISHED: + /* expect a Finished message from server */ + + DBG("DTLS_HT_FINISHED"); + if ((role == DTLS_CLIENT && state != DTLS_STATE_WAIT_SERVERFINISHED) || + (role == DTLS_SERVER && state != DTLS_STATE_WAIT_FINISHED)) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + err = check_finished(ctx, peer, record_header, data, data_length); + if (err < 0) { + WARN("error in check_finished err: %i", err); + return err; + } + if (role == DTLS_SERVER) { + /* send ServerFinished */ + update_hs_hash(peer, data, data_length); + + if (dtls_send_finished(ctx, peer, PRF_LABEL(server), + PRF_LABEL_SIZE(server)) > 0) { + peer->state = DTLS_STATE_CONNECTED; + } else if (role == DTLS_CLIENT) { + WARN("sending server Finished failed"); + } + } else { + peer->state = DTLS_STATE_CONNECTED; + } + + break; + + /************************************************************************ + * Server states + ************************************************************************/ + + case DTLS_HT_CLIENT_KEY_EXCHANGE: + /* handle ClientHello, update msg and msglen and goto next if not finished */ + + DBG("DTLS_HT_CLIENT_KEY_EXCHANGE"); + + if (state != DTLS_STATE_WAIT_CLIENTKEYEXCHANGE) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + err = check_client_keyexchange(ctx, &peer->handshake_params, data, data_length); + if (err < 0) { + WARN("error in check_client_keyexchange err: %i", err); + return err; + } + update_hs_hash(peer, data, data_length); + + if (peer->handshake_params.cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 && + ctx && ctx->h && ctx->h->verify_ecdsa_key) + peer->state = DTLS_STATE_WAIT_CERTIFICATEVERIFY; + else + peer->state = DTLS_STATE_WAIT_CLIENTCHANGECIPHERSPEC; + break; + + case DTLS_HT_CERTIFICATE_VERIFY: + + DBG("DTLS_HT_CERTIFICATE_VERIFY"); + if (state != DTLS_STATE_WAIT_CERTIFICATEVERIFY) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + err = check_client_certificate_verify(ctx, peer, data, data_length); + if (err < 0) { + WARN("error in check_client_certificate_verify err: %i", err); + return err; + } + + update_hs_hash(peer, data, data_length); + peer->state = DTLS_STATE_WAIT_CLIENTCHANGECIPHERSPEC; + break; + + case DTLS_HT_CLIENT_HELLO: + /* At this point, we have a good relationship with this peer. This + * state is left for re-negotiation of key material. */ + + DBG("DTLS_HT_CLIENT_HELLO"); + if ((peer && state != DTLS_STATE_CONNECTED) || + (!peer && state != DTLS_STATE_WAIT_CLIENTHELLO)) { + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + /* When no DTLS state exists for this peer, we only allow a + Client Hello message with + + a) a valid cookie, or + b) no cookie. + + 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, record_header, data, + data_length); + if (err < 0) { + WARN("error in dtls_verify_peer err: %i", err); + return err; + } + + if (err > 0) { + DBG("server hello verify was sent"); + return err; + } + + if (!peer) { + + /* msg contains a Client Hello with a valid cookie, so we can + * safely create the server state machine and continue with + * the handshake. */ + peer = dtls_new_peer(session); + if (!peer) { + DBG("cannot create peer"); + return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); + } + peer->role = DTLS_SERVER; + + /* Initialize record sequence number to 1 for new peers. The first + * record with sequence number 0 is a stateless Hello Verify Request. + */ + peer->rseq[5] = 1; + dtls_add_peer(ctx, peer); + } + + clear_hs_hash(peer); + + /* First negotiation step: check for PSK + * + * Note that we already have checked that msg is a Handshake + * message containing a ClientHello. dtls_get_cipher() therefore + * does not check again. + */ + err = dtls_update_parameters(ctx, peer, data, data_length); + if (err < 0) { + + WARN("error updating security parameters"); + return err; + } + + /* update finish MAC */ + update_hs_hash(peer, data, data_length); + + err = dtls_send_server_hello_msgs(ctx, peer); + if (err < 0) { + return err; + } + if (peer->handshake_params.cipher == TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 && + ctx && ctx->h && ctx->h->verify_ecdsa_key) + peer->state = DTLS_STATE_WAIT_CLIENTCERTIFICATE; + else + peer->state = DTLS_STATE_WAIT_CLIENTKEYEXCHANGE; + + /* after sending the ServerHelloDone, we expect the + * ClientKeyExchange (possibly containing the PSK id), + * followed by a ChangeCipherSpec and an encrypted Finished. + */ + + break; + + case DTLS_HT_HELLO_REQUEST: + + DBG("DTLS_HT_HELLO_REQUEST"); + if (state != DTLS_STATE_CONNECTED) { + /* we should just ignore such packages when in handshake */ + return 0; + } + + /* send ClientHello with empty Cookie */ + err = dtls_send_client_hello(ctx, peer, NULL, 0); + if (err < 0) + WARN("cannot send ClientHello"); + else + peer->state = DTLS_STATE_CLIENTHELLO; + + return err; + default: + DBG("unhandled message %d", data[0]); + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + return err; +} + +static int +handle_ccs(dtls_context_t *ctx, dtls_peer_t *peer, + uint8 *record_header, uint8 *data, size_t data_length) +{ + int err; + dtls_handshake_parameters_t *handshake = &peer->handshake_params; + dtls_security_parameters_t *security = &peer->security_params; + + /* A CCS message is handled after a KeyExchange message was + * received from the client. When security parameters have been + * updated successfully and a ChangeCipherSpec message was sent + * by ourself, the security context is switched and the record + * sequence number is reset. */ + + if (!peer || peer->state != DTLS_STATE_WAIT_CLIENTCHANGECIPHERSPEC) { + WARN("expected ChangeCipherSpec during handshake"); + return dtls_alert_fatal_create(DTLS_ALERT_UNEXPECTED_MESSAGE); + } + + if (data_length < 1 || data[0] != 1) + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + + /* send change cipher spec message and switch to new configuration */ + err = dtls_send_ccs(ctx, peer); + if (err < 0) { + WARN("cannot send CCS message"); + return err; + } + + err = calculate_key_block(ctx, handshake, security, + &peer->session); + if (err < 0) { + return err; + } + + err = init_cipher(handshake, security, peer->role); + if (err < 0) { + return err; + } + + inc_uint(uint16, peer->epoch); + memset(peer->rseq, 0, sizeof(peer->rseq)); + + peer->state = DTLS_STATE_WAIT_FINISHED; + + dtls_debug_keyblock(security); + + return 0; +} + +/** + * Handles incoming Alert messages. This function returns \c 1 if the + * connection should be closed and the peer is to be invalidated. + */ +static int +handle_alert(dtls_context_t *ctx, dtls_peer_t *peer, + uint8 *record_header, uint8 *data, size_t data_length) { + int free_peer = 0; /* indicates whether to free peer */ + + if (data_length < 2) + return dtls_alert_fatal_create(DTLS_ALERT_DECODE_ERROR); + + INFO("** Alert: level %d, description %d", data[0], data[1]); + + if (!peer) { + WARN("got an alert for an unknown peer, we probably already removed it, ignore it"); + return 0; + } + + /* The peer object is invalidated for FATAL alerts and close + * notifies. This is done in two steps.: First, remove the object + * from our list of peers. After that, the event handler callback is + * invoked with the still existing peer object. Finally, the storage + * used by peer is released. + */ + if (data[0] == DTLS_ALERT_LEVEL_FATAL || data[1] == DTLS_ALERT_CLOSE_NOTIFY) { + DBG("%d invalidate peer", data[1]); + +#ifndef WITH_CONTIKI + HASH_DEL_PEER(ctx->peers, peer); +#else /* WITH_CONTIKI */ + list_remove(ctx->peers, peer); + +#ifndef NDEBUG + PRINTF("removed peer ["); + PRINT6ADDR(&peer->session.addr); + PRINTF("]:%d", uip_ntohs(peer->session.port)); +#endif +#endif /* WITH_CONTIKI */ + + free_peer = 1; + + } + + (void)CALL(ctx, event, &peer->session, + (dtls_alert_level_t)data[0], (unsigned short)data[1]); + switch (data[1]) { + case DTLS_ALERT_CLOSE_NOTIFY: + /* If state is DTLS_STATE_CLOSING, we have already sent a + * close_notify so, do not send that again. */ + if (peer->state != DTLS_STATE_CLOSING) { + peer->state = DTLS_STATE_CLOSING; + dtls_alert(ctx, peer, DTLS_ALERT_LEVEL_FATAL, DTLS_ALERT_CLOSE_NOTIFY); + } else + peer->state = DTLS_STATE_CLOSED; + break; + default: + ; + } + + if (free_peer) { + dtls_stop_retransmission(ctx, peer); + dtls_destory_peer(ctx, peer, 0); + } + + return free_peer; +} + +static int dtls_alert_send_from_err(dtls_context_t *ctx, dtls_peer_t *peer, + session_t *session, int err) +{ + int level; + int desc; + + if (err < -(1 << 8) && err > -(3 << 8)) { + level = ((-err) & 0xff00) >> 8; + desc = (-err) & 0xff; + if (!peer) { + peer = dtls_get_peer(ctx, session); + } + if (peer) { + return dtls_alert(ctx, peer, level, desc); + } + } else if (err == -1) { + if (!peer) { + peer = dtls_get_peer(ctx, session); + } + if (peer) { + return dtls_alert(ctx, peer, DTLS_ALERT_LEVEL_FATAL, DTLS_ALERT_INTERNAL_ERROR); + } + } + return -1; +} + +/** + * Handles incoming data as DTLS message from given peer. + */ +int +dtls_handle_message(dtls_context_t *ctx, + session_t *session, + uint8 *msg, int msglen) { + dtls_peer_t *peer = NULL; + unsigned int rlen; /* record length */ + uint8 *data; /* (decrypted) payload */ + size_t data_length; /* length of decrypted payload + (without MAC and padding) */ + int err; + + /* check if we have DTLS state for addr/port/ifindex */ + peer = dtls_get_peer(ctx, session); + + if (!peer) { + DBG("dtls_handle_message: PEER NOT FOUND"); + dtls_dsrv_log_addr(LOG_DEBUG, "peer addr", session); + } else { + DBG("dtls_handle_message: FOUND PEER"); + } + + /* FIXME: check sequence number of record and drop message if the + * number is not exactly the last number that we have responded to + 1. + * Otherwise, stop retransmissions for this specific peer and + * continue processing. */ + if (peer) { + dtls_stop_retransmission(ctx, peer); + } + + while ((rlen = is_record(msg,msglen))) { + dtls_peer_type role; + dtls_state_t state; + + DBG("got packet %d (%d bytes)", msg[0], rlen); + if (peer) { + /* skip packet if it is from a different epoch */ + if (memcmp(DTLS_RECORD_HEADER(msg)->epoch, + peer->epoch, sizeof(uint16)) != 0) + goto next; + + if (!decrypt_verify(peer, msg, rlen, &data, &data_length)) { + INFO("decrypt_verify() failed"); + goto next; + } + role = peer->role; + state = peer->state; + } else { + /* is_record() ensures that msg contains at least a record header */ + data = msg + DTLS_RH_LENGTH; + data_length = rlen - DTLS_RH_LENGTH; + state = DTLS_STATE_WAIT_CLIENTHELLO; + role = DTLS_SERVER; + } + + dtls_dsrv_hexdump_log(LOG_DEBUG, "receive header", msg, + sizeof(dtls_record_header_t), 1); + dtls_dsrv_hexdump_log(LOG_DEBUG, "receive unencrypted", data, data_length, 1); + + /* Handle received record according to the first byte of the + * message, i.e. the subprotocol. We currently do not support + * combining multiple fragments of one type into a single + * record. */ + + switch (msg[0]) { + + case DTLS_CT_CHANGE_CIPHER_SPEC: + err = handle_ccs(ctx, peer, msg, data, data_length); + if (err < 0) { + WARN("error while handling ChangeCipherSpec package"); + dtls_alert_send_from_err(ctx, peer, session, err); + return err; + } + break; + + case DTLS_CT_ALERT: + err = handle_alert(ctx, peer, msg, data, data_length); + if (err < 0) { + WARN("received wrong package"); + /* handle alert has invalidated peer */ + peer = NULL; + return err; + } + break; + + case DTLS_CT_HANDSHAKE: + err = handle_handshake(ctx, peer, session, role, state, msg, data, data_length); + DBG("Returned from handle_handshake: %d",err); + if (err < 0) { + WARN("error while handling handshake package"); + dtls_alert_send_from_err(ctx, peer, session, err); + return err; + } + if (peer && peer->state == DTLS_STATE_CONNECTED) { + DBG("DTLS_STATE_CONNECTED"); + /* stop retransmissions */ + dtls_stop_retransmission(ctx, peer); + CALL(ctx, event, &peer->session, 0, DTLS_EVENT_CONNECTED); + } + break; + + case DTLS_CT_APPLICATION_DATA: + INFO("** application data:"); + if (!peer) { + WARN("no peer available, send an alert"); + dtls_alert(ctx, peer, DTLS_ALERT_LEVEL_FATAL, DTLS_ALERT_UNEXPECTED_MESSAGE); + return -1; + } + CALL(ctx, read, &peer->session, data, data_length); + break; + default: + INFO("dropped unknown message of type %d",msg[0]); + } + + next: + /* advance msg by length of ciphertext */ + msg += rlen; + msglen -= rlen; + } + DBG("Leaving dtls_handle_message"); + return 0; +} + +dtls_context_t * +dtls_new_context(void *app_data) { + dtls_context_t *c; + dtls_tick_t now; +#ifndef WITH_CONTIKI + FILE *urandom = fopen("/dev/urandom", "r"); + unsigned char buf[sizeof(unsigned long)]; +#endif /* WITH_CONTIKI */ + + dtls_ticks(&now); +#ifdef WITH_CONTIKI + /* FIXME: need something better to init PRNG here */ + prng_init(now); +#else /* WITH_CONTIKI */ +/* + if (!urandom) { + DBG("cannot initialize PRNG"); + return NULL; + } + + if (fread(buf, 1, sizeof(buf), urandom) != sizeof(buf)) { + dsrv_log(LOG_EMERG, "cannot initialize PRNG"); + return NULL; + } + + fclose(urandom); + prng_init((unsigned long)*buf); + */ + // just randomly flip some bits + unsigned long mask = 0x0000; + for(int i=0; i<sizeof(buf); i++) { + // XXX need to flip some bits + } + prng_init((unsigned long)*buf); +#endif /* WITH_CONTIKI */ + + c = &the_dtls_context; + + memset(c, 0, sizeof(dtls_context_t)); + c->app = app_data; + + LIST_STRUCT_INIT(c, sendqueue); + +#ifdef WITH_CONTIKI + LIST_STRUCT_INIT(c, peers); + /* LIST_STRUCT_INIT(c, key_store); */ + + process_start(&dtls_retransmit_process, (char *)c); + PROCESS_CONTEXT_BEGIN(&dtls_retransmit_process); + /* the retransmit timer must be initialized to some large value */ + etimer_set(&c->retransmit_timer, 0xFFFF); + PROCESS_CONTEXT_END(&coap_retransmit_process); +#endif /* WITH_CONTIKI */ + + if (prng(c->cookie_secret, DTLS_COOKIE_SECRET_LENGTH)) + c->cookie_secret_age = now; + else + goto error; + + return c; + + error: + DBG("cannot create DTLS context"); + if (c) + dtls_free_context(c); + return NULL; +} + +void dtls_free_context(dtls_context_t *ctx) { + dtls_peer_t *p; + +#ifndef WITH_CONTIKI + dtls_peer_t *tmp; + + if (ctx->peers) { + HASH_ITER(hh, ctx->peers, p, tmp) { + dtls_destory_peer(ctx, p, 1); + } + } +#else /* WITH_CONTIKI */ + for (p = list_head(ctx->peers); p; p = list_item_next(p)) + dtls_destory_peer(ctx, p, 1); +#endif /* WITH_CONTIKI */ +} + +int +dtls_connect_peer(dtls_context_t *ctx, dtls_peer_t *peer) { + int res; + + assert(peer); + if (!peer) + return -1; + + /* check if the same peer is already in our list */ + if (peer == dtls_get_peer(ctx, &peer->session)) { + DBG("found peer, try to re-connect"); + /* FIXME: send HelloRequest if we are server, + ClientHello with good cookie if client */ + return 0; + } + + /* set peer role to server: */ + peer->role = DTLS_CLIENT; + + dtls_add_peer(ctx, peer); + + /* send ClientHello with empty Cookie */ + res = dtls_send_client_hello(ctx, peer, NULL, 0); + if (res < 0) + WARN("cannot send ClientHello"); + else + peer->state = DTLS_STATE_CLIENTHELLO; + + return res; +} + +int +dtls_connect(dtls_context_t *ctx, const session_t *dst) { + dtls_peer_t *peer; + + peer = dtls_get_peer(ctx, dst); + + if (!peer) + peer = dtls_new_peer(dst); + + if (!peer) { + DBG("cannot create new peer"); + return -1; + } + + return dtls_connect_peer(ctx, peer); +} + +static void +dtls_retransmit(dtls_context_t *context, netq_t *node) { + if (!context || !node) + return; + + /* re-initialize timeout when maximum number of retransmissions are not reached yet */ + if (node->retransmit_cnt < DTLS_DEFAULT_MAX_RETRANSMIT) { + unsigned char sendbuf[DTLS_MAX_BUF]; + size_t len = sizeof(sendbuf); + + node->retransmit_cnt++; + node->t += (node->timeout << node->retransmit_cnt); + netq_insert_node((netq_t **)context->sendqueue, node); + + DBG("** retransmit packet"); + + if (dtls_prepare_record(node->peer, DTLS_CT_HANDSHAKE, + (uint8 **)&(node->data), &(node->length), 1, + sendbuf, &len) > 0) { + + dtls_dsrv_hexdump_log(LOG_DEBUG, "retransmit header", sendbuf, + sizeof(dtls_record_header_t), 1); + dtls_dsrv_hexdump_log(LOG_DEBUG, "retransmit unencrypted", node->data, + node->length, 1); + + (void)CALL(context, write, &node->peer->session, sendbuf, len); + } + return; + } + + /* no more retransmissions, remove node from system */ + + DBG("** removed transaction"); + + /* And finally delete the node */ + netq_node_free(node); +} + +static void +dtls_stop_retransmission(dtls_context_t *context, dtls_peer_t *peer) { + void *node; + node = list_head((list_t)context->sendqueue); + + while (node) { + if (dtls_session_equals(&((netq_t *)node)->peer->session, + &peer->session)) { + void *tmp = node; + node = list_item_next(node); + list_remove((list_t)context->sendqueue, tmp); + netq_node_free((netq_t *)tmp); + } else + node = list_item_next(node); + } +} + +void +dtls_check_retransmit(dtls_context_t *context, clock_time_t *next) { + dtls_tick_t now; + netq_t *node = netq_head((netq_t **)context->sendqueue); + + dtls_ticks(&now); + while (node && node->t <= now) { + netq_pop_first((netq_t **)context->sendqueue); + dtls_retransmit(context, node); + node = netq_head((netq_t **)context->sendqueue); + } + + if (next && node) + *next = node->t; +} + +#ifdef WITH_CONTIKI +/*---------------------------------------------------------------------------*/ +/* message retransmission */ +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(dtls_retransmit_process, ev, data) +{ + clock_time_t now; + netq_t *node; + + PROCESS_BEGIN(); + + DBG("Started DTLS retransmit process\r"); + + while(1) { + PROCESS_YIELD(); + if (ev == PROCESS_EVENT_TIMER) { + if (etimer_expired(&the_dtls_context.retransmit_timer)) { + + node = list_head(the_dtls_context.sendqueue); + + now = clock_time(); + while (node && node->t <= now) { + dtls_retransmit(&the_dtls_context, list_pop(the_dtls_context.sendqueue)); + node = list_head(the_dtls_context.sendqueue); + } + + /* need to set timer to some value even if no nextpdu is available */ + etimer_set(&the_dtls_context.retransmit_timer, + node ? node->t - now : 0xFFFF); + } + } + } + + PROCESS_END(); +} +#endif /* WITH_CONTIKI */