From 575ec1047d5065c65559af709cce0d8947fe85e2 Mon Sep 17 00:00:00 2001 From: Arnulf Rupp Date: Thu, 9 Mar 2023 15:22:21 +0100 Subject: [PATCH] [bbtc] Initial commit of bluetooth based commissioning. This commit introduces first implementation of Bluetooth based comissioning for thread devices. Co-authored-by: Przemyslaw Bida --- .github/workflows/posix.yml | 13 + etc/cmake/options.cmake | 1 + etc/gn/openthread.gni | 3 + examples/platforms/simulation/CMakeLists.txt | 1 + examples/platforms/simulation/ble.c | 76 +++ include/openthread/BUILD.gn | 3 + include/openthread/ble_secure.h | 467 +++++++++++++++++ include/openthread/instance.h | 2 +- include/openthread/platform/ble.h | 275 ++++++++++ include/openthread/tcat.h | 276 ++++++++++ script/check-arm-build | 2 +- script/test | 2 +- src/cli/CMakeLists.txt | 1 + src/cli/README.md | 1 + src/cli/README_TCAT.md | 37 ++ src/cli/cli.cpp | 10 + src/cli/cli.hpp | 4 + src/cli/cli_config.h | 10 + src/cli/cli_tcat.cpp | 172 ++++++ src/cli/cli_tcat.hpp | 89 ++++ src/core/BUILD.gn | 9 + src/core/CMakeLists.txt | 3 + src/core/api/ble_secure_api.cpp | 226 ++++++++ src/core/common/instance.cpp | 3 + src/core/common/instance.hpp | 9 + src/core/common/message.hpp | 3 +- src/core/config/dtls.h | 2 +- src/core/config/ip6.h | 2 +- src/core/meshcop/dtls.cpp | 258 +++++++-- src/core/meshcop/dtls.hpp | 135 ++++- src/core/meshcop/tcat_agent.cpp | 519 +++++++++++++++++++ src/core/meshcop/tcat_agent.hpp | 382 ++++++++++++++ src/core/radio/ble_secure.cpp | 475 +++++++++++++++++ src/core/radio/ble_secure.hpp | 499 ++++++++++++++++++ src/posix/platform/CMakeLists.txt | 1 + src/posix/platform/ble.cpp | 76 +++ tests/unit/test_platform.cpp | 52 ++ third_party/mbedtls/mbedtls-config.h | 4 + 38 files changed, 4055 insertions(+), 48 deletions(-) create mode 100644 examples/platforms/simulation/ble.c create mode 100644 include/openthread/ble_secure.h create mode 100644 include/openthread/platform/ble.h create mode 100644 include/openthread/tcat.h create mode 100644 src/cli/README_TCAT.md create mode 100644 src/cli/cli_tcat.cpp create mode 100644 src/cli/cli_tcat.hpp create mode 100644 src/core/api/ble_secure_api.cpp create mode 100644 src/core/meshcop/tcat_agent.cpp create mode 100644 src/core/meshcop/tcat_agent.hpp create mode 100644 src/core/radio/ble_secure.cpp create mode 100644 src/core/radio/ble_secure.hpp create mode 100644 src/posix/platform/ble.cpp diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml index f260f039557a..396e40e239c2 100644 --- a/.github/workflows/posix.yml +++ b/.github/workflows/posix.yml @@ -45,6 +45,19 @@ permissions: jobs: + ble_sim: + runs-on: ubuntu-20.04 + steps: + - name: Harden Runner + uses: step-security/harden-runner@6b3083af2869dc3314a0257a42f4af696cc79ba3 # v2.3.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + - name: Compile + run: | + ./script/cmake-build simulation -DOT_BLE_TCAT=ON + expects-linux: runs-on: ubuntu-20.04 env: diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 3740dacb42a6..1bf8d1528388 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -172,6 +172,7 @@ ot_option(OT_ASSERT OPENTHREAD_CONFIG_ASSERT_ENABLE "assert function OT_ASSERT() ot_option(OT_BACKBONE_ROUTER OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE "backbone router functionality") ot_option(OT_BACKBONE_ROUTER_DUA_NDPROXYING OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE "BBR DUA ND Proxy") ot_option(OT_BACKBONE_ROUTER_MULTICAST_ROUTING OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE "BBR MR") +ot_option(OT_BLE_TCAT OPENTHREAD_CONFIG_BLE_TCAT_ENABLE "Ble based thread commissioning") ot_option(OT_BORDER_AGENT OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE "border agent") ot_option(OT_BORDER_AGENT_ID OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE "create and save border agent ID") ot_option(OT_BORDER_ROUTER OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE "border router") diff --git a/etc/gn/openthread.gni b/etc/gn/openthread.gni index 9bbb543d2575..1433efb5f689 100644 --- a/etc/gn/openthread.gni +++ b/etc/gn/openthread.gni @@ -81,6 +81,9 @@ if (openthread_enable_core_config_args) { # Enable backbone router functionality openthread_config_backbone_router_enable = false + # Enable BLE based commissioning functionality + OPENTHREAD_CONFIG_BLE_TCAT_ENABLE = false + # Enable border agent support openthread_config_border_agent_enable = false diff --git a/examples/platforms/simulation/CMakeLists.txt b/examples/platforms/simulation/CMakeLists.txt index 883ee641ce22..3dd0d10bdca5 100644 --- a/examples/platforms/simulation/CMakeLists.txt +++ b/examples/platforms/simulation/CMakeLists.txt @@ -59,6 +59,7 @@ set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE) add_library(openthread-simulation alarm.c + ble.c crypto.c diag.c dso_transport.c diff --git a/examples/platforms/simulation/ble.c b/examples/platforms/simulation/ble.c new file mode 100644 index 000000000000..31d5f39751a9 --- /dev/null +++ b/examples/platforms/simulation/ble.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023, 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. + */ + +#include + +otError otPlatBleEnable(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleDisable(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapAdvStart(otInstance *aInstance, uint16_t aInterval) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aInterval); + OT_UNUSED_VARIABLE(aType); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapAdvStop(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapDisconnect(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGattMtuGet(otInstance *aInstance, uint16_t *aMtu) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aMtu); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGattServerIndicate(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aHandle); + OT_UNUSED_VARIABLE(aPacket); + return OT_ERROR_NOT_IMPLEMENTED; +} diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 5bc1c09677be..aba31daceaa5 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -41,6 +41,7 @@ source_set("openthread") { public = [ "backbone_router.h", "backbone_router_ftd.h", + "ble_secure.h", "border_agent.h", "border_router.h", "border_routing.h", @@ -84,6 +85,7 @@ source_set("openthread") { "ping_sender.h", "platform/alarm-micro.h", "platform/alarm-milli.h", + "platform/ble.h", "platform/border_routing.h", "platform/crypto.h", "platform/debug_uart.h", @@ -114,6 +116,7 @@ source_set("openthread") { "srp_client_buffers.h", "srp_server.h", "tasklet.h", + "tcat.h", "tcp.h", "tcp_ext.h", "thread.h", diff --git a/include/openthread/ble_secure.h b/include/openthread/ble_secure.h new file mode 100644 index 000000000000..e23db5d5ee3a --- /dev/null +++ b/include/openthread/ble_secure.h @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2023, 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 BLE Secure implementation. + * + * @note + * The functions in this module require the build-time feature `OPENTHREAD_CONFIG_BLE_TCAT_ENABLE=1`. + * + * @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_BLE_SECURE_H_ +#define OPENTHREAD_BLE_SECURE_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-ble-secure + * + * @brief + * This module includes functions that control BLE Secure (TLS over BLE) communication. + * + * The functions in this module are available when BLE Secure API feature + * (`OPENTHREAD_CONFIG_BLE_TCAT_ENABLE`) is enabled. + * + * @{ + * + */ + +/** + * Pointer to call when ble secure connection state changes. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aConnected TRUE, if a secure connection was established, FALSE otherwise. + * @param[in] aBleConnectionOpen TRUE if a BLE connection was established to carry a TLS data stream, FALSE + * otherwise. + * @param[in] aContext A pointer to arbitrary context information. + * + */ +typedef void (*otHandleBleSecureConnect)(otInstance *aInstance, + bool aConnected, + bool aBleConnectionOpen, + void *aContext); + +/** + * Pointer to call when data was received over a BLE Secure TLS connection. + * + * + * @param[in] aMessage A pointer to the message. + * @param[in] aTcatMessageType The message type received. + * @param[in] aServiceName The name of the service the message is direced to. + * @param[in] aContext A pointer to arbitrary context information. + * + */ +typedef otHandleTcatApplicationDataReceive otHandleBleSecureReceive; + +/** + * Starts the BLE Secure service. + * When TLV mode is activate, the function will be called once a complete TLV was received and the + * message offset points to the TLV value. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aConnectHandler A pointer to a function that will be called when the connection + * state changes. + * @param[in] aReceiveHandler A pointer to a function that will be called once data has been received + * over the TLS connection. + * @param[in] aTlvMode A boolean value indicating if line mode shall be activated. + * @param[in] aContext A pointer to arbitrary context information. May be NULL if not used. + * + * @retval OT_ERROR_NONE Successfully started the BLE Secure server. + * @retval OT_ERROR_ALREADY The service was stated already. + * + */ +otError otBleSecureStart(otInstance *aInstance, + otHandleBleSecureConnect aConnectHandler, + otHandleBleSecureReceive aReceiveHandler, + bool aTlvMode, + void *aContext); + +/** + * Enables the TCAT protocol over BLE Secure. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aVendorInfo A pointer to the Vendor Information (must remain valid after the method call, may be + * NULL). + * @param[in] aHandler A pointer to a function that is called when the join operation completes. + * + * @retval OT_ERROR_NONE Successfully started the BLE Secure Joiner role. + * @retval OT_ERROR_INVALID_ARGS @p aElevationPsk or @p aVendorInfo is invalid. + * @retval OT_ERROR_INVALID_STATE The BLE function has not been started or line mode is not selected. + * + */ +otError otBleSecureTcatStart(otInstance *aInstance, otTcatVendorInfo *aVendorInfo, otHandleTcatJoin aHandler); + +/** + * Stops the BLE Secure server. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + */ +void otBleSecureStop(otInstance *aInstance); + +/** + * Sets the Pre-Shared Key (PSK) and cipher suite + * TLS_PSK_WITH_AES_128_CCM_8. + * + * @note Requires the build-time feature `MBEDTLS_KEY_EXCHANGE_PSK_ENABLED` to be enabled. + * + * @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. + * + */ +void otBleSecureSetPsk(otInstance *aInstance, + const uint8_t *aPsk, + uint16_t aPskLength, + const uint8_t *aPskIdentity, + uint16_t aPskIdLength); + +/** + * Returns the peer x509 certificate base64 encoded. + * + * @note Requires the build-time features `MBEDTLS_BASE64_C` and + * `MBEDTLS_SSL_KEEP_PEER_CERTIFICATE` to be enabled. + * + * @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_INVALID_STATE Not connected yet. + * @retval OT_ERROR_NONE Successfully get the peer certificate. + * @retval OT_ERROR_NO_BUFS Can't allocate memory for certificate. + * + */ +otError otBleSecureGetPeerCertificateBase64(otInstance *aInstance, + unsigned char *aPeerCert, + size_t *aCertLength, + size_t aCertBufferSize); + +/** + * Returns an attribute value identified by its OID from the subject + * of the peer x509 certificate. The peer OID is provided in binary format. + * The attribute length is set if the attribute was successfully read or zero + * if unsuccessful. The ANS1 type as is set as defineded in the ITU-T X.690 standard + * if the attribute was successfully read. + * + * @note Requires the build-time feature + * `MBEDTLS_SSL_KEEP_PEER_CERTIFICATE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aOid A pointer to the OID to be found. + * @param[in] aOidLength The length of the OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * @param[out] aAns1Type A pointer to the ANS1 type of the attribute written to the buffer. + * + * @retval OT_ERROR_INVALID_STATE Not connected yet. + * @retval OT_ERROR_NONE Successfully read attribute. + * @retval OT_ERROR_NO_BUFS Insufficient memory for storing the attribute value. + * + */ +otError otBleSecureGetPeerSubjectAttributeByOid(otInstance *aInstance, + const char *aOid, + size_t aOidLength, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize, + int *aAns1Type); + +/** + * Returns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the peer x509 certificate, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @note Requires the build-time feature + * `MBEDTLS_SSL_KEEP_PEER_CERTIFICATE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval OT_ERROR_NONE Successfully read attribute. + * @retval OT_NOT_FOUND The requested attribute was not found. + * @retval OT_ERROR_NO_BUFS Insufficient memory for storing the attribute value. + * @retval OT_ERROR_INVALID_STATE Not connected yet. + * @retval OT_ERROR_NOT_IMPLEMENTED The value of aThreadOidDescriptor is >127. + * @retval OT_ERROR_PARSE The certificate extensions could not be parsed. + * + */ +otError otBleSecureGetThreadAttributeFromPeerCertificate(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize); + +/** + * Returns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the own x509 certificate, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval OT_ERROR_NONE Successfully read attribute. + * @retval OT_NOT_FOUND The requested attribute was not found. + * @retval OT_ERROR_NO_BUFS Insufficient memory for storing the attribute value. + * @retval OT_ERROR_INVALID_STATE Not connected yet. + * @retval OT_ERROR_NOT_IMPLEMENTED The value of aThreadOidDescriptor is >127. + * @retval OT_ERROR_PARSE The certificate extensions could not be parsed. + * + */ +otError otBleSecureGetThreadAttributeFromOwnCertificate(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize); + +/** + * Returns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the CA x509 certificate chain, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval OT_ERROR_NONE Successfully read attribute. + * @retval OT_NOT_FOUND The requested attribute was not found. + * @retval OT_ERROR_NO_BUFS Insufficient memory for storing the attribute value. + * @retval OT_ERROR_INVALID_STATE Not connected yet. + * @retval OT_ERROR_NOT_IMPLEMENTED The value of aThreadOidDescriptor is >127. + * @retval OT_ERROR_PARSE The certificate extensions could not be parsed. + * + */ +otError otBleSecureGetThreadAttributeFromCaCertificateChain(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize); + +/** + * Sets the authentication mode for the BLE 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 otBleSecureSetSslAuthMode(otInstance *aInstance, bool aVerifyPeerCertificate); + +/** + * Sets the local device's X509 certificate with corresponding private key for + * TLS session with TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8. + * + * @note Requires `MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED=1`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aX509Cert 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. + * + */ +void otBleSecureSetCertificate(otInstance *aInstance, + const uint8_t *aX509Cert, + uint32_t aX509Length, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength); + +/** + * Sets the trusted top level CAs. It is needed for validating the + * certificate of the peer. + * + * TLS mode "ECDHE ECDSA with AES 128 CCM 8" for secure BLE. + * + * @note Requires `MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED=1`. + * + * @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. + * + */ +void otBleSecureSetCaCertificateChain(otInstance *aInstance, + const uint8_t *aX509CaCertificateChain, + uint32_t aX509CaCertChainLength); + +/** + * Initializes TLS session with a peer using an already open BLE connection. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval OT_ERROR_NONE Successfully started TLS connection. + * + */ +otError otBleSecureConnect(otInstance *aInstance); + +/** + * Stops the BLE and TLS connection. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + */ +void otBleSecureDisconnect(otInstance *aInstance); + +/** + * Indicates whether or not the TLS session is connected. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval TRUE The TLS session is connected. + * @retval FALSE The TLS session is not connected. + * + */ +bool otBleSecureIsConnected(otInstance *aInstance); + +/** + * Indicates whether or not the TLS session is active. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval TRUE If TLS session is active. + * @retval FALSE If TLS session is not active. + * + */ +bool otBleSecureIsConnectionActive(otInstance *aInstance); + +/** + * Indicates whether or not the commissioner rights have been elevated using elevation PSK. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval TRUE If commissioner rights are elevated. + * @retval FALSE If commissioner rights are not elevated. + * + */ +bool otBleSecureIsTcatElevated(otInstance *aInstance); + +/** + * Indicates whether or not the TCAT agent is enabled. + * + * @retval TRUE The TCAT agent is enabled. + * @retval FALSE The TCAT agent is not enabled. + * + */ +bool otBleSecureIsTcatEnabled(otInstance *aInstance); + +/** + * Sends a secure BLE message. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the message to send. + * + * If the return value is OT_ERROR_NONE, OpenThread takes ownership of @p aMessage, and the caller should no longer + * reference @p aMessage. If the return value is not OT_ERROR_NONE, the caller retains ownership of @p aMessage, + * including freeing @p aMessage if the message buffer is no longer needed. + * + * @retval OT_ERROR_NONE Successfully sent message. + * @retval OT_ERROR_NO_BUFS Failed to allocate buffer memory. + * @retval OT_ERROR_INVALID_STATE TLS connection was not initialized. + * + */ +otError otBleSecureSendMessage(otInstance *aInstance, otMessage *aMessage); + +/** + * Sends a secure BLE data packet. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aBuf A pointer to the data to send. + * @param[in] aLength A number indicating the length of the data buffer. + * + * @retval OT_ERROR_NONE Successfully sent data. + * @retval OT_ERROR_NO_BUFS Failed to allocate buffer memory. + * @retval OT_ERROR_INVALID_STATE TLS connection was not initialized. + * + */ +otError otBleSecureSend(otInstance *aInstance, uint8_t *aBuf, uint16_t aLength); + +/** + * Sends a secure BLE data packet containing a TCAT application TLV. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aBuf A pointer to the data to send. + * @param[in] aLength A number indicating the length of the data buffer. + * + * @retval OT_ERROR_NONE Successfully sent data. + * @retval OT_ERROR_NO_BUFS Failed to allocate buffer memory. + * @retval OT_ERROR_INVALID_STATE TLS connection was not initialized. + * + */ +otError otBleSecureSendApplicationTlv(otInstance *aInstance, uint8_t *aBuf, uint16_t aLength); + +/** + * Flushes the send buffer. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval OT_ERROR_NONE Successfully flushed output buffer. + * @retval OT_ERROR_NO_BUFS Failed to allocate buffer memory. + * @retval OT_ERROR_INVALID_STATE TLS connection was not initialized. + * + */ +otError otBleSecureFlush(otInstance *aInstance); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OPENTHREAD_BLE_SECURE_H_ */ diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 31f36a7ab00f..4b5b9d15ec1b 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (346) +#define OPENTHREAD_API_VERSION (347) /** * @addtogroup api-instance diff --git a/include/openthread/platform/ble.h b/include/openthread/platform/ble.h new file mode 100644 index 000000000000..7e664dcfda85 --- /dev/null +++ b/include/openthread/platform/ble.h @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2023, 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 a OpenThread BLE GATT peripheral interface driver. + * + */ + +#ifndef OPENTHREAD_PLATFORM_BLE_H_ +#define OPENTHREAD_PLATFORM_BLE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/** + * @addtogroup plat-ble + * + * @brief + * This module includes the platform abstraction for BLE Host communication. + * The platform needs to implement Bluetooth LE 4.2 or higher. + * + * @{ + * + */ + +/** + * Time slot duration on PHY layer in microseconds (0.625ms). + * + */ + +#define OT_BLE_TIMESLOT_UNIT 625 + +/** + * Minimum allowed interval for advertising packet in OT_BLE_ADV_INTERVAL_UNIT units (20ms). + * + */ + +#define OT_BLE_ADV_INTERVAL_MIN 0x0020 + +/** + * Maximum allowed interval for advertising packet in OT_BLE_ADV_INTERVAL_UNIT units (10.24s). + * + */ + +#define OT_BLE_ADV_INTERVAL_MAX 0x4000 + +/** + * Unit used to calculate interval duration (0.625ms). + * + */ + +#define OT_BLE_ADV_INTERVAL_UNIT OT_BLE_TIMESLOT_UNIT + +/** + * Represents a BLE packet. + * + */ +typedef struct otBleRadioPacket +{ + uint8_t *mValue; ///< The value of an attribute + uint16_t mLength; ///< Length of the @p mValue. + int8_t mPower; ///< Transmit/receive power in dBm. +} otBleRadioPacket; + +/******************************************************************************* + * @section Bluetooth Low Energy management. + ******************************************************************************/ + +/** + * Enable the Bluetooth Low Energy radio. + * + * @note BLE Device should use the highest ATT_MTU supported that does not + * exceed OT_BLE_ATT_MTU_MAX octets. + * + * @param[in] aInstance The OpenThread instance structure. + * + * @retval OT_ERROR_NONE Successfully enabled. + * @retval OT_ERROR_FAILED The BLE radio could not be enabled. + */ +otError otPlatBleEnable(otInstance *aInstance); + +/** + * Disable the Bluetooth Low Energy radio. + * + * When disabled, the BLE stack will flush event queues and not generate new + * events. The BLE peripheral is turned off or put into a low power sleep + * state. Any dynamic memory used by the stack should be released, + * but static memory may remain reserved. + * + * @param[in] aInstance The OpenThread instance structure. + * + * @retval OT_ERROR_NONE Successfully transitioned to disabled. + * @retval OT_ERROR_FAILED The BLE radio could not be disabled. + * + */ +otError otPlatBleDisable(otInstance *aInstance); + +/**************************************************************************** + * @section Bluetooth Low Energy GAP. + ***************************************************************************/ + +/** + * Starts BLE Advertising procedure. + * + * The BLE device shall use undirected advertising with no filter applied. + * A single BLE Advertising packet must be sent on all advertising + * channels (37, 38 and 39). + * + * @note This function shall be used only for BLE Peripheral role. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aInterval The interval between subsequent advertising packets + * in OT_BLE_ADV_INTERVAL_UNIT units. + * Shall be within OT_BLE_ADV_INTERVAL_MIN and + * OT_BLE_ADV_INTERVAL_MAX range. + * + * @retval OT_ERROR_NONE Advertising procedure has been started. + * @retval OT_ERROR_INVALID_STATE BLE Device is in invalid state. + * @retval OT_ERROR_INVALID_ARGS Invalid interval value has been supplied. + * + */ +otError otPlatBleGapAdvStart(otInstance *aInstance, uint16_t aInterval); + +/** + * Stops BLE Advertising procedure. + * + * @note This function shall be used only for BLE Peripheral role. + * + * @param[in] aInstance The OpenThread instance structure. + * + * @retval OT_ERROR_NONE Advertising procedure has been stopped. + * @retval OT_ERROR_INVALID_STATE BLE Device is in invalid state. + * + */ +otError otPlatBleGapAdvStop(otInstance *aInstance); + +/** + * The BLE driver calls this method to notify OpenThread that a BLE Device has + * been connected. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aConnectionId The identifier of the open connection. + * + */ +extern void otPlatBleGapOnConnected(otInstance *aInstance, uint16_t aConnectionId); + +/** + * The BLE driver calls this method to notify OpenThread that the BLE Device + * has been disconnected. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aConnectionId The identifier of the closed connection. + * + */ +extern void otPlatBleGapOnDisconnected(otInstance *aInstance, uint16_t aConnectionId); + +/** + * Disconnects BLE connection. + * + * The BLE device shall indicate the user has terminated the connection. + * + * @param[in] aInstance The OpenThread instance structure. + * + * @retval OT_ERROR_NONE Disconnection procedure has been started. + * @retval OT_ERROR_INVALID_STATE BLE Device is in invalid state. + * + */ +otError otPlatBleGapDisconnect(otInstance *aInstance); + +/******************************************************************************* + * @section Bluetooth Low Energy GATT Common. + *******************************************************************************/ + +/** + * Reads currently use value of ATT_MTU. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[out] aMtu A pointer to output the current ATT_MTU value. + * + * @retval OT_ERROR_NONE ATT_MTU value has been placed in @p aMtu. + * @retval OT_ERROR_FAILED BLE Device cannot determine its ATT_MTU. + * + */ +otError otPlatBleGattMtuGet(otInstance *aInstance, uint16_t *aMtu); + +/******************************************************************************* + * @section Bluetooth Low Energy GATT Server. + ******************************************************************************/ + +/** + * Sends ATT Handle Value Indication. + * + * @note This function shall be used only for GATT Server. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aHandle The handle of the attribute to be indicated. + * @param[in] aPacket A pointer to the packet contains value to be indicated. + * + * @retval OT_ERROR_NONE ATT Handle Value Indication has been sent. + * @retval OT_ERROR_INVALID_STATE BLE Device is in invalid state. + * @retval OT_ERROR_INVALID_ARGS Invalid handle value, data or data length has been supplied. + * @retval OT_ERROR_NO_BUFS No available internal buffer found. + * + */ +otError otPlatBleGattServerIndicate(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket); + +/** + * The BLE driver calls this method to notify OpenThread that an ATT Write Request + * packet has been received. + * + * @note This function shall be used only for GATT Server. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aHandle The handle of the attribute to be written. + * @param[in] aPacket A pointer to the packet contains value to be written to the attribute. + * + */ +extern void otPlatBleGattServerOnWriteRequest(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket); + +/** + * The BLE driver calls this method to notify OpenThread that an ATT Read Request + * packet has been received. + * + * @note This function shall be used only for GATT Server. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aHandle The handle of the attribute to be read. + * @param[out] aPacket A pointer to the packet to be filled with pointers to attribute data to be read. + * + */ +extern void otPlatBleGattServerOnReadRequest(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // end of extern "C" +#endif + +#endif // OPENTHREAD_PLATFORM_BLE_H_ diff --git a/include/openthread/tcat.h b/include/openthread/tcat.h new file mode 100644 index 000000000000..cb93ba428527 --- /dev/null +++ b/include/openthread/tcat.h @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2023, 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 TCAT. + * + * @note + * The functions in this module require the build-time feature `OPENTHREAD_CONFIG_BLE_TCAT_ENABLE=1`. + * + * @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_TCAT_H_ +#define OPENTHREAD_TCAT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-ble-secure + * + * @brief + * This module includes functions that implement TCAT communication. + * + * The functions in this module are available when TCAT feature + * (`OPENTHREAD_CONFIG_BLE_TCAT_ENABLE`) is enabled. + * + * @{ + * + */ + +#define OT_TCAT_MAX_SERVICE_NAME_LENGTH \ + 15 ///< Maximum string length of a UDP or TCP service name (does not include null char). + +/** + * Represents TCAT TLV types. + * + */ +typedef enum otTcatTlvType +{ + // Command Class General + OT_TCAT_TLV_COMMAND = 0, ///< TCAT command TLV --> !!!! OBSOLETE!!!! + OT_TCAT_TLV_RESPONSE_WITH_STATUS = 1, ///< TCAT response with status value TLV + OT_TCAT_TLV_RESPONSE_WITH_PAYLOAD = 2, ///< TCAT response with payload TLV + OT_TCAT_TLV_RESPONSE_EVENT = 3, ///< TCAT response event TLV (reserved) + OT_TCAT_TLV_GET_NETWORK_NAME = 8, ///< TCAT network name query TLV + OT_TCAT_TLV_DISCONNECT = 9, ///< TCAT disconnect request TLV + OT_TCAT_TLV_PING = 10, ///< TCAT ping request TLV + OT_TCAT_TLV_GET_DEVICE_ID = 11, ///< TCAT device ID query TLV + OT_TCAT_TLV_GET_EXTENDED_PAN_ID = 12, ///< TCAT extended PAN ID query TLV + OT_TCAT_TLV_PRESENT_PSKD_HASH = 30, // 16, ///< TCAT commissioner rights elevation request TLV using PSKd hash + OT_TCAT_TLV_PRESENT_PSKC_HASH = 17, ///< TCAT commissioner rights elevation request TLV using PSKc hash + OT_TCAT_TLV_PRESENT_INSTALL_CODE_HASH = + 31, // 18, ///< TCAT commissioner rights elevation request TLV using install code + OT_TCAT_TLV_REQUEST_RANDOM_NUM_CHALLENGE = 19, ///< TCAT random number challenge query TLV + OT_TCAT_TLV_REQUEST_PSKD_HASH = 20, ///< TCAT PSKd hash request TLV + + // Command Class Commissioning + OT_TCAT_TLV_SET_ACTIVE_OPERATIONAL_DATASET = 16, // 32, ///< TCAT active operational dataset TLV + OT_TCAT_TLV_SET_ACTIVE_OPERATIONAL_DATASET1 = 33, ///< TCAT active operational dataset alternative #1 TLV + OT_TCAT_TLV_GET_PROVISIONING_TLVS = 36, ///< TCAT provisioning TLVs query TLV + OT_TCAT_TLV_GET_COMMISSIONER_CERTIFICATE = 37, ///< TCAT commissioner certificate query TLV + OT_TCAT_TLV_GET_DIAGNOSTIC_TLVS = 38, ///< TCAT diagnostics TLVs query TLV + OT_TCAT_TLV_START_THREAD_INTERFACE = 39, ///< TCAT start thread interface request TLV + OT_TCAT_TLV_STOP_THREAD_INTERFACE = 40, ///< TCAT stop thread interface request TLV + + // Command Class Extraction + OT_TCAT_TLV_GET_ACTIVE_OPERATIONAL_DATASET = 48, ///< TCAT active oerational dataset query TLV + OT_TCAT_TLV_GET_ACTIVE_OPERATIONAL_DATASET1 = 49, ///< TCAT active oerational dataset alternative #1 query TLV + + // Command Class Decommissioning + OT_TCAT_TLV_DECOMMISSION = 96, ///< TCAT decommission request TLV + + // Command Class Application + OT_TCAT_TLV_SELECT_APPLICATION_LAYER_UDP = 128, ///< TCAT select UDP protocol application layer request TLV + OT_TCAT_TLV_SELECT_APPLICATION_LAYER_TCP = 129, ///< TCAT select TCP protocol application layer request TLV + OT_TCAT_TLV_SEND_APPLICATION_DATA = 18, // 130, ///< TCAT send application data TLV + OT_TCAT_TLV_SEND_VENDOR_SPECIFIC_DATA = 159, ///< TCAT send vendor specific command or data TLV + + // Command Class CCM + OT_TCAT_TLV_SET_LDEVID_OPERATIONAL_CERT = 160, ///< TCAT LDevID operational certificate TLV + OT_TCAT_TLV_SET_LDEVID_PRIVATE_KEY = 161, ///< TCAT LDevID operational certificate pricate key TLV + OT_TCAT_TLV_SET_DOMAIN_CA_CERT = 162, ///< TCAT domain CA certificate TLV + +} otTcatTlvType; + +/** + * Represents TCAT Command types. ---> OBSOLETE + * + */ +typedef enum otTcatCommandType +{ + OT_TCAT_COMMAND_TERMINATE = 0, ///< Terminate connection + OT_TCAT_COMMAND_THREAD_START = 1, ///< Start Thread Interface + OT_TCAT_COMMAND_THREAD_STOP = 2, ///< Stop Thread Interface + +} otTcatCommandType; + +/** + * Represents TCAT status code. + * + */ +typedef enum otTcatStatusCode +{ + OT_TCAT_STATUS_SUCCESS = 0, ///< Command or request was successfully processed + OT_TCAT_STATUS_UNSUPPORTED = 1, ///< Requested command or received TLV is not supported + OT_TCAT_STATUS_PARSE_ERROR = 2, ///< Request / command could not be parsed correctly + OT_TCAT_STATUS_VALUE_ERROR = 3, ///< The value of the transmitted TLV has an error + OT_TCAT_STATUS_GENERAL_ERROR = 4, ///< An error not matching any other category occurred + OT_TCAT_STATUS_BUSY = 5, ///< Command cannot be executed because the resource is busy + OT_TCAT_STATUS_UNDEFINED = 6, ///< The requested value, data or service is not defined (currently) or not present + OT_TCAT_STATUS_HASH_ERROR = 7, ///< The hash value presented by the commissioner was incorrect + OT_TCAT_STATUS_UNAUTHORIZED = 8, ///< Sender does not have sufficient authorization for the given command + +} otTcatStatusCode; + +/** + * Represents TCAT status. + * + */ +typedef enum otTcatMessageType +{ + OT_TCAT_MESSAGE_TYPE_NONE = 0, ///< Message which has been sent without activating the TCAT agent + OT_TCAT_MESSAGE_TYPE_STATUS = 1, ///< Message containing a status code (byte) as defined in otTcatStatusCode + OT_TCAT_MESSAGE_TYPE_UDP = 2, ///< Message directed to a UDP service + OT_TCAT_MESSAGE_TYPE_TCP = 3, ///< Message directed to a TCP service + OT_TCAT_MESSAGE_TYPE_CHANGED_TO_UDP_SERVICE = 4, ///< Client has changed to a UDP service + OT_TCAT_MESSAGE_TYPE_CHANGED_TO_TCP_SERVICE = 5, ///< Client has changed to a TCP service + +} otTcatMessageType; + +/** + * The certificate authorization field header type to indicate the type and version of the certificate. + * + */ +typedef uint8_t otTcatCertificateAuthorizationFieldHeader; + +enum +{ + OT_TCAT_CERTIFICATE_AUTHORIZATION_FIELD_HEADER_C_FLAG = 1 << 0, ///< TCAT commissioner ('1') or device ('0') + OT_TCAT_CERTIFICATE_AUTHORIZATION_FIELD_HEADER_VERSION = 0xD0, ///< Header version (3 bits) +}; + +/** + * The command class flag type to indicate which requirements apply for a given command class. + * + * This is a combination of bit-flags. The specific bit-flags are defined in the enumeration + * `OT_TCAT_COMMAND_CLASS_FLAG_*`. + * + */ +typedef uint8_t otTcatCommandClassFlags; + +enum +{ + OT_TCAT_COMMAND_CLASS_FLAG_ACCESS = + 1 << 0, ///< Access to the command class (device: without without additional requirements). + OT_TCAT_COMMAND_CLASS_FLAG_PSKD = 1 << 1, ///< Access requires proof-of-possession of the device's PSKd + OT_TCAT_COMMAND_CLASS_FLAG_NETWORK_NAME = 1 << 2, ///< Access requires matching network name + OT_TCAT_COMMAND_CLASS_FLAG_XPANID = 1 << 3, ///< Access requires matching XPANID + OT_TCAT_COMMAND_CLASS_FLAG_THREAD_DOMAIN = 1 << 4, ///< Access requires matching XPANID + OT_TCAT_COMMAND_CLASS_FLAG_PSKC = 1 << 5, ///< Access requires proof-of-possession of the device's PSKc +}; + +/** + * @struct otTcatCertificateAuthorizationField + * + * Represents a data structure for storing TCAT Commissioner authorization information is the field 1.3.6.1.4.1.44970.3. + * + */ +OT_TOOL_PACKED_BEGIN +struct otTcatCertificateAuthorizationField +{ + otTcatCertificateAuthorizationFieldHeader mHeader; ///< Typ and version + otTcatCommandClassFlags mCommissioningFlags; ///< Command class flags + otTcatCommandClassFlags mExtractionFlags; ///< Command class flags + otTcatCommandClassFlags mDecommissioningFlags; ///< Command class flags + otTcatCommandClassFlags mApplicationFlags; ///< Command class flags + +} OT_TOOL_PACKED_END; + +/** + * Represents a data structure for storing TCAT Commissioner authorization information is the field 1.3.6.1.4.1.44970.3. + * + */ +typedef struct otTcatCertificateAuthorizationField otTcatCertificateAuthorizationField; + +/** + * This structure represents a TCAT vendor information. + * + */ +typedef struct otTcatVendorInfo +{ + const char *mProvisioningUrl; ///< Provisioning URL path string + const char *mVendorName; ///< Vendor name string + const char *mVendorModel; ///< Vendor model string + const char *mVendorSwVersion; ///< Vendor software version string + const char *mVendorData; ///< Vendor specific data string + const char *mPskdString; ///< Vendor managed pre-shared key for device + const char *mInstallCode; ///< Vendor managed install code string + const char *mDeviceId; ///< Vendor managed device ID string (if NULL: device ID is set to EUI-64 in binary format) + +} otTcatVendorInfo; + +/** + * Pointer to call when application data was received over a TCAT TLS connection. + * + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aMessage A pointer to the message. + * @param[in] aTcatMessageType The message type received. + * @param[in] aServiceName The name of the service the message is direced to. + * @param[in] aContext A pointer to arbitrary context information. + * + */ +typedef void (*otHandleTcatApplicationDataReceive)(otInstance *aInstance, + otMessage *aMessage, + otTcatMessageType aTcatMessageType, + const char *aServiceName, + void *aContext); + +/** + * Pointer to call to notify the completion of a join operation. + * + * @param[in] aError OT_ERROR_NONE if the join process succeeded. + * OT_ERROR_SECURITY if the join process failed due to security credentials. + * + * @param[in] aContext A pointer to arbitrary context information. + * + */ +typedef void (*otHandleTcatJoin)(otError aError, void *aContext); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* OPENTHREAD_TCAT_H_ */ diff --git a/script/check-arm-build b/script/check-arm-build index 398dff766827..c440958295e0 100755 --- a/script/check-arm-build +++ b/script/check-arm-build @@ -91,7 +91,7 @@ build_nrf52840() main() { - export CPPFLAGS="${CPPFLAGS:-} -DNDEBUG" + export CPPFLAGS="${CPPFLAGS-} -DNDEBUG" if [[ $# == 0 ]]; then build_nrf52840 diff --git a/script/test b/script/test index 8a2372598812..17cf0478e5da 100755 --- a/script/test +++ b/script/test @@ -656,7 +656,7 @@ main() { envsetup "$@" - if [[ -z ${1:-} ]]; then + if [[ -z ${1-} ]]; then print_usage 1 fi diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index c203c266750f..d18f60cedabe 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -47,6 +47,7 @@ set(COMMON_SOURCES cli_output.cpp cli_srp_client.cpp cli_srp_server.cpp + cli_tcat.cpp cli_tcp.cpp cli_udp.cpp ) diff --git a/src/cli/README.md b/src/cli/README.md index 3ad8e6c20825..63b42c5fd000 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -115,6 +115,7 @@ Done - [sntp](#sntp-query-sntp-server-ip-sntp-server-port) - [state](#state) - [srp](README_SRP.md) +- [tcat](README_TCAT.md) - [tcp](README_TCP.md) - [thread](#thread-start) - [timeinqueue](#timeinqueue) diff --git a/src/cli/README_TCAT.md b/src/cli/README_TCAT.md new file mode 100644 index 000000000000..066a57109827 --- /dev/null +++ b/src/cli/README_TCAT.md @@ -0,0 +1,37 @@ +# OpenThread CLI - TCAT Example + +## Command List + +- help [#help] +- start [#start] +- stop [#stop] + +### help + +print help + +```bash +tcat help +help +start +stop +Done +``` + +### start + +Start tcat server and ble advertisement. + +```bash +tcat start +Done +``` + +### stop + +Stop tcat server and ble advertisement. + +```bash +tcat stop +Done +``` diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 05da535690b8..8eafc7a5787f 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -147,6 +147,9 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE , mHistory(aInstance, *this) #endif +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE + , mTcat(aInstance, *this) +#endif #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE , mLocateInProgress(false) #endif @@ -7107,6 +7110,10 @@ template <> otError Interpreter::Process(Arg aArgs[]) return error; } +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE +template <> otError Interpreter::Process(Arg aArgs[]) { return mTcat.Process(aArgs); } +#endif + #if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mTcp.Process(aArgs); } #endif @@ -8121,6 +8128,9 @@ otError Interpreter::ProcessCommand(Arg aArgs[]) CmdEntry("srp"), #endif CmdEntry("state"), +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE + CmdEntry("tcat"), +#endif #if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE CmdEntry("tcp"), #endif diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 54b0346c420f..71221bb0026e 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -70,6 +70,7 @@ #include "cli/cli_output.hpp" #include "cli/cli_srp_client.hpp" #include "cli/cli_srp_server.hpp" +#include "cli/cli_tcat.hpp" #include "cli/cli_tcp.hpp" #include "cli/cli_udp.hpp" #if OPENTHREAD_CONFIG_COAP_API_ENABLE @@ -622,6 +623,9 @@ class Interpreter : public OutputImplementer, public Output #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE History mHistory; #endif +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE + Tcat mTcat; +#endif #endif // OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE diff --git a/src/cli/cli_config.h b/src/cli/cli_config.h index c3762c4a1107..564755b1d2d3 100644 --- a/src/cli/cli_config.h +++ b/src/cli/cli_config.h @@ -58,6 +58,16 @@ #define OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH 384 #endif +/** + * @def OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE + * + * Indicates whether TCAT should be enabled in the CLI tool. + * + */ +#ifndef OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE +#define OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE 1 +#endif + /** * @def OPENTHREAD_CONFIG_CLI_TCP_ENABLE * diff --git a/src/cli/cli_tcat.cpp b/src/cli/cli_tcat.cpp new file mode 100644 index 000000000000..034b3092a3bf --- /dev/null +++ b/src/cli/cli_tcat.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2023, 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. + */ + +#include "openthread-core-config.h" + +#include "cli/cli_output.hpp" + +#include "cli/cli_tcat.hpp" + +#include + +#include +#include +#include + +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE + +#define OT_CLI_BBTC_X509_CERT \ + "-----BEGIN CERTIFICATE-----\r\n" \ + "MIIBdzCCAR2gAwIBAgIEESIzAzAKBggqhkjOPQQDAjBaMQswCQYDVQQGEwJERTER\r\n" \ + "MA8GA1UEBxMIR2FyY2hpbmcxDDAKBgNVBAsTA1NUQTERMA8GA1UEChMITXlWZW5k\r\n" \ + "b3IxFzAVBgNVBAMTDm9wdG90cm9uaWMuY29tMB4XDTIzMDUyMDIzMDYxNloXDTI0\r\n" \ + "MDUyMDIzMDYxNlowFTETMBEGA1UEAxMKRGV2aWNlVHlwZTBZMBMGByqGSM49AgEG\r\n" \ + "CCqGSM49AwEHA0IABNmItKyl/hfcUXALCehmka0iO7sZtRUcLIqL2zbD6X+2EjY7\r\n" \ + "sInir9hSIyf7V+doP/Z+W5v8yG3402jJNk47ZqOjFjAUMBIGCSsGAQQBgt8qAwQF\r\n" \ + "AAA/AAAwCgYIKoZIzj0EAwIDSAAwRQIhAPrqeTxnxkDbA/lDpvozNsc5UPeY6xt5\r\n" \ + "kRjRj73CUN3tAiBycioHdhXOx8+f7fYe030Nw5wzDgjJ8wuu/QElIeCzCg==\r\n" \ + "-----END CERTIFICATE-----\r\n" + +#define OT_CLI_BBTC_PRIV_KEY \ + "-----BEGIN EC PRIVATE KEY-----\r\n" \ + "MHcCAQEEIF4xKKPlATLttEC2OOvzjKSFUo85tE7uh68vdn0hj965oAoGCCqGSM49\r\n" \ + "AwEHoUQDQgAE2Yi0rKX+F9xRcAsJ6GaRrSI7uxm1FRwsiovbNsPpf7YSNjuwieKv\r\n" \ + "2FIjJ/tX52g/9n5bm/zIbfjTaMk2Tjtmow==\r\n" \ + "-----END EC PRIVATE KEY-----\r\n" + +#define OT_CLI_BBTC_TRUSTED_ROOT_CERTIFICATE \ + "-----BEGIN CERTIFICATE-----\r\n" \ + "MIIB3TCCAYOgAwIBAgIJAIEkU9Kpk7sQMAoGCCqGSM49BAMCMFoxCzAJBgNVBAYT\r\n" \ + "AkRFMREwDwYDVQQHEwhHYXJjaGluZzEMMAoGA1UECxMDU1RBMREwDwYDVQQKEwhN\r\n" \ + "eVZlbmRvcjEXMBUGA1UEAxMOb3B0b3Ryb25pYy5jb20wHhcNMjMwMzI0MjMwODI2\r\n" \ + "WhcNMjYwMzI0MjMwODI2WjBaMQswCQYDVQQGEwJERTERMA8GA1UEBxMIR2FyY2hp\r\n" \ + "bmcxDDAKBgNVBAsTA1NUQTERMA8GA1UEChMITXlWZW5kb3IxFzAVBgNVBAMTDm9w\r\n" \ + "dG90cm9uaWMuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIZXjlDNlAxIV\r\n" \ + "k19EVfeQRj755MWWlZnDhaZKbMPuuP+EML9zdIwWDeCleRP5tKq5fmWp0s81lRjr\r\n" \ + "F2AwIs/TLaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUDf0KHNxzEy7q\r\n" \ + "znA405Fx1lQsRLowCgYIKoZIzj0EAwIDSAAwRQIhAPDKNTxO8sLkns1y7ec2w2oR\r\n" \ + "CYoQyDj2d498XeWYkSVuAiBz+GSRnTmdCFzQKfL8/ma7QaNdXihKYrWUdqvlynVV\r\n" \ + "MQ==\r\n" \ + "-----END CERTIFICATE-----\r\n" + +namespace ot { + +namespace Cli { + +otTcatVendorInfo sVendorInfo; +const char kPskdVendor[] = "J01NM3"; +const char kUrl[] = "dummy_url"; + +static void HandleBleSecureReceive(otInstance *aInstance, + otMessage *aMessage, + otTcatMessageType aTcatMessageType, + const char *aServiceName, + void *aContext) +{ + OT_UNUSED_VARIABLE(aContext); + OT_UNUSED_VARIABLE(aTcatMessageType); + OT_UNUSED_VARIABLE(aServiceName); + uint16_t nLen; + uint8_t buf[100]; + + nLen = otMessageRead(aMessage, otMessageGetOffset(aMessage), buf + 5, sizeof(buf) - 6); + buf[nLen + 5] = 0; + + memcpy(buf, "RECV:", 5); + + IgnoreReturnValue(otBleSecureSendApplicationTlv(aInstance, buf, strlen((char *)buf))); + IgnoreReturnValue(otBleSecureFlush(aInstance)); +} + +template <> otError Tcat::Process(Arg aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgs); + + sVendorInfo.mPskdString = kPskdVendor; + sVendorInfo.mProvisioningUrl = kUrl; + + otError error = OT_ERROR_NONE; + + otInstance *myOpenThreadInstance = GetInstancePtr(); + + otBleSecureSetCertificate(myOpenThreadInstance, (const uint8_t *)(OT_CLI_BBTC_X509_CERT), + sizeof(OT_CLI_BBTC_X509_CERT), (const uint8_t *)(OT_CLI_BBTC_PRIV_KEY), + sizeof(OT_CLI_BBTC_PRIV_KEY)); + + otBleSecureSetCaCertificateChain(myOpenThreadInstance, (const uint8_t *)(OT_CLI_BBTC_TRUSTED_ROOT_CERTIFICATE), + sizeof(OT_CLI_BBTC_TRUSTED_ROOT_CERTIFICATE)); + + otBleSecureSetSslAuthMode(myOpenThreadInstance, true); + + SuccessOrExit(error = otBleSecureStart(myOpenThreadInstance, nullptr, HandleBleSecureReceive, true, nullptr)); + SuccessOrExit(error = otBleSecureTcatStart(myOpenThreadInstance, &sVendorInfo, nullptr)); + +exit: + return error; +} + +template <> otError Tcat::Process(Arg aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgs); + otError error = OT_ERROR_NONE; + otBleSecureStop(GetInstancePtr()); + return error; +} + +otError Tcat::Process(Arg aArgs[]) +{ +#define CmdEntry(aCommandString) \ + { \ + aCommandString, &Tcat::Process \ + } + + static constexpr Command kCommands[] = {CmdEntry("start"), CmdEntry("stop")}; + + static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted"); + + otError error = OT_ERROR_NONE; + const Command *command; + + if (aArgs[0].IsEmpty() || (aArgs[0] == "help")) + { + OutputCommandTable(kCommands); + ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE); + } + + command = BinarySearch::Find(aArgs[0].GetCString(), kCommands); + VerifyOrExit(command != nullptr); + + error = (this->*command->mHandler)(aArgs + 1); + +exit: + return error; +} + +} // namespace Cli +} // namespace ot +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE diff --git a/src/cli/cli_tcat.hpp b/src/cli/cli_tcat.hpp new file mode 100644 index 000000000000..32e2e6d6f93c --- /dev/null +++ b/src/cli/cli_tcat.hpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023, 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. + */ + +#ifndef CLI_TCAT_HPP_ +#define CLI_TCAT_HPP_ + +#include "openthread-core-config.h" + +#include "cli/cli_output.hpp" + +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE + +namespace ot { + +namespace Cli { + +/** + * Implements the Tcat CLI interpreter. + * + */ +class Tcat : private Output +{ +public: + typedef Utils::CmdLineParser::Arg Arg; + + /** + * Constructor + * + * @param[in] aInstance The OpenThread Instance. + * @param[in] aOutputImplementer An `OutputImplementer`. + * + */ + Tcat(otInstance *aInstance, OutputImplementer &aOutputImplementer) + : Output(aInstance, aOutputImplementer) + { + } + + /** + * Processes a CLI sub-command. + * + * @param[in] aArgs An array of command line arguments. + * + * @retval OT_ERROR_NONE Successfully executed the CLI command. + * @retval OT_ERROR_PENDING The CLI command was successfully started but final result is pending. + * @retval OT_ERROR_INVALID_COMMAND Invalid or unknown CLI command. + * @retval OT_ERROR_INVALID_ARGS Invalid arguments. + * @retval ... Error during execution of the CLI command. + * + */ + otError Process(Arg aArgs[]); + +private: + using Command = CommandEntry; + + template otError Process(Arg aArgs[]); +}; + +} // namespace Cli + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_BLE_SECURE_CLI_ENABLE + +#endif // CLI_TCAT_HPP_ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 1b3297074db1..2c197942ba15 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -74,6 +74,10 @@ if (openthread_enable_core_config_args) { defines += [ "OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE=1" ] } + if (OPENTHREAD_CONFIG_BLE_TCAT_ENABLE) { + defines += [ "OPENTHREAD_CONFIG_BLE_TCAT_ENABLE=1" ] + } + if (openthread_config_border_agent_enable) { defines += [ "OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE=1" ] } @@ -301,6 +305,7 @@ config("core_config") { openthread_core_files = [ "api/backbone_router_api.cpp", "api/backbone_router_ftd_api.cpp", + "api/ble_secure_api.cpp", "api/border_agent_api.cpp", "api/border_router_api.cpp", "api/border_routing_api.cpp", @@ -530,6 +535,8 @@ openthread_core_files = [ "meshcop/network_name.hpp", "meshcop/panid_query_client.cpp", "meshcop/panid_query_client.hpp", + "meshcop/tcat_agent.cpp", + "meshcop/tcat_agent.hpp", "meshcop/timestamp.cpp", "meshcop/timestamp.hpp", "net/checksum.cpp", @@ -585,6 +592,8 @@ openthread_core_files = [ "net/tcp6_ext.hpp", "net/udp6.cpp", "net/udp6.hpp", + "radio/ble_secure.cpp", + "radio/ble_secure.hpp", "radio/max_power_table.hpp", "radio/radio.cpp", "radio/radio.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e77b576e173b..8bd846066430 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -33,6 +33,7 @@ set(COMMON_INCLUDES set(COMMON_SOURCES api/backbone_router_api.cpp api/backbone_router_ftd_api.cpp + api/ble_secure_api.cpp api/border_agent_api.cpp api/border_router_api.cpp api/border_routing_api.cpp @@ -158,6 +159,7 @@ set(COMMON_SOURCES meshcop/meshcop_tlvs.cpp meshcop/network_name.cpp meshcop/panid_query_client.cpp + meshcop/tcat_agent.cpp meshcop/timestamp.cpp net/checksum.cpp net/dhcp6_client.cpp @@ -185,6 +187,7 @@ set(COMMON_SOURCES net/tcp6.cpp net/tcp6_ext.cpp net/udp6.cpp + radio/ble_secure.cpp radio/radio.cpp radio/radio_callbacks.cpp radio/radio_platform.cpp diff --git a/src/core/api/ble_secure_api.cpp b/src/core/api/ble_secure_api.cpp new file mode 100644 index 000000000000..ba30263d7aed --- /dev/null +++ b/src/core/api/ble_secure_api.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2022, 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 BLE Secure API. + */ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + +#include + +#include +#include "common/as_core_type.hpp" +#include "common/code_utils.hpp" +#include "common/locator_getters.hpp" +#include "meshcop/tcat_agent.hpp" +#include "radio/ble_secure.hpp" + +#include + +using namespace ot; + +otError otBleSecureStart(otInstance *aInstance, + otHandleBleSecureConnect aConnectHandler, + otHandleBleSecureReceive aReceiveHandler, + bool aTlvMode, + void *aContext) +{ + return AsCoreType(aInstance).Get().Start(aConnectHandler, aReceiveHandler, aTlvMode, aContext); +} + +otError otBleSecureTcatStart(otInstance *aInstance, otTcatVendorInfo *aVendorInfo, otHandleTcatJoin aHandler) +{ + return AsCoreType(aInstance).Get().TcatStart( + static_cast(aVendorInfo), aHandler); +} + +void otBleSecureStop(otInstance *aInstance) { AsCoreType(aInstance).Get().Stop(); } + +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +void otBleSecureSetPsk(otInstance *aInstance, + const uint8_t *aPsk, + uint16_t aPskLength, + const uint8_t *aPskIdentity, + uint16_t aPskIdLength) +{ + OT_ASSERT(aPsk != nullptr && aPskLength != 0 && aPskIdentity != nullptr && aPskIdLength != 0); + + AsCoreType(aInstance).Get().SetPreSharedKey(aPsk, aPskLength, aPskIdentity, aPskIdLength); +} +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + +#if defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) +otError otBleSecureGetPeerCertificateBase64(otInstance *aInstance, + unsigned char *aPeerCert, + size_t *aCertLength, + size_t aCertBufferSize) +{ + return AsCoreType(aInstance).Get().GetPeerCertificateBase64(aPeerCert, aCertLength, + aCertBufferSize); +} +#endif // defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + +#if defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) +otError otBleSecureGetPeerSubjectAttributeByOid(otInstance *aInstance, + const char *aOid, + size_t aOidLength, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize, + int *aAns1Type) +{ + return AsCoreType(aInstance).Get().GetPeerSubjectAttributeByOid( + aOid, aOidLength, aAttributeBuffer, aAttributeLength, aAttributeBufferSize, aAns1Type); +} + +otError otBleSecureGetThreadAttributeFromPeerCertificate(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) +{ + return AsCoreType(aInstance).Get().GetThreadAttributeFromPeerCertificate( + aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, aAttributeBufferSize); +} +#endif // defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + +otError otBleSecureGetThreadAttributeFromOwnCertificate(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) +{ + return AsCoreType(aInstance).Get().GetThreadAttributeFromOwnCertificate( + aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, aAttributeBufferSize); +} + +otError otBleSecureGetThreadAttributeFromCaCertificateChain(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) +{ + return AsCoreType(aInstance).Get().GetThreadAttributeFromCaCertificateChain( + aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, aAttributeBufferSize); +} + +void otBleSecureSetSslAuthMode(otInstance *aInstance, bool aVerifyPeerCertificate) +{ + AsCoreType(aInstance).Get().SetSslAuthMode(aVerifyPeerCertificate); +} + +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +void otBleSecureSetCertificate(otInstance *aInstance, + const uint8_t *aX509Cert, + uint32_t aX509Length, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength) +{ + OT_ASSERT(aX509Cert != nullptr && aX509Length != 0 && aPrivateKey != nullptr && aPrivateKeyLength != 0); + + AsCoreType(aInstance).Get().SetCertificate(aX509Cert, aX509Length, aPrivateKey, aPrivateKeyLength); +} + +void otBleSecureSetCaCertificateChain(otInstance *aInstance, + const uint8_t *aX509CaCertificateChain, + uint32_t aX509CaCertChainLength) +{ + OT_ASSERT(aX509CaCertificateChain != nullptr && aX509CaCertChainLength != 0); + + AsCoreType(aInstance).Get().SetCaCertificateChain(aX509CaCertificateChain, aX509CaCertChainLength); +} +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +otError otBleSecureConnect(otInstance *aInstance) { return AsCoreType(aInstance).Get().Connect(); } + +void otBleSecureDisconnect(otInstance *aInstance) { AsCoreType(aInstance).Get().Disconnect(); } + +bool otBleSecureIsConnected(otInstance *aInstance) { return AsCoreType(aInstance).Get().IsConnected(); } + +bool otBleSecureIsConnectionActive(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().IsConnectionActive(); +} + +bool otBleSecureIsPskdVerified(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().IsPskdVerified(); +} + +bool otBleSecureIsPskcVerified(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().IsPskcVerified(); +} + +bool otBleSecureIsTcatEnabled(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().IsTcatEnabled(); +} + +otError otBleSecureSendMessage(otInstance *aInstance, otMessage *aMessage) +{ + return AsCoreType(aInstance).Get().SendMessage(static_cast(aMessage)); +} + +otError otBleSecureSend(otInstance *aInstance, uint8_t *aBuf, uint16_t aLength) +{ + return AsCoreType(aInstance).Get().Send(aBuf, aLength); +} + +otError otBleSecureSendApplicationTlv(otInstance *aInstance, uint8_t *aBuf, uint16_t aLength) +{ + return AsCoreType(aInstance).Get().SendApplicationTlv(aBuf, aLength); +} + +otError otBleSecureFlush(otInstance *aInstance) { return AsCoreType(aInstance).Get().Flush(); } + +void otPlatBleGattServerOnWriteRequest(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket) +{ + OT_UNUSED_VARIABLE(aHandle); // Only a single handle is expected for RX + + if (aPacket == NULL) + return; + + AsCoreType(aInstance).Get().HandleBleReceive(aPacket->mValue, aPacket->mLength); +} + +void otPlatBleGapOnConnected(otInstance *aInstance, uint16_t aConnectionId) +{ + IgnoreReturnValue(AsCoreType(aInstance).Get().HandleBleConnected(aConnectionId)); +} + +void otPlatBleGapOnDisconnected(otInstance *aInstance, uint16_t aConnectionId) +{ + IgnoreReturnValue(AsCoreType(aInstance).Get().HandleBleDisconnected(aConnectionId)); +} + +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index 10d545d669ac..e14d6164943a 100644 --- a/src/core/common/instance.cpp +++ b/src/core/common/instance.cpp @@ -201,6 +201,9 @@ Instance::Instance(void) #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE , mApplicationCoapSecure(*this, /* aLayerTwoSecurity */ true) #endif +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + , mApplicationBleSecure(*this) +#endif #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE , mPingSender(*this) #endif diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index 0a262bab0337..d07eb2c47d8b 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -97,6 +97,7 @@ #include "net/sntp_client.hpp" #include "net/srp_client.hpp" #include "net/srp_server.hpp" +#include "radio/ble_secure.hpp" #include "thread/address_resolver.hpp" #include "thread/announce_begin_server.hpp" #include "thread/announce_sender.hpp" @@ -590,6 +591,10 @@ class Instance : public otInstance, private NonCopyable Coap::CoapSecure mApplicationCoapSecure; #endif +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + Ble::BleSecure mApplicationBleSecure; +#endif + #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE Utils::PingSender mPingSender; #endif @@ -1010,6 +1015,10 @@ template <> inline FactoryDiags::Diags &Instance::Get(void) { return mDiags; } template <> inline Utils::PowerCalibration &Instance::Get(void) { return mPowerCalibration; } #endif +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +template <> inline Ble::BleSecure &Instance::Get(void) { return mApplicationBleSecure; } +#endif + /** * @} * diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index d40bf08a2781..0ad3ee9a96ea 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -284,7 +284,8 @@ class Message : public otMessage, public Buffer, public GetProvider kTypeSupervision = 2, ///< A child supervision frame. kTypeMacEmptyData = 3, ///< An empty MAC data frame. kTypeIp4 = 4, ///< A full uncompressed IPv4 packet, for NAT64. - kTypeOther = 5, ///< Other (data) message. + kTypeBle = 5, ///< A BLE payload message. + kTypeOther = 6, ///< Other (data) message. }; /** diff --git a/src/core/config/dtls.h b/src/core/config/dtls.h index 09e833788bcc..5f0182aafb88 100644 --- a/src/core/config/dtls.h +++ b/src/core/config/dtls.h @@ -59,7 +59,7 @@ #ifndef OPENTHREAD_CONFIG_DTLS_ENABLE #define OPENTHREAD_CONFIG_DTLS_ENABLE \ (OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE || OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE || \ - OPENTHREAD_CONFIG_COMMISSIONER_ENABLE || OPENTHREAD_CONFIG_JOINER_ENABLE) + OPENTHREAD_CONFIG_COMMISSIONER_ENABLE || OPENTHREAD_CONFIG_JOINER_ENABLE || OPENTHREAD_CONFIG_BLE_TCAT_ENABLE) #endif #endif // CONFIG_DTLS_H_ diff --git a/src/core/config/ip6.h b/src/core/config/ip6.h index 75976e1a1630..82dc45c95477 100644 --- a/src/core/config/ip6.h +++ b/src/core/config/ip6.h @@ -178,7 +178,7 @@ * Define as 1 to enable support for TLS over TCP. * */ -#if OPENTHREAD_CONFIG_TCP_ENABLE && !defined(OPENTHREAD_CONFIG_TLS_ENABLE) +#if (OPENTHREAD_CONFIG_TCP_ENABLE || OPENTHREAD_CONFIG_BLE_TCAT_ENABLE) && !defined(OPENTHREAD_CONFIG_TLS_ENABLE) #define OPENTHREAD_CONFIG_TLS_ENABLE 1 #endif diff --git a/src/core/meshcop/dtls.cpp b/src/core/meshcop/dtls.cpp index 28337a62e697..4f95d676907c 100644 --- a/src/core/meshcop/dtls.cpp +++ b/src/core/meshcop/dtls.cpp @@ -73,7 +73,7 @@ const int Dtls::sHashes[] = {MBEDTLS_MD_SHA256, MBEDTLS_MD_NONE}; #endif #endif -Dtls::Dtls(Instance &aInstance, bool aLayerTwoSecurity) +Dtls::Dtls(Instance &aInstance, bool aLayerTwoSecurity, bool aDatagramTransport) : InstanceLocator(aInstance) , mState(kStateClosed) , mPskLength(0) @@ -82,12 +82,15 @@ Dtls::Dtls(Instance &aInstance, bool aLayerTwoSecurity) , mTimerIntermediate(0) , mTimerSet(false) , mLayerTwoSecurity(aLayerTwoSecurity) + , mDatagramTransport(aDatagramTransport) , mReceiveMessage(nullptr) + , mContext(nullptr) , mSocket(aInstance) + , mTransportContext(nullptr) , mMessageSubType(Message::kSubTypeNone) , mMessageDefaultSubType(Message::kSubTypeNone) { -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE #ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED mPreSharedKey = nullptr; mPreSharedKeyIdentity = nullptr; @@ -121,9 +124,12 @@ Dtls::Dtls(Instance &aInstance, bool aLayerTwoSecurity) void Dtls::FreeMbedtls(void) { #if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_COOKIE_C) - mbedtls_ssl_cookie_free(&mCookieCtx); + if (mDatagramTransport) + { + mbedtls_ssl_cookie_free(&mCookieCtx); + } #endif -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE #ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED mbedtls_x509_crt_free(&mCaChain); mbedtls_x509_crt_free(&mOwnCert); @@ -144,7 +150,8 @@ Error Dtls::Open(ReceiveHandler aReceiveHandler, ConnectedHandler aConnectedHand mConnectedCallback.Set(aConnectedHandler, aContext); mReceiveCallback.Set(aReceiveHandler, aContext); - mState = kStateOpen; + mContext = aContext; + mState = kStateOpen; exit: return error; @@ -178,11 +185,17 @@ void Dtls::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageI ExitNow(); case Dtls::kStateOpen: + IgnoreError(mSocket.Connect(Ip6::SockAddr(aMessageInfo.GetPeerAddr(), aMessageInfo.GetPeerPort()))); + mMessageInfo.SetPeerAddr(aMessageInfo.GetPeerAddr()); mMessageInfo.SetPeerPort(aMessageInfo.GetPeerPort()); mMessageInfo.SetIsHostInterface(aMessageInfo.IsHostInterface()); - mMessageInfo.SetSockAddr(aMessageInfo.GetSockAddr()); + if (Get().HasUnicastAddress(aMessageInfo.GetSockAddr())) + { + mMessageInfo.SetSockAddr(aMessageInfo.GetSockAddr()); + } + mMessageInfo.SetSockPort(aMessageInfo.GetSockPort()); SuccessOrExit(Setup(false)); @@ -232,6 +245,7 @@ Error Dtls::Bind(TransportCallback aCallback, void *aContext) VerifyOrExit(!mTransportCallback.IsSet(), error = kErrorAlready); mTransportCallback.Set(aCallback, aContext); + mTransportContext = aContext; exit: return error; @@ -248,7 +262,7 @@ Error Dtls::Setup(bool aClient) mbedtls_ssl_init(&mSsl); mbedtls_ssl_config_init(&mConf); -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE #ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED mbedtls_x509_crt_init(&mCaChain); mbedtls_x509_crt_init(&mOwnCert); @@ -256,15 +270,20 @@ Error Dtls::Setup(bool aClient) #endif #endif #if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_COOKIE_C) - mbedtls_ssl_cookie_init(&mCookieCtx); + if (mDatagramTransport) + { + mbedtls_ssl_cookie_init(&mCookieCtx); + } #endif - rval = mbedtls_ssl_config_defaults(&mConf, aClient ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_PRESET_DEFAULT); + rval = mbedtls_ssl_config_defaults( + &mConf, aClient ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER, + mDatagramTransport ? MBEDTLS_SSL_TRANSPORT_DATAGRAM : MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); VerifyOrExit(rval == 0); -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE - if (mVerifyPeerCertificate && mCipherSuites[0] == MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) +#if OPENTHREAD_CONFIG_TLS_API_ENABLE + if (mVerifyPeerCertificate && (mCipherSuites[0] == MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 || + mCipherSuites[0] == MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)) { mbedtls_ssl_conf_authmode(&mConf, MBEDTLS_SSL_VERIFY_REQUIRED); } @@ -313,7 +332,7 @@ Error Dtls::Setup(bool aClient) mbedtls_ssl_conf_dbg(&mConf, HandleMbedtlsDebug, this); #if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_COOKIE_C) - if (!aClient) + if (!aClient && mDatagramTransport) { rval = mbedtls_ssl_cookie_setup(&mCookieCtx, Crypto::MbedTls::CryptoSecurePrng, nullptr); VerifyOrExit(rval == 0); @@ -326,16 +345,20 @@ Error Dtls::Setup(bool aClient) VerifyOrExit(rval == 0); mbedtls_ssl_set_bio(&mSsl, this, &Dtls::HandleMbedtlsTransmit, HandleMbedtlsReceive, nullptr); - mbedtls_ssl_set_timer_cb(&mSsl, this, &Dtls::HandleMbedtlsSetTimer, HandleMbedtlsGetTimer); + + if (mDatagramTransport) + { + mbedtls_ssl_set_timer_cb(&mSsl, this, &Dtls::HandleMbedtlsSetTimer, HandleMbedtlsGetTimer); + } if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) { rval = mbedtls_ssl_set_hs_ecjpake_password(&mSsl, mPsk, mPskLength); } -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE else { - rval = SetApplicationCoapSecureKeys(); + rval = SetApplicationSecureKeys(); } #endif VerifyOrExit(rval == 0); @@ -348,10 +371,10 @@ Error Dtls::Setup(bool aClient) { LogInfo("DTLS started"); } -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE else { - LogInfo("Application Coap Secure DTLS started"); + LogInfo("Application Secure (D)TLS started"); } #endif @@ -369,14 +392,16 @@ Error Dtls::Setup(bool aClient) return Crypto::MbedTls::MapError(rval); } -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE -int Dtls::SetApplicationCoapSecureKeys(void) +#if OPENTHREAD_CONFIG_TLS_API_ENABLE +int Dtls::SetApplicationSecureKeys(void) { int rval = 0; switch (mCipherSuites[0]) { case MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + case MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + #ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED if (mCaChainSrc != nullptr) { @@ -426,14 +451,15 @@ int Dtls::SetApplicationCoapSecureKeys(void) return rval; } -#endif // OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#endif // OPENTHREAD_CONFIG_TLS_API_ENABLE void Dtls::Close(void) { Disconnect(); - mState = kStateClosed; - mTimerSet = false; + mState = kStateClosed; + mTransportContext = nullptr; + mTimerSet = false; mTransportCallback.Clear(); IgnoreError(mSocket.Close()); @@ -472,7 +498,7 @@ Error Dtls::SetPsk(const uint8_t *aPsk, uint8_t aPskLength) return error; } -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE #ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED void Dtls::SetCertificate(const uint8_t *aX509Certificate, @@ -491,7 +517,15 @@ void Dtls::SetCertificate(const uint8_t *aX509Certificate, mPrivateKeySrc = aPrivateKey; mPrivateKeyLength = aPrivateKeyLength; - mCipherSuites[0] = MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; + if (mDatagramTransport) + { + mCipherSuites[0] = MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; + } + else + { + mCipherSuites[0] = MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256; + } + mCipherSuites[1] = 0; } @@ -550,7 +584,163 @@ Error Dtls::GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLeng } #endif // defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) -#endif // OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) +Error Dtls::GetPeerSubjectAttributeByOid(const char *aOid, + size_t aOidLength, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize, + int *aAns1Type) +{ + Error error = kErrorNone; + const mbedtls_asn1_named_data *data; + size_t length; + mbedtls_x509_crt *peerCert = const_cast(mbedtls_ssl_get_peer_cert(&mSsl)); + + if (aAttributeLength != nullptr) + { + *aAttributeLength = 0; + } + + VerifyOrExit(aAttributeBuffer != nullptr, error = kErrorNoBufs); + VerifyOrExit(peerCert != nullptr, error = kErrorInvalidState); + data = mbedtls_asn1_find_named_data(&peerCert->subject, aOid, aOidLength); + VerifyOrExit(data != nullptr, error = kErrorNotFound); + length = data->val.len; + VerifyOrExit(length <= aAttributeBufferSize, error = kErrorNoBufs); + + if (aAttributeLength != nullptr) + { + *aAttributeLength = length; + } + + if (aAns1Type != nullptr) + { + *aAns1Type = data->val.tag; + } + + memcpy(aAttributeBuffer, data->val.p, length); + +exit: + return error; +} + +Error Dtls::GetThreadAttributeFromPeerCertificate(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) +{ + const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&mSsl); + + return GetThreadAttributeFromCertificate(cert, aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, + aAttributeBufferSize); +} + +#endif // defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + +Error Dtls::GetThreadAttributeFromOwnCertificate(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) +{ + const mbedtls_x509_crt *cert = &mOwnCert; + + return GetThreadAttributeFromCertificate(cert, aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, + aAttributeBufferSize); +} + +Error Dtls::GetThreadAttributeFromCaCertificateChain(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) +{ + const mbedtls_x509_crt *cert = &mCaChain; + + return GetThreadAttributeFromCertificate(cert, aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, + aAttributeBufferSize); +} + +Error Dtls::GetThreadAttributeFromCertificate(const mbedtls_x509_crt *aCert, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) +{ + Error error = kErrorNotFound; + char oid[9] = {0x2B, 0x06, 0x01, 0x04, 0x01, static_cast(0x82), static_cast(0xDF), + 0x2A, 0x00}; // 1.3.6.1.4.1.44970.0 + mbedtls_x509_buf v3_ext; + unsigned char *p, *end, *end_ext_data; + size_t len; + mbedtls_x509_buf extn_oid; + int ret, is_critical; + + if (aAttributeLength != nullptr) + { + *aAttributeLength = 0; + } + + VerifyOrExit(aCert != nullptr, error = kErrorInvalidState); + v3_ext = aCert->v3_ext; + p = v3_ext.p; + VerifyOrExit(p != nullptr, error = kErrorInvalidState); + end = p + v3_ext.len; + VerifyOrExit(mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) == 0, + error = kErrorParse); + VerifyOrExit(end == p + len, error = kErrorParse); + + VerifyOrExit(aThreadOidDescriptor < 128, error = kErrorNotImplemented); + oid[sizeof(oid) - 1] = static_cast(aThreadOidDescriptor); + + while (p < end) + { + is_critical = 0; + VerifyOrExit(mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) == 0, + error = kErrorParse); + end_ext_data = p + len; + + // Get extension ID + VerifyOrExit(mbedtls_asn1_get_tag(&p, end_ext_data, &extn_oid.len, MBEDTLS_ASN1_OID) == 0, error = kErrorParse); + extn_oid.tag = MBEDTLS_ASN1_OID; + extn_oid.p = p; + p += extn_oid.len; + + // Get optional critical + ret = mbedtls_asn1_get_bool(&p, end_ext_data, &is_critical); + VerifyOrExit(ret == 0 || ret == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG, error = kErrorParse); + + // Data should be octet string type + VerifyOrExit(mbedtls_asn1_get_tag(&p, end_ext_data, &len, MBEDTLS_ASN1_OCTET_STRING) == 0, error = kErrorParse); + VerifyOrExit(end_ext_data == p + len, error = kErrorParse); + + if (is_critical) + continue; + if (extn_oid.len != sizeof(oid)) + continue; + + if (memcmp(extn_oid.p, oid, sizeof(oid)) == 0) + { + if (aAttributeLength != nullptr) + { + *aAttributeLength = len; + } + + if (aAttributeBuffer != nullptr) + { + VerifyOrExit(len <= aAttributeBufferSize, error = kErrorNoBufs); + memcpy(aAttributeBuffer, p, len); + } + + error = kErrorNone; + break; + } + } + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_TLS_API_ENABLE #ifdef MBEDTLS_SSL_SRV_C Error Dtls::SetClientId(const uint8_t *aClientId, uint8_t aLength) @@ -606,10 +796,10 @@ int Dtls::HandleMbedtlsTransmit(const unsigned char *aBuf, size_t aLength) { LogDebg("HandleMbedtlsTransmit"); } -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE else { - LogDebg("ApplicationCoapSecure HandleMbedtlsTransmit"); + LogDebg("Application CoapSecure or Ble HandleMbedtlsTransmit"); } #endif @@ -650,10 +840,10 @@ int Dtls::HandleMbedtlsReceive(unsigned char *aBuf, size_t aLength) { LogDebg("HandleMbedtlsReceive"); } -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE else { - LogDebg("ApplicationCoapSecure HandleMbedtlsReceive"); + LogDebg("Application CoapSecure or Ble HandleMbedtlsReceive"); } #endif @@ -682,10 +872,10 @@ int Dtls::HandleMbedtlsGetTimer(void) { LogDebg("HandleMbedtlsGetTimer"); } -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE else { - LogDebg("ApplicationCoapSecure HandleMbedtlsGetTimer"); + LogDebg("Application CoapSecure or Ble HandleMbedtlsGetTimer"); } #endif @@ -720,10 +910,10 @@ void Dtls::HandleMbedtlsSetTimer(uint32_t aIntermediate, uint32_t aFinish) { LogDebg("SetTimer"); } -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE else { - LogDebg("ApplicationCoapSecure SetTimer"); + LogDebg("Application CoapSecure or Ble SetTimer"); } #endif diff --git a/src/core/meshcop/dtls.hpp b/src/core/meshcop/dtls.hpp index 2a4df728d226..916c269680b5 100644 --- a/src/core/meshcop/dtls.hpp +++ b/src/core/meshcop/dtls.hpp @@ -36,12 +36,22 @@ #include "openthread-core-config.h" +#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE || OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +#define OPENTHREAD_CONFIG_TLS_API_ENABLE 1 +#endif + #include #include #include #include -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +#ifndef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#error OPENTHREAD_CONFIG_BLE_TCAT_ENABLE requires MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif +#endif + +#if OPENTHREAD_CONFIG_TLS_API_ENABLE #if defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) #include #endif @@ -79,7 +89,7 @@ class Dtls : public InstanceLocator * @param[in] aLayerTwoSecurity Specifies whether to use layer two security or not. * */ - explicit Dtls(Instance &aInstance, bool aLayerTwoSecurity); + explicit Dtls(Instance &aInstance, bool aLayerTwoSecurity, bool aDatagramTransport = true); /** * Pointer is called when a connection is established or torn down. @@ -212,7 +222,7 @@ class Dtls : public InstanceLocator */ Error SetPsk(const uint8_t *aPsk, uint8_t aPskLength); -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE #ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED /** * Sets the Pre-Shared Key (PSK) for DTLS sessions- @@ -280,6 +290,106 @@ class Dtls : public InstanceLocator Error GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize); #endif // defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) +#if defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + /** + * Returns an attribute value identified by its OID from the subject + * of the peer x509 certificate. The peer OID is provided in binary format. + * The attribute length is set if the attribute was successfully read or zero + * if unsuccessful. The ANS1 type as is set as defineded in the ITU-T X.690 standard + * if the attribute was successfully read. + * + * @param[in] aOid A pointer to the OID to be found. + * @param[in] aOidLength The length of the OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * @param[out] aAns1Type A pointer to the ANS1 type of the attribute written to the buffer. + * + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNone Successfully read attribute. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * + */ + Error GetPeerSubjectAttributeByOid(const char *aOid, + size_t aOidLength, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize, + int *aAns1Type); + + /** + * Eeturns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the peer x509 certificate, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorNotFound The requested attribute was not found. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNotImplemented The value of aThreadOidDescriptor is >127. + * @retval kErrorParse The certificate extensions could not be parsed. + * + */ + Error GetThreadAttributeFromPeerCertificate(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize); +#endif // defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + + /** + * Returns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the own x509 certificate, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorNotFound The requested attribute was not found. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNotImplemented The value of aThreadOidDescriptor is >127. + * @retval kErrorParse The certificate extensions could not be parsed. + * + */ + Error GetThreadAttributeFromOwnCertificate(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize); + + /** + * Returns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the CA x509 certificate chain, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorNotFound The requested attribute was not found. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNotImplemented The value of aThreadOidDescriptor is >127. + * @retval kErrorParse The certificate extensions could not be parsed. + * + */ + Error GetThreadAttributeFromCaCertificateChain(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize); + /** * Set the authentication mode for a dtls connection. * @@ -290,7 +400,7 @@ class Dtls : public InstanceLocator * */ void SetSslAuthMode(bool aVerifyPeerCertificate) { mVerifyPeerCertificate = aVerifyPeerCertificate; } -#endif // OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#endif // OPENTHREAD_CONFIG_TLS_API_ENABLE #ifdef MBEDTLS_SSL_SRV_C /** @@ -357,7 +467,7 @@ class Dtls : public InstanceLocator static constexpr uint32_t kGuardTimeNewConnectionMilli = 2000; -#if !OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if !OPENTHREAD_CONFIG_TLS_API_ENABLE static constexpr uint16_t kApplicationDataMaxLength = 1152; #else static constexpr uint16_t kApplicationDataMaxLength = OPENTHREAD_CONFIG_DTLS_APPLICATION_DATA_MAX_LENGTH; @@ -369,14 +479,20 @@ class Dtls : public InstanceLocator void FreeMbedtls(void); Error Setup(bool aClient); -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE /** * Set keys and/or certificates for dtls session dependent of used cipher suite. * * @retval mbedtls error, 0 if successfully. * */ - int SetApplicationCoapSecureKeys(void); + int SetApplicationSecureKeys(void); + + Error GetThreadAttributeFromCertificate(const mbedtls_x509_crt *aCert, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize); #endif static void HandleMbedtlsDebug(void *aContext, int aLevel, const char *aFile, int aLine, const char *aStr); @@ -459,7 +575,7 @@ class Dtls : public InstanceLocator #endif #endif -#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#if OPENTHREAD_CONFIG_TLS_API_ENABLE #ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED const uint8_t *mCaChainSrc; uint32_t mCaChainLength; @@ -494,16 +610,19 @@ class Dtls : public InstanceLocator bool mTimerSet : 1; bool mLayerTwoSecurity : 1; + bool mDatagramTransport : 1; Message *mReceiveMessage; Callback mConnectedCallback; Callback mReceiveCallback; + void *mContext; Ip6::MessageInfo mMessageInfo; Ip6::Udp::Socket mSocket; Callback mTransportCallback; + void *mTransportContext; Message::SubType mMessageSubType; Message::SubType mMessageDefaultSubType; diff --git a/src/core/meshcop/tcat_agent.cpp b/src/core/meshcop/tcat_agent.cpp new file mode 100644 index 000000000000..e86ad0a9b1bf --- /dev/null +++ b/src/core/meshcop/tcat_agent.cpp @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2023, 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 TCAT Agent service. + */ + +#include "tcat_agent.hpp" + +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + +#include + +#include "common/array.hpp" +#include "common/as_core_type.hpp" +#include "common/code_utils.hpp" +#include "common/debug.hpp" +#include "common/encoding.hpp" +#include "common/instance.hpp" +#include "common/locator_getters.hpp" +#include "common/log.hpp" +#include "common/string.hpp" +#include "meshcop/meshcop.hpp" +#include "radio/radio.hpp" +#include "thread/thread_netif.hpp" +#include "thread/uri_paths.hpp" +#include "utils/otns.hpp" + +namespace ot { +namespace MeshCoP { + +RegisterLogModule("TcatAgent"); + +bool TcatAgent::VendorInfo::IsValid(void) const +{ + if (mProvisioningUrl != nullptr && !IsValidUtf8String(mProvisioningUrl)) + return false; + + return true; +} + +TcatAgent::TcatAgent(Instance &aInstance) + : InstanceLocator(aInstance) + , mVendorInfo(nullptr) + , mCurrentApplicationLayer(OT_TCAT_MESSAGE_TYPE_NONE) + , mEnabled(false) + , mConnected(false) + , mAlreadyCommissioned(false) + , mPskdVerified(false) + , mPskcVerified(false) +{ + mJoinerPskd.Clear(); + mCurrentServiceName[0] = 0; +} + +Error TcatAgent::Start(TcatAgent::VendorInfo *aVendorInfo, + AppDataReceiveCallback aAppDataReceiveCallback, + JoinCallback aHandler, + void *aContext) +{ + Error error = kErrorNone; + + LogInfo("TCAT agent starting"); + + VerifyOrExit(aVendorInfo != nullptr, error = kErrorInvalidArgs); + VerifyOrExit(aVendorInfo->mPskdString != nullptr, error = kErrorInvalidArgs); + SuccessOrExit(error = mJoinerPskd.SetFrom(aVendorInfo->mPskdString)); + + if (aVendorInfo != nullptr) + { + VerifyOrExit(aVendorInfo->IsValid(), error = kErrorInvalidArgs); + } + + mAppDataReceiveCallback.Set(aAppDataReceiveCallback, aContext); + mJoinCallback.Set(aHandler, aContext); + + mVendorInfo = aVendorInfo; + mCurrentApplicationLayer = OT_TCAT_MESSAGE_TYPE_NONE; + mEnabled = true; + mConnected = false; + mAlreadyCommissioned = false; + mPskdVerified = false; + mPskcVerified = false; + +exit: + LogError("start TCAT agent", error); + return error; +} + +void TcatAgent::Stop(void) +{ + mCurrentApplicationLayer = OT_TCAT_MESSAGE_TYPE_NONE; + mEnabled = false; + mConnected = false; + mAlreadyCommissioned = false; + mPskdVerified = false; + mPskcVerified = false; + mAppDataReceiveCallback.Clear(); + mJoinCallback.Clear(); + LogInfo("TCAT agent stopped"); +} + +Error TcatAgent::Connected(MeshCoP::Dtls &aTlsContext) +{ + size_t len; + Error error; + + VerifyOrExit(mEnabled, error = kErrorInvalidState); + SuccessOrExit(error = aTlsContext.GetThreadAttributeFromPeerCertificate( + 3, reinterpret_cast(&mCommissionerAuthorizationField), &len, + sizeof(mCommissionerAuthorizationField))); + VerifyOrExit(len == sizeof(mCommissionerAuthorizationField), error = kErrorParse); + SuccessOrExit( + error = aTlsContext.GetThreadAttributeFromOwnCertificate( + 3, reinterpret_cast(&mDeviceAuthorizationField), &len, sizeof(mDeviceAuthorizationField))); + VerifyOrExit(len == sizeof(mCommissionerAuthorizationField), error = kErrorParse); + + mCurrentApplicationLayer = OT_TCAT_MESSAGE_TYPE_NONE; + mCurrentServiceName[0] = 0; + mConnected = true; + mAlreadyCommissioned = false; // TODO + mPskdVerified = false; + mPskcVerified = false; + LogInfo("TCAT agent connected"); + +exit: + return error; +} + +void TcatAgent::Disconnected() +{ + mCurrentApplicationLayer = OT_TCAT_MESSAGE_TYPE_NONE; + mConnected = false; + mAlreadyCommissioned = false; + mPskdVerified = false; + mPskcVerified = false; + LogInfo("TCAT agent disconnected"); +} + +bool TcatAgent::IsCommandClassAuthorized(CommandClassFlags aCommissionerCommandClassFlags, + CommandClassFlags aDeviceCommandClassFlags) const +{ + bool additionalDeviceRequirementMet = false; + + if ((aCommissionerCommandClassFlags & OT_TCAT_COMMAND_CLASS_FLAG_ACCESS) == 0) + { + LogInfo("TCAT not authorized"); + return false; + } + + if (aDeviceCommandClassFlags & OT_TCAT_COMMAND_CLASS_FLAG_ACCESS) + { + additionalDeviceRequirementMet = true; + } + + // more to come + + LogInfo("TCAT authorized"); + return true; +} + +Error TcatAgent::HandleSingleTlv(ot::Message &aIncommingMessage, ot::Message &aOutgoingMessage) +{ + Error error = kErrorParse; + ot::Tlv tlv; + uint32_t offset = aIncommingMessage.GetOffset(); + uint32_t lenght; + bool response = false; + + VerifyOrExit(mConnected, error = kErrorInvalidState); + SuccessOrExit(error = aIncommingMessage.Read(offset, tlv)); + + if (tlv.IsExtended()) + { + ot::ExtendedTlv extTlv; + SuccessOrExit(error = aIncommingMessage.Read(offset, extTlv)); + lenght = extTlv.GetLength(); + offset += sizeof(ot::ExtendedTlv); + } + else + { + lenght = tlv.GetLength(); + offset += sizeof(ot::Tlv); + } + + if (tlv.GetType() < OT_TCAT_TLV_SET_ACTIVE_OPERATIONAL_DATASET) + { + error = HandleGeneral(aIncommingMessage, aOutgoingMessage, tlv.GetType(), offset, lenght, response); + } + else if (tlv.GetType() < OT_TCAT_TLV_GET_ACTIVE_OPERATIONAL_DATASET) + { + error = HandleCommissioning(aIncommingMessage, aOutgoingMessage, tlv.GetType(), offset, lenght, response); + } + else if (tlv.GetType() < OT_TCAT_TLV_DECOMMISSION) + { + error = HandleExtraction(aIncommingMessage, aOutgoingMessage, tlv.GetType(), offset, lenght, response); + } + else if (tlv.GetType() < OT_TCAT_TLV_SELECT_APPLICATION_LAYER_UDP) + { + error = HandleDecommissioning(aIncommingMessage, aOutgoingMessage, tlv.GetType(), offset, lenght, response); + } + else if (tlv.GetType() < OT_TCAT_TLV_SET_LDEVID_OPERATIONAL_CERT) + { + error = HandleApplication(aIncommingMessage, aOutgoingMessage, tlv.GetType(), offset, lenght, response); + } + else + { + error = HandleCCM(aIncommingMessage, aOutgoingMessage, tlv.GetType(), offset, lenght, response); + } + + if (!response) + { + ot::Tlv statusTlv; + StatusCode statusCode; + + tlv.SetType(TlvType::kResponseWithStatus); + tlv.SetLength(sizeof(statusCode)); + + switch (error) + { + case kErrorNone: + statusCode = StatusCode::kTcatSuccess; + break; + + case kErrorParse: + statusCode = StatusCode::kTcatParseError; + break; + + case kErrorInvalidCommand: + statusCode = StatusCode::kTcatUnsupported; + break; + + case kErrorRejected: + statusCode = StatusCode::kTcatUnauthorized; + break; + + default: + statusCode = StatusCode::kTcatGeneralError; + break; + } + + SuccessOrExit(error = aOutgoingMessage.Append(statusTlv)); + SuccessOrExit(error = aOutgoingMessage.Append(statusCode)); + } + + switch (tlv.GetType()) + { + case TlvType::kSetActiveOperationalDataset: + error = HandleActiveDataset(aIncommingMessage, offset, tlv.GetLength()); + break; + + default: + error = kErrorParse; + } + +exit: + return error; +} + +Error TcatAgent::HandleGeneral(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse) +{ + Error error; + CommandType command; + + OT_UNUSED_VARIABLE(aOutgoingMessage); + OT_UNUSED_VARIABLE(aHasResponse); + + switch (aTlvType) + { + case TlvType::kCommand: + VerifyOrExit(aLength == sizeof(command), error = kErrorParse); + SuccessOrExit(error = aIncommingMessage.Read(aOffset, &command, sizeof(command))); + error = HandleCommand(command, aOutgoingMessage); + break; + + case TlvType::kDisconnect: + error = kErrorAbort; + break; + + default: + error = kErrorInvalidCommand; + } + +exit: + return error; +} + +Error TcatAgent::HandleCommissioning(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse) +{ + Error error; + + OT_UNUSED_VARIABLE(aOutgoingMessage); + OT_UNUSED_VARIABLE(aHasResponse); + + if (!IsCommandClassAuthorized(mCommissionerAuthorizationField.mCommissioningFlags, + mDeviceAuthorizationField.mCommissioningFlags)) + { + error = kErrorRejected; + ExitNow(); + } + + switch (aTlvType) + { + case TlvType::kSetActiveOperationalDataset: + error = HandleActiveDataset(aIncommingMessage, aOffset, aLength); + break; + + default: + error = kErrorInvalidCommand; + } + +exit: + return error; +} + +Error TcatAgent::HandleExtraction(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse) +{ + Error error; + + OT_UNUSED_VARIABLE(aIncommingMessage); + OT_UNUSED_VARIABLE(aOutgoingMessage); + OT_UNUSED_VARIABLE(aOffset); + OT_UNUSED_VARIABLE(aLength); + OT_UNUSED_VARIABLE(aHasResponse); + + switch (aTlvType) + { + default: + error = kErrorInvalidCommand; + } + + // exit: + return error; +} + +Error TcatAgent::HandleDecommissioning(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse) +{ + Error error; + + OT_UNUSED_VARIABLE(aIncommingMessage); + OT_UNUSED_VARIABLE(aOutgoingMessage); + OT_UNUSED_VARIABLE(aOffset); + OT_UNUSED_VARIABLE(aLength); + OT_UNUSED_VARIABLE(aHasResponse); + + switch (aTlvType) + { + default: + error = kErrorInvalidCommand; + } + + // exit: + return error; +} + +Error TcatAgent::HandleApplication(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse) +{ + Error error; + + OT_UNUSED_VARIABLE(aOutgoingMessage); + OT_UNUSED_VARIABLE(aLength); + OT_UNUSED_VARIABLE(aHasResponse); + + switch (aTlvType) + { + case kSendApplicationData: + LogInfo("Application data len:%d, offset:%d", aLength, aOffset); + aIncommingMessage.SetOffset(aOffset); + mAppDataReceiveCallback.InvokeIfSet(&GetInstance(), &aIncommingMessage, mCurrentApplicationLayer, + mCurrentServiceName); + break; + + default: + error = kErrorInvalidCommand; + } + + // exit: + return error; +} + +Error TcatAgent::HandleCCM(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse) +{ + Error error; + + OT_UNUSED_VARIABLE(aIncommingMessage); + OT_UNUSED_VARIABLE(aOutgoingMessage); + OT_UNUSED_VARIABLE(aOffset); + OT_UNUSED_VARIABLE(aLength); + OT_UNUSED_VARIABLE(aHasResponse); + + switch (aTlvType) + { + default: + error = kErrorInvalidCommand; + } + + // exit: + return error; +} + +Error TcatAgent::HandleCommand(CommandType aCommand, ot::Message &aOutgoingMessage) +{ + Error error = kErrorNone; + ot::Tlv tlv; + StatusCode response = StatusCode::kTcatSuccess; + + tlv.SetType(TlvType::kResponseWithStatus); + tlv.SetLength(sizeof(response)); + + switch (aCommand) + { + case CommandType::kTerminate: + Stop(); + break; + + case CommandType::kThreadStart: + +#if OPENTHREAD_CONFIG_LINK_RAW_ENABLE + if (Get().IsEnabled()) + response = StatusCode::kTcatUndefined; +#endif + if (response == StatusCode::kTcatSuccess) + { + Get().Up(); + if (Get().Start() != kErrorNone) + response = StatusCode::kTcatUndefined; + } + + break; + + case CommandType::kThreadStop: + Get().Stop(); + break; + + default: + response = StatusCode::kTcatParseError; + } + + SuccessOrExit(error = aOutgoingMessage.Append(tlv)); + SuccessOrExit(error = aOutgoingMessage.Append(response)); + +exit: + return error; +} + +Error TcatAgent::HandleActiveDataset(ot::Message &aIncommingMessage, uint32_t aOffset, uint32_t aLength) +{ + Dataset dataset; + otOperationalDatasetTlvs datasetTlvs; + Error error; + + SuccessOrExit(error = dataset.ReadFromMessage(aIncommingMessage, aOffset, aLength)); + dataset.ConvertTo(datasetTlvs); + Get().Save(datasetTlvs); + +exit: + return error; +} + +} // namespace MeshCoP +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE diff --git a/src/core/meshcop/tcat_agent.hpp b/src/core/meshcop/tcat_agent.hpp new file mode 100644 index 000000000000..478c2709fc8f --- /dev/null +++ b/src/core/meshcop/tcat_agent.hpp @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2023, 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 + * Implements the TCAT Agent service. + */ + +#ifndef TCAT_AGENT_HPP_ +#define TCAT_AGENT_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + +#include + +#include "common/as_core_type.hpp" +#include "common/callback.hpp" +#include "common/locator.hpp" +#include "common/log.hpp" +#include "common/message.hpp" +#include "common/non_copyable.hpp" +#include "mac/mac_types.hpp" +#include "meshcop/dtls.hpp" +#include "meshcop/meshcop.hpp" +#include "meshcop/meshcop_tlvs.hpp" + +namespace ot { + +namespace MeshCoP { + +class TcatAgent : public InstanceLocator, private NonCopyable +{ +public: + /** + * Pointer to call when application data was received over the TLS connection. + * + * Please see otHandleTcatApplicationDataReceive for details. + * + */ + typedef otHandleTcatApplicationDataReceive AppDataReceiveCallback; + + /** + * Pointer to call to notify the completion of a join operation. + * + * Please see otHandleTcatJoin for details. + * + */ + typedef otHandleTcatJoin JoinCallback; + + /** + * The certificate authorization field header type to indicate the type and version of the certificate. + * + */ + typedef otTcatCertificateAuthorizationFieldHeader CertificateAuthorizationFieldHeader; + + /** + * The command class flag type to indicate which requirements apply for a given command class. + * + * This is a combination of bit-flags. The specific bit-flags are defined in the enumeration + * `OT_TCAT_COMMAND_CLASS_FLAG_*`. + * + */ + typedef otTcatCommandClassFlags CommandClassFlags; + + /** + * + * Represents a data structure for storing TCAT Commissioner authorization information is the + * field 1.3.6.1.4.1.44970.3. + * + */ + typedef otTcatCertificateAuthorizationField CertificateAuthorizationField; + + /** + * Represents the TCAT vendor information. + * + */ + class VendorInfo : public otTcatVendorInfo + { + public: + /** + * Validates whether the TCAT vendor information is valid. + * + * @returns Whether the parameters are valid. + * + */ + bool IsValid(void) const; + }; + + /** + * TCAT TLV Types. + * + */ + enum TlvType : uint8_t + { + // Command Class General + kCommand = OT_TCAT_TLV_COMMAND, ///< TCAT command TLV --> !!!! OBSOLETE!!!! + kResponseWithStatus = OT_TCAT_TLV_RESPONSE_WITH_STATUS, ///< TCAT response with status value TLV + kResponseWithPayload = OT_TCAT_TLV_RESPONSE_WITH_PAYLOAD, ///< TCAT response with payload TLV + kResponseEvent = OT_TCAT_TLV_RESPONSE_EVENT, ///< TCAT response event TLV (reserved) + kGetNetworkName = OT_TCAT_TLV_GET_NETWORK_NAME, ///< TCAT network name query TLV + kDisconnect = OT_TCAT_TLV_DISCONNECT, ///< TCAT disconnect request TLV + kPing = OT_TCAT_TLV_PING, ///< TCAT ping request TLV + kGetDeviceId = OT_TCAT_TLV_GET_DEVICE_ID, ///< TCAT device ID query TLV + kGetExtendedPanID = OT_TCAT_TLV_GET_EXTENDED_PAN_ID, ///< TCAT extended PAN ID query TLV + kPresentPskdHash = + OT_TCAT_TLV_PRESENT_PSKD_HASH, ///< TCAT commissioner rights elevation request TLV using PSKd hash + kPresentPskcHash = + OT_TCAT_TLV_PRESENT_PSKC_HASH, ///< TCAT commissioner rights elevation request TLV using PSKc hash + kPresentInstallCodeHash = OT_TCAT_TLV_PRESENT_INSTALL_CODE_HASH, ///< TCAT commissioner rights elevation request + ///< TLV using install code + kRequestRandomNumChallenge = + OT_TCAT_TLV_REQUEST_RANDOM_NUM_CHALLENGE, ///< TCAT random number challenge query TLV + kRequestPskdHash = OT_TCAT_TLV_REQUEST_PSKD_HASH, ///< TCAT PSKd hash request TLV + + // Command Class Commissioning + kSetActiveOperationalDataset = + OT_TCAT_TLV_SET_ACTIVE_OPERATIONAL_DATASET, ///< TCAT active operational dataset TLV + kSetActiveOperationalDataset1 = + OT_TCAT_TLV_SET_ACTIVE_OPERATIONAL_DATASET1, ///< TCAT active operational dataset alterative #1 TLV + kGetProvissioningTlvs = OT_TCAT_TLV_GET_PROVISIONING_TLVS, ///< TCAT provisioning TLVs query TLV + kGetCommissionerCertificate = + OT_TCAT_TLV_GET_COMMISSIONER_CERTIFICATE, ///< TCAT commissioner certificate query TLV + kGetDiagnosticTlvs = OT_TCAT_TLV_GET_DIAGNOSTIC_TLVS, ///< TCAT diagnostics TLVs query TLV + kStartThreadInterface = OT_TCAT_TLV_START_THREAD_INTERFACE, ///< TCAT start thread interface request TLV + kStopThreadInterface = OT_TCAT_TLV_STOP_THREAD_INTERFACE, ///< TCAT stop thread interface request TLV + + // Command Class Extraction + kGetActiveOperationalDataset = + OT_TCAT_TLV_GET_ACTIVE_OPERATIONAL_DATASET, ///< TCAT active oerational dataset query TLV + kGetActiveOperationalDataset1 = + OT_TCAT_TLV_GET_ACTIVE_OPERATIONAL_DATASET1, ///< TCAT active oerational dataset alterative #1 query TLV + + // Command Class Decommissioning + kDecommission = OT_TCAT_TLV_DECOMMISSION, ///< TCAT decommission request TLV + + // Command Class Application + kSelectApplicationLayerUdp = + OT_TCAT_TLV_SELECT_APPLICATION_LAYER_UDP, ///< TCAT select UDP protocol application layer request TLV + kSelectApplicationLayerTcp = + OT_TCAT_TLV_SELECT_APPLICATION_LAYER_TCP, ///< TCAT select TCP protocol application layer request TLV + kSendApplicationData = OT_TCAT_TLV_SEND_APPLICATION_DATA, ///< TCAT send application data TLV + kSendVendorSpecificData = + OT_TCAT_TLV_SEND_VENDOR_SPECIFIC_DATA, ///< TCAT send vendor specific command or data TLV + + // Command Class CCM + kSetLDevIdOperationalCert = + OT_TCAT_TLV_SET_LDEVID_OPERATIONAL_CERT, ///< TCAT LDevID operational certificate TLV + kSetLDevIdPrivateKey = + OT_TCAT_TLV_SET_LDEVID_PRIVATE_KEY, ///< TCAT LDevID operational certificate pricate key TLV + kSetDomainCaCert = OT_TCAT_TLV_SET_DOMAIN_CA_CERT, ///< TCAT domain CA certificate TLV + }; + + /** + * TCAT Command Types. --> OBSOLETE + * + */ + enum CommandType : uint8_t + { + kTerminate = OT_TCAT_COMMAND_TERMINATE, ///< Terminate connection + kThreadStart = OT_TCAT_COMMAND_THREAD_START, ///< Start Thread Interface + kThreadStop = OT_TCAT_COMMAND_THREAD_STOP, ///< Stop Thread Interface + }; + + /** + * TCAT Response Types. + * + */ + enum StatusCode : uint8_t + { + kTcatSuccess = OT_TCAT_STATUS_SUCCESS, ///< Command or request was successfully processed + kTcatUnsupported = OT_TCAT_STATUS_UNSUPPORTED, ///< Requested command or received TLV is not supported + kTcatParseError = OT_TCAT_STATUS_PARSE_ERROR, ///< Request / command could not be parsed correctly + kTcatValueError = OT_TCAT_STATUS_VALUE_ERROR, ///< The value of the transmitted TLV has an error + kTcatGeneralError = OT_TCAT_STATUS_GENERAL_ERROR, ///< An error not matching any other category occurred + kTcatBusy = OT_TCAT_STATUS_BUSY, ///< Command cannot be executed because the resource is busy + kTcatUndefined = OT_TCAT_STATUS_UNDEFINED, ///< The requested value, data or service is not defined (currently) + ///< or not present + kTcatHashError = OT_TCAT_STATUS_HASH_ERROR, ///< The hash value presented by the commissioner was incorrect + kTcatUnauthorized = + OT_TCAT_STATUS_UNAUTHORIZED, ///< Sender does not have sufficient authorization for the given command + }; + + /** + * Represents TCAT message type. + * + */ + typedef otTcatMessageType TcatMessageType; + + /** + * Initializes the Joiner object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit TcatAgent(Instance &aInstance); + + /** + * Enables the TCAT protocol. + * + * @retval kErrorNone Successfully started the TCAT agent. + * @retval kErrorInvalidArgs The aVendorInfo is invalid. + * + */ + Error Start(VendorInfo *aVendorInfo, + AppDataReceiveCallback aAppDataReceiveCallback, + JoinCallback aHandler, + void *aContext); + + /** + * Stops the TCAT protocol. + * + */ + void Stop(void); + + /** + * Informs the TCAT agent that a TCAT connection has been established. + * + * @retval kErrorNone Successfully connected the TCAT agent. + * @retval kErrorNotFound The requested attribute was not found. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * @retval kErrorInvalidState TLS not connected or TCAT agent not enabled. + * @retval kErrorParse The certificate extensions could not be parsed. + * + */ + Error Connected(MeshCoP::Dtls &aTlsContext); + + /** + * Informs the TCAT agent that the TCAT connection has been closed. + * + */ + void Disconnected(); + + /** + * Indicates whether or not the TCAT agent is enabled. + * + * @retval TRUE The TCAT agent is enabled. + * @retval FALSE The TCAT agent is not enabled. + * + */ + bool IsEnabled(void) const { return mEnabled; } + + /** + * Indicates whether or not the TCAT agent is connected. + * + * @retval TRUE The TCAT agent is connected. + * @retval FALSE The TCAT agent is not connected. + * + */ + bool IsConnected(void) const { return mConnected; } + + /** + * Indicates whether or not a command class is authorized. + * + * @retval TRUE The command class is authorized. + * @retval FALSE The command class is not authorized. + * + */ + bool IsCommandClassAuthorized(CommandClassFlags aCommissionerCommandClassFlags, + CommandClassFlags aDeviceCommandClassFlags) const; + + /** + * Indicates whether or not the TCAT session has verified the commissioner is in possession of PSKd. + * + * @retval TRUE The TCAT session has verified PSKd. + * @retval FALSE The TCAT session does not verified PSKd. + * + */ + bool IsPskdVerified(void) const { return mPskdVerified; } + + /** + * Indicates whether or not the TCAT session has verified the commissioner is in possession of PSKc. + * + * @retval TRUE The TCAT session has verified PSKc. + * @retval FALSE The TCAT session does not verified PSKc. + * + */ + bool IsPskcVerified(void) const { return mPskcVerified; } + + /** + * Processes an incoming TCAT TLV. + * + * @retval kErrorNone Successfully processed. + * @retval kErrorInvalidState The TCAT agent is not in connected state. + * @retval kErrorInvalidArgs The invalid argument value. + * @retval kErrorParse The incoming meassge could not be parsed. + * @retval kErrorAbort The incoming message was a request for terminating the TCAT link. + * + */ + Error HandleSingleTlv(ot::Message &aIncommingMessage, ot::Message &aOutgoingMessage); + +private: + Error HandleGeneral(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse); + Error HandleCommissioning(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse); + Error HandleExtraction(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse); + Error HandleDecommissioning(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse); + Error HandleApplication(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse); + Error HandleCCM(ot::Message &aIncommingMessage, + ot::Message &aOutgoingMessage, + uint8_t aTlvType, + uint32_t aOffset, + uint32_t aLength, + bool &aHasResponse); + + Error HandleCommand(CommandType aCommand, ot::Message &aOutgoingMessage); + Error HandleActiveDataset(ot::Message &aIncommingMessage, uint32_t aOffset, uint32_t aLength); + + static constexpr uint16_t kJoinerUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT; + + JoinerPskd mJoinerPskd; + VendorInfo *mVendorInfo; + Callback mJoinCallback; + Callback mAppDataReceiveCallback; + CertificateAuthorizationField mCommissionerAuthorizationField; + CertificateAuthorizationField mDeviceAuthorizationField; + TcatMessageType mCurrentApplicationLayer; + char mCurrentServiceName[OT_TCAT_MAX_SERVICE_NAME_LENGTH + 1]; + bool mEnabled : 1; + bool mConnected : 1; + bool mAlreadyCommissioned : 1; + bool mPskdVerified : 1; + bool mPskcVerified : 1; + bool mResponse : 1; +}; + +} // namespace MeshCoP +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + +#endif // TCAT_AGENT_HPP_ diff --git a/src/core/radio/ble_secure.cpp b/src/core/radio/ble_secure.cpp new file mode 100644 index 000000000000..1290a45b066f --- /dev/null +++ b/src/core/radio/ble_secure.cpp @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2023, 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. + */ + +#include "ble_secure.hpp" + +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + +#include +#include "common/instance.hpp" +#include "common/locator_getters.hpp" +#include "common/log.hpp" +#include "common/tlvs.hpp" +#include "meshcop/dtls.hpp" + +/** + * @file + * This file implements the secure Ble agent. + */ + +namespace ot { +namespace Ble { + +RegisterLogModule("BleSecure"); + +BleSecure::BleSecure(Instance &aInstance) + : InstanceLocator(aInstance) + , mTls(aInstance, false, false) + , mTcatAgent(aInstance) + , mTlvMode(0) + , mReceivedMessage(nullptr) + , mSentMessage(nullptr) + , mTransmitTask(aInstance, BleSecure::HandleTransmit, this) + , mBleConnectionOpen(false) + , mMtuSize(kInitialMtuSize) +{ +} + +Error BleSecure::Start(ConnectCallback aConnectHandler, ReceiveCallback aReceiveHandler, bool aTlvMode, void *aContext) +{ + Error error = kErrorNone; + + mConnectCallback.Set(aConnectHandler, aContext); + mReceiveCallback.Set(aReceiveHandler, aContext); + mTlvMode = aTlvMode; + mMtuSize = kInitialMtuSize; + + SuccessOrExit(error = otPlatBleEnable(&GetInstance())); + SuccessOrExit(error = otPlatBleGapAdvStart(&GetInstance(), 0)); + SuccessOrExit(error = mTls.Open(&BleSecure::HandleTlsReceive, &BleSecure::HandleTlsConnected, this)); + SuccessOrExit(error = mTls.Bind(HandleTransport, this)); + +exit: + return error; +} + +Error BleSecure::TcatStart(MeshCoP::TcatAgent::VendorInfo *aVendorInfo, MeshCoP::TcatAgent::JoinCallback aJoinHandler) +{ + return mTcatAgent.Start(aVendorInfo, mReceiveCallback.GetHandler(), aJoinHandler, mReceiveCallback.GetContext()); +} + +void BleSecure::Stop(void) +{ + otPlatBleGapAdvStop(&GetInstance()); + otPlatBleDisable(&GetInstance()); + mBleConnectionOpen = false; + mMtuSize = kInitialMtuSize; + + if (mTcatAgent.IsEnabled()) + mTcatAgent.Stop(); + mTls.Close(); + + mTransmitQueue.DequeueAndFreeAll(); + + mConnectCallback.Clear(); + mReceiveCallback.Clear(); + + FreeMessage(mReceivedMessage); + mReceivedMessage = nullptr; + FreeMessage(mSentMessage); + mSentMessage = nullptr; +} + +Error BleSecure::Connect() +{ + Ip6::SockAddr sockaddr; + + return mTls.Connect(sockaddr); +} + +void BleSecure::Disconnect(void) +{ + if (mTls.IsConnected()) + { + mTls.Disconnect(); + } + + if (mBleConnectionOpen) + { + otPlatBleGapDisconnect(&GetInstance()); + } +} + +void BleSecure::SetPsk(const MeshCoP::JoinerPskd &aPskd) +{ + static_assert(static_cast(MeshCoP::JoinerPskd::kMaxLength) <= + static_cast(MeshCoP::Dtls::kPskMaxLength), + "The maximum length of TLS PSK is smaller than joiner PSKd"); + + SuccessOrAssert(mTls.SetPsk(reinterpret_cast(aPskd.GetAsCString()), aPskd.GetLength())); +} + +Error BleSecure::SendMessage(ot::Message *aMessage) +{ + Error error = kErrorNone; + + VerifyOrExit(IsConnected(), error = kErrorInvalidState); + VerifyOrExit(mSentMessage != nullptr, error = kErrorNoBufs); + VerifyOrExit(aMessage != nullptr, error = kErrorInvalidArgs); + SuccessOrExit(error = mSentMessage->AppendBytesFromMessage(*aMessage, 0, aMessage->GetLength())); + + if (mSentMessage->GetLength() > kFlushThreshold) + { + error = Flush(); + } + + if (error == kErrorNone) + FreeMessage(aMessage); + +exit: + return error; +} + +Error BleSecure::Send(uint8_t *aBuf, uint16_t aLength) +{ + Error error = kErrorNone; + + VerifyOrExit(IsConnected(), error = kErrorInvalidState); + VerifyOrExit(mSentMessage != nullptr, error = kErrorNoBufs); + SuccessOrExit(error = mSentMessage->AppendBytes(aBuf, aLength)); + + if (mSentMessage->GetLength() > kFlushThreshold) + { + error = Flush(); + } + +exit: + return error; +} + +Error BleSecure::SendApplicationTlv(uint8_t *aBuf, uint16_t aLength) +{ + if (aLength > 254) + { + ot::ExtendedTlv tlv; + + tlv.SetType(ot::MeshCoP::TcatAgent::TlvType::kSendApplicationData); + tlv.SetLength(aLength); + Send(reinterpret_cast(&tlv), sizeof(tlv)); + } + else + { + ot::Tlv tlv; + + tlv.SetType(ot::MeshCoP::TcatAgent::TlvType::kSendApplicationData); + tlv.SetLength(aLength); + Send(reinterpret_cast(&tlv), sizeof(tlv)); + } + + return Send(aBuf, aLength); +} + +Error BleSecure::Flush() +{ + Error error = kErrorNone; + + VerifyOrExit(IsConnected(), error = kErrorInvalidState); + VerifyOrExit(mSentMessage != nullptr, error = kErrorNoBufs); + VerifyOrExit(mSentMessage->GetLength() != 0, error = kErrorNone); + + mTransmitQueue.Enqueue(*mSentMessage); + mTransmitTask.Post(); + + mSentMessage = Get().Allocate(Message::kTypeBle); + VerifyOrExit(mSentMessage != nullptr, error = kErrorNoBufs); + +exit: + return error; +} + +Error BleSecure::HandleBleReceive(uint8_t *aBuf, uint16_t aLength) +{ + ot::Message *message = nullptr; + Ip6::MessageInfo theMessageInfo; + + VerifyOrExit((message = Get().Allocate(Message::kTypeBle, 0)) != nullptr); + SuccessOrExit(message->AppendBytes(aBuf, aLength)); + + // Cannot call Receive(..) directly because Setup(..) and mState are private + mTls.HandleUdpReceive(*message, theMessageInfo); + + FreeMessage(message); + + return kErrorNone; + +exit: + FreeMessage(message); + return kErrorNoBufs; +} + +Error BleSecure::HandleBleConnected(uint16_t aConnectionId) +{ + OT_UNUSED_VARIABLE(aConnectionId); + + mBleConnectionOpen = true; + + otPlatBleGattMtuGet(&GetInstance(), &mMtuSize); + + mConnectCallback.InvokeIfSet(&GetInstance(), IsConnected(), mBleConnectionOpen); + + return kErrorNone; +} + +Error BleSecure::HandleBleDisconnected(uint16_t aConnectionId) +{ + OT_UNUSED_VARIABLE(aConnectionId); + + mBleConnectionOpen = false; + mMtuSize = kInitialMtuSize; + + if (IsConnected()) + { + Disconnect(); // Stop TLS connection + } + else + { + mConnectCallback.InvokeIfSet(&GetInstance(), false, mBleConnectionOpen); + } + + return kErrorNone; +} + +void BleSecure::HandleTlsConnected(void *aContext, bool aConnected) +{ + return static_cast(aContext)->HandleTlsConnected(aConnected); +} + +void BleSecure::HandleTlsConnected(bool aConnected) +{ + if (aConnected) + { + if (mReceivedMessage == nullptr) + { + mReceivedMessage = Get().Allocate(Message::kTypeBle); + } + + if (mSentMessage == nullptr) + { + mSentMessage = Get().Allocate(Message::kTypeBle); + } + + if (mTcatAgent.IsEnabled()) + mTcatAgent.Connected(mTls); + } + else + { + FreeMessage(mReceivedMessage); + mReceivedMessage = nullptr; + FreeMessage(mSentMessage); + mSentMessage = nullptr; + + if (mTcatAgent.IsEnabled()) + mTcatAgent.Disconnected(); + } + + mConnectCallback.InvokeIfSet(&GetInstance(), aConnected, mBleConnectionOpen); +} + +void BleSecure::HandleTlsReceive(void *aContext, uint8_t *aBuf, uint16_t aLength) +{ + return static_cast(aContext)->HandleTlsReceive(aBuf, aLength); +} + +void BleSecure::HandleTlsReceive(uint8_t *aBuf, uint16_t aLength) +{ + VerifyOrExit(mReceivedMessage != nullptr); + + if (mTlvMode) + { + ot::Tlv tlv; + uint32_t requiredBytes = sizeof(Tlv); + uint32_t offset; + + while (aLength > 0) + { + if (mReceivedMessage->GetLength() < requiredBytes) + { + uint32_t missingBytes = requiredBytes - mReceivedMessage->GetLength(); + + if (missingBytes > aLength) + { + SuccessOrExit(mReceivedMessage->AppendBytes(aBuf, aLength)); + break; + } + else + { + SuccessOrExit(mReceivedMessage->AppendBytes(aBuf, missingBytes)); + aLength -= missingBytes; + aBuf += missingBytes; + } + } + + mReceivedMessage->Read(0, tlv); + + if (tlv.IsExtended()) + { + ot::ExtendedTlv extTlv; + requiredBytes = sizeof(extTlv); + + if (mReceivedMessage->GetLength() < requiredBytes) + continue; + + mReceivedMessage->Read(0, extTlv); + requiredBytes = extTlv.GetSize(); + offset = sizeof(extTlv); + } + else + { + requiredBytes = tlv.GetSize(); + offset = sizeof(tlv); + } + + if (mReceivedMessage->GetLength() < requiredBytes) + continue; + + // TLV fully loaded + + if (mTcatAgent.IsEnabled()) + { + if (mSentMessage != nullptr) + { + Error error = mTcatAgent.HandleSingleTlv(*mReceivedMessage, *mSentMessage); + Flush(); + + if (error == kErrorAbort) + { + Disconnect(); // Keep BLE active (for testing) + // Stop(); // Stop BLE interface + ExitNow(); + } + } + } + else + { + mReceivedMessage->SetOffset(offset); + mReceiveCallback.InvokeIfSet(&GetInstance(), mReceivedMessage, OT_TCAT_MESSAGE_TYPE_NONE, ""); + } + + SuccessOrExit(mReceivedMessage->SetLength(0)); // also sets the offset to 0 + requiredBytes = sizeof(Tlv); + } + } + else + { + SuccessOrExit(mReceivedMessage->AppendBytes(aBuf, aLength)); + + mReceiveCallback.InvokeIfSet(&GetInstance(), mReceivedMessage, OT_TCAT_MESSAGE_TYPE_NONE, ""); + mReceivedMessage->SetLength(0); + } + +exit: + return; +} + +void BleSecure::HandleTransmit(Tasklet &aTasklet) +{ + static_cast(static_cast(aTasklet).GetContext())->HandleTransmit(); +} + +void BleSecure::HandleTransmit(void) +{ + Error error = kErrorNone; + ot::Message *message = mTransmitQueue.GetHead(); + + VerifyOrExit(message != nullptr); + mTransmitQueue.Dequeue(*message); + + if (mTransmitQueue.GetHead() != nullptr) + { + mTransmitTask.Post(); + } + + SuccessOrExit(error = mTls.Send(*message, message->GetLength())); + +exit: + if (error != kErrorNone) + { + LogNote("Transmit: %s", ErrorToString(error)); + message->Free(); + } + else + { + LogDebg("Transmit: %s", ErrorToString(error)); + } +} + +Error BleSecure::HandleTransport(void *aContext, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + OT_UNUSED_VARIABLE(aMessageInfo); + return static_cast(aContext)->HandleTransport(aMessage); +} + +Error BleSecure::HandleTransport(ot::Message &aMessage) +{ + otBleRadioPacket packet; + uint16_t len = aMessage.GetLength(); + uint16_t offset = 0; + otError error = OT_ERROR_NONE; + + while (len > 0) + { + if (len <= mMtuSize - kGattOverhead) + packet.mLength = len; + else + packet.mLength = mMtuSize - kGattOverhead; + + if (packet.mLength > kPacketBufferSize) + packet.mLength = kPacketBufferSize; + + aMessage.Read(offset, mPacketBuffer, packet.mLength); + packet.mValue = mPacketBuffer; + packet.mPower = 0; + + error = otPlatBleGattServerIndicate(&GetInstance(), kTxBleHandle, &packet); + if (error != OT_ERROR_NONE) + break; + + len -= packet.mLength; + offset += packet.mLength; + } + + // if(error != OT_ERROR_NONE) --> close connection? Not free message? + + aMessage.Free(); + return kErrorNone; +} + +} // namespace Ble +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE diff --git a/src/core/radio/ble_secure.hpp b/src/core/radio/ble_secure.hpp new file mode 100644 index 000000000000..b3e22daaed35 --- /dev/null +++ b/src/core/radio/ble_secure.hpp @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2023, 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. + */ + +#ifndef BLE_SECURE_HPP_ +#define BLE_SECURE_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + +#include + +#include "meshcop/dtls.hpp" +#include "meshcop/meshcop.hpp" +#include "meshcop/tcat_agent.hpp" + +//#include + +/** + * @file + * Includes definitions for the secure BLE agent. + */ + +namespace ot { + +namespace Ble { + +class BleSecure : public InstanceLocator, private NonCopyable +{ +public: + /** + * Pointer to call when the secure BLE connection state changes. + * + * Please see otHandleBleSecureConnect for details. + * + */ + typedef otHandleBleSecureConnect ConnectCallback; + + /** + * Pointer to call when data was received over the TLS connection. + * If line mode is activated the function is called only after EOL has been received. + * + * Please see otHandleBleSecureReceive for details. + * + */ + typedef otHandleBleSecureReceive ReceiveCallback; + + /** + * Constructor initializes the object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit BleSecure(Instance &aInstance); + + /** + * Method starts the secure BLE agent. + * + * @retval kErrorNone Successfully started the BLE agent. + * @retval kErrorAlready Already started. + * + */ + Error Start(ConnectCallback aConnectHandler, ReceiveCallback aReceiveHandler, bool aTlvMode, void *aContext); + + /** + * Enables the TCAT protocol over BLE Secure. + * + * @retval kErrorNone Successfully started the BLE Secure Joiner role. + * @retval kErrorInvalidArgs The aVendorInfo is invalid. + * @retval kErrorInvaidState The BLE function has not been started or line mode is not selected. + * + */ + Error TcatStart(MeshCoP::TcatAgent::VendorInfo *aVendorInfo, MeshCoP::TcatAgent::JoinCallback aHandler); + + /** + * Stops the secure BLE agent. + * + */ + void Stop(void); + + /** + * Initializes TLS session with a peer using an already open BLE connection. + * + * @retval kErrorNone Successfully started TLS connection. + * + */ + Error Connect(); + + /** + * Stops the BLE and TLS connection. + * + */ + void Disconnect(void); + + /** + * Indicates whether or not the TLS session is active. + * + * @retval TRUE If TLS session is active. + * @retval FALSE If TLS session is not active. + * + */ + bool IsConnectionActive(void) const { return mTls.IsConnectionActive(); } + + /** + * Indicates whether or not the TLS session is connected. + * + * @retval TRUE The TLS session is connected. + * @retval FALSE The TLS session is not connected. + * + */ + bool IsConnected(void) const { return mTls.IsConnected(); } + + /** + * Indicates whether or not the TCAT agent is enabled. + * + * @retval TRUE The TCAT agent is enabled. + * @retval FALSE The TCAT agent is not enabled. + * + */ + bool IsTcatEnabled(void) const { return mTcatAgent.IsEnabled(); } + + /** + * Indicates whether or not the TCAT session has verified the commissioner is in possession of PSKd. + * + * @retval TRUE The TCAT session has verified PSKd. + * @retval FALSE The TCAT session does not verified PSKd. + * + */ + bool IsPskdVerified(void) const { return mTcatAgent.IsPskdVerified(); } + + /** + * Indicates whether or not the TCAT session has verified the commissioner is in possession of PSKc. + * + * @retval TRUE The TCAT session has verified PSKc. + * @retval FALSE The TCAT session does not verified PSKc. + * + */ + bool IsPskcVerified(void) const { return mTcatAgent.IsPskcVerified(); } + + /** + * Sets the PSK. + * + * @param[in] aPsk A pointer to the PSK. + * @param[in] aPskLength The PSK length. + * + * @retval kErrorNone Successfully set the PSK. + * @retval kErrorInvalidArgs The PSK is invalid. + * + */ + Error SetPsk(const uint8_t *aPsk, uint8_t aPskLength) { return mTls.SetPsk(aPsk, aPskLength); } + + /** + * Sets the PSK. + * + * @param[in] aPskd A Joiner PSKd. + * + */ + void SetPsk(const MeshCoP::JoinerPskd &aPskd); + +#ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + /** + * Sets the Pre-Shared Key (PSK) for TLS sessions identified by a PSK. + * + * TLS mode "TLS with AES 128 CCM 8" for secure BLE. + * + * @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. + * + */ + void SetPreSharedKey(const uint8_t *aPsk, uint16_t aPskLength, const uint8_t *aPskIdentity, uint16_t aPskIdLength) + { + mTls.SetPreSharedKey(aPsk, aPskLength, aPskIdentity, aPskIdLength); + } +#endif // MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + /** + * Sets a X509 certificate with corresponding private key for TLS session. + * + * TLS mode "ECDHE ECDSA with AES 128 CCM 8" for secure BLE. + * + * @param[in] aX509Cert A pointer to the PEM formatted X509 PEM 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. + * + */ + void SetCertificate(const uint8_t *aX509Cert, + uint32_t aX509Length, + const uint8_t *aPrivateKey, + uint32_t aPrivateKeyLength) + { + mTls.SetCertificate(aX509Cert, aX509Length, aPrivateKey, aPrivateKeyLength); + } + + /** + * Sets the trusted top level CAs. It is needed for validate the certificate of the peer. + * + * TLS mode "ECDHE ECDSA with AES 128 CCM 8" for secure BLE. + * + * @param[in] aX509CaCertificateChain A pointer to the PEM formatted X509 CA chain. + * @param[in] aX509CaCertChainLength The length of chain. + * + */ + void SetCaCertificateChain(const uint8_t *aX509CaCertificateChain, uint32_t aX509CaCertChainLength) + { + mTls.SetCaCertificateChain(aX509CaCertificateChain, aX509CaCertChainLength); + } +#endif // MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +#if defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + /** + * Returns the peer x509 certificate base64 encoded. + * + * TLS mode "ECDHE ECDSA with AES 128 CCM 8" for secure BLE. + * + * @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 kErrorNone Successfully get the peer certificate. + * @retval kErrorNoBufs Can't allocate memory for certificate. + * + */ + Error GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize) + { + return mTls.GetPeerCertificateBase64(aPeerCert, aCertLength, aCertBufferSize); + } +#endif // defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + +#if defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + /** + * Returns an attribute value identified by its OID from the subject + * of the peer x509 certificate. The peer OID is provided in binary format. + * The attribute length is set if the attribute was successfully read or zero + * if unsuccessful. The ANS1 type as is set as defineded in the ITU-T X.690 standard + * if the attribute was successfully read. + * + * @param[in] aOid A pointer to the OID to be found. + * @param[in] aOidLength The length of the OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * @param[out] aAns1Type A pointer to the ANS1 type of the attribute written to the buffer. + * + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNone Successfully read attribute. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * + */ + Error GetPeerSubjectAttributeByOid(const char *aOid, + size_t aOidLength, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize, + int *aAns1Type) + { + return mTls.GetPeerSubjectAttributeByOid(aOid, aOidLength, aAttributeBuffer, aAttributeLength, + aAttributeBufferSize, aAns1Type); + } + + /** + * Returns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the peer x509 certificate, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorNotFound The requested attribute was not found. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNotImplemented The value of aThreadOidDescriptor is >127. + * @retval kErrorParse The certificate extensions could not be parsed. + * + */ + Error GetThreadAttributeFromPeerCertificate(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) + { + return mTls.GetThreadAttributeFromPeerCertificate(aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, + aAttributeBufferSize); + } +#endif // defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + + /** + * Returns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the own x509 certificate, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorNotFound The requested attribute was not found. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNotImplemented The value of aThreadOidDescriptor is >127. + * @retval kErrorParse The certificate extensions could not be parsed. + * + */ + Error GetThreadAttributeFromOwnCertificate(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) + { + return mTls.GetThreadAttributeFromOwnCertificate(aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, + aAttributeBufferSize); + } + + /** + * Returns an attribute value for the OID 1.3.6.1.4.1.44970.x from the v3 extensions of + * the CA x509 certificate chain, where the last digit x is set to aThreadOidDescriptor. + * The attribute length is set if the attribute was successfully read or zero if unsuccessful. + * Requires a connection to be active. + * + * @param[in] aThreadOidDescriptor The last digit of the Thread attribute OID. + * @param[out] aAttributeBuffer A pointer to the attribute buffer. + * @param[out] aAttributeLength A pointer to the length of the attribute written to the buffer. + * @param[in] aAttributeBufferSize The buffer size of aAttributeBuffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorNotFound The requested attribute was not found. + * @retval kErrorNoBufs Insufficient memory for storing the attribute value. + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNotImplemented The value of aThreadOidDescriptor is >127. + * @retval kErrorParse The certificate extensions could not be parsed. + * + */ + Error GetThreadAttributeFromCaCertificateChain(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength, + size_t aAttributeBufferSize) + { + return mTls.GetThreadAttributeFromCaCertificateChain(aThreadOidDescriptor, aAttributeBuffer, aAttributeLength, + aAttributeBufferSize); + } + + /** + * Sets the authentication mode for the BLE secure connection. It disables or enables the verification + * of peer certificate. + * + * @param[in] aVerifyPeerCertificate true, if the peer certificate should be verified + * + */ + void SetSslAuthMode(bool aVerifyPeerCertificate) { mTls.SetSslAuthMode(aVerifyPeerCertificate); } + + /** + * Sends a secure BLE message. + * + * @param[in] aMessage A pointer to the message to send. + * + * If the return value is kErrorNone, OpenThread takes ownership of @p aMessage, and the caller should no longer + * reference @p aMessage. If the return value is not kErrorNone, the caller retains ownership of @p aMessage, + * including freeing @p aMessage if the message buffer is no longer needed. + * + * @retval kErrorNone Successfully sent message. + * @retval kErrorNoBufs Failed to allocate buffer memory. + * @retval kErrorInvalidState TLS connection was not initialized. + * + */ + Error SendMessage(Message *aMessage); + + /** + * Sends a secure BLE data packet. + * + * @param[in] aBuf A pointer to the data to send. + * @param[in] aLength A number indicating the length of the data buffer. + * + * @retval kErrorNone Successfully sent data. + * @retval kErrorNoBufs Failed to allocate buffer memory. + * @retval kErrorInvalidState TLS connection was not initialized. + * + */ + Error Send(uint8_t *aBuf, uint16_t aLength); + + /** + * Sends a secure BLE data packet containing a TCAT application TLV. + * + * @param[in] aBuf A pointer to the data to send. + * @param[in] aLength A number indicating the length of the data buffer. + * + * @retval kErrorNone Successfully sent data. + * @retval kErrorNoBufs Failed to allocate buffer memory. + * @retval kErrorInvalidState TLS connection was not initialized. + * + */ + Error SendApplicationTlv(uint8_t *aBuf, uint16_t aLength); + + /** + * Sends all remaining bytes in the send buffer. + * + * @retval kErrorNone Successfully enqueued data into the output interface. + * @retval kErrorNoBufs Failed to allocate buffer memory. + * @retval kErrorInvalidState TLS connection was not initialized. + * + */ + Error Flush(); + + /** + * Used to pass data received over a BLE link to the secure BLE server. + * + * @param[in] aBuf A pointer to the data received. + * @param[in] aLength A number indicating the length of the data buffer. + * + */ + Error HandleBleReceive(uint8_t *aBuf, uint16_t aLength); + + /** + * Used to notify the secure BLE server that a BLE Device has been connected. + * + * @param[in] aConnectionId The identifier of the open connection. + * + */ + Error HandleBleConnected(uint16_t aConnectionId); + + /** + * Used to notify the secure BLE server that the BLE Device has been disconnected. + * + * @param[in] aConnectionId The identifier of the open connection. + * + */ + Error HandleBleDisconnected(uint16_t aConnectionId); + +private: + static constexpr uint8_t kInitialMtuSize = 23; // ATT_MTU + static constexpr uint8_t kGattOverhead = 3; // BLE GATT payload fits MTU size - 3 bytes + static constexpr uint8_t kPacketBufferSize = 64; // must be >= kInitialMtuSize - kGattOverhead + static constexpr uint16_t kTxBleHandle = 0; // Characteristics Handle for TX (not used) + static constexpr uint16_t kFlushThreshold = + 256; // mSentMessage length exceeding this threshold triggers flushing the buffer + + static void HandleTlsConnected(void *aContext, bool aConnected); + void HandleTlsConnected(bool aConnected); + + static void HandleTlsReceive(void *aContext, uint8_t *aBuf, uint16_t aLength); + void HandleTlsReceive(uint8_t *aBuf, uint16_t aLength); + + static void HandleTransmit(Tasklet &aTasklet); + void HandleTransmit(void); + + static Error HandleTransport(void *aContext, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + Error HandleTransport(ot::Message &aMessage); + + MeshCoP::Dtls mTls; + MeshCoP::TcatAgent mTcatAgent; + Callback mConnectCallback; + Callback mReceiveCallback; + bool mTlvMode; + ot::Message *mReceivedMessage; + ot::Message *mSentMessage; + ot::MessageQueue mTransmitQueue; + TaskletContext mTransmitTask; + uint8_t mPacketBuffer[kPacketBufferSize]; + bool mBleConnectionOpen; + uint16_t mMtuSize; +}; + +} // namespace Ble +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + +#endif // BLE_SECURE_HPP_ diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 02a30dc08219..e555f175ab8d 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -103,6 +103,7 @@ endif() add_library(openthread-posix alarm.cpp + ble.cpp backbone.cpp backtrace.cpp config_file.cpp diff --git a/src/posix/platform/ble.cpp b/src/posix/platform/ble.cpp new file mode 100644 index 000000000000..31d5f39751a9 --- /dev/null +++ b/src/posix/platform/ble.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023, 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. + */ + +#include + +otError otPlatBleEnable(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleDisable(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapAdvStart(otInstance *aInstance, uint16_t aInterval) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aInterval); + OT_UNUSED_VARIABLE(aType); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapAdvStop(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapDisconnect(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGattMtuGet(otInstance *aInstance, uint16_t *aMtu) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aMtu); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGattServerIndicate(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aHandle); + OT_UNUSED_VARIABLE(aPacket); + return OT_ERROR_NOT_IMPLEMENTED; +} diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 5487643140e5..d57c1ce22db0 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -35,6 +35,9 @@ #include #include +#ifdef OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +#include +#endif enum { @@ -623,4 +626,53 @@ void otPlatDnsCancelUpstreamQuery(otInstance *aInstance, otPlatDnsUpstreamQuery } #endif +#ifdef OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +otError otPlatBleEnable(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleDisable(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapAdvStart(otInstance *aInstance, uint16_t aInterval) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aInterval); + OT_UNUSED_VARIABLE(aType); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapAdvStop(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGapDisconnect(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGattMtuGet(otInstance *aInstance, uint16_t *aMtu) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aMtu); + return OT_ERROR_NOT_IMPLEMENTED; +} + +otError otPlatBleGattServerIndicate(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aHandle); + OT_UNUSED_VARIABLE(aPacket); + return OT_ERROR_NOT_IMPLEMENTED; +} +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + } // extern "C" diff --git a/third_party/mbedtls/mbedtls-config.h b/third_party/mbedtls/mbedtls-config.h index ffb50df1424e..bbf129801504 100644 --- a/third_party/mbedtls/mbedtls-config.h +++ b/third_party/mbedtls/mbedtls-config.h @@ -92,6 +92,10 @@ #define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED #endif +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +#define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE +#endif + #ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED #define MBEDTLS_BASE64_C #define MBEDTLS_ECDH_C