From 6f639f387be1844b3fba36ca3307b3bd6bfdbdc0 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 Co-authored-by: Piotr Jasinski --- .github/workflows/build.yml | 12 + etc/cmake/options.cmake | 1 + etc/gn/openthread.gni | 3 + examples/platforms/simulation/CMakeLists.txt | 1 + examples/platforms/simulation/ble.c | 75 +++ include/openthread/BUILD.gn | 3 + include/openthread/ble_secure.h | 460 ++++++++++++++++ include/openthread/instance.h | 2 +- include/openthread/platform/ble.h | 278 ++++++++++ include/openthread/tcat.h | 173 ++++++ 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 | 177 ++++++ src/cli/cli_tcat.hpp | 89 +++ src/core/BUILD.gn | 9 + src/core/CMakeLists.txt | 3 + src/core/api/ble_secure_api.cpp | 193 +++++++ 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 | 239 ++++++-- src/core/meshcop/dtls.hpp | 140 ++++- src/core/meshcop/tcat_agent.cpp | 524 ++++++++++++++++++ src/core/meshcop/tcat_agent.hpp | 423 +++++++++++++++ src/core/radio/ble_secure.cpp | 539 +++++++++++++++++++ src/core/radio/ble_secure.hpp | 517 ++++++++++++++++++ src/posix/platform/CMakeLists.txt | 1 + src/posix/platform/ble.cpp | 75 +++ tests/unit/test_platform.cpp | 51 ++ third_party/mbedtls/mbedtls-config.h | 4 + 36 files changed, 4031 insertions(+), 43 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/build.yml b/.github/workflows/build.yml index fb03695b2d14..8760f98dc6f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -202,6 +202,18 @@ jobs: run: | ./script/test build + 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 + arm-gcc: name: arm-gcc-${{ matrix.gcc_ver }} runs-on: ubuntu-20.04 diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index a03af20130b3..a9f80fa7957c 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..277ee830df04 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 c99597d4a072..709ffd8e77d2 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 dns.c diff --git a/examples/platforms/simulation/ble.c b/examples/platforms/simulation/ble.c new file mode 100644 index 000000000000..2fe3c64535d5 --- /dev/null +++ b/examples/platforms/simulation/ble.c @@ -0,0 +1,75 @@ +/* + * 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); + 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..9499916eaac0 --- /dev/null +++ b/include/openthread/ble_secure.h @@ -0,0 +1,460 @@ +/* + * 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. + * + */ +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, const 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[in,out] aCertLength On input, the size the max size of @p aPeerCert. + * On output, the length of the base64 encoded peer certificate. + * + * @retval OT_ERROR_NONE Successfully get the peer certificate. + * @retval OT_ERROR_INVALID_ARGS @p aInstance or @p aCertLength is invalid. + * @retval OT_ERROR_INVALID_STATE Not connected yet. + * @retval OT_ERROR_NO_BUFS Can't allocate memory for certificate. + * + */ +otError otBleSecureGetPeerCertificateBase64(otInstance *aInstance, unsigned char *aPeerCert, size_t *aCertLength); + +/** + * 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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * @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_INVALID_ARGS Invalid attribute length. + * @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, + 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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @retval OT_ERROR_NONE Successfully read attribute. + * @retval OT_ERROR_INVALID_ARGS Invalid attribute length. + * @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); + +/** + * 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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @retval OT_ERROR_NONE Successfully read attribute. + * @retval OT_ERROR_INVALID_ARGS Invalid attribute length. + * @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); + +/** + * 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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @retval OT_ERROR_NONE Successfully read attribute. + * @retval OT_ERROR_INVALID_ARGS Invalid attribute length. + * @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); + +/** + * 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 active (connected or conneting). + * + * @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 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 TCAT agent is enabled. + * + * @retval TRUE The TCAT agent is enabled. + * @retval FALSE The TCAT agent is not enabled. + * + */ +bool otBleSecureIsTcatEnabled(otInstance *aInstance); + +/** + * Indicates whether or not a TCAT command class is authorized. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCommandClass A command class to check. + * + * @retval TRUE The command class is authorized. + * @retval FALSE The command class is not authorized. + * + */ +bool otBleSecureIsCommandClassAuthorized(otInstance *aInstance, otTcatCommandClass aCommandClass); + +/** + * 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 424cf09a7851..6f2181238218 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 (359) +#define OPENTHREAD_API_VERSION (360) /** * @addtogroup api-instance diff --git a/include/openthread/platform/ble.h b/include/openthread/platform/ble.h new file mode 100644 index 000000000000..2f729cb0e3a5 --- /dev/null +++ b/include/openthread/platform/ble.h @@ -0,0 +1,278 @@ +/* + * 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 + +/** + * Maximum allowed ATT MTU size (must be >= 23). + * + */ + +#define OT_BLE_ATT_MTU_MAX 67 + +/** + * 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); + +/** + * The BLE driver calls this method to notify OpenThread that ATT_MTU has been updated. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aMtu The updated ATT_MTU value. + * + */ +extern void otPlatBleGattOnMtuUpdate(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); + +/** + * @} + * + */ + +#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..bdb66d7ecec0 --- /dev/null +++ b/include/openthread/tcat.h @@ -0,0 +1,173 @@ +/* + * 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 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; + +/** + * Represents TCAT a command class. + * + */ +typedef enum otTcatCommandClass +{ + OT_TCAT_COMMAND_CLASS_GENERAL = 1, ///< TCAT commands related to general operations + OT_TCAT_COMMAND_CLASS_COMMISSIONING = 2, ///< TCAT commands related to commissioning + OT_TCAT_COMMAND_CLASS_EXTRACTION = 3, ///< TCAT commands related to key extraction + OT_TCAT_COMMAND_CLASS_DECOMMISSIONING = 4, ///< TCAT commands related to de-commissioning + OT_TCAT_COMMAND_CLASS_APPLICATION = 5, ///< TCAT commands related to application layer +} otTcatCommandClass; + +/** + * This structure represents a TCAT vendor information. + * + * The content of this structure MUST persist and remain unchanged while a TCAT session is running. + * + */ +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] aOffset The offset where the application data begins. + * @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, + const otMessage *aMessage, + uint32_t aOffset, + 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/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 2f44e3dc82f4..7e947a141087 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -117,6 +117,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 deaabc96fd57..309012ec09aa 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_CLI_BLE_SECURE_ENABLE + , mTcat(aInstance, *this) +#endif #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE , mLocateInProgress(false) #endif @@ -7626,6 +7629,10 @@ template <> otError Interpreter::Process(Arg aArgs[]) return error; } +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_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 @@ -8769,6 +8776,9 @@ otError Interpreter::ProcessCommand(Arg aArgs[]) CmdEntry("srp"), #endif CmdEntry("state"), +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_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 a581088a1bb1..feae7c48dd2f 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 @@ -624,6 +625,9 @@ class Interpreter : public OutputImplementer, public Output #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE History mHistory; #endif +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_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..ac9b5e1cc3cb 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_CLI_BLE_SECURE_ENABLE + * + * Indicates whether TCAT should be enabled in the CLI tool. + * + */ +#ifndef OPENTHREAD_CONFIG_CLI_BLE_SECURE_ENABLE +#define OPENTHREAD_CONFIG_CLI_BLE_SECURE_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..33869a3aba36 --- /dev/null +++ b/src/cli/cli_tcat.cpp @@ -0,0 +1,177 @@ +/* + * 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_CLI_BLE_SECURE_ENABLE + +#define OT_CLI_BBTC_X509_CERT \ + "-----BEGIN CERTIFICATE-----\r\n" \ + "MIIBezCCASKgAwIBAgIEAQIDBDAKBggqhkjOPQQDAjBaMQswCQYDVQQGEwJERTER\r\n" \ + "MA8GA1UEBxMIR2FyY2hpbmcxDDAKBgNVBAsTA1NUQTERMA8GA1UEChMITXlWZW5k\r\n" \ + "b3IxFzAVBgNVBAMTDm9wdG90cm9uaWMuY29tMB4XDTIzMDgwODEwNDI0OVoXDTI0\r\n" \ + "MDgwODEwNDI0OVowGjEYMBYGA1UEAxMPVW5saW1pdGVkQWNjZXNzMFkwEwYHKoZI\r\n" \ + "zj0CAQYIKoZIzj0DAQcDQgAED3i3op7pdhAk8QvpytITPSrnhsFVg6SZhbFbAmw9\r\n" \ + "fCTGOomXDYARz2h3lSHxVdZUKjsn2bDX3s0PJ4urbpZhLqMWMBQwEgYJKwYBBAGC\r\n" \ + "3yoDBAUAAQEBATAKBggqhkjOPQQDAgNHADBEAiAyCjOy0NDw00w7tNq0HK0LX2rk\r\n" \ + "bRcWg8L4mHdYS5+lRwIgRM4b1eItMNpFTSLq174ZIjtq96QSEHA/8yS1UPyo9MM=\r\n" \ + "-----END CERTIFICATE-----\r\n" + +#define OT_CLI_BBTC_PRIV_KEY \ + "-----BEGIN EC PRIVATE KEY-----\r\n" \ + "MHcCAQEEIHqCO3YjGKu1469GTKwS+w+1MHCMwKPtN+yreiXbn0b5oAoGCCqGSM49\r\n" \ + "AwEHoUQDQgAED3i3op7pdhAk8QvpytITPSrnhsFVg6SZhbFbAmw9fCTGOomXDYAR\r\n" \ + "z2h3lSHxVdZUKjsn2bDX3s0PJ4urbpZhLg==\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, + const otMessage *aMessage, + uint32_t aOffset, + otTcatMessageType aTcatMessageType, + const char *aServiceName, + void *aContext) +{ + OT_UNUSED_VARIABLE(aContext); + OT_UNUSED_VARIABLE(aTcatMessageType); + OT_UNUSED_VARIABLE(aServiceName); + static constexpr int kTextMaxLen = 100; + static constexpr uint8_t kBufPrefixLen = 5; + uint16_t nLen; + uint8_t buf[kTextMaxLen]; + + nLen = otMessageRead(aMessage, aOffset, buf + kBufPrefixLen, sizeof(buf) - kBufPrefixLen - 1); + + memcpy(buf, "RECV:", kBufPrefixLen); + + buf[nLen + kBufPrefixLen] = 0; + + IgnoreReturnValue(otBleSecureSendApplicationTlv(aInstance, buf, strlen((char *)buf))); + IgnoreReturnValue(otBleSecureFlush(aInstance)); +} + +template <> otError Tcat::Process(Arg aArgs[]) +{ + OT_UNUSED_VARIABLE(aArgs); + + otError error = OT_ERROR_NONE; + + sVendorInfo.mPskdString = kPskdVendor; + sVendorInfo.mProvisioningUrl = kUrl; + + otBleSecureSetCertificate(GetInstancePtr(), reinterpret_cast(OT_CLI_BBTC_X509_CERT), + sizeof(OT_CLI_BBTC_X509_CERT), reinterpret_cast(OT_CLI_BBTC_PRIV_KEY), + sizeof(OT_CLI_BBTC_PRIV_KEY)); + + otBleSecureSetCaCertificateChain(GetInstancePtr(), + reinterpret_cast(OT_CLI_BBTC_TRUSTED_ROOT_CERTIFICATE), + sizeof(OT_CLI_BBTC_TRUSTED_ROOT_CERTIFICATE)); + + otBleSecureSetSslAuthMode(GetInstancePtr(), true); + + SuccessOrExit(error = otBleSecureStart(GetInstancePtr(), nullptr, HandleBleSecureReceive, true, nullptr)); + SuccessOrExit(error = otBleSecureTcatStart(GetInstancePtr(), &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_CLI_BLE_SECURE_ENABLE diff --git a/src/cli/cli_tcat.hpp b/src/cli/cli_tcat.hpp new file mode 100644 index 000000000000..3f1d0be6e6ed --- /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_CLI_BLE_SECURE_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_CLI_BLE_SECURE_ENABLE + +#endif // CLI_TCAT_HPP_ diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index d44f410fdf6c..ddaa82ccc036 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 c8e4079ab040..1bcac7677cff 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..42df546bbf72 --- /dev/null +++ b/src/core/api/ble_secure_api.cpp @@ -0,0 +1,193 @@ +/* + * 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 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" + +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, const otTcatVendorInfo *aVendorInfo, otHandleTcatJoin aHandler) +{ + return AsCoreType(aInstance).Get().TcatStart(AsCoreType(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) +{ + AssertPointerIsNotNull(aPsk); + AssertPointerIsNotNull(aPskIdentity); + OT_ASSERT(aPskLength != 0 && 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) +{ + return AsCoreType(aInstance).Get().GetPeerCertificateBase64(aPeerCert, aCertLength); +} +#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, + int *aAns1Type) +{ + return AsCoreType(aInstance).Get().GetPeerSubjectAttributeByOid(aOid, aOidLength, aAttributeBuffer, + aAttributeLength, aAns1Type); +} + +otError otBleSecureGetThreadAttributeFromPeerCertificate(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength) +{ + return AsCoreType(aInstance).Get().GetThreadAttributeFromPeerCertificate( + aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); +} +#endif // defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + +otError otBleSecureGetThreadAttributeFromOwnCertificate(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength) +{ + return AsCoreType(aInstance).Get().GetThreadAttributeFromOwnCertificate( + aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); +} + +otError otBleSecureGetThreadAttributeFromCaCertificateChain(otInstance *aInstance, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength) +{ + return AsCoreType(aInstance).Get().GetThreadAttributeFromCaCertificateChain( + aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); +} + +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 otBleSecureIsConnectionActive(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().IsConnectionActive(); +} + +bool otBleSecureIsConnected(otInstance *aInstance) { return AsCoreType(aInstance).Get().IsConnected(); } + +bool otBleSecureIsTcatEnabled(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().IsTcatEnabled(); +} + +bool otBleSecureIsCommandClassAuthorized(otInstance *aInstance, otTcatCommandClass aCommandClass) +{ + return AsCoreType(aInstance).Get().IsCommandClassAuthorized( + static_cast(aCommandClass)); +} + +otError otBleSecureSendMessage(otInstance *aInstance, otMessage *aMessage) +{ + return AsCoreType(aInstance).Get().SendMessage(AsCoreType(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(); } + +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index 6dcc3e8927c3..ec9ded0fb9e4 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 d213c692e014..00b3193993cb 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" @@ -591,6 +592,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 @@ -988,6 +993,10 @@ template <> inline Nat64::Translator &Instance::Get(void) { return mNat64Transla template <> inline Srp::Server &Instance::Get(void) { return mSrpServer; } #endif +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +template <> inline Ble::BleSecure &Instance::Get(void) { return mApplicationBleSecure; } +#endif + #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE 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..45c893ece966 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,13 @@ Dtls::Dtls(Instance &aInstance, bool aLayerTwoSecurity) , mTimerIntermediate(0) , mTimerSet(false) , mLayerTwoSecurity(aLayerTwoSecurity) + , mDatagramTransport(aDatagramTransport) , mReceiveMessage(nullptr) , mSocket(aInstance) , 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 +122,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); @@ -178,11 +182,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)); @@ -248,7 +258,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 +266,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 +328,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 +341,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 +367,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 +388,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,7 +447,7 @@ int Dtls::SetApplicationCoapSecureKeys(void) return rval; } -#endif // OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#endif // OPENTHREAD_CONFIG_TLS_API_ENABLE void Dtls::Close(void) { @@ -472,7 +493,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 +512,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 +579,155 @@ 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, + int *aAns1Type) +{ + Error error = kErrorNone; + const mbedtls_asn1_named_data *data; + size_t length; + size_t attributeBufferSize; + mbedtls_x509_crt *peerCert = const_cast(mbedtls_ssl_get_peer_cert(&mSsl)); + + VerifyOrExit(aAttributeLength != nullptr, error = kErrorInvalidArgs); + attributeBufferSize = *aAttributeLength; + *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 <= attributeBufferSize, 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) +{ + const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&mSsl); + + return GetThreadAttributeFromCertificate(cert, aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); +} + +#endif // defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + +Error Dtls::GetThreadAttributeFromOwnCertificate(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength) +{ + const mbedtls_x509_crt *cert = &mOwnCert; + + return GetThreadAttributeFromCertificate(cert, aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); +} + +Error Dtls::GetThreadAttributeFromCaCertificateChain(int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength) +{ + const mbedtls_x509_crt *cert = &mCaChain; + + return GetThreadAttributeFromCertificate(cert, aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); +} + +Error Dtls::GetThreadAttributeFromCertificate(const mbedtls_x509_crt *aCert, + int aThreadOidDescriptor, + uint8_t *aAttributeBuffer, + size_t *aAttributeLength) +{ + 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, *endExtData; + size_t len; + size_t attributeBufferSize; + mbedtls_x509_buf extnOid; + int ret, isCritical; + + VerifyOrExit(aAttributeLength != nullptr, error = kErrorInvalidArgs); + attributeBufferSize = *aAttributeLength; + *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) + { + isCritical = 0; + VerifyOrExit(mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) == 0, + error = kErrorParse); + endExtData = p + len; + + // Get extension ID + VerifyOrExit(mbedtls_asn1_get_tag(&p, endExtData, &extnOid.len, MBEDTLS_ASN1_OID) == 0, error = kErrorParse); + extnOid.tag = MBEDTLS_ASN1_OID; + extnOid.p = p; + p += extnOid.len; + + // Get optional critical + ret = mbedtls_asn1_get_bool(&p, endExtData, &isCritical); + VerifyOrExit(ret == 0 || ret == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG, error = kErrorParse); + + // Data should be octet string type + VerifyOrExit(mbedtls_asn1_get_tag(&p, endExtData, &len, MBEDTLS_ASN1_OCTET_STRING) == 0, error = kErrorParse); + VerifyOrExit(endExtData == p + len, error = kErrorParse); + + if (isCritical || extnOid.len != sizeof(oid)) + { + continue; + } + + if (memcmp(extnOid.p, oid, sizeof(oid)) == 0) + { + if (aAttributeLength != nullptr) + { + *aAttributeLength = len; + } + + if (aAttributeBuffer != nullptr) + { + VerifyOrExit(len <= attributeBufferSize, 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 +783,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 +827,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 +859,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 +897,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..e10e507c3b16 100644 --- a/src/core/meshcop/dtls.hpp +++ b/src/core/meshcop/dtls.hpp @@ -36,12 +36,28 @@ #include "openthread-core-config.h" +#ifdef OPENTHREAD_CONFIG_TLS_API_ENABLE +#error `OPENTHREAD_CONFIG_TLS_API_ENABLE` must not be defined directly, it is determine from `COAP_SECURE_API_ENABLE` and `BLE_TCAT_ENABLE` +#endif + +#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE || OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +#define OPENTHREAD_CONFIG_TLS_API_ENABLE 1 +#else +#define OPENTHREAD_CONFIG_TLS_API_ENABLE 0 +#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 +95,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 +228,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 +296,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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * @param[out] aAns1Type A pointer to the ANS1 type of the attribute written to the buffer. + * + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorInvalidArgs Invalid attribute length. + * @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, + 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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorInvalidArgs Invalid attribute length. + * @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); +#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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorInvalidArgs Invalid attribute length. + * @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); + + /** + * 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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @retval kErrorNone Successfully read attribute. + * @retval kErrorInvalidArgs Invalid attribute length. + * @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); + /** * Set the authentication mode for a dtls connection. * @@ -290,7 +406,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 +473,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 +485,19 @@ 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); #endif static void HandleMbedtlsDebug(void *aContext, int aLevel, const char *aFile, int aLine, const char *aStr); @@ -459,7 +580,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 +615,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..0ca0718bb495 --- /dev/null +++ b/src/core/meshcop/tcat_agent.cpp @@ -0,0 +1,524 @@ +/* + * 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/code_utils.hpp" +#include "common/debug.hpp" +#include "common/encoding.hpp" +#include "common/instance.hpp" +#include "common/locator_getters.hpp" +#include "common/string.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 +{ + return mProvisioningUrl == nullptr || IsValidUtf8String(mProvisioningUrl) || mPskdString != nullptr; +} + +TcatAgent::TcatAgent(Instance &aInstance) + : InstanceLocator(aInstance) + , mVendorInfo(nullptr) + , mCurrentApplicationLayer(kMessageTypeNone) + , mState(kStateDisabled) + , mAlreadyCommissioned(false) + , mCommissionerHasNetworkName(false) + , mCommissionerHasDomainName(false) + , mCommissionerHasExtendedPanId(false) +{ + mJoinerPskd.Clear(); + mCurrentServiceName[0] = 0; +} + +Error TcatAgent::Start(const TcatAgent::VendorInfo &aVendorInfo, + AppDataReceiveCallback aAppDataReceiveCallback, + JoinCallback aHandler, + void *aContext) +{ + Error error = kErrorNone; + + LogInfo("Starting"); + + VerifyOrExit(aVendorInfo.IsValid(), error = kErrorInvalidArgs); + SuccessOrExit(error = mJoinerPskd.SetFrom(aVendorInfo.mPskdString)); + + mAppDataReceiveCallback.Set(aAppDataReceiveCallback, aContext); + mJoinCallback.Set(aHandler, aContext); + + mVendorInfo = &aVendorInfo; + mCurrentApplicationLayer = kMessageTypeNone; + mState = kStateEnabled; + mAlreadyCommissioned = false; + +exit: + LogError("start TCAT agent", error); + return error; +} + +void TcatAgent::Stop(void) +{ + mCurrentApplicationLayer = kMessageTypeNone; + mState = kStateDisabled; + mAlreadyCommissioned = false; + mAppDataReceiveCallback.Clear(); + mJoinCallback.Clear(); + LogInfo("TCAT agent stopped"); +} + +Error TcatAgent::Connected(MeshCoP::Dtls &aTlsContext) +{ + size_t len; + Error error; + + VerifyOrExit(IsEnabled(), error = kErrorInvalidState); + len = sizeof(mCommissionerAuthorizationField); + SuccessOrExit( + error = aTlsContext.GetThreadAttributeFromPeerCertificate( + kCertificateAuthorizationField, reinterpret_cast(&mCommissionerAuthorizationField), &len)); + VerifyOrExit(len == sizeof(mCommissionerAuthorizationField), error = kErrorParse); + VerifyOrExit((mCommissionerAuthorizationField.mHeader & kCommissionerFlag) == 1, error = kErrorParse); + + len = sizeof(mDeviceAuthorizationField); + SuccessOrExit(error = aTlsContext.GetThreadAttributeFromOwnCertificate( + kCertificateAuthorizationField, reinterpret_cast(&mDeviceAuthorizationField), &len)); + VerifyOrExit(len == sizeof(mDeviceAuthorizationField), error = kErrorParse); + VerifyOrExit((mDeviceAuthorizationField.mHeader & kCommissionerFlag) == 0, error = kErrorParse); + + mCommissionerHasDomainName = false; + mCommissionerHasNetworkName = false; + mCommissionerHasExtendedPanId = false; + + len = sizeof(mCommissionerDomainName) - 1; + if (aTlsContext.GetThreadAttributeFromPeerCertificate( + kCertificateDomainName, reinterpret_cast(&mCommissionerDomainName), &len) == kErrorNone) + { + mCommissionerDomainName.m8[len] = '\0'; + mCommissionerHasDomainName = true; + } + + len = sizeof(mCommissionerNetworkName) - 1; + if (aTlsContext.GetThreadAttributeFromPeerCertificate( + kCertificateNetworkName, reinterpret_cast(&mCommissionerNetworkName), &len) == kErrorNone) + { + mCommissionerNetworkName.m8[len] = '\0'; + mCommissionerHasNetworkName = true; + } + + len = sizeof(mCommissionerExtendedPanId); + if (aTlsContext.GetThreadAttributeFromPeerCertificate( + kCertificateExtendedPanId, reinterpret_cast(&mCommissionerExtendedPanId), &len) == kErrorNone) + { + if (len == sizeof(mCommissionerExtendedPanId)) + { + mCommissionerHasExtendedPanId = true; + } + } + + mCurrentApplicationLayer = kMessageTypeNone; + mCurrentServiceName[0] = 0; + mState = kStateConnected; + mAlreadyCommissioned = Get().IsCommissioned(); + LogInfo("TCAT agent connected"); + +exit: + return error; +} + +void TcatAgent::Disconnected(void) +{ + mCurrentApplicationLayer = kMessageTypeNone; + mAlreadyCommissioned = false; + + if (mState != kStateDisabled) + { + mState = kStateEnabled; + } + + LogInfo("TCAT agent disconnected"); +} + +bool TcatAgent::IsCommandClassAuthorized(CommandClassFlags aCommissionerCommandClassFlags, + CommandClassFlags aDeviceCommandClassFlags, + Dataset *aDataset) const +{ + bool authorized = false; + bool additionalDeviceRequirementMet = false; + bool domainNamesMatch = false; + bool networkNamesMatch = false; + bool extendedPanIdsMatch = false; + + VerifyOrExit(IsConnected()); + VerifyOrExit(aCommissionerCommandClassFlags & kAccessFlag); + + if (aDeviceCommandClassFlags & kAccessFlag) + { + additionalDeviceRequirementMet = true; + } + + if (aDeviceCommandClassFlags & kPskdFlag) + { + additionalDeviceRequirementMet = true; + } + + if (aDeviceCommandClassFlags & kPskcFlag) + { + additionalDeviceRequirementMet = true; + } + + if (mCommissionerHasNetworkName || mCommissionerHasExtendedPanId) + { + Dataset::Info datasetInfo; + Error datasetError = kErrorNone; + + if (aDataset == nullptr) + { + datasetError = Get().Read(datasetInfo); + } + else + { + aDataset->ConvertTo(datasetInfo); + } + + if (datasetError == kErrorNone) + { + if (datasetInfo.IsNetworkNamePresent() && mCommissionerHasNetworkName && + (datasetInfo.GetNetworkName() == mCommissionerNetworkName)) + { + networkNamesMatch = true; + } + + if (datasetInfo.IsExtendedPanIdPresent() && mCommissionerHasExtendedPanId && + (datasetInfo.GetExtendedPanId() == mCommissionerExtendedPanId)) + { + extendedPanIdsMatch = true; + } + } + } + + if (!networkNamesMatch) + { + VerifyOrExit((aCommissionerCommandClassFlags & kNetworkNameFlag) == 0); + } + else if (aDeviceCommandClassFlags & kNetworkNameFlag) + { + additionalDeviceRequirementMet = true; + } + + if (!extendedPanIdsMatch) + { + VerifyOrExit((aCommissionerCommandClassFlags & kExtendedPanIdFlag) == 0); + } + else if (aDeviceCommandClassFlags & kExtendedPanIdFlag) + { + additionalDeviceRequirementMet = true; + } + +#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) + if (mCommissionerHasDomainName && (Get().GetDomainName() == mCommissionerDomainName)) + { + domainNamesMatch = true; + } +#endif + + if (!domainNamesMatch) + { + VerifyOrExit((aCommissionerCommandClassFlags & kThreadDomainFlag) == 0); + } + else if (aDeviceCommandClassFlags & kThreadDomainFlag) + { + additionalDeviceRequirementMet = true; + } + + if (additionalDeviceRequirementMet) + { + authorized = true; + } + +exit: + return authorized; +} + +bool TcatAgent::IsCommandClassAuthorized(CommandClass aCommandClass) const +{ + bool authorized = false; + + switch (aCommandClass) + { + case kGeneral: + authorized = true; + break; + + case kCommissioning: + authorized = IsCommandClassAuthorized(mCommissionerAuthorizationField.mCommissioningFlags, + mDeviceAuthorizationField.mCommissioningFlags, nullptr); + break; + + case kExtraction: + authorized = IsCommandClassAuthorized(mCommissionerAuthorizationField.mExtractionFlags, + mDeviceAuthorizationField.mExtractionFlags, nullptr); + break; + + case kTlvDecommissioning: + authorized = IsCommandClassAuthorized(mCommissionerAuthorizationField.mDecommissioningFlags, + mDeviceAuthorizationField.mDecommissioningFlags, nullptr); + break; + + case kApplication: + authorized = IsCommandClassAuthorized(mCommissionerAuthorizationField.mApplicationFlags, + mDeviceAuthorizationField.mApplicationFlags, nullptr); + break; + + case kInvalid: + authorized = false; + break; + } + + return authorized; +} + +TcatAgent::CommandClass TcatAgent::GetCommandClass(uint8_t aTlvType) const +{ + static constexpr int kGeneralTlvs = 0x1F; + static constexpr int kCommissioningTlvs = 0x3F; + static constexpr int kExtractionTlvs = 0x5F; + static constexpr int kTlvDecommissioningTlvs = 0x7F; + static constexpr int kApplicationTlvs = 0x9F; + + if (aTlvType <= kGeneralTlvs) + { + return kGeneral; + } + else if (aTlvType <= kCommissioningTlvs) + { + return kCommissioning; + } + else if (aTlvType <= kExtractionTlvs) + { + return kExtraction; + } + else if (aTlvType <= kTlvDecommissioningTlvs) + { + return kTlvDecommissioning; + } + else if (aTlvType <= kApplicationTlvs) + { + return kApplication; + } + else + { + return kInvalid; + } +} + +bool TcatAgent::CanProcessTlv(uint8_t aTlvType) const +{ + CommandClass tlvCommandClass = GetCommandClass(aTlvType); + return IsCommandClassAuthorized(tlvCommandClass); +} + +Error TcatAgent::HandleSingleTlv(const Message &aIncommingMessage, Message &aOutgoingMessage) +{ + Error error = kErrorParse; + ot::Tlv tlv; + uint32_t offset = aIncommingMessage.GetOffset(); + uint32_t length; + bool response = false; + + VerifyOrExit(IsConnected(), error = kErrorInvalidState); + SuccessOrExit(error = aIncommingMessage.Read(offset, tlv)); + + if (tlv.IsExtended()) + { + ot::ExtendedTlv extTlv; + SuccessOrExit(error = aIncommingMessage.Read(offset, extTlv)); + length = extTlv.GetLength(); + offset += sizeof(ot::ExtendedTlv); + } + else + { + length = tlv.GetLength(); + offset += sizeof(ot::Tlv); + } + + if (!CanProcessTlv(tlv.GetType())) + { + error = kErrorRejected; + } + else + { + switch (tlv.GetType()) + { + case kTlvDisconnect: + error = kErrorAbort; + break; + + case kTlvSetActiveOperationalDataset: + error = HandleSetActiveOperationalDataset(aIncommingMessage, offset, length); + break; + + case kTlvStartThreadInterface: + error = HandleStartThreadInterface(); + break; + + case kTlvStopThreadInterface: + Get().Stop(); + error = kErrorNone; + break; + + case kTlvSendApplicationData: + LogInfo("Application data len:%d, offset:%d", length, offset); + mAppDataReceiveCallback.InvokeIfSet(&GetInstance(), &aIncommingMessage, offset, + MapEnum(mCurrentApplicationLayer), mCurrentServiceName); + response = true; + error = kErrorNone; + break; + + default: + error = kErrorInvalidCommand; + } + } + + if (!response) + { + ot::Tlv statusTlv; + StatusCode statusCode; + + statusTlv.SetType(kTlvResponseWithStatus); + statusTlv.SetLength(sizeof(statusCode)); + + switch (error) + { + case kErrorNone: + statusCode = kStatusSuccess; + break; + + case kErrorInvalidState: + statusCode = kStatusUndefined; + break; + + case kErrorParse: + statusCode = kStatusParseError; + break; + + case kErrorInvalidCommand: + statusCode = kStatusUnsupported; + break; + + case kErrorRejected: + statusCode = kStatusUnauthorized; + break; + + case kErrorNotImplemented: + statusCode = kStatusUnsupported; + break; + + default: + statusCode = kStatusGeneralError; + break; + } + + SuccessOrExit(error = ot::Tlv::Append(aOutgoingMessage, statusCode)); + } + +exit: + return error; +} + +Error TcatAgent::HandleSetActiveOperationalDataset(const Message &aIncommingMessage, uint32_t aOffset, uint32_t aLength) +{ + Dataset dataset; + otOperationalDatasetTlvs datasetTlvs; + Error error; + + SuccessOrExit(error = dataset.ReadFromMessage(aIncommingMessage, aOffset, aLength)); + + if (!IsCommandClassAuthorized(mCommissionerAuthorizationField.mApplicationFlags, + mDeviceAuthorizationField.mApplicationFlags, &dataset)) + { + error = kErrorRejected; + ExitNow(); + } + + dataset.ConvertTo(datasetTlvs); + error = Get().Save(datasetTlvs); + +exit: + return error; +} + +Error TcatAgent::HandleStartThreadInterface(void) +{ + Error error; + Dataset::Info datasetInfo; + + VerifyOrExit(Get().Read(datasetInfo) == kErrorNone, error = kErrorInvalidState); + VerifyOrExit(datasetInfo.IsNetworkKeyPresent(), error = kErrorInvalidState); + +#if OPENTHREAD_CONFIG_LINK_RAW_ENABLE + VerifyOrExit(!Get().IsEnabled(), error = kErrorInvalidState); +#endif + + Get().Up(); + error = Get().Start(); + +exit: + return error; +} + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_WARN) +void TcatAgent::LogError(const char *aActionText, Error aError) +{ + if (aError != kErrorNone) + { + LogWarn("Failed to %s: %s", aActionText, ErrorToString(aError)); + } +} +#endif + +} // 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..45b4b2a3ab03 --- /dev/null +++ b/src/core/meshcop/tcat_agent.hpp @@ -0,0 +1,423 @@ +/* + * 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 + +#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/dataset.hpp" +#include "meshcop/dtls.hpp" +#include "meshcop/meshcop.hpp" +#include "meshcop/meshcop_tlvs.hpp" + +namespace ot { + +namespace Ble { +class BleSecure; +} + +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; + + /** + * Represents TCAT a command class. + * + */ + enum CommandClass + { + kGeneral = OT_TCAT_COMMAND_CLASS_GENERAL, ///< TCAT commands related to general operations + kCommissioning = OT_TCAT_COMMAND_CLASS_COMMISSIONING, ///< TCAT commands related to commissioning + kExtraction = OT_TCAT_COMMAND_CLASS_EXTRACTION, ///< TCAT commands related to key extraction + kTlvDecommissioning = OT_TCAT_COMMAND_CLASS_DECOMMISSIONING, ///< TCAT commands related to de-commissioning + kApplication = OT_TCAT_COMMAND_CLASS_APPLICATION, ///< TCAT commands related to application layer + kInvalid ///< TCAT command belongs to reserved pool or is invalid + }; + + /** + * The certificate authorization field header type to indicate the type and version of the certificate. + * + */ + enum CertificateAuthorizationFieldHeader : uint8_t + { + kCommissionerFlag = 1 << 0, ///< TCAT commissioner ('1') or device ('0') + kHeaderVersion = 0xD0, ///< Header version (3 bits) + }; + + /** + * The command class flag type to indicate which requirements apply for a given command class. + * + */ + enum CommandClassFlags : uint8_t + { + kAccessFlag = 1 << 0, ///< Access to the command class (device: without without additional requirements). + kPskdFlag = 1 << 1, ///< Access requires proof-of-possession of the device's PSKd + kNetworkNameFlag = 1 << 2, ///< Access requires matching network name + kExtendedPanIdFlag = 1 << 3, ///< Access requires matching XPANID + kThreadDomainFlag = 1 << 4, ///< Access requires matching XPANID + kPskcFlag = 1 << 5, ///< Access requires proof-of-possession of the device's PSKc + }; + + /** + * + * 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 CertificateAuthorizationField + { + CertificateAuthorizationFieldHeader mHeader; ///< Typ and version + CommandClassFlags mCommissioningFlags; ///< Command class flags + CommandClassFlags mExtractionFlags; ///< Command class flags + CommandClassFlags mDecommissioningFlags; ///< Command class flags + CommandClassFlags mApplicationFlags; ///< Command class flags + + } OT_TOOL_PACKED_END; + + typedef CertificateAuthorizationField 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 + kTlvResponseWithStatus = 1, ///< TCAT response with status value TLV + kTlvResponseWithPayload = 2, ///< TCAT response with payload TLV + kTlvResponseEvent = 3, ///< TCAT response event TLV (reserved) + kTlvGetNetworkName = 8, ///< TCAT network name query TLV + kTlvDisconnect = 9, ///< TCAT disconnect request TLV + kTlvPing = 10, ///< TCAT ping request TLV + kTlvGetDeviceId = 11, ///< TCAT device ID query TLV + kTlvGetExtendedPanID = 12, ///< TCAT extended PAN ID query TLV + kTlvPresentPskdHash = 16, ///< TCAT commissioner rights elevation request TLV using PSKd hash + kTlvPresentPskcHash = 17, ///< TCAT commissioner rights elevation request TLV using PSKc hash + kTlvPresentInstallCodeHash = 18, ///< TCAT commissioner rights elevation request TLV using install code + kTlvRequestRandomNumChallenge = 19, ///< TCAT random number challenge query TLV + kTlvRequestPskdHash = 20, ///< TCAT PSKd hash request TLV + + // Command Class Commissioning + kTlvSetActiveOperationalDataset = 32, ///< TCAT active operational dataset TLV + kTlvSetActiveOperationalDatasetAlternative = 33, ///< TCAT active operational dataset alternative #1 TLV + kTlvGetProvissioningTlvs = 36, ///< TCAT provisioning TLVs query TLV + kTlvGetCommissionerCertificate = 37, ///< TCAT commissioner certificate query TLV + kTlvGetDiagnosticTlvs = 38, ///< TCAT diagnostics TLVs query TLV + kTlvStartThreadInterface = 39, ///< TCAT start thread interface request TLV + kTlvStopThreadInterface = 40, ///< TCAT stop thread interface request TLV + + // Command Class Extraction + kTlvGetActiveOperationalDataset = 64, ///< TCAT active oerational dataset query TLV + kTlvGetActiveOperationalDatasetAlternative = 65, ///< TCAT active oerational dataset alternative #1 query TLV + + // Command Class Decommissioning + kTlvDecommission = 96, ///< TCAT decommission request TLV + + // Command Class Application + kTlvSelectApplicationLayerUdp = 128, ///< TCAT select UDP protocol application layer request TLV + kTlvSelectApplicationLayerTcp = 129, ///< TCAT select TCP protocol application layer request TLV + kTlvSendApplicationData = 130, ///< TCAT send application data TLV + kTlvSendVendorSpecificData = 159, ///< TCAT send vendor specific command or data TLV + + // Command Class CCM + kTlvSetLDevIdOperationalCert = 160, ///< TCAT LDevID operational certificate TLV + kTlvSetLDevIdPrivateKey = 161, ///< TCAT LDevID operational certificate pricate key TLV + kTlvSetDomainCaCert = 162, ///< TCAT domain CA certificate TLV + }; + + /** + * TCAT Response Types. + * + */ + enum StatusCode : uint8_t + { + kStatusSuccess = OT_TCAT_STATUS_SUCCESS, ///< Command or request was successfully processed + kStatusUnsupported = OT_TCAT_STATUS_UNSUPPORTED, ///< Requested command or received TLV is not supported + kStatusParseError = OT_TCAT_STATUS_PARSE_ERROR, ///< Request / command could not be parsed correctly + kStatusValueError = OT_TCAT_STATUS_VALUE_ERROR, ///< The value of the transmitted TLV has an error + kStatusGeneralError = OT_TCAT_STATUS_GENERAL_ERROR, ///< An error not matching any other category occurred + kStatusBusy = OT_TCAT_STATUS_BUSY, ///< Command cannot be executed because the resource is busy + kStatusUndefined = OT_TCAT_STATUS_UNDEFINED, ///< The requested value, data or service is not defined + ///< (currently) or not present + kStatusHashError = OT_TCAT_STATUS_HASH_ERROR, ///< The hash value presented by the commissioner was incorrect + kStatusUnauthorized = + OT_TCAT_STATUS_UNAUTHORIZED, ///< Sender does not have sufficient authorization for the given command + }; + + /** + * Represents TCAT message type. + * + */ + enum TcatMessageType : uint8_t + { + kMessageTypeNone = OT_TCAT_MESSAGE_TYPE_NONE, ///< Message which has been sent without activating the TCAT agent + kMessageTypeStatus = + OT_TCAT_MESSAGE_TYPE_STATUS, ///< Message containing a status code (byte) as defined in otTcatStatusCode + kMessageTypeUdp = OT_TCAT_MESSAGE_TYPE_UDP, ///< Message directed to a UDP service + kMessageTypeTcp = OT_TCAT_MESSAGE_TYPE_TCP, ///< Message directed to a TCP service + kMessageTypeChangedUdpService = + OT_TCAT_MESSAGE_TYPE_CHANGED_TO_UDP_SERVICE, ///< Client has changed to a UDP service + kMessageTypeChangedTcpService = + OT_TCAT_MESSAGE_TYPE_CHANGED_TO_TCP_SERVICE, ///< Client has changed to a TCP service + }; + + /** + * Represents a TCAT certificate V3 extension attribute (OID 1.3.6.1.4.1.44970.x). + * + */ + enum TcatCertificateAttribute + { + kCertificateDomainName = 1, + kCertificateAuthorizationField = 3, + kCertificateNetworkName = 4, + kCertificateExtendedPanId = 5, + }; + + /** + * Represents TCAT status. + * + */ + enum State : uint8_t + { + kStateDisabled, + kStateEnabled, + kStateConnected, + }; + + /** + * 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(const VendorInfo &aVendorInfo, + AppDataReceiveCallback aAppDataReceiveCallback, + JoinCallback aHandler, + void *aContext); + + /** + * Stops the TCAT protocol. + * + */ + void Stop(void); + + /** + * 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 mState != kStateDisabled; } + + /** + * 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 mState == kStateConnected; } + + /** + * Indicates whether or not a command class with given flags is authorized. + * + * @retval TRUE The command class is authorized. + * @retval FALSE The command class is not authorized. + * + */ + bool IsCommandClassAuthorized(CommandClassFlags aCommissionerCommandClassFlags, + CommandClassFlags aDeviceCommandClassFlags, + Dataset *aDataset) const; + + /** + * 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(CommandClass aCommandClass) const; + + /** + * Returns command class to which the argument TLV type belongs. + * + * @retval kGeneral Tlv type belongs to General command class + * @retval kCommissioning Tlv type belongs to Commissioning command class + * @retval kExtraction Tlv type belongs to Extraction command class + * @retval kTlvDecommissioning Tlv type belongs to Decommissioning command class + * @retval kApplication Tlv type belongs to Application command class + * @retval kInvalid Tlv type is reserved, is unimplemented or is invalid + * + */ + CommandClass GetCommandClass(uint8_t aTlvType) const; + + /** + * Indicates whether or not the provided TLV type is authorized. + * + * @retval TRUE The TLV type is authorized. + * @retval FALSE The TLV type is not authorized. + * + */ + bool CanProcessTlv(uint8_t aTlvType) const; + +private: + /** + * 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(void); + + /** + * 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(const Message &aIncommingMessage, Message &aOutgoingMessage); + Error HandleSetActiveOperationalDataset(const Message &aIncommingMessage, uint32_t aOffset, uint32_t aLength); + Error HandleStartThreadInterface(void); + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_WARN) + void LogError(const char *aActionText, Error aError); +#else + void LogError(const char *, Error) {} +#endif + + static constexpr uint16_t kJoinerUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT; + + JoinerPskd mJoinerPskd; + const VendorInfo *mVendorInfo; + Callback mJoinCallback; + Callback mAppDataReceiveCallback; + CertificateAuthorizationField mCommissionerAuthorizationField; + CertificateAuthorizationField mDeviceAuthorizationField; + TcatMessageType mCurrentApplicationLayer; + NetworkName mCommissionerNetworkName; + NetworkName mCommissionerDomainName; + ExtendedPanId mCommissionerExtendedPanId; + char mCurrentServiceName[OT_TCAT_MAX_SERVICE_NAME_LENGTH + 1]; + State mState; + bool mAlreadyCommissioned : 1; + bool mCommissionerHasNetworkName : 1; + bool mCommissionerHasDomainName : 1; + bool mCommissionerHasExtendedPanId : 1; + + friend class Ble::BleSecure; +}; + +} // namespace MeshCoP + +DefineCoreType(otTcatVendorInfo, MeshCoP::TcatAgent::VendorInfo); + +DefineMapEnum(otTcatMessageType, MeshCoP::TcatAgent::TcatMessageType); + +typedef UintTlvInfo ResponseWithStatusTlv; + +} // 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..d350c29eb1fc --- /dev/null +++ b/src/core/radio/ble_secure.cpp @@ -0,0 +1,539 @@ +/* + * 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" + +using namespace ot; + +/** + * @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(false) + , mReceivedMessage(nullptr) + , mSendMessage(nullptr) + , mTransmitTask(aInstance) + , mBleState(kStopped) + , mMtuSize(kInitialMtuSize) +{ +} + +Error BleSecure::Start(ConnectCallback aConnectHandler, ReceiveCallback aReceiveHandler, bool aTlvMode, void *aContext) +{ + Error error = kErrorNone; + + VerifyOrExit(mBleState == kStopped, error = kErrorAlready); + + 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: + if (error != kErrorNone) + { + mBleState = kAdvertising; + } + return error; +} + +Error BleSecure::TcatStart(const MeshCoP::TcatAgent::VendorInfo &aVendorInfo, + MeshCoP::TcatAgent::JoinCallback aJoinHandler) +{ + return mTcatAgent.Start(aVendorInfo, mReceiveCallback.GetHandler(), aJoinHandler, mReceiveCallback.GetContext()); +} + +void BleSecure::Stop(void) +{ + VerifyOrExit(mBleState != kStopped); + SuccessOrExit(otPlatBleGapAdvStop(&GetInstance())); + SuccessOrExit(otPlatBleDisable(&GetInstance())); + mBleState = kStopped; + mMtuSize = kInitialMtuSize; + + if (mTcatAgent.IsEnabled()) + { + mTcatAgent.Stop(); + } + + mTls.Close(); + + mTransmitQueue.DequeueAndFreeAll(); + + mConnectCallback.Clear(); + mReceiveCallback.Clear(); + + FreeMessage(mReceivedMessage); + mReceivedMessage = nullptr; + FreeMessage(mSendMessage); + mSendMessage = nullptr; + +exit: + return; +} + +Error BleSecure::Connect(void) +{ + Ip6::SockAddr sockaddr; + + return mTls.Connect(sockaddr); +} + +void BleSecure::Disconnect(void) +{ + if (mTls.IsConnected()) + { + mTls.Disconnect(); + } + + if (mBleState == kConnected) + { + 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())); +} + +#if defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) +Error BleSecure::GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength) +{ + Error error; + + VerifyOrExit(aCertLength != nullptr, error = kErrorInvalidArgs); + + error = mTls.GetPeerCertificateBase64(aPeerCert, aCertLength, *aCertLength); + +exit: + return error; +} +#endif // defined(MBEDTLS_BASE64_C) && defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + +Error BleSecure::SendMessage(ot::Message &aMessage) +{ + Error error = kErrorNone; + + VerifyOrExit(IsConnected(), error = kErrorInvalidState); + if (mSendMessage == nullptr) + { + mSendMessage = Get().Allocate(Message::kTypeBle); + VerifyOrExit(mSendMessage != nullptr, error = kErrorNoBufs); + } + SuccessOrExit(error = mSendMessage->AppendBytesFromMessage(aMessage, 0, aMessage.GetLength())); + SuccessOrExit(error = Flush()); + +exit: + aMessage.Free(); + return error; +} + +Error BleSecure::Send(uint8_t *aBuf, uint16_t aLength) +{ + Error error = kErrorNone; + + VerifyOrExit(IsConnected(), error = kErrorInvalidState); + if (mSendMessage == nullptr) + { + mSendMessage = Get().Allocate(Message::kTypeBle); + VerifyOrExit(mSendMessage != nullptr, error = kErrorNoBufs); + } + SuccessOrExit(error = mSendMessage->AppendBytes(aBuf, aLength)); + +exit: + return error; +} + +Error BleSecure::SendApplicationTlv(uint8_t *aBuf, uint16_t aLength) +{ + Error error = kErrorNone; + if (aLength > Tlv::kBaseTlvMaxLength) + { + ot::ExtendedTlv tlv; + + tlv.SetType(ot::MeshCoP::TcatAgent::kTlvSendApplicationData); + tlv.SetLength(aLength); + SuccessOrExit(error = Send(reinterpret_cast(&tlv), sizeof(tlv))); + } + else + { + ot::Tlv tlv; + + tlv.SetType(ot::MeshCoP::TcatAgent::kTlvSendApplicationData); + tlv.SetLength(aLength); + SuccessOrExit(error = Send(reinterpret_cast(&tlv), sizeof(tlv))); + } + + error = Send(aBuf, aLength); +exit: + return error; +} + +Error BleSecure::Flush(void) +{ + Error error = kErrorNone; + + VerifyOrExit(IsConnected(), error = kErrorInvalidState); + VerifyOrExit(mSendMessage->GetLength() != 0, error = kErrorNone); + + mTransmitQueue.Enqueue(*mSendMessage); + mTransmitTask.Post(); + + mSendMessage = nullptr; + +exit: + return error; +} + +Error BleSecure::HandleBleReceive(uint8_t *aBuf, uint16_t aLength) +{ + ot::Message *message = nullptr; + Ip6::MessageInfo messageInfo; + Error error = kErrorNone; + + if ((message = Get().Allocate(Message::kTypeBle, 0)) == nullptr) + { + error = kErrorNoBufs; + ExitNow(); + } + SuccessOrExit(error = message->AppendBytes(aBuf, aLength)); + + // Cannot call Receive(..) directly because Setup(..) and mState are private + mTls.HandleUdpReceive(*message, messageInfo); + +exit: + FreeMessage(message); + return error; +} + +void BleSecure::HandleBleConnected(uint16_t aConnectionId) +{ + OT_UNUSED_VARIABLE(aConnectionId); + + mBleState = kConnected; + + otPlatBleGattMtuGet(&GetInstance(), &mMtuSize); + + mConnectCallback.InvokeIfSet(&GetInstance(), IsConnected(), true); +} + +void BleSecure::HandleBleDisconnected(uint16_t aConnectionId) +{ + OT_UNUSED_VARIABLE(aConnectionId); + + mBleState = kAdvertising; + mMtuSize = kInitialMtuSize; + + if (IsConnected()) + { + Disconnect(); // Stop TLS connection + } + + mConnectCallback.InvokeIfSet(&GetInstance(), false, false); +} + +Error BleSecure::HandleBleMtuUpdate(uint16_t aMtu) +{ + Error error = kErrorNone; + + if (aMtu <= OT_BLE_ATT_MTU_MAX) + { + mMtuSize = aMtu; + } + else + { + mMtuSize = OT_BLE_ATT_MTU_MAX; + error = kErrorInvalidArgs; + } + + return error; +} + +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 (mTcatAgent.IsEnabled()) + mTcatAgent.Connected(mTls); + } + else + { + FreeMessage(mReceivedMessage); + mReceivedMessage = nullptr; + + if (mTcatAgent.IsEnabled()) + mTcatAgent.Disconnected(); + } + + mConnectCallback.InvokeIfSet(&GetInstance(), aConnected, true); +} + +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) + { + SuccessOrExit(mReceivedMessage->AppendBytes(aBuf, aLength)); + mReceiveCallback.InvokeIfSet(&GetInstance(), mReceivedMessage, 0, OT_TCAT_MESSAGE_TYPE_NONE, ""); + mReceivedMessage->SetLength(0); + } + else + { + 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()) + { + ot::Message *message; + Error error = kErrorNone; + + message = Get().Allocate(Message::kTypeBle); + VerifyOrExit(message != nullptr, error = kErrorNoBufs); + + error = mTcatAgent.HandleSingleTlv(*mReceivedMessage, *message); + if (message->GetLength() != 0) + { + IgnoreReturnValue(SendMessage(*message)); + } + + if (error == kErrorAbort) + { + Disconnect(); // Keep BLE active (for testing) + Stop(); // Stop BLE interface + ExitNow(); + } + } + else + { + mReceivedMessage->SetOffset(offset); + mReceiveCallback.InvokeIfSet(&GetInstance(), mReceivedMessage, offset, OT_TCAT_MESSAGE_TYPE_NONE, ""); + } + + SuccessOrExit(mReceivedMessage->SetLength(0)); // also sets the offset to 0 + requiredBytes = sizeof(Tlv); + } + } + +exit: + return; +} + +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; + Error error = kErrorNone; + + 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; + + SuccessOrExit(error = otPlatBleGattServerIndicate(&GetInstance(), kTxBleHandle, &packet)); + + len -= packet.mLength; + offset += packet.mLength; + } + + aMessage.Free(); +exit: + return error; +} + +} // namespace Ble +} // namespace ot + +void otPlatBleGattServerOnWriteRequest(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket) +{ + OT_UNUSED_VARIABLE(aHandle); // Only a single handle is expected for RX + + VerifyOrExit(aPacket != nullptr); + AsCoreType(aInstance).Get().HandleBleReceive(aPacket->mValue, aPacket->mLength); +exit: + return; +} + +void otPlatBleGapOnConnected(otInstance *aInstance, uint16_t aConnectionId) +{ + AsCoreType(aInstance).Get().HandleBleConnected(aConnectionId); +} + +void otPlatBleGapOnDisconnected(otInstance *aInstance, uint16_t aConnectionId) +{ + AsCoreType(aInstance).Get().HandleBleDisconnected(aConnectionId); +} + +void otPlatBleGattOnMtuUpdate(otInstance *aInstance, uint16_t aMtu) +{ + IgnoreReturnValue(AsCoreType(aInstance).Get().HandleBleMtuUpdate(aMtu)); +} + +#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..2f9565a01882 --- /dev/null +++ b/src/core/radio/ble_secure.hpp @@ -0,0 +1,517 @@ +/* + * 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; + + /** + * Represents TCAT a command class. + * + */ + typedef MeshCoP::TcatAgent::CommandClass CommandClass; + + /** + * Constructor initializes the object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit BleSecure(Instance &aInstance); + + /** + * Starts the secure BLE agent. + * + * @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 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. + * + * @param[in] aVendorInfo A reference to the Vendor Information (must remain valid after the method call) + * @param[in] aHandler Callback to a function that is called when the join operation completes. + * + * @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(const 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(void); + + /** + * Stops the BLE and TLS connection. + * + */ + void Disconnect(void); + + /** + * Indicates whether or not the TLS session is active (connected or conneting). + * + * @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 a TCAT command class is authorized. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCommandClass A command class to check. + * + * @retval TRUE The command class is authorized. + * @retval FALSE The command class is not authorized. + * + */ + bool IsCommandClassAuthorized(CommandClass aCommandClass) const + { + return mTcatAgent.IsCommandClassAuthorized(aCommandClass); + } + + /** + * 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 On input, the size the max size of @p aPeerCert. + * On output, the length of the base64 encoded peer certificate. + * + * @retval kErrorNone Successfully get the peer certificate. + * @retval kErrorInvalidArgs @p aInstance or @p aCertLength is invalid. + * @retval kErrorInvalidState Not connected yet. + * @retval kErrorNoBufs Can't allocate memory for certificate. + * + */ + Error GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength); +#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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * @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, + int *aAns1Type) + { + return mTls.GetPeerSubjectAttributeByOid(aOid, aOidLength, aAttributeBuffer, aAttributeLength, 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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @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) + { + return mTls.GetThreadAttributeFromPeerCertificate(aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); + } +#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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @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) + { + return mTls.GetThreadAttributeFromOwnCertificate(aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); + } + + /** + * 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[in,out] aAttributeLength On input, the size the max size of @p aAttributeBuffer. + * On output, the length of the attribute written to the buffer. + * + * @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) + { + return mTls.GetThreadAttributeFromCaCertificateChain(aThreadOidDescriptor, aAttributeBuffer, aAttributeLength); + } + + /** + * 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(void); + + /** + * 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. + * + */ + void 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. + * + */ + void HandleBleDisconnected(uint16_t aConnectionId); + + /** + * Used to notify the secure BLE server that the BLE Device has updated ATT_MTU size. + * + * @param[in] aMtu The updated ATT_MTU value. + * + */ + Error HandleBleMtuUpdate(uint16_t aMtu); + +private: + enum BleState : uint8_t + { + kStopped = 0, // Ble secure not started. + kAdvertising = 1, // Ble secure not advertising. + kConnected = 2, // Ble secure not connected. + }; + + 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 = OT_BLE_ATT_MTU_MAX - kGattOverhead; + static constexpr uint16_t kTxBleHandle = 0; // Characteristics Handle for TX (not used) + + 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); + + void HandleTransmit(void); + + static Error HandleTransport(void *aContext, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + Error HandleTransport(ot::Message &aMessage); + + using TxTask = TaskletIn; + + MeshCoP::Dtls mTls; + MeshCoP::TcatAgent mTcatAgent; + Callback mConnectCallback; + Callback mReceiveCallback; + bool mTlvMode; + ot::Message *mReceivedMessage; + ot::Message *mSendMessage; + ot::MessageQueue mTransmitQueue; + TxTask mTransmitTask; + uint8_t mPacketBuffer[kPacketBufferSize]; + BleState mBleState; + 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 503a33e75bd7..4695c907ce65 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 configuration.cpp diff --git a/src/posix/platform/ble.cpp b/src/posix/platform/ble.cpp new file mode 100644 index 000000000000..2fe3c64535d5 --- /dev/null +++ b/src/posix/platform/ble.cpp @@ -0,0 +1,75 @@ +/* + * 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); + 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..41832f75dbb2 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,52 @@ 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); + 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