diff --git a/Android.mk b/Android.mk index 063dafe61..ca5797cac 100644 --- a/Android.mk +++ b/Android.mk @@ -49,6 +49,7 @@ OPENTHREAD_PUBLIC_CFLAGS := \ -DOPENTHREAD_CONFIG_MAC_FILTER_ENABLE=1 \ -DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1 \ -DOPENTHREAD_CONFIG_PING_SENDER_ENABLE=1 \ + -DOPENTHREAD_CONFIG_TLS_ENABLE=1 \ -DOPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE=1 \ -DOPENTHREAD_FTD=1 \ -DOPENTHREAD_PLATFORM_POSIX=1 \ diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 3f12693ae..8086b2c29 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 (277) +#define OPENTHREAD_API_VERSION (278) /** * @addtogroup api-instance diff --git a/include/openthread/tcp_ext.h b/include/openthread/tcp_ext.h index 576e32e78..41c2fb84e 100644 --- a/include/openthread/tcp_ext.h +++ b/include/openthread/tcp_ext.h @@ -147,8 +147,8 @@ enum * @p aLength if the send buffer reaches capacity. * @param[in] aFlags Flags specifying options for this operation (see enumeration above). * - * @returns OT_ERROR_NONE Successfully copied data into the send buffer and sent it on the TCP endpoint. - * @returns OT_ERROR_FAILED Failed to send out data on the TCP endpoint. + * @retval OT_ERROR_NONE Successfully copied data into the send buffer and sent it on the TCP endpoint. + * @retval OT_ERROR_FAILED Failed to send out data on the TCP endpoint. */ otError otTcpCircularSendBufferWrite(otTcpEndpoint *aEndpoint, otTcpCircularSendBuffer *aSendBuffer, @@ -184,7 +184,7 @@ void otTcpCircularSendBufferHandleForwardProgress(otTcpCircularSendBuffer *aSend * * @param[in] aSendBuffer A pointer to the TCP circular send buffer whose amount of free space to return. * - * @return The amount of free space in the send buffer. + * @returns The amount of free space in the send buffer. */ size_t otTcpCircularSendBufferGetFreeSpace(const otTcpCircularSendBuffer *aSendBuffer); @@ -215,6 +215,37 @@ void otTcpCircularSendBufferForceDiscardAll(otTcpCircularSendBuffer *aSendBuffer */ otError otTcpCircularSendBufferDeinitialize(otTcpCircularSendBuffer *aSendBuffer); +/** + * Context structure to use with mbedtls_ssl_set_bio. + */ +typedef struct otTcpEndpointAndCircularSendBuffer +{ + otTcpEndpoint *mEndpoint; + otTcpCircularSendBuffer *mSendBuffer; +} otTcpEndpointAndCircularSendBuffer; + +/** + * Non-blocking send callback to pass to mbedtls_ssl_set_bio. + * + * @param[in] aCtx A pointer to an otTcpEndpointAndCircularSendBuffer. + * @param[in] aBuf The data to add to the send buffer. + * @param[in] aLen The amount of data to add to the send buffer. + * + * @returns The number of bytes sent, or an mbedtls error code. + */ +int otTcpMbedTlsSslSendCallback(void *aCtx, const unsigned char *aBuf, size_t aLen); + +/** + * Non-blocking receive callback to pass to mbedtls_ssl_set_bio. + * + * @param[in] aCtx A pointer to an otTcpEndpointAndCircularSendBuffer. + * @param[out] aBuf The buffer into which to receive data. + * @param[in] aLen The maxmimum amount of data that can be received. + * + * @returns The number of bytes received, or an mbedtls error code. + */ +int otTcpMbedTlsSslRecvCallback(void *aCtx, unsigned char *aBuf, size_t aLen); + /** * @} * diff --git a/src/cli/README_TCP.md b/src/cli/README_TCP.md index eb42fbaff..3869570b8 100644 --- a/src/cli/README_TCP.md +++ b/src/cli/README_TCP.md @@ -157,14 +157,19 @@ stoplistening Done ``` -### init [\] +### init [\] [\] Initializes the example TCP listener and the example TCP endpoint. +- mode: this specifies the buffering strategy and whether to use TLS. The possible values are "linked", "circular" (default), and "tls". - size: the size of the receive buffer to associate with the example TCP endpoint. If left unspecified, the maximum size is used. +If "tls" is used, then the TLS protocol will be used for the connection (on top of TCP). When communicating over TCP between two nodes, either both should use TLS or neither should (a non-TLS endpoint cannot communicate with a TLS endpoint). The first two options, "linked" and "circular", specify that TLS should not be used and specify a buffering strategy to use with TCP; two endpoints of a TCP connection may use different buffering strategies. + +The behaviors of "linked" and "circular" buffering are identical, but the option is provided so that users of TCP can inspect the code to see an example of using the two buffering strategies. + ```bash -> tcp init +> tcp init tls Done ``` diff --git a/src/cli/cli_tcp.cpp b/src/cli/cli_tcp.cpp index 2ee39b12c..5c0517884 100644 --- a/src/cli/cli_tcp.cpp +++ b/src/cli/cli_tcp.cpp @@ -46,6 +46,11 @@ #include "common/encoding.hpp" #include "common/timer.hpp" +#if OPENTHREAD_CONFIG_TLS_ENABLE +#include +#include +#endif + namespace ot { namespace Cli { @@ -55,11 +60,30 @@ TcpExample::TcpExample(otInstance *aInstance, OutputImplementer &aOutputImplemen , mEndpointConnected(false) , mSendBusy(false) , mUseCircularSendBuffer(true) + , mUseTls(false) + , mTlsHandshakeComplete(false) , mBenchmarkBytesTotal(0) , mBenchmarkBytesUnsent(0) { + mEndpointAndCircularSendBuffer.mEndpoint = &mEndpoint; + mEndpointAndCircularSendBuffer.mSendBuffer = &mSendBuffer; } +#if OPENTHREAD_CONFIG_TLS_ENABLE +static int mbedtls_entropy_function(void *ctx, unsigned char *buffer, size_t length) +{ + OT_UNUSED_VARIABLE(ctx); + otError error = ot::Random::Crypto::FillBuffer(static_cast(buffer), static_cast(length)); + return (error == OT_ERROR_NONE) ? 0 : 1; +} + +void TcpExample::MbedTlsDebugOutput(void *ctx, int level, const char *file, int line, const char *str) +{ + TcpExample &tcpExample = *static_cast(ctx); + tcpExample.OutputLine("%s:%d:%d: %s", file, line, level, str); +} +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + template <> otError TcpExample::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; @@ -70,18 +94,84 @@ template <> otError TcpExample::Process(Arg aArgs[]) if (aArgs[0].IsEmpty()) { mUseCircularSendBuffer = true; + mUseTls = false; receiveBufferSize = sizeof(mReceiveBufferBytes); } else { - if (aArgs[0] == "circular") - { - mUseCircularSendBuffer = true; - } - else if (aArgs[0] == "linked") + if (aArgs[0] == "linked") { mUseCircularSendBuffer = false; + mUseTls = false; } + else if (aArgs[0] == "circular") + { + mUseCircularSendBuffer = true; + mUseTls = false; + } +#if OPENTHREAD_CONFIG_TLS_ENABLE + else if (aArgs[0] == "tls") + { + mUseCircularSendBuffer = true; + mUseTls = true; + + // mbedtls_debug_set_threshold(0); + + mbedtls_ctr_drbg_init(&mCtrDrbg); + mbedtls_x509_crt_init(&mSrvCert); + mbedtls_pk_init(&mPKey); + + mbedtls_ssl_init(&mSslContext); + mbedtls_ssl_config_init(&mSslConfig); + mbedtls_ssl_conf_rng(&mSslConfig, mbedtls_ctr_drbg_random, &mCtrDrbg); + // mbedtls_ssl_conf_dbg(&mSslConfig, MbedTlsDebugOutput, this); + mbedtls_ssl_conf_authmode(&mSslConfig, MBEDTLS_SSL_VERIFY_NONE); + mbedtls_entropy_init(&mEntropy); + +#if (MBEDTLS_VERSION_NUMBER >= 0x03020000) + mbedtls_ssl_conf_min_tls_version(&mSslConfig, MBEDTLS_SSL_VERSION_TLS1_2); + mbedtls_ssl_conf_max_tls_version(&mSslConfig, MBEDTLS_SSL_VERSION_TLS1_2); +#else + mbedtls_ssl_conf_min_version(&mSslConfig, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + mbedtls_ssl_conf_max_version(&mSslConfig, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); +#endif + +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) +#include "crypto/mbedtls.hpp" + int rv = mbedtls_pk_parse_key(&mPKey, reinterpret_cast(sSrvKey), sSrvKeyLength, + nullptr, 0, Crypto::MbedTls::CryptoSecurePrng, nullptr); +#else + int rv = mbedtls_pk_parse_key(&mPKey, reinterpret_cast(sSrvKey), sSrvKeyLength, + nullptr, 0); +#endif + if (rv != 0) + { + OutputLine("mbedtls_pk_parse_key returned %d", rv); + } + + rv = mbedtls_ctr_drbg_seed(&mCtrDrbg, mbedtls_entropy_function, nullptr, nullptr, 0); + if (rv != 0) + { + OutputLine("mbedtls_ctr_drbg_seed returned %d", rv); + } + + rv = mbedtls_x509_crt_parse(&mSrvCert, reinterpret_cast(sSrvPem), sSrvPemLength); + if (rv != 0) + { + OutputLine("mbedtls_x509_crt_parse (1) returned %d", rv); + } + rv = mbedtls_x509_crt_parse(&mSrvCert, reinterpret_cast(sCasPem), sCasPemLength); + if (rv != 0) + { + OutputLine("mbedtls_x509_crt_parse (2) returned %d", rv); + } + rv = mbedtls_ssl_setup(&mSslContext, &mSslConfig); + if (rv != 0) + { + OutputLine("mbedtls_ssl_setup returned %d", rv); + } + } +#endif // OPENTHREAD_CONFIG_TLS_ENABLE else { ExitNow(error = OT_ERROR_INVALID_ARGS); @@ -159,6 +249,19 @@ template <> otError TcpExample::Process(Arg aArgs[]) VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE); +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) + { + mbedtls_entropy_free(&mEntropy); + mbedtls_ssl_config_free(&mSslConfig); + mbedtls_ssl_free(&mSslContext); + + mbedtls_pk_free(&mPKey); + mbedtls_x509_crt_free(&mSrvCert); + mbedtls_ctr_drbg_free(&mCtrDrbg); + } +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + endpointError = otTcpEndpointDeinitialize(&mEndpoint); mSendBusy = false; @@ -212,6 +315,18 @@ template <> otError TcpExample::Process(Arg aArgs[]) SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort)); VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) + { + int rv = mbedtls_ssl_config_defaults(&mSslConfig, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (rv != 0) + { + OutputLine("mbedtls_ssl_config_defaults returned %d", rv); + } + } +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + SuccessOrExit(error = otTcpConnect(&mEndpoint, &sockaddr, OT_TCP_CONNECT_NO_FAST_OPEN)); mEndpointConnected = true; @@ -230,9 +345,24 @@ template <> otError TcpExample::Process(Arg aArgs[]) if (mUseCircularSendBuffer) { - size_t written; - SuccessOrExit(error = otTcpCircularSendBufferWrite(&mEndpoint, &mSendBuffer, aArgs[0].GetCString(), - aArgs[0].GetLength(), &written, 0)); +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) + { + int rv = mbedtls_ssl_write(&mSslContext, reinterpret_cast(aArgs[0].GetCString()), + aArgs[0].GetLength()); + if (rv < 0 && rv != MBEDTLS_ERR_SSL_WANT_WRITE && rv != MBEDTLS_ERR_SSL_WANT_READ) + { + ExitNow(error = kErrorFailed); + } + error = kErrorNone; + } + else +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + { + size_t written; + SuccessOrExit(error = otTcpCircularSendBufferWrite(&mEndpoint, &mSendBuffer, aArgs[0].GetCString(), + aArgs[0].GetLength(), &written, 0)); + } } else { @@ -442,6 +572,27 @@ void TcpExample::HandleTcpEstablished(otTcpEndpoint *aEndpoint) { OT_UNUSED_VARIABLE(aEndpoint); OutputLine("TCP: Connection established"); +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) + { + int rv; + rv = mbedtls_ssl_set_hostname(&mSslContext, "localhost"); + if (rv != 0) + { + OutputLine("mbedtls_ssl_set_hostname returned %d", rv); + } + rv = mbedtls_ssl_set_hs_ecjpake_password( + &mSslContext, reinterpret_cast(sEcjpakePassword), sEcjpakePasswordLength); + if (rv != 0) + { + OutputLine("mbedtls_ssl_set_hs_ecjpake_password returned %d", rv); + } + mbedtls_ssl_set_bio(&mSslContext, &mEndpointAndCircularSendBuffer, otTcpMbedTlsSslSendCallback, + otTcpMbedTlsSslRecvCallback, nullptr); + mTlsHandshakeComplete = false; + ContinueTLSHandshake(); + } +#endif // OPENTHREAD_CONFIG_TLS_ENABLE } void TcpExample::HandleTcpSendDone(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData) @@ -488,6 +639,13 @@ void TcpExample::HandleTcpForwardProgress(otTcpEndpoint *aEndpoint, size_t aInSe otTcpCircularSendBufferHandleForwardProgress(&mSendBuffer, aInSendBuffer); +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) + { + ContinueTLSHandshake(); + } +#endif + /* Handle case where we're in a benchmark. */ if (mBenchmarkBytesTotal != 0) { @@ -512,20 +670,53 @@ void TcpExample::HandleTcpReceiveAvailable(otTcpEndpoint *aEndpoint, OT_UNUSED_VARIABLE(aBytesRemaining); OT_ASSERT(aEndpoint == &mEndpoint); - if (aBytesAvailable > 0) +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls && ContinueTLSHandshake()) { - const otLinkedBuffer *data; - size_t totalReceived = 0; + return; + } +#endif - IgnoreError(otTcpReceiveByReference(aEndpoint, &data)); - for (; data != nullptr; data = data->mNext) + if ((mTlsHandshakeComplete || !mUseTls) && aBytesAvailable > 0) + { +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) { - OutputLine("TCP: Received %u bytes: %.*s", static_cast(data->mLength), - static_cast(data->mLength), reinterpret_cast(data->mData)); - totalReceived += data->mLength; + uint8_t buffer[500]; + for (;;) + { + int rv = mbedtls_ssl_read(&mSslContext, buffer, sizeof(buffer)); + if (rv < 0) + { + if (rv == MBEDTLS_ERR_SSL_WANT_READ) + { + break; + } + OutputLine("TLS receive failure: %d", rv); + } + else + { + OutputLine("TLS: Received %u bytes: %.*s", static_cast(rv), rv, + reinterpret_cast(buffer)); + } + } + OutputLine("(TCP: Received %u bytes)", static_cast(aBytesAvailable)); + } + else +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + { + const otLinkedBuffer *data; + size_t totalReceived = 0; + IgnoreError(otTcpReceiveByReference(aEndpoint, &data)); + for (; data != nullptr; data = data->mNext) + { + OutputLine("TCP: Received %u bytes: %.*s", static_cast(data->mLength), + static_cast(data->mLength), reinterpret_cast(data->mData)); + totalReceived += data->mLength; + } + OT_ASSERT(aBytesAvailable == totalReceived); + IgnoreReturnValue(otTcpCommitReceive(aEndpoint, totalReceived, 0)); } - OT_ASSERT(aBytesAvailable == totalReceived); - IgnoreReturnValue(otTcpCommitReceive(aEndpoint, totalReceived, 0)); } if (aEndOfStream) @@ -554,6 +745,13 @@ void TcpExample::HandleTcpDisconnected(otTcpEndpoint *aEndpoint, otTcpDisconnect OutputLine("TCP: %s", Stringify(aReason, kReasonStrings)); +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) + { + mbedtls_ssl_session_reset(&mSslContext); + } +#endif + // We set this to false even for the TIME-WAIT state, so that we can reuse // the active socket if an incoming connection comes in instead of waiting // for the 2MSL timeout. @@ -599,6 +797,26 @@ void TcpExample::HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aE mEndpointConnected = true; OutputFormat("Accepted connection from "); OutputSockAddrLine(*aPeer); + +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) + { + int rv; + + rv = mbedtls_ssl_config_defaults(&mSslConfig, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (rv != 0) + { + OutputLine("mbedtls_ssl_config_defaults returned %d", rv); + } + mbedtls_ssl_conf_ca_chain(&mSslConfig, mSrvCert.next, nullptr); + rv = mbedtls_ssl_conf_own_cert(&mSslConfig, &mSrvCert, &mPKey); + if (rv != 0) + { + OutputLine("mbedtls_ssl_conf_own_cert returned %d", rv); + } + } +#endif // OPENTHREAD_CONFIG_TLS_ENABLE } otError TcpExample::ContinueBenchmarkCircularSend(void) @@ -612,10 +830,30 @@ otError TcpExample::ContinueBenchmarkCircularSend(void) uint32_t flag = (toSendThisIteration < freeSpace && toSendThisIteration < mBenchmarkBytesUnsent) ? OT_TCP_CIRCULAR_SEND_BUFFER_WRITE_MORE_TO_COME : 0; - size_t written; + size_t written = 0; - SuccessOrExit(error = otTcpCircularSendBufferWrite(&mEndpoint, &mSendBuffer, sBenchmarkData, - toSendThisIteration, &written, flag)); +#if OPENTHREAD_CONFIG_TLS_ENABLE + if (mUseTls) + { + int rv = mbedtls_ssl_write(&mSslContext, reinterpret_cast(sBenchmarkData), + toSendThisIteration); + if (rv > 0) + { + written = static_cast(rv); + OT_ASSERT(written <= mBenchmarkBytesUnsent); + } + else if (rv != MBEDTLS_ERR_SSL_WANT_WRITE && rv != MBEDTLS_ERR_SSL_WANT_READ) + { + ExitNow(error = kErrorFailed); + } + error = kErrorNone; + } + else +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + { + SuccessOrExit(error = otTcpCircularSendBufferWrite(&mEndpoint, &mSendBuffer, sBenchmarkData, + toSendThisIteration, &written, flag)); + } mBenchmarkBytesUnsent -= written; } @@ -642,6 +880,31 @@ void TcpExample::CompleteBenchmark(void) mBenchmarkBytesTotal = 0; } +#if OPENTHREAD_CONFIG_TLS_ENABLE +bool TcpExample::ContinueTLSHandshake(void) +{ + bool wasNotAlreadyDone = false; + int rv; + + if (!mTlsHandshakeComplete) + { + rv = mbedtls_ssl_handshake(&mSslContext); + if (rv == 0) + { + OutputLine("TLS Handshake Complete"); + mTlsHandshakeComplete = true; + } + else if (rv != MBEDTLS_ERR_SSL_WANT_READ && rv != MBEDTLS_ERR_SSL_WANT_WRITE) + { + OutputLine("TLS Handshake Failed: %d", rv); + } + wasNotAlreadyDone = true; + } + + return wasNotAlreadyDone; +} +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + } // namespace Cli } // namespace ot diff --git a/src/cli/cli_tcp.hpp b/src/cli/cli_tcp.hpp index 53f890715..7e7f62d48 100644 --- a/src/cli/cli_tcp.hpp +++ b/src/cli/cli_tcp.hpp @@ -39,6 +39,15 @@ #include #include +#if OPENTHREAD_CONFIG_TLS_ENABLE + +#include +#include +#include +#include + +#endif + #include "cli/cli_config.h" #include "cli/cli_output.hpp" #include "common/time.hpp" @@ -80,6 +89,10 @@ private: otError ContinueBenchmarkCircularSend(void); void CompleteBenchmark(void); +#if OPENTHREAD_CONFIG_TLS_ENABLE + bool ContinueTLSHandshake(void); +#endif + static void HandleTcpEstablishedCallback(otTcpEndpoint *aEndpoint); static void HandleTcpSendDoneCallback(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData); static void HandleTcpForwardProgressCallback(otTcpEndpoint *aEndpoint, size_t aInSendBuffer, size_t aBacklog); @@ -108,6 +121,10 @@ private: otTcpEndpoint **aAcceptInto); void HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aEndpoint, const otSockAddr *aPeer); +#if OPENTHREAD_CONFIG_TLS_ENABLE + static void MbedTlsDebugOutput(void *ctx, int level, const char *file, int line, const char *str); +#endif + otTcpEndpoint mEndpoint; otTcpListener mListener; @@ -115,6 +132,8 @@ private: bool mEndpointConnected; bool mSendBusy; bool mUseCircularSendBuffer; + bool mUseTls; + bool mTlsHandshakeComplete; otTcpCircularSendBuffer mSendBuffer; otLinkedBuffer mSendLink; @@ -127,8 +146,71 @@ private: uint32_t mBenchmarkBytesUnsent; TimeMilli mBenchmarkStart; - static constexpr const char *sBenchmarkData = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - static constexpr const size_t sBenchmarkDataLength = 52; + otTcpEndpointAndCircularSendBuffer mEndpointAndCircularSendBuffer; + +#if OPENTHREAD_CONFIG_TLS_ENABLE + mbedtls_ssl_context mSslContext; + mbedtls_ssl_config mSslConfig; + mbedtls_ctr_drbg_context mCtrDrbg; + mbedtls_x509_crt mSrvCert; + mbedtls_pk_context mPKey; + mbedtls_entropy_context mEntropy; +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + + static constexpr const char *sBenchmarkData = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + static constexpr const size_t sBenchmarkDataLength = 1040; + +#if OPENTHREAD_CONFIG_TLS_ENABLE + static constexpr const char *sCasPem = "-----BEGIN CERTIFICATE-----\r\n" + "MIIBtDCCATqgAwIBAgIBTTAKBggqhkjOPQQDAjBLMQswCQYDVQQGEwJOTDERMA8G\r\n" + "A1UEChMIUG9sYXJTU0wxKTAnBgNVBAMTIFBvbGFyU1NMIFRlc3QgSW50ZXJtZWRp\r\n" + "YXRlIEVDIENBMB4XDTE1MDkwMTE0MDg0M1oXDTI1MDgyOTE0MDg0M1owSjELMAkG\r\n" + "A1UEBhMCVUsxETAPBgNVBAoTCG1iZWQgVExTMSgwJgYDVQQDEx9tYmVkIFRMUyBU\r\n" + "ZXN0IGludGVybWVkaWF0ZSBDQSAzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\r\n" + "732fWHLNPMPsP1U1ibXvb55erlEVMlpXBGsj+KYwVqU1XCmW9Z9hhP7X/5js/DX9\r\n" + "2J/utoHyjUtVpQOzdTrbsaMQMA4wDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAgNo\r\n" + "ADBlAjAJRxbGRas3NBmk9MnGWXg7PT1xnRELHRWWIvfLdVQt06l1/xFg3ZuPdQdt\r\n" + "Qh7CK80CMQD7wa1o1a8qyDKBfLN636uKmKGga0E+vYXBeFCy9oARBangGCB0B2vt\r\n" + "pz590JvGWfM=\r\n" + "-----END CERTIFICATE-----\r\n"; + static constexpr const size_t sCasPemLength = 665; // includes NUL byte + + static constexpr const char *sSrvPem = "-----BEGIN CERTIFICATE-----\r\n" + "MIICHzCCAaWgAwIBAgIBCTAKBggqhkjOPQQDAjA+MQswCQYDVQQGEwJOTDERMA8G\r\n" + "A1UEChMIUG9sYXJTU0wxHDAaBgNVBAMTE1BvbGFyc3NsIFRlc3QgRUMgQ0EwHhcN\r\n" + "MTMwOTI0MTU1MjA0WhcNMjMwOTIyMTU1MjA0WjA0MQswCQYDVQQGEwJOTDERMA8G\r\n" + "A1UEChMIUG9sYXJTU0wxEjAQBgNVBAMTCWxvY2FsaG9zdDBZMBMGByqGSM49AgEG\r\n" + "CCqGSM49AwEHA0IABDfMVtl2CR5acj7HWS3/IG7ufPkGkXTQrRS192giWWKSTuUA\r\n" + "2CMR/+ov0jRdXRa9iojCa3cNVc2KKg76Aci07f+jgZ0wgZowCQYDVR0TBAIwADAd\r\n" + "BgNVHQ4EFgQUUGGlj9QH2deCAQzlZX+MY0anE74wbgYDVR0jBGcwZYAUnW0gJEkB\r\n" + "PyvLeLUZvH4kydv7NnyhQqRAMD4xCzAJBgNVBAYTAk5MMREwDwYDVQQKEwhQb2xh\r\n" + "clNTTDEcMBoGA1UEAxMTUG9sYXJzc2wgVGVzdCBFQyBDQYIJAMFD4n5iQ8zoMAoG\r\n" + "CCqGSM49BAMCA2gAMGUCMQCaLFzXptui5WQN8LlO3ddh1hMxx6tzgLvT03MTVK2S\r\n" + "C12r0Lz3ri/moSEpNZWqPjkCMCE2f53GXcYLqyfyJR078c/xNSUU5+Xxl7VZ414V\r\n" + "fGa5kHvHARBPc8YAIVIqDvHH1Q==\r\n" + "-----END CERTIFICATE-----\r\n"; + static constexpr const size_t sSrvPemLength = 813; // includes NUL byte + + static constexpr const char *sSrvKey = "-----BEGIN EC PRIVATE KEY-----\r\n" + "MHcCAQEEIPEqEyB2AnCoPL/9U/YDHvdqXYbIogTywwyp6/UfDw6noAoGCCqGSM49\r\n" + "AwEHoUQDQgAEN8xW2XYJHlpyPsdZLf8gbu58+QaRdNCtFLX3aCJZYpJO5QDYIxH/\r\n" + "6i/SNF1dFr2KiMJrdw1VzYoqDvoByLTt/w==\r\n" + "-----END EC PRIVATE KEY-----\r\n"; + static constexpr const size_t sSrvKeyLength = 233; // includes NUL byte + + static constexpr const char *sEcjpakePassword = "TLS-over-TCPlp"; + static constexpr const size_t sEcjpakePasswordLength = 14; +#endif // OPENTHREAD_CONFIG_TLS_ENABLE }; } // namespace Cli diff --git a/src/core/api/tcp_ext_api.cpp b/src/core/api/tcp_ext_api.cpp index ce69183fb..c6540662c 100644 --- a/src/core/api/tcp_ext_api.cpp +++ b/src/core/api/tcp_ext_api.cpp @@ -41,6 +41,12 @@ #include "common/locator_getters.hpp" #include "net/tcp6_ext.hpp" +#if OPENTHREAD_CONFIG_TLS_ENABLE + +#include + +#endif + using namespace ot; void otTcpCircularSendBufferInitialize(otTcpCircularSendBuffer *aSendBuffer, void *aDataBuffer, size_t aCapacity) @@ -79,4 +85,52 @@ otError otTcpCircularSendBufferDeinitialize(otTcpCircularSendBuffer *aSendBuffer return AsCoreType(aSendBuffer).Deinitialize(); } +#if OPENTHREAD_CONFIG_TLS_ENABLE + +int otTcpMbedTlsSslSendCallback(void *aCtx, const unsigned char *aBuf, size_t aLen) +{ + otTcpEndpointAndCircularSendBuffer *pair = static_cast(aCtx); + otTcpEndpoint *endpoint = pair->mEndpoint; + otTcpCircularSendBuffer *sendBuffer = pair->mSendBuffer; + size_t bytes_written; + int result; + otError error; + + error = otTcpCircularSendBufferWrite(endpoint, sendBuffer, aBuf, aLen, &bytes_written, 0); + VerifyOrExit(error == OT_ERROR_NONE, result = MBEDTLS_ERR_SSL_INTERNAL_ERROR); + VerifyOrExit(aLen == 0 || bytes_written != 0, result = MBEDTLS_ERR_SSL_WANT_WRITE); + result = static_cast(bytes_written); + +exit: + return result; +} + +int otTcpMbedTlsSslRecvCallback(void *aCtx, unsigned char *aBuf, size_t aLen) +{ + otTcpEndpointAndCircularSendBuffer *pair = static_cast(aCtx); + otTcpEndpoint *endpoint = pair->mEndpoint; + size_t bytes_read = 0; + const otLinkedBuffer *buffer; + int result; + otError error; + + error = otTcpReceiveByReference(endpoint, &buffer); + VerifyOrExit(error == OT_ERROR_NONE, result = MBEDTLS_ERR_SSL_INTERNAL_ERROR); + while (bytes_read != aLen && buffer != nullptr) + { + size_t to_copy = OT_MIN(aLen - bytes_read, buffer->mLength); + memcpy(&aBuf[bytes_read], buffer->mData, to_copy); + bytes_read += to_copy; + buffer = buffer->mNext; + } + VerifyOrExit(aLen == 0 || bytes_read != 0, result = MBEDTLS_ERR_SSL_WANT_READ); + IgnoreReturnValue(otTcpCommitReceive(endpoint, bytes_read, 0)); + result = static_cast(bytes_read); + +exit: + return result; +} + +#endif // OPENTHREAD_CONFIG_TLS_ENABLE + #endif // OPENTHREAD_CONFIG_TCP_ENABLE diff --git a/src/core/config/ip6.h b/src/core/config/ip6.h index a9498b5f4..75976e1a1 100644 --- a/src/core/config/ip6.h +++ b/src/core/config/ip6.h @@ -172,6 +172,16 @@ #define OPENTHREAD_CONFIG_TCP_ENABLE 1 #endif +/** + * @def OPENTHREAD_CONFIG_TLS_ENABLE + * + * Define as 1 to enable support for TLS over TCP. + * + */ +#if OPENTHREAD_CONFIG_TCP_ENABLE && !defined(OPENTHREAD_CONFIG_TLS_ENABLE) +#define OPENTHREAD_CONFIG_TLS_ENABLE 1 +#endif + /** * @def OPENTHREAD_CONFIG_IP6_ALLOW_LOOP_BACK_HOST_DATAGRAMS * diff --git a/src/core/utils/heap.hpp b/src/core/utils/heap.hpp index 6fd75dc9d..bd391a97d 100644 --- a/src/core/utils/heap.hpp +++ b/src/core/utils/heap.hpp @@ -227,7 +227,7 @@ public: size_t GetFreeSize(void) const { return mMemory.mFreeSize; } private: -#if OPENTHREAD_CONFIG_DTLS_ENABLE +#if OPENTHREAD_CONFIG_TLS_ENABLE || OPENTHREAD_CONFIG_DTLS_ENABLE static constexpr uint16_t kMemorySize = OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE; #else static constexpr uint16_t kMemorySize = OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS; diff --git a/tests/scripts/expect/cli-tcp-tls.exp b/tests/scripts/expect/cli-tcp-tls.exp new file mode 100644 index 000000000..acc06258f --- /dev/null +++ b/tests/scripts/expect/cli-tcp-tls.exp @@ -0,0 +1,165 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2022, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +source "tests/scripts/expect/_common.exp" +source "tests/scripts/expect/_multinode.exp" + +setup_two_nodes + +switch_node 1 +send "tcp init tls\n" +expect_line "Done" + +switch_node 2 +send "tcp init tls\n" +expect_line "Done" +set addr_2 [get_ipaddr mleid] +send "tcp listen :: 30000\n" +expect_line "Done" +send "tcp stoplistening\n" +expect_line "Done" + +switch_node 1 +send "tcp connect $addr_2 30000\n" +expect_line "Done" +expect "TCP: Connection refused" + +switch_node 2 +send "tcp listen :: 30000\n" +expect_line "Done" + +switch_node 1 +set addr_1 [get_ipaddr mleid] +send "tcp bind $addr_1 25000\n" +expect_line "Done" +send "tcp connect $addr_2 30000\n" +expect_line "Done" +expect "TCP: Connection established" +expect "TLS Handshake Complete" + +switch_node 2 +expect "Accepted connection from \\\[$addr_1\\\]:25000" +expect "TCP: Connection established" +expect "TLS Handshake Complete" + +switch_node 1 +send "tcp send hello\n" +expect_line "Done" + +switch_node 2 +expect "TLS: Received 5 bytes: hello" +expect "(TCP: Received 26 bytes)" +send "tcp send world\n" +expect_line "Done" + +switch_node 1 +expect "TLS: Received 5 bytes: world" +expect "(TCP: Received 26 bytes)" +send "tcp sendend\n" +expect_line "Done" +send "tcp send more\n" +expect_line "Error 1: Failed" + +switch_node 2 +expect "TCP: Reached end of stream" +send "tcp send goodbye\n" +expect_line "Done" + +switch_node 1 +expect "TLS: Received 7 bytes: goodbye" +expect "(TCP: Received 28 bytes)" + +switch_node 2 +send "tcp sendend\n" +expect_line "Done" +expect "TCP: Disconnected" + +switch_node 1 +expect "TCP: Reached end of stream" +expect "TCP: Entered TIME-WAIT state" +set timeout 130 +expect "TCP: Disconnected" + +# Test second connection with roles reversed (to be sure we're managing TLS state correctly) + +send "tcp listen :: 30000\n" +expect_line "Done" + +switch_node 2 +send "tcp bind $addr_2 25000\n" +expect_line "Done" +send "tcp connect $addr_1 30000\n" +expect_line "Done" +expect "TCP: Connection established" +expect "TLS Handshake Complete" + +switch_node 1 +expect "Accepted connection from \\\[$addr_2\\\]:25000" +expect "TCP: Connection established" +expect "TLS Handshake Complete" + +switch_node 2 +send "tcp send hello\n" +expect_line "Done" + +switch_node 1 +expect "TLS: Received 5 bytes: hello" +expect "(TCP: Received 26 bytes)" +send "tcp send world\n" +expect_line "Done" + +switch_node 2 +expect "TLS: Received 5 bytes: world" +expect "(TCP: Received 26 bytes)" +send "tcp sendend\n" +expect_line "Done" +send "tcp send more\n" +expect_line "Error 1: Failed" + +switch_node 1 +expect "TCP: Reached end of stream" +send "tcp send goodbye\n" +expect_line "Done" + +switch_node 2 +expect "TLS: Received 7 bytes: goodbye" +expect "(TCP: Received 28 bytes)" + +switch_node 1 +send "tcp sendend\n" +expect_line "Done" +expect "TCP: Disconnected" + +switch_node 2 +expect "TCP: Reached end of stream" +expect "TCP: Entered TIME-WAIT state" +set timeout 130 +expect "TCP: Disconnected" + +dispose_all diff --git a/third_party/mbedtls/mbedtls-config.h b/third_party/mbedtls/mbedtls-config.h index 131aaafea..ffb50df14 100644 --- a/third_party/mbedtls/mbedtls-config.h +++ b/third_party/mbedtls/mbedtls-config.h @@ -86,6 +86,9 @@ #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE #define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#endif + +#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE || OPENTHREAD_CONFIG_TLS_ENABLE #define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED #endif