/* dtls -- a very basic DTLS implementation * * Copyright (C) 2011--2013 Olaf Bergmann * Copyright (C) 2013 Hauke Mehrtens * * 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. */ /** * @file dtls.h * @brief High level DTLS API and visible structures. */ #ifndef _DTLS_DTLS_H_ #define _DTLS_DTLS_H_ #include #include "t_list.h" #include "state.h" #include "peer.h" #ifndef WITH_CONTIKI #include "uthash.h" #include "t_list.h" #endif /* WITH_CONTIKI */ #include "alert.h" #include "crypto.h" #include "hmac.h" #include "global.h" #include "dtls_time.h" #ifndef DTLSv12 #define DTLS_VERSION 0xfeff /* DTLS v1.1 */ #else #define DTLS_VERSION 0xfefd /* DTLS v1.2 */ #endif typedef enum dtls_credentials_type_t { DTLS_PSK_HINT, DTLS_PSK_IDENTITY, DTLS_PSK_KEY } dtls_credentials_type_t; typedef struct dtls_ecdsa_key_t { dtls_ecdh_curve curve; const unsigned char *priv_key; /** < private key as bytes > */ const unsigned char *pub_key_x; /** < x part of the public key for the given private key > */ const unsigned char *pub_key_y; /** < y part of the public key for the given private key > */ } dtls_ecdsa_key_t; /** Length of the secret that is used for generating Hello Verify cookies. */ #define DTLS_COOKIE_SECRET_LENGTH 12 struct dtls_context_t; /** * This structure contains callback functions used by tinydtls to * communicate with the application. At least the write function must * be provided. It is called by the DTLS state machine to send packets * over the network. The read function is invoked to deliver decrypted * and verfified application data. The third callback is an event * handler function that is called when alert messages are encountered * or events generated by the library have occured. */ typedef struct { /** * Called from dtls_handle_message() to send DTLS packets over the * network. The callback function must use the network interface * denoted by session->ifindex to send the data. * * @param ctx The current DTLS context. * @param session The session object, including the address of the * remote peer where the data shall be sent. * @param buf The data to send. * @param len The actual length of @p buf. * @return The callback function must return the number of bytes * that were sent, or a value less than zero to indicate an * error. */ int (*write)(struct dtls_context_t *ctx, session_t *session, uint8 *buf, size_t len); /** * Called from dtls_handle_message() deliver application data that was * received on the given session. The data is delivered only after * decryption and verification have succeeded. * * @param ctx The current DTLS context. * @param session The session object, including the address of the * data's origin. * @param buf The received data packet. * @param len The actual length of @p buf. * @return ignored */ int (*read)(struct dtls_context_t *ctx, session_t *session, uint8 *buf, size_t len); /** * The event handler is called when a message from the alert * protocol is received or the state of the DTLS session changes. * * @param ctx The current dtls context. * @param session The session object that was affected. * @param level The alert level or @c 0 when an event ocurred that * is not an alert. * @param code Values less than @c 256 indicate alerts, while * @c 256 or greater indicate internal DTLS session changes. * @return ignored */ int (*event)(struct dtls_context_t *ctx, session_t *session, dtls_alert_level_t level, unsigned short code); #ifdef DTLS_PSK /** * Called during handshake to get information related to the * psk key exchange. The type of information requested is * indicated by @p type which will be one of DTLS_PSK_HINT, * DTLS_PSK_IDENTITY, or DTLS_PSK_KEY. The called function * must store the requested item in the buffer @p result of * size @p result_length. On success, the function must return * the actual number of bytes written to @p result, of a * value less than zero on error. The parameter @p desc may * contain additional request information (e.g. the psk_identity * for which a key is requested when @p type == @c DTLS_PSK_KEY. * * @param ctx The current dtls context. * @param session The session where the key will be used. * @param type The type of the requested information. * @param desc Additional request information * @param desc_len The actual length of desc. * @param result Must be filled with the requested information. * @param result_length Maximum size of @p result. * @return The number of bytes written to @p result or a value * less than zero on error. */ int (*get_psk_info)(struct dtls_context_t *ctx, const session_t *session, dtls_credentials_type_t type, const unsigned char *desc, size_t desc_len, unsigned char *result, size_t result_length); #endif /* DTLS_PSK */ #ifdef DTLS_ECC /** * Called during handshake to get the server's or client's ecdsa * key used to authenticate this server or client in this * session. If found, the key must be stored in @p result and * the return value must be @c 0. If not found, @p result is * undefined and the return value must be less than zero. * * If ECDSA should not be supported, set this pointer to NULL. * * Implement this if you want to provide your own certificate to * the other peer. This is mandatory for a server providing ECDSA * support and optional for a client. A client doing DTLS client * authentication has to implementing this callback. * * @param ctx The current dtls context. * @param session The session where the key will be used. * @param result Must be set to the key object to used for the given * session. * @return @c 0 if result is set, or less than zero on error. */ int (*get_ecdsa_key)(struct dtls_context_t *ctx, const session_t *session, const dtls_ecdsa_key_t **result); /** * Called during handshake to check the peer's pubic key in this * session. If the public key matches the session and should be * considerated valid the return value must be @c 0. If not valid, * the return value must be less than zero. * * If ECDSA should not be supported, set this pointer to NULL. * * Implement this if you want to verify the other peers public key. * This is mandatory for a DTLS client doing based ECDSA * authentication. A server implementing this will request the * client to do DTLS client authentication. * * @param ctx The current dtls context. * @param session The session where the key will be used. * @param other_pub_x x component of the public key. * @param other_pub_y y component of the public key. * @return @c 0 if public key matches, or less than zero on error. * error codes: * return dtls_alert_fatal_create(DTLS_ALERT_BAD_CERTIFICATE); * return dtls_alert_fatal_create(DTLS_ALERT_UNSUPPORTED_CERTIFICATE); * return dtls_alert_fatal_create(DTLS_ALERT_CERTIFICATE_REVOKED); * return dtls_alert_fatal_create(DTLS_ALERT_CERTIFICATE_EXPIRED); * return dtls_alert_fatal_create(DTLS_ALERT_CERTIFICATE_UNKNOWN); * return dtls_alert_fatal_create(DTLS_ALERT_UNKNOWN_CA); */ int (*verify_ecdsa_key)(struct dtls_context_t *ctx, const session_t *session, const unsigned char *other_pub_x, const unsigned char *other_pub_y, size_t key_size); #endif /* DTLS_ECC */ } dtls_handler_t; /** Holds global information of the DTLS engine. */ typedef struct dtls_context_t { unsigned char cookie_secret[DTLS_COOKIE_SECRET_LENGTH]; clock_time_t cookie_secret_age; /**< the time the secret has been generated */ #ifndef WITH_CONTIKI dtls_peer_t *peers; /**< peer hash map */ #else /* WITH_CONTIKI */ LIST_STRUCT(peers); struct etimer retransmit_timer; /**< fires when the next packet must be sent */ #endif /* WITH_CONTIKI */ LIST_STRUCT(sendqueue); /**< the packets to send */ void *app; /**< application-specific data */ dtls_handler_t *h; /**< callback handlers */ unsigned char readbuf[DTLS_MAX_BUF]; } dtls_context_t; /** * This function initializes the tinyDTLS memory management and must * be called first. */ void dtls_init(); /** * Creates a new context object. The storage allocated for the new * object must be released with dtls_free_context(). */ dtls_context_t *dtls_new_context(void *app_data); /** Releases any storage that has been allocated for \p ctx. */ void dtls_free_context(dtls_context_t *ctx); #define dtls_set_app_data(CTX,DATA) ((CTX)->app = (DATA)) #define dtls_get_app_data(CTX) ((CTX)->app) /** Sets the callback handler object for @p ctx to @p h. */ static inline void dtls_set_handler(dtls_context_t *ctx, dtls_handler_t *h) { ctx->h = h; } /** * Establishes a DTLS channel with the specified remote peer @p dst. * This function returns @c 0 if that channel already exists, a value * greater than zero when a new ClientHello message was sent, and * a value less than zero on error. * * @param ctx The DTLS context to use. * @param dst The remote party to connect to. * @return A value less than zero on error, greater or equal otherwise. */ int dtls_connect(dtls_context_t *ctx, const session_t *dst); /** * Establishes a DTLS channel with the specified remote peer. * This function returns @c 0 if that channel already exists, a value * greater than zero when a new ClientHello message was sent, and * a value less than zero on error. * * @param ctx The DTLS context to use. * @param peer The peer object that describes the session. * @return A value less than zero on error, greater or equal otherwise. */ int dtls_connect_peer(dtls_context_t *ctx, dtls_peer_t *peer); /** * Closes the DTLS connection associated with @p remote. This function * returns zero on success, and a value less than zero on error. */ int dtls_close(dtls_context_t *ctx, const session_t *remote); int dtls_renegotiate(dtls_context_t *ctx, const session_t *dst); /** * Writes the application data given in @p buf to the peer specified * by @p session. * * @param ctx The DTLS context to use. * @param session The remote transport address and local interface. * @param buf The data to write. * @param len The actual length of @p data. * * @return The number of bytes written or @c -1 on error. */ int dtls_write(struct dtls_context_t *ctx, session_t *session, uint8 *buf, size_t len); /** * Checks sendqueue of given DTLS context object for any outstanding * packets to be transmitted. * * @param context The DTLS context object to use. * @param next If not NULL, @p next is filled with the timestamp * of the next scheduled retransmission, or @c 0 when no packets are * waiting. */ void dtls_check_retransmit(dtls_context_t *context, clock_time_t *next); #define DTLS_COOKIE_LENGTH 16 #define DTLS_CT_CHANGE_CIPHER_SPEC 20 #define DTLS_CT_ALERT 21 #define DTLS_CT_HANDSHAKE 22 #define DTLS_CT_APPLICATION_DATA 23 /** Generic header structure of the DTLS record layer. */ typedef struct __attribute__((__packed__)) { uint8 content_type; /**< content type of the included message */ uint16 version; /**< Protocol version */ uint16 epoch; /**< counter for cipher state changes */ uint48 sequence_number; /**< sequence number */ uint16 length; /**< length of the following fragment */ /* fragment */ } dtls_record_header_t; /* Handshake types */ #define DTLS_HT_HELLO_REQUEST 0 #define DTLS_HT_CLIENT_HELLO 1 #define DTLS_HT_SERVER_HELLO 2 #define DTLS_HT_HELLO_VERIFY_REQUEST 3 #define DTLS_HT_CERTIFICATE 11 #define DTLS_HT_SERVER_KEY_EXCHANGE 12 #define DTLS_HT_CERTIFICATE_REQUEST 13 #define DTLS_HT_SERVER_HELLO_DONE 14 #define DTLS_HT_CERTIFICATE_VERIFY 15 #define DTLS_HT_CLIENT_KEY_EXCHANGE 16 #define DTLS_HT_FINISHED 20 /** Header structure for the DTLS handshake protocol. */ typedef struct __attribute__((__packed__)) { uint8 msg_type; /**< Type of handshake message (one of DTLS_HT_) */ uint24 length; /**< length of this message */ uint16 message_seq; /**< Message sequence number */ uint24 fragment_offset; /**< Fragment offset. */ uint24 fragment_length; /**< Fragment length. */ /* body */ } dtls_handshake_header_t; /** Structure of the Client Hello message. */ typedef struct __attribute__((__packed__)) { uint16 version; /**< Client version */ uint32 gmt_random; /**< GMT time of the random byte creation */ unsigned char random[28]; /**< Client random bytes */ /* session id (up to 32 bytes) */ /* cookie (up to 32 bytes) */ /* cipher suite (2 to 2^16 -1 bytes) */ /* compression method */ } dtls_client_hello_t; /** Structure of the Hello Verify Request. */ typedef struct __attribute__((__packed__)) { uint16 version; /**< Server version */ uint8 cookie_length; /**< Length of the included cookie */ uint8 cookie[]; /**< up to 32 bytes making up the cookie */ } dtls_hello_verify_t; #if 0 /** * Checks a received DTLS record for consistency and eventually decrypt, * verify, decompress and reassemble the contained fragment for * delivery to high-lever clients. * * \param state The DTLS record state for the current session. * \param */ int dtls_record_read(dtls_state_t *state, uint8 *msg, int msglen); #endif /** * Handles incoming data as DTLS message from given peer. * * @param ctx The dtls context to use. * @param session The current session * @param msg The received data * @param msglen The actual length of @p msg. * @return A value less than zero on error, zero on success. */ int dtls_handle_message(dtls_context_t *ctx, session_t *session, uint8 *msg, int msglen); /** * Check if @p session is associated with a peer object in @p context. * This function returns a pointer to the peer if found, NULL otherwise. * * @param context The DTLS context to search. * @param session The remote address and local interface * @return A pointer to the peer associated with @p session or NULL if * none exists. */ dtls_peer_t *dtls_get_peer(const dtls_context_t *context, const session_t *session); #endif /* _DTLS_DTLS_H_ */ /** * @mainpage * * @author Olaf Bergmann, TZI Uni Bremen * * This library provides a very simple datagram server with DTLS * support. It is designed to support session multiplexing in * single-threaded applications and thus targets specifically on * embedded systems. * * @section license License * * This software is under the MIT License. * * @subsection uthash UTHash * * This library uses uthash to manage * its peers (not used for Contiki). @b uthash uses the BSD revised license, see * http://uthash.sourceforge.net/license.html. * * @subsection sha256 Aaron D. Gifford's SHA256 Implementation * * tinyDTLS provides HMAC-SHA256 with BSD-licensed code from Aaron D. Gifford, * see www.aarongifford.com. * * @subsection aes Rijndael Implementation From OpenBSD * * The AES implementation is taken from rijndael.{c,h} contained in the crypto * sub-system of the OpenBSD operating system. It is copyright by Vincent Rijmen, * * Antoon Bosselaers and Paulo Barreto. See rijndael.c * for License info. * * @section download Getting the Files * * You can get the sources either from the downloads section or * through git from the project develop page. * * @section config Configuration * * Use @c configure to set up everything for a successful build. For Contiki, use the * option @c --with-contiki. * * @section build Building * * After configuration, just type * @code make * @endcode * optionally followed by * @code make install * @endcode * The Contiki version is integrated with the Contiki build system, hence you do not * need to invoke @c make explicitely. Just add @c tinydtls to the variable @c APPS * in your @c Makefile. * * @addtogroup dtls_usage DTLS Usage * * @section dtls_server_example DTLS Server Example * * This section shows how to use the DTLS library functions to setup a * simple secure UDP echo server. The application is responsible for the * entire network communication and thus will look like a usual UDP * server with socket creation and binding and a typical select-loop as * shown below. The minimum configuration required for DTLS is the * creation of the dtls_context_t using dtls_new_context(), and a callback * for sending data. Received packets are read by the application and * passed to dtls_handle_message() as shown in @ref dtls_read_cb. * For any useful communication to happen, read and write call backs * and a key management function should be registered as well. * * @code dtls_context_t *the_context = NULL; int fd, result; static dtls_handler_t cb = { .write = send_to_peer, .read = read_from_peer, .event = NULL, .get_psk_key = get_psk_key }; fd = socket(...); if (fd < 0 || bind(fd, ...) < 0) exit(-1); the_context = dtls_new_context(&fd); dtls_set_handler(the_context, &cb); while (1) { ...initialize fd_set rfds and timeout ... result = select(fd+1, &rfds, NULL, 0, NULL); if (FD_ISSET(fd, &rfds)) dtls_handle_read(the_context); } dtls_free_context(the_context); * @endcode * * @subsection dtls_read_cb The Read Callback * * The DTLS library expects received raw data to be passed to * dtls_handle_message(). The application is responsible for * filling a session_t structure with the address data of the * remote peer as illustrated by the following example: * * @code int dtls_handle_read(struct dtls_context_t *ctx) { int *fd; session_t session; static uint8 buf[DTLS_MAX_BUF]; int len; fd = dtls_get_app_data(ctx); assert(fd); session.size = sizeof(session.addr); len = recvfrom(*fd, buf, sizeof(buf), 0, &session.addr.sa, &session.size); return len < 0 ? len : dtls_handle_message(ctx, &session, buf, len); } * @endcode * * Once a new DTLS session was established and DTLS ApplicationData has been * received, the DTLS server invokes the read callback with the MAC-verified * cleartext data as its argument. A read callback for a simple echo server * could look like this: * @code int read_from_peer(struct dtls_context_t *ctx, session_t *session, uint8 *data, size_t len) { return dtls_write(ctx, session, data, len); } * @endcode * * @subsection dtls_send_cb The Send Callback * * The callback function send_to_peer() is called whenever data must be * sent over the network. Here, the sendto() system call is used to * transmit data within the given session. The socket descriptor required * by sendto() has been registered as application data when the DTLS context * was created with dtls_new_context(). * Note that it is on the application to buffer the data when it cannot be * sent at the time this callback is invoked. The following example thus * is incomplete as it would have to deal with EAGAIN somehow. * @code int send_to_peer(struct dtls_context_t *ctx, session_t *session, uint8 *data, size_t len) { int fd = *(int *)dtls_get_app_data(ctx); return sendto(fd, data, len, MSG_DONTWAIT, &session->addr.sa, session->size); } * @endcode * * @subsection dtls_get_psk_info The Key Storage * * When a new DTLS session is created, the library must ask the application * for keying material. To do so, it invokes the registered call-back function * get_psk_info() with the current context and session information as parameter. * When the call-back function is invoked with the parameter @p type set to * @c DTLS_PSK_IDENTITY, the result parameter @p result must be filled with * the psk_identity_hint in case of a server, or the actual psk_identity in * case of a client. When @p type is @c DTLS_PSK_KEY, the result parameter * must be filled with a key for the given identity @p id. The function must * return the number of bytes written to @p result which must not exceed * @p result_length. * In case of an error, the function must return a negative value that * corresponds to a valid error code defined in alert.h. * * @code int get_psk_info(struct dtls_context_t *ctx UNUSED_PARAM, const session_t *session UNUSED_PARAM, dtls_credentials_type_t type, const unsigned char *id, size_t id_len, unsigned char *result, size_t result_length) { switch (type) { case DTLS_PSK_IDENTITY: if (result_length < psk_id_length) { dtls_warn("cannot set psk_identity -- buffer too small\n"); return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); } memcpy(result, psk_id, psk_id_length); return psk_id_length; case DTLS_PSK_KEY: if (id_len != psk_id_length || memcmp(psk_id, id, id_len) != 0) { dtls_warn("PSK for unknown id requested, exiting\n"); return dtls_alert_fatal_create(DTLS_ALERT_ILLEGAL_PARAMETER); } else if (result_length < psk_key_length) { dtls_warn("cannot set psk -- buffer too small\n"); return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); } memcpy(result, psk_key, psk_key_length); return psk_key_length; default: dtls_warn("unsupported request type: %d\n", type); } return dtls_alert_fatal_create(DTLS_ALERT_INTERNAL_ERROR); } * @endcode * * @subsection dtls_events The Event Notifier * * Applications that want to be notified whenever the status of a DTLS session * has changed can register an event handling function with the field @c event * in the dtls_handler_t structure (see \ref dtls_server_example). The call-back * function is called for alert messages and internal state changes. For alert * messages, the argument @p level will be set to a value greater than zero, and * @p code will indicate the notification code. For internal events, @p level * is @c 0, and @p code a value greater than @c 255. * * Internal events are DTLS_EVENT_CONNECTED, @c DTLS_EVENT_CONNECT, and * @c DTLS_EVENT_RENEGOTIATE. * * @code int handle_event(struct dtls_context_t *ctx, session_t *session, dtls_alert_level_t level, unsigned short code) { ... do something with event ... return 0; } * @endcode * * @section dtls_client_example DTLS Client Example * * A DTLS client is constructed like a server but needs to actively setup * a new session by calling dtls_connect() at some point. As this function * usually returns before the new DTLS channel is established, the application * must register an event handler and wait for @c DTLS_EVENT_CONNECT before * it can send data over the DTLS channel. * */ /** * @addtogroup contiki Contiki * * To use tinyDTLS as Contiki application, place the source code in the directory * @c apps/tinydtls in the Contiki source tree and invoke configure with the option * @c --with-contiki. This will define WITH_CONTIKI in tinydtls.h and include * @c Makefile.contiki in the main Makefile. To cross-compile for another platform * you will need to set your host and build system accordingly. For example, * when configuring for ARM, you would invoke * @code ./configure --with-contiki --build=x86_64-linux-gnu --host=arm-none-eabi * @endcode * on an x86_64 linux host. * * Then, create a Contiki project with @c APPS += tinydtls in its Makefile. A sample * server could look like this (with read_from_peer() and get_psk_key() as shown above). * * @code #include "contiki.h" #include "tinydtls.h" #include "dtls.h" #define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN]) #define UIP_UDP_BUF ((struct uip_udp_hdr *)&uip_buf[UIP_LLIPH_LEN]) int send_to_peer(struct dtls_context_t *, session_t *, uint8 *, size_t); static struct uip_udp_conn *server_conn; static dtls_context_t *dtls_context; static dtls_handler_t cb = { .write = send_to_peer, .read = read_from_peer, .event = NULL, .get_psk_key = get_psk_key }; PROCESS(server_process, "DTLS server process"); AUTOSTART_PROCESSES(&server_process); PROCESS_THREAD(server_process, ev, data) { PROCESS_BEGIN(); dtls_init(); server_conn = udp_new(NULL, 0, NULL); udp_bind(server_conn, UIP_HTONS(5684)); dtls_context = dtls_new_context(server_conn); if (!dtls_context) { dtls_emerg("cannot create context\n"); PROCESS_EXIT(); } dtls_set_handler(dtls_context, &cb); while(1) { PROCESS_WAIT_EVENT(); if(ev == tcpip_event && uip_newdata()) { session_t session; uip_ipaddr_copy(&session.addr, &UIP_IP_BUF->srcipaddr); session.port = UIP_UDP_BUF->srcport; session.size = sizeof(session.addr) + sizeof(session.port); dtls_handle_message(ctx, &session, uip_appdata, uip_datalen()); } } PROCESS_END(); } int send_to_peer(struct dtls_context_t *ctx, session_t *session, uint8 *data, size_t len) { struct uip_udp_conn *conn = (struct uip_udp_conn *)dtls_get_app_data(ctx); uip_ipaddr_copy(&conn->ripaddr, &session->addr); conn->rport = session->port; uip_udp_packet_send(conn, data, len); memset(&conn->ripaddr, 0, sizeof(server_conn->ripaddr)); memset(&conn->rport, 0, sizeof(conn->rport)); return len; } * @endcode */