diff --git a/.travis/script.sh b/.travis/script.sh index 7be504faf..d621319c2 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -52,6 +52,7 @@ set -x --with-ncp-bus=uart \ --with-examples=posix \ --enable-application-coap \ + --enable-application-coap-secure \ --enable-border-router \ --enable-cert-log \ --enable-child-supervision \ diff --git a/configure.ac b/configure.ac index 04e73ed08..1dab46e16 100644 --- a/configure.ac +++ b/configure.ac @@ -764,6 +764,37 @@ AC_SUBST(OPENTHREAD_ENABLE_MTD_NETWORK_DIAGNOSTIC) AM_CONDITIONAL([OPENTHREAD_ENABLE_MTD_NETWORK_DIAGNOSTIC], [test "${enable_mtd_network_diagnostic}" = "yes"]) AC_DEFINE_UNQUOTED([OPENTHREAD_ENABLE_MTD_NETWORK_DIAGNOSTIC],[${OPENTHREAD_ENABLE_MTD_NETWORK_DIAGNOSTIC}],[Define to 1 to enable network diagnostic for MTD.]) +# +# Application CoAP Secure +# + +AC_ARG_ENABLE(application_coap_secure, + [AS_HELP_STRING([--enable-application-coap-secure],[Enable CoAP Secure to an application.@<:@default=no@:>@.])], + [ + case "${enableval}" in + + no|yes) + enable_application_coap_secure=${enableval} + ;; + + *) + AC_MSG_ERROR([Invalid value ${enable_application_coap_secure} for --enable-application-coap_secure]) + ;; + esac + ], + [enable_application_coap_secure=no]) + +if test "$enable_application_coap_secure" = "yes"; then + OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE=1 + enable_application_coap="yes" +else + OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE=0 +fi + +AC_SUBST(OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE) +AM_CONDITIONAL([OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE], [test "${enable_application_coap_secure}" = "yes"]) +AC_DEFINE_UNQUOTED([OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE],[${OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE}],[Define to 1 if you want to enable CoAP Secure to an application.]) + # # Thread Commissioner # @@ -828,7 +859,7 @@ AC_SUBST(OPENTHREAD_ENABLE_JOINER) AM_CONDITIONAL([OPENTHREAD_ENABLE_JOINER], [test "${enable_joiner}" = "yes"]) AC_DEFINE_UNQUOTED([OPENTHREAD_ENABLE_JOINER],[${OPENTHREAD_ENABLE_JOINER}],[Define to 1 to enable the joiner role.]) -if test "${enable_commissioner}" = "yes" -o "${enable_joiner}" = "yes" -o "${enable_border_agent}" = "yes"; then +if test "${enable_commissioner}" = "yes" -o "${enable_joiner}" = "yes" -o "${enable_border_agent}" = "yes" -o "${enable_application_coap_secure}" = "yes"; then enable_dtls="yes" OPENTHREAD_ENABLE_DTLS=1 else @@ -1787,6 +1818,7 @@ AC_MSG_NOTICE([ OpenThread DHCPv6 Client support : ${enable_dhcp6_client} OpenThread DNS Client support : ${enable_dns_client} OpenThread Application CoAP support : ${enable_application_coap} + OpenThread Application CoAP Secure support: ${enable_application_coap_secure} OpenThread Raw Link-Layer support : ${enable_raw_link_api} OpenThread Border Agent support : ${enable_border_agent} OpenThread Border Router support : ${enable_border_router} diff --git a/examples/common-switches.mk b/examples/common-switches.mk index 0f2ade320..b72832156 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -43,6 +43,10 @@ ifeq ($(COAP),1) configure_OPTIONS += --enable-application-coap endif +ifeq ($(COAPS),1) +configure_OPTIONS += --enable-application-coap-secure +endif + ifeq ($(COMMISSIONER),1) configure_OPTIONS += --enable-commissioner endif diff --git a/include/openthread/Makefile.am b/include/openthread/Makefile.am index 54351f34f..e86743c31 100644 --- a/include/openthread/Makefile.am +++ b/include/openthread/Makefile.am @@ -51,6 +51,7 @@ openthread_headers = \ channel_monitor.h \ child_supervision.h \ cli.h \ + coap_secure.h \ coap.h \ commissioner.h \ config.h \ diff --git a/include/openthread/coap.h b/include/openthread/coap.h index 688d41935..8a73d58e7 100644 --- a/include/openthread/coap.h +++ b/include/openthread/coap.h @@ -160,6 +160,9 @@ typedef enum otCoapOptionContentFormat { OT_COAP_OPTION_CONTENT_FORMAT_OCTET_STREAM = 42, ///< application/octet-stream OT_COAP_OPTION_CONTENT_FORMAT_EXI = 47, ///< application/exi OT_COAP_OPTION_CONTENT_FORMAT_JSON = 50, ///< application/json + OT_COAP_OPTION_CONTENT_FORMAT_PKCS10 = 70, ///< application/pkcs10 + OT_COAP_OPTION_CONTENT_FORMAT_PKCS7 = 80, ///< application/pkcs7 + OT_COAP_OPTION_CONTENT_FORMAT_JWS = 101 ///< application/json-web-signature } otCoapOptionContentFormat; #define OT_COAP_HEADER_MAX_LENGTH 128 ///< Max CoAP header length (bytes) @@ -402,6 +405,16 @@ otCoapType otCoapHeaderGetType(const otCoapHeader *aHeader); */ otCoapCode otCoapHeaderGetCode(const otCoapHeader *aHeader); +/** + * This method returns the CoAP Code as human readable string. + * + * @param[in] aHeader A pointer to the CoAP header. + * + * @ returns The CoAP Code as string. + * + */ +const char *otCoapHeaderCodeToString(const otCoapHeader *aHeader); + /** * This function returns the Message ID value. * diff --git a/include/openthread/coap_secure.h b/include/openthread/coap_secure.h new file mode 100644 index 000000000..bf6f0aefb --- /dev/null +++ b/include/openthread/coap_secure.h @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file defines the top-level functions for the OpenThread CoAP Secure implementation. + * + * @note + * To enable cipher suite DTLS_PSK_WITH_AES_128_CCM_8, MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + * must be enabled in mbedtls-config.h + * To enable cipher suite DTLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + * MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED must be enabled in mbedtls-config.h. + */ + +#ifndef OPENTHREAD_COAP_SECURE_H_ +#define OPENTHREAD_COAP_SECURE_H_ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-coap-secure + * + * @brief + * This module includes functions that control CoAP Secure (CoAP over DTLS) communication. + * + * The functions in this module are available when application-coap-secure feature + * (`OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE`) is enabled. + * + * @{ + * + */ + +#define OT_DEFAULT_COAP_SECURE_PORT 5684 ///< Default CoAP Secure port, as specified in RFC 7252 + +/** + * This function pointer is called when the DTLS connection state changes. + * + * @param[in] aConnected true, if a connection was established, false otherwise. + * @param[in] aContext A pointer to arbitrary context information. + * + */ +typedef void (*otHandleCoapSecureClientConnect)(bool aConnected, void *aContext); + +/** + * This function starts the CoAP Secure service. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aPort The local UDP port to bind to. + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Successfully started the CoAP Secure server. + * + */ +otError otCoapSecureStart(otInstance *aInstance, uint16_t aPort, void *aContext); + +/** + * This function stops the CoAP Secure server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval OT_ERROR_NONE Successfully stopped the CoAP Secure server. + */ +otError otCoapSecureStop(otInstance *aInstance); + +/** + * This method sets the Pre-Shared Key (PSK) and cipher suite + * DTLS_PSK_WITH_AES_128_CCM_8. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aPSK A pointer to the PSK. + * @param[in] aPskLength The PSK length. + * @param[in] aPskIdentity The Identity Name for the PSK. + * @param[in] aPskIdLength The PSK Identity Length. + * + * @retval OT_ERROR_NONE Successfully set the PSK. + * @retval OT_ERROR_INVALID_ARGS The PSK is invalid. + * @retval OT_ERROR_DISABLED_FEATURE Mbedtls config not enabled + * MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + * + */ +otError otCoapSecureSetPsk(otInstance * aInstance, + const uint8_t *aPsk, + uint16_t aPskLength, + const uint8_t *aPskIdentity, + uint16_t aPskIdLength); + +/** + * This method returns the peer x509 certificate base64 encoded. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[out] aPeerCert A pointer to the base64 encoded certificate buffer. + * @param[out] aCertLength The length of the base64 encoded peer certificate. + * @param[in] aCertBufferSize The buffer size of aPeerCert. + * + * @retval OT_ERROR_NONE Successfully get the peer certificate. + * @retval OT_ERROR_DISABLED_FEATURE Mbedtls config not enabled MBEDTLS_BASE64_C. + * + */ +otError otCoapSecureGetPeerCertificateBase64(otInstance * aInstance, + unsigned char *aPeerCert, + uint64_t * aCertLength, + uint64_t aCertBufferSize); + +/** + * This method sets the authentication mode for the coap secure connection. + * + * Disable or enable the verification of peer certificate. + * Must be called before start. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aVerifyPeerCertificate true, to verify the peer certificate. + * + */ +void otCoapSecureSetSslAuthMode(otInstance *aInstance, bool aVerifyPeerCertificate); + +/** + * This method sets the local device's X509 certificate with corresponding private key for + * DTLS session with DTLS_ECDHE_ECDSA_WITH_AES_128_CCM_8. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aX509Certificate A pointer to the PEM formatted X509 certificate. + * @param[in] aX509Length The length of certificate. + * @param[in] aPrivateKey A pointer to the PEM formatted private key. + * @param[in] aPrivateKeyLength The length of the private key. + * + * @retval OT_ERROR_NONE Successfully set the x509 certificate + * with his private key. + * @retval OT_ERROR_DISABLED_FEATURE Mbedtls config not enabled + * MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED. + * + */ +otError otCoapSecureSetCertificate(otInstance * aInstance, + const uint8_t *aX509Cert, + uint32_t aX509Length, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength); + +/** + * This method sets the trusted top level CAs. It is needed for validating the + * certificate of the peer. + * + * DTLS mode "ECDHE ECDSA with AES 128 CCM 8" for Application CoAPS. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aX509CaCertificateChain A pointer to the PEM formatted X509 CA chain. + * @param[in] aX509CaCertChainLength The length of chain. + * + * @retval OT_ERROR_NONE Successfully set the the trusted top level CAs. + * + */ +otError otCoapSecureSetCaCertificateChain(otInstance * aInstance, + const uint8_t *aX509CaCertificateChain, + uint32_t aX509CaCertChainLength); + +/** + * This method initializes DTLS session with a peer. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessageInfo A pointer to a message info structure. + * @param[in] aCallback A pointer to a function that will be called when the DTLS connection + * state changes. + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Successfully started DTLS connection. + * + */ +otError otCoapSecureConnect(otInstance * aInstance, + const otMessageInfo * aMessageInfo, + otHandleCoapSecureClientConnect aHandler, + void * aContext); + +/** + * This method stops the DTLS connection. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval OT_ERROR_NONE Successfully stopped the DTLS connection. + * + */ +otError otCoapSecureDisconnect(otInstance *aInstance); + +/** + * This method indicates whether or not the DTLS session is connected. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval TRUE The DTLS session is connected. + * @retval FALSE The DTLS session is not connected. + * + */ +bool otCoapSecureIsConnected(otInstance *aInstance); + +/** + * This method indicates whether or not the DTLS session is active. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval TRUE If DTLS session is active. + * @retval FALSE If DTLS session is not active. + * + */ +bool otCoapSecureIsConncetionActive(otInstance *aInstance); + +/** + * This method sends a CoAP request over secure DTLS connection. + * + * If a response for a request is expected, respective function and context information should be provided. + * If no response is expected, these arguments should be NULL pointers. + * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A reference to the message to send. + * @param[in] aHandler A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Successfully sent CoAP message. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * @retvak OT_ERROR_INVALID_STATE DTLS connection was not initialized. + * + */ +otError otCoapSecureSendRequest(otInstance * aInstance, + otMessage * aMessage, + otCoapResponseHandler aHandler, + void * aContext); + +/** + * This function adds a resource to the CoAP Secure server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aResource A pointer to the resource. + * + * @retval OT_ERROR_NONE Successfully added @p aResource. + * @retval OT_ERROR_ALREADY The @p aResource was already added. + * + */ +otError otCoapSecureAddResource(otInstance *aInstance, otCoapResource *aResource); + +/** + * This function removes a resource from the CoAP Secure server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aResource A pointer to the resource. + * + */ +void otCoapSecureRemoveResource(otInstance *aInstance, otCoapResource *aResource); + +/** + * This function sets the default handler for unhandled CoAP Secure requests. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aHandler A function pointer that shall be called when an unhandled request arrives. + * @param[in] aContext A pointer to arbitrary context information. May be NULL if not used. + * + */ +void otCoapSecureSetDefaultHandler(otInstance *aInstance, otCoapRequestHandler aHandler, void *aContext); + +/** + * This method sets the connected callback to indicate, when + * a Client connect to the CoAP Secure server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessageInfo A pointer to a message info structure. + * @param[in] aHandler A pointer to a function that will be called once DTLS connection is established. + * + */ +void otCoapSecureSetClientConnectedCallback(otInstance * aInstance, + otHandleCoapSecureClientConnect aHandler, + void * aContext); + +/** + * This function sends a CoAP response from the CoAP Secure server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the CoAP response to send. + * @param[in] aMessageInfo A pointer to the message info associated with @p aMessage. + * + * @retval OT_ERROR_NONE Successfully enqueued the CoAP response message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers available to send the CoAP response. + * + */ +otError otCoapSecureSendResponse(otInstance *aInstance, otMessage *aMessage, const otMessageInfo *aMessageInfo); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OPENTHREAD_COAP_SECURE_H_ */ diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am index d97a29574..71fb85d3e 100644 --- a/src/cli/Makefile.am +++ b/src/cli/Makefile.am @@ -144,6 +144,7 @@ libopenthread_cli_ftd_a_CPPFLAGS = \ SOURCES_COMMON = \ cli.cpp \ cli_coap.cpp \ + cli_coap_secure.cpp \ cli_console.cpp \ cli_dataset.cpp \ cli_uart.cpp \ @@ -161,6 +162,7 @@ libopenthread_cli_mtd_a_SOURCES = \ noinst_HEADERS = \ cli.hpp \ cli_coap.hpp \ + cli_coap_secure.hpp \ cli_console.hpp \ cli_dataset.hpp \ cli_server.hpp \ diff --git a/src/cli/README.md b/src/cli/README.md index e13c200db..9503535f9 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -14,6 +14,7 @@ OpenThread test scripts use the CLI to execute test cases. * [childmax](#childmax) * [childtimeout](#childtimeout) * [coap](#coap-start) +* [coaps](#coaps-start-checkpeercert) * [commissioner](#commissioner-start-provisioningurl) * [contextreusedelay](#contextreusedelay) * [counter](#counter) @@ -233,7 +234,7 @@ Coap service started: Done Stops the application coap service. ```bash -> coap start +> coap stop Coap service stopped: Done ``` @@ -272,6 +273,104 @@ coap response sent successfully! Received coap response ``` +### coaps start \ + +Starts the Application CoAP Secure Service. + +* checkPeerCert: Peer Certificate Check can be disabled by typing false. + +```bash +> coaps start false +Verify Peer Certificate: false. Coap Secure service started: Done +> coaps start +Verify Peer Certificate: true. Coap Secure service started: Done +``` + +### coaps stop + +Stops the Application CoAP Secure Service. + +```bash +> coaps stop +Coap Secure service stopped: Done +``` + +### coaps set psk \ \ + +Set a pre-shared key with his identifier and the ciphersuite +"DTLS_PSK_WITH_AES_128_CCM_8" for the dtls session. + +* preSharedKey: The pre-shared key (PSK) for dtls session. +* keyIdentity: The identifier for the PSK. + +```bash +> coaps set psk myPreSharedSecret myIdentifier +Coap Secure set PSK: Done +``` + +### coaps set x509 + +Set X.509 Certificate with his private key, which is saved in +src/cli/x509_cert_key.hpp. + +```bash +> coaps set x509 +Coap Secure set own .X509 certificate: Done +``` + +### coaps connect \ \[port\] + +Open a dtls session to a CoAP Secure Server. + +* serverAddress: IPv6 address of Server + +```bash +> coaps connect 2001:1234::321 +Coap Secure connect: Done +CoAP Secure connected! +``` + +### coaps disconnect + +Terminate the dtls session to the Server. + +```bash +> coaps disconnect +CoAP Secure not connected or disconnected. +Done +``` + +### coaps \ \ \ \[type\] \[payload\] + +* method: CoAP method to be used (GET/PUT/POST/DELETE). +* address: IPv6 address of the CoAP Secure server to query. +* uri: URI String of the resource on the CoAP server. +* type: Switch between confirmable ("con") and non-confirmable (default). +* payload: In case of PUT/POST/DELETE a payload can be encapsulated. + +```bash +> coaps get 2001:1234::321 secret +Sending coap secure request: Done +Received coap secure response + CoapSecure RX Header Informations: + Type 16 (NON CONF) + Code 69 (Coap Code CONTENT) + With payload (hex): +4a756e2031322031353a30373a3336 +> coaps put 2001:1234::321 test non-con hello +Sending coap secure request: Done +> coaps post 2001:1234::321 test con +Sending coap secure request: Done +Received coap secure response + CoapSecure RX Header Informations: + Type 32 (NON CONF) + Code 69 (Coap Code CONTENT) + With payload (hex): +4a756e2031322031353a30373a3336 +> coaps delete 2001:1234::321 test +Sending coap secure request: Done +``` + ### commissioner start \ Start the Commissioner role. diff --git a/src/cli/README_COAPS.md b/src/cli/README_COAPS.md new file mode 100644 index 000000000..a0f7f76a8 --- /dev/null +++ b/src/cli/README_COAPS.md @@ -0,0 +1,155 @@ +# OpenThread CLI - COAPS Example + +The OpenThread CoAP Secure APIs may be invoked via the OpenThread CLI. + +CoAP Secure use DTLS (over UDP) to make an end to end encrypted connection. + +### For use PSK with AES128 CCM8 + + 1a. enter your psk and his identifier + +```bash +coaps set psk +``` + +### For use ECDHE ECDSA with AES128 CCM8 + + 1b. set the private key and .X509 certificate stored in core/cli/x509_cert_key.hpp. + + > _optional_: add your [own](#create-ec-private-key) X.509 certificate and private key to 'core/cli/x509_cert_key.hpp'. + +```bash +coaps set x509 +``` + +## Start CoAPS Service (for Server and Client) + + 2. start (init) the coaps api and starts listen on coaps port (5684) + +```bash +> coaps start +``` + +## CoAP Secure Server + + * add a coap resource to provide to a coap client + +```bash +coaps resource +``` + +## Connect to a DTLS Server + + * connect to a dtls server + +```bash +coaps connect (port, if not default) +``` + +## CoAP Secure Client + +* get a resource form the server + +```bash +coaps get (serversIp) (Con/NotCon) (payload) +``` + +> post, put and delete also possible + + +<> must +() opt + + +## Complete example for DTLS/CoAP server (Node 1) + +In this example the coap server is also the dtls server. +The dtls server waits for incoming connection on coaps port 5684. +The [Node 2](#complete-example-dtlscoap-client-node-2) below is able to connect to this coaps server. + +> Note: Node 1 and Node 2 must use the same mode. Either PSK or Certificate based. + +``` + Node 1 +--------- +|CoAPS | +|Server | <--Listen on Port 5684-- (Node 2) +| | +--------- +``` + +### with PSK + +```bash +coaps set psk secretPSK Client_identity +coaps start +coaps resource test +``` + +### with Certificate + +```bash +coaps set x509 +coaps start (false) +coaps resource test +``` + +* param false: optional, disables peer certificate validation. + +## Complete example DTLS/CoAP client (Node 2) + +In this example the coap client is also the dtls client. +The dlts client can connect to a coaps server which is listen on coaps port 5684, e.g to the [Node 1](#complete-example-for-dtlscoap-server-node-1) above. + +> Note: Node 1 and Node 2 must use the same mode. Either PSK or Certificate based. + +``` + Node 2 +--------- +|CoAPS | +|Client |--Connect to Server on Port 5684--> (Node 1) +| | +--------- +``` + +### with PSK + +```bash +coaps set psk secretPSK Client_identity +coaps start +coaps connect 2001:620:190:ffa1::321 +coaps get test +coaps disconnect +``` + +### with Certificate + +```bash +coaps set x509 +coaps start (false) +coaps connect +coaps get test +coaps disconnect +``` + +* param false: optional, disables peer certificate validation. + +## Create own Private Key with a .X509 Certificate + +### Create EC Private Key + +```bash +openssl ecparam -genkey -out myECKey.pem -name prime256v1 -noout +``` + +* ecparam: Key for Elliptic Curve Algorithms +* -name: The elliptic curve to chose. a list of available curves `openssl ecparam -list_curves` +* -noout: private key without EC Parameters + +### Create .X509 Certificate + +```bash +openssl req -x509 -new -key myECKey.pem -out myX509Cert.pem -days 30 +``` + +* -days: validity time of certificate diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 828522b3a..30f0c6423 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -80,6 +80,10 @@ #include "cli_coap.hpp" #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#include "cli_coap_secure.hpp" +#endif + #if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_DEBUG_UART) && OPENTHREAD_POSIX #include #endif @@ -106,6 +110,9 @@ const struct Command Interpreter::sCommands[] = { #if OPENTHREAD_ENABLE_APPLICATION_COAP {"coap", &Interpreter::ProcessCoap}, #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + {"coaps", &Interpreter::ProcessCoapSecure}, +#endif #if OPENTHREAD_ENABLE_COMMISSIONER && OPENTHREAD_FTD {"commissioner", &Interpreter::ProcessCommissioner}, #endif @@ -287,6 +294,9 @@ Interpreter::Interpreter(Instance *aInstance) #if OPENTHREAD_ENABLE_APPLICATION_COAP , mCoap(*this) #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + , mCoapSecure(*this) +#endif { #ifdef OTDLL assert(mApiInstance); @@ -649,6 +659,17 @@ void Interpreter::ProcessCoap(int argc, char *argv[]) #endif // OPENTHREAD_ENABLE_APPLICATION_COAP +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +void Interpreter::ProcessCoapSecure(int argc, char *argv[]) +{ + otError error; + error = mCoapSecure.Process(argc, argv); + AppendResult(error); +} + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + #if OPENTHREAD_FTD void Interpreter::ProcessContextIdReuseDelay(int argc, char *argv[]) { diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index dedcf714a..98659d480 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -50,6 +50,11 @@ #include "cli/cli_coap.hpp" #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#include +#include "cli/cli_coap_secure.hpp" +#endif + #include "common/code_utils.hpp" #include "common/instance.hpp" @@ -94,6 +99,7 @@ struct Command class Interpreter { friend class Coap; + friend class CoapsSecure; friend class UdpExample; public: @@ -192,6 +198,9 @@ private: #if OPENTHREAD_ENABLE_APPLICATION_COAP void ProcessCoap(int argc, char *argv[]); #endif // OPENTHREAD_ENABLE_APPLICATION_COAP +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + void ProcessCoapSecure(int argc, char *argv[]); +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP #if OPENTHREAD_ENABLE_COMMISSIONER && OPENTHREAD_FTD void ProcessCommissioner(int argc, char *argv[]); #endif // OPENTHREAD_ENABLE_COMMISSIONER && OPENTHREAD_FTD @@ -436,6 +445,11 @@ private: Coap mCoap; #endif // OPENTHREAD_ENABLE_APPLICATION_COAP +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + + CoapsSecure mCoapSecure; + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE }; } // namespace Cli diff --git a/src/cli/cli_coap_secure.cpp b/src/cli/cli_coap_secure.cpp new file mode 100644 index 000000000..278a7172f --- /dev/null +++ b/src/cli/cli_coap_secure.cpp @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements a simple CLI for the CoAP Secure service. + */ + +#include "cli_coap_secure.hpp" + +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +#include + +#include "cli/cli.hpp" +#include "coap/coap_header.hpp" +#include "coap/coap_secure.hpp" + +// header for place your x509 certificate and private key +#include "x509_cert_key.hpp" + +#include + +namespace ot { +namespace Cli { + +CoapsSecure::CoapsSecure(Interpreter &aInterpreter) + : mInterpreter(aInterpreter) + , mShutdownFlag(false) + , mUseCertificate(false) + , mPskLength(0) + , mPskIdLength(0) +{ + memset(&mResource, 0, sizeof(mResource)); + memset(&mPsk, 0, sizeof(mPsk)); + memset(&mPskId, 0, sizeof(mPskId)); +} + +void CoapsSecure::PrintHeaderInfos(otCoapHeader *aHeader) const +{ + otCoapCode mCoapCode; + otCoapType mCoapType; + + mCoapCode = otCoapHeaderGetCode(aHeader); + mCoapType = otCoapHeaderGetType(aHeader); + + mInterpreter.mServer->OutputFormat("\r\n CoapSecure RX Header Information:" + "\r\n Type %d => ", + static_cast(mCoapType)); + + switch (mCoapType) + { + case OT_COAP_TYPE_ACKNOWLEDGMENT: + mInterpreter.mServer->OutputFormat("Ack"); + break; + case OT_COAP_TYPE_CONFIRMABLE: + mInterpreter.mServer->OutputFormat("Confirmable"); + break; + case OT_COAP_TYPE_NON_CONFIRMABLE: + mInterpreter.mServer->OutputFormat("NonConfirmable"); + break; + case OT_COAP_TYPE_RESET: + mInterpreter.mServer->OutputFormat("Reset"); + break; + default: + break; + } + mInterpreter.mServer->OutputFormat("\r\n Code %d => %s\r\n", static_cast(mCoapCode), + static_cast(otCoapHeaderCodeToString(aHeader))); +} + +void CoapsSecure::PrintPayload(otMessage *aMessage) const +{ + uint8_t buf[kMaxBufferSize]; + uint16_t bytesToPrint; + uint16_t bytesPrinted = 0; + uint16_t length = otMessageGetLength(aMessage) - otMessageGetOffset(aMessage); + + if (length > 0) + { + mInterpreter.mServer->OutputFormat(" With payload [UTF8]:\r\n", aMessage); + + while (length > 0) + { + bytesToPrint = (length < sizeof(buf)) ? length : sizeof(buf); + otMessageRead(aMessage, otMessageGetOffset(aMessage) + bytesPrinted, buf, bytesToPrint); + + for (int i = 0; i < bytesToPrint; i++) + { + mInterpreter.mServer->OutputFormat("%c", buf[i]); + } + mInterpreter.mServer->OutputFormat("\r\n"); + + length -= bytesToPrint; + bytesPrinted += bytesToPrint; + } + } + else + { + mInterpreter.mServer->OutputFormat(" No payload."); + } + + mInterpreter.mServer->OutputFormat("\r\n> "); +} + +otError CoapsSecure::Process(int argc, char *argv[]) +{ + otError error = OT_ERROR_NONE; + otIp6Address coapDestinationIp; + otMessageInfo messageInfo; + bool mVerifyPeerCert = true; + long value; + + VerifyOrExit(argc > 0, error = OT_ERROR_INVALID_ARGS); + + if (strcmp(argv[0], "start") == 0) + { + if (argc > 1) + { + if (strcmp(argv[1], "false") == 0) + { + mVerifyPeerCert = false; + } + else if (strcmp(argv[1], "true") != 0) + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + otCoapSecureSetSslAuthMode(mInterpreter.mInstance, mVerifyPeerCert); + SuccessOrExit(error = otCoapSecureStart(mInterpreter.mInstance, OT_DEFAULT_COAP_SECURE_PORT, this)); + otCoapSecureSetClientConnectedCallback(mInterpreter.mInstance, &CoapsSecure::HandleClientConnect, this); +#if CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER + otCoapSecureSetDefaultHandler(mInterpreter.mInstance, &CoapsSecure::DefaultHandle, this); +#endif // CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + if (mUseCertificate) + { + mInterpreter.mServer->OutputFormat("Verify Peer Certificate: %s. Coap Secure service started: ", + mVerifyPeerCert ? "true" : "false"); + } +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + } + else if (strcmp(argv[0], "set") == 0) + { + if (argc > 1) + { + if (strcmp(argv[1], "psk") == 0) + { + if (argc > 3) + { + mPskLength = (uint8_t)strlen(argv[2]); + mPskIdLength = (uint8_t)strlen(argv[3]); + + memcpy(mPsk, argv[2], mPskLength); + memcpy(mPskId, argv[3], mPskIdLength); + + SuccessOrExit( + error = otCoapSecureSetPsk(mInterpreter.mInstance, mPsk, mPskLength, mPskId, mPskIdLength)); + mUseCertificate = false; + mInterpreter.mServer->OutputFormat("Coap Secure set PSK: "); + } + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + else if (strcmp(argv[1], "x509") == 0) + { +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + SuccessOrExit(error = otCoapSecureSetCertificate( + mInterpreter.mInstance, (const uint8_t *)OT_CLI_COAPS_X509_CERT, + sizeof(OT_CLI_COAPS_X509_CERT), (const uint8_t *)OT_CLI_COAPS_PRIV_KEY, + sizeof(OT_CLI_COAPS_PRIV_KEY))); + + SuccessOrExit(error = otCoapSecureSetCaCertificateChain( + mInterpreter.mInstance, (const uint8_t *)OT_CLI_COAPS_TRUSTED_ROOT_CERTIFICATE, + sizeof(OT_CLI_COAPS_TRUSTED_ROOT_CERTIFICATE))); + mUseCertificate = true; + + mInterpreter.mServer->OutputFormat("Coap Secure set own .X509 certificate: "); +#else + ExitNow(error = OT_ERROR_DISABLED_FEATURE); +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + } + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + else if (strcmp(argv[0], "connect") == 0) + { + // Destination IPv6 address + if (argc > 1) + { + // parse ipAddr + SuccessOrExit(error = otIp6AddressFromString(argv[1], &coapDestinationIp)); + memset(&messageInfo, 0, sizeof(messageInfo)); + messageInfo.mPeerAddr = coapDestinationIp; + messageInfo.mPeerPort = OT_DEFAULT_COAP_SECURE_PORT; + messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD; + + // check for port specification + if (argc > 2) + { + error = Interpreter::ParseLong(argv[2], value); + SuccessOrExit(error); + messageInfo.mPeerPort = static_cast(value); + } + + SuccessOrExit(error = otCoapSecureConnect(mInterpreter.mInstance, &messageInfo, + &CoapsSecure::HandleClientConnect, this)); + mInterpreter.mServer->OutputFormat("Coap Secure connect: "); + } + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + else if (strcmp(argv[0], "resource") == 0) + { + mResource.mUriPath = mUriPath; + mResource.mContext = this; + mResource.mHandler = &CoapsSecure::HandleServerResponse; + + if (argc > 1) + { + strlcpy(mUriPath, argv[1], kMaxUriLength); + SuccessOrExit(error = otCoapSecureAddResource(mInterpreter.mInstance, &mResource)); + } + + mInterpreter.mServer->OutputFormat("Resource name is '%s': ", mResource.mUriPath); + } + else if (strcmp(argv[0], "disconnect") == 0) + { + SuccessOrExit(error = otCoapSecureDisconnect(mInterpreter.mInstance)); + } + else if (strcmp(argv[0], "stop") == 0) + { + if (otCoapSecureIsConncetionActive(mInterpreter.mInstance)) + { + error = otCoapSecureDisconnect(mInterpreter.mInstance); + mShutdownFlag = true; + } + else + { + SuccessOrExit(error = Stop()); + } + } + else if (strcmp(argv[0], "help") == 0) + { + mInterpreter.mServer->OutputFormat("CLI CoAPS help:\r\n\r\n"); + mInterpreter.mServer->OutputFormat(">'coaps start (false)' " + ": start coap secure service, false disable peer cert verification\r\n"); + mInterpreter.mServer->OutputFormat(">'coaps set psk ' " + ": set Preshared Key and Client Identity (Ciphresuit PSK_AES128)\r\n"); + mInterpreter.mServer->OutputFormat(">'coaps set x509' " + ": set X509 Cert und Private Key (Ciphresuit ECDHE_ECDSA_AES128)\r\n"); + mInterpreter.mServer->OutputFormat(">'coaps connect (port)' " + ": start dtls session with a server\r\n"); + mInterpreter.mServer->OutputFormat(">'coaps get' 'coaps put' 'coaps post' 'coaps delete' " + ": interact with coap resource from server, ipv6 is not need as client\r\n"); + mInterpreter.mServer->OutputFormat( + " >> args:(ipv6_addr_srv) and, if you have payload: \r\n"); + mInterpreter.mServer->OutputFormat(">'coaps resource ' " + ": add a coap server resource with 'helloWorld' as content.\r\n"); + mInterpreter.mServer->OutputFormat(">'coaps disconnect' " + ": stop dtls session with a server\r\n"); + mInterpreter.mServer->OutputFormat(">'coaps stop' " + ": stop coap secure service\r\n"); + mInterpreter.mServer->OutputFormat("\r\n legend: <>: must, (): opt " + "\r\n"); + mInterpreter.mServer->OutputFormat("\r\n"); + } + else + { + error = ProcessRequest(argc, argv); + } + +exit: + return error; +} + +otError CoapsSecure::Stop(void) +{ + otError error = OT_ERROR_ABORT; + otCoapRemoveResource(mInterpreter.mInstance, &mResource); + error = otCoapSecureStop(mInterpreter.mInstance); + mInterpreter.mServer->OutputFormat("Coap Secure service stopped: "); + return error; +} + +void OTCALL CoapsSecure::HandleClientConnect(bool aConnected, void *aContext) +{ + static_cast(aContext)->HandleClientConnect(aConnected); +} + +void CoapsSecure::HandleClientConnect(bool aConnected) +{ + if (aConnected) + { + mInterpreter.mServer->OutputFormat("CoAP Secure connected!\r\n> "); + } + else + { + if (!mShutdownFlag) + { + mInterpreter.mServer->OutputFormat("CoAP Secure not connected or disconnected.\r\n> "); + } + else + { + mInterpreter.mServer->OutputFormat("CoAP Secure disconnected before stop.\r\n> "); + if (Stop() == OT_ERROR_NONE) + { + mInterpreter.mServer->OutputFormat(" Done\r\n> "); + } + else + { + mInterpreter.mServer->OutputFormat(" With error\r\n> "); + } + mShutdownFlag = false; + } + } + + OT_UNUSED_VARIABLE(aConnected); +} + +void OTCALL CoapsSecure::HandleServerResponse(void * aContext, + otCoapHeader * aHeader, + otMessage * aMessage, + const otMessageInfo *aMessageInfo) +{ + static_cast(aContext)->HandleServerResponse(aHeader, aMessage, aMessageInfo); +} + +void CoapsSecure::HandleServerResponse(otCoapHeader *aHeader, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + otError error = OT_ERROR_NONE; + otCoapHeader responseHeader; + otMessage * responseMessage; + otCoapCode responseCode = OT_COAP_CODE_EMPTY; + char responseContent[] = "helloWorld"; + + mInterpreter.mServer->OutputFormat( + "Received coap secure request from [%x:%x:%x:%x:%x:%x:%x:%x]: ", + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[0]), HostSwap16(aMessageInfo->mSockAddr.mFields.m16[1]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[2]), HostSwap16(aMessageInfo->mSockAddr.mFields.m16[3]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[4]), HostSwap16(aMessageInfo->mSockAddr.mFields.m16[5]), + HostSwap16(aMessageInfo->mSockAddr.mFields.m16[6]), HostSwap16(aMessageInfo->mSockAddr.mFields.m16[7])); + + switch (otCoapHeaderGetCode(aHeader)) + { + case OT_COAP_CODE_GET: + mInterpreter.mServer->OutputFormat("GET"); + break; + + case OT_COAP_CODE_DELETE: + mInterpreter.mServer->OutputFormat("DELETE"); + break; + + case OT_COAP_CODE_PUT: + mInterpreter.mServer->OutputFormat("PUT"); + break; + + case OT_COAP_CODE_POST: + mInterpreter.mServer->OutputFormat("POST"); + break; + + default: + mInterpreter.mServer->OutputFormat("Undefined\r\n"); + return; + } + + PrintPayload(aMessage); + + if ((otCoapHeaderGetType(aHeader) == OT_COAP_TYPE_CONFIRMABLE) || otCoapHeaderGetCode(aHeader) == OT_COAP_CODE_GET) + { + if (otCoapHeaderGetCode(aHeader) == OT_COAP_CODE_GET) + { + responseCode = OT_COAP_CODE_CONTENT; + } + else + { + responseCode = OT_COAP_CODE_VALID; + } + + otCoapHeaderInit(&responseHeader, OT_COAP_TYPE_ACKNOWLEDGMENT, responseCode); + otCoapHeaderSetMessageId(&responseHeader, otCoapHeaderGetMessageId(aHeader)); + otCoapHeaderSetToken(&responseHeader, otCoapHeaderGetToken(aHeader), otCoapHeaderGetTokenLength(aHeader)); + + if (otCoapHeaderGetCode(aHeader) == OT_COAP_CODE_GET) + { + otCoapHeaderSetPayloadMarker(&responseHeader); + } + + responseMessage = otCoapNewMessage(mInterpreter.mInstance, &responseHeader); + VerifyOrExit(responseMessage != NULL, error = OT_ERROR_NO_BUFS); + + if (otCoapHeaderGetCode(aHeader) == OT_COAP_CODE_GET) + { + SuccessOrExit(error = otMessageAppend(responseMessage, &responseContent, sizeof(responseContent))); + } + + SuccessOrExit(error = otCoapSecureSendResponse(mInterpreter.mInstance, responseMessage, aMessageInfo)); + } + +exit: + + if (error != OT_ERROR_NONE && responseMessage != NULL) + { + mInterpreter.mServer->OutputFormat("Cannot send coap secure response message: Error %d: %s\r\n", error, + otThreadErrorToString(error)); + otMessageFree(responseMessage); + } + else if (responseCode >= OT_COAP_CODE_RESPONSE_MIN) + { + mInterpreter.mServer->OutputFormat("coap secure response sent successfully!\r\n"); + } +} + +otError CoapsSecure::ProcessRequest(int argc, char *argv[]) +{ + otError error = OT_ERROR_NONE; + otMessage * message = NULL; + otMessageInfo messageInfo; + otCoapHeader header; + uint16_t payloadLength = 0; + uint8_t indexShifter = 0; + + // Default parameters + char coapUri[kMaxUriLength] = "test"; + otCoapType coapType = OT_COAP_TYPE_NON_CONFIRMABLE; + otCoapCode coapCode = OT_COAP_CODE_GET; + otIp6Address coapDestinationIp; + + VerifyOrExit(argc > 0, error = OT_ERROR_INVALID_ARGS); + + // CoAP-Code + if (strcmp(argv[0], "get") == 0) + { + coapCode = OT_COAP_CODE_GET; + } + else if (strcmp(argv[0], "post") == 0) + { + coapCode = OT_COAP_CODE_POST; + } + else if (strcmp(argv[0], "put") == 0) + { + coapCode = OT_COAP_CODE_PUT; + } + else if (strcmp(argv[0], "delete") == 0) + { + coapCode = OT_COAP_CODE_DELETE; + } + else + { + ExitNow(error = OT_ERROR_PARSE); + } + + // Destination IPv6 address + if (argc > 1) + { + error = otIp6AddressFromString(argv[1], &coapDestinationIp); + } + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + + // Destination IPv6 address not need as client + // if no IPv6 is entered, so ignore it and go away + if (error == OT_ERROR_NONE) + { + indexShifter = 0; + } + else + { + indexShifter = 1; + } + + // CoAP-URI + if (argc > (2 - indexShifter)) + { + strlcpy(coapUri, argv[2 - indexShifter], kMaxUriLength); + } + + // CoAP-Type + if (argc > (3 - indexShifter)) + { + if (strcmp(argv[3 - indexShifter], "con") == 0) + { + coapType = OT_COAP_TYPE_CONFIRMABLE; + } + } + + otCoapHeaderInit(&header, coapType, coapCode); + otCoapHeaderGenerateToken(&header, ot::Coap::Header::kDefaultTokenLength); + SuccessOrExit(error = otCoapHeaderAppendUriPathOptions(&header, coapUri)); + + if (argc > (4 - indexShifter)) + { + payloadLength = static_cast(strlen(argv[4 - indexShifter])); + + if (payloadLength > 0) + { + otCoapHeaderSetPayloadMarker(&header); + } + } + + message = otCoapNewMessage(mInterpreter.mInstance, &header); + VerifyOrExit(message != NULL, error = OT_ERROR_NO_BUFS); + + // add payload + if (payloadLength > 0) + { + SuccessOrExit(error = otMessageAppend(message, argv[4 - indexShifter], payloadLength)); + } + + memset(&messageInfo, 0, sizeof(messageInfo)); + messageInfo.mPeerAddr = coapDestinationIp; + messageInfo.mPeerPort = OT_DEFAULT_COAP_PORT; + messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD; + + if ((coapType == OT_COAP_TYPE_CONFIRMABLE) || (coapCode == OT_COAP_CODE_GET)) + { + error = otCoapSecureSendRequest(mInterpreter.mInstance, message, &CoapsSecure::HandleClientResponse, this); + } + else + { + error = otCoapSecureSendRequest(mInterpreter.mInstance, message, NULL, NULL); + } + + mInterpreter.mServer->OutputFormat("Sending coap secure request: "); + +exit: + + if ((error != OT_ERROR_NONE) && (message != NULL)) + { + otMessageFree(message); + } + + return error; +} + +void OTCALL CoapsSecure::HandleClientResponse(void * aContext, + otCoapHeader * aHeader, + otMessage * aMessage, + const otMessageInfo *aMessageInfo, + otError aError) +{ + static_cast(aContext)->HandleClientResponse(aHeader, aMessage, aMessageInfo, aError); +} + +void CoapsSecure::HandleClientResponse(otCoapHeader * aHeader, + otMessage * aMessage, + const otMessageInfo *aMessageInfo, + otError aError) +{ + if (aError != OT_ERROR_NONE) + { + mInterpreter.mServer->OutputFormat("Error receiving coap secure response message: Error %d: %s\r\n", aError, + otThreadErrorToString(aError)); + } + else + { + mInterpreter.mServer->OutputFormat("Received coap secure response"); + PrintHeaderInfos(aHeader); + PrintPayload(aMessage); + } + + OT_UNUSED_VARIABLE(aHeader); + OT_UNUSED_VARIABLE(aMessageInfo); +} + +#if CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER +void OTCALL CoapsSecure::DefaultHandle(void * aContext, + otCoapHeader * aHeader, + otMessage * aMessage, + const otMessageInfo *aMessageInfo) +{ + static_cast(aContext)->DefaultHandle(aHeader, aMessage, aMessageInfo); +} + +void CoapsSecure::DefaultHandle(otCoapHeader *aHeader, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + OT_UNUSED_VARIABLE(aMessage); + + otError error = OT_ERROR_NONE; + otCoapHeader responseHeader; + otMessage * responseMessage; + + if (otCoapHeaderGetType(aHeader) == OT_COAP_TYPE_CONFIRMABLE || otCoapHeaderGetCode(aHeader) == OT_COAP_CODE_GET) + { + otCoapHeaderInit(&responseHeader, OT_COAP_TYPE_NON_CONFIRMABLE, OT_COAP_CODE_NOT_FOUND); + otCoapHeaderSetMessageId(&responseHeader, otCoapHeaderGetMessageId(aHeader)); + otCoapHeaderSetToken(&responseHeader, otCoapHeaderGetToken(aHeader), otCoapHeaderGetTokenLength(aHeader)); + + responseMessage = otCoapNewMessage(mInterpreter.mInstance, &responseHeader); + VerifyOrExit(responseMessage != NULL, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = otCoapSecureSendResponse(mInterpreter.mInstance, responseMessage, aMessageInfo)); + } + +exit: + + mInterpreter.mServer->OutputFormat("Default handler called.\r\n> "); +} +#endif // CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE diff --git a/src/cli/cli_coap_secure.hpp b/src/cli/cli_coap_secure.hpp new file mode 100644 index 000000000..e9c9ee4ab --- /dev/null +++ b/src/cli/cli_coap_secure.hpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file contains definitions for a simple CLI CoAP Secure server and client. + */ + +#ifndef CLI_COAP_SECURE_HPP_ +#define CLI_COAP_SECURE_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +#include "coap/coap_header.hpp" +#include "coap/coap_secure.hpp" + +/** + * to test the default handler for not handled requests set to 1. + */ +#define CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER 0 + +namespace ot { +namespace Cli { + +class Interpreter; + +/** + * This class implements the CLI CoAP Secure server and client. + * + */ +class CoapsSecure +{ +public: + /** + * Constructor + * + * @param[in] aInterpreter The CLI interpreter. + * + */ + CoapsSecure(Interpreter &aInterpreter); + + /** + * This method interprets a list of CLI arguments. + * + * @param[in] argc The number of elements in argv. + * @param[in] argv A pointer to an array of command line arguments. + * + */ + otError Process(int argc, char *argv[]); + +private: + enum + { + kMaxUriLength = 32, + kMaxBufferSize = 16, + kPskMaxLength = 32, + kPskIdMaxLength = 32 + }; + + void PrintHeaderInfos(otCoapHeader *aHeader) const; + + void PrintPayload(otMessage *aMessage) const; + + otError ProcessRequest(int argc, char *argv[]); + + otError Stop(void); + + static void OTCALL HandleServerResponse(void * aContext, + otCoapHeader * aHeader, + otMessage * aMessage, + const otMessageInfo *aMessageInfo); + void HandleServerResponse(otCoapHeader *aHeader, otMessage *aMessage, const otMessageInfo *aMessageInfo); + + static void OTCALL HandleClientResponse(void * aContext, + otCoapHeader * aHeader, + otMessage * aMessage, + const otMessageInfo *aMessageInfo, + otError aError); + void HandleClientResponse(otCoapHeader * aHeader, + otMessage * aMessage, + const otMessageInfo *aMessageInfo, + otError aError); + +#if CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER + static void OTCALL DefaultHandle(void * aContext, + otCoapHeader * aHeader, + otMessage * aMessage, + const otMessageInfo *aMessageInfo); + void DefaultHandle(otCoapHeader *aHeader, otMessage *aMessage, const otMessageInfo *aMessageInfo); +#endif // CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER + + static void OTCALL HandleClientConnect(bool aConnected, void *aContext); + void HandleClientConnect(bool aConnected); + + otCoapResource mResource; + char mUriPath[kMaxUriLength]; + + Interpreter &mInterpreter; + + bool mShutdownFlag; + bool mUseCertificate; + uint8_t mPsk[kPskMaxLength]; + uint8_t mPskLength; + uint8_t mPskId[kPskIdMaxLength]; + uint8_t mPskIdLength; +}; + +} // namespace Cli +} // namespace ot + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +#endif // CLI_COAP_SECURE_HPP_ diff --git a/src/cli/x509_cert_key.hpp b/src/cli/x509_cert_key.hpp new file mode 100644 index 000000000..cd7032610 --- /dev/null +++ b/src/cli/x509_cert_key.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file contains the X.509 certificate and private key for Application + * CoAP Secure use with cipher suite ECDHE_ECDSA_WITH_AES_128_CCM8. + */ + +#ifndef SRC_CLI_X509_CERT_KEY_HPP_ +#define SRC_CLI_X509_CERT_KEY_HPP_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) + +/**SERVER + * Generate your own private key and certificate using openssl. + * + * 1. Generate a EC (Elliptic Curve) Private Key. + * 'openssl ecparam -genkey -out myECKey.pem -name prime256v1 -noout' + * 2. Generate a .X509 Certificate (Contains Public Key). + * 'openssl req -x509 -new -key myECKey.pem -out myX509Cert.pem' + */ + +/** + * place your X.509 certificate (PEM format) for ssl session + * with ECDHE_ECDSA_WITH_AES_128_CCM_8 here. + */ +#define OT_CLI_COAPS_X509_CERT \ +"-----BEGIN CERTIFICATE-----\r\n" \ +"MIIBrTCCAVICBgDRArfDJTAKBggqhkjOPQQDAjBcMQswCQYDVQQGEwJaWTESMBAG\r\n" \ +"A1UECAwJWW91clN0YXRlMRAwDgYDVQQKDAdZb3VyT3JnMRQwEgYDVQQLDAtZb3Vy\r\n" \ +"T3JnVW5pdDERMA8GA1UEAwwIVmVuZG9yQ0EwIBcNMTgwNzEzMTIzNzA3WhgPMjI5\r\n" \ +"MjA0MjYxMjM3MDdaMGExCzAJBgNVBAYTAlpZMRIwEAYDVQQIDAlZb3VyU3RhdGUx\r\n" \ +"EDAOBgNVBAoMB1lvdXJPcmcxFDASBgNVBAsMC1lvdXJPcmdVbml0MRYwFAYDVQQD\r\n" \ +"DA1QWEMzLkU3NS0xMDBBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIUtlV99w\r\n" \ +"OggiASflg6CVsGMzXMXYrNgQ1piLIybCkrq+YoqJ3mwcbJHWlvxGPxNIQw6i8kzK\r\n" \ +"bkC642ZWgBT5MzAKBggqhkjOPQQDAgNJADBGAiEA/1yk69A+37kLBvdOWPDRXGwe\r\n" \ +"0AoICTGaLqzB3cF5mtACIQC28WwmzHb5gqe3nOPAM73py1v17EXZj07PU89BAEcb\r\n" \ +"yg==\r\n" \ +"-----END CERTIFICATE-----\r\n" + +/** + * place your private key (PEM format) for ssl session + * with ECDHE_ECDSA_WITH_AES_128_CCM_8 here. + */ +#define OT_CLI_COAPS_PRIV_KEY \ +"-----BEGIN EC PRIVATE KEY-----\r\n" \ +"MHcCAQEEIFYQh2R7M48qOHePw+VE4b034UlZmWWC/iNAK34sQbucoAoGCCqGSM49\r\n" \ +"AwEHoUQDQgAEIUtlV99wOggiASflg6CVsGMzXMXYrNgQ1piLIybCkrq+YoqJ3mwc\r\n" \ +"bJHWlvxGPxNIQw6i8kzKbkC642ZWgBT5Mw==\r\n" \ +"-----END EC PRIVATE KEY-----\r\n" + +/** + * Place peers CA certificate (PEM format) here. + * It's necessary to validate the peers certificate. If you haven't a + * CA certificate, you must run the coaps without checking certificate. + */ +#define OT_CLI_COAPS_TRUSTED_ROOT_CERTIFICATE \ +"-----BEGIN CERTIFICATE-----\r\n" \ +"MIICDzCCAbWgAwIBAgIESZYC0jAKBggqhkjOPQQDAjBcMQswCQYDVQQGEwJaWTES\r\n" \ +"MBAGA1UECAwJWW91clN0YXRlMRAwDgYDVQQKDAdZb3VyT3JnMRQwEgYDVQQLDAtZ\r\n" \ +"b3VyT3JnVW5pdDERMA8GA1UEAwwIVmVuZG9yQ0EwIBcNMTgwNzEzMTE1NjA5WhgP\r\n" \ +"MjI5MjA0MjYxMTU2MDlaMFwxCzAJBgNVBAYTAlpZMRIwEAYDVQQIDAlZb3VyU3Rh\r\n" \ +"dGUxEDAOBgNVBAoMB1lvdXJPcmcxFDASBgNVBAsMC1lvdXJPcmdVbml0MREwDwYD\r\n" \ +"VQQDDAhWZW5kb3JDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGAAuYcBIgP0\r\n" \ +"fMC1Bd+1nAH5S1goR0TaDAIadK4hULQr5LwziuDk9XTQaOTwmWB9iR1eiHC6RY8W\r\n" \ +"wyrGBbnEbzujYzBhMB0GA1UdDgQWBBQ+yCpIszhzbmXe2At1GofREjnBxjAfBgNV\r\n" \ +"HSMEGDAWgBQ+yCpIszhzbmXe2At1GofREjnBxjAPBgNVHRMBAf8EBTADAQH/MA4G\r\n" \ +"A1UdDwEB/wQEAwIBhjAKBggqhkjOPQQDAgNIADBFAiBW60XgdSRD24rbTgdneS+V\r\n" \ +"SHVix8LuXunPYW50LmxbrwIhAOw4gMroRIOS26y0TcND03FnyO3wBNF9MjM0hWKQ\r\n" \ +"JXk3\r\n" \ +"-----END CERTIFICATE-----\r\n" \ + +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* SRC_CLI_X509_CERT_KEY_HPP_ */ diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 51266cda0..be1b7de5a 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -107,6 +107,7 @@ SOURCES_COMMON = \ api/channel_monitor_api.cpp \ api/child_supervision_api.cpp \ api/coap_api.cpp \ + api/coap_secure_api.cpp \ api/commissioner_api.cpp \ api/crypto_api.cpp \ api/dataset_api.cpp \ diff --git a/src/core/api/coap_api.cpp b/src/core/api/coap_api.cpp index a120cc06e..b2c30bb54 100644 --- a/src/core/api/coap_api.cpp +++ b/src/core/api/coap_api.cpp @@ -113,6 +113,11 @@ otCoapCode otCoapHeaderGetCode(const otCoapHeader *aHeader) return static_cast(aHeader)->GetCode(); } +const char *otCoapHeaderCodeToString(const otCoapHeader *aHeader) +{ + return static_cast(aHeader)->CodeToString(); +} + uint16_t otCoapHeaderGetMessageId(const otCoapHeader *aHeader) { return static_cast(aHeader)->GetMessageId(); diff --git a/src/core/api/coap_secure_api.cpp b/src/core/api/coap_secure_api.cpp new file mode 100644 index 000000000..154deff78 --- /dev/null +++ b/src/core/api/coap_secure_api.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the OpenThread CoAP Secure API. + */ + +#include "openthread-core-config.h" + +#include + +#include "coap/coap_header.hpp" +#include "coap/coap_secure.hpp" +#include "common/instance.hpp" + +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +using namespace ot; + +otError otCoapSecureStart(otInstance *aInstance, uint16_t aPort, void *aContext) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().Start(aPort, NULL, aContext); +} + +otError otCoapSecureSetCertificate(otInstance * aInstance, + const uint8_t *aX509Cert, + uint32_t aX509Length, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength) +{ +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + Instance &instance = *static_cast(aInstance); + + if (aX509Cert == NULL || aX509Length == 0 || aPrivateKey == NULL || aPrivateKeyLength == 0) + { + return OT_ERROR_INVALID_ARGS; + } + + return instance.GetApplicationCoapSecure().SetCertificate(aX509Cert, aX509Length, aPrivateKey, aPrivateKeyLength); +#else + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aX509Cert); + OT_UNUSED_VARIABLE(aX509Length); + OT_UNUSED_VARIABLE(aPrivateKey); + OT_UNUSED_VARIABLE(aPrivateKeyLength); + + return OT_ERROR_DISABLED_FEATURE; +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +} + +otError otCoapSecureSetCaCertificateChain(otInstance * aInstance, + const uint8_t *aX509CaCertificateChain, + uint32_t aX509CaCertChainLength) +{ +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + Instance &instance = *static_cast(aInstance); + + if (aX509CaCertificateChain == NULL || aX509CaCertChainLength == 0) + { + return OT_ERROR_INVALID_ARGS; + } + + return instance.GetApplicationCoapSecure().SetCaCertificateChain(aX509CaCertificateChain, aX509CaCertChainLength); +#else + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aX509CaCertificateChain); + OT_UNUSED_VARIABLE(aX509CaCertChainLength); + + return OT_ERROR_DISABLED_FEATURE; +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +} + +otError otCoapSecureSetPsk(otInstance * aInstance, + const uint8_t *aPsk, + uint16_t aPskLength, + const uint8_t *aPskIdentity, + uint16_t aPskIdLength) +{ +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + Instance &instance = *static_cast(aInstance); + + if (aPsk == NULL || aPskLength == 0 || aPskIdentity == NULL || aPskIdLength == 0) + { + return OT_ERROR_INVALID_ARGS; + } + + return instance.GetApplicationCoapSecure().SetPreSharedKey(aPsk, aPskLength, aPskIdentity, aPskIdLength); +#else + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aPsk); + OT_UNUSED_VARIABLE(aPskLength); + OT_UNUSED_VARIABLE(aPskIdentity); + OT_UNUSED_VARIABLE(aPskIdLength); + + return OT_ERROR_DISABLED_FEATURE; +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +} + +otError otCoapSecureGetPeerCertificateBase64(otInstance * aInstance, + unsigned char *aPeerCert, + uint64_t * aCertLength, + uint64_t aCertBufferSize) +{ +#ifdef MBEDTLS_BASE64_C + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().GetPeerCertificateBase64(aPeerCert, (size_t *)aCertLength, + (size_t)aCertBufferSize); +#else + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aPeerCert); + OT_UNUSED_VARIABLE(aCertLength); + OT_UNUSED_VARIABLE(aCertBufferSize); + + return OT_ERROR_DISABLED_FEATURE; +#endif // MBEDTLS_BASE64_C +} + +void otCoapSecureSetSslAuthMode(otInstance *aInstance, bool aVerifyPeerCertificate) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetApplicationCoapSecure().SetSslAuthMode(aVerifyPeerCertificate); +} + +otError otCoapSecureConnect(otInstance * aInstance, + const otMessageInfo * aMessageInfo, + otHandleCoapSecureClientConnect aHandler, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().Connect(*static_cast(aMessageInfo), aHandler, + aContext); +} + +otError otCoapSecureDisconnect(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().Disconnect(); +} + +bool otCoapSecureIsConnected(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().IsConnected(); +} + +bool otCoapSecureIsConncetionActive(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().IsConnectionActive(); +} + +otError otCoapSecureStop(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().Stop(); +} + +otError otCoapSecureSendRequest(otInstance * aInstance, + otMessage * aMessage, + otCoapResponseHandler aHandler = NULL, + void * aContext = NULL) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().SendMessage(*static_cast(aMessage), aHandler, aContext); +} + +otError otCoapSecureAddResource(otInstance *aInstance, otCoapResource *aResource) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().AddResource(*static_cast(aResource)); +} + +void otCoapSecureRemoveResource(otInstance *aInstance, otCoapResource *aResource) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetApplicationCoapSecure().RemoveResource(*static_cast(aResource)); +} + +void otCoapSecureSetClientConnectedCallback(otInstance * aInstance, + otHandleCoapSecureClientConnect aHandler, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetApplicationCoapSecure().SetClientConnectedCallback(aHandler, aContext); +} + +void otCoapSecureSetDefaultHandler(otInstance *aInstance, otCoapRequestHandler aHandler, void *aContext) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetApplicationCoapSecure().SetDefaultHandler(aHandler, aContext); +} + +otError otCoapSecureSendResponse(otInstance *aInstance, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetApplicationCoapSecure().SendMessage(*static_cast(aMessage), + *static_cast(aMessageInfo)); +} + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE diff --git a/src/core/coap/coap_header.cpp b/src/core/coap/coap_header.cpp index 0149edb5f..e0e2e4a77 100644 --- a/src/core/coap/coap_header.cpp +++ b/src/core/coap/coap_header.cpp @@ -432,5 +432,102 @@ void Header::SetDefaultResponseHeader(const Header &aRequestHeader) SetToken(aRequestHeader.GetToken(), aRequestHeader.GetTokenLength()); } +#if OPENTHREAD_ENABLE_APPLICATION_COAP +const char *Header::CodeToString(void) const +{ + const char *mCodeString; + + switch (mHeader.mFields.mCode) + { + case OT_COAP_CODE_INTERNAL_ERROR: + mCodeString = "InternalError"; + break; + case OT_COAP_CODE_METHOD_NOT_ALLOWED: + mCodeString = "MethodNotAllowed"; + break; + case OT_COAP_CODE_CONTENT: + mCodeString = "Content"; + break; + case OT_COAP_CODE_EMPTY: + mCodeString = "Empty"; + break; + case OT_COAP_CODE_GET: + mCodeString = "Get"; + break; + case OT_COAP_CODE_POST: + mCodeString = "Post"; + break; + case OT_COAP_CODE_PUT: + mCodeString = "Put"; + break; + case OT_COAP_CODE_DELETE: + mCodeString = "Delete"; + break; + case OT_COAP_CODE_NOT_FOUND: + mCodeString = "NotFound"; + break; + case OT_COAP_CODE_UNSUPPORTED_FORMAT: + mCodeString = "UnsupportedFormat"; + break; + case OT_COAP_CODE_RESPONSE_MIN: + mCodeString = "ResponseMin"; + break; + case OT_COAP_CODE_CREATED: + mCodeString = "Created"; + break; + case OT_COAP_CODE_DELETED: + mCodeString = "Deleted"; + break; + case OT_COAP_CODE_VALID: + mCodeString = "Valid"; + break; + case OT_COAP_CODE_CHANGED: + mCodeString = "Changed"; + break; + case OT_COAP_CODE_BAD_REQUEST: + mCodeString = "BadRequest"; + break; + case OT_COAP_CODE_UNAUTHORIZED: + mCodeString = "Unauthorized"; + break; + case OT_COAP_CODE_BAD_OPTION: + mCodeString = "BadOption"; + break; + case OT_COAP_CODE_FORBIDDEN: + mCodeString = "Forbidden"; + break; + case OT_COAP_CODE_NOT_ACCEPTABLE: + mCodeString = "NotAcceptable"; + break; + case OT_COAP_CODE_PRECONDITION_FAILED: + mCodeString = "PreconditionFailed"; + break; + case OT_COAP_CODE_REQUEST_TOO_LARGE: + mCodeString = "RequestTooLarge"; + break; + case OT_COAP_CODE_NOT_IMPLEMENTED: + mCodeString = "NotImplemented"; + break; + case OT_COAP_CODE_BAD_GATEWAY: + mCodeString = "BadGateway"; + break; + case OT_COAP_CODE_SERVICE_UNAVAILABLE: + mCodeString = "ServiceUnavailable"; + break; + case OT_COAP_CODE_GATEWAY_TIMEOUT: + mCodeString = "GatewayTimeout"; + break; + case OT_COAP_CODE_PROXY_NOT_SUPPORTED: + mCodeString = "ProxyNotSupported"; + break; + default: + mCodeString = "Unknown"; + break; + } + + return mCodeString; +} +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP + } // namespace Coap } // namespace ot diff --git a/src/core/coap/coap_header.hpp b/src/core/coap/coap_header.hpp index 4f00e4403..d61017080 100644 --- a/src/core/coap/coap_header.hpp +++ b/src/core/coap/coap_header.hpp @@ -175,6 +175,16 @@ public: */ void SetCode(Code aCode) { mHeader.mFields.mCode = static_cast(aCode); } +#if OPENTHREAD_ENABLE_APPLICATION_COAP + /** + * This method returns the CoAP Code as human readable string. + * + * @ returns The CoAP Code as string. + * + */ + const char *CodeToString(void) const; +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP + /** * This method returns the Message ID value. * diff --git a/src/core/coap/coap_secure.cpp b/src/core/coap/coap_secure.cpp index d6db38d98..23983a03a 100644 --- a/src/core/coap/coap_secure.cpp +++ b/src/core/coap/coap_secure.cpp @@ -54,9 +54,27 @@ CoapSecure::CoapSecure(Instance &aInstance) , mTransportContext(NULL) , mTransmitMessage(NULL) , mTransmitTask(aInstance, &CoapSecure::HandleUdpTransmit, this) + , mLayerTwoSecurity(false) { } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +CoapSecure::CoapSecure(Instance & aInstance, + Tasklet::Handler aUdpTransmitHandle, + Timer::Handler aRetransmissionTimer, + Timer::Handler aResponsesQueueTimer) + : CoapBase(aInstance, aRetransmissionTimer, aResponsesQueueTimer) + , mConnectedCallback(NULL) + , mConnectedContext(NULL) + , mTransportCallback(NULL) + , mTransportContext(NULL) + , mTransmitMessage(NULL) + , mTransmitTask(aInstance, aUdpTransmitHandle, this) + , mLayerTwoSecurity(true) +{ +} +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + otError CoapSecure::Start(uint16_t aPort, TransportCallback aCallback, void *aContext) { otError error = OT_ERROR_NONE; @@ -135,6 +153,53 @@ otError CoapSecure::SetPsk(const uint8_t *aPsk, uint8_t aPskLength) return GetNetif().GetDtls().SetPsk(aPsk, aPskLength); } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +otError CoapSecure::SetCertificate(const uint8_t *aX509Cert, + uint32_t aX509Length, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength) +{ + return GetNetif().GetDtls().SetCertificate(aX509Cert, aX509Length, aPrivateKey, aPrivateKeyLength); +} + +otError CoapSecure::SetCaCertificateChain(const uint8_t *aX509CaCertificateChain, uint32_t aX509CaCertChainLenth) +{ + return GetNetif().GetDtls().SetCaCertificateChain(aX509CaCertificateChain, aX509CaCertChainLenth); +} +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +otError CoapSecure::SetPreSharedKey(const uint8_t *aPsk, + uint16_t aPskLength, + const uint8_t *aPskIdentity, + uint16_t aPskIdLength) +{ + return GetNetif().GetDtls().SetPreSharedKey(aPsk, aPskLength, aPskIdentity, aPskIdLength); +} +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + +#ifdef MBEDTLS_BASE64_C +otError CoapSecure::GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize) +{ + return GetNetif().GetDtls().GetPeerCertificateBase64(aPeerCert, aCertLength, aCertBufferSize); +} +#endif // MBEDTLS_BASE64_C + +void CoapSecure::SetClientConnectedCallback(ConnectedCallback aCallback, void *aContext) +{ + mConnectedCallback = aCallback; + mConnectedContext = aContext; +} + +void CoapSecure::SetSslAuthMode(bool aVerifyPeerCertificate) +{ + GetNetif().GetDtls().SetSslAuthMode(aVerifyPeerCertificate); +} + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + otError CoapSecure::SendMessage(Message &aMessage, otCoapResponseHandler aHandler, void *aContext) { otError error = OT_ERROR_NONE; @@ -183,8 +248,8 @@ void CoapSecure::Receive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo mPeerAddress.SetSockPort(aMessageInfo.GetSockPort()); - netif.GetDtls().Start(false, &CoapSecure::HandleDtlsConnected, &CoapSecure::HandleDtlsReceive, - CoapSecure::HandleDtlsSend, this); + VerifyOrExit(netif.GetDtls().Start(false, &CoapSecure::HandleDtlsConnected, &CoapSecure::HandleDtlsReceive, + CoapSecure::HandleDtlsSend, this) == OT_ERROR_NONE); } else { @@ -248,7 +313,7 @@ otError CoapSecure::HandleDtlsSend(const uint8_t *aBuf, uint16_t aLength, uint8_ { VerifyOrExit((mTransmitMessage = mSocket.NewMessage(0)) != NULL, error = OT_ERROR_NO_BUFS); mTransmitMessage->SetSubType(aMessageSubType); - mTransmitMessage->SetLinkSecurityEnabled(false); + mTransmitMessage->SetLinkSecurityEnabled(mLayerTwoSecurity); } SuccessOrExit(error = mTransmitMessage->Append(aBuf, aLength)); @@ -312,6 +377,33 @@ void CoapSecure::HandleResponsesQueueTimer(Timer &aTimer) aTimer.GetOwner().CoapBase::HandleResponsesQueueTimer(); } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +ApplicationCoapSecure::ApplicationCoapSecure(Instance &aInstance) + : CoapSecure(aInstance, + &ApplicationCoapSecure::HandleUdpTransmit, + &ApplicationCoapSecure::HandleRetransmissionTimer, + &ApplicationCoapSecure::HandleResponsesQueueTimer) +{ +} + +void ApplicationCoapSecure::HandleUdpTransmit(Tasklet &aTasklet) +{ + aTasklet.GetOwner().CoapSecure::HandleUdpTransmit(); +} + +void ApplicationCoapSecure::HandleRetransmissionTimer(Timer &aTimer) +{ + aTimer.GetOwner().CoapBase::HandleRetransmissionTimer(); +} + +void ApplicationCoapSecure::HandleResponsesQueueTimer(Timer &aTimer) +{ + aTimer.GetOwner().CoapBase::HandleResponsesQueueTimer(); +} + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + } // namespace Coap } // namespace ot diff --git a/src/core/coap/coap_secure.hpp b/src/core/coap/coap_secure.hpp index a5e2693c8..9b19affa1 100644 --- a/src/core/coap/coap_secure.hpp +++ b/src/core/coap/coap_secure.hpp @@ -34,6 +34,8 @@ #include "coap/coap.hpp" #include "meshcop/dtls.hpp" +#include + /** * @file * This file includes definitions for the secure CoAP agent. @@ -73,6 +75,23 @@ public: */ explicit CoapSecure(Instance &aInstance); +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** + * This constructor initializes the object. + * (Used for Application CoAPS) + * + * @param[in] aInstance A reference to the OpenThread instance. + * @param[in] aUdpTransmitHandle Handler for udp transmit. + * @param[in] aRetransmissionTimer Handler for retransmission. + * @param[in] aResponsesQueueTimer Handler for Queue Responses. + * + */ + explicit CoapSecure(Instance & aInstance, + Tasklet::Handler aUdpTransmitHandle, + Timer::Handler aRetransmissionTimer, + Timer::Handler aResponsesQueueTimer); +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * This method starts the secure CoAP agent. * @@ -106,8 +125,9 @@ public: /** * This method initializes DTLS session with a peer. * - * @param[in] aMessageInfo A reference to an address of the peer. - * @param[in] aCallback A pointer to a function that will be called once DTLS connection is established. + * @param[in] aMessageInfo A pointer to a message info structure. + * @param[in] aCallback A pointer to a function that will be called once DTLS connection is + * established. * * @retval OT_ERROR_NONE Successfully started DTLS connection. * @@ -160,6 +180,101 @@ public: */ otError SetPsk(const uint8_t *aPsk, uint8_t aPskLength); +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + /** + * This method sets the Pre Shared Key (PSK) for DTLS sessions + * identified by a PSK. + * DTLS mode "TLS with AES 128 CCM 8" for Application CoAPS. + * + * @param[in] aPsk A pointer to the PSK. + * @param[in] aPskLength The PSK char length. + * @param[in] aPskIdentity The Identity Name for the PSK. + * @param[in] aPskIdLength The PSK Identity Length. + * + * @retval OT_ERROR_NONE Successfully set the PSK. + * + */ + otError SetPreSharedKey(const uint8_t *aPsk, + uint16_t aPskLength, + const uint8_t *aPskIdentity, + uint16_t aPskIdLength); +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + /** + * This method sets a X509 certificate with corresponding private key for DTLS session. + * + * DTLS mode "ECDHE ECDSA with AES 128 CCM 8" for Application CoAPS. + * + * @param[in] aX509Certificate A pointer to the PEM formatted X509 PEM certificate. + * @param[in] aX509CertLength The length of certificate. + * @param[in] aPrivateKey A pointer to the PEM formatted private key. + * @param[in] aPrivateKeyLength The length of the private key. + * + * @retval OT_ERROR_NONE Successfully set the x509 certificate with his private key. + * + */ + otError SetCertificate(const uint8_t *aX509Cert, + uint32_t aX509Length, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength); + + /** + * This method sets the trusted top level CAs. It is needed for validate the + * certificate of the peer. + * + * DTLS mode "ECDHE ECDSA with AES 128 CCM 8" for Application CoAPS. + * + * @param[in] aX509CaCertificateChain A pointer to the PEM formatted X509 CA chain. + * @param[in] aX509CaCertChainLength The length of chain. + * + * @retval OT_ERROR_NONE Successfully set the the trusted top level CAs. + * + */ + otError SetCaCertificateChain(const uint8_t *aX509CaCertificateChain, uint32_t aX509CaCertChainLength); +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +#ifdef MBEDTLS_BASE64_C + /** + * This method returns the peer x509 certificate base64 encoded. + * + * DTLS mode "ECDHE ECDSA with AES 128 CCM 8" for Application CoAPS. + * + * @param[out] aPeerCert A pointer to the base64 encoded certificate buffer. + * @param[out] aCertLength The length of the base64 encoded peer certificate. + * @param[in] aCertBufferSize The buffer size of aPeerCert. + * + * @retval OT_ERROR_NONE Successfully get the peer certificate. + * @retval OT_ERROR_NO_BUFS Can't allocate memory for certificate. + * + */ + otError GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize); +#endif // MBEDTLS_BASE64_C + + /** + * This method sets the connected callback to indicate, when + * a Client connect to the CoAP Secure server. + * + * @param[in] aCallback A pointer to a function that will be called once DTLS connection is + * established. + * @param[in] aContext A pointer to arbitrary context information. + * + */ + void SetClientConnectedCallback(ConnectedCallback aCallback, void *aContext); + + /** + * This method set the authentication mode for the coap secure connection. + * Disable or enable the verification of peer certificate. + * + * @param[in] aVerifyPeerCertificate true, if the peer certificate should verify. + * + */ + void SetSslAuthMode(bool aVerifyPeerCertificate); + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * This method sends a CoAP message over secure DTLS connection. * @@ -218,6 +333,9 @@ public: */ const Ip6::MessageInfo &GetPeerMessageInfo(void) const { return mPeerAddress; } +protected: + void HandleUdpTransmit(void); + private: virtual otError Send(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); @@ -231,7 +349,6 @@ private: otError HandleDtlsSend(const uint8_t *aBuf, uint16_t aLength, uint8_t aMessageSubType); static void HandleUdpTransmit(Tasklet &aTasklet); - void HandleUdpTransmit(void); static void HandleRetransmissionTimer(Timer &aTimer); static void HandleResponsesQueueTimer(Timer &aTimer); @@ -243,8 +360,35 @@ private: void * mTransportContext; Message * mTransmitMessage; Tasklet mTransmitTask; + + bool mLayerTwoSecurity : 1; }; +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + +/** + * This class implements the application CoAP Secure client and server. + * + */ +class ApplicationCoapSecure : public CoapSecure +{ +public: + /** + * This constructor initializes the object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit ApplicationCoapSecure(Instance &aInstance); + +private: + static void HandleRetransmissionTimer(Timer &aTimer); + static void HandleResponsesQueueTimer(Timer &aTimer); + static void HandleUdpTransmit(Tasklet &aTasklet); +}; + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + } // namespace Coap } // namespace ot diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index 2c7522d54..eee4b2eda 100644 --- a/src/core/common/instance.cpp +++ b/src/core/common/instance.cpp @@ -67,6 +67,9 @@ Instance::Instance(void) #if OPENTHREAD_ENABLE_APPLICATION_COAP , mApplicationCoap(*this) #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + , mApplicationCoapSecure(*this) +#endif #if OPENTHREAD_ENABLE_CHANNEL_MONITOR , mChannelMonitor(*this) #endif diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index 7d2320e02..a0fe62a1f 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -328,6 +328,16 @@ public: Coap::ApplicationCoap &GetApplicationCoap(void) { return mApplicationCoap; } #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** + * This method returns a reference to application COAP Secure object. + * + * @returns A reference to the application COAP Secure object. + * + */ + Coap::ApplicationCoapSecure &GetApplicationCoapSecure(void) { return mApplicationCoapSecure; } +#endif + #if OPENTHREAD_ENABLE_CHANNEL_MONITOR /** * This method returns a reference to ChannelMonitor object. @@ -426,6 +436,10 @@ private: Coap::ApplicationCoap mApplicationCoap; #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + Coap::ApplicationCoapSecure mApplicationCoapSecure; +#endif + #if OPENTHREAD_ENABLE_CHANNEL_MONITOR Utils::ChannelMonitor mChannelMonitor; #endif @@ -571,6 +585,13 @@ template <> inline Coap::ApplicationCoap &Instance::Get(void) } #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +template <> inline Coap::ApplicationCoapSecure &Instance::Get(void) +{ + return GetApplicationCoapSecure(); +} +#endif + #if OPENTHREAD_ENABLE_COMMISSIONER && OPENTHREAD_FTD template <> inline MeshCoP::Commissioner &Instance::Get(void) { diff --git a/src/core/meshcop/dtls.cpp b/src/core/meshcop/dtls.cpp index 6f71ea766..623865fc8 100644 --- a/src/core/meshcop/dtls.cpp +++ b/src/core/meshcop/dtls.cpp @@ -56,6 +56,7 @@ namespace MeshCoP { Dtls::Dtls(Instance &aInstance) : InstanceLocator(aInstance) , mPskLength(0) + , mVerifyPeerCertificate(true) , mStarted(false) , mTimer(aInstance, &Dtls::HandleTimer, this) , mTimerIntermediate(0) @@ -68,15 +69,42 @@ Dtls::Dtls(Instance &aInstance) , mSendHandler(NULL) , mContext(NULL) , mClient(false) + , mGuardTimerSet(false) , mMessageSubType(Message::kSubTypeNone) , mMessageDefaultSubType(Message::kSubTypeNone) { +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + mPreSharedKey = NULL; + mPreSharedKeyIdentity = NULL; + mPreSharedKeyIdLength = 0; + mPreSharedKeyLength = 0; +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + mCaChainSrc = NULL; + mCaChainLength = 0; + mOwnCertSrc = NULL; + mOwnCertLength = 0; + mPrivateKeySrc = NULL; + mPrivateKeyLength = 0; + memset(&mCaChain, 0, sizeof(mCaChain)); + memset(&mOwnCert, 0, sizeof(mOwnCert)); + memset(&mPrivateKey, 0, sizeof(mPrivateKey)); + mbedtls_x509_crt_init(&mCaChain); + mbedtls_x509_crt_init(&mOwnCert); + mbedtls_pk_init(&mPrivateKey); +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + + memset(mCipherSuites, 0, sizeof(mCipherSuites)); memset(mPsk, 0, sizeof(mPsk)); memset(&mEntropy, 0, sizeof(mEntropy)); memset(&mCtrDrbg, 0, sizeof(mCtrDrbg)); memset(&mSsl, 0, sizeof(mSsl)); memset(&mConf, 0, sizeof(mConf)); memset(&mCookieCtx, 0, sizeof(mCookieCtx)); + mProvisioningUrl.Init(); } @@ -111,9 +139,8 @@ otError Dtls::Start(bool aClient, SendHandler aSendHandler, void * aContext) { - static const int ciphersuites[2] = {0xC0FF, 0}; // EC-JPAKE cipher suite - otExtAddress eui64; - int rval; + otExtAddress eui64; + int rval; mConnectedHandler = aConnectedHandler; mReceiveHandler = aReceiveHandler; @@ -123,6 +150,9 @@ otError Dtls::Start(bool aClient, mReceiveMessage = NULL; mMessageSubType = Message::kSubTypeNone; + // do not handle new connection before guard time expired + VerifyOrExit(mGuardTimerSet == false, rval = MBEDTLS_ERR_SSL_TIMEOUT); + mbedtls_ssl_init(&mSsl); mbedtls_ssl_config_init(&mConf); mbedtls_ctr_drbg_init(&mCtrDrbg); @@ -141,10 +171,23 @@ otError Dtls::Start(bool aClient, MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_PRESET_DEFAULT); VerifyOrExit(rval == 0); +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + if (mVerifyPeerCertificate && mCipherSuites[0] == MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) + { + mbedtls_ssl_conf_authmode(&mConf, MBEDTLS_SSL_VERIFY_REQUIRED); + } + else + { + mbedtls_ssl_conf_authmode(&mConf, MBEDTLS_SSL_VERIFY_NONE); + } +#else + OT_UNUSED_VARIABLE(mVerifyPeerCertificate); +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + mbedtls_ssl_conf_rng(&mConf, mbedtls_ctr_drbg_random, &mCtrDrbg); mbedtls_ssl_conf_min_version(&mConf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); mbedtls_ssl_conf_max_version(&mConf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); - mbedtls_ssl_conf_ciphersuites(&mConf, ciphersuites); + mbedtls_ssl_conf_ciphersuites(&mConf, mCipherSuites); mbedtls_ssl_conf_export_keys_cb(&mConf, HandleMbedtlsExportKeys, this); mbedtls_ssl_conf_handshake_timeout(&mConf, 8000, 60000); mbedtls_ssl_conf_dbg(&mConf, HandleMbedtlsDebug, this); @@ -165,29 +208,108 @@ otError Dtls::Start(bool aClient, mbedtls_ssl_set_bio(&mSsl, this, &Dtls::HandleMbedtlsTransmit, HandleMbedtlsReceive, NULL); mbedtls_ssl_set_timer_cb(&mSsl, this, &Dtls::HandleMbedtlsSetTimer, HandleMbedtlsGetTimer); - rval = mbedtls_ssl_set_hs_ecjpake_password(&mSsl, mPsk, mPskLength); + if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + { + rval = mbedtls_ssl_set_hs_ecjpake_password(&mSsl, mPsk, mPskLength); + } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + else + { + rval = SetApplicationCoapSecureKeys(); + } +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE VerifyOrExit(rval == 0); mStarted = true; Process(); - otLogInfoMeshCoP(GetInstance(), "DTLS started"); + if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + { + otLogInfoMeshCoP(GetInstance(), "DTLS started"); + } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + else + { + otLogInfoCoap(GetInstance(), "Application Coap Secure DTLS started"); + } +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE exit: return MapError(rval); } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +int Dtls::SetApplicationCoapSecureKeys(void) +{ + int rval = 0; + + VerifyOrExit(&mCipherSuites[0] != NULL, rval = MBEDTLS_ERR_SSL_BAD_INPUT_DATA); + + switch (mCipherSuites[0]) + { + case MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + if (mCaChainSrc != NULL) + { + rval = mbedtls_x509_crt_parse(&mCaChain, (const unsigned char *)mCaChainSrc, (size_t)mCaChainLength); + VerifyOrExit(rval == 0); + mbedtls_ssl_conf_ca_chain(&mConf, &mCaChain, NULL); + } + + if (mOwnCertSrc != NULL && mPrivateKeySrc != NULL) + { + rval = mbedtls_x509_crt_parse(&mOwnCert, (const unsigned char *)mOwnCertSrc, (size_t)mOwnCertLength); + VerifyOrExit(rval == 0); + rval = mbedtls_pk_parse_key(&mPrivateKey, (const unsigned char *)mPrivateKeySrc, (size_t)mPrivateKeyLength, + NULL, 0); + VerifyOrExit(rval == 0); + rval = mbedtls_ssl_conf_own_cert(&mConf, &mOwnCert, &mPrivateKey); + VerifyOrExit(rval == 0); + } +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + break; + + case MBEDTLS_TLS_PSK_WITH_AES_128_CCM_8: +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + rval = mbedtls_ssl_conf_psk(&mConf, (unsigned char *)mPreSharedKey, mPreSharedKeyLength, + (unsigned char *)mPreSharedKeyIdentity, mPreSharedKeyIdLength); + VerifyOrExit(rval == 0); +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + break; + + default: + otLogCritCoap(GetInstance(), "Application Coap Secure DTLS: Not supported cipher."); + rval = MBEDTLS_ERR_SSL_BAD_INPUT_DATA; + ExitNow(); + break; + } + +exit: + return rval; +} + +void Dtls::SetSslAuthMode(bool aVerifyPeerCertificate) +{ + mVerifyPeerCertificate = aVerifyPeerCertificate; +} + +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + otError Dtls::Stop(void) { mbedtls_ssl_close_notify(&mSsl); Close(); + return OT_ERROR_NONE; } void Dtls::Close(void) { + // guard time, that the possible close notify + // not open an invalid (new) connection + mGuardTimerSet = true; + mTimer.Start(kGuardTimeNewConnectionMilli); VerifyOrExit(mStarted); - mStarted = false; mbedtls_ssl_free(&mSsl); mbedtls_ssl_config_free(&mConf); @@ -195,6 +317,14 @@ void Dtls::Close(void) mbedtls_entropy_free(&mEntropy); mbedtls_ssl_cookie_free(&mCookieCtx); +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + mbedtls_x509_crt_free(&mCaChain); + mbedtls_x509_crt_free(&mOwnCert); + mbedtls_pk_free(&mPrivateKey); +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + if (mConnectedHandler != NULL) { mConnectedHandler(mContext, false); @@ -216,12 +346,101 @@ otError Dtls::SetPsk(const uint8_t *aPsk, uint8_t aPskLength) VerifyOrExit(aPskLength <= sizeof(mPsk), error = OT_ERROR_INVALID_ARGS); memcpy(mPsk, aPsk, aPskLength); - mPskLength = aPskLength; + mPskLength = aPskLength; + mCipherSuites[0] = MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8; + mCipherSuites[1] = 0; exit: return error; } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +otError Dtls::SetCertificate(const uint8_t *aX509Certificate, + uint32_t aX509CertLength, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength) +{ + otError error = OT_ERROR_NONE; + + assert(aX509CertLength > 0); + assert(aX509Certificate != NULL); + + assert(aPrivateKeyLength > 0); + assert(aPrivateKey != NULL); + + mOwnCertSrc = aX509Certificate; + mOwnCertLength = aX509CertLength; + mPrivateKeySrc = aPrivateKey; + mPrivateKeyLength = aPrivateKeyLength; + + mCipherSuites[0] = MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; + mCipherSuites[1] = 0; + + return error; +} + +otError Dtls::SetCaCertificateChain(const uint8_t *aX509CaCertificateChain, uint32_t aX509CaCertChainLength) +{ + otError error = OT_ERROR_NONE; + + assert(aX509CaCertChainLength > 0); + assert(aX509CaCertificateChain != NULL); + + mCaChainSrc = aX509CaCertificateChain; + mCaChainLength = aX509CaCertChainLength; + + return error; +} + +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + +otError Dtls::SetPreSharedKey(const uint8_t *aPsk, + uint16_t aPskLength, + const uint8_t *aPskIdentity, + uint16_t aPskIdLength) +{ + otError error = OT_ERROR_NONE; + + assert(aPsk != NULL); + assert(aPskIdentity != NULL); + assert(aPskLength > 0); + assert(aPskIdLength > 0); + + mPreSharedKey = aPsk; + mPreSharedKeyLength = aPskLength; + mPreSharedKeyIdentity = aPskIdentity; + mPreSharedKeyIdLength = aPskIdLength; + + mCipherSuites[0] = MBEDTLS_TLS_PSK_WITH_AES_128_CCM_8; + mCipherSuites[1] = 0; + + return error; +} +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + +#ifdef MBEDTLS_BASE64_C + +otError Dtls::GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(IsConnected() == true, error = OT_ERROR_INVALID_STATE); + + VerifyOrExit(mbedtls_base64_encode(aPeerCert, aCertBufferSize, aCertLength, mSsl.session->peer_cert->raw.p, + mSsl.session->peer_cert->raw.len) == 0, + error = OT_ERROR_NO_BUFS); + +exit: + return error; +} + +#endif // MBEDTLS_BASE64_C +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + otError Dtls::SetClientId(const uint8_t *aClientId, uint8_t aLength) { int rval = mbedtls_ssl_set_client_transport_id(&mSsl, aClientId, aLength); @@ -277,7 +496,16 @@ int Dtls::HandleMbedtlsTransmit(const unsigned char *aBuf, size_t aLength) otError error; int rval = 0; - otLogInfoMeshCoP(GetInstance(), "Dtls::HandleMbedtlsTransmit"); + if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + { + otLogInfoMeshCoP(GetInstance(), "Dtls::HandleMbedtlsTransmit"); + } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + else + { + otLogInfoCoap(GetInstance(), "Dtls::ApplicationCoapSecure HandleMbedtlsTransmit"); + } +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE error = mSendHandler(mContext, aBuf, static_cast(aLength), mMessageSubType); @@ -311,7 +539,16 @@ int Dtls::HandleMbedtlsReceive(unsigned char *aBuf, size_t aLength) { int rval; - otLogInfoMeshCoP(GetInstance(), "Dtls::HandleMbedtlsReceive"); + if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + { + otLogInfoMeshCoP(GetInstance(), "Dtls::HandleMbedtlsReceive"); + } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + else + { + otLogInfoCoap(GetInstance(), "Dtls:: ApplicationCoapSecure HandleMbedtlsReceive"); + } +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE VerifyOrExit(mReceiveMessage != NULL && mReceiveLength != 0, rval = MBEDTLS_ERR_SSL_WANT_READ); @@ -337,7 +574,16 @@ int Dtls::HandleMbedtlsGetTimer(void) { int rval; - otLogInfoMeshCoP(GetInstance(), "Dtls::HandleMbedtlsGetTimer"); + if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + { + otLogInfoMeshCoP(GetInstance(), "Dtls::HandleMbedtlsGetTimer"); + } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + else + { + otLogInfoCoap(GetInstance(), "Dtls:: ApplicationCoapSecure HandleMbedtlsGetTimer"); + } +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE if (!mTimerSet) { @@ -366,7 +612,16 @@ void Dtls::HandleMbedtlsSetTimer(void *aContext, uint32_t aIntermediate, uint32_ void Dtls::HandleMbedtlsSetTimer(uint32_t aIntermediate, uint32_t aFinish) { - otLogInfoMeshCoP(GetInstance(), "Dtls::SetTimer"); + if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + { + otLogInfoMeshCoP(GetInstance(), "Dtls::SetTimer"); + } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + else + { + otLogInfoCoap(GetInstance(), "Dtls::ApplicationCoapSecure SetTimer"); + } +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE if (aFinish == 0) { @@ -407,7 +662,16 @@ int Dtls::HandleMbedtlsExportKeys(const unsigned char *aMasterSecret, GetNetif().GetKeyManager().SetKek(kek); - otLogInfoMeshCoP(GetInstance(), "Generated KEK"); + if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + { + otLogInfoMeshCoP(GetInstance(), "Generated KEK"); + } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + else + { + otLogInfoCoap(GetInstance(), "ApplicationCoapSecure Generated KEK"); + } +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE OT_UNUSED_VARIABLE(aMasterSecret); return 0; @@ -420,7 +684,15 @@ void Dtls::HandleTimer(Timer &aTimer) void Dtls::HandleTimer(void) { - Process(); + if (!mGuardTimerSet) + { + Process(); + } + else + { + mGuardTimerSet = false; + mTimer.Stop(); + } } void Dtls::Process(void) @@ -492,7 +764,10 @@ void Dtls::Process(void) } mbedtls_ssl_session_reset(&mSsl); - mbedtls_ssl_set_hs_ecjpake_password(&mSsl, mPsk, mPskLength); + if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + { + mbedtls_ssl_set_hs_ecjpake_password(&mSsl, mPsk, mPskLength); + } break; } } @@ -511,15 +786,69 @@ otError Dtls::MapError(int rval) switch (rval) { +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + case MBEDTLS_ERR_PK_TYPE_MISMATCH: + case MBEDTLS_ERR_PK_FILE_IO_ERROR: + case MBEDTLS_ERR_PK_KEY_INVALID_VERSION: + case MBEDTLS_ERR_PK_KEY_INVALID_FORMAT: + case MBEDTLS_ERR_PK_UNKNOWN_PK_ALG: + case MBEDTLS_ERR_PK_PASSWORD_REQUIRED: + case MBEDTLS_ERR_PK_PASSWORD_MISMATCH: + case MBEDTLS_ERR_PK_INVALID_PUBKEY: + case MBEDTLS_ERR_PK_INVALID_ALG: + case MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE: + case MBEDTLS_ERR_PK_BAD_INPUT_DATA: + case MBEDTLS_ERR_X509_SIG_MISMATCH: + case MBEDTLS_ERR_X509_BAD_INPUT_DATA: + case MBEDTLS_ERR_X509_FILE_IO_ERROR: + case MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT: + case MBEDTLS_ERR_X509_INVALID_VERSION: + case MBEDTLS_ERR_X509_UNKNOWN_SIG_ALG: + case MBEDTLS_ERR_X509_INVALID_SERIAL: + case MBEDTLS_ERR_X509_UNKNOWN_OID: + case MBEDTLS_ERR_X509_INVALID_FORMAT: + case MBEDTLS_ERR_X509_INVALID_ALG: + case MBEDTLS_ERR_X509_INVALID_NAME: + case MBEDTLS_ERR_X509_INVALID_DATE: + case MBEDTLS_ERR_X509_INVALID_SIGNATURE: + case MBEDTLS_ERR_X509_INVALID_EXTENSIONS: + case MBEDTLS_ERR_X509_UNKNOWN_VERSION: +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED case MBEDTLS_ERR_SSL_BAD_INPUT_DATA: error = OT_ERROR_INVALID_ARGS; break; +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + case MBEDTLS_ERR_PK_ALLOC_FAILED: + case MBEDTLS_ERR_X509_BUFFER_TOO_SMALL: + case MBEDTLS_ERR_X509_ALLOC_FAILED: +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED case MBEDTLS_ERR_SSL_ALLOC_FAILED: case MBEDTLS_ERR_SSL_WANT_WRITE: error = OT_ERROR_NO_BUFS; break; +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + case MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE: + case MBEDTLS_ERR_PK_SIG_LEN_MISMATCH: + case MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE: + case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + case MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED: + case MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED: + error = OT_ERROR_SECURITY; + break; + +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + case MBEDTLS_ERR_X509_FATAL_ERROR: + error = OT_ERROR_FAILED; + break; +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + + case MBEDTLS_ERR_SSL_TIMEOUT: + error = OT_ERROR_BUSY; + break; + default: assert(rval >= 0); break; @@ -534,25 +863,49 @@ void Dtls::HandleMbedtlsDebug(void *ctx, int level, const char *, int, const cha OT_UNUSED_VARIABLE(pThis); OT_UNUSED_VARIABLE(str); - switch (level) + if (pThis->mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) { - case 1: - otLogCritMbedTls(pThis->GetInstance(), "%s", str); - break; + switch (level) + { + case 1: + otLogCritMbedTls(pThis->GetInstance(), "%s", str); + break; - case 2: - otLogWarnMbedTls(pThis->GetInstance(), "%s", str); - break; + case 2: + otLogWarnMbedTls(pThis->GetInstance(), "%s", str); + break; - case 3: - otLogInfoMbedTls(pThis->GetInstance(), "%s", str); - break; + case 3: + otLogInfoMbedTls(pThis->GetInstance(), "%s", str); + break; - case 4: - default: - otLogDebgMbedTls(pThis->GetInstance(), "%s", str); - break; + case 4: + default: + otLogDebgMbedTls(pThis->GetInstance(), "%s", str); + break; + } } +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + else + { + switch (level) + { + case 1: + otLogCritCoap(pThis->GetInstance(), "ApplicationCoapSecure Mbedtls: %s", str); + break; + case 2: + otLogWarnCoap(pThis->GetInstance(), "ApplicationCoapSecure Mbedtls: %s", str); + break; + case 3: + otLogInfoCoap(pThis->GetInstance(), "ApplicationCoapSecure Mbedtls: %s", str); + break; + case 4: + default: + otLogDebgCoap(pThis->GetInstance(), "ApplicationCoapSecure Mbedtls: %s", str); + break; + } + } +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE } } // namespace MeshCoP diff --git a/src/core/meshcop/dtls.hpp b/src/core/meshcop/dtls.hpp index 80085fd18..1059a4415 100644 --- a/src/core/meshcop/dtls.hpp +++ b/src/core/meshcop/dtls.hpp @@ -44,6 +44,16 @@ #include #include +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#include +#include +#include +#include +#include +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + #include "common/locator.hpp" #include "common/message.hpp" #include "common/timer.hpp" @@ -59,8 +69,13 @@ class Dtls : public InstanceLocator public: enum { - kPskMaxLength = 32, + kPskMaxLength = 32, + kGuardTimeNewConnectionMilli = 2000, +#if !OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE kApplicationDataMaxLength = 512, +#else + kApplicationDataMaxLength = OPENTHREAD_CONFIG_DTLS_APPLICATION_DATA_MAX_LENGTH, +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE }; /** @@ -104,11 +119,15 @@ public: /** * This method starts the DTLS service. * - * @param[in] aClient TRUE if operating as a client, FALSE if operating as a server. - * @param[in] aConnectedHandler A pointer to the connected handler. - * @param[in] aReceiveHandler A pointer to the receive handler. - * @param[in] aSendHandler A pointer to the send handler. - * @param[in] aContext A pointer to application-specific context. + * For CoAP Secure API do first: + * Set X509 Pk and Cert for use DTLS mode ECDHE ECDSA with AES 128 CCM 8 or + * set PreShared Key for use DTLS mode PSK with AES 128 CCM 8. + * + * @param[in] aClient TRUE if operating as a client, FALSE if operating as a server. + * @param[in] aConnectedHandler A pointer to the connected handler. + * @param[in] aReceiveHandler A pointer to the receive handler. + * @param[in] aSendHandler A pointer to the send handler. + * @param[in] aContext A pointer to application-specific context. * * @retval OT_ERROR_NONE Successfully started the DTLS service. * @@ -130,7 +149,7 @@ public: /** * This method indicates whether or not the DTLS service is active. * - * @returns TRUE if the DTLS service is active, FALSE otherwise. + * @returns true if the DTLS service is active, false otherwise. * */ bool IsStarted(void); @@ -146,6 +165,94 @@ public: */ otError SetPsk(const uint8_t *aPsk, uint8_t aPskLength); +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + /** + * This method sets the Pre-Shared Key (PSK) for DTLS sessions- + * identified by a PSK. + * + * DTLS mode "PSK with AES 128 CCM 8" for Application CoAPS. + * + * @param[in] aPsk A pointer to the PSK. + * @param[in] aPskLength The PSK char length. + * @param[in] aPskIdentity The Identity Name for the PSK. + * @param[in] aPskIdLength The PSK Identity Length. + * + * @retval OT_ERROR_NONE Successfully set the PSK. + * + */ + otError SetPreSharedKey(const uint8_t *aPsk, + uint16_t aPskLength, + const uint8_t *aPskIdentity, + uint16_t aPskIdLength); + +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + + /** + * This method sets a reference to the own x509 certificate with corresponding private key. + * + * DTLS mode "ECDHE ECDSA with AES 128 CCM 8" for Application CoAPS. + * + * @param[in] aX509Certificate A pointer to the PEM formatted X509 certificate. + * @param[in] aX509CertLength The length of certificate. + * @param[in] aPrivateKey A pointer to the PEM formatted private key. + * @param[in] aPrivateKeyLength The length of the private key. + * + * @retval OT_ERROR_NONE Successfully set the x509 certificate with his private key. + * + */ + otError SetCertificate(const uint8_t *aX509Certificate, + uint32_t aX509CertLength, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength); + + /** + * This method sets the trusted top level CAs. It is needed for validate the + * certificate of the peer. + * + * DTLS mode "ECDHE ECDSA with AES 128 CCM 8" for Application CoAPS. + * + * @param[in] aX509CaCertificateChain A pointer to the PEM formatted X509 CA chain. + * @param[in] aX509CaCertChainLength The length of chain. + * + * @retval OT_ERROR_NONE Successfully set the the trusted top level CAs. + * + */ + otError SetCaCertificateChain(const uint8_t *aX509CaCertificateChain, uint32_t aX509CaCertChainLength); + +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +#ifdef MBEDTLS_BASE64_C + /** + * This method returns the peer x509 certificate base64 encoded. + * + * DTLS mode "ECDHE ECDSA with AES 128 CCM 8" for Application CoAPS. + * + * @param[out] aPeerCert A pointer to the base64 encoded certificate buffer. + * @param[out] aCertLength The length of the base64 encoded peer certificate. + * @param[in] aCertBufferSize The buffer size of aPeerCert. + * + * @retval OT_ERROR_NONE Successfully get the peer certificate. + * @retval OT_ERROR_NO_BUFS Can't allocate memory for certificate. + * + */ + otError GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize); +#endif // MBEDTLS_BASE64_C + + /** + * This method set the authentication mode for a dtls connection. + * + * Disable or enable the verification of peer certificate. + * Must called before start. + * + * @param[in] aVerifyPeerCertificate true, if the peer certificate should verify. + * + */ + void SetSslAuthMode(bool aVerifyPeerCertificate); +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * This method sets the Client ID used for generating the Hello Cookie. * @@ -208,6 +315,16 @@ public: private: static otError MapError(int rval); +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** + * Set keys and/or certificates for dtls session dependent of used cipher suite. + * + * @retval mbedtls error, 0 if successfully. + * + */ + int SetApplicationCoapSecureKeys(void); +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + static void HandleMbedtlsDebug(void *ctx, int level, const char *file, int line, const char *str); static int HandleMbedtlsGetTimer(void *aContext); @@ -242,9 +359,34 @@ private: void Close(void); void Process(void); + int mCipherSuites[2]; uint8_t mPsk[kPskMaxLength]; uint8_t mPskLength; +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + + const uint8_t * mCaChainSrc; + uint32_t mCaChainLength; + const uint8_t * mOwnCertSrc; + uint32_t mOwnCertLength; + const uint8_t * mPrivateKeySrc; + uint32_t mPrivateKeyLength; + mbedtls_x509_crt mCaChain; + mbedtls_x509_crt mOwnCert; + mbedtls_pk_context mPrivateKey; +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + const uint8_t *mPreSharedKey; + const uint8_t *mPreSharedKeyIdentity; + uint16_t mPreSharedKeyLength; + uint16_t mPreSharedKeyIdLength; +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + + bool mVerifyPeerCertificate; + mbedtls_entropy_context mEntropy; mbedtls_ctr_drbg_context mCtrDrbg; mbedtls_ssl_context mSsl; @@ -265,6 +407,7 @@ private: SendHandler mSendHandler; void * mContext; bool mClient; + bool mGuardTimerSet; uint8_t mMessageSubType; uint8_t mMessageDefaultSubType; diff --git a/src/core/openthread-core-default-config.h b/src/core/openthread-core-default-config.h index 9d04eba0d..3bfa48eec 100644 --- a/src/core/openthread-core-default-config.h +++ b/src/core/openthread-core-default-config.h @@ -1067,8 +1067,12 @@ * */ #ifndef OPENTHREAD_CONFIG_HEAP_SIZE +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#define OPENTHREAD_CONFIG_HEAP_SIZE (1800 * sizeof(void *)) +#else #define OPENTHREAD_CONFIG_HEAP_SIZE (1536 * sizeof(void *)) -#endif +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#endif // OPENTHREAD_CONFIG_HEAP_SIZE /** * @def OPENTHREAD_CONFIG_HEAP_SIZE_NO_DTLS @@ -1080,6 +1084,16 @@ #define OPENTHREAD_CONFIG_HEAP_SIZE_NO_DTLS 384 #endif +/** + * @def OPENTHREAD_CONFIG_DTLS_APPLICATION_DATA_MAX_LENGTH + * + * The size of dtls application data when the CoAP Secure API is enabled. + * + */ +#ifndef OPENTHREAD_CONFIG_DTLS_APPLICATION_DATA_MAX_LENGTH +#define OPENTHREAD_CONFIG_DTLS_APPLICATION_DATA_MAX_LENGTH 1400 +#endif + /** * @def OPENTHREAD_CONFIG_ENABLE_STEERING_DATA_SET_OOB * diff --git a/third_party/mbedtls/Makefile.am b/third_party/mbedtls/Makefile.am index a6e680e90..4c4dc5753 100644 --- a/third_party/mbedtls/Makefile.am +++ b/third_party/mbedtls/Makefile.am @@ -62,6 +62,7 @@ libmbedcrypto_a_SOURCES = \ repo/library/md_wrap.c \ repo/library/memory_buffer_alloc.c \ repo/library/oid.c \ + repo/library/pem.c \ repo/library/pk.c \ repo/library/pk_wrap.c \ repo/library/pkparse.c \ diff --git a/third_party/mbedtls/mbedtls-config.h b/third_party/mbedtls/mbedtls-config.h index 8b365a211..036ca7a61 100644 --- a/third_party/mbedtls/mbedtls-config.h +++ b/third_party/mbedtls/mbedtls-config.h @@ -527,6 +527,15 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. */ //#define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +/** + * Enable the PSK ciphersuite for Application Coap Secure. + */ +#ifndef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * \def MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED * @@ -699,6 +708,29 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. */ //#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +/** + * Enables ciphersuite ECDHE ECDSA with x.509 certificate + * for Application Coap Secure. + */ +#ifndef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) + +/** + * To increase the security with ECDHE ECDSA enable + * X509 CHECK KEY USAGE. + * Not required! + */ + +#ifndef MBEDTLS_X509_CHECK_KEY_USAGE +//#define MBEDTLS_X509_CHECK_KEY_USAGE +#endif // MBEDTLS_X509_CHECK_KEY_USAGE + +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * \def MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED * @@ -1569,6 +1601,18 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. */ //#define MBEDTLS_BASE64_C +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +/** + * Requiered by ciphersuite ECDHE ECDSA with x.509 certificate + * used by Application CoAP Secure. + */ +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) +#ifndef MBEDTLS_BASE64_C +#define MBEDTLS_BASE64_C +#endif // MBEDTLS_BASE64_C +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * \def MBEDTLS_BIGNUM_C * @@ -1786,6 +1830,17 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. #define MBEDTLS_ECDH_C #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) +/** + * Required for ECDHE_ECDSA by Application CoAPS. + */ +#ifndef MBEDTLS_ECDH_C +#define MBEDTLS_ECDH_C +#endif // MBEDTLS_ECDH_C +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * \def MBEDTLS_ECDSA_C * @@ -1803,6 +1858,17 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. #define MBEDTLS_ECDSA_C #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +/** + * Required for ECDHA ECDSA by Application CoAPS. + */ +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) +#ifndef MBEDTLS_ECDSA_C +#define MBEDTLS_ECDSA_C +#endif // MBEDTLS_ECDSA_C +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * \def MBEDTLS_ECJPAKE_C * @@ -2056,6 +2122,18 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. */ //#define MBEDTLS_PEM_PARSE_C +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +/** + * Requiered by ciphersuite ECDHE ECDSA with x.509 certificate + * used by Application CoAP Secure. + */ +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) +#ifndef MBEDTLS_PEM_PARSE_C +#define MBEDTLS_PEM_PARSE_C +#endif // MBEDTLS_PEM_PARSE_C +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * \def MBEDTLS_PEM_WRITE_C * @@ -2411,6 +2489,17 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. #define MBEDTLS_X509_USE_C #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +/** + * Required for ECDHE ECDSA by Application CoAPS. + */ +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) +#ifndef MBEDTLS_X509_USE_C +#define MBEDTLS_X509_USE_C +#endif // MBEDTLS_X509_USE_C +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * \def MBEDTLS_X509_CRT_PARSE_C * @@ -2429,6 +2518,17 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. #define MBEDTLS_X509_CRT_PARSE_C #endif +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +/** + * Required for ECDHE ECDSA by Application CoAPS. + */ +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) +#ifndef MBEDTLS_X509_CRT_PARSE_C +#define MBEDTLS_X509_CRT_PARSE_C +#endif // MBEDTLS_X509_CRT_PARSE_C +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif // OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE + /** * \def MBEDTLS_X509_CRL_PARSE_C * @@ -2591,7 +2691,11 @@ __inline int windows_kernel_snprintf(char * s, size_t n, const char * format, .. //#define MBEDTLS_SSL_CACHE_DEFAULT_MAX_ENTRIES 50 /**< Maximum entries in cache */ /* SSL options */ +#if OPENTHREAD_ENABLE_APPLICATION_COAP_SECURE +#define MBEDTLS_SSL_MAX_CONTENT_LEN 900 /**< Maxium fragment length in bytes, determines the size of each of the two internal I/O buffers */ +#else #define MBEDTLS_SSL_MAX_CONTENT_LEN 768 /**< Maxium fragment length in bytes, determines the size of each of the two internal I/O buffers */ +#endif //#define MBEDTLS_SSL_DEFAULT_TICKET_LIFETIME 86400 /**< Lifetime of session tickets (if enabled) */ //#define MBEDTLS_PSK_MAX_LEN 32 /**< Max size of TLS pre-shared keys, in bytes (default 256 bits) */ //#define MBEDTLS_SSL_COOKIE_TIMEOUT 60 /**< Default expiration delay of DTLS cookies, in seconds if HAVE_TIME, or in number of cookies issued */