/* * coap_tinydtls.c -- Datagram Transport Layer Support for libcoap with tinydtls * * Copyright (C) 2016-2020 Olaf Bergmann * Copyright (C) 2020 Jon Shallow * * SPDX-License-Identifier: BSD-2-Clause * * This file is part of the CoAP library libcoap. Please see README for terms * of use. */ #include "coap3/coap_internal.h" #ifdef HAVE_LIBTINYDTLS /* We want TinyDTLS versions of these, not libcoap versions */ #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_URL #undef PACKAGE_VERSION #include #include #include typedef struct coap_tiny_context_t { struct dtls_context_t *dtls_context; coap_context_t *coap_context; #ifdef DTLS_ECC coap_dtls_pki_t setup_data; coap_binary_t *priv_key; coap_binary_t *pub_key; #endif /* DTLS_ECC */ } coap_tiny_context_t; static dtls_tick_t dtls_tick_0 = 0; static coap_tick_t coap_tick_0 = 0; 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_shutdown(void) { } void * coap_dtls_get_tls(const coap_session_t *c_session, coap_tls_library_t *tls_lib) { if (tls_lib) *tls_lib = COAP_TLS_LIBRARY_TINYDTLS; if (c_session && c_session->context && c_session->context->dtls_context) { const coap_tiny_context_t *t_context = (const coap_tiny_context_t *)c_session->context->dtls_context; return t_context->dtls_context; } return NULL; } 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_tiny_context_t *t_context = (coap_tiny_context_t *)dtls_get_app_data(dtls_context); coap_context_t *coap_context = t_context ? t_context->coap_context : NULL; coap_session_t *coap_session; coap_address_t remote_addr; assert(coap_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) { 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_tiny_context_t *t_context = (coap_tiny_context_t *)dtls_get_app_data(dtls_context); coap_context_t *coap_context = t_context ? t_context->coap_context : NULL; coap_session_t *coap_session; coap_address_t remote_addr; assert(coap_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) { coap_log(LOG_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_tiny_context_t *t_context = (coap_tiny_context_t *)dtls_get_app_data(dtls_context); coap_context_t *coap_context = t_context ? t_context->coap_context : NULL; coap_session_t *coap_session; int fatal_error = DTLS_ALERT_INTERNAL_ERROR; size_t identity_length; uint8_t psk[128]; size_t psk_len = 0; coap_address_t remote_addr; coap_dtls_cpsk_t *setup_cdata; coap_dtls_spsk_t *setup_sdata; coap_bin_const_t temp; assert(coap_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) { coap_log(LOG_DEBUG, "cannot get PSK, session not found\n"); goto error; } switch (type) { case DTLS_PSK_IDENTITY: if (!coap_context || !coap_context->get_client_psk || coap_session->type != COAP_SESSION_TYPE_CLIENT) goto error; setup_cdata = &coap_session->cpsk_setup_data; temp.s = id; temp.length = id_len; coap_session_refresh_psk_hint(coap_session, &temp); coap_log(LOG_DEBUG, "got psk_identity_hint: '%.*s'\n", (int)id_len, id ? (const char*)id : ""); if (setup_cdata->validate_ih_call_back) { coap_str_const_t lhint; lhint.length = id_len; lhint.s = id; const coap_dtls_cpsk_info_t *psk_info = setup_cdata->validate_ih_call_back(&lhint, coap_session, setup_cdata->ih_call_back_arg); if (psk_info == NULL) return 0; if (psk_info->identity.length >= result_length) return 0; if (psk_info->key.length > sizeof(psk)) return 0; if (coap_session->psk_identity) { coap_delete_bin_const(coap_session->psk_identity); } identity_length = psk_info->identity.length; coap_session->psk_identity = coap_new_bin_const(psk_info->identity.s, identity_length); memcpy(result, psk_info->identity.s, identity_length); result[identity_length] = '\000'; coap_session_refresh_psk_key(coap_session, &psk_info->key); return identity_length; } identity_length = 0; /* result_length is max size of the returned identity */ 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 or buffer too small\n"); fatal_error = DTLS_ALERT_CLOSE_NOTIFY; goto error; } return (int)identity_length; case DTLS_PSK_KEY: if (coap_session->type == COAP_SESSION_TYPE_CLIENT) { if (!coap_context || !coap_context->get_client_psk) goto error; identity_length = 0; /* Use psk[] as a scratch area for returning the unused identity */ psk_len = coap_context->get_client_psk(coap_session, (const uint8_t*)id, id_len, psk, &identity_length, sizeof(psk), result, result_length); if (!psk_len) { coap_log(LOG_WARNING, "no pre-shared key for given realm or buffer too small\n"); fatal_error = DTLS_ALERT_CLOSE_NOTIFY; goto error; } return (int)psk_len; } if (coap_context->get_server_psk) { setup_sdata = &coap_session->context->spsk_setup_data; if (!id) id = (const uint8_t *)""; /* Track the Identity being used */ if (coap_session->psk_identity) coap_delete_bin_const(coap_session->psk_identity); coap_session->psk_identity = coap_new_bin_const(id, id_len); coap_log(LOG_DEBUG, "got psk_identity: '%.*s'\n", (int)id_len, id); if (setup_sdata->validate_id_call_back) { coap_bin_const_t lidentity; lidentity.length = id_len; lidentity.s = (const uint8_t*)id; const coap_bin_const_t *psk_key = setup_sdata->validate_id_call_back(&lidentity, coap_session, setup_sdata->id_call_back_arg); if (psk_key == NULL) return 0; if (psk_key->length > result_length) return 0; memcpy(result, psk_key->s, psk_key->length); coap_session_refresh_psk_key(coap_session, psk_key); return psk_key->length; } return (int)coap_context->get_server_psk(coap_session, (const uint8_t*)id, id_len, (uint8_t*)result, result_length); } return 0; case DTLS_PSK_HINT: 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: return dtls_alert_fatal_create(fatal_error); } #ifdef DTLS_ECC static int get_ecdsa_key(struct dtls_context_t *dtls_context, const session_t *dtls_session COAP_UNUSED, const dtls_ecdsa_key_t **result) { static dtls_ecdsa_key_t ecdsa_key; coap_tiny_context_t *t_context = (coap_tiny_context_t *)dtls_get_app_data(dtls_context); ecdsa_key.curve = DTLS_ECDH_CURVE_SECP256R1; ecdsa_key.priv_key = t_context->priv_key->s; ecdsa_key.pub_key_x = t_context->pub_key->s; ecdsa_key.pub_key_y = &t_context->pub_key->s[DTLS_EC_KEY_SIZE]; *result = &ecdsa_key; return 0; } /* 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 and s values of the public key */ }; #define DTLS_CE_LENGTH (sizeof(cert_asn1_header) + key_size + key_size) static int verify_ecdsa_key(struct dtls_context_t *dtls_context COAP_UNUSED, const session_t *dtls_session COAP_UNUSED, const uint8_t *other_pub_x, const uint8_t *other_pub_y, size_t key_size) { coap_tiny_context_t *t_context = (coap_tiny_context_t *)dtls_get_app_data(dtls_context); if (t_context && t_context->setup_data.validate_cn_call_back) { /* Need to build asn.1 certificate - code taken from tinydtls */ uint8 *p; uint8 buf[DTLS_CE_LENGTH]; coap_session_t *c_session; coap_address_t remote_addr; /* Certificate * * Start message construction at beginning of buffer. */ p = buf; memcpy(p, &cert_asn1_header, sizeof(cert_asn1_header)); p += sizeof(cert_asn1_header); memcpy(p, other_pub_x, key_size); p += key_size; memcpy(p, other_pub_y, key_size); p += key_size; assert(p <= (buf + sizeof(buf))); get_session_addr(dtls_session, &remote_addr); c_session = coap_session_get_by_peer(t_context->coap_context, &remote_addr, dtls_session->ifindex); if (!c_session) return -3; if (!t_context->setup_data.validate_cn_call_back(COAP_DTLS_RPK_CERT_CN, buf, p-buf, c_session, 0, 1, t_context->setup_data.cn_call_back_arg)) { return -1; } } return 0; } static dtls_handler_t ec_cb = { .write = dtls_send_to_peer, .read = dtls_application_data, .event = dtls_event, .get_psk_info = NULL, .get_ecdsa_key = get_ecdsa_key, .verify_ecdsa_key = verify_ecdsa_key }; #endif /* DTLS_ECC */ static dtls_handler_t psk_cb = { .write = dtls_send_to_peer, .read = dtls_application_data, .event = dtls_event, .get_psk_info = get_psk_info, #ifdef DTLS_ECC .get_ecdsa_key = NULL, .verify_ecdsa_key = NULL #endif }; void * coap_dtls_new_context(coap_context_t *coap_context) { coap_tiny_context_t *t_context = coap_malloc(sizeof(coap_tiny_context_t)); struct dtls_context_t *dtls_context = t_context ? dtls_new_context(t_context) : NULL; if (!dtls_context) goto error; memset(t_context, 0, sizeof(coap_tiny_context_t)); t_context->coap_context = coap_context; t_context->dtls_context = dtls_context; dtls_set_handler(dtls_context, &psk_cb); return t_context; error: if (t_context) coap_free(t_context); if (dtls_context) coap_dtls_free_context(dtls_context); return NULL; } void coap_dtls_free_context(void *handle) { if (handle) { coap_tiny_context_t *t_context = (coap_tiny_context_t *)handle; #ifdef DTLS_ECC if (t_context->priv_key) { coap_delete_binary(t_context->priv_key); t_context->priv_key = NULL; } if (t_context->pub_key) { coap_delete_binary(t_context->pub_key); t_context->pub_key = NULL; } #endif /* DTLS_ECC */ if (t_context->dtls_context) dtls_free_context(t_context->dtls_context); coap_free(t_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->addr_info.remote, dtls_session); dtls_session->ifindex = session->ifindex; coap_log(LOG_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; coap_tiny_context_t *t_context = (coap_tiny_context_t *)session->context->dtls_context; dtls_context_t *dtls_context = t_context ? t_context->dtls_context : NULL; session_t *dtls_session = dtls_context ? coap_dtls_new_session(session) : NULL; if (!dtls_session) return NULL; peer = dtls_get_peer(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(dtls_context, dtls_session) >= 0) { peer = dtls_get_peer(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) { coap_tiny_context_t *t_context = (coap_tiny_context_t *)coap_session->context->dtls_context; dtls_context_t *dtls_context = t_context ? t_context->dtls_context : NULL; if (dtls_context == NULL) return; if (coap_session->tls && dtls_context) { dtls_peer_t *peer = dtls_get_peer(dtls_context, (session_t *)coap_session->tls); if ( peer ) dtls_reset_peer(dtls_context, peer); else dtls_close(dtls_context, (session_t *)coap_session->tls); coap_log(LOG_DEBUG, "***removed session %p\n", coap_session->tls); coap_free_type(COAP_DTLS_SESSION, coap_session->tls); coap_session->tls = NULL; coap_handle_event(coap_session->context, COAP_EVENT_DTLS_CLOSED, coap_session); } } int coap_dtls_send(coap_session_t *session, const uint8_t *data, size_t data_len ) { int res; uint8_t *data_rw; coap_tiny_context_t *t_context = (coap_tiny_context_t *)session->context->dtls_context; dtls_context_t *dtls_context = t_context ? t_context->dtls_context : NULL; assert(dtls_context); coap_log(LOG_DEBUG, "call dtls_write\n"); coap_event_dtls = -1; /* Need to do this to not get a compiler warning about const parameters */ memcpy (&data_rw, &data, sizeof(data_rw)); res = dtls_write(dtls_context, (session_t *)session->tls, data_rw, data_len); if (res < 0) coap_log(LOG_WARNING, "coap_dtls_send: cannot send PDU\n"); if (coap_event_dtls >= 0) { /* COAP_EVENT_DTLS_CLOSED event reported in coap_session_disconnected() */ if (coap_event_dtls != COAP_EVENT_DTLS_CLOSED) 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 == COAP_EVENT_DTLS_CLOSED || 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 *tiny_context) { clock_time_t next = 0; coap_tiny_context_t *t_context = (coap_tiny_context_t *)tiny_context; dtls_context_t *dtls_context = t_context ? t_context->dtls_context : NULL; if (tiny_context) dtls_check_retransmit(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, coap_tick_t now) { (void)session; (void)now; 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; uint8_t *data_rw; coap_tiny_context_t *t_context = (coap_tiny_context_t *)session->context->dtls_context; dtls_context_t *dtls_context = t_context ? t_context->dtls_context : NULL; assert(dtls_context); coap_event_dtls = -1; /* Need to do this to not get a compiler warning about const parameters */ memcpy (&data_rw, &data, sizeof(data_rw)); err = dtls_handle_message(dtls_context, dtls_session, data_rw, (int)data_len); if (err){ coap_event_dtls = COAP_EVENT_DTLS_ERROR; } if (coap_event_dtls >= 0) { /* COAP_EVENT_DTLS_CLOSED event reported in coap_session_disconnected() */ if (coap_event_dtls != COAP_EVENT_DTLS_CLOSED) 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 == COAP_EVENT_DTLS_CLOSED || 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; coap_tiny_context_t *t_context = (coap_tiny_context_t *)session->context->dtls_context; dtls_context_t *dtls_context = t_context ? t_context->dtls_context : NULL; uint8_t *data_rw; assert(dtls_context); dtls_session_init(&dtls_session); put_session_addr(&session->addr_info.remote, &dtls_session); dtls_session.ifindex = session->ifindex; /* Need to do this to not get a compiler warning about const parameters */ memcpy (&data_rw, &data, sizeof(data_rw)); int res = dtls_handle_message(dtls_context, &dtls_session, data_rw, (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; } int coap_tls_is_supported(void) { return 0; } coap_tls_version_t * coap_get_tls_library_version(void) { static coap_tls_version_t version; const char *vers = dtls_package_version(); version.version = 0; if (vers) { long int p1, p2 = 0, p3 = 0; char* endptr; p1 = strtol(vers, &endptr, 10); if (*endptr == '.') { p2 = strtol(endptr+1, &endptr, 10); if (*endptr == '.') { p3 = strtol(endptr+1, &endptr, 10); } } version.version = (p1 << 16) | (p2 << 8) | p3; } version.built_version = version.version; version.type = COAP_TLS_LIBRARY_TINYDTLS; return &version; } #ifdef DTLS_ECC static const uint8_t b64_6[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, /* + / */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, /* 0 1 2 3 4 5 6 7 8 9 = */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, /* A B C D E F G H I J K L M N O */ 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* P Q R S T U V W X Y Z */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, /* a b c d e f g h i j k l m n o */ 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* p q r s t u v w x y z */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 }; /* caller must free off returned coap_binary_t* */ static coap_binary_t * pem_base64_decode (const uint8_t *data, size_t size) { uint8_t *tbuf = coap_malloc(size); size_t nbytesdecoded; size_t i; coap_binary_t *decoded; uint8_t *ptr; uint8_t *out; size_t nb64bytes = 0; for (i = 0; i < size; i++) { switch (data[i]) { case ' ': case '\r': case '\n': case '\t': break; default: if (b64_6[data[i]] == 64) goto end; tbuf[nb64bytes++] = data[i]; break; } } end: nbytesdecoded = ((nb64bytes + 3) / 4) * 3; decoded = coap_new_binary(nbytesdecoded + 1); if (!decoded) return NULL; out = decoded->s; ptr = tbuf; while (nb64bytes > 4) { *(out++) = b64_6[ptr[0]] << 2 | b64_6[ptr[1]] >> 4; *(out++) = b64_6[ptr[1]] << 4 | b64_6[ptr[2]] >> 2; *(out++) = b64_6[ptr[2]] << 6 | b64_6[ptr[3]]; ptr += 4; nb64bytes -= 4; } /* Note: (nb64bytes == 1) is an error */ if (nb64bytes > 1) { *(out++) = b64_6[ptr[0]] << 2 | b64_6[ptr[1]] >> 4; } if (nb64bytes > 2) { *(out++) = b64_6[ptr[1]] << 4 | b64_6[ptr[2]] >> 2; } if (nb64bytes > 3) { *(out++) = b64_6[ptr[2]] << 6 | b64_6[ptr[3]]; } decoded->length = nbytesdecoded - ((4 - nb64bytes) & 3); coap_free(tbuf); return decoded; } typedef coap_binary_t * (*asn1_callback)(const uint8_t *data, size_t size); static int asn1_verify_privkey(const uint8_t *data, size_t size) { /* Check if we have the private key (with optional leading 0x00) */ /* skip leading 0x00 */ if (size - 1 == DTLS_EC_KEY_SIZE && *data == '\000') { --size; ++data; } /* Check if we have the private key */ if (size != DTLS_EC_KEY_SIZE) return 0; return 1; } static int asn1_verify_pubkey(const uint8_t *data, size_t size) { (void)data; /* We have the public key (with a leading 0x00 (no unused bits) 0x04 (not compressed() */ if (size - 2 != 2 * DTLS_EC_KEY_SIZE) return 0; return 1; } static int asn1_verify_curve(const uint8_t *data, size_t size) { static uint8_t prime256v1_oid[] = /* OID 1.2.840.10045.3.1.7 */ { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 }; /* Check that we have the correct EC (only one supported) */ if (size != sizeof(prime256v1_oid) || memcmp(data, prime256v1_oid, size) != 0) return 0; return 1; } static int asn1_verify_pkcs8_version(const uint8_t *data, size_t size) { /* Check that we have the version */ if (size != 1 || *data != 0) return 0; return 1; } static int asn1_verify_ec_identifier(const uint8_t *data, size_t size) { static uint8_t ec_public_key_oid[] = /* OID 1.2.840.10045.2.1 */ { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 }; /* Check that we have the correct ecPublicKey */ if (size != sizeof(ec_public_key_oid) || memcmp(data, ec_public_key_oid, size) != 0) return 0; return 1; } static int asn1_verify_ec_key(const uint8_t *data, size_t size) { (void)data; if (size == 0) return 0; return 1; } static int asn1_derive_keys(coap_tiny_context_t *t_context, const uint8_t *priv_data, size_t priv_len, const uint8_t *pub_data, size_t pub_len, int is_pkcs8) { coap_binary_t *test; t_context->priv_key = get_asn1_tag(COAP_ASN1_OCTETSTRING, priv_data, priv_len, asn1_verify_privkey); if (!t_context->priv_key) { coap_log(LOG_INFO, "EC Private Key (RPK) invalid\n"); return 0; } /* skip leading 0x00 */ if (t_context->priv_key->length - 1 == DTLS_EC_KEY_SIZE && t_context->priv_key->s[0] == '\000') { t_context->priv_key->length--; t_context->priv_key->s++; } if (!is_pkcs8) { /* pkcs8 abstraction tested for valid eliptic curve */ test = get_asn1_tag(COAP_ASN1_IDENTIFIER, priv_data, priv_len, asn1_verify_curve); if (!test) { coap_log(LOG_INFO, "EC Private Key (RPK) invalid elliptic curve\n"); coap_delete_binary(t_context->priv_key); t_context->priv_key = NULL; return 0; } coap_delete_binary(test); } t_context->pub_key = get_asn1_tag(COAP_ASN1_BITSTRING, pub_data, pub_len, asn1_verify_pubkey); if (!t_context->pub_key) { coap_log(LOG_INFO, "EC Public Key (RPK) invalid\n"); coap_delete_binary(t_context->priv_key); t_context->priv_key = NULL; return 0; } /* Drop leading 0x00 and 0x04 */ t_context->pub_key->s += 2; t_context->pub_key->length -= 2; dtls_set_handler(t_context->dtls_context, &ec_cb); return 1; } static coap_binary_t * ec_abstract_pkcs8_asn1(const uint8_t *asn1_ptr, size_t asn1_length) { coap_binary_t *test; test = get_asn1_tag(COAP_ASN1_INTEGER, asn1_ptr, asn1_length, asn1_verify_pkcs8_version); if (!test) return 0; coap_delete_binary(test); test = get_asn1_tag(COAP_ASN1_IDENTIFIER, asn1_ptr, asn1_length, asn1_verify_ec_identifier); if (!test) return 0; coap_delete_binary(test); test = get_asn1_tag(COAP_ASN1_IDENTIFIER, asn1_ptr, asn1_length, asn1_verify_curve); if (!test) { coap_log(LOG_INFO, "EC Private Key (RPK) invalid elliptic curve\n"); return 0; } coap_delete_binary(test); test = get_asn1_tag(COAP_ASN1_OCTETSTRING, asn1_ptr, asn1_length, asn1_verify_ec_key); return test; } static coap_binary_t * pem_decode_mem_asn1(const char *begstr, const uint8_t *str) { char *bcp = str ? strstr((const char*)str, begstr) : NULL; char *tcp = bcp ? strstr(bcp, "-----END ") : NULL; if (bcp && tcp) { bcp += strlen(begstr); return pem_base64_decode ((const uint8_t *)bcp, tcp - bcp); } return NULL; } #endif /* DTLS_ECC */ int coap_dtls_context_set_pki(coap_context_t *ctx, const coap_dtls_pki_t* setup_data, const coap_dtls_role_t role COAP_UNUSED ) { #ifdef DTLS_ECC coap_tiny_context_t *t_context; coap_binary_t *asn1_priv; coap_binary_t *asn1_pub; coap_binary_t *asn1_temp; int is_pkcs8 = 0; if (!setup_data) return 0; if (setup_data->version != COAP_DTLS_PKI_SETUP_VERSION) return 0; if (!setup_data->is_rpk_not_cert) { coap_log(LOG_WARNING, "Only RPK, not full PKI is supported\n"); return 0; } if (!ctx) return 0; t_context = (coap_tiny_context_t *)ctx->dtls_context; if (!t_context) return 0; if (t_context->priv_key) { coap_delete_binary(t_context->priv_key); t_context->priv_key = NULL; } if (t_context->pub_key) { coap_delete_binary(t_context->pub_key); t_context->pub_key = NULL; } t_context->setup_data = *setup_data; /* All should be RPK only now */ switch (setup_data->pki_key.key_type) { case COAP_PKI_KEY_PEM: coap_log(LOG_WARNING, "RPK keys cannot be in COAP_PKI_KEY_PEM format\n"); break; case COAP_PKI_KEY_PEM_BUF: if (setup_data->pki_key.key.pem_buf.public_cert && setup_data->pki_key.key.pem_buf.public_cert[0] && setup_data->pki_key.key.pem_buf.private_key && setup_data->pki_key.key.pem_buf.private_key[0]) { /* Need to take PEM memory information and convert to binary */ asn1_priv = pem_decode_mem_asn1("-----BEGIN EC PRIVATE KEY-----", setup_data->pki_key.key.pem_buf.private_key); if (!asn1_priv) { asn1_priv = pem_decode_mem_asn1("-----BEGIN PRIVATE KEY-----", setup_data->pki_key.key.pem_buf.private_key); if (!asn1_priv) { coap_log(LOG_INFO, "Private Key (RPK) invalid\n"); return 0; } asn1_temp = ec_abstract_pkcs8_asn1(asn1_priv->s, asn1_priv->length); if (!asn1_temp) { coap_log(LOG_INFO, "PKCS#8 Private Key (RPK) invalid\n"); coap_delete_binary(asn1_priv); return 0; } coap_delete_binary(asn1_priv); asn1_priv = asn1_temp; is_pkcs8 = 1; } asn1_pub = pem_decode_mem_asn1( "-----BEGIN PUBLIC KEY-----", setup_data->pki_key.key.pem_buf.public_cert); if (!asn1_pub) { asn1_pub = pem_decode_mem_asn1("-----BEGIN EC PRIVATE KEY-----", setup_data->pki_key.key.pem_buf.private_key); if (!asn1_pub) { asn1_pub = pem_decode_mem_asn1("-----BEGIN PRIVATE KEY-----", setup_data->pki_key.key.pem_buf.private_key); if (!asn1_pub) { coap_log(LOG_INFO, "Public Key (RPK) invalid\n"); coap_delete_binary(asn1_priv); return 0; } asn1_temp = ec_abstract_pkcs8_asn1(asn1_pub->s, asn1_pub->length); if (!asn1_temp) { coap_log(LOG_INFO, "PKCS#8 Private Key (RPK) invalid\n"); coap_delete_binary(asn1_priv); coap_delete_binary(asn1_pub); return 0; } coap_delete_binary(asn1_pub); asn1_pub = asn1_temp; is_pkcs8 = 1; } } if (!asn1_derive_keys(t_context, asn1_priv->s, asn1_priv->length, asn1_pub->s, asn1_pub->length, is_pkcs8)) { coap_log(LOG_INFO, "Unable to derive Public/Private Keys\n"); coap_delete_binary(asn1_priv); coap_delete_binary(asn1_pub); return 0; } coap_delete_binary(asn1_priv); coap_delete_binary(asn1_pub); return 1; } break; case COAP_PKI_KEY_ASN1: if (setup_data->pki_key.key.asn1.private_key && setup_data->pki_key.key.asn1.private_key_len && setup_data->pki_key.key.asn1.private_key_type == COAP_ASN1_PKEY_EC) { const uint8_t* private_key = setup_data->pki_key.key.asn1.private_key; size_t private_key_len = setup_data->pki_key.key.asn1.private_key_len; /* Check to see whether this is in pkcs8 format or not */ asn1_temp = ec_abstract_pkcs8_asn1( setup_data->pki_key.key.asn1.private_key, setup_data->pki_key.key.asn1.private_key_len); if (asn1_temp) { private_key = asn1_temp->s; private_key_len = asn1_temp->length; is_pkcs8 = 1; } /* Need to take ASN1 memory information and convert to binary */ if (setup_data->pki_key.key.asn1.public_cert && setup_data->pki_key.key.asn1.public_cert_len) { if (!asn1_derive_keys(t_context, private_key, private_key_len, setup_data->pki_key.key.asn1.public_cert, setup_data->pki_key.key.asn1.public_cert_len, is_pkcs8)) { coap_log(LOG_INFO, "Unable to derive Public/Private Keys\n"); if (asn1_temp) coap_delete_binary(asn1_temp); return 0; } } else { if (!asn1_derive_keys(t_context, private_key, private_key_len, private_key, private_key_len, is_pkcs8)) { coap_log(LOG_INFO, "Unable to derive Public/Private Keys\n"); if (asn1_temp) coap_delete_binary(asn1_temp); return 0; } } return 1; } break; case COAP_PKI_KEY_PKCS11: coap_log(LOG_WARNING, "RPK keys cannot be in COAP_PKI_KEY_PCKS11 format\n"); break; default: break; } #else /* ! DTLS_ECC */ (void)ctx; (void)setup_data; #endif /* ! DTLS_ECC */ return 0; } int coap_dtls_context_set_pki_root_cas(coap_context_t *ctx COAP_UNUSED, const char *ca_file COAP_UNUSED, const char *ca_path COAP_UNUSED ) { coap_log(LOG_WARNING, "Root CAs PKI not supported\n"); return 0; } int coap_dtls_context_set_cpsk(coap_context_t *coap_context COAP_UNUSED, coap_dtls_cpsk_t *setup_data ) { if (!setup_data) return 0; return 1; } int coap_dtls_context_set_spsk(coap_context_t *coap_context COAP_UNUSED, coap_dtls_spsk_t *setup_data ) { if (!setup_data) return 0; if (setup_data->validate_sni_call_back) { coap_log(LOG_WARNING, "CoAP Server with TinyDTLS does not support SNI selection\n"); } return 1; } int coap_dtls_context_check_keys_enabled(coap_context_t *ctx COAP_UNUSED) { return 1; } #if !COAP_DISABLE_TCP void *coap_tls_new_client_session(coap_session_t *session COAP_UNUSED, int *connected COAP_UNUSED) { return NULL; } void *coap_tls_new_server_session(coap_session_t *session COAP_UNUSED, int *connected COAP_UNUSED) { return NULL; } void coap_tls_free_session(coap_session_t *coap_session COAP_UNUSED) { } ssize_t coap_tls_write(coap_session_t *session COAP_UNUSED, const uint8_t *data COAP_UNUSED, size_t data_len COAP_UNUSED ) { return -1; } ssize_t coap_tls_read(coap_session_t *session COAP_UNUSED, uint8_t *data COAP_UNUSED, size_t data_len COAP_UNUSED ) { return -1; } #endif /* !COAP_DISABLE_TCP */ coap_digest_ctx_t * coap_digest_setup(void) { dtls_sha256_ctx *digest_ctx = coap_malloc(sizeof(dtls_sha256_ctx)); if (digest_ctx) { dtls_sha256_init(digest_ctx); } return digest_ctx; } void coap_digest_free(coap_digest_ctx_t *digest_ctx) { coap_free(digest_ctx); } int coap_digest_update(coap_digest_ctx_t *digest_ctx, const uint8_t *data, size_t data_len) { dtls_sha256_update(digest_ctx, data, data_len); return 1; } int coap_digest_final(coap_digest_ctx_t *digest_ctx, coap_digest_t *digest_buffer) { dtls_sha256_final((uint8_t*)digest_buffer, digest_ctx); coap_digest_free(digest_ctx); return 1; } #else /* !HAVE_LIBTINYDTLS */ #ifdef __clang__ /* Make compilers happy that do not like empty modules. As this function is * never used, we ignore -Wunused-function at the end of compiling this file */ #pragma GCC diagnostic ignored "-Wunused-function" #endif static inline void dummy(void) { } #endif /* HAVE_LIBTINYDTLS */