/* * coap_tinydtls.c -- Datagram Transport Layer Support for libcoap with tinydtls * * Copyright (C) 2016 Olaf Bergmann * * This file is part of the CoAP library libcoap. Please see README for terms * of use. */ #include "coap_config.h" #ifdef HAVE_LIBTINYDTLS #include "net.h" #include "address.h" #include "debug.h" #include "mem.h" #include #include static dtls_tick_t dtls_tick_0 = 0; static coap_tick_t coap_tick_0 = 0; /* Prototypes from dtls_debug.h as including that header will conflict * with coap_config.h. */ void dtls_set_log_level(int); int dtls_get_log_level(void); int coap_dtls_is_supported(void) { return 1; } void coap_dtls_startup(void) { dtls_init(); dtls_ticks(&dtls_tick_0); coap_ticks(&coap_tick_0); } void coap_dtls_set_log_level(int level) { dtls_set_log_level(level); } int coap_dtls_get_log_level(void) { return dtls_get_log_level(); } static void get_session_addr(const session_t *s, coap_address_t *a) { #ifdef WITH_CONTIKI a->addr = s->addr; a->port = s->port; #else if (s->addr.sa.sa_family == AF_INET6) { a->size = (socklen_t)sizeof(a->addr.sin6); a->addr.sin6 = s->addr.sin6; } else if (s->addr.sa.sa_family == AF_INET) { a->size = (socklen_t)sizeof(a->addr.sin); a->addr.sin = s->addr.sin; } else { a->size = (socklen_t)s->size; a->addr.sa = s->addr.sa; } #endif } static void put_session_addr(const coap_address_t *a, session_t *s) { #ifdef WITH_CONTIKI s->size = (unsigned char)sizeof(s->addr); s->addr = a->addr; s->port = a->port; #else if (a->addr.sa.sa_family == AF_INET6) { s->size = (socklen_t)sizeof(s->addr.sin6); s->addr.sin6 = a->addr.sin6; } else if (a->addr.sa.sa_family == AF_INET) { s->size = (socklen_t)sizeof(s->addr.sin); s->addr.sin = a->addr.sin; } else { s->size = (socklen_t)a->size; s->addr.sa = a->addr.sa; } #endif } static int dtls_send_to_peer(struct dtls_context_t *dtls_context, session_t *dtls_session, uint8 *data, size_t len) { coap_context_t *coap_context = (coap_context_t *)dtls_get_app_data(dtls_context); coap_session_t *coap_session; coap_address_t remote_addr; get_session_addr(dtls_session, &remote_addr); coap_session = coap_session_get_by_peer(coap_context, &remote_addr, dtls_session->ifindex); if (!coap_session) { coap_log(LOG_WARNING, "dtls_send_to_peer: cannot find local interface\n"); return -3; } return (int)coap_session_send(coap_session, data, len); } static int dtls_application_data(struct dtls_context_t *dtls_context, session_t *dtls_session, uint8 *data, size_t len) { coap_context_t *coap_context = (coap_context_t *)dtls_get_app_data(dtls_context); coap_session_t *coap_session; coap_address_t remote_addr; get_session_addr(dtls_session, &remote_addr); coap_session = coap_session_get_by_peer(coap_context, &remote_addr, dtls_session->ifindex); if (!coap_session) { debug("dropped message that was received on invalid interface\n"); return -1; } return coap_handle_dgram(coap_context, coap_session, data, len); } static int coap_event_dtls = 0; static int dtls_event(struct dtls_context_t *dtls_context, session_t *dtls_session, dtls_alert_level_t level, uint16_t code) { (void)dtls_context; (void)dtls_session; if (level == DTLS_ALERT_LEVEL_FATAL) coap_event_dtls = COAP_EVENT_DTLS_ERROR; /* handle DTLS events */ switch (code) { case DTLS_ALERT_CLOSE_NOTIFY: { coap_event_dtls = COAP_EVENT_DTLS_CLOSED; break; } case DTLS_EVENT_CONNECTED: { coap_event_dtls = COAP_EVENT_DTLS_CONNECTED; break; } case DTLS_EVENT_RENEGOTIATE: { coap_event_dtls = COAP_EVENT_DTLS_RENEGOTIATE; break; } default: ; } return 0; } /* This function is the "key store" for tinyDTLS. It is called to * retrieve a key for the given identity within this particular * session. */ static int get_psk_info(struct dtls_context_t *dtls_context, const session_t *dtls_session, dtls_credentials_type_t type, const uint8_t *id, size_t id_len, unsigned char *result, size_t result_length) { coap_context_t *coap_context; coap_session_t *coap_session; int fatal_error = DTLS_ALERT_INTERNAL_ERROR; size_t identity_length; static int client = 0; static uint8_t psk[128]; static size_t psk_len = 0; coap_address_t remote_addr; if (type == DTLS_PSK_KEY && client) { if (psk_len > result_length) { coap_log(LOG_WARNING, "cannot set psk -- buffer too small\n"); goto error; } memcpy(result, psk, psk_len); client = 0; return (int)psk_len; } client = 0; coap_context = (coap_context_t *)dtls_get_app_data(dtls_context); get_session_addr(dtls_session, &remote_addr); coap_session = coap_session_get_by_peer(coap_context, &remote_addr, dtls_session->ifindex); if (!coap_session) { debug("cannot get PSK, session not found\n"); goto error; } switch (type) { case DTLS_PSK_IDENTITY: if (id_len) coap_log(LOG_DEBUG, "got psk_identity_hint: '%.*s'\n", (int)id_len, id); if (!coap_context || !coap_context->get_client_psk) goto error; identity_length = 0; psk_len = coap_context->get_client_psk(coap_session, (const uint8_t*)id, id_len, (uint8_t*)result, &identity_length, result_length, psk, sizeof(psk)); if (!psk_len) { coap_log(LOG_WARNING, "no PSK identity for given realm\n"); fatal_error = DTLS_ALERT_CLOSE_NOTIFY; goto error; } client = 1; return (int)identity_length; case DTLS_PSK_KEY: if (coap_context->get_server_psk) return (int)coap_context->get_server_psk(coap_session, (const uint8_t*)id, id_len, (uint8_t*)result, result_length); return 0; break; case DTLS_PSK_HINT: client = 0; if (coap_context->get_server_hint) return (int)coap_context->get_server_hint(coap_session, (uint8_t *)result, result_length); return 0; default: coap_log(LOG_WARNING, "unsupported request type: %d\n", type); } error: client = 0; return dtls_alert_fatal_create(fatal_error); } static dtls_handler_t cb = { .write = dtls_send_to_peer, .read = dtls_application_data, .event = dtls_event, .get_psk_info = get_psk_info, #ifdef WITH_ECC .get_ecdsa_key = NULL, .verify_ecdsa_key = NULL #endif }; void * coap_dtls_new_context(struct coap_context_t *coap_context) { struct dtls_context_t *dtls_context = dtls_new_context(coap_context); if (!dtls_context) goto error; dtls_set_handler(dtls_context, &cb); return dtls_context; error: coap_dtls_free_context(dtls_context); return NULL; } void coap_dtls_free_context(void *handle) { if (handle) { struct dtls_context_t *dtls_context = (struct dtls_context_t *)handle; dtls_free_context(dtls_context); } } static session_t * coap_dtls_new_session(coap_session_t *session) { session_t *dtls_session = coap_malloc_type(COAP_DTLS_SESSION, sizeof(session_t)); if (dtls_session) { /* create tinydtls session object from remote address and local * endpoint handle */ dtls_session_init(dtls_session); put_session_addr(&session->remote_addr, dtls_session); dtls_session->ifindex = session->ifindex; debug("*** new session %p\n", (void *)dtls_session); } return dtls_session; } void *coap_dtls_new_server_session(coap_session_t *session) { return coap_dtls_new_session(session); } void *coap_dtls_new_client_session(coap_session_t *session) { dtls_peer_t *peer; session_t *dtls_session = coap_dtls_new_session(session); if (!dtls_session) return NULL; peer = dtls_get_peer((struct dtls_context_t *)session->context->dtls_context, dtls_session); if (!peer) { /* The peer connection does not yet exist. */ /* dtls_connect() returns a value greater than zero if a new * connection attempt is made, 0 for session reuse. */ if (dtls_connect((struct dtls_context_t *)session->context->dtls_context, dtls_session) >= 0) { peer = dtls_get_peer((struct dtls_context_t *)session->context->dtls_context, dtls_session); } } if (!peer) { /* delete existing session because the peer object has been invalidated */ coap_free_type(COAP_DTLS_SESSION, dtls_session); dtls_session = NULL; } return dtls_session; } void coap_dtls_session_update_mtu(coap_session_t *session) { (void)session; } void coap_dtls_free_session(coap_session_t *coap_session) { struct dtls_context_t *ctx = (struct dtls_context_t *)coap_session->context->dtls_context; if (coap_session->tls) { dtls_peer_t *peer = dtls_get_peer(ctx, (session_t *)coap_session->tls); if ( peer ) dtls_reset_peer(ctx, peer); else dtls_close(ctx, (session_t *)coap_session->tls); debug("*** removed session %p\n", coap_session->tls); coap_free_type(COAP_DTLS_SESSION, coap_session->tls); } } int coap_dtls_send(coap_session_t *session, const uint8_t *data, size_t data_len ) { int res; coap_log(LOG_DEBUG, "call dtls_write\n"); coap_event_dtls = -1; res = dtls_write((struct dtls_context_t *)session->context->dtls_context, (session_t *)session->tls, (uint8 *)data, data_len); if (res < 0) coap_log(LOG_WARNING, "coap_dtls_send: cannot send PDU\n"); if (coap_event_dtls >= 0) { coap_handle_event(session->context, coap_event_dtls, session); if (coap_event_dtls == COAP_EVENT_DTLS_CONNECTED) coap_session_connected(session); else if (coap_event_dtls == DTLS_ALERT_CLOSE_NOTIFY || coap_event_dtls == COAP_EVENT_DTLS_ERROR) coap_session_disconnected(session, COAP_NACK_TLS_FAILED); } return res; } int coap_dtls_is_context_timeout(void) { return 1; } coap_tick_t coap_dtls_get_context_timeout(void *dtls_context) { clock_time_t next = 0; dtls_check_retransmit((struct dtls_context_t *)dtls_context, &next); if (next > 0) return ((coap_tick_t)(next - dtls_tick_0)) * COAP_TICKS_PER_SECOND / DTLS_TICKS_PER_SECOND + coap_tick_0; return 0; } coap_tick_t coap_dtls_get_timeout(coap_session_t *session) { (void)session; return 0; } void coap_dtls_handle_timeout(coap_session_t *session) { (void)session; return; } int coap_dtls_receive(coap_session_t *session, const uint8_t *data, size_t data_len ) { session_t *dtls_session = (session_t *)session->tls; int err; coap_event_dtls = -1; err = dtls_handle_message( (struct dtls_context_t *)session->context->dtls_context, dtls_session, (uint8 *)data, (int)data_len); if (err){ coap_event_dtls = COAP_EVENT_DTLS_ERROR; } if (coap_event_dtls >= 0) { coap_handle_event(session->context, coap_event_dtls, session); if (coap_event_dtls == COAP_EVENT_DTLS_CONNECTED) coap_session_connected(session); else if (coap_event_dtls == DTLS_ALERT_CLOSE_NOTIFY || coap_event_dtls == COAP_EVENT_DTLS_ERROR) coap_session_disconnected(session, COAP_NACK_TLS_FAILED); } return err; } int coap_dtls_hello(coap_session_t *session, const uint8_t *data, size_t data_len ) { session_t dtls_session; struct dtls_context_t *dtls_context = (struct dtls_context_t *)session->context->dtls_context; dtls_session_init(&dtls_session); put_session_addr(&session->remote_addr, &dtls_session); dtls_session.ifindex = session->ifindex; int res = dtls_handle_message(dtls_context, &dtls_session, (uint8 *)data, (int)data_len); if (res >= 0) { if (dtls_get_peer(dtls_context, &dtls_session)) res = 1; else res = 0; } return res; } unsigned int coap_dtls_get_overhead(coap_session_t *session) { (void)session; return 13 + 8 + 8; } #ifdef __GNUC__ #define UNUSED __attribute__((unused)) #else /* __GNUC__ */ #define UNUSED #endif /* __GNUC__ */ int coap_tls_is_supported(void) { return 0; } coap_tls_version_t * coap_get_tls_library_version(void) { static coap_tls_version_t version; version.version = DTLS_VERSION; version.type = COAP_TLS_LIBRARY_TINYDTLS; return &version; } int coap_dtls_context_set_pki( coap_context_t *ctx UNUSED, coap_dtls_pki_t* setup_data UNUSED ) { return 0; } int coap_dtls_context_set_psk(coap_context_t *ctx UNUSED, const char *hint UNUSED, const uint8_t *key UNUSED, size_t key_len UNUSED ) { return 1; } int coap_dtls_context_check_keys_enabled(coap_context_t *ctx UNUSED) { return 1; } void *coap_tls_new_client_session(coap_session_t *session UNUSED, int *connected UNUSED) { return NULL; } void *coap_tls_new_server_session(coap_session_t *session UNUSED, int *connected UNUSED) { return NULL; } void coap_tls_free_session(coap_session_t *coap_session UNUSED) { } ssize_t coap_tls_write(coap_session_t *session UNUSED, const uint8_t *data UNUSED, size_t data_len UNUSED ) { return -1; } ssize_t coap_tls_read(coap_session_t *session UNUSED, uint8_t *data UNUSED, size_t data_len UNUSED ) { return -1; } #undef UNUSED #else /* !HAVE_LIBTINYDTLS */ /* make compilers happy that do not like empty modules */ static inline void dummy(void) { } #endif /* HAVE_LIBTINYDTLS */