diff --git a/.gitmodules b/.gitmodules index 9665dc27..23aeefb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,7 @@ [submodule "third_party/coreMQTT"] path = third_party/coreMQTT url = https://github.com/FreeRTOS/coreMQTT +[submodule "third_party/wolfssl"] + path = third_party/wolfssl + url = https://github.com/The-Capable-Hub/wolfssl.git + branch = wbeasley/cheriot-changes diff --git a/README.md b/README.md index a353db8b..fe867ce3 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The network stack includes components from a variety of third parties: - [FreeRTOS coreSNTP](https://github.com/FreeRTOS/coreSNTP) provides the SNTP client. - [FreeRTOS coreMQTT](https://github.com/FreeRTOS/coreMQTT) provides the MQTT client. - [BearSSL](https://www.bearssl.org) provides the TLS 1.2 stack. + - [wolfSSL](https://www.wolfssl.com) provides an alternative TLS stack (TLS 1.3 capable). This demonstrates the CHERIoT platform's ability to adopt existing codebases. We are building around 100 KLoC of third-party code into this stack. @@ -39,6 +40,14 @@ Four are mostly existing third-party code with thin wrappers: - The TLS stack is, again, mostly unmodified BearSSL code, with just some thin wrappers added around the edges. - The MQTT compartment, like the SNTP compartment, is just another consumer of the network stack (the TLS layer, specifically) and provides a simple interface for connecting to MQTT servers, publishing messages and receiving notifications of publish events. +Two additional compartments provide an alternative TLS stack based on wolfSSL (TLS 1.3): + + - The `WolfSSLTLS` compartment wraps wolfSSL and mirrors the BearSSL TLS compartment's interface. + - The `WolfSSLMQTT` compartment provides a wolfSSL-backed MQTT client equivalent to the existing BearSSL `MQTT` compartment. + +Examples `06.HTTPS-wolfssl`, `07.HTTPS-wolfssl-concurrent`, and `08.MQTT-wolfssl` demonstrate these compartments. +See [`tests/wolfssl/wolfcrypt/README.md`](tests/wolfssl/wolfcrypt/README.md) for the wolfCrypt test and benchmark application. + These are joined by three new compartments: - The firewall compartment is the only thing that talks directly to the network device. @@ -84,7 +93,7 @@ It is fault-tolerant: when an error is triggered (CHERI spatial or temporal safe We expand on this capability [below](#automatic-restart-of-the-tcpip-stack). Unlike the TCP/IP stack, the TLS compartment is almost completely stateless. -This makes resetting the compartment trivial, and gives strong flow isolation properties: Even if an attacker compromises the TLS compartment by sending malicious data over one connection that triggers a bug in BearSSL (unlikely), it is extraordinarily difficult for them to interfere with any other TLS connection. +This makes resetting the compartment trivial, and gives strong flow isolation properties: Even if an attacker compromises the TLS compartment by sending malicious data over one connection that triggers a bug in the TLS library (unlikely), it is extraordinarily difficult for them to interfere with any other TLS connection. All inbound and outbound data go through the on-device firewall, which is controlled by the Network API compartment. The TCP/IP stack has no access to the NetAPI control-plane interface. diff --git a/THIRD_PARTY_NOTICES.txt b/THIRD_PARTY_NOTICES.txt index 5ef905bd..9bf9540e 100644 --- a/THIRD_PARTY_NOTICES.txt +++ b/THIRD_PARTY_NOTICES.txt @@ -6,6 +6,7 @@ This repository includes several submodules with their own licenses: - FreeRTOS+TCP (third_party/freertos-plus-tcp/LICENSE.md) - coreMQTT (MIT, see third_party/coreMQTT/LICENSE) - coreSNTP (MIT, see third_party/coreSNTP/LICENSE) + - wolfSSL (GPLv3 or commercial license, see third_party/wolfSSL/LICENSING) New code in this repository is covered by the MIT license, as listed in LICENSE and in per-file copyright headers. diff --git a/examples/06.HTTPS-wolfssl/Comodo_AAA_Services_root.h b/examples/06.HTTPS-wolfssl/Comodo_AAA_Services_root.h new file mode 100644 index 00000000..56ecaaed --- /dev/null +++ b/examples/06.HTTPS-wolfssl/Comodo_AAA_Services_root.h @@ -0,0 +1,103 @@ +// Comodo AAA Certificate Services root CA self-signed, valid 2004–2028 (used by example.com) +// self-signed RSA-SHA1 +// +// To regen: +// 1. Get the root cert from the system trust store: +// cp /etc/ssl/certs/Comodo_AAA_Services_root.pem comodo_aaa.pem +// 2. Convert PEM → DER: +// openssl x509 -in comodo_aaa.pem -outform DER -out comodo_aaa.der +// 3. Convert to C byte array: +// xxd -i comodo_aaa.der | sed -e '1s/.*/static const unsigned char COMODO_AAA_SERVICES_ROOT_DER[] = {/'-e '/^unsigned int/d' +// +static const unsigned char COMODO_AAA_SERVICES_ROOT_DER[] = { + 0x30, 0x82, 0x04, 0x32, 0x30, 0x82, 0x03, 0x1a, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x01, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x7b, 0x31, 0x0b, 0x30, + 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, + 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x12, 0x47, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, + 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, + 0x0c, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, + 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x11, 0x43, 0x6f, 0x6d, 0x6f, + 0x64, 0x6f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, + 0x64, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x18, + 0x41, 0x41, 0x41, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x34, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x31, 0x32, 0x33, + 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x7b, 0x31, 0x0b, + 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, + 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x12, 0x47, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, + 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, + 0x07, 0x0c, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, + 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x11, 0x43, 0x6f, 0x6d, + 0x6f, 0x64, 0x6f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, + 0x65, 0x64, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x18, 0x41, 0x41, 0x41, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, + 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbe, 0x40, + 0x9d, 0xf4, 0x6e, 0xe1, 0xea, 0x76, 0x87, 0x1c, 0x4d, 0x45, 0x44, 0x8e, + 0xbe, 0x46, 0xc8, 0x83, 0x06, 0x9d, 0xc1, 0x2a, 0xfe, 0x18, 0x1f, 0x8e, + 0xe4, 0x02, 0xfa, 0xf3, 0xab, 0x5d, 0x50, 0x8a, 0x16, 0x31, 0x0b, 0x9a, + 0x06, 0xd0, 0xc5, 0x70, 0x22, 0xcd, 0x49, 0x2d, 0x54, 0x63, 0xcc, 0xb6, + 0x6e, 0x68, 0x46, 0x0b, 0x53, 0xea, 0xcb, 0x4c, 0x24, 0xc0, 0xbc, 0x72, + 0x4e, 0xea, 0xf1, 0x15, 0xae, 0xf4, 0x54, 0x9a, 0x12, 0x0a, 0xc3, 0x7a, + 0xb2, 0x33, 0x60, 0xe2, 0xda, 0x89, 0x55, 0xf3, 0x22, 0x58, 0xf3, 0xde, + 0xdc, 0xcf, 0xef, 0x83, 0x86, 0xa2, 0x8c, 0x94, 0x4f, 0x9f, 0x68, 0xf2, + 0x98, 0x90, 0x46, 0x84, 0x27, 0xc7, 0x76, 0xbf, 0xe3, 0xcc, 0x35, 0x2c, + 0x8b, 0x5e, 0x07, 0x64, 0x65, 0x82, 0xc0, 0x48, 0xb0, 0xa8, 0x91, 0xf9, + 0x61, 0x9f, 0x76, 0x20, 0x50, 0xa8, 0x91, 0xc7, 0x66, 0xb5, 0xeb, 0x78, + 0x62, 0x03, 0x56, 0xf0, 0x8a, 0x1a, 0x13, 0xea, 0x31, 0xa3, 0x1e, 0xa0, + 0x99, 0xfd, 0x38, 0xf6, 0xf6, 0x27, 0x32, 0x58, 0x6f, 0x07, 0xf5, 0x6b, + 0xb8, 0xfb, 0x14, 0x2b, 0xaf, 0xb7, 0xaa, 0xcc, 0xd6, 0x63, 0x5f, 0x73, + 0x8c, 0xda, 0x05, 0x99, 0xa8, 0x38, 0xa8, 0xcb, 0x17, 0x78, 0x36, 0x51, + 0xac, 0xe9, 0x9e, 0xf4, 0x78, 0x3a, 0x8d, 0xcf, 0x0f, 0xd9, 0x42, 0xe2, + 0x98, 0x0c, 0xab, 0x2f, 0x9f, 0x0e, 0x01, 0xde, 0xef, 0x9f, 0x99, 0x49, + 0xf1, 0x2d, 0xdf, 0xac, 0x74, 0x4d, 0x1b, 0x98, 0xb5, 0x47, 0xc5, 0xe5, + 0x29, 0xd1, 0xf9, 0x90, 0x18, 0xc7, 0x62, 0x9c, 0xbe, 0x83, 0xc7, 0x26, + 0x7b, 0x3e, 0x8a, 0x25, 0xc7, 0xc0, 0xdd, 0x9d, 0xe6, 0x35, 0x68, 0x10, + 0x20, 0x9d, 0x8f, 0xd8, 0xde, 0xd2, 0xc3, 0x84, 0x9c, 0x0d, 0x5e, 0xe8, + 0x2f, 0xc9, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xc0, 0x30, 0x81, + 0xbd, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, + 0xa0, 0x11, 0x0a, 0x23, 0x3e, 0x96, 0xf1, 0x07, 0xec, 0xe2, 0xaf, 0x29, + 0xef, 0x82, 0xa5, 0x7f, 0xd0, 0x30, 0xa4, 0xb4, 0x30, 0x0e, 0x06, 0x03, + 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, + 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, + 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, + 0x04, 0x74, 0x30, 0x72, 0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, + 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x41, 0x41, 0x41, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, + 0x72, 0x6c, 0x30, 0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, + 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, + 0x6f, 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x41, 0x41, 0x41, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, + 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x08, 0x56, 0xfc, 0x02, 0xf0, 0x9b, + 0xe8, 0xff, 0xa4, 0xfa, 0xd6, 0x7b, 0xc6, 0x44, 0x80, 0xce, 0x4f, 0xc4, + 0xc5, 0xf6, 0x00, 0x58, 0xcc, 0xa6, 0xb6, 0xbc, 0x14, 0x49, 0x68, 0x04, + 0x76, 0xe8, 0xe6, 0xee, 0x5d, 0xec, 0x02, 0x0f, 0x60, 0xd6, 0x8d, 0x50, + 0x18, 0x4f, 0x26, 0x4e, 0x01, 0xe3, 0xe6, 0xb0, 0xa5, 0xee, 0xbf, 0xbc, + 0x74, 0x54, 0x41, 0xbf, 0xfd, 0xfc, 0x12, 0xb8, 0xc7, 0x4f, 0x5a, 0xf4, + 0x89, 0x60, 0x05, 0x7f, 0x60, 0xb7, 0x05, 0x4a, 0xf3, 0xf6, 0xf1, 0xc2, + 0xbf, 0xc4, 0xb9, 0x74, 0x86, 0xb6, 0x2d, 0x7d, 0x6b, 0xcc, 0xd2, 0xf3, + 0x46, 0xdd, 0x2f, 0xc6, 0xe0, 0x6a, 0xc3, 0xc3, 0x34, 0x03, 0x2c, 0x7d, + 0x96, 0xdd, 0x5a, 0xc2, 0x0e, 0xa7, 0x0a, 0x99, 0xc1, 0x05, 0x8b, 0xab, + 0x0c, 0x2f, 0xf3, 0x5c, 0x3a, 0xcf, 0x6c, 0x37, 0x55, 0x09, 0x87, 0xde, + 0x53, 0x40, 0x6c, 0x58, 0xef, 0xfc, 0xb6, 0xab, 0x65, 0x6e, 0x04, 0xf6, + 0x1b, 0xdc, 0x3c, 0xe0, 0x5a, 0x15, 0xc6, 0x9e, 0xd9, 0xf1, 0x59, 0x48, + 0x30, 0x21, 0x65, 0x03, 0x6c, 0xec, 0xe9, 0x21, 0x73, 0xec, 0x9b, 0x03, + 0xa1, 0xe0, 0x37, 0xad, 0xa0, 0x15, 0x18, 0x8f, 0xfa, 0xba, 0x02, 0xce, + 0xa7, 0x2c, 0xa9, 0x10, 0x13, 0x2c, 0xd4, 0xe5, 0x08, 0x26, 0xab, 0x22, + 0x97, 0x60, 0xf8, 0x90, 0x5e, 0x74, 0xd4, 0xa2, 0x9a, 0x53, 0xbd, 0xf2, + 0xa9, 0x68, 0xe0, 0xa2, 0x6e, 0xc2, 0xd7, 0x6c, 0xb1, 0xa3, 0x0f, 0x9e, + 0xbf, 0xeb, 0x68, 0xe7, 0x56, 0xf2, 0xae, 0xf2, 0xe3, 0x2b, 0x38, 0x3a, + 0x09, 0x81, 0xb5, 0x6b, 0x85, 0xd7, 0xbe, 0x2d, 0xed, 0x3f, 0x1a, 0xb7, + 0xb2, 0x63, 0xe2, 0xf5, 0x62, 0x2c, 0x82, 0xd4, 0x6a, 0x00, 0x41, 0x50, + 0xf1, 0x39, 0x83, 0x9f, 0x95, 0xe9, 0x36, 0x96, 0x98, 0x6e +}; diff --git a/examples/06.HTTPS-wolfssl/https_wolfssl.cc b/examples/06.HTTPS-wolfssl/https_wolfssl.cc new file mode 100644 index 00000000..39d9e4ad --- /dev/null +++ b/examples/06.HTTPS-wolfssl/https_wolfssl.cc @@ -0,0 +1,122 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT +// +// wolfSSL HTTPS example, mirrors examples/03.HTTPS/https.cc but uses the WolfSSLTLS compartment instead of BearSSL TLS. +// + +#include +#if __has_include() +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Comodo_AAA_Services_root.h" + +using Debug = ConditionalDebug; +constexpr bool UseIPv6 = CHERIOT_RTOS_OPTION_IPv6; + +DECLARE_AND_DEFINE_CONNECTION_CAPABILITY(ExampleComTLS, + "example.com", + 443, + ConnectionTypeTCP); + +DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(TestMalloc, 32 * 1024); +#define TEST_MALLOC STATIC_SEALED_VALUE(TestMalloc) + +void __cheri_compartment("https_wolfssl_example") example() +{ + Debug::log("Waiting for network..."); + network_start(); + + // Cert date validation requires correct time. + { + Timeout t{MS_TO_TICKS(5000)}; + while (sntp_update(&t) != 0) + { + Debug::log("Waiting for NTP..."); + t = Timeout{MS_TO_TICKS(5000)}; + } + } + + Debug::log("Attempting to connect to example.com:443 via wolfSSL TLS 1.3"); + + static const WolfSSLTrustAnchor trustAnchors[] = { + {COMODO_AAA_SERVICES_ROOT_DER, sizeof(COMODO_AAA_SERVICES_ROOT_DER)}, + }; + + Timeout unlimited{UnlimitedTimeout}; + auto tlsConn = wolftls_connection_create( + &unlimited, + TEST_MALLOC, + CONNECTION_CAPABILITY(ExampleComTLS), + trustAnchors, + 1); + + if (!__builtin_cheri_tag_get(tlsConn)) + { + Debug::log("Failed to create TLS connection"); + return; + } + Debug::log("TLS handshake complete"); + + static char request[] = "GET / HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: cheriot-wolfssl\r\n" + "Connection: close\r\n" + "Accept: */*\r\n" + "\r\n"; + constexpr size_t toSend = sizeof(request) - 1; + size_t sent = 0; + while (sent < toSend) + { + ssize_t r = wolftls_connection_send( + &unlimited, tlsConn, &request[sent], toSend - sent); + if (r > 0) + sent += r; + else + { + Debug::log("Send failed: {}", r); + break; + } + } + Debug::log("Sent {} bytes, waiting for response", sent); + + while (true) + { + auto [received, buffer] = + wolftls_connection_receive(&unlimited, tlsConn); + if (received > 0) + { + Debug::log( + "Received {} bytes:\n{}", + received, + std::string_view(reinterpret_cast(buffer), received)); + heap_free(TEST_MALLOC, buffer); + } + else if (received == 0 || received == -ENOTCONN) + { + Debug::log("Connection closed"); + break; + } + else if (received == -ETIMEDOUT) + { + Debug::log("Receive timed out"); + } + else + { + Debug::log("Receive error: {}", received); + break; + } + } + + wolftls_connection_close(&unlimited, tlsConn); + Debug::log("Done. Free heap: {}", heap_available()); +} diff --git a/examples/06.HTTPS-wolfssl/xmake.lua b/examples/06.HTTPS-wolfssl/xmake.lua new file mode 100644 index 00000000..fb6a95f8 --- /dev/null +++ b/examples/06.HTTPS-wolfssl/xmake.lua @@ -0,0 +1,50 @@ +-- Copyright The Good Penguin Ltd +-- SPDX-License-Identifier: MIT + +sdkdir = path.absolute("../../../cheriot-rtos/sdk") + +set_project("CHERIoT wolfSSL HTTPS Example") + +includes(sdkdir) +set_toolchains("cheriot-clang") +includes(path.join(sdkdir, "lib")) +includes("../../lib") + +option("board") + set_default("sonata-1.3") + +compartment("https_wolfssl_example") + add_includedirs("../../include") + add_deps("freestanding", "DNS", "TCPIP", "NetAPI", "WolfSSLTLS", "Firewall", "SNTP", "time_helpers", "debug") + add_files("https_wolfssl.cc") + add_rules("cheriot.network-stack.ipv6") + +firmware("06.https_wolfssl_example") + set_policy("build.warning", true) + add_deps("https_wolfssl_example") + on_load(function(target) + target:values_set("board", "$(board)") + target:values_set("threads", { + { + compartment = "https_wolfssl_example", + priority = 1, + entry_point = "example", + stack_size = 0x2000, + trusted_stack_frames = 6 + }, + { + compartment = "TCPIP", + priority = 1, + entry_point = "ip_thread_entry", + stack_size = 0xe00, + trusted_stack_frames = 5 + }, + { + compartment = "Firewall", + priority = 2, + entry_point = "ethernet_run_driver", + stack_size = 0x1000, + trusted_stack_frames = 5 + } + }, {expand = false}) + end) diff --git a/examples/07.HTTPS-wolfssl-concurrent/https_wolfssl_concurrent.cc b/examples/07.HTTPS-wolfssl-concurrent/https_wolfssl_concurrent.cc new file mode 100644 index 00000000..ebe5ec95 --- /dev/null +++ b/examples/07.HTTPS-wolfssl-concurrent/https_wolfssl_concurrent.cc @@ -0,0 +1,185 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT +// +// Concurrent wolfSSL TLS example opens two HTTPS connections simultaneously +// from separate threads to exercise the multi-threaded wolfSSL mutex support. +// +// We have to do the handshake stage sequentially because it takes long enough to +// do both simultaneously that the server times out. +// +// We use example.com and example.org as they use the same Comodo AAA root cert +// +// Both threads log with a [A]/[B] tag so interleaving is visible on the UART. +// + +#include +#if __has_include() +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Bit dirty but just re-use the cert from example 06 +#include "../06.HTTPS-wolfssl/Comodo_AAA_Services_root.h" + +using Debug = ConditionalDebug; +constexpr bool UseIPv6 = CHERIOT_RTOS_OPTION_IPv6; + +DECLARE_AND_DEFINE_CONNECTION_CAPABILITY(ExampleComTLS, + "example.com", + 443, + ConnectionTypeTCP); + +DECLARE_AND_DEFINE_CONNECTION_CAPABILITY(ExampleOrgTLS, + "example.org", + 443, + ConnectionTypeTCP); + +// Separate allocators per connection to avoid allocator contention. +DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(MallocA, 24 * 1024); +DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(MallocB, 24 * 1024); +#define MALLOC_A STATIC_SEALED_VALUE(MallocA) +#define MALLOC_B STATIC_SEALED_VALUE(MallocB) + +static FlagLockPriorityInherited handshakeLock; + +static const WolfSSLTrustAnchor trustAnchors[] = { + {COMODO_AAA_SERVICES_ROOT_DER, sizeof(COMODO_AAA_SERVICES_ROOT_DER)}, +}; + +static void sync_network(const char *tag) +{ + Debug::log("[{}] Waiting for network...", tag); + network_start(); + Timeout t{MS_TO_TICKS(5000)}; + while (sntp_update(&t) != 0) + { + Debug::log("[{}] Waiting for NTP...", tag); + t = Timeout{MS_TO_TICKS(5000)}; + } + Debug::log("[{}] Network and NTP ready", tag); +} + +static bool run_connection(const char *tag, + AllocatorCapability alloc, + ConnectionCapability conn, + const char *host, + const char *request) +{ + Timeout unlimited{UnlimitedTimeout}; + + WolfSSLConnection tlsConn = nullptr; + { + LockGuard g{handshakeLock, &unlimited}; + Debug::log("[{}] Creating TLS connection to {}...", tag, host); + tlsConn = wolftls_connection_create( + &unlimited, alloc, conn, trustAnchors, 1); + } + + if (!CHERI::Capability{tlsConn}.is_valid()) + { + Debug::log("[{}] Failed to create TLS connection", tag); + return false; + } + Debug::log("[{}] TLS handshake complete", tag); + + size_t toSend = strlen(request); + size_t sent = 0; + while (sent < toSend) + { + ssize_t r = wolftls_connection_send( + &unlimited, + tlsConn, + // Cast away const: the API takes void* but does not mutate. + const_cast(request) + sent, + toSend - sent); + if (r > 0) + sent += r; + else + { + Debug::log("[{}] Send failed: {}", tag, r); + wolftls_connection_close(&unlimited, tlsConn); + return false; + } + } + Debug::log("[{}] Sent {} bytes, waiting for response", tag, sent); + + size_t totalReceived = 0; + while (true) + { + auto [received, buffer] = + wolftls_connection_receive(&unlimited, tlsConn); + if (received > 0) + { + totalReceived += received; + Debug::log("[{}] Received {} bytes:\n{}", + tag, + received, + std::string_view(reinterpret_cast(buffer), received)); + heap_free(alloc, buffer); + } + else if (received == 0 || received == -ENOTCONN) + { + Debug::log("[{}] Connection closed, total received: {} bytes", + tag, totalReceived); + break; + } + else if (received == -ETIMEDOUT) + { + Debug::log("[{}] Receive timed out", tag); + } + else + { + Debug::log("[{}] Receive error: {}", tag, received); + wolftls_connection_close(&unlimited, tlsConn); + return false; + } + } + + wolftls_connection_close(&unlimited, tlsConn); + Debug::log("[{}] Done. Free heap: {}", tag, heap_available()); + return totalReceived > 0; +} + +void __cheri_compartment("https_wolfssl_concurrent") connection_a() +{ + sync_network("A"); + static const char request[] = "GET / HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: cheriot-wolfssl-concurrent\r\n" + "Connection: close\r\n" + "Accept: */*\r\n" + "\r\n"; + bool ok = run_connection("A", + MALLOC_A, + CONNECTION_CAPABILITY(ExampleComTLS), + "example.com", + request); + Debug::log("[A] {}", ok ? "SUCCESS" : "FAILED"); +} + +void __cheri_compartment("https_wolfssl_concurrent") connection_b() +{ + sync_network("B"); + static const char request[] = "GET / HTTP/1.1\r\n" + "Host: example.org\r\n" + "User-Agent: cheriot-wolfssl-concurrent\r\n" + "Connection: close\r\n" + "Accept: */*\r\n" + "\r\n"; + bool ok = run_connection("B", + MALLOC_B, + CONNECTION_CAPABILITY(ExampleOrgTLS), + "example.org", + request); + Debug::log("[B] {}", ok ? "SUCCESS" : "FAILED"); +} diff --git a/examples/07.HTTPS-wolfssl-concurrent/xmake.lua b/examples/07.HTTPS-wolfssl-concurrent/xmake.lua new file mode 100644 index 00000000..d88ac7e1 --- /dev/null +++ b/examples/07.HTTPS-wolfssl-concurrent/xmake.lua @@ -0,0 +1,57 @@ +-- Copyright The Good Penguin Ltd +-- SPDX-License-Identifier: MIT + +sdkdir = path.absolute("../../../cheriot-rtos/sdk") + +set_project("CHERIoT wolfSSL Concurrent HTTPS Example") + +includes(sdkdir) +set_toolchains("cheriot-clang") +includes(path.join(sdkdir, "lib")) +includes("../../lib") + +option("board") + set_default("sonata-1.3") + +compartment("https_wolfssl_concurrent") + add_includedirs("../../include") + add_deps("freestanding", "DNS", "TCPIP", "NetAPI", "WolfSSLTLS", "Firewall", "SNTP", "time_helpers", "debug") + add_files("https_wolfssl_concurrent.cc") + add_rules("cheriot.network-stack.ipv6") + +firmware("07.https_wolfssl_concurrent") + set_policy("build.warning", true) + add_deps("https_wolfssl_concurrent") + on_load(function(target) + target:values_set("board", "$(board)") + target:values_set("threads", { + { + compartment = "https_wolfssl_concurrent", + priority = 1, + entry_point = "connection_a", + stack_size = 0x2000, + trusted_stack_frames = 6 + }, + { + compartment = "https_wolfssl_concurrent", + priority = 1, + entry_point = "connection_b", + stack_size = 0x2000, + trusted_stack_frames = 6 + }, + { + compartment = "TCPIP", + priority = 1, + entry_point = "ip_thread_entry", + stack_size = 0xe00, + trusted_stack_frames = 5 + }, + { + compartment = "Firewall", + priority = 2, + entry_point = "ethernet_run_driver", + stack_size = 0x1000, + trusted_stack_frames = 5 + } + }, {expand = false}) + end) diff --git a/examples/08.MQTT-wolfssl/mosquitto.org.h b/examples/08.MQTT-wolfssl/mosquitto.org.h new file mode 100644 index 00000000..af63155d --- /dev/null +++ b/examples/08.MQTT-wolfssl/mosquitto.org.h @@ -0,0 +1,100 @@ +// Trust anchor for test.mosquitto.org:8883 (TLS-encrypted MQTT broker, valid 2020-2030). +// +// To regen: +// 1. Download the CA certificate: +// curl https://test.mosquitto.org/ssl/mosquitto.org.crt -o mosquitto.org.crt +// 2. Convert PEM -> DER: +// openssl x509 -in mosquitto.org.crt -outform DER -out mosquitto.org.der +// 3. Convert to a C byte array: +// xxd -i comodo_aaa.der \ +// | sed -e '1s/.*/static const unsigned char MOSQUITTO_ORG_CA_DER[] = {/' \ +// -e '/^unsigned int/d' +// +static const unsigned char MOSQUITTO_ORG_CA_DER[] = { + 0x30, 0x82, 0x04, 0x03, 0x30, 0x82, 0x02, 0xeb, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x14, 0x05, 0x8d, 0x61, 0x94, 0x21, 0xaf, 0x76, 0x3e, 0x0d, + 0x84, 0x15, 0xe4, 0x67, 0xfb, 0x8b, 0x51, 0x93, 0x48, 0x2c, 0x0c, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, + 0x05, 0x00, 0x30, 0x81, 0x90, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, + 0x55, 0x04, 0x08, 0x0c, 0x0e, 0x55, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x20, + 0x4b, 0x69, 0x6e, 0x67, 0x64, 0x6f, 0x6d, 0x31, 0x0e, 0x30, 0x0c, 0x06, + 0x03, 0x55, 0x04, 0x07, 0x0c, 0x05, 0x44, 0x65, 0x72, 0x62, 0x79, 0x31, + 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x4d, 0x6f, + 0x73, 0x71, 0x75, 0x69, 0x74, 0x74, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x02, 0x43, 0x41, 0x31, 0x16, 0x30, 0x14, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0d, 0x6d, 0x6f, 0x73, 0x71, 0x75, + 0x69, 0x74, 0x74, 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x31, 0x1f, 0x30, 0x1d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, + 0x10, 0x72, 0x6f, 0x67, 0x65, 0x72, 0x40, 0x61, 0x74, 0x63, 0x68, 0x6f, + 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x30, 0x30, + 0x36, 0x30, 0x39, 0x31, 0x31, 0x30, 0x36, 0x33, 0x39, 0x5a, 0x17, 0x0d, + 0x33, 0x30, 0x30, 0x36, 0x30, 0x37, 0x31, 0x31, 0x30, 0x36, 0x33, 0x39, + 0x5a, 0x30, 0x81, 0x90, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x0c, 0x0e, 0x55, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x20, 0x4b, + 0x69, 0x6e, 0x67, 0x64, 0x6f, 0x6d, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, + 0x55, 0x04, 0x07, 0x0c, 0x05, 0x44, 0x65, 0x72, 0x62, 0x79, 0x31, 0x12, + 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x4d, 0x6f, 0x73, + 0x71, 0x75, 0x69, 0x74, 0x74, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x0b, 0x0c, 0x02, 0x43, 0x41, 0x31, 0x16, 0x30, 0x14, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0d, 0x6d, 0x6f, 0x73, 0x71, 0x75, 0x69, + 0x74, 0x74, 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x31, 0x1f, 0x30, 0x1d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10, + 0x72, 0x6f, 0x67, 0x65, 0x72, 0x40, 0x61, 0x74, 0x63, 0x68, 0x6f, 0x6f, + 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, + 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, + 0x00, 0xc1, 0x34, 0x1c, 0xa9, 0x88, 0xcd, 0xf4, 0xce, 0xc2, 0x42, 0x8b, + 0x4f, 0x74, 0xc7, 0x1d, 0xef, 0x8e, 0x6d, 0xd8, 0xb3, 0x6a, 0x63, 0xe0, + 0x51, 0x99, 0x83, 0xeb, 0x84, 0xdf, 0xdf, 0x32, 0x5d, 0x35, 0xe6, 0x06, + 0x62, 0x7e, 0x02, 0x11, 0x76, 0xf2, 0x3f, 0xa7, 0xf2, 0xde, 0xd5, 0x9c, + 0xf1, 0x2d, 0x9b, 0xa1, 0x6e, 0x9d, 0xce, 0xb1, 0xfc, 0x49, 0xd1, 0x5f, + 0xf6, 0xea, 0x37, 0xdb, 0x41, 0x89, 0x03, 0xd0, 0x7b, 0x53, 0x51, 0x56, + 0x4d, 0xed, 0xf1, 0x75, 0xaf, 0xcb, 0x9b, 0x72, 0x45, 0x7d, 0xa1, 0xe3, + 0x91, 0x6c, 0x3b, 0x8c, 0x1c, 0x1c, 0x6a, 0xe4, 0x19, 0x8e, 0x91, 0x88, + 0x34, 0x76, 0xa9, 0x1d, 0x19, 0x69, 0x88, 0x26, 0x6c, 0xaa, 0xe0, 0x2d, + 0x84, 0xe8, 0x31, 0x5b, 0xd4, 0xa0, 0x0e, 0x06, 0x25, 0x1b, 0x31, 0x00, + 0xb3, 0x4e, 0xa9, 0x90, 0x41, 0x62, 0x33, 0x0f, 0xaa, 0x0d, 0xf2, 0xe8, + 0xfe, 0xcc, 0x45, 0x28, 0x1e, 0xaf, 0x42, 0x51, 0x5e, 0x90, 0xc7, 0x82, + 0xca, 0x68, 0xcb, 0x09, 0xb3, 0x70, 0x3c, 0x9c, 0xaa, 0xca, 0x11, 0x66, + 0x3d, 0x6c, 0x22, 0xa3, 0xf3, 0xc3, 0x32, 0xbb, 0x81, 0x4f, 0x33, 0xc7, + 0xdd, 0xc8, 0xa8, 0x06, 0x7a, 0xc9, 0x58, 0xa5, 0xdc, 0xdc, 0xe8, 0xd7, + 0x74, 0xb1, 0x85, 0x24, 0xe7, 0xe3, 0xee, 0x93, 0xf4, 0x8f, 0xf7, 0x6b, + 0xd8, 0xb1, 0xfb, 0xd9, 0xe4, 0xaf, 0xbf, 0x73, 0xd0, 0x40, 0x59, 0x7d, + 0xd0, 0x26, 0x4f, 0x16, 0x1a, 0xc2, 0x51, 0xc4, 0x47, 0x49, 0x2c, 0x68, + 0x13, 0xac, 0xa3, 0x18, 0xe7, 0x67, 0xcf, 0xb7, 0xfa, 0x3e, 0xf7, 0x8b, + 0x20, 0x1e, 0x7b, 0xe2, 0x44, 0x0e, 0x47, 0x0b, 0x7c, 0x78, 0xf9, 0xf4, + 0xca, 0x27, 0x6b, 0x4c, 0x2d, 0x62, 0x72, 0xd8, 0xa4, 0x10, 0x3d, 0xe7, + 0x1d, 0x88, 0x4c, 0x50, 0xe5, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x53, + 0x30, 0x51, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, + 0x14, 0xf5, 0x55, 0xeb, 0x10, 0x54, 0x14, 0xf8, 0x86, 0x28, 0x3c, 0xa8, + 0xe5, 0x5d, 0xfe, 0x1d, 0xb8, 0x78, 0x37, 0xd6, 0x12, 0x30, 0x1f, 0x06, + 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xf5, 0x55, + 0xeb, 0x10, 0x54, 0x14, 0xf8, 0x86, 0x28, 0x3c, 0xa8, 0xe5, 0x5d, 0xfe, + 0x1d, 0xb8, 0x78, 0x37, 0xd6, 0x12, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, + 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, + 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x66, 0xbd, 0x91, 0x2d, 0xb5, + 0x37, 0xbd, 0x13, 0x84, 0xce, 0xbf, 0x1e, 0x3f, 0x43, 0xee, 0x66, 0xd5, + 0xc4, 0xa2, 0xc1, 0x8d, 0x55, 0x9e, 0xd9, 0x33, 0xec, 0x19, 0xf6, 0xe5, + 0xde, 0xb1, 0x03, 0x7d, 0x9f, 0x8e, 0x29, 0x16, 0x76, 0x8f, 0xa0, 0x02, + 0xea, 0xbe, 0xe3, 0x6f, 0x84, 0xd9, 0x3b, 0x77, 0x73, 0x17, 0x6a, 0x7a, + 0x76, 0x06, 0xeb, 0x95, 0x4e, 0xf5, 0x63, 0xfe, 0x0a, 0xd1, 0x37, 0x73, + 0x22, 0x34, 0x63, 0xdd, 0xc4, 0x37, 0x29, 0x29, 0xb8, 0xd4, 0x9b, 0xd4, + 0x43, 0x48, 0x59, 0xfd, 0xcd, 0x38, 0x88, 0x60, 0xe0, 0xff, 0x15, 0x9f, + 0xfa, 0x9a, 0x79, 0xf2, 0x77, 0xcf, 0x01, 0x8c, 0x2e, 0x7a, 0xba, 0xee, + 0x3c, 0xd5, 0xa6, 0x95, 0x2b, 0x56, 0x01, 0x77, 0xf4, 0x51, 0x3a, 0x91, + 0xb6, 0x0e, 0x21, 0x40, 0x35, 0x81, 0xb9, 0x41, 0x43, 0x25, 0x3b, 0x96, + 0xba, 0xe0, 0x6f, 0x11, 0x7b, 0x9d, 0xcf, 0xbe, 0x1e, 0x87, 0xfc, 0x0a, + 0xb0, 0xcc, 0x1f, 0xbb, 0x51, 0xc5, 0xbe, 0x3c, 0xb9, 0x67, 0x48, 0x8c, + 0x0d, 0x4f, 0x0f, 0x50, 0x37, 0xa9, 0x8d, 0x5a, 0x25, 0x38, 0x2b, 0x9e, + 0xf5, 0xab, 0x21, 0x95, 0x2e, 0x04, 0x07, 0x92, 0x04, 0x09, 0xd4, 0x91, + 0xd9, 0x32, 0x2d, 0x9c, 0x02, 0x22, 0x23, 0x08, 0xa6, 0xc7, 0xcd, 0xfd, + 0x2d, 0xd5, 0x1d, 0x46, 0xe7, 0x5a, 0x7c, 0xcb, 0xb9, 0x4f, 0x95, 0xe6, + 0x6b, 0x5f, 0x36, 0x38, 0x2d, 0x3f, 0xbb, 0xfc, 0x51, 0x94, 0x49, 0xbe, + 0xb6, 0xf2, 0x86, 0x1a, 0x67, 0xc5, 0x70, 0xdd, 0x29, 0x8a, 0xa5, 0x65, + 0xf0, 0xea, 0xd2, 0x3c, 0x18, 0x08, 0x95, 0xbf, 0xb5, 0x20, 0xa2, 0x44, + 0x9b, 0xf5, 0xeb, 0x89, 0x6a, 0xff, 0x0a, 0xae, 0x21, 0xfc, 0x97, 0xc1, + 0xec, 0xd4, 0xec, 0x7b, 0x35, 0x6c, 0x96, 0x09, 0x01, 0x6a, 0x85 +}; diff --git a/examples/08.MQTT-wolfssl/mqtt_wolfssl.cc b/examples/08.MQTT-wolfssl/mqtt_wolfssl.cc new file mode 100644 index 00000000..94c4284e --- /dev/null +++ b/examples/08.MQTT-wolfssl/mqtt_wolfssl.cc @@ -0,0 +1,266 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT +// +// wolfSSL MQTT example, mirrors examples/04.MQTT/mqtt.cc but uses the +// WolfSSLMQTT compartment instead of the BearSSL-backed MQTT compartment. + +#include +#if __has_include() +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "mosquitto.org.h" + +using CHERI::Capability; + +using Debug = ConditionalDebug; +constexpr bool UseIPv6 = CHERIOT_RTOS_OPTION_IPv6; + +constexpr size_t MQTTMaximumClientLength = 23; +constexpr std::string_view clientIDPrefix{"cheriotWolfMQTT"}; +std::array clientID; +static_assert(clientIDPrefix.size() < clientID.size()); + +constexpr const size_t networkBufferSize = 1024; +constexpr const size_t incomingPublishCount = 20; +constexpr const size_t outgoingPublishCount = 20; + +// test.mosquitto.org port 8883: TLS-encrypted, unauthenticated +DECLARE_AND_DEFINE_CONNECTION_CAPABILITY(MosquittoOrgMQTT, + "test.mosquitto.org", + 8883, + ConnectionTypeTCP); + +DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(mqttTestMalloc, 48 * 1024); + +constexpr std::string_view testTopic{"cherries"}; +constexpr std::string_view testPayload{"MORELLO"}; + +static int ackReceived = 0; +static int publishReceived = 0; + +void __cheri_callback publishCallback(const char *topicName, + size_t topicNameLength, + const void *payload, + size_t payloadLength) +{ + Timeout t{MS_TO_TICKS(5000)}; + if (heap_claim_ephemeral(&t, topicName) != 0 || + !CHERI::check_pointer(topicName, topicNameLength)) + { + Debug::log( + "Cannot claim or verify PUBLISH callback topic name pointer."); + return; + } + + if (heap_claim_ephemeral(&t, payload) != 0 || + !CHERI::check_pointer(payload, payloadLength)) + { + Debug::log("Cannot claim or verify PUBLISH callback payload pointer."); + return; + } + + Debug::log("Got a PUBLISH for topic {}", + std::string_view{topicName, topicNameLength}); + publishReceived++; +} + +void __cheri_callback ackCallback(uint16_t packetID, bool isReject) +{ + Debug::log("Got an ACK for packet {}", packetID); + + if (isReject) + { + Debug::log("However the ACK is a SUBSCRIBE REJECT notification"); + } + + ackReceived++; +} + +void __cheri_compartment("mqtt_wolfssl_example") example() +{ + int ret; + Timeout t{MS_TO_TICKS(5000)}; + Debug::log("Waiting for network..."); + network_start(); + Debug::log("Waiting for NTP..."); + // SNTP must be run for wolfSSL to validate certificate dates. + while (sntp_update(&t) != 0) + { + Debug::log("Failed to update NTP time"); + Timeout oneSecond{MS_TO_TICKS(1000)}; + t = Timeout{MS_TO_TICKS(5000)}; + } + Debug::log("Updating NTP took {} ticks", t.elapsed); + t = UnlimitedTimeout; + { + timeval tv; + int ret = gettimeofday(&tv, nullptr); + if (ret != 0) + { + Debug::log("Failed to get time of day: {}", ret); + } + else + { + Debug::log("Current UNIX epoch time: {}", (int32_t)tv.tv_sec); + } + } + + auto heapAtStart = + heap_quota_remaining(STATIC_SEALED_VALUE(mqttTestMalloc)); + + Debug::log("Generating client ID..."); + memcpy(clientID.data(), clientIDPrefix.data(), clientIDPrefix.size()); + mqtt_wolfssl_generate_client_id(clientID.data() + clientIDPrefix.size(), + clientID.size() - clientIDPrefix.size()); + + Debug::log("Connecting to MQTT broker via wolfSSL TLS..."); + + static const WolfSSLTrustAnchor trustAnchors[] = { + {MOSQUITTO_ORG_CA_DER, sizeof(MOSQUITTO_ORG_CA_DER)}, + }; + + auto handle = mqtt_wolfssl_connect(&t, + STATIC_SEALED_VALUE(mqttTestMalloc), + CONNECTION_CAPABILITY(MosquittoOrgMQTT), + publishCallback, + ackCallback, + trustAnchors, + 1, + networkBufferSize, + incomingPublishCount, + outgoingPublishCount, + clientID.data(), + clientID.size()); + + if (!Capability{handle}.is_valid()) + { + Debug::log("Failed to connect."); + return; + } + + Debug::log("Connected to MQTT broker!"); + + Debug::log("Subscribing to test topic '{}'.", testTopic); + + ret = mqtt_wolfssl_subscribe(&t, + handle, + 1, // QoS 1 = delivered at least once + testTopic.data(), + testTopic.size()); + + if (ret < 0) + { + Debug::log("Failed to subscribe, error {}.", ret); + return; + } + + Debug::log("Now fetching the SUBACK."); + + while (ackReceived == 0) + { + t = Timeout{MS_TO_TICKS(100)}; + ret = mqtt_wolfssl_run(&t, handle); + + if (ret < 0) + { + Debug::log("Failed to wait for the SUBACK, error {}.", ret); + return; + } + } + + Debug::log("Publishing a value to test topic '{}'.", testTopic); + + t = Timeout{MS_TO_TICKS(5000)}; + ret = mqtt_wolfssl_publish(&t, + handle, + 1, // QoS 1 = delivered at least once + testTopic.data(), + testTopic.size(), + static_cast(testPayload.data()), + testPayload.size()); + + if (ret < 0) + { + Debug::log("Failed to publish, error {}.", ret); + return; + } + + Debug::log( + "Now fetching the PUBACK and waiting for the publish notification."); + + while (ackReceived == 1 || publishReceived == 0) + { + t = Timeout{MS_TO_TICKS(100)}; + ret = mqtt_wolfssl_run(&t, handle); + + if (ret < 0) + { + Debug::log("Failed to wait for the PUBACK/PUBLISH, error {}.", ret); + return; + } + } + + Debug::log("Unsubscribing from topic '{}'.", testTopic); + + t = Timeout{MS_TO_TICKS(5000)}; + ret = mqtt_wolfssl_unsubscribe(&t, + handle, + 1, // QoS 1 = delivered at least once + testTopic.data(), + testTopic.size()); + + if (ret < 0) + { + Debug::log("Failed to unsubscribe, error {}.", ret); + return; + } + + while (ackReceived == 2) + { + t = Timeout{MS_TO_TICKS(100)}; + ret = mqtt_wolfssl_run(&t, handle); + + if (ret < 0) + { + Debug::log("Failed to wait for the UNSUBACK, error {}.", ret); + return; + } + } + + Debug::log("Disconnecting from the broker."); + + t = Timeout{MS_TO_TICKS(5000)}; + ret = mqtt_wolfssl_disconnect( + &t, STATIC_SEALED_VALUE(mqttTestMalloc), handle); + + if (ret < 0) + { + Debug::log("Failed to disconnect, error {}.", ret); + return; + } + + Debug::log("Now checking for leaks."); + + auto heapAtEnd = heap_quota_remaining(STATIC_SEALED_VALUE(mqttTestMalloc)); + if (heapAtStart != heapAtEnd) + { + Debug::log("Warning: The implementation leaked {} bytes ({} vs. {}).", + heapAtEnd - heapAtStart, + heapAtStart, + heapAtEnd); + } + else + { + Debug::log("No leaks detected."); + } + + Debug::log("Done testing wolfSSL MQTT."); +} diff --git a/examples/08.MQTT-wolfssl/xmake.lua b/examples/08.MQTT-wolfssl/xmake.lua new file mode 100644 index 00000000..9ea55417 --- /dev/null +++ b/examples/08.MQTT-wolfssl/xmake.lua @@ -0,0 +1,54 @@ +-- Copyright The Good Penguin Ltd +-- SPDX-License-Identifier: MIT + +sdkdir = path.absolute("../../../cheriot-rtos/sdk") + +set_project("CHERIoT wolfSSL MQTT Example") + +includes(sdkdir) + +set_toolchains("cheriot-clang") + +includes(path.join(sdkdir, "lib")) +includes("../../lib") + +option("board") + set_default("sonata-1.3") + +compartment("mqtt_wolfssl_example") + add_includedirs("../../include") + add_deps("freestanding", "DNS", "TCPIP", "NetAPI", "WolfSSLTLS", "WolfSSLMQTT", + "Firewall", "SNTP", "time_helpers", "debug") + add_deps("stdio") + add_files("mqtt_wolfssl.cc") + add_rules("cheriot.network-stack.ipv6") + +firmware("08.mqtt_wolfssl_example") + set_policy("build.warning", true) + add_deps("mqtt_wolfssl_example") + on_load(function(target) + target:values_set("board", "$(board)") + target:values_set("threads", { + { + compartment = "mqtt_wolfssl_example", + priority = 1, + entry_point = "example", + stack_size = 0x3000, + trusted_stack_frames = 8 + }, + { + compartment = "TCPIP", + priority = 1, + entry_point = "ip_thread_entry", + stack_size = 0xe00, + trusted_stack_frames = 5 + }, + { + compartment = "Firewall", + priority = 2, + entry_point = "ethernet_run_driver", + stack_size = 0x1000, + trusted_stack_frames = 5 + } + }, {expand = false}) + end) diff --git a/include/mqtt_wolfssl.h b/include/mqtt_wolfssl.h new file mode 100644 index 00000000..359d5084 --- /dev/null +++ b/include/mqtt_wolfssl.h @@ -0,0 +1,116 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MITP + +#pragma once +#include +#include +#include + +struct CHERIoTWolfSSLMqttContext; + +typedef CHERI_SEALED(struct CHERIoTWolfSSLMqttContext *) WolfSSLMQTTConnection; + +/** + * Type of the PUBLISH callback. This user callback, passed to + * `mqtt_wolfssl_connect`, will be called on all PUBLISH notifications from + * the broker. + * + * `topicName` and `payload` (and their respective size arguments) indicate the + * topic of the PUBLISH, and the corresponding payload. Both are only valid + * within the context of the callback and thus passed as a read-only, + * non-capturable capabilities. + */ +typedef void __cheri_callback (*WolfSSLMQTTPublishCallback)( + const char *topicName, + size_t topicNameLength, + const void *payload, + size_t payloadLength); + +/** + * Type of the ACK callback. This user callback, passed to + * `mqtt_wolfssl_connect`, will be called on all ACKs from the broker + * (SUBACK, PUBACK, etc.). + */ +typedef void __cheri_callback (*WolfSSLMQTTAckCallback)(uint16_t packetID, + bool isReject); + +/** + * Creates a new unauthenticated TLS-tunneled MQTT connection using wolfSSL. + * Returns null on failure, or a sealed MQTT connection object on success. + * + * This is the wolfSSL equivalent of `mqtt_connect`. See `mqtt.h` for full + * parameter documentation. + */ +WolfSSLMQTTConnection __cheri_compartment("WolfSSLMQTT") + mqtt_wolfssl_connect(Timeout *t, + AllocatorCapability allocator, + ConnectionCapability hostCapability, + WolfSSLMQTTPublishCallback publishCallback, + WolfSSLMQTTAckCallback ackCallback, + const WolfSSLTrustAnchor *trustAnchors, + size_t trustAnchorsCount, + size_t networkBufferSize, + size_t incomingPublishCount, + size_t outgoingPublishCount, + const char *clientID, + size_t clientIDLength, + bool newSession __if_cxx(= true)); + +/** + * Close a TLS-tunneled MQTT connection (wolfSSL variant). + * See `mqtt.h` for full documentation. + */ +int __cheri_compartment("WolfSSLMQTT") + mqtt_wolfssl_disconnect(Timeout *t, + AllocatorCapability allocator, + WolfSSLMQTTConnection mqttHandle); + +/** + * Publish on a given wolfSSL MQTT connection. + * See `mqtt.h` for full documentation. + */ +int __cheri_compartment("WolfSSLMQTT") + mqtt_wolfssl_publish(Timeout *t, + WolfSSLMQTTConnection mqttHandle, + uint8_t qos, + const char *topic, + size_t topicLength, + const void *payload, + size_t payloadLength, + bool retain __if_cxx(= false)); + +/** + * Subscribe on a given wolfSSL MQTT connection. + * See `mqtt.h` for full documentation. + */ +int __cheri_compartment("WolfSSLMQTT") + mqtt_wolfssl_subscribe(Timeout *t, + WolfSSLMQTTConnection mqttHandle, + uint8_t qos, + const char *filter, + size_t filterLength); + +/** + * Unsubscribe on a given wolfSSL MQTT connection. + * See `mqtt.h` for full documentation. + */ +int __cheri_compartment("WolfSSLMQTT") + mqtt_wolfssl_unsubscribe(Timeout *t, + WolfSSLMQTTConnection mqttHandle, + uint8_t qos, + const char *filter, + size_t filterLength); + +/** + * Fetch ACK and PUBLISH notifications on a given wolfSSL MQTT connection. + * See `mqtt.h` for full documentation. + */ +int __cheri_compartment("WolfSSLMQTT") + mqtt_wolfssl_run(Timeout *t, WolfSSLMQTTConnection mqttHandle); + +/** + * Generate a valid, random MQTT 3.1.1 client ID. + * See `mqtt.h` for full documentation. + */ +int __cheri_compartment("WolfSSLMQTT") + mqtt_wolfssl_generate_client_id(char *buffer, size_t length); diff --git a/include/tls_wolfssl.h b/include/tls_wolfssl.h new file mode 100644 index 00000000..9433b11d --- /dev/null +++ b/include/tls_wolfssl.h @@ -0,0 +1,128 @@ +#pragma once +#include +#include +#include + +/* Create a sealed WolfSSLContext that is created & sealed in the WolfSSLTLS + * compartment. It is returned to the calling compartment when + * wolftls_connection_create is called and handed back when calling the other + * API functions. + */ +typedef CHERI_SEALED(struct WolfSSLContext *) WolfSSLConnection; + +/* Trust anchor is a certificate that you unconditionally trust (typically a + * root CA certificate) It is a DER-encoded X.509 certificate. wolfSSL parses + the raw bytes internally. + */ +typedef struct { + const unsigned char *certDer; + size_t certDerLength; +} WolfSSLTrustAnchor; + +/** + * Creates a new TLS connection. This will block until the connection is + * established, an error happens, or the timeout expires. Returns an untagged + * value on failure or a sealed TLS connection object on success. + * + * The state for the TLS connection will be allocated with `allocator`. The + * connection will be made to the host identified by the connection capability, + * which must authorise a TCP connection. Once the connection is made, the + * certificates will be validated against the trust anchors provided via the + * `trustAnchors` parameter, which contains a pointer to an array of + * `trustAnchorsCount` DER-encoded CA certificates. + * + * This can fail if: + * + * - The connection capability is not a valid TCP connection capability. + * - The allocator capability does not have enough quota to satisfy the + * allocations. + * - The remote host is not accessible. + * - The remote host's certificate is not trusted by the trust anchors. + * + * This function assumes that the trust anchors are valid and will not be + * freed during the call. If this is not the case, then this function can + * abort *without* gracefully freeing the resources it has allocated. These + * are allocated with the callee's allocator and so the caller is able to + * mount a denial of service attack on itself via a concurrent free. + * + * Known problems with this API: + * + * - The reason for the failure is not reported. + */ +__cheri_compartment("WolfSSLTLS") +WolfSSLConnection wolftls_connection_create( + Timeout *t, + AllocatorCapability allocator, + ConnectionCapability connectionCapability, + const WolfSSLTrustAnchor *trustAnchors, + size_t trustAnchorsCount); + +/** + * Sends `length` bytes from `buffer` to the remote host. Returns the + * number of bytes sent, or a negative error code. + * + * The `sealedConnection` parameter is a pointer to a TLS connection, returned + * by `wolftls_connection_create`. + + * - `-EINVAL`: Invalid timeout pointer or invalid/unsealed connection + * - `-ETIMEDOUT`: Lock acquisition timeout or timeout mid-write exits loop, + * returning bytes sent so far + * - `-EPERM`: Buffer lacks Load permission + * - `-EIO`: wolfSSL write error before any bytes were sent + * + */ +__cheri_compartment("WolfSSLTLS") +ssize_t wolftls_connection_send( + Timeout *t, + WolfSSLConnection sealedConnection, + void *buffer, + size_t length); + +/** + * Receive data from the TLS connection. This will block until data are + * received, an error happens, or the timeout expires. If data are received, + * they will be stored in a newly allocated buffer (allocated with the + * allocator provided to `wolftls_connection_create`) and returned along with their + * length. The caller is responsible for freeing this buffer. On error, the + * return value is an untagged value and a negative error code. + * + * The negative values will be errno values: + * + * - `-EINVAL`: The socket is not valid. + * - `-ETIMEDOUT`: The timeout was reached before data could be received. + * - `-ENOMEM`: Memory was insufficient to allocate the receive buffer. + * - `-ENOTCONN`: The TLS layer returned an unexpected error + */ +__cheri_compartment("WolfSSLTLS") +NetworkReceiveResult wolftls_connection_receive( + Timeout *t, + WolfSSLConnection sealedConnection); + +/** + * Receive data from the TLS connection into a preallocated buffer. This will + * block until data are received, an error happens, or the timeout expires. If + * data are received, they will be stored in the provided buffer. + * + * The return value is either the number of bytes received, zero if the + * connection is closed, or a negative error code. + * + * The negative values will be errno values: + * + * - `-EINVAL`: The socket is not valid. + * - `-ETIMEDOUT`: The timeout was reached before data could be received. + * - `-EPERM`: The receive buffer provided does not feature write permissions. + */ +__cheri_compartment("WolfSSLTLS") +int wolftls_connection_receive_preallocated( + Timeout *t, + WolfSSLConnection sealedConnection, + void *buffer, + size_t length); + +/** + * Close a TLS connection. + */ +__cheri_compartment("WolfSSLTLS") +int wolftls_connection_close( + Timeout *t, + WolfSSLConnection sealed); diff --git a/lib/mqtt_wolfssl/mqtt_wolfssl.cc b/lib/mqtt_wolfssl/mqtt_wolfssl.cc new file mode 100644 index 00000000..ab13d540 --- /dev/null +++ b/lib/mqtt_wolfssl/mqtt_wolfssl.cc @@ -0,0 +1,1148 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT +// +// wolfSSL variant of the MQTT compartment. Mirrors lib/mqtt/mqtt.cc but uses +// the WolfSSLTLS compartment instead of BearSSL TLS. + +#include +#if __has_include() +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using CHERI::Capability; + +constexpr bool DebugMQTT = +#ifdef DEBUG_WolfSSLMQTT + DEBUG_WolfSSLMQTT +#else + false +#endif + ; + +using Debug = ConditionalDebug; + +struct NetworkContext +{ + WolfSSLConnection tlsHandle; + + /** + * Pointer to the caller-supplied PUBLISH callback. This callback will + * be called on all PUBLISH notifications from the broker. + */ + WolfSSLMQTTPublishCallback publishCallback; + + /** + * Pointer to the caller-supplied ACK callback. This callback will be + * called on all ACKs from the broker (SUBACK, PUBACK, etc.). Note + * that CONNACK does not trigger this callback since it is consumed by + * `MQTT_Init`. + */ + WolfSSLMQTTAckCallback ackCallback; + + /** + * Flag set when the connection is terminated. + */ + bool isDisconnected; +}; + +/** + * The object for a sealed CHERIoT MQTT connection. + */ +struct CHERIoTWolfSSLMqttContext +{ + // The underlying TLS stream. + WolfSSLConnection tlsHandle; + + /** + * MQTT internal buffers. We must keep a link to them here for + * freeing. + */ + MQTTContext_t coreMQTTContext; + TransportInterface_t transportInterface; + MQTTFixedBuffer_t networkBuffer; + NetworkContext_t networkContext; + + /** + * Lock on which the whole public API synchronizes. This is a + * recursive mutex so that you can publish during a callback. + * Arbitrary recursion is discouraged by coreMQTT, but at least this + * minimal case works. + * + * See: https://github.com/FreeRTOS/coreMQTT/issues/278 + */ + RecursiveMutex lock; + + /** + * Constructor of the CHERIoT MQTT context object. We keep + * track of all allocated objects to free them later on. + */ + CHERIoTWolfSSLMqttContext(WolfSSLConnection tlsHandle, AllocatorCapability allocator) + : tlsHandle{tlsHandle} + { + } + + /** + * Destructor of the CHERIoT MQTT context object. This takes + * care of closing the TLS link, and de-allocating all objects. + */ + ~CHERIoTWolfSSLMqttContext() + { + Timeout t{UnlimitedTimeout}; + wolftls_connection_close(&t, tlsHandle); + } + + /** + * Following this we allocate variable length data: + * - incoming publishes (array of MQTTPubAckInfo_t) + * - outgoing publishes (array of MQTTPubAckInfo_t) + * - the network buffer (array of uint8_t) + */ + alignas(MQTTPubAckInfo_t) uint8_t variableLengthData; +}; + +namespace +{ + + /** + * Helper to return the unsealing key for CHERIoT MQTT objects. + */ + __always_inline auto mqtt_wolfssl_key() + + { + return STATIC_SEALING_TYPE(WolfSSLMQTTHandle); + } + + /** + * Returns a weak pseudo-random number. + */ + uint64_t rand() + { + static EntropySource rng; + return rng(); + } + + /** + * Helper to run a callback, accounting for its runtime in the timeout + * object. + */ + auto with_elapse_timeout(Timeout *t, auto callback) + { + SystickReturn timestampBefore = thread_systemtick_get(); + auto ret = callback(); + SystickReturn timestampAfter = thread_systemtick_get(); + // Timeouts should not overflow a 32 bit value + t->elapse(timestampAfter.lo - timestampBefore.lo); + return ret; + } + + /** + * Helper to retry an MQTT operation as long as it returns + * `MQTTSendFailed` and there is time left. This is useful to reduce + * code duplication, as `MQTT_Publish`, `MQTT_Subscribe`, and + * `MQTT_Unsubscribe` all require the same error handling logic. + */ + int with_sendfailed_retry(Timeout *t, + const char *mqttOpName, + CHERIoTWolfSSLMqttContext *connection, + auto callback) + { + MQTTStatus_t status; + do + { + status = with_elapse_timeout(t, callback); + + if (status != MQTTSuccess) + { + if (status == MQTTNoMemory) + { + return -ENOMEM; + } + if (status == MQTTSendFailed) + { + // If the TLS link is still live, try + // again until we are out of time. + if (connection->networkContext.isDisconnected) + { + return -ECONNABORTED; + } + } + else if (status == MQTTBadParameter) + { + Debug::log( + "{} gave -EINVAL, this may indicate a bug in this code.", + mqttOpName); + return -EINVAL; + } + else + { + Debug::log("Unknown error returned by {}.", mqttOpName); + return -EAGAIN; + } + } + } while (t->remaining > 0 && status != MQTTSuccess); + + if (status != MQTTSuccess) + { + return -ETIMEDOUT; + } + + return 0; + } + + /** + * Helper to unseal the CHERIoT MQTT context, acquire the lock, and + * execute a given callback with the unsealed context. + * + * If the parameter `destructMode` is set to `true`, this will acquire + * the lock in destruct mode, and free the sealed CHERIoT MQTT handle. + */ + ssize_t with_sealed_mqtt_context(Timeout *timeout, + WolfSSLMQTTConnection sealed, + auto callback, + bool destructMode = false) + { + Sealed sealedContext{sealed}; + auto *unsealed = token_unseal(mqtt_wolfssl_key(), sealedContext); + if (unsealed == nullptr) + { + Debug::log("Failed to unseal WolfSSL MQTT context {}", sealed); + return -EINVAL; + } + + if (destructMode) + { + // If destruction mode was passed, upgrade the lock for + // destruction and destroy the sealed object after calling the + // callback. + if (!unsealed->lock.try_lock(timeout)) + { + Debug::log( + "Failed to acquire lock on WolfSSL MQTT context during close"); + return -ETIMEDOUT; + } + unsealed->lock.upgrade_for_destruction(); + return callback(unsealed); + } + if (LockGuard g{unsealed->lock, timeout}) + { + return callback(unsealed); + } + Debug::log("Failed to acquire lock on WolfSSL MQTT context"); + return -ETIMEDOUT; + } + + /** + * Note from the MQTT 3.1.1 spec: + * The Server MUST allow ClientIds which are between 1 and 23 UTF-8 encoded + * bytes in length, and that contain only the characters + * "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + * + * Note from us: UTF-8 encoding of 0-9, a-z, A-Z, is 1 Byte per + * character. + */ + constexpr const char ClientIdCharacters[] = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + constexpr const int MQTTMaximumClientIDSize = 23; + + /** + * Helper to check if a client ID is valid according to the MQTT + * specification. + */ + bool is_valid_client_id(std::string_view id) + { + if (id.size() > MQTTMaximumClientIDSize || id.size() == 0) + { + return false; + } + + for (char c : id) + { + switch (c) + { + default: + return false; + case '0' ... '9': + case 'a' ... 'z': + case 'A' ... 'Z': + continue; + } + } + + return true; + } + + /** + * Helper to check if a buffer contains a \0 character. + */ + bool has_null_character(const char *buffer, size_t length) + { + // TODO use `memchr` (or a string view and `find`) when + // available + for (size_t i = 0; i < length; i++) + { + if (buffer[i] == '\0') + { + return true; + } + } + return false; + } + + /** + * Callback provided to coreMQTT. + * + * Provides transport interface for receiving data on the network. + * + * Returns the number of bytes received or a negative value to indicate + * error. (as specified in the coreMQTT documentation) + */ + int32_t transport_recv(NetworkContext_t *networkContext, + void *recvBuffer, + size_t bytesToRecv) + { + auto sealed = networkContext->tlsHandle; + + /** + * Note from the coreMQTT documentation: It is RECOMMENDED that the + * transport receive implementation does NOT block when requested to + * read a single byte. A single byte read request can be made by the + * caller to check whether there is a new frame available on the + * network for reading. However, the receive implementation MAY + * block for a timeout period when it is requested to read more than + * 1 byte. This is because once the caller is aware that a new frame + * is available to read on the network, then the likelihood of + * reading more than one byte over the network becomes high. + */ + // TODO Determine a good value for this timeout. + Timeout t{MS_TO_TICKS(1000)}; + if (bytesToRecv == 1) + { + t = {0}; + } + + int32_t received = wolftls_connection_receive_preallocated( + &t, sealed, recvBuffer, bytesToRecv); + + Debug::log("Received {} bytes from the network", received); + + /** + * Note from the coreMQTT documentation on the return value: If no + * data is available on the network to read and no error has + * occurred, zero MUST be the return value. A zero return value + * SHOULD represent that the read operation can be retried by + * calling the API function. Zero MUST NOT be returned if a network + * disconnection has occurred. + */ + if (received == -ETIMEDOUT) + { + // In the case of timeout, this read operation can be + // retried. + received = 0; + } + else if (received == -ENOTCONN) + { + // The TCP/TLS link is dead + networkContext->isDisconnected = true; + } + + return received; + } + + /** + * Callback provided to coreMQTT. + * + * Provides transport interface for sending data over the network. + * + * Returns the number of bytes sent or a negative value to indicate + * error. (as specified in the coreMQTT documentation) + */ + int32_t transport_send(NetworkContext_t *networkContext, + const void *sendBuffer, + size_t bytesToSend) + { + auto sealed = networkContext->tlsHandle; + + // TODO Determine a good value for this timeout. + Timeout t{MS_TO_TICKS(1000)}; + + int32_t sent = wolftls_connection_send( + &t, sealed, const_cast(sendBuffer), bytesToSend); + + if (sent > 0 && sent != bytesToSend) + { + Debug::log("Partial send: {} < {}", sent, bytesToSend); + } + + /** + * Note from the coreMQTT documentation on the return value: If + * no data is transmitted over the network due to a full TX + * buffer and no network error has occurred, this MUST return + * zero as the return value. A zero return value SHOULD + * represent that the send operation can be retried by calling + * the API function. Zero MUST NOT be returned if a network + * disconnection has occurred. + */ + if (sent == -ETIMEDOUT) + { + // In the case of timeout, this send operation can be + // retried. + sent = 0; + } + else if (sent == -ENOTCONN) + { + // The TCP/TLS link is dead + networkContext->isDisconnected = true; + } + + return sent; + } + + /** + * Callback provided to coreMQTT. + * + * Returns the time elapsed in milliseconds since an unspecified epoch. + * + * Note from the coreMQTT documentation: The timer should be a + * monotonic timer. It just needs to provide an incrementing count of + * milliseconds elapsed since a given epoch. + */ + uint32_t get_current_time() + { + // The return value is only a 32-bit integer, so we will + // overflow after 4,294,967,295 ms (which should be about 7 + // weeks). The overflow is not a problem though, as wraparound + // is defined and coreMQTT only uses this for additions and + // substractions, not ordered comparisons. See: + // https://github.com/FreeRTOS/coreMQTT/issues/277 + uint64_t currentCycle = rdcycle64(); + + // Convert to milliseconds + constexpr uint64_t MilliSecondsPerSecond = 1000; + constexpr uint64_t CyclesPerMilliSecond = + CPU_TIMER_HZ / MilliSecondsPerSecond; + static_assert(CyclesPerMilliSecond > 0, + "The CPU frequency is too low for the coreMQTT time " + "function, which provides time in milliseconds."); + uint64_t currentTime = currentCycle / CyclesPerMilliSecond; + + // Truncate into 32 bit + return currentTime & 0xFFFFFFFF; + } + + /** + * Callback provided to coreMQTT. + * + * Note from the coreMQTT documentation: This callback will be called + * on all incoming publishes and incoming acks if deserialized with a + * result of `MQTTSuccess` or `MQTTServerRefused`. The latter can be + * obtained when deserializing a SUBACK, indicating a broker's + * rejection of a subscribe. + */ + void event_callback(MQTTContext_t *coreMQTTContext, + MQTTPacketInfo_t *packetInfo, + MQTTDeserializedInfo_t *deserializedInfo) + { + NetworkContext_t *networkContext = + coreMQTTContext->transportInterface.pNetworkContext; + auto publishCallback = networkContext->publishCallback; + auto ackCallback = networkContext->ackCallback; + auto publishInfo = deserializedInfo->pPublishInfo; + + // The packet type field corresponds to the MQTT Control Packet + // fixed header. In the MQTT Control Packet fixed header, only + // the 4 most significant bits contain the packet type (hence + // the & 0xF0U). The 4 least significant bits contain flags + // specific to each packet type. + uint8_t packetType = packetInfo->type & 0xF0U; + + Debug::log("User callback triggered for packet type {}.", packetType); + + if (packetType == MQTT_PACKET_TYPE_PUBLISH && publishCallback) + { + // This should never fail - if the packet is of type + // PUBLISH, the topic and payload should always be set. + Debug::Assert(publishInfo->pTopicName && publishInfo->pPayload, + "The packet is of type PUBLISH, but topic or payload " + "are not set."); + + // The payload and topic are only valid within the + // context of the callback: make them read-only, + // non-capturable, and limits to the length of the + // topic and payload (up to representability) + Capability topic{publishInfo->pTopicName}; + Capability payload{publishInfo->pPayload}; + topic.permissions() &= CHERI::Permission::Load; + topic.bounds() = publishInfo->topicNameLength; + payload.permissions() &= CHERI::Permission::Load; + payload.bounds().set_inexact(publishInfo->payloadLength); + + /* + * We've had to set the payload bounds inexactly, because payloads + * are often larger than what our capabilities can represent with + * byte-granularity. The way the deserializer works (see + * deserializePublish in coreMQTT/source/core_mqtt_serializer.c) is + * to point into the (reused) packet buffer. That means that bytes + * between payload.base and payload.address are part of the current + * packet (for example, the packet identifier or topic string) while + * bytes between payload.address + payloadLength and payload.top are + * from prior packets. In case our client is multiplexing between + * multiple consumers, zero that region. + * + * Recall that payload.bounds() is top - address, *as if* we could + * precisely set bounds, not top - base. + */ + if (static_cast(payload.bounds()) > + publishInfo->payloadLength) + { + memset(static_cast( + const_cast(publishInfo->pPayload)) + + publishInfo->payloadLength, + 0, + static_cast(payload.bounds()) - + publishInfo->payloadLength); + } + + publishCallback(topic, + publishInfo->topicNameLength, + payload, + publishInfo->payloadLength); + } + else if (ackCallback) + { + bool isReject = false; + if (deserializedInfo->deserializationResult != MQTTSuccess) + { + // This should only ever happen for a SUBACK. + Debug::Assert(packetType == MQTT_PACKET_TYPE_SUBACK, + "The packet deserialization status is " + "MQTTServerRefused but " + "the packet is not of type SUBACK (type {}).", + packetType); + + isReject = true; + } + + ackCallback(deserializedInfo->packetIdentifier, isReject); + } + } +} // namespace + +// Public WolfSSL MQTT API + +WolfSSLMQTTConnection mqtt_wolfssl_connect(Timeout *t, + AllocatorCapability allocator, + ConnectionCapability hostCapability, + WolfSSLMQTTPublishCallback publishCallback, + WolfSSLMQTTAckCallback ackCallback, + const WolfSSLTrustAnchor *trustAnchors, + size_t trustAnchorsCount, + size_t networkBufferSize, + size_t incomingPublishCount, + size_t outgoingPublishCount, + const char *clientID, + size_t clientIDLength, + bool newSession) +{ + if (!check_timeout_pointer(t)) + { + return nullptr; + } + + if (!CHERI::check_pointer(clientID, clientIDLength)) + { + return nullptr; + } + + if constexpr (DebugMQTT) + { + if (!is_valid_client_id({clientID, clientIDLength})) + { + Debug::log("Passed invalid client ID."); + return nullptr; + } + } + + // Allocate MQTT internal buffers as part of the sealed allocation. + // Note: no explicit zero-ing needed here (unlike suggested by + // coreMQTT), we can assume that the allocator zeroes out for us. + size_t handleSize = + sizeof(CHERIoTWolfSSLMqttContext) - + sizeof(CHERIoTWolfSSLMqttContext::variableLengthData) + networkBufferSize + + sizeof(MQTTPubAckInfo_t) * (incomingPublishCount + outgoingPublishCount); + + // Create a sealed MQTT handle. + void *unsealedMQTTHandle; + auto sealedMQTTHandle = token_sealed_unsealed_alloc( + t, allocator, mqtt_wolfssl_key(), handleSize, &unsealedMQTTHandle); + if (sealedMQTTHandle == nullptr) + { + Debug::log("Failed to allocate CHERIoT WolfSSL MQTT context."); + return nullptr; + } + + Debug::log("Created CHERIoT WolfSSL MQTT context sealed with {}.", + mqtt_wolfssl_key()); + + // Set up a TLS stream with the broker. + auto tlsHandle = wolftls_connection_create( + t, allocator, hostCapability, trustAnchors, trustAnchorsCount); + if (!Capability{tlsHandle}.is_valid()) + { + Debug::log("Failed to open wolfSSL TLS stream."); + + // Manually destroy the handle, as we have not yet wrapped it + // into a smart pointer. + token_obj_destroy(allocator, mqtt_wolfssl_key(), sealedMQTTHandle); + return nullptr; + } + + Debug::log("Created wolfSSL TLS stream."); + + // Initialize the sealed MQTT handle. + CHERIoTWolfSSLMqttContext *context = + new (unsealedMQTTHandle) CHERIoTWolfSSLMqttContext{tlsHandle, allocator}; + + // We allocated variable-size data structures at the end of the CHERIoT + // MQTT context. Get pointers to them. + MQTTPubAckInfo_t *incomingPublishes = + reinterpret_cast(&context->variableLengthData); + MQTTPubAckInfo_t *outgoingPublishes = + incomingPublishes + incomingPublishCount; + uint8_t *networkBuffer = reinterpret_cast(outgoingPublishes) + + sizeof(MQTTPubAckInfo_t) * outgoingPublishCount; + + // Initialize context nested structures. + context->networkContext.tlsHandle = tlsHandle; + context->networkContext.publishCallback = publishCallback; + context->networkContext.ackCallback = ackCallback; + context->networkBuffer.pBuffer = networkBuffer; + context->networkBuffer.size = networkBufferSize; + context->transportInterface.recv = transport_recv; + context->transportInterface.send = transport_send; + // TODO we do not support writev yet, which is optional. + context->transportInterface.writev = NULL; + context->transportInterface.pNetworkContext = &context->networkContext; + + auto cleanup = [&](void *) { + // `token_obj_destroy` will free the `CHERIoTMqttContext` + // object through `heap_free`, but not call its destructor. We + // must do that manually. + context->~CHERIoTWolfSSLMqttContext(); + token_obj_destroy(allocator, + mqtt_wolfssl_key(), + static_cast(sealedMQTTHandle)); + }; + std::unique_ptr sealedContext{sealedMQTTHandle, + cleanup}; + + Debug::log("Initializing coreMQTT."); + MQTTStatus_t ret = MQTT_Init(&context->coreMQTTContext, + &context->transportInterface, + get_current_time, + event_callback, + &context->networkBuffer); + + if (ret != MQTTSuccess) + { + Debug::log("Failed to initialize MQTT, error {}.", ret); + + // Note: the sealed context `sealedContext` cleans up after + // itself, closing the TLS link in the process, no need to do + // so explicitly. + return nullptr; + } + + Debug::log("Initializing coreMQTT QoS."); + // Note from the coreMQTT documentation: This function must be called + // on an `MQTTContext_t` after `MQTT_Init` and before any other + // function to initialize an MQTT context for QoS > 0. + // + // We *could* take a qos argument to this function and spare the call + // to `MQTT_InitStatefulQoS` when passed 0, but this would make it + // possible for users to mismatch this function's qos argument and that + // of subsequent API calls. It's safer to call it in any case. + ret = MQTT_InitStatefulQoS(&context->coreMQTTContext, + outgoingPublishes, + outgoingPublishCount, + incomingPublishes, + incomingPublishCount); + + if (ret != MQTTSuccess) + { + Debug::log("Failed to initialize MQTT QoS, error {}.", ret); + return nullptr; + } + + // Prepare the low-level MQTT API connect call. + MQTTConnectInfo_t connectInfo = {0}; + + connectInfo.cleanSession = newSession; + connectInfo.pClientIdentifier = clientID; + connectInfo.clientIdentifierLength = clientIDLength; + + Debug::log("Using client ID {}", + std::string_view(clientID, clientIDLength)); + + // Note: there are a number of optional fields in connectInfo to + // specify a keepalive (`connectInfo.keepAliveSeconds`), a username + // (`pUserName`, `userNameLength`), a password (`pPassword`, + // `passwordLength`), and others. We don't support these for now. + + Debug::log("Connecting to the broker."); + + // `sessionPresent` will be set to true by `MQTT_Connect` if a previous + // session was present; otherwise it will be set to false. It is only + // relevant if not establishing a clean session. + bool sessionPresent; + + do + { + // `remaining` is in milliseconds + uint32_t remaining = (t->remaining * MS_PER_TICK); + ret = with_elapse_timeout(t, [&]() { + return MQTT_Connect(&context->coreMQTTContext, + &connectInfo, + nullptr, + remaining, + &sessionPresent); + }); + + if (ret == MQTTNoMemory || ret == MQTTBadParameter) + { + // If we run OOM, or pass invalid parameters (which is + // likely a bug in this code), retrying won't help. + break; + } + + if (context->networkContext.isDisconnected) + { + // If the link died, retrying won't help. + Debug::log("Connection aborted while connecting."); + break; + } + } while (t->remaining > 0 && ret != MQTTSuccess); + + if (ret != MQTTSuccess) + { + Debug::log("Failed to perform MQTT connect, error {}.", ret); + return nullptr; + } + + if (newSession) + { + Debug::Assert(sessionPresent == false, + "Successfully connected, but a previous session was " + "unexpectedly present (client ID re-used?)."); + } + else + { + Debug::Assert( + sessionPresent == true, + "Successfully connected, but there was no previous session."); + } + + Debug::log("Connected to the broker {}.", hostCapability); + + return static_cast(sealedContext.release()); +} + +int mqtt_wolfssl_disconnect(Timeout *t, + AllocatorCapability allocator, + WolfSSLMQTTConnection mqttHandle) +{ + if (!check_timeout_pointer(t)) + { + return -EINVAL; + } + + // Proceed in two parts. First attempt to disconnect from the broker + // (through `MQTT_Disconnect`). If that fails, e.g., because we are out + // of time or memory, we must consider the connection still open. In + // that case, we return an error. If this succeeds, we proceed to + // terminate the TLS link and free all the memory we allocated. + // + // Note: do not use `with_sendfailed_retry` here, as we need more + // thorough error handling. + int ret = with_sealed_mqtt_context( + t, mqttHandle, [&](CHERIoTWolfSSLMqttContext *connection) { + MQTTContext_t *coreMQTTContext = &connection->coreMQTTContext; + MQTTStatus_t status; + do + { + if (connection->networkContext.isDisconnected) + { + // If the connection is dropped, nothing to + // do here other than tear it down. + return 0; + } + + status = with_elapse_timeout(t, [&]() { + // `MQTT_ProcessLoop` handles keepalive. + return MQTT_Disconnect(coreMQTTContext); + }); + + if (status != MQTTSuccess) + { + Debug::log("MQTT Disconnect failed, error: {}", status); + + if (status == MQTTNoMemory) + { + return -ENOMEM; + } + if (status == MQTTBadParameter) + { + // This shouldn't happen and possibly + // indicates a bug in our code. + Debug::log("MQTT_Disconnect gave -EINVAL, this " + "may indicate a bug in this code."); + return -EINVAL; + } + + Debug::log("MQTT_Disconnect gave unknown error."); + return -EAGAIN; + } + } while (t->remaining > 0 && status != MQTTSuccess); + + if (status != MQTTSuccess) + { + return -ETIMEDOUT; + } + + return 0; + }); + + if (ret < 0) + { + // Disconnecting failed. Leave the TLS link open and return an + // error. + return ret; + } + + // Now terminate the TLS connection and free our resources. This cannot + // fail. + return with_sealed_mqtt_context( + t, + mqttHandle, + [&](CHERIoTWolfSSLMqttContext *connection) { + connection->~CHERIoTWolfSSLMqttContext(); + token_obj_destroy(allocator, mqtt_wolfssl_key(), mqttHandle); + return 0; + }, + true /* grab the context in destruct mode */); +} + +int mqtt_wolfssl_publish(Timeout *t, + WolfSSLMQTTConnection mqttHandle, + uint8_t qos, + const char *topic, + size_t topicLength, + const void *payload, + size_t payloadLength, + bool retain) +{ + if (!CHERI::check_pointer(topic, topicLength)) + { + return -EINVAL; + } + + if (!CHERI::check_pointer(payload, payloadLength)) + { + return -EINVAL; + } + + if (!check_timeout_pointer(t)) + { + return -EINVAL; + } + + if (qos > MQTTQoS2) + { + return -EINVAL; + } + + /** + * Validate the topic (done similarly with the filter in + * `mqtt_subscribe` and `mqtt_unsubscribe`). Without these checks, we + * would send an invalid PUBLISH to the broker, causing our connection + * to be terminated. Since these are only there for convenience / not + * security-relevant, only enable in debug mode. + */ + + if constexpr (DebugMQTT) + { + // Note from the MQTT 3.1.1 spec: 'All Topic Names and Topic Filters + // MUST be at least one character long' + if (topicLength < 1) + { + return -EINVAL; + } + + // Note from the MQTT 3.1.1 spec: 'Topic Names and Topic Filters MUST + // NOT include the null character' + if (has_null_character(topic, topicLength)) + { + return -EINVAL; + } + } + + return with_sealed_mqtt_context( + t, mqttHandle, [&](CHERIoTWolfSSLMqttContext *connection) { + MQTTContext_t *coreMQTTContext = &connection->coreMQTTContext; + MQTTPublishInfo_t publishInfo; + + publishInfo.qos = static_cast(qos); + publishInfo.pTopicName = topic; + publishInfo.topicNameLength = topicLength; + publishInfo.pPayload = payload; + publishInfo.payloadLength = payloadLength; + publishInfo.retain = retain; + + // Packet ID is needed for QoS > 0. + int packetId = MQTT_GetPacketId(coreMQTTContext); + + int ret = + with_sendfailed_retry(t, "MQTT_Publish", connection, [&]() { + return MQTT_Publish(coreMQTTContext, &publishInfo, packetId); + }); + + if (ret == 0) + { + return packetId; + } + + return ret; + }); +} + +int mqtt_wolfssl_subscribe(Timeout *t, + WolfSSLMQTTConnection mqttHandle, + uint8_t qos, + const char *filter, + size_t filterLength) +{ + if (!CHERI::check_pointer(filter, filterLength)) + { + return -EINVAL; + } + + if (!check_timeout_pointer(t)) + { + return -EINVAL; + } + + if (qos > MQTTQoS2) + { + return -EINVAL; + } + + if constexpr (DebugMQTT) + { + if (filterLength < 1) + { + return -EINVAL; + } + + if (has_null_character(filter, filterLength)) + { + return -EINVAL; + } + } + + return with_sealed_mqtt_context( + t, mqttHandle, [&](CHERIoTWolfSSLMqttContext *connection) { + MQTTContext_t *coreMQTTContext = &connection->coreMQTTContext; + + MQTTSubscribeInfo_t subscription; + subscription.qos = static_cast(qos); + subscription.pTopicFilter = filter; + subscription.topicFilterLength = filterLength; + + // Obtain a new packet id for the subscription. + int packetId = MQTT_GetPacketId(coreMQTTContext); + + int ret = + with_sendfailed_retry(t, "MQTT_Subscribe", connection, [&]() { + return MQTT_Subscribe( + coreMQTTContext, &subscription, 1, packetId); + }); + + if (ret == 0) + { + return packetId; + } + + return ret; + }); +} + +int mqtt_wolfssl_unsubscribe(Timeout *t, + WolfSSLMQTTConnection mqttHandle, + uint8_t qos, + const char *filter, + size_t filterLength) +{ + if (!CHERI::check_pointer(filter, filterLength)) + { + return -EINVAL; + } + + if (!check_timeout_pointer(t)) + { + return -EINVAL; + } + + if (qos > MQTTQoS2) + { + return -EINVAL; + } + + if constexpr (DebugMQTT) + { + if (filterLength < 1) + { + return -EINVAL; + } + + if (has_null_character(filter, filterLength)) + { + return -EINVAL; + } + } + + return with_sealed_mqtt_context( + t, mqttHandle, [&](CHERIoTWolfSSLMqttContext *connection) { + MQTTContext_t *coreMQTTContext = &connection->coreMQTTContext; + + MQTTSubscribeInfo_t unsubscribe; + unsubscribe.qos = static_cast(qos); + unsubscribe.pTopicFilter = filter; + unsubscribe.topicFilterLength = filterLength; + + // Obtain a new packet id for the unsubscribe request. + int packetId = MQTT_GetPacketId(coreMQTTContext); + + int ret = + with_sendfailed_retry(t, "MQTT_Unsubscribe", connection, [&]() { + return MQTT_Unsubscribe( + coreMQTTContext, &unsubscribe, 1, packetId); + }); + + if (ret == 0) + { + return packetId; + } + + return ret; + }); +} + +int mqtt_wolfssl_run(Timeout *t, WolfSSLMQTTConnection mqttHandle) +{ + if (!check_timeout_pointer(t)) + { + return -EINVAL; + } + + return with_sealed_mqtt_context( + t, mqttHandle, [&](CHERIoTWolfSSLMqttContext *connection) { + MQTTContext_t *coreMQTTContext = &connection->coreMQTTContext; + MQTTStatus_t status; + + // Note: do not use `with_sendfailed_retry` here, as we need + // more thorough error handling. + do + { + status = with_elapse_timeout(t, [&]() { + // `MQTT_ProcessLoop` handles keepalive. + return MQTT_ProcessLoop(coreMQTTContext); + }); + + if (status != MQTTSuccess) + { + Debug::log("MQTT ProcessLoop failed, error: {}", status); + + if (status == MQTTNoMemory) + { + return -ENOMEM; + } + if (status == MQTTSendFailed || status == MQTTRecvFailed || + status == MQTTNeedMoreBytes) + { + // If the TLS link is still live, try + // again until we are out of time. + if (connection->networkContext.isDisconnected) + { + return -ECONNABORTED; + } + } + else if (status == MQTTBadResponse || + status == MQTTIllegalState || + status == MQTTKeepAliveTimeout) + { + // Something is broken in the + // coreMQTT client, or in the broker. + // Consider this connection dead. + return -ECONNABORTED; + } + else if (status == MQTTBadParameter) + { + // This one shouldn't happen and would + // possibly indicate a bug in our code. + Debug::log("MQTT_ProcessLoop gave -EINVAL, this " + "may indicate a bug in this code."); + return -EINVAL; + } + else + { + Debug::log("MQTT_ProcessLoop gave unknown error."); + return -EAGAIN; + } + } + } while (t->remaining > 0 && status != MQTTSuccess); + + if (status != MQTTSuccess) + { + return -ETIMEDOUT; + } + + return 0; + }); +} + +int mqtt_wolfssl_generate_client_id(char *buffer, size_t length) +{ + auto characters = + std::string_view(ClientIdCharacters, sizeof(ClientIdCharacters) - 1); + + if ((length > MQTTMaximumClientIDSize) || (length == 0)) + { + return -EINVAL; + } + + if (!CHERI::check_pointer(buffer, length)) + { + return -EINVAL; + } + + for (size_t i = 0; i < length; i++) + { + // Select a character at random. + // Note: biased because of the modulo, see API documentation + buffer[i] = characters[rand() % characters.size()]; + } + + Debug::Assert(is_valid_client_id({buffer, length}), + "`mqtt_wolfssl_generate_client_id` generated an invalid " + "client ID."); + + return 0; +} diff --git a/lib/mqtt_wolfssl/xmake.lua b/lib/mqtt_wolfssl/xmake.lua new file mode 100644 index 00000000..2a5ae432 --- /dev/null +++ b/lib/mqtt_wolfssl/xmake.lua @@ -0,0 +1,16 @@ +-- Copyright The Good Penguin Ltd +-- SPDX-License-Identifier: MIT + +debugOption("WolfSSLMQTT") + +compartment("WolfSSLMQTT") + add_rules("cheriot.component-debug") + set_default(false) + add_deps("freestanding", "NetAPI", "WolfSSLTLS") + add_files("mqtt_wolfssl.cc") + add_defines("CHERIOT_NO_AMBIENT_MALLOC", "CHERIOT_NO_NEW_DELETE") + add_includedirs(".", "../mqtt", "../../include", "../../third_party/coreMQTT/source/include", + "../../third_party/coreMQTT/source/interface") + add_files("../../third_party/coreMQTT/source/core_mqtt.c", + "../../third_party/coreMQTT/source/core_mqtt_serializer.c", + "../../third_party/coreMQTT/source/core_mqtt_state.c") diff --git a/lib/wolfssl/tls_wolfssl.cc b/lib/wolfssl/tls_wolfssl.cc new file mode 100644 index 00000000..490abd15 --- /dev/null +++ b/lib/wolfssl/tls_wolfssl.cc @@ -0,0 +1,500 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "wolfssl_glue.h" + +using namespace CHERI; + +constexpr bool DebugWolfTLS = +#ifdef DEBUG_WolfSSLTLS + DEBUG_WolfSSLTLS +#else + false +#endif + ; + +using Debug = ConditionalDebug; + +struct WolfSSLContext +{ + WOLFSSL_CTX *sslCtx; + WOLFSSL *ssl; + AllocatorCapability allocator; + Socket socket; + FlagLockPriorityInherited lock; + + WolfSSLContext() : sslCtx(nullptr), ssl(nullptr) {} + ~WolfSSLContext(); +}; + +WolfSSLContext::~WolfSSLContext() +{ + if (ssl) + wolfSSL_free(ssl); + if (sslCtx) + wolfSSL_CTX_free(sslCtx); + Timeout t{UnlimitedTimeout}; + network_socket_close(&t, allocator, socket); + wolfSSL_Cleanup(); +} + +namespace +{ + __always_inline auto wolf_key() + { + return STATIC_SEALING_TYPE(WolfSSLConnection); + } + + // IO callbacks bridging wolfSSL record I/O to CHERIoT NetAPI sockets. + extern "C" int wolfssl_io_recv(WOLFSSL *, char *buf, int sz, void *ctx) + { + auto *c = static_cast(ctx); + // Use a local Timeout so check_timeout_pointer in the network + // compartment receives a stack-derived capability. + Timeout localT{0}; + if (heap_claim_ephemeral(&localT, buf) != 0) + return WOLFSSL_CBIO_ERR_GENERAL; + Capability rbuf{buf}; + rbuf.bounds().set_inexact_at_most(sz); + sz = static_cast(rbuf.length()); + rbuf.permissions() &= Permission::Store; + Debug::log("Receiving {} bytes into {}", sz, rbuf); + int r = + network_socket_receive_preallocated(&localT, c->socket, rbuf, sz); + Debug::log("Network stack returned {}", r); + if (r > 0) + return r; + if (r == -ENOTCONN) + return WOLFSSL_CBIO_ERR_CONN_CLOSE; + if (r == -ETIMEDOUT) + return WOLFSSL_CBIO_ERR_WANT_READ; + return WOLFSSL_CBIO_ERR_GENERAL; + } + + extern "C" int wolfssl_io_send(WOLFSSL *, char *buf, int sz, void *ctx) + { + auto *c = static_cast(ctx); + // Use a local Timeout so check_timeout_pointer in the network + // compartment receives a stack-derived capability. + Timeout localT{0}; + if (heap_claim_ephemeral(&localT, buf) != 0) + return WOLFSSL_CBIO_ERR_GENERAL; + Capability rbuf{buf}; + rbuf.bounds().set_inexact_at_most(sz); + sz = static_cast(rbuf.length()); + rbuf.permissions() &= Permission::Load; + Debug::log("Sending {} bytes of records", sz); + int r = network_socket_send(&localT, + c->socket, + rbuf, + sz); + Debug::log("Send returned {}", r); + if (r > 0) + return r; + if (r == -ETIMEDOUT) + return WOLFSSL_CBIO_ERR_WANT_WRITE; + if (r == -ENOTCONN) + return WOLFSSL_CBIO_ERR_CONN_CLOSE; + return WOLFSSL_CBIO_ERR_GENERAL; + } + + ssize_t with_sealed_context(Timeout *t, + WolfSSLConnection conn, + auto fn) + { + Sealed sealedCtx{conn}; + auto *ctx = token_unseal(wolf_key(), sealedCtx); + if (!ctx) + { + Debug::log("Failed to unseal WolfSSL context"); + return -EINVAL; + } + Debug::log("unseal:ctx ptr=0x{:x}", (uintptr_t)ctx); + if (LockGuard g{ctx->lock, t}) + { + wolfssl_set_fallback_heap(ctx->allocator); + return fn(ctx); + } + Debug::log("Timed out acquiring WolfSSL context lock"); + return -ETIMEDOUT; + } + +} // namespace + +WolfSSLConnection wolftls_connection_create(Timeout *t, + AllocatorCapability allocator, + ConnectionCapability connectionCapability, + const WolfSSLTrustAnchor *trustAnchors, + size_t trustAnchorsCount) +{ + Debug::log("wolftls_connection_create: entered, t={}", t); + if (!check_timeout_pointer(t)) + { + Debug::log("wolftls_connection_create: check_timeout_pointer failed"); + return nullptr; + } + + if (wolfSSL_Init() != WOLFSSL_SUCCESS) + { + Debug::log("wolfSSL_Init failed"); + return nullptr; + } + + const char *hostname = network_host_get(connectionCapability); + if (!hostname) + { + Debug::log("Failed to get hostname from connection capability"); + return nullptr; + } + + auto socketDeleter = [&](Socket s) { + Timeout unlimited{UnlimitedTimeout}; + network_socket_close(&unlimited, allocator, s); + t->elapse(unlimited.elapsed); + }; + SealedOwner socket{ + network_socket_connect_tcp(t, allocator, connectionCapability), socketDeleter}; + if (!socket) + { + Debug::log("TCP connect failed"); + return nullptr; + } + + wolfssl_set_fallback_heap(allocator); + + WOLFSSL_CTX *sslCtx = + wolfSSL_CTX_new_ex(wolfTLSv1_3_client_method_ex(allocator), allocator); + if (!sslCtx) + { + Debug::log("wolfSSL_CTX_new_ex failed"); + return nullptr; + } + auto ctxDeleter = [](WOLFSSL_CTX *c) { wolfSSL_CTX_free(c); }; + std::unique_ptr sslCtxOwner{sslCtx, + ctxDeleter}; + + wolfSSL_CTX_SetIORecv(sslCtx, wolfssl_io_recv); + wolfSSL_CTX_SetIOSend(sslCtx, wolfssl_io_send); + wolfSSL_CTX_set_verify(sslCtx, WOLFSSL_VERIFY_PEER, nullptr); + + for (size_t i = 0; i < trustAnchorsCount; ++i) + { + int r = wolfSSL_CTX_load_verify_buffer(sslCtx, + trustAnchors[i].certDer, + (long)trustAnchors[i].certDerLength, + WOLFSSL_FILETYPE_ASN1); + if (r != WOLFSSL_SUCCESS) + { + Debug::log("Failed to load trust anchor {}: {}", i, r); + return nullptr; + } + } + + WOLFSSL *ssl = wolfSSL_new(sslCtx); + if (!ssl) + { + Debug::log("wolfSSL_new failed"); + return nullptr; + } + auto sslDeleter = [](WOLFSSL *s) { wolfSSL_free(s); }; + std::unique_ptr sslOwner{ssl, sslDeleter}; + + if (wolfSSL_check_domain_name(ssl, hostname) != WOLFSSL_SUCCESS) + { + Debug::log("wolfSSL_check_domain_name failed"); + return nullptr; + } + if (wolfSSL_UseSNI(ssl, + WOLFSSL_SNI_HOST_NAME, + hostname, + (word16)strlen(hostname)) != WOLFSSL_SUCCESS) + { + Debug::log("wolfSSL_UseSNI failed"); + return nullptr; + } + + auto [unsealed, sealed] = + token_allocate(t, allocator, wolf_key()); + auto rawSealed = sealed.get(); + if (rawSealed == nullptr) + { + Debug::log("Failed to allocate sealed WolfSSL context"); + return nullptr; + } + + WolfSSLContext *ctx = new (unsealed) WolfSSLContext{}; + ctx->sslCtx = sslCtxOwner.release(); + ctx->ssl = sslOwner.release(); + ctx->allocator = allocator; + ctx->socket = socket.release(); + + auto cleanup = [&](decltype(sealed)) { + ctx->~WolfSSLContext(); + token_obj_destroy(allocator, wolf_key(), rawSealed); + }; + SealedOwner sealedCtx{rawSealed, + cleanup}; + + wolfSSL_SetIOReadCtx(ssl, ctx); + wolfSSL_SetIOWriteCtx(ssl, ctx); + wolfssl_set_fallback_heap(allocator); + + // Handshake loop, wolfSSL_connect drives the TLS 1.3 state machine, + // calling wolfssl_io_recv/send as needed. + while (true) + { + int ret = wolfSSL_connect(ssl); + if (ret == WOLFSSL_SUCCESS) + break; + int err = wolfSSL_get_error(ssl, ret); + if (err == WOLFSSL_ERROR_WANT_READ || err == WOLFSSL_ERROR_WANT_WRITE) + { + if (t->may_block()) + { + Timeout shortSleep{1}; + thread_sleep(&shortSleep); + t->elapse(shortSleep.elapsed); + continue; + } + Debug::log("Handshake timed out"); + return nullptr; + } + Debug::log("Handshake failed, wolfSSL error {}", err); + return nullptr; + } + + Debug::log("TLS 1.3 handshake complete for {}", hostname); + return sealedCtx.release(); +} + +ssize_t wolftls_connection_send(Timeout *t, + WolfSSLConnection sealedConnection, + void *buffer, + size_t length) +{ + if (!check_timeout_pointer(t)) + return -EINVAL; + + return with_sealed_context(t, sealedConnection, [&](WolfSSLContext *ctx) -> ssize_t { + int ret = heap_claim_ephemeral(t, buffer); + if (ret != 0) + return ret; + if (!check_pointer(buffer, length)) + return -EPERM; + + size_t sent = 0; + while (sent < length) + { + Debug::log("wolfSSL_write {} bytes", length - sent); + int r = wolfSSL_write( + ctx->ssl, static_cast(buffer) + sent, length - sent); + Debug::log("wolfSSL_write returned {}", r); + if (r > 0) + { + sent += r; + continue; + } + int err = wolfSSL_get_error(ctx->ssl, r); + if (err == WOLFSSL_ERROR_WANT_WRITE || err == WOLFSSL_ERROR_WANT_READ) + { + if (t->may_block()) + { + Timeout shortSleep{1}; + thread_sleep(&shortSleep); + t->elapse(shortSleep.elapsed); + continue; + } + break; + } + Debug::log("wolfSSL_write error {}", err); + return sent > 0 ? static_cast(sent) : -EIO; + } + return static_cast(sent); + }); +} + +int wolftls_connection_receive_preallocated(Timeout *t, + WolfSSLConnection sealedConnection, + void *buffer, + size_t length) +{ + if (!check_timeout_pointer(t)) + return -EINVAL; + + return with_sealed_context(t, sealedConnection, [&](WolfSSLContext *ctx) -> ssize_t { + int ret = heap_claim_ephemeral(t, buffer); + if (ret != 0) + return ret; + if (!check_pointer(buffer, length)) + return -EPERM; + + Debug::log("wolfSSL_read into {} byte buffer", length); + while (true) + { + int r = wolfSSL_read(ctx->ssl, buffer, (int)length); + Debug::log("wolfSSL_read returned {}", r); + if (r > 0) + return r; + int err = wolfSSL_get_error(ctx->ssl, r); + if (err == WOLFSSL_ERROR_WANT_READ) + { + if (t->may_block()) + { + Timeout shortSleep{1}; + thread_sleep(&shortSleep); + t->elapse(shortSleep.elapsed); + continue; + } + return -ETIMEDOUT; + } + if (err == WOLFSSL_ERROR_ZERO_RETURN) + return 0; + Debug::log("wolfSSL_read error {}", err); + return -ENOTCONN; + } + }); +} + +NetworkReceiveResult wolftls_connection_receive(Timeout *t, + WolfSSLConnection sealedConnection) +{ + uint8_t *buffer = nullptr; + ssize_t result = + with_sealed_context(t, sealedConnection, [&](WolfSSLContext *ctx) -> ssize_t { + // If the heap is near exhaustion don't block + Timeout zeroTimeout{0}; + int available = 4096; + while (true) + { + buffer = static_cast( + heap_allocate(&zeroTimeout, ctx->allocator, available)); + t->elapse(zeroTimeout.elapsed); + if (Capability{buffer}.is_valid()) + break; + if (available > 128) + { + available = 128; + continue; + } + auto quota = heap_quota_remaining(ctx->allocator); + if (quota > 16 && (size_t)available > quota - 16) + { + available = quota - 16; + continue; + } + return t->may_block() ? -ENOMEM : -ETIMEDOUT; + } + + Debug::log("wolfSSL_read into {} byte buffer", available); + while (true) + { + int r = wolfSSL_read(ctx->ssl, buffer, available); + Debug::log("wolfSSL_read returned {}", r); + if (r > 0) + return r; + int err = wolfSSL_get_error(ctx->ssl, r); + if (err == WOLFSSL_ERROR_WANT_READ) + { + if (t->may_block()) + { + Timeout shortSleep{1}; + thread_sleep(&shortSleep); + t->elapse(shortSleep.elapsed); + continue; + } + heap_free(ctx->allocator, buffer); + buffer = nullptr; + return -ETIMEDOUT; + } + if (err == WOLFSSL_ERROR_ZERO_RETURN) + { + heap_free(ctx->allocator, buffer); + buffer = nullptr; + return 0; + } + Debug::log("wolfSSL_read error {}", err); + heap_free(ctx->allocator, buffer); + buffer = nullptr; + return -ENOTCONN; + } + }); + return {result, buffer}; +} + +int wolftls_connection_close(Timeout *t, WolfSSLConnection sealed) +{ + if (!check_timeout_pointer(t)) + return -EINVAL; + + Sealed sealedCtx{sealed}; + auto *ctx = token_unseal(wolf_key(), sealedCtx); + if (!ctx) + { + Debug::log("close: failed to unseal"); + return -EINVAL; + } + + if (!ctx->lock.try_lock(t)) + return -ETIMEDOUT; + + wolfssl_set_fallback_heap(ctx->allocator); + + // wolfSSL_shutdown drives the close_notify exchange, + // calling wolfssl_io_recv/send as needed, same pattern as the handshake. + while (true) + { + int ret = wolfSSL_shutdown(ctx->ssl); + if (ret == WOLFSSL_SUCCESS) + break; + if (ret == WOLFSSL_SHUTDOWN_NOT_DONE) + { + // Local close_notify sent; waiting for peer's reply. + if (t->may_block()) + { + Timeout shortSleep{1}; + thread_sleep(&shortSleep); + t->elapse(shortSleep.elapsed); + continue; + } + Debug::log("Timed out waiting for peer close_notify"); + break; + } + int err = wolfSSL_get_error(ctx->ssl, ret); + if (err == WOLFSSL_ERROR_WANT_READ || err == WOLFSSL_ERROR_WANT_WRITE) + { + if (t->may_block()) + { + Timeout shortSleep{1}; + thread_sleep(&shortSleep); + t->elapse(shortSleep.elapsed); + continue; + } + Debug::log("Timed out during TLS shutdown"); + break; + } + // SOCKET_ERROR_E / SOCKET_PEER_CLOSED_E: TCP was already torn down + // by the peer after it sent its close_notify + if (err == SOCKET_ERROR_E || err == SOCKET_PEER_CLOSED_E) + break; + Debug::log("wolfSSL_shutdown error {}", err); + break; + } + + ctx->lock.upgrade_for_destruction(); + auto alloc = ctx->allocator; + ctx->~WolfSSLContext(); + token_obj_destroy(alloc, wolf_key(), sealed); + return 0; +} diff --git a/lib/wolfssl/user_settings.h b/lib/wolfssl/user_settings.h new file mode 100644 index 00000000..509a8b69 --- /dev/null +++ b/lib/wolfssl/user_settings.h @@ -0,0 +1,540 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT +// +// Based on wolfSSL examples/configs/user_settings_template.h +// +// Algorithm/feature selections follow the template defaults +// CHERIoT platform-porting changes are applied + +#ifndef WOLFSSL_USER_SETTINGS_H +#define WOLFSSL_USER_SETTINGS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* CHERIoT is always an embedded target */ +#define TARGET_EMBEDDED + +/* ------------------------------------------------------------------------- */ +/* Platform */ +/* ------------------------------------------------------------------------- */ +#define WOLFSSL_GENERAL_ALIGNMENT 8 /* CHERIoT capabilities are 8-byte aligned */ +#define SIZEOF_LONG_LONG 8 +/* Otherwise wc_ptr_t becomes size_t and causes issues with round-trip casts*/ +#define HAVE_UINTPTR_T +#if 0 /* Disable 64-bit types */ + #define NO_64BIT +#endif + +#ifdef TARGET_EMBEDDED + /* Controlled via xmake option "wolfssl-single-threaded" (default off). + * When off: WOLFSSL_USER_MUTEX suppresses the pthreads fallback in wc_port.h. + * wolfssl_glue.cc provides the wc_*Mutex implementations. + * When on: wolfSSL compiles its own no-op mutex stubs. Safe only when the + * WolfSSLTLS compartment is called from a single thread at a time. */ + #ifndef SINGLE_THREADED + #define WOLFSSL_USER_MUTEX + typedef struct { uint32_t lockWord; } wolfSSL_Mutex; + #endif + + /* Controlled via xmake option "wolfssl-small-stack" (default off). + * When on: Per-function crypto buffers are allocated on the heap via XMALLOC. + * wolfssl_glue provides a fallback heap for the NULL heap hints this generates; + * wolfssl_set_fallback_heap() must be called before use. + * When off: all XMALLOC callers must pass a valid heap pointer; NULL heap + * is an error and returns NULL immediately. */ + //#define WOLFSSL_SMALL_STACK + + /* Force ASN template setters (GetASN_Int8Bit, GetASN_Buffer, etc.) to + allocate onto the stack. Otherwise, these store stack derived + pointers on the heap which will cause a tag violation on CHERIoT. + Only comes into play if WOLFSSL_SMALL_STACK is defined. + */ + #define WOLFSSL_ASN_TEMPLATE_STACK_ALLOC + + /* Disable the built-in socket support and use the IO callbacks. + * Set IO callbacks with wolfSSL_CTX_SetIORecv/wolfSSL_CTX_SetIOSend + */ + #define WOLFSSL_USER_IO + + /* No BSD socket glue on CHERIoT */ + #define WOLFSSL_NO_SOCK +#endif + +/* ------------------------------------------------------------------------- */ +/* Math Configuration */ +/* ------------------------------------------------------------------------- */ +/* Wolf Single Precision Math */ +#if 1 /* SP Math (recommended) */ + #define WOLFSSL_HAVE_SP_RSA + #define WOLFSSL_HAVE_SP_DH + #define WOLFSSL_HAVE_SP_ECC + //#define WOLFSSL_SP_4096 /* Enable RSA/RH 4096-bit support */ + #define WOLFSSL_SP_384 /* Enable ECC 384-bit SECP384R1 support */ + + //#define WOLFSSL_SP_MATH /* only SP math - disables integer.c/tfm.c */ + #define WOLFSSL_SP_MATH_ALL /* use SP math for all key sizes and curves */ + + //#define WOLFSSL_SP_NO_MALLOC + //#define WOLFSSL_SP_DIV_32 /* do not use 64-bit divides */ + + #ifdef TARGET_EMBEDDED + /* use smaller version of code */ + #define WOLFSSL_SP_SMALL + #endif + + /* CHERIoT target is RISC-V 32-bit */ + //#define SP_WORD_SIZE 32 + + /* SP Assembly Speedups - specific to chip type */ + //#define WOLFSSL_SP_ASM + //#define WOLFSSL_SP_X86_64 + //#define WOLFSSL_SP_X86 + //#define WOLFSSL_SP_ARM32_ASM + //#define WOLFSSL_SP_ARM64_ASM + //#define WOLFSSL_SP_ARM_THUMB_ASM + //#define WOLFSSL_SP_ARM_CORTEX_M_ASM +#elif 1 + /* Fast Math (tfm.c) (stack based and timing resistant) */ + #define USE_FAST_MATH + #define TFM_TIMING_RESISTANT +#else + /* Normal (integer.c) (heap based, not timing resistant) - not recommended */ + #define USE_INTEGER_HEAP_MATH +#endif + + +/* ------------------------------------------------------------------------- */ +/* Crypto */ +/* ------------------------------------------------------------------------- */ +/* RSA */ +#undef NO_RSA +#if 1 /* RSA */ + #ifdef USE_FAST_MATH + /* Maximum math bits (Max RSA key bits * 2) */ + #define FP_MAX_BITS 4096 + #endif + + /* half as much memory but twice as slow */ + //#define RSA_LOW_MEM + + /* Enables blinding mode, to prevent timing attacks */ + #define WC_RSA_BLINDING + + /* RSA PSS Support */ + #define WC_RSA_PSS +#else + #define NO_RSA +#endif + +/* DH */ +#undef NO_DH +#if 1 /* DH */ + /* Use table for DH instead of -lm (math) lib */ + #if 1 /* FFDHE parameters */ + #define WOLFSSL_DH_CONST + #define HAVE_FFDHE_2048 + //#define HAVE_FFDHE_4096 + //#define HAVE_FFDHE_6144 + //#define HAVE_FFDHE_8192 + #endif +#else + #define NO_DH +#endif + +/* ECC */ +#undef HAVE_ECC +#if 1 /* ECC */ + #define HAVE_ECC + + /* Manually define enabled curves */ + #define ECC_USER_CURVES + + #ifdef ECC_USER_CURVES + /* Manual Curve Selection */ + //#define HAVE_ECC192 + //#define HAVE_ECC224 + #undef NO_ECC256 + #define HAVE_ECC384 + //#define HAVE_ECC521 + #endif + + /* Fixed point cache (speeds repeated operations against same private key) */ + //#define FP_ECC + #ifdef FP_ECC + /* Bits / Entries */ + #define FP_ENTRIES 2 + #define FP_LUT 4 + #endif + + /* Optional ECC calculation method */ + /* Note: doubles heap usage, but slightly faster */ + #define ECC_SHAMIR + + /* Reduces heap usage, but slower */ + #define ECC_TIMING_RESISTANT + + /* Compressed ECC Key Support */ + //#define HAVE_COMP_KEY + + /* Use alternate ECC size for ECC math */ + #ifdef USE_FAST_MATH + /* MAX ECC BITS = ROUND8(MAX ECC) * 2 */ + #if defined(NO_RSA) && defined(NO_DH) + /* Custom fastmath size if not using RSA/DH */ + #define FP_MAX_BITS (256 * 2) + #else + /* use heap allocation for ECC points */ + #define ALT_ECC_SIZE + + /* wolfSSL will compute the FP_MAX_BITS_ECC, but it can be overridden */ + //#define FP_MAX_BITS_ECC (256 * 2) + #endif + + /* Speedups specific to curve */ + #ifndef NO_ECC256 + #define TFM_ECC256 + #endif + #endif +#endif + + +/* AES */ +#undef NO_AES +#if 1 /* AES */ + #define HAVE_AES_CBC + + /* GCM Method: GCM_TABLE_4BIT, GCM_SMALL, GCM_WORD32 or GCM_TABLE */ + #define HAVE_AESGCM + #ifdef TARGET_EMBEDDED + #define GCM_SMALL + #else + #define GCM_TABLE_4BIT + #endif + + //#define WOLFSSL_AES_DIRECT + //#define HAVE_AES_ECB + //#define WOLFSSL_AES_COUNTER + //#define HAVE_AESCCM +#else + #define NO_AES +#endif + + +/* DES3 */ +#undef NO_DES3 +#if 0 /* DES3 (legacy, not recommended) */ +#else + #define NO_DES3 +#endif + +/* ChaCha20 / Poly1305 */ +#undef HAVE_CHACHA +#undef HAVE_POLY1305 +#if 1 /* ChaCha20 / Poly1305 */ + #define HAVE_CHACHA + #define HAVE_POLY1305 + + /* Needed for Poly1305 */ + #define HAVE_ONE_TIME_AUTH +#endif + +/* Ed25519 / Curve25519 */ +#undef HAVE_CURVE25519 +#undef HAVE_ED25519 +#if 0 /* Ed25519 / Curve25519 */ + #define HAVE_CURVE25519 + #define HAVE_ED25519 /* ED25519 Requires SHA512 */ + + /* Optionally use small math (less flash, slower) */ + #if 1 /* Small Curve25519 */ + #define CURVED25519_SMALL + #endif +#endif + + +/* ------------------------------------------------------------------------- */ +/* Hashing */ +/* ------------------------------------------------------------------------- */ +/* Sha */ +#undef NO_SHA +#if 1 /* SHA-1 */ + /* 1k smaller, but 25% slower */ + //#define USE_SLOW_SHA +#else + #define NO_SHA +#endif + +/* Sha256 */ +#undef NO_SHA256 +#if 1 /* SHA-256 */ + /* not unrolled - ~2k smaller and ~25% slower */ + //#define USE_SLOW_SHA256 + + /* SHA-224 (requires SHA-256) */ + #if 0 /* SHA-224 */ + #define WOLFSSL_SHA224 + #endif +#else + #define NO_SHA256 +#endif + +/* Sha512 */ +#undef WOLFSSL_SHA512 +#if 1 + #define WOLFSSL_SHA512 + + /* SHA-384 (requires SHA-512) */ + #undef WOLFSSL_SHA384 + #if 1 /* SHA-384 */ + #define WOLFSSL_SHA384 + #endif + + /* over twice as small, but 50% slower */ + //#define USE_SLOW_SHA512 +#endif + +/* Sha3 */ +#undef WOLFSSL_SHA3 +#if 0 /* SHA-3 */ + #define WOLFSSL_SHA3 +#endif + +/* MD5 */ +#undef NO_MD5 +#if 0 /* MD5 (legacy, not recommended) */ + /* MD5 enabled */ +#else + #define NO_MD5 +#endif + +/* HKDF */ +#undef HAVE_HKDF +#if 1 /* HKDF (TLS 1.3 requires this) */ + #define HAVE_HKDF +#endif + +/* CMAC */ +#undef WOLFSSL_CMAC +#if 0 /* CMAC */ + #define WOLFSSL_CMAC +#endif + + +/* ------------------------------------------------------------------------- */ +/* Benchmark / Test */ +/* ------------------------------------------------------------------------- */ +#ifdef TARGET_EMBEDDED + /* Use reduced benchmark / test sizes */ + #define BENCH_EMBEDDED +#endif + +/* Use test buffers from array (not filesystem) */ +#ifndef NO_FILESYSTEM +#define USE_CERT_BUFFERS_256 +#define USE_CERT_BUFFERS_2048 +#endif + +/* ------------------------------------------------------------------------- */ +/* Debugging */ +/* ------------------------------------------------------------------------- */ + +#undef DEBUG_WOLFSSL +#undef NO_ERROR_STRINGS +#if 0 /* Enable debug logging */ + #define DEBUG_WOLFSSL +#else + #if 0 /* Disable error strings to save flash */ + #define NO_ERROR_STRINGS + #endif +#endif + + +/* ------------------------------------------------------------------------- */ +/* Memory */ +/* ------------------------------------------------------------------------- */ + +/* CHERIoT custom allocators - XMALLOC/XFREE/XREALLOC in wolfssl_glue.cc + * use the void* heap hint (caller's AllocatorCapability cast to void*) to + * call heap_allocate/heap_free. */ +#define XMALLOC_USER + +#if 0 /* Static memory (no heap) */ + #define WOLFSSL_STATIC_MEMORY + + /* Disable fallback malloc/free */ + #define WOLFSSL_NO_MALLOC + #if 1 /* Trap malloc failure */ + #define WOLFSSL_MALLOC_CHECK /* trap malloc failure */ + #endif +#endif + +/* Memory callbacks */ +#if 0 /* wolfSSL memory callbacks */ + #undef USE_WOLFSSL_MEMORY + #define USE_WOLFSSL_MEMORY + + /* Use this to measure / print heap usage */ + #if 0 /* Memory tracking / debug */ + #define WOLFSSL_TRACK_MEMORY + #define WOLFSSL_DEBUG_MEMORY + #endif +#else + #ifndef WOLFSSL_STATIC_MEMORY + #define NO_WOLFSSL_MEMORY + /* Otherwise we will use stdlib malloc, free and realloc */ + #endif +#endif + + +/* ------------------------------------------------------------------------- */ +/* Port */ +/* ------------------------------------------------------------------------- */ + +/* CHERIoT custom time function */ +#define USER_TIME +#define XTIME(t) cheriot_wolfssl_time(t) +#define XGMTIME(c, t) gmtime(c) + + +/* ------------------------------------------------------------------------- */ +/* RNG */ +/* ------------------------------------------------------------------------- */ + +/* P-RNG with hash DRBG seeded by CHERIoT entropy source */ +extern int cheriot_wolfssl_seed(unsigned char *output, unsigned int sz); +#undef CUSTOM_RAND_GENERATE_SEED +#define CUSTOM_RAND_GENERATE_SEED cheriot_wolfssl_seed + +#undef HAVE_HASHDRBG +#define HAVE_HASHDRBG + + +/* ------------------------------------------------------------------------- */ +/* Custom Standard Lib */ +/* ------------------------------------------------------------------------- */ +/* STRING_USER: take ownership of ALL wolfSSL string/memory macros. + * types.h defines XSTRNCAT unconditionally (no #ifndef guard) inside the + * #ifndef STRING_USER block, overriding any earlier definition. The only + * correct way to prevent that is STRING_USER. */ +#define STRING_USER + +#include + +/* Memory, all available in CHERIoT freestanding string.h */ +#define XMEMCPY(d,s,l) memcpy((d),(s),(l)) +#define XMEMSET(b,c,l) memset((b),(c),(l)) +#define XMEMCMP(s1,s2,n) memcmp((s1),(s2),(n)) +#define XMEMMOVE(d,s,l) memmove((d),(s),(l)) + +/* String basics, all available in CHERIoT freestanding string.h */ +#define XSTRLEN(s1) strlen((s1)) +#define XSTRNCPY(s1,s2,n) strncpy((s1),(s2),(n)) +#define XSTRSTR(s1,s2) strstr((s1),(s2)) +#define XSTRNSTR(s1,s2,n) strnstr((s1),(s2),(n)) +#define XSTRNCMP(s1,s2,n) strncmp((s1),(s2),(n)) +#define XSTRCMP(s1,s2) strcmp((s1),(s2)) + +/* strncat absent from CHERIoT freestanding libc - provided in wolfssl_glue.cc */ +extern char *cheriot_wolfssl_strncat(char *dst, const char *src, unsigned int n); +#define XSTRNCAT(s1,s2,n) cheriot_wolfssl_strncat((s1),(s2),(n)) + +/* strsep/strtok use wolfSSL's own implementations */ +#define USE_WOLF_STRSEP +#define USE_WOLF_STRTOK +#define XSTRSEP(s1,d) wc_strsep((s1),(d)) +#define XSTRTOK(s1,d,ptr) wc_strtok((s1),(d),(ptr)) + +/* strcasecmp / strncasecmp absent from CHERIoT freestanding libc - wolfssl_glue.cc */ +extern int cheriot_wolfssl_strcasecmp(const char *s1, const char *s2); +#define XSTRCASECMP(s1,s2) cheriot_wolfssl_strcasecmp((s1),(s2)) +extern int cheriot_wolfssl_strncasecmp(const char *s1, const char *s2, unsigned int n); +#define XSTRNCASECMP(s1,s2,n) cheriot_wolfssl_strncasecmp((s1),(s2),(n)) + +/* atoi absent from CHERIoT freestanding libc - provided in wolfssl_glue.cc */ +extern int cheriot_wolfssl_atoi(const char *s); +#define XATOI(s) cheriot_wolfssl_atoi((s)) + +#define XSNPRINTF snprintf + +/* No in CHERIoT environment sp block the stdatomic include path */ +#define NO_STDATOMIC_H + +/* ------------------------------------------------------------------------- */ +/* Enable Features */ +/* ------------------------------------------------------------------------- */ + +#define WOLFSSL_TLS13 +#define WOLFSSL_OLD_PRIME_CHECK /* Use faster DH prime checking */ +#define HAVE_TLS_EXTENSIONS +#define HAVE_SNI +#define HAVE_SUPPORTED_CURVES +#define WOLFSSL_BASE64_ENCODE + +//#define WOLFSSL_KEY_GEN /* For RSA Key gen only */ +//#define KEEP_PEER_CERT +//#define HAVE_COMP_KEY + +/* TLS Session Cache */ +#if 0 /* Small session cache */ + #define SMALL_SESSION_CACHE +#else + #define NO_SESSION_CACHE +#endif + +/* Store cipher-suite names as char[] arrays rather than const char* pointers. + * The CHERIoT LLVM backend cannot initialise a global array of capability + * pointers at link time; this avoids the resulting crash on cipher lookup. */ +#define WOLFSSL_NAMES_STATIC + +/* Keep SHA-256 message schedule on the stack to avoid XMALLOC round-trips. */ +#define SHA256_MANY_REGISTERS + + +/* ------------------------------------------------------------------------- */ +/* Disable Features */ +/* ------------------------------------------------------------------------- */ +//#define NO_WOLFSSL_SERVER +//#define NO_WOLFSSL_CLIENT +//#define NO_CRYPT_TEST +//#define NO_CRYPT_BENCHMARK +//#define WOLFCRYPT_ONLY + +/* do not warn when file is included to be built and not required to be */ +#define WOLFSSL_IGNORE_FILE_WARN + +/* In-lining of misc.c functions */ +/* If defined, must include wolfcrypt/src/misc.c in build */ +/* Slower, but about 1k smaller */ +//#define NO_INLINE + +#ifdef TARGET_EMBEDDED + #define NO_FILESYSTEM + #define NO_WRITEV + #define NO_MAIN_DRIVER + #define NO_DEV_RANDOM +#endif + +/* CHERIoT has no stdio filesystem */ +#define NO_STDIO_FILESYSTEM + +#define NO_OLD_TLS +#define NO_PSK + +#define NO_DSA +#define NO_RC4 +#define NO_MD4 +#define NO_PWDBASED +//#define NO_CODING +//#define NO_ASN_TIME +//#define NO_CERTS +//#define NO_SIG_WRAPPER + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSSL_USER_SETTINGS_H */ diff --git a/lib/wolfssl/wolfssl_glue.cc b/lib/wolfssl/wolfssl_glue.cc new file mode 100644 index 00000000..7329ea63 --- /dev/null +++ b/lib/wolfssl/wolfssl_glue.cc @@ -0,0 +1,241 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT + +// CHERIoT headers first - prevents the logging.h extern-"C" + +// cheri-builtins.h template collision when wolfSSL headers follow. +#include +#include +#include + +#include + +#include +#ifndef SINGLE_THREADED +#include +#include +#endif + +#include "wolfssl_glue.h" + +constexpr bool DebugWolfGlue = +#ifdef DEBUG_WOLFSSLTLS + DEBUG_WOLFSSLTLS +#else + false +#endif + ; + +using GlueDebug = ConditionalDebug; +using namespace CHERI; + +extern "C" +{ + +// This CHERIoT EntropySource is why this is a .cc +uint64_t cheriot_wolfssl_get_entropy() +{ + static EntropySource source; + return source(); +} + +// Seed callback - called by wolfSSL DRBG via CUSTOM_RAND_GENERATE_SEED. +int cheriot_wolfssl_seed(unsigned char *output, unsigned int sz) +{ + unsigned int i = 0; + while (i < sz) + { + uint64_t e = cheriot_wolfssl_get_entropy(); + unsigned int chunk = sz - i < 8 ? sz - i : 8; + memcpy(output + i, &e, chunk); + i += chunk; + } + return 0; +} + +// When WOLFSSL_SMALL_STACK is enabled wolfSSL generates many XMALLOC calls +// with a NULL heap hint. A fallback heap bridges those call sites. +// When disabled every caller must supply a valid heap pointer. +#if defined(WOLFSSL_SMALL_STACK) +static AllocatorCapability fallbackHeap; + +void wolfssl_set_fallback_heap(void *heap) +{ + fallbackHeap = (AllocatorCapability)heap; +} +#endif + +void *XMALLOC(size_t n, void *heap, int type) +{ + (void)type; + if (n == 0) + return NULL; + if (heap == NULL) + { +#if defined(WOLFSSL_SMALL_STACK) + GlueDebug::log( + "XMALLOC: NULL heap n={} type={} using fallback", + n, + type); + heap = fallbackHeap; +#else + GlueDebug::log( + "XMALLOC: NULL heap n={} type={} - caller must provide heap", + n, + type); + return NULL; +#endif + } + AllocatorCapability cap = (AllocatorCapability)heap; + if (!Capability{cap}.is_valid()) + { + GlueDebug::log("XMALLOC: untagged heap cap n={} heap=0x{:x}", + n, (uintptr_t)heap); + return NULL; + } + Timeout t = {0, 0}; + void *ptr = heap_allocate( + &t, + cap, + n, + AllocateWaitNone); + if (!Capability{ptr}.is_valid()) + GlueDebug::log("XMALLOC: allocation failed n={}", n); + return Capability{ptr}.is_valid() ? ptr : NULL; +} + +void XFREE(void *p, void *heap, int type) +{ + (void)type; + if (p == NULL) + return; + if (heap == NULL) + { +#if defined(WOLFSSL_SMALL_STACK) + GlueDebug::log("XFREE: NULL heap p={} using fallback", p); + heap = fallbackHeap; +#else + GlueDebug::log( + "XFREE: NULL heap p={} - caller must provide heap", + p); + return; +#endif + } + AllocatorCapability cap = (AllocatorCapability)heap; + if (!Capability{cap}.is_valid()) + { + GlueDebug::log("XFREE: untagged heap cap p={}", p); + return; + } + heap_free(cap, p); +} + +void *XREALLOC(void *p, size_t n, void *heap, int type) +{ + (void)type; + if (n == 0) + return NULL; + if (heap == NULL) + { +#if defined(WOLFSSL_SMALL_STACK) + GlueDebug::log("XREALLOC: NULL heap p={} n={} using fallback", p, n); + heap = fallbackHeap; +#else + GlueDebug::log( + "XREALLOC: NULL heap p={} n={} - caller must provide heap", + p, + n); + return NULL; +#endif + } + AllocatorCapability cap = (AllocatorCapability)heap; + if (!Capability{cap}.is_valid()) + { + GlueDebug::log("XREALLOC: untagged cap p={} n={}", p, n); + return NULL; + } + Timeout t = {0, 0}; + void *newptr = heap_allocate( + &t, + cap, + n, + AllocateWaitNone); + if (!Capability{newptr}.is_valid()) + return NULL; + if (p != NULL) + { + size_t oldSize = Capability{p}.length(); + memcpy(newptr, p, oldSize < n ? oldSize : n); + heap_free(cap, p); + } + return newptr; +} + +int cheriot_wolfssl_strcasecmp(const char *s1, const char *s2) { + unsigned char a, b; + do { + a = (unsigned char)*s1++; + b = (unsigned char)*s2++; + if (a >= 'A' && a <= 'Z') a += 'a' - 'A'; + if (b >= 'A' && b <= 'Z') b += 'a' - 'A'; + } while (a && a == b); + return (int)a - (int)b; +} + +int cheriot_wolfssl_strncasecmp(const char *s1, const char *s2, unsigned int n) { + while (n--) { + unsigned char a = (unsigned char)*s1++; + unsigned char b = (unsigned char)*s2++; + if (a >= 'A' && a <= 'Z') a += 'a' - 'A'; + if (b >= 'A' && b <= 'Z') b += 'a' - 'A'; + if (a != b) return (int)a - (int)b; + if (!a) break; + } + return 0; +} + +char *cheriot_wolfssl_strncat(char *dst, const char *src, unsigned int n) { + char *d = dst; + while (*d) d++; + while (n-- && *src) *d++ = *src++; + *d = '\0'; + return dst; +} + +int cheriot_wolfssl_atoi(const char *s) { + int result = 0, sign = 1; + while (*s == ' ' || *s == '\t') s++; + if (*s == '-') { sign = -1; s++; } + else if (*s == '+') s++; + while (*s >= '0' && *s <= '9') + result = result * 10 + (*s++ - '0'); + return sign * result; +} + +#ifndef SINGLE_THREADED +int wc_InitMutex(wolfSSL_Mutex *m) +{ + m->lockWord = 0; + return 0; +} + +int wc_FreeMutex(wolfSSL_Mutex *m) +{ + (void)m; + return 0; +} + +int wc_LockMutex(wolfSSL_Mutex *m) +{ + Timeout t{UnlimitedTimeout}; + auto *s = reinterpret_cast(m); + return flaglock_priority_inheriting_trylock(&t, s) == 0 ? 0 : BAD_MUTEX_E; +} + +int wc_UnLockMutex(wolfSSL_Mutex *m) +{ + flaglock_unlock(reinterpret_cast(m)); + return 0; +} +#endif // !SINGLE_THREADED + +} // extern "C" diff --git a/lib/wolfssl/wolfssl_glue.h b/lib/wolfssl/wolfssl_glue.h new file mode 100644 index 00000000..e89a2c4f --- /dev/null +++ b/lib/wolfssl/wolfssl_glue.h @@ -0,0 +1,31 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT + +#pragma once +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// Required to provide some random seed / entropy source to wolfssl +int cheriot_wolfssl_seed(unsigned char *output, unsigned int sz); + +int cheriot_wolfssl_strcasecmp(const char *s1, const char *s2); + +long cheriot_wolfssl_time(long *tloc); + +#if defined(WOLFSSL_SMALL_STACK) +// Fallback heap for callers that pass a NULL heap hint. +// Required when WOLFSSL_SMALL_STACK is enabled (xmake option wolfssl-small-stack). +void wolfssl_set_fallback_heap(void *heap); +#else +// No-op when WOLFSSL_SMALL_STACK is disabled: all heap hints are explicit so there is nothing to fall back to. +static inline void wolfssl_set_fallback_heap(void *heap) { (void)heap; } +#endif + + +#ifdef __cplusplus +} +#endif diff --git a/lib/wolfssl/wolfssl_time.c b/lib/wolfssl/wolfssl_time.c new file mode 100644 index 00000000..2f270457 --- /dev/null +++ b/lib/wolfssl/wolfssl_time.c @@ -0,0 +1,14 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT +// Keeps track of time using SNTP derived time +#include + +long cheriot_wolfssl_time(long *tloc) +{ + struct timeval tv; + timeval_calculate(&tv); + long t = (long)tv.tv_sec; + if (tloc) + *tloc = t; + return t; +} diff --git a/lib/wolfssl/xmake.lua b/lib/wolfssl/xmake.lua new file mode 100644 index 00000000..c3c09fec --- /dev/null +++ b/lib/wolfssl/xmake.lua @@ -0,0 +1,114 @@ +debugOption("WolfSSLTLS") + +option("wolfssl-small-stack") + set_default(false) + set_description("Enable WOLFSSL_SMALL_STACK: allocate per-function crypto buffers on the heap instead of the stack. Requires wolfssl_set_fallback_heap() to be called for code paths that pass a NULL heap hint.") + set_showmenu(true) + add_defines("WOLFSSL_SMALL_STACK") + +option("wolfssl-single-threaded") + set_default(true) + set_description("Enable SINGLE_THREADED: disable wolfSSL's internal mutex locking. Use only when the WolfSSLTLS compartment is guaranteed to be called from a single thread at a time.") + set_showmenu(true) + add_defines("SINGLE_THREADED") + +compartment("WolfSSLTLS") + add_rules("cheriot.component-debug") + add_deps("freestanding") + add_deps("cxxrt", "string", "debug") + add_options("wolfssl-small-stack", "wolfssl-single-threaded") + set_default(false) + + -- Pull in user_settings.h from this directory before any wolfSSL header. + add_defines("WOLFSSL_USER_SETTINGS") + -- CHERIoT SDK defines maxalign_t; standard C11 uses max_align_t. + add_defines("max_align_t=maxalign_t") + -- Suppress warnings from wolfSSL third-party source that are not actionable: + -- -Wignored-attributes: wolfSSL's fallback thread platform sets + -- WOLFSSL_THREAD to __stdcall, which is silently ignored on RISC-V. + -- -Watomic-alignment: wolfSSL uses volatile int (not _Atomic) for its + -- atomic ref-counts; clang warns the 4-byte operations may not be + -- lock-free on this target. + add_cxflags("-Wno-ignored-attributes", "-Wno-atomic-alignment", "-Wno-parentheses-equality", "-Wno-tautological-compare", {force = true}) + add_defines("CHERIOT_NO_AMBIENT_MALLOC", "CHERIOT_NO_NEW_DELETE") + + -- Include paths: + -- "." → user_settings.h (this dir) + -- "../../include" → NetAPI.h etc (network-stack public headers) + -- "../../third_party/wolfssl" → , + add_includedirs( + ".", + "../../include", + "../../third_party/wolfssl") + + add_deps("NetAPI", "SNTP", "time_helpers", "atomiccap") + add_files("wolfssl_glue.cc", "wolfssl_time.c", "tls_wolfssl.cc") + + -- wolfssl/src - TLS Bit + add_files( + "../../third_party/wolfssl/src/ssl.c", + "../../third_party/wolfssl/src/ssl_api_cert.c", + "../../third_party/wolfssl/src/ssl_api_crl_ocsp.c", + "../../third_party/wolfssl/src/ssl_api_pk.c", + "../../third_party/wolfssl/src/ssl_asn1.c", + "../../third_party/wolfssl/src/ssl_bn.c", + "../../third_party/wolfssl/src/ssl_certman.c", + "../../third_party/wolfssl/src/ssl_crypto.c", + "../../third_party/wolfssl/src/ssl_load.c", + "../../third_party/wolfssl/src/ssl_misc.c", + "../../third_party/wolfssl/src/ssl_sess.c", + "../../third_party/wolfssl/src/ssl_sk.c") + add_files( + "../../third_party/wolfssl/src/bio.c", + "../../third_party/wolfssl/src/internal.c", + "../../third_party/wolfssl/src/keys.c", + "../../third_party/wolfssl/src/tls.c", + "../../third_party/wolfssl/src/tls13.c", + "../../third_party/wolfssl/src/wolfio.c", + "../../third_party/wolfssl/src/x509.c", + "../../third_party/wolfssl/src/x509_str.c", + "../../third_party/wolfssl/src/pk.c", + "../../third_party/wolfssl/src/pk_ec.c", + "../../third_party/wolfssl/src/pk_rsa.c", + "../../third_party/wolfssl/src/ocsp.c", + "../../third_party/wolfssl/src/crl.c") + + -- wolfcrypt/src - crypto primitives + add_files( + "../../third_party/wolfssl/wolfcrypt/src/aes.c", + "../../third_party/wolfssl/wolfcrypt/src/sha.c", + "../../third_party/wolfssl/wolfcrypt/src/sha256.c", + "../../third_party/wolfssl/wolfcrypt/src/sha512.c", + "../../third_party/wolfssl/wolfcrypt/src/hmac.c", + "../../third_party/wolfssl/wolfcrypt/src/hash.c", + "../../third_party/wolfssl/wolfcrypt/src/kdf.c", + "../../third_party/wolfssl/wolfcrypt/src/random.c", + "../../third_party/wolfssl/wolfcrypt/src/md5.c") + add_files( + "../../third_party/wolfssl/wolfcrypt/src/asn.c", + "../../third_party/wolfssl/wolfcrypt/src/coding.c", + "../../third_party/wolfssl/wolfcrypt/src/logging.c", + "../../third_party/wolfssl/wolfcrypt/src/memory.c", + "../../third_party/wolfssl/wolfcrypt/src/misc.c", + "../../third_party/wolfssl/wolfcrypt/src/error.c", + "../../third_party/wolfssl/wolfcrypt/src/wc_port.c", + "../../third_party/wolfssl/wolfcrypt/src/wc_encrypt.c", + "../../third_party/wolfssl/wolfcrypt/src/signature.c", + "../../third_party/wolfssl/wolfcrypt/src/cryptocb.c") + add_files( + "../../third_party/wolfssl/wolfcrypt/src/ecc.c", + "../../third_party/wolfssl/wolfcrypt/src/rsa.c", + "../../third_party/wolfssl/wolfcrypt/src/dh.c", + "../../third_party/wolfssl/wolfcrypt/src/sp_int.c", + "../../third_party/wolfssl/wolfcrypt/src/sp_c32.c", + "../../third_party/wolfssl/wolfcrypt/src/sp_c64.c", + "../../third_party/wolfssl/wolfcrypt/src/wolfmath.c") + add_files( + "../../third_party/wolfssl/wolfcrypt/src/chacha.c", + "../../third_party/wolfssl/wolfcrypt/src/poly1305.c", + "../../third_party/wolfssl/wolfcrypt/src/chacha20_poly1305.c", + "../../third_party/wolfssl/wolfcrypt/src/curve25519.c", + "../../third_party/wolfssl/wolfcrypt/src/ed25519.c", + "../../third_party/wolfssl/wolfcrypt/src/fe_operations.c", + "../../third_party/wolfssl/wolfcrypt/src/ge_operations.c", + "../../third_party/wolfssl/wolfcrypt/src/cmac.c") diff --git a/lib/xmake.lua b/lib/xmake.lua index d58f6375..a04b70c0 100644 --- a/lib/xmake.lua +++ b/lib/xmake.lua @@ -34,7 +34,9 @@ includes("tcpip", "netapi", "sntp", "mqtt", + "mqtt_wolfssl", "tls", "dns", - "firewall") + "firewall", + "wolfssl") diff --git a/tests/wolfssl/wolfcrypt/README.md b/tests/wolfssl/wolfcrypt/README.md new file mode 100644 index 00000000..26b34dff --- /dev/null +++ b/tests/wolfssl/wolfcrypt/README.md @@ -0,0 +1,139 @@ +# wolfCrypt Tests for CHERIoT + +wolfCrypt is the cryptography library at the core of wolfSSL. +This directory contains a combined project that can run the wolfCrypt test suite, benchmark suite, or both. + +By default both run: the test suite first, then the benchmark. + +## Building and running + +```bash +cd tests/wolfssl/wolfcrypt +xmake config --sdk=/cheriot-tools --board=sonata-1.3 -m release +xmake +cp ./build/cheriot/cheriot/release/firmware.uf2 /mnt/SONATA/ +``` + +## Options + +| Option | Default | Description | +|--------|---------|-------------| +| `run_test` | `true` | Run the wolfCrypt test suite | +| `run_bench` | `true` | Run the wolfCrypt benchmark suite | +| `heartbeat` | `false` | Print elapsed time every 5 s (useful on long runs) | +| `board` | `sonata-1.3` | Target board (`sonata-1.3`, `sail`, etc.) | + +Examples: + +```bash +# Test only +xmake config --sdk=/cheriot-tools --board=sonata-1.3 -m release --run_bench=n + +# Benchmark only +xmake config --sdk=/cheriot-tools --board=sonata-1.3 -m release --run_test=n + +# Both with heartbeat (Indicates that the board is still alive on long runs) +xmake config --sdk=/cheriot-tools --board=sonata-1.3 -m release --heartbeat=y +``` + +## Expected output + +### Test suite + +The test suite takes around 10 minutes on SONATA. + +``` +WolfCrypt: === wolfCrypt test suite === +------------------------------------------------------------------------------ + wolfSSL version 5.9.1 +------------------------------------------------------------------------------ +macro test passed! +error test passed! +MEMORY test passed! +base64 test passed! +asn test passed! +SHA test passed! +SHA-256 test passed! +SHA-384 test passed! +SHA-512 test passed! +SHA-512/224 test passed! +SHA-512/256 test passed! +RANDOM test passed! +Hash test passed! +HMAC-SHA test passed! +HMAC-SHA256 test passed! +HMAC-SHA384 test passed! +HMAC-SHA512 test passed! +HMAC-KDF test passed! +PRF test passed! +TLSv1.3 KDF test passed! +GMAC test passed! +Chacha test passed! +POLY1305 test passed! +ChaCha20-Poly1305 AEAD test passed! +AES test passed! +AES192 test passed! +AES256 test passed! +AES-CBC test passed! +AES-GCM test passed! +RSA test passed! +DH test passed! +ECC test passed! +ECC buffer test passed! +logging test passed! +time test passed! +mutex test passed! +Test complete +WolfCrypt: === ALL TESTS PASSED === +``` + +### Benchmark suite + +``` +WolfCrypt: === wolfCrypt benchmark suite === +wolfCrypt Benchmark (block bytes 1024, min 1.0 sec each) +RNG SHA-256 DRBG 175.0 KiB took 1.101 seconds, 158.906 KiB/s +AES-128-CBC-enc 25.0 KiB took 4.329 seconds, 5.775 KiB/s +AES-128-CBC-dec 25.0 KiB took 4.313 seconds, 5.796 KiB/s +AES-192-CBC-enc 25.0 KiB took 5.191 seconds, 4.816 KiB/s +AES-192-CBC-dec 25.0 KiB took 5.152 seconds, 4.852 KiB/s +AES-256-CBC-enc 25.0 KiB took 6.059 seconds, 4.126 KiB/s +AES-256-CBC-dec 25.0 KiB took 5.988 seconds, 4.175 KiB/s +AES-128-GCM-enc 25.0 KiB took 5.935 seconds, 4.212 KiB/s +AES-128-GCM-dec 25.0 KiB took 5.933 seconds, 4.213 KiB/s +AES-192-GCM-enc 25.0 KiB took 6.848 seconds, 3.651 KiB/s +AES-192-GCM-dec 25.0 KiB took 6.848 seconds, 3.651 KiB/s +AES-256-GCM-enc 25.0 KiB took 7.747 seconds, 3.227 KiB/s +AES-256-GCM-dec 25.0 KiB took 7.749 seconds, 3.226 KiB/s +AES-128-GCM-enc-no_AAD 25.0 KiB took 5.912 seconds, 4.229 KiB/s +AES-128-GCM-dec-no_AAD 25.0 KiB took 5.910 seconds, 4.230 KiB/s +AES-192-GCM-enc-no_AAD 25.0 KiB took 6.824 seconds, 3.664 KiB/s +AES-192-GCM-dec-no_AAD 25.0 KiB took 6.825 seconds, 3.663 KiB/s +AES-256-GCM-enc-no_AAD 25.0 KiB took 7.723 seconds, 3.237 KiB/s +AES-256-GCM-dec-no_AAD 25.0 KiB took 7.724 seconds, 3.237 KiB/s +GMAC Small 25.0 KiB took 1.574 seconds, 15.881 KiB/s +CHACHA 700.0 KiB took 1.002 seconds, 698.864 KiB/s +CHA-POLY 350.0 KiB took 1.040 seconds, 336.554 KiB/s +POLY1305 1.0 MiB took 1.009 seconds, 1.210 MiB/s +SHA 975.0 KiB took 1.018 seconds, 957.817 KiB/s +SHA-256 525.0 KiB took 1.041 seconds, 504.415 KiB/s +SHA-384 150.0 KiB took 1.151 seconds, 130.289 KiB/s +SHA-512 150.0 KiB took 1.150 seconds, 130.437 KiB/s +SHA-512/224 150.0 KiB took 1.150 seconds, 130.455 KiB/s +SHA-512/256 150.0 KiB took 1.154 seconds, 129.972 KiB/s +HMAC-SHA 950.0 KiB took 1.004 seconds, 946.033 KiB/s +HMAC-SHA256 500.0 KiB took 1.003 seconds, 498.398 KiB/s +HMAC-SHA384 150.0 KiB took 1.167 seconds, 128.587 KiB/s +HMAC-SHA512 150.0 KiB took 1.166 seconds, 128.596 KiB/s +RSA 2048 public 8 ops took 1.038 sec, avg 129.761 ms, 7.706 ops/sec +RSA 2048 private 2 ops took 15.161 sec, avg 7580.444 ms, 0.132 ops/sec +DH 2048 key gen 1 ops took 3.181 sec, avg 3181.317 ms, 0.314 ops/sec +DH 2048 agree 2 ops took 6.363 sec, avg 3181.502 ms, 0.314 ops/sec +ECC [ SECP256R1] 256 key gen 2 ops took 1.071 sec, avg 535.416 ms, 1.868 ops/sec +ECDHE [ SECP256R1] 256 agree 2 ops took 1.068 sec, avg 534.194 ms, 1.872 ops/sec +ECDSA [ SECP256R1] 256 sign 2 ops took 1.179 sec, avg 589.603 ms, 1.696 ops/sec +ECDSA [ SECP256R1] 256 verify 2 ops took 2.161 sec, avg 1080.369 ms, 0.926 ops/sec +RNG 256 SHA256 Init/Free 86 ops took 1.007 sec, avg 11.709 ms, 85.403 ops/sec +Benchmark complete +WolfCrypt: === BENCHMARK COMPLETE === +``` diff --git a/tests/wolfssl/wolfcrypt/time_stub.cc b/tests/wolfssl/wolfcrypt/time_stub.cc new file mode 100644 index 00000000..21bd16a7 --- /dev/null +++ b/tests/wolfssl/wolfcrypt/time_stub.cc @@ -0,0 +1,21 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT + +// Stub timeval_calculate for firmware that doesn't include SNTP. +// Returns a monotonically increasing time derived from the cycle counter. + +#include +#include +#include + +// 2026-01-01 00:00:00 UTC +static const time_t BASE_TIME = 1767225600; + +int timeval_calculate(struct timeval *__restrict tp) +{ + uint64_t cycles = rdcycle64(); + tp->tv_sec = BASE_TIME + (time_t)(cycles / CPU_TIMER_HZ); + tp->tv_usec = (suseconds_t)((cycles % CPU_TIMER_HZ) / + (CPU_TIMER_HZ / 1000000)); + return 0; +} diff --git a/tests/wolfssl/wolfcrypt/wolfcrypt.cc b/tests/wolfssl/wolfcrypt/wolfcrypt.cc new file mode 100644 index 00000000..0fe4c5bb --- /dev/null +++ b/tests/wolfssl/wolfcrypt/wolfcrypt.cc @@ -0,0 +1,106 @@ +// Copyright The Good Penguin Ltd +// SPDX-License-Identifier: MIT +// Combined wolfCrypt test and benchmark for CHERIoT. +// Compile with -DRUN_TEST and/or -DRUN_BENCH to select what runs. +// If both are enabled, the test suite runs first. + +#include + +#if !defined(RUN_TEST) && !defined(RUN_BENCH) +#error "At least one of run_test or run_bench must be enabled via xmake options" +#endif + +#ifdef RUN_TEST +#include +#endif +#ifdef RUN_BENCH +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../../lib/wolfssl/wolfssl_glue.h" + +using Debug = ConditionalDebug; + +#ifdef RUN_BENCH +// benchmark.c measures wall time so stub here for that based on cpu cycles +extern "C" double current_time(int reset) +{ + return (double)rdcycle64() / (double)CPU_TIMER_HZ; +} +#endif + +extern "C" ErrorRecoveryBehaviour +compartment_error_handler(ErrorState *frame, size_t mcause, size_t mtval) +{ + if (mcause == priv::MCAUSE_CHERI) + { + auto [exceptionCode, registerNumber] = + CHERI::extract_cheri_mtval(mtval); + Debug::log("CHERI fault: {} at {} register {}: {}", + exceptionCode, + frame->pcc, + registerNumber, + registerNumber == CHERI::RegisterNumber::CZR + ? nullptr + : *frame->get_register_value(registerNumber)); + } + else + { + Debug::log("Fault mcause={} at {}", mcause, frame->pcc); + } + return ErrorRecoveryBehaviour::ForceUnwind; +} + +DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(WolfCryptHeap, 64 * 1024); +#define WOLFCRYPT_HEAP STATIC_SEALED_VALUE(WolfCryptHeap) + +void __cheri_compartment("wolfcrypt") heartbeat_main() +{ + uint32_t elapsed_s = 0; + while (true) + { + Timeout t = {0, MS_TO_TICKS(5000)}; + thread_sleep(&t, ThreadSleepNoEarlyWake); + elapsed_s += 5; + printf("[%4us] still running...\n", elapsed_s); + } +} + +void __cheri_compartment("wolfcrypt") combined_main() +{ + wolfssl_set_fallback_heap((void *)WOLFCRYPT_HEAP); + +#ifdef RUN_TEST + Debug::log("=== wolfCrypt test suite ==="); + wc_test_ret_t tret = wolfcrypt_test(NULL); + if (tret == 0) + { + Debug::log("=== ALL TESTS PASSED ==="); + } + else + { + Debug::log("=== TESTS FAILED: ret={} ===", static_cast(tret)); + } +#endif + +#ifdef RUN_BENCH + Debug::log("=== wolfCrypt benchmark suite ==="); + int bret = benchmark_test(NULL); + if (bret == 0) + { + Debug::log("=== BENCHMARK COMPLETE ==="); + } + else + { + Debug::log("=== BENCHMARK FAILED: ret={} ===", bret); + } +#endif +} diff --git a/tests/wolfssl/wolfcrypt/xmake.lua b/tests/wolfssl/wolfcrypt/xmake.lua new file mode 100644 index 00000000..176448c1 --- /dev/null +++ b/tests/wolfssl/wolfcrypt/xmake.lua @@ -0,0 +1,144 @@ +-- Copyright The Good Penguin Ltd +-- SPDX-License-Identifier: MIT +-- Combined wolfCrypt test and benchmark for CHERIoT. +-- Use --run_test=y (default) and/or --run_bench=y to control what runs. +-- If both are enabled, the test suite runs first, then the benchmark. +-- Default board is sonata; use --board=sail for the CHERIoT simulator. + +sdkdir = path.absolute("/workspaces/cheriot-rtos/sdk") +set_project("wolfcrypt") + +includes(sdkdir) +set_toolchains("cheriot-clang") +includes(path.join(sdkdir, "lib")) + +option("board") + set_default("sonata-1.3") + set_showmenu(true) + +option("heartbeat") + set_default(false) + set_showmenu(true) + set_description("Enable heartbeat thread that prints elapsed time every 5 seconds") + +option("run_test") + set_default(true) + set_showmenu(true) + set_description("Run the wolfCrypt test suite (wolfcrypt_test)") + +option("run_bench") + set_default(true) + set_showmenu(true) + set_description("Run the wolfCrypt benchmark suite (benchmark_test)") + +debugOption("wolfcrypt") + +library("time_helpers_stub") + add_includedirs("../../../include") + add_files("time_stub.cc") + +compartment("wolfcrypt") + add_rules("cheriot.component-debug") + add_deps("freestanding", "string", "stdio", "cxxrt", "debug", "atomiccap", "time_helpers_stub") + + add_defines("WOLFSSL_USER_SETTINGS") + -- Suppress warnings from wolfSSL third-party source that are not actionable: + -- -Wignored-attributes: wolfSSL's fallback thread platform sets + -- WOLFSSL_THREAD to __stdcall, which is silently ignored on RISC-V. + -- -Watomic-alignment: wolfSSL uses volatile int (not _Atomic) for its + -- atomic ref-counts; clang warns the 4-byte operations may not be + -- lock-free on this target. + add_cxflags("-Wno-ignored-attributes", "-Wno-atomic-alignment", "-Wno-parentheses-equality", "-Wno-tautological-compare", {force = true}) + add_defines("CHERIOT_NO_AMBIENT_MALLOC", "CHERIOT_NO_NEW_DELETE") + add_defines("max_align_t=maxalign_t") + add_defines("WOLFSSL_SMALL_STACK") + -- CHERIoT does not support printing floats + add_defines("WOLFSSL_NO_FLOAT_FMT") + + add_includedirs( + "../../../lib/wolfssl", + "../../../include", + "../../../third_party/wolfssl") + + add_files("wolfcrypt.cc") + add_files( + "../../../lib/wolfssl/wolfssl_glue.cc", + "../../../lib/wolfssl/wolfssl_time.c") + + if get_config("run_test") then + add_defines("RUN_TEST") + add_files("../../../third_party/wolfssl/wolfcrypt/test/test.c") + end + + if get_config("run_bench") then + add_defines("RUN_BENCH") + -- benchmark.c uses current_time() which we provide; needs softfloat for double return + add_defines("WOLFSSL_USER_CURRTIME") + add_deps("softfloat64") + add_files("../../../third_party/wolfssl/wolfcrypt/benchmark/benchmark.c") + end + + -- wolfcrypt/src (shared by both test and bench) + add_files( + "../../../third_party/wolfssl/wolfcrypt/src/aes.c", + "../../../third_party/wolfssl/wolfcrypt/src/sha.c", + "../../../third_party/wolfssl/wolfcrypt/src/sha256.c", + "../../../third_party/wolfssl/wolfcrypt/src/sha512.c", + "../../../third_party/wolfssl/wolfcrypt/src/hmac.c", + "../../../third_party/wolfssl/wolfcrypt/src/hash.c", + "../../../third_party/wolfssl/wolfcrypt/src/kdf.c", + "../../../third_party/wolfssl/wolfcrypt/src/random.c", + "../../../third_party/wolfssl/wolfcrypt/src/md5.c") + add_files( + "../../../third_party/wolfssl/wolfcrypt/src/asn.c", + "../../../third_party/wolfssl/wolfcrypt/src/coding.c", + "../../../third_party/wolfssl/wolfcrypt/src/logging.c", + "../../../third_party/wolfssl/wolfcrypt/src/memory.c", + "../../../third_party/wolfssl/wolfcrypt/src/misc.c", + "../../../third_party/wolfssl/wolfcrypt/src/error.c", + "../../../third_party/wolfssl/wolfcrypt/src/wc_port.c", + "../../../third_party/wolfssl/wolfcrypt/src/wc_encrypt.c", + "../../../third_party/wolfssl/wolfcrypt/src/signature.c", + "../../../third_party/wolfssl/wolfcrypt/src/cryptocb.c") + add_files( + "../../../third_party/wolfssl/wolfcrypt/src/ecc.c", + "../../../third_party/wolfssl/wolfcrypt/src/rsa.c", + "../../../third_party/wolfssl/wolfcrypt/src/dh.c", + "../../../third_party/wolfssl/wolfcrypt/src/sp_int.c", + "../../../third_party/wolfssl/wolfcrypt/src/sp_c32.c", + "../../../third_party/wolfssl/wolfcrypt/src/sp_c64.c", + "../../../third_party/wolfssl/wolfcrypt/src/wolfmath.c") + add_files( + "../../../third_party/wolfssl/wolfcrypt/src/chacha.c", + "../../../third_party/wolfssl/wolfcrypt/src/poly1305.c", + "../../../third_party/wolfssl/wolfcrypt/src/chacha20_poly1305.c", + "../../../third_party/wolfssl/wolfcrypt/src/curve25519.c", + "../../../third_party/wolfssl/wolfcrypt/src/ed25519.c", + "../../../third_party/wolfssl/wolfcrypt/src/fe_operations.c", + "../../../third_party/wolfssl/wolfcrypt/src/ge_operations.c", + "../../../third_party/wolfssl/wolfcrypt/src/cmac.c") + +firmware("wolfcrypt-cheriot") + add_deps("wolfcrypt") + on_load(function(target) + target:values_set("board", "$(board)") + local threads = { + { + compartment = "wolfcrypt", + priority = 1, + entry_point = "combined_main", + stack_size = 0xa000, + trusted_stack_frames = 8 + } + } + if get_config("heartbeat") then + table.insert(threads, { + compartment = "wolfcrypt", + priority = 2, + entry_point = "heartbeat_main", + stack_size = 0x400, + trusted_stack_frames = 4 + }) + end + target:values_set("threads", threads, {expand = false}) + end) diff --git a/third_party/wolfssl b/third_party/wolfssl new file mode 160000 index 00000000..88fe9739 --- /dev/null +++ b/third_party/wolfssl @@ -0,0 +1 @@ +Subproject commit 88fe9739f44b431b60f6e30e3bf278f101d5b122