From 72bb36bbf8a0d8466ce4c2099dc9492f9d107369 Mon Sep 17 00:00:00 2001 From: Gaston Valiente Date: Mon, 6 Apr 2026 00:28:04 -0300 Subject: [PATCH 1/7] Fix RSA 2048-bit deadlock on ESP32 by resetting watchdog in RNG callback --- src/primitive_crypto.cc | 37 ++++++++++++++++++++++++------------- src/scheduler.h | 2 ++ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/primitive_crypto.cc b/src/primitive_crypto.cc index 386b2063f..bc2657057 100644 --- a/src/primitive_crypto.cc +++ b/src/primitive_crypto.cc @@ -42,6 +42,9 @@ #include "sha.h" #include "siphash.h" #include "tags.h" +#include "vm.h" +#include "scheduler.h" +#include "os.h" #if (defined(MBEDTLS_CHACHAPOLY_C) && defined(MBEDTLS_CHACHA20_C)) || (defined(CONFIG_MBEDTLS_POLY1305_C) && defined(CONFIG_MBEDTLS_CHACHA20_C)) #define SUPPORT_CHACHA20_POLY1305 1 @@ -759,6 +762,14 @@ PRIMITIVE(aes_ecb_close) { static int rsa_rng(void* ctx, unsigned char* buffer, size_t len) { + Process* process = reinterpret_cast(ctx); + if (process != null) { + // Reset the watchdog timer for this process. + // We take the scheduler lock to ensure 64-bit timestamp updates are atomic + // and safe against the scheduler tick reading them. + Locker locker(VM::current()->scheduler()->mutex()); + process->set_run_timestamp(OS::get_monotonic_time()); + } #ifdef TOIT_ESP32 esp_fill_random(buffer, len); #else @@ -790,7 +801,7 @@ static bool is_pem(Blob key) { // For private keys, an optional password blob may be supplied (length 0 = none). // Returns 0 on success, a non-zero mbedtls error code on failure. // The caller is responsible for calling mbedtls_pk_free on *pk in all cases. -static int rsa_parse_key_from_blob(mbedtls_pk_context* pk, Blob key, Blob password, bool is_private) { +static int rsa_parse_key_from_blob(mbedtls_pk_context* pk, Blob key, Blob password, bool is_private, Process* process) { // mbedtls_pk_parse_key / mbedtls_pk_parse_public_key require the buffer to // be null-terminated when the input is PEM. For DER, the null byte is // actually harmful if it's passed as part of the length. @@ -820,7 +831,7 @@ static int rsa_parse_key_from_blob(mbedtls_pk_context* pk, Blob key, Blob passwo ret = mbedtls_pk_parse_key(pk, parse_buf, parse_len, pwd, password.length(), - rsa_rng, NULL); + rsa_rng, process); } else { ret = mbedtls_pk_parse_public_key(pk, parse_buf, parse_len); } @@ -885,7 +896,7 @@ PRIMITIVE(rsa_generate) { return tls_error(null, process, ret); } - ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(pk), rsa_rng, NULL, bits, 65537); + ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(pk), rsa_rng, process, bits, 65537); if (ret != 0) { mbedtls_pk_free(&pk); return tls_error(null, process, ret); @@ -926,7 +937,7 @@ PRIMITIVE(rsa_sign) { mbedtls_pk_context pk; mbedtls_pk_init(&pk); - int ret = rsa_parse_key_from_blob(&pk, private_key_der, Blob(), true); + int ret = rsa_parse_key_from_blob(&pk, private_key_der, Blob(), true, process); if (ret != 0) { mbedtls_pk_free(&pk); return tls_error(null, process, ret); @@ -939,7 +950,7 @@ PRIMITIVE(rsa_sign) { uint8_t sig[MBEDTLS_PK_SIGNATURE_MAX_SIZE]; size_t actual_len = 0; ret = mbedtls_pk_sign(&pk, md_alg, digest.address(), digest.length(), - sig, sizeof(sig), &actual_len, rsa_rng, NULL); + sig, sizeof(sig), &actual_len, rsa_rng, process); mbedtls_pk_free(&pk); if (ret != 0) return tls_error(null, process, ret); @@ -961,7 +972,7 @@ PRIMITIVE(rsa_verify) { mbedtls_pk_context pk; mbedtls_pk_init(&pk); - int ret = rsa_parse_key_from_blob(&pk, public_key_der, Blob(), false); + int ret = rsa_parse_key_from_blob(&pk, public_key_der, Blob(), false, process); if (ret != 0) { mbedtls_pk_free(&pk); return tls_error(null, process, ret); @@ -983,7 +994,7 @@ PRIMITIVE(rsa_get_private_key_der) { mbedtls_pk_context pk; mbedtls_pk_init(&pk); - int ret = rsa_parse_key_from_blob(&pk, key, password, true); + int ret = rsa_parse_key_from_blob(&pk, key, password, true, process); if (ret != 0) { mbedtls_pk_free(&pk); return tls_error(null, process, ret); @@ -1012,9 +1023,9 @@ PRIMITIVE(rsa_get_public_key_der) { // Try parsing as a private key first (which contains the public key). // If that fails, try as a public key. - int ret = rsa_parse_key_from_blob(&pk, key, Blob(), true); + int ret = rsa_parse_key_from_blob(&pk, key, Blob(), true, process); if (ret != 0) { - ret = rsa_parse_key_from_blob(&pk, key, Blob(), false); + ret = rsa_parse_key_from_blob(&pk, key, Blob(), false, process); } if (ret != 0) { mbedtls_pk_free(&pk); @@ -1045,7 +1056,7 @@ PRIMITIVE(rsa_encrypt) { mbedtls_pk_context pk; mbedtls_pk_init(&pk); - int ret = rsa_parse_key_from_blob(&pk, public_key_der, Blob(), false); + int ret = rsa_parse_key_from_blob(&pk, public_key_der, Blob(), false, process); if (ret != 0) { mbedtls_pk_free(&pk); return tls_error(null, process, ret); @@ -1068,7 +1079,7 @@ PRIMITIVE(rsa_encrypt) { size_t output_len = 0; ret = mbedtls_pk_encrypt(&pk, data.address(), data.length(), - ByteArray::Bytes(result).address(), &output_len, output_size, rsa_rng, NULL); + ByteArray::Bytes(result).address(), &output_len, output_size, rsa_rng, process); mbedtls_pk_free(&pk); if (ret != 0) return tls_error(null, process, ret); @@ -1086,7 +1097,7 @@ PRIMITIVE(rsa_decrypt) { mbedtls_pk_context pk; mbedtls_pk_init(&pk); - int ret = rsa_parse_key_from_blob(&pk, private_key_der, Blob(), true); + int ret = rsa_parse_key_from_blob(&pk, private_key_der, Blob(), true, process); if (ret != 0) { mbedtls_pk_free(&pk); return tls_error(null, process, ret); @@ -1109,7 +1120,7 @@ PRIMITIVE(rsa_decrypt) { size_t output_len = 0; ret = mbedtls_pk_decrypt(&pk, data.address(), data.length(), - ByteArray::Bytes(result).address(), &output_len, output_size, rsa_rng, NULL); + ByteArray::Bytes(result).address(), &output_len, output_size, rsa_rng, process); mbedtls_pk_free(&pk); if (ret != 0) return tls_error(null, process, ret); diff --git a/src/scheduler.h b/src/scheduler.h index 6f17f3ea8..46787bdb6 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -147,6 +147,8 @@ class Scheduler { bool is_locked() const { return OS::is_locked(mutex_); } bool is_boot_process(Process* process) const { return boot_process_ == process; } + Mutex* mutex() const { return mutex_; } + void iterate_process_chunks(void* context, process_chunk_callback_t callback); private: From 07b3b8e155450d6b7173cb80c4deb2d773437940 Mon Sep 17 00:00:00 2001 From: Gaston Valiente Date: Mon, 6 Apr 2026 16:27:11 -0300 Subject: [PATCH 2/7] feat: add asynchronous RSA key generation and enable mbedTLS hardware MPI support across ESP32 targets. --- lib/crypto/rsa.toit | 27 ++- .../propagation/type_primitive_crypto.cc | 3 + src/event_sources/async_posix.cc | 2 +- src/event_sources/async_posix.h | 2 +- src/primitive.h | 7 +- src/primitive_crypto.cc | 207 ++++++++++++++++-- src/tags.h | 6 +- toolchains/esp32/sdkconfig.defaults | 2 +- toolchains/esp32c3/sdkconfig.defaults | 2 +- toolchains/esp32c6/sdkconfig.defaults | 2 +- toolchains/esp32s2/sdkconfig.defaults | 2 +- toolchains/esp32s3/sdkconfig.defaults | 2 +- 12 files changed, 238 insertions(+), 26 deletions(-) diff --git a/lib/crypto/rsa.toit b/lib/crypto/rsa.toit index 9c2644546..a7f324a4d 100644 --- a/lib/crypto/rsa.toit +++ b/lib/crypto/rsa.toit @@ -5,6 +5,7 @@ import .sha import .sha1 import ..io as io +import monitor show ResourceState_ import encoding.base64 /** @@ -124,7 +125,18 @@ class RsaKey: */ static generate --bits/int=2048 -> RsaKeyPair: if bits != 1024 and bits != 2048 and bits != 3072 and bits != 4096: throw "INVALID_ARGUMENT" - pair := rsa-generate_ bits + pair := null + catch --trace=(: it != "UNIMPLEMENTED"): + group := rsa-generate-init_ + resource-id := rsa-generate-start_ group bits + state := ResourceState_ group resource-id + state.wait + pair = rsa-generate-finish_ resource-id + state.dispose + + if not pair: + pair = rsa-generate_ bits + return RsaKeyPair RsaKey.internal_ pair[0] true RsaKey.internal_ pair[1] false @@ -279,4 +291,15 @@ rsa-encrypt_ public-key-der/ByteArray data/ByteArray padding/int hash/int -> Byt // Primitive: decrypt data with a private key DER blob. rsa-decrypt_ private-key-der/ByteArray data/ByteArray padding/int hash/int -> ByteArray: #primitive.crypto.rsa-decrypt - \ No newline at end of file + +/** Initializes a resource group for RSA key generation. */ +rsa-generate-init_ -> int: + #primitive.crypto.rsa-generate-init + +/** Starts the asynchronous RSA key generation. */ +rsa-generate-start_ group/int bits/int -> int: + #primitive.crypto.rsa-generate-start + +/** Finishes the asynchronous RSA key generation and returns the key pair. */ +rsa-generate-finish_ resource-id/int -> List: + #primitive.crypto.rsa-generate-finish \ No newline at end of file diff --git a/src/compiler/propagation/type_primitive_crypto.cc b/src/compiler/propagation/type_primitive_crypto.cc index 4d473c5b6..fc183d06a 100644 --- a/src/compiler/propagation/type_primitive_crypto.cc +++ b/src/compiler/propagation/type_primitive_crypto.cc @@ -55,6 +55,9 @@ TYPE_PRIMITIVE_ANY(rsa_verify) TYPE_PRIMITIVE_ANY(rsa_generate) TYPE_PRIMITIVE_ANY(rsa_encrypt) TYPE_PRIMITIVE_ANY(rsa_decrypt) +TYPE_PRIMITIVE_ANY(rsa_generate_init) +TYPE_PRIMITIVE_ANY(rsa_generate_start) +TYPE_PRIMITIVE_ANY(rsa_generate_finish) } // namespace toit::compiler } // namespace toit diff --git a/src/event_sources/async_posix.cc b/src/event_sources/async_posix.cc index 8e99c0fdd..3898c03fe 100644 --- a/src/event_sources/async_posix.cc +++ b/src/event_sources/async_posix.cc @@ -15,7 +15,7 @@ #include "../top.h" -#if defined(TOIT_POSIX) +#if defined(TOIT_POSIX) || defined(TOIT_ESP32) #include "async_posix.h" diff --git a/src/event_sources/async_posix.h b/src/event_sources/async_posix.h index cbc29df45..59782e2ce 100644 --- a/src/event_sources/async_posix.h +++ b/src/event_sources/async_posix.h @@ -17,7 +17,7 @@ #include "../top.h" -#if defined(TOIT_POSIX) +#if defined(TOIT_POSIX) || defined(TOIT_ESP32) #include "../linked.h" #include "../resource.h" diff --git a/src/primitive.h b/src/primitive.h index 03202cf70..796f1d511 100644 --- a/src/primitive.h +++ b/src/primitive.h @@ -541,7 +541,10 @@ namespace toit { PRIMITIVE(rsa_get_private_key_der, 2) \ PRIMITIVE(rsa_get_public_key_der, 1) \ PRIMITIVE(rsa_encrypt, 4) \ - PRIMITIVE(rsa_decrypt, 4) + PRIMITIVE(rsa_decrypt, 4) \ + PRIMITIVE(rsa_generate_init, 0) \ + PRIMITIVE(rsa_generate_start, 2) \ + PRIMITIVE(rsa_generate_finish, 1) #define MODULE_CRYPTO_RANDOM(PRIMITIVE) \ PRIMITIVE(random, 1) \ @@ -1092,6 +1095,7 @@ Object* get_absolute_path(Process* process, const wchar_t* pathname, wchar_t* ou #define _A_T_RmtResourceGroup(N, name) MAKE_UNPACKING_MACRO(RmtResourceGroup, N, name) #define _A_T_PcntUnitResourceGroup(N, name) MAKE_UNPACKING_MACRO(PcntUnitResourceGroup, N, name) #define _A_T_EspNowResourceGroup(N, name) MAKE_UNPACKING_MACRO(EspNowResourceGroup, N, name) +#define _A_T_RsaGenerationResourceGroup(N, name) MAKE_UNPACKING_MACRO(RsaGenerationResourceGroup, N, name) #define _A_T_Resource(N, name) MAKE_UNPACKING_MACRO(Resource, N, name) #define _A_T_Directory(N, name) MAKE_UNPACKING_MACRO(Directory, N, name) @@ -1109,6 +1113,7 @@ Object* get_absolute_path(Process* process, const wchar_t* pathname, wchar_t* ou #define _A_T_EthernetEvents(N, name) MAKE_UNPACKING_MACRO(EthernetEvents, N, name) #define _A_T_EthernetIpEvents(N, name) MAKE_UNPACKING_MACRO(EthernetIpEvents, N, name) #define _A_T_MbedTlsSocket(N, name) MAKE_UNPACKING_MACRO(MbedTlsSocket, N, name) +#define _A_T_RsaGenerationResource(N, name) MAKE_UNPACKING_MACRO(RsaGenerationResource, N, name) #define _A_T_BaseMbedTlsSocket(N, name) MAKE_UNPACKING_MACRO(BaseMbedTlsSocket, N, name) #define _A_T_X509Certificate(N, name) MAKE_UNPACKING_MACRO(X509Certificate, N, name) #define _A_T_AesContext(N, name) MAKE_UNPACKING_MACRO(AesContext, N, name) diff --git a/src/primitive_crypto.cc b/src/primitive_crypto.cc index bc2657057..56efe75c4 100644 --- a/src/primitive_crypto.cc +++ b/src/primitive_crypto.cc @@ -45,6 +45,7 @@ #include "vm.h" #include "scheduler.h" #include "os.h" +#include "event_sources/async_posix.h" #if (defined(MBEDTLS_CHACHAPOLY_C) && defined(MBEDTLS_CHACHA20_C)) || (defined(CONFIG_MBEDTLS_POLY1305_C) && defined(CONFIG_MBEDTLS_CHACHA20_C)) #define SUPPORT_CHACHA20_POLY1305 1 @@ -761,15 +762,7 @@ PRIMITIVE(aes_ecb_close) { } -static int rsa_rng(void* ctx, unsigned char* buffer, size_t len) { - Process* process = reinterpret_cast(ctx); - if (process != null) { - // Reset the watchdog timer for this process. - // We take the scheduler lock to ensure 64-bit timestamp updates are atomic - // and safe against the scheduler tick reading them. - Locker locker(VM::current()->scheduler()->mutex()); - process->set_run_timestamp(OS::get_monotonic_time()); - } +static int rsa_rng(void* /*ctx*/, unsigned char* buffer, size_t len) { #ifdef TOIT_ESP32 esp_fill_random(buffer, len); #else @@ -831,7 +824,7 @@ static int rsa_parse_key_from_blob(mbedtls_pk_context* pk, Blob key, Blob passwo ret = mbedtls_pk_parse_key(pk, parse_buf, parse_len, pwd, password.length(), - rsa_rng, process); + rsa_rng, null); } else { ret = mbedtls_pk_parse_public_key(pk, parse_buf, parse_len); } @@ -881,6 +874,192 @@ static ByteArray* rsa_export_der(mbedtls_pk_context* pk, Process* process, bool static const int RSA_PADDING_PKCS1_V15 = 0; static const int RSA_PADDING_OAEP_V21 = 1; +#ifdef CONFIG_TOIT_CRYPTO_EXTRA + +class RsaGenerationEventSource : public AsyncEventSource { + public: + static RsaGenerationEventSource* instance() { + static RsaGenerationEventSource* instance = _new RsaGenerationEventSource(); + return instance; + } + private: + RsaGenerationEventSource() : AsyncEventSource("RsaGeneration") {} +}; + +class RsaGenerationResourceGroup : public ResourceGroup { + public: + TAG(RsaGenerationResourceGroup); + explicit RsaGenerationResourceGroup(Process* process) + : ResourceGroup(process, RsaGenerationEventSource::instance()) {} + + protected: + uint32 on_event(Resource* resource, word data, uint32_t state) override { + return state | data; + } +}; + +class RsaGenerationResource : public Resource { + public: + TAG(RsaGenerationResource); + explicit RsaGenerationResource(RsaGenerationResourceGroup* group, int bits) + : Resource(group), bits_(bits) {} + + ~RsaGenerationResource() { + delete thread_; + if (prv_buf_) free(prv_buf_); + if (pub_buf_) free(pub_buf_); + } + + int bits() const { return bits_; } + + void set_results(unsigned char* prv, size_t prv_len, unsigned char* pub, size_t pub_len) { + prv_buf_ = prv; + prv_len_ = prv_len; + pub_buf_ = pub; + pub_len_ = pub_len; + } + + unsigned char* prv_buf() { return prv_buf_; } + size_t prv_len() { return prv_len_; } + unsigned char* pub_buf() { return pub_buf_; } + size_t pub_len() { return pub_len_; } + + int error() const { return error_; } + void set_error(int error) { error_ = error; } + + AsyncEventThread* thread() { + if (thread_ == null) { + thread_ = _new AsyncEventThread("RSA Gen", RsaGenerationEventSource::instance()); + if (thread_) thread_->start(); + } + return thread_; + } + + private: + int bits_; + int error_ = 0; + unsigned char* prv_buf_ = null; + size_t prv_len_ = 0; + unsigned char* pub_buf_ = null; + size_t pub_len_ = 0; + AsyncEventThread* thread_ = null; +}; + +PRIMITIVE(rsa_generate_init) { + ByteArray* proxy = process->object_heap()->allocate_proxy(); + if (proxy == null) FAIL(ALLOCATION_FAILED); + RsaGenerationResourceGroup* group = _new RsaGenerationResourceGroup(process); + if (group == null) FAIL(MALLOC_FAILED); + proxy->set_external_address(group); + return proxy; +} + +PRIMITIVE(rsa_generate_start) { + ARGS(RsaGenerationResourceGroup, group, int, bits); + if (bits != 1024 && bits != 2048 && bits != 3072 && bits != 4096) FAIL(INVALID_ARGUMENT); + + RsaGenerationResource* resource = _new RsaGenerationResource(group, bits); + if (!resource) FAIL(MALLOC_FAILED); + + ByteArray* proxy = process->object_heap()->allocate_proxy(); + if (proxy == null) { + delete resource; + FAIL(ALLOCATION_FAILED); + } + + AsyncEventThread* thread = resource->thread(); + if (!thread) { + delete resource; + FAIL(MALLOC_FAILED); + } + + bool success = thread->run(resource, [](Resource* r) { + RsaGenerationResource* res = static_cast(r); + mbedtls_pk_context pk; + mbedtls_pk_init(&pk); + int ret = mbedtls_pk_setup(&pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)); + if (ret == 0) { + ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(pk), rsa_rng, null, res->bits(), 65537); + } + + if (ret == 0) { + unsigned char* prv = (unsigned char*)malloc(RSA_PRV_DER_MAX_BYTES); + unsigned char* pub = (unsigned char*)malloc(RSA_PUB_DER_MAX_BYTES); + if (!prv || !pub) { + free(prv); free(pub); + ret = MBEDTLS_ERR_PK_ALLOC_FAILED; + } else { + int prv_len = mbedtls_pk_write_key_der(&pk, prv, RSA_PRV_DER_MAX_BYTES); + int pub_len = mbedtls_pk_write_pubkey_der(&pk, pub, RSA_PUB_DER_MAX_BYTES); + if (prv_len < 0 || pub_len < 0) { + ret = prv_len < 0 ? prv_len : pub_len; + free(prv); free(pub); + } else { + // mbedtls writes from the end. Move to start. + unsigned char* prv_start = (unsigned char*)malloc(prv_len); + unsigned char* pub_start = (unsigned char*)malloc(pub_len); + if (!prv_start || !pub_start) { + free(prv_start); free(pub_start); + free(prv); free(pub); + ret = MBEDTLS_ERR_PK_ALLOC_FAILED; + } else { + memcpy(prv_start, prv + RSA_PRV_DER_MAX_BYTES - prv_len, prv_len); + memcpy(pub_start, pub + RSA_PUB_DER_MAX_BYTES - pub_len, pub_len); + res->set_results(prv_start, prv_len, pub_start, pub_len); + free(prv); free(pub); + } + } + } + } + mbedtls_pk_free(&pk); + res->set_error(ret); + return (word)1; // Indicate done. + }); + + if (!success) { + delete resource; + FAIL(INVALID_STATE); + } + + group->register_resource(resource); + proxy->set_external_address(resource); + return proxy; +} + +PRIMITIVE(rsa_generate_finish) { + ARGS(RsaGenerationResource, resource); + if (resource->error() != 0) { + int err = resource->error(); + resource->resource_group()->unregister_resource(resource); + return tls_error(null, process, err); + } + + ByteArray* prv_der = process->allocate_byte_array(resource->prv_len()); + ByteArray* pub_der = process->allocate_byte_array(resource->pub_len()); + if (!prv_der || !pub_der) { + resource->resource_group()->unregister_resource(resource); + FAIL(ALLOCATION_FAILED); + } + + memcpy(ByteArray::Bytes(prv_der).address(), resource->prv_buf(), resource->prv_len()); + memcpy(ByteArray::Bytes(pub_der).address(), resource->pub_buf(), resource->pub_len()); + + Array* pair = process->object_heap()->allocate_array(2, process->null_object()); + if (pair == null) { + resource->resource_group()->unregister_resource(resource); + FAIL(ALLOCATION_FAILED); + } + pair->at_put(0, prv_der); + pair->at_put(1, pub_der); + + resource->resource_group()->unregister_resource(resource); + resource_proxy->clear_external_address(); + + return pair; +} + +#endif // CONFIG_TOIT_CRYPTO_EXTRA + // rsa_generate returns [private_key_der, public_key_der] as a Toit Array. PRIMITIVE(rsa_generate) { ARGS(int, bits); @@ -896,7 +1075,7 @@ PRIMITIVE(rsa_generate) { return tls_error(null, process, ret); } - ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(pk), rsa_rng, process, bits, 65537); + ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(pk), rsa_rng, null, bits, 65537); if (ret != 0) { mbedtls_pk_free(&pk); return tls_error(null, process, ret); @@ -950,7 +1129,7 @@ PRIMITIVE(rsa_sign) { uint8_t sig[MBEDTLS_PK_SIGNATURE_MAX_SIZE]; size_t actual_len = 0; ret = mbedtls_pk_sign(&pk, md_alg, digest.address(), digest.length(), - sig, sizeof(sig), &actual_len, rsa_rng, process); + sig, sizeof(sig), &actual_len, rsa_rng, null); mbedtls_pk_free(&pk); if (ret != 0) return tls_error(null, process, ret); @@ -1079,7 +1258,7 @@ PRIMITIVE(rsa_encrypt) { size_t output_len = 0; ret = mbedtls_pk_encrypt(&pk, data.address(), data.length(), - ByteArray::Bytes(result).address(), &output_len, output_size, rsa_rng, process); + ByteArray::Bytes(result).address(), &output_len, output_size, rsa_rng, null); mbedtls_pk_free(&pk); if (ret != 0) return tls_error(null, process, ret); @@ -1120,7 +1299,7 @@ PRIMITIVE(rsa_decrypt) { size_t output_len = 0; ret = mbedtls_pk_decrypt(&pk, data.address(), data.length(), - ByteArray::Bytes(result).address(), &output_len, output_size, rsa_rng, process); + ByteArray::Bytes(result).address(), &output_len, output_size, rsa_rng, null); mbedtls_pk_free(&pk); if (ret != 0) return tls_error(null, process, ret); diff --git a/src/tags.h b/src/tags.h index e1659738e..065bc45cb 100644 --- a/src/tags.h +++ b/src/tags.h @@ -67,7 +67,7 @@ namespace toit { fn(AeadContext) \ fn(TlsHandshakeToken) \ fn(EspNowResource) \ - fn(MbedTlsSocket) + fn(MbedTlsSocket) // When adding a class make sure that they all are subclasses of // the BleCallbackResource. If it isn't update the Min/MaxTag below. @@ -142,7 +142,9 @@ enum StructTag { // Misc. FontTag, ImageOutputStreamTag, - ChannelTag + ChannelTag, + RsaGenerationResourceTag, + RsaGenerationResourceGroupTag }; #undef MAKE_ENUM diff --git a/toolchains/esp32/sdkconfig.defaults b/toolchains/esp32/sdkconfig.defaults index 56f45bc7e..7b5c132bc 100644 --- a/toolchains/esp32/sdkconfig.defaults +++ b/toolchains/esp32/sdkconfig.defaults @@ -62,7 +62,7 @@ CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=7800 CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=3700 CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK=y CONFIG_MBEDTLS_ECP_RESTARTABLE=y -CONFIG_MBEDTLS_HARDWARE_MPI=n +CONFIG_MBEDTLS_HARDWARE_MPI=y CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y CONFIG_MBEDTLS_POLY1305_C=y diff --git a/toolchains/esp32c3/sdkconfig.defaults b/toolchains/esp32c3/sdkconfig.defaults index be096bbe3..937b2a01f 100644 --- a/toolchains/esp32c3/sdkconfig.defaults +++ b/toolchains/esp32c3/sdkconfig.defaults @@ -51,7 +51,7 @@ CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=7800 CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=3700 CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK=y CONFIG_MBEDTLS_ECP_RESTARTABLE=y -CONFIG_MBEDTLS_HARDWARE_MPI=n +CONFIG_MBEDTLS_HARDWARE_MPI=y CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y CONFIG_MBEDTLS_POLY1305_C=y diff --git a/toolchains/esp32c6/sdkconfig.defaults b/toolchains/esp32c6/sdkconfig.defaults index e087d9083..391a6d4d2 100644 --- a/toolchains/esp32c6/sdkconfig.defaults +++ b/toolchains/esp32c6/sdkconfig.defaults @@ -52,7 +52,7 @@ CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=3700 CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK=y CONFIG_MBEDTLS_ECP_RESTARTABLE=y CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=n -CONFIG_MBEDTLS_HARDWARE_MPI=n +CONFIG_MBEDTLS_HARDWARE_MPI=y CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y CONFIG_MBEDTLS_POLY1305_C=y diff --git a/toolchains/esp32s2/sdkconfig.defaults b/toolchains/esp32s2/sdkconfig.defaults index df790aea1..b55233a0a 100644 --- a/toolchains/esp32s2/sdkconfig.defaults +++ b/toolchains/esp32s2/sdkconfig.defaults @@ -44,7 +44,7 @@ CONFIG_LWIP_HOOK_IP6_INPUT_NONE=y CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=7800 CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=3700 CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK=y -CONFIG_MBEDTLS_HARDWARE_MPI=n +CONFIG_MBEDTLS_HARDWARE_MPI=y CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y CONFIG_MBEDTLS_POLY1305_C=y diff --git a/toolchains/esp32s3/sdkconfig.defaults b/toolchains/esp32s3/sdkconfig.defaults index 282005b77..71d1a1b49 100644 --- a/toolchains/esp32s3/sdkconfig.defaults +++ b/toolchains/esp32s3/sdkconfig.defaults @@ -57,7 +57,7 @@ CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=7800 CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=3700 CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK=y CONFIG_MBEDTLS_ECP_RESTARTABLE=y -CONFIG_MBEDTLS_HARDWARE_MPI=n +CONFIG_MBEDTLS_HARDWARE_MPI=y CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y CONFIG_MBEDTLS_POLY1305_C=y From 8b49aa0da6b854c255347df9d1ce49cd2f41d9fd Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Tue, 7 Apr 2026 22:19:14 +0200 Subject: [PATCH 3/7] Fix RSA resource cleanup and ESP32 Thread::cancel - Add RsaGenerationResource_ class with finalizer/close pattern to ensure proper cleanup of resource group and state on exceptions (e.g. with-timeout). - Add rsa-generate-close primitive to tear down the resource group. - Fix dangling proxy in rsa_generate_finish error paths by adding missing clear_external_address calls. - Add no-op Thread::cancel() for ESP32 (required since async_posix is now compiled for ESP32). --- lib/crypto/rsa.toit | 49 +++++++++++++++---- .../propagation/type_primitive_crypto.cc | 1 + src/os_esp32.cc | 5 ++ src/primitive.h | 3 +- src/primitive_crypto.cc | 9 ++++ 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/lib/crypto/rsa.toit b/lib/crypto/rsa.toit index a7f324a4d..b57fc80ca 100644 --- a/lib/crypto/rsa.toit +++ b/lib/crypto/rsa.toit @@ -127,12 +127,12 @@ class RsaKey: if bits != 1024 and bits != 2048 and bits != 3072 and bits != 4096: throw "INVALID_ARGUMENT" pair := null catch --trace=(: it != "UNIMPLEMENTED"): - group := rsa-generate-init_ - resource-id := rsa-generate-start_ group bits - state := ResourceState_ group resource-id - state.wait - pair = rsa-generate-finish_ resource-id - state.dispose + resource := RsaGenerationResource_ bits + try: + resource.wait + pair = resource.finish + finally: + resource.close if not pair: pair = rsa-generate_ bits @@ -292,14 +292,43 @@ rsa-encrypt_ public-key-der/ByteArray data/ByteArray padding/int hash/int -> Byt rsa-decrypt_ private-key-der/ByteArray data/ByteArray padding/int hash/int -> ByteArray: #primitive.crypto.rsa-decrypt +class RsaGenerationResource_: + group_ := null + state_ := null + + constructor bits/int: + group_ = rsa-generate-init_ + add-finalizer this:: close + resource-id := rsa-generate-start_ group_ bits + state_ = ResourceState_ group_ resource-id + + wait -> none: + state_.wait + + finish -> List: + return rsa-generate-finish_ state_.resource + + close: + if not group_: return + critical-do: + if state_: state_.dispose + state_ = null + rsa-generate-close_ group_ + group_ = null + remove-finalizer this + /** Initializes a resource group for RSA key generation. */ -rsa-generate-init_ -> int: +rsa-generate-init_: #primitive.crypto.rsa-generate-init /** Starts the asynchronous RSA key generation. */ -rsa-generate-start_ group/int bits/int -> int: +rsa-generate-start_ group bits/int: #primitive.crypto.rsa-generate-start /** Finishes the asynchronous RSA key generation and returns the key pair. */ -rsa-generate-finish_ resource-id/int -> List: - #primitive.crypto.rsa-generate-finish \ No newline at end of file +rsa-generate-finish_ resource-id -> List: + #primitive.crypto.rsa-generate-finish + +/** Closes the RSA generation resource group. */ +rsa-generate-close_ group -> none: + #primitive.crypto.rsa-generate-close \ No newline at end of file diff --git a/src/compiler/propagation/type_primitive_crypto.cc b/src/compiler/propagation/type_primitive_crypto.cc index fc183d06a..4cf745fd4 100644 --- a/src/compiler/propagation/type_primitive_crypto.cc +++ b/src/compiler/propagation/type_primitive_crypto.cc @@ -58,6 +58,7 @@ TYPE_PRIMITIVE_ANY(rsa_decrypt) TYPE_PRIMITIVE_ANY(rsa_generate_init) TYPE_PRIMITIVE_ANY(rsa_generate_start) TYPE_PRIMITIVE_ANY(rsa_generate_finish) +TYPE_PRIMITIVE_ANY(rsa_generate_close) } // namespace toit::compiler } // namespace toit diff --git a/src/os_esp32.cc b/src/os_esp32.cc index d388544d8..aebfe9bc7 100644 --- a/src/os_esp32.cc +++ b/src/os_esp32.cc @@ -280,6 +280,11 @@ void Thread::run() { thread_start(void_cast(this)); } +void Thread::cancel() { + // No-op on ESP32. The thread will check for STOPPED state after + // completing its current work item. +} + void Thread::join() { ASSERT(handle_ != null); auto thread = reinterpret_cast(handle_); diff --git a/src/primitive.h b/src/primitive.h index 796f1d511..365be4768 100644 --- a/src/primitive.h +++ b/src/primitive.h @@ -544,7 +544,8 @@ namespace toit { PRIMITIVE(rsa_decrypt, 4) \ PRIMITIVE(rsa_generate_init, 0) \ PRIMITIVE(rsa_generate_start, 2) \ - PRIMITIVE(rsa_generate_finish, 1) + PRIMITIVE(rsa_generate_finish, 1) \ + PRIMITIVE(rsa_generate_close, 1) #define MODULE_CRYPTO_RANDOM(PRIMITIVE) \ PRIMITIVE(random, 1) \ diff --git a/src/primitive_crypto.cc b/src/primitive_crypto.cc index 56efe75c4..f7c70fd5b 100644 --- a/src/primitive_crypto.cc +++ b/src/primitive_crypto.cc @@ -1031,6 +1031,7 @@ PRIMITIVE(rsa_generate_finish) { if (resource->error() != 0) { int err = resource->error(); resource->resource_group()->unregister_resource(resource); + resource_proxy->clear_external_address(); return tls_error(null, process, err); } @@ -1038,6 +1039,7 @@ PRIMITIVE(rsa_generate_finish) { ByteArray* pub_der = process->allocate_byte_array(resource->pub_len()); if (!prv_der || !pub_der) { resource->resource_group()->unregister_resource(resource); + resource_proxy->clear_external_address(); FAIL(ALLOCATION_FAILED); } @@ -1058,6 +1060,13 @@ PRIMITIVE(rsa_generate_finish) { return pair; } +PRIMITIVE(rsa_generate_close) { + ARGS(RsaGenerationResourceGroup, group); + group->tear_down(); + group_proxy->clear_external_address(); + return process->null_object(); +} + #endif // CONFIG_TOIT_CRYPTO_EXTRA // rsa_generate returns [private_key_der, public_key_der] as a Toit Array. From f6e6e8faf21a080f330c5174b28246ab99550057 Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Tue, 7 Apr 2026 22:25:59 +0200 Subject: [PATCH 4/7] Add trailing newline to rsa.toit --- lib/crypto/rsa.toit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/crypto/rsa.toit b/lib/crypto/rsa.toit index b57fc80ca..23c6c6352 100644 --- a/lib/crypto/rsa.toit +++ b/lib/crypto/rsa.toit @@ -331,4 +331,4 @@ rsa-generate-finish_ resource-id -> List: /** Closes the RSA generation resource group. */ rsa-generate-close_ group -> none: - #primitive.crypto.rsa-generate-close \ No newline at end of file + #primitive.crypto.rsa-generate-close From 870bbbc9f512976e97c9e37c299603cecaac7b22 Mon Sep 17 00:00:00 2001 From: Gaston Valiente Date: Sat, 18 Apr 2026 01:27:35 -0300 Subject: [PATCH 5/7] Enable hardware MPI for ESP32, optimize RSA key memory allocation, and remove unused scheduler mutex accessor --- src/primitive_crypto.cc | 22 ++++++++++------------ src/scheduler.h | 2 -- toolchains/esp32/sdkconfig | 5 +++-- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/primitive_crypto.cc b/src/primitive_crypto.cc index f7c70fd5b..a8f2950e8 100644 --- a/src/primitive_crypto.cc +++ b/src/primitive_crypto.cc @@ -996,18 +996,16 @@ PRIMITIVE(rsa_generate_start) { free(prv); free(pub); } else { // mbedtls writes from the end. Move to start. - unsigned char* prv_start = (unsigned char*)malloc(prv_len); - unsigned char* pub_start = (unsigned char*)malloc(pub_len); - if (!prv_start || !pub_start) { - free(prv_start); free(pub_start); - free(prv); free(pub); - ret = MBEDTLS_ERR_PK_ALLOC_FAILED; - } else { - memcpy(prv_start, prv + RSA_PRV_DER_MAX_BYTES - prv_len, prv_len); - memcpy(pub_start, pub + RSA_PUB_DER_MAX_BYTES - pub_len, pub_len); - res->set_results(prv_start, prv_len, pub_start, pub_len); - free(prv); free(pub); - } + memmove(prv, prv + RSA_PRV_DER_MAX_BYTES - prv_len, prv_len); + memmove(pub, pub + RSA_PUB_DER_MAX_BYTES - pub_len, pub_len); + + unsigned char* prv_resized = (unsigned char*)realloc(prv, prv_len); + if (prv_resized != null) prv = prv_resized; + + unsigned char* pub_resized = (unsigned char*)realloc(pub, pub_len); + if (pub_resized != null) pub = pub_resized; + + res->set_results(prv, prv_len, pub, pub_len); } } } diff --git a/src/scheduler.h b/src/scheduler.h index 46787bdb6..6f17f3ea8 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -147,8 +147,6 @@ class Scheduler { bool is_locked() const { return OS::is_locked(mutex_); } bool is_boot_process(Process* process) const { return boot_process_ == process; } - Mutex* mutex() const { return mutex_; } - void iterate_process_chunks(void* context, process_chunk_callback_t callback); private: diff --git a/toolchains/esp32/sdkconfig b/toolchains/esp32/sdkconfig index 3e419d250..8e290ab48 100644 --- a/toolchains/esp32/sdkconfig +++ b/toolchains/esp32/sdkconfig @@ -1752,7 +1752,8 @@ CONFIG_MBEDTLS_ECP_RESTARTABLE=y CONFIG_MBEDTLS_CMAC_C=y CONFIG_MBEDTLS_HARDWARE_AES=y CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y -# CONFIG_MBEDTLS_HARDWARE_MPI is not set +CONFIG_MBEDTLS_HARDWARE_MPI=y +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set CONFIG_MBEDTLS_HARDWARE_SHA=y CONFIG_MBEDTLS_ROM_MD5=y # CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set @@ -2129,7 +2130,7 @@ CONFIG_TOIT_ENABLE_ESPNOW=y CONFIG_TOIT_SYSTEM_SOURCE="system/extensions/esp32/boot.toit" CONFIG_TOIT_SYSTEM_SOURCE_PROJECT_ROOT="system" CONFIG_RODATA_PADDING=2097152 -# end of Advanced setup +# end of Advanced setup # end of Toit # end of Component config From d5ab232db5a87f6e8a4293443a15c635cdc91d58 Mon Sep 17 00:00:00 2001 From: Gaston Valiente Date: Tue, 5 May 2026 12:02:45 -0300 Subject: [PATCH 6/7] feat: enable hardware MPI support and optimize RSA key generation memory management --- src/primitive_crypto.cc | 174 ++++++++++++++++++++++------------- toolchains/esp32/sdkconfig | 4 +- toolchains/esp32c3/sdkconfig | 7 +- toolchains/esp32c6/sdkconfig | 7 +- toolchains/esp32s2/sdkconfig | 7 +- toolchains/esp32s3/sdkconfig | 7 +- 6 files changed, 133 insertions(+), 73 deletions(-) diff --git a/src/primitive_crypto.cc b/src/primitive_crypto.cc index a8f2950e8..aa0efa218 100644 --- a/src/primitive_crypto.cc +++ b/src/primitive_crypto.cc @@ -882,8 +882,17 @@ class RsaGenerationEventSource : public AsyncEventSource { static RsaGenerationEventSource* instance = _new RsaGenerationEventSource(); return instance; } + + AsyncEventThread* thread() { + if (thread_ == null) { + thread_ = _new AsyncEventThread("RSA Gen", this); + if (thread_) thread_->start(); + } + return thread_; + } private: RsaGenerationEventSource() : AsyncEventSource("RsaGeneration") {} + AsyncEventThread* thread_ = null; }; class RsaGenerationResourceGroup : public ResourceGroup { @@ -893,9 +902,7 @@ class RsaGenerationResourceGroup : public ResourceGroup { : ResourceGroup(process, RsaGenerationEventSource::instance()) {} protected: - uint32 on_event(Resource* resource, word data, uint32_t state) override { - return state | data; - } + uint32 on_event(Resource* resource, word data, uint32_t state) override; }; class RsaGenerationResource : public Resource { @@ -905,46 +912,55 @@ class RsaGenerationResource : public Resource { : Resource(group), bits_(bits) {} ~RsaGenerationResource() { - delete thread_; if (prv_buf_) free(prv_buf_); if (pub_buf_) free(pub_buf_); } - int bits() const { return bits_; } - - void set_results(unsigned char* prv, size_t prv_len, unsigned char* pub, size_t pub_len) { - prv_buf_ = prv; - prv_len_ = prv_len; - pub_buf_ = pub; - pub_len_ = pub_len; + void delete_or_mark_for_deletion() override { + bool should_delete = false; + { + Locker locker(resource_group()->event_source()->mutex()); + if (thread_running_) { + destroy_when_done_ = true; + } else { + should_delete = true; + } + } + if (should_delete) delete this; } - unsigned char* prv_buf() { return prv_buf_; } - size_t prv_len() { return prv_len_; } - unsigned char* pub_buf() { return pub_buf_; } - size_t pub_len() { return pub_len_; } - - int error() const { return error_; } - void set_error(int error) { error_ = error; } - - AsyncEventThread* thread() { - if (thread_ == null) { - thread_ = _new AsyncEventThread("RSA Gen", RsaGenerationEventSource::instance()); - if (thread_) thread_->start(); + void on_thread_done() { + bool should_delete = false; + { + Locker locker(resource_group()->event_source()->mutex()); + if (destroy_when_done_) { + should_delete = true; + } else { + thread_running_ = false; + } } - return thread_; + if (should_delete) delete this; } - private: - int bits_; - int error_ = 0; + int bits() const { return bits_; } + + bool thread_running_ = false; unsigned char* prv_buf_ = null; - size_t prv_len_ = 0; unsigned char* pub_buf_ = null; + int error_ = 0; + size_t prv_len_ = 0; size_t pub_len_ = 0; - AsyncEventThread* thread_ = null; + + private: + int bits_; + bool destroy_when_done_ = false; }; +uint32 RsaGenerationResourceGroup::on_event(Resource* resource, word data, uint32_t state) { + static_cast(resource)->on_thread_done(); + return state | data; +} + PRIMITIVE(rsa_generate_init) { ByteArray* proxy = process->object_heap()->allocate_proxy(); if (proxy == null) FAIL(ALLOCATION_FAILED); @@ -961,18 +977,26 @@ PRIMITIVE(rsa_generate_start) { RsaGenerationResource* resource = _new RsaGenerationResource(group, bits); if (!resource) FAIL(MALLOC_FAILED); + resource->prv_buf_ = unvoid_cast(malloc(RSA_PRV_DER_MAX_BYTES)); + resource->pub_buf_ = unvoid_cast(malloc(RSA_PUB_DER_MAX_BYTES)); + if (!resource->prv_buf_ || !resource->pub_buf_) { + delete resource; + FAIL(MALLOC_FAILED); + } + ByteArray* proxy = process->object_heap()->allocate_proxy(); if (proxy == null) { delete resource; FAIL(ALLOCATION_FAILED); } - AsyncEventThread* thread = resource->thread(); + AsyncEventThread* thread = RsaGenerationEventSource::instance()->thread(); if (!thread) { delete resource; FAIL(MALLOC_FAILED); } + resource->thread_running_ = true; bool success = thread->run(resource, [](Resource* r) { RsaGenerationResource* res = static_cast(r); mbedtls_pk_context pk; @@ -983,38 +1007,42 @@ PRIMITIVE(rsa_generate_start) { } if (ret == 0) { - unsigned char* prv = (unsigned char*)malloc(RSA_PRV_DER_MAX_BYTES); - unsigned char* pub = (unsigned char*)malloc(RSA_PUB_DER_MAX_BYTES); - if (!prv || !pub) { - free(prv); free(pub); - ret = MBEDTLS_ERR_PK_ALLOC_FAILED; + unsigned char* prv = res->prv_buf_; + unsigned char* pub = res->pub_buf_; + int prv_len = mbedtls_pk_write_key_der(&pk, prv, RSA_PRV_DER_MAX_BYTES); + int pub_len = mbedtls_pk_write_pubkey_der(&pk, pub, RSA_PUB_DER_MAX_BYTES); + if (prv_len < 0 || pub_len < 0) { + ret = prv_len < 0 ? prv_len : pub_len; } else { - int prv_len = mbedtls_pk_write_key_der(&pk, prv, RSA_PRV_DER_MAX_BYTES); - int pub_len = mbedtls_pk_write_pubkey_der(&pk, pub, RSA_PUB_DER_MAX_BYTES); - if (prv_len < 0 || pub_len < 0) { - ret = prv_len < 0 ? prv_len : pub_len; - free(prv); free(pub); - } else { - // mbedtls writes from the end. Move to start. - memmove(prv, prv + RSA_PRV_DER_MAX_BYTES - prv_len, prv_len); - memmove(pub, pub + RSA_PUB_DER_MAX_BYTES - pub_len, pub_len); - - unsigned char* prv_resized = (unsigned char*)realloc(prv, prv_len); - if (prv_resized != null) prv = prv_resized; - - unsigned char* pub_resized = (unsigned char*)realloc(pub, pub_len); - if (pub_resized != null) pub = pub_resized; - - res->set_results(prv, prv_len, pub, pub_len); - } + // mbedtls writes from the end. Move to start. + memmove(prv, prv + RSA_PRV_DER_MAX_BYTES - prv_len, prv_len); + memmove(pub, pub + RSA_PUB_DER_MAX_BYTES - pub_len, pub_len); + + // RSA_PRV_DER_MAX_BYTES is typically ~550 bytes, but generated keys + // usually only need ~300 bytes. The realloc shrinks the buffer to the + // actual size to recover the unused memory. If realloc fails, it returns + // null without freeing the original block, so the original pointer is + // preserved and no memory is leaked — it just means we keep the larger + // buffer, which is acceptable. + unsigned char* prv_resized = unvoid_cast(realloc(prv, prv_len)); + if (prv_resized) prv = prv_resized; + + unsigned char* pub_resized = unvoid_cast(realloc(pub, pub_len)); + if (pub_resized) pub = pub_resized; + + res->prv_buf_ = prv; + res->prv_len_ = prv_len; + res->pub_buf_ = pub; + res->pub_len_ = pub_len; } } mbedtls_pk_free(&pk); - res->set_error(ret); + res->error_ = ret; return (word)1; // Indicate done. }); if (!success) { + resource->thread_running_ = false; delete resource; FAIL(INVALID_STATE); } @@ -1026,29 +1054,49 @@ PRIMITIVE(rsa_generate_start) { PRIMITIVE(rsa_generate_finish) { ARGS(RsaGenerationResource, resource); - if (resource->error() != 0) { - int err = resource->error(); + if (resource->error_ != 0) { + int err = resource->error_; resource->resource_group()->unregister_resource(resource); resource_proxy->clear_external_address(); return tls_error(null, process, err); } - ByteArray* prv_der = process->allocate_byte_array(resource->prv_len()); - ByteArray* pub_der = process->allocate_byte_array(resource->pub_len()); + // Attempt a best-effort final realloc in case the one in the generation + // thread failed due to heap fragmentation at that point in time. + // If this also fails, we harmlessly keep the original (larger) buffer. + if (resource->prv_buf_ != null) { + unsigned char* p = unvoid_cast(realloc(resource->prv_buf_, resource->prv_len_)); + if (p != null) resource->prv_buf_ = p; + } + if (resource->pub_buf_ != null) { + unsigned char* p = unvoid_cast(realloc(resource->pub_buf_, resource->pub_len_)); + if (p != null) resource->pub_buf_ = p; + } + + // Use the copy pattern (allocate + memcpy). This is safer for GC retries than + // transferring ownership, as the original buffers remain intact until + // the primitive is guaranteed to succeed. + ByteArray* prv_der = process->allocate_byte_array(resource->prv_len_); + ByteArray* pub_der = process->allocate_byte_array(resource->pub_len_); + if (!prv_der || !pub_der) { - resource->resource_group()->unregister_resource(resource); - resource_proxy->clear_external_address(); + // Do not call unregister_resource or clear_external_address on OOM error paths. FAIL(ALLOCATION_FAILED); } - memcpy(ByteArray::Bytes(prv_der).address(), resource->prv_buf(), resource->prv_len()); - memcpy(ByteArray::Bytes(pub_der).address(), resource->pub_buf(), resource->pub_len()); + memcpy(ByteArray::Bytes(prv_der).address(), resource->prv_buf_, resource->prv_len_); + memcpy(ByteArray::Bytes(pub_der).address(), resource->pub_buf_, resource->pub_len_); Array* pair = process->object_heap()->allocate_array(2, process->null_object()); if (pair == null) { - resource->resource_group()->unregister_resource(resource); + // Do not call unregister_resource or clear_external_address on OOM error paths. FAIL(ALLOCATION_FAILED); } + + // Success path: we can now safely free the original buffers as they have been copied. + free(resource->prv_buf_); resource->prv_buf_ = null; + free(resource->pub_buf_); resource->pub_buf_ = null; + pair->at_put(0, prv_der); pair->at_put(1, pub_der); diff --git a/toolchains/esp32/sdkconfig b/toolchains/esp32/sdkconfig index 8e290ab48..99bfa2233 100644 --- a/toolchains/esp32/sdkconfig +++ b/toolchains/esp32/sdkconfig @@ -2122,7 +2122,7 @@ CONFIG_TOIT_REPORT_I2S_DATA_LOSS=y CONFIG_TOIT_INTERPRETER_IN_IRAM=y # CONFIG_TOIT_INTERPRETER_HELPERS_IN_IRAM is not set CONFIG_TOIT_CRYPTO=y -# CONFIG_TOIT_CRYPTO_EXTRA is not set +CONFIG_TOIT_CRYPTO_EXTRA=y CONFIG_TOIT_ENABLE_IP=y CONFIG_TOIT_ENABLE_ETHERNET=y CONFIG_TOIT_ENABLE_WIFI=y @@ -2130,7 +2130,7 @@ CONFIG_TOIT_ENABLE_ESPNOW=y CONFIG_TOIT_SYSTEM_SOURCE="system/extensions/esp32/boot.toit" CONFIG_TOIT_SYSTEM_SOURCE_PROJECT_ROOT="system" CONFIG_RODATA_PADDING=2097152 -# end of Advanced setup +# end of Advanced setup # end of Toit # end of Component config diff --git a/toolchains/esp32c3/sdkconfig b/toolchains/esp32c3/sdkconfig index fc9570ff6..852d38fd1 100644 --- a/toolchains/esp32c3/sdkconfig +++ b/toolchains/esp32c3/sdkconfig @@ -1775,7 +1775,10 @@ CONFIG_MBEDTLS_HARDWARE_AES=y CONFIG_MBEDTLS_AES_USE_INTERRUPT=y CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y -# CONFIG_MBEDTLS_HARDWARE_MPI is not set +CONFIG_MBEDTLS_HARDWARE_MPI=y +CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI=y +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 CONFIG_MBEDTLS_HARDWARE_SHA=y CONFIG_MBEDTLS_ROM_MD5=y # CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set @@ -2134,7 +2137,7 @@ CONFIG_TOIT_REPORT_I2S_DATA_LOSS=y CONFIG_TOIT_INTERPRETER_IN_IRAM=y CONFIG_TOIT_INTERPRETER_HELPERS_IN_IRAM=y CONFIG_TOIT_CRYPTO=y -# CONFIG_TOIT_CRYPTO_EXTRA is not set +CONFIG_TOIT_CRYPTO_EXTRA=y CONFIG_TOIT_ENABLE_IP=y CONFIG_TOIT_ENABLE_ETHERNET=y CONFIG_TOIT_ENABLE_WIFI=y diff --git a/toolchains/esp32c6/sdkconfig b/toolchains/esp32c6/sdkconfig index 4e0e9a524..6bb93ed9a 100644 --- a/toolchains/esp32c6/sdkconfig +++ b/toolchains/esp32c6/sdkconfig @@ -1946,7 +1946,10 @@ CONFIG_MBEDTLS_HARDWARE_AES=y CONFIG_MBEDTLS_AES_USE_INTERRUPT=y CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 # CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER is not set -# CONFIG_MBEDTLS_HARDWARE_MPI is not set +CONFIG_MBEDTLS_HARDWARE_MPI=y +CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI=y +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 CONFIG_MBEDTLS_HARDWARE_SHA=y CONFIG_MBEDTLS_HARDWARE_ECC=y CONFIG_MBEDTLS_ECC_OTHER_CURVES_SOFT_FALLBACK=y @@ -2312,7 +2315,7 @@ CONFIG_TOIT_REPORT_I2S_DATA_LOSS=y CONFIG_TOIT_INTERPRETER_IN_IRAM=y CONFIG_TOIT_INTERPRETER_HELPERS_IN_IRAM=y CONFIG_TOIT_CRYPTO=y -# CONFIG_TOIT_CRYPTO_EXTRA is not set +CONFIG_TOIT_CRYPTO_EXTRA=y CONFIG_TOIT_ENABLE_IP=y CONFIG_TOIT_ENABLE_ETHERNET=y CONFIG_TOIT_ENABLE_WIFI=y diff --git a/toolchains/esp32s2/sdkconfig b/toolchains/esp32s2/sdkconfig index c6ba4c1e9..1efb1108a 100644 --- a/toolchains/esp32s2/sdkconfig +++ b/toolchains/esp32s2/sdkconfig @@ -1608,7 +1608,10 @@ CONFIG_MBEDTLS_AES_USE_INTERRUPT=y CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 CONFIG_MBEDTLS_HARDWARE_GCM=y CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y -# CONFIG_MBEDTLS_HARDWARE_MPI is not set +CONFIG_MBEDTLS_HARDWARE_MPI=y +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 CONFIG_MBEDTLS_HARDWARE_SHA=y CONFIG_MBEDTLS_ROM_MD5=y # CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set @@ -2000,7 +2003,7 @@ CONFIG_TOIT_REPORT_I2S_DATA_LOSS=y CONFIG_TOIT_INTERPRETER_IN_IRAM=y CONFIG_TOIT_INTERPRETER_HELPERS_IN_IRAM=y CONFIG_TOIT_CRYPTO=y -# CONFIG_TOIT_CRYPTO_EXTRA is not set +CONFIG_TOIT_CRYPTO_EXTRA=y CONFIG_TOIT_ENABLE_IP=y CONFIG_TOIT_ENABLE_ETHERNET=y CONFIG_TOIT_ENABLE_WIFI=y diff --git a/toolchains/esp32s3/sdkconfig b/toolchains/esp32s3/sdkconfig index 593d633af..479ded4f3 100644 --- a/toolchains/esp32s3/sdkconfig +++ b/toolchains/esp32s3/sdkconfig @@ -1997,7 +1997,10 @@ CONFIG_MBEDTLS_HARDWARE_AES=y CONFIG_MBEDTLS_AES_USE_INTERRUPT=y CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y -# CONFIG_MBEDTLS_HARDWARE_MPI is not set +CONFIG_MBEDTLS_HARDWARE_MPI=y +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 CONFIG_MBEDTLS_HARDWARE_SHA=y CONFIG_MBEDTLS_ROM_MD5=y # CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set @@ -2408,7 +2411,7 @@ CONFIG_TOIT_REPORT_I2S_DATA_LOSS=y CONFIG_TOIT_INTERPRETER_IN_IRAM=y CONFIG_TOIT_INTERPRETER_HELPERS_IN_IRAM=y CONFIG_TOIT_CRYPTO=y -# CONFIG_TOIT_CRYPTO_EXTRA is not set +CONFIG_TOIT_CRYPTO_EXTRA=y CONFIG_TOIT_ENABLE_IP=y CONFIG_TOIT_ENABLE_ETHERNET=y CONFIG_TOIT_ENABLE_WIFI=y From 17d3a86bd93463d31a305551da73b6db1ef885d6 Mon Sep 17 00:00:00 2001 From: Gaston Valiente Date: Sun, 14 Jun 2026 22:46:56 -0300 Subject: [PATCH 7/7] fix: make RsaGenerationResource fields private, add thread guard, fix resource tags, enable CRYPTO_EXTRA in sdkconfig.defaults --- src/primitive_crypto.cc | 102 +++++++++++++++++--------- src/tags.h | 8 +- toolchains/esp32/sdkconfig.defaults | 1 + toolchains/esp32c3/sdkconfig.defaults | 1 + toolchains/esp32c6/sdkconfig.defaults | 1 + toolchains/esp32s2/sdkconfig.defaults | 1 + toolchains/esp32s3/sdkconfig.defaults | 1 + 7 files changed, 75 insertions(+), 40 deletions(-) diff --git a/src/primitive_crypto.cc b/src/primitive_crypto.cc index aa0efa218..f98ae110f 100644 --- a/src/primitive_crypto.cc +++ b/src/primitive_crypto.cc @@ -943,17 +943,53 @@ class RsaGenerationResource : public Resource { } int bits() const { return bits_; } + bool is_thread_running() const { return thread_running_; } + int error() const { return error_; } + size_t prv_len() const { return prv_len_; } + size_t pub_len() const { return pub_len_; } + unsigned char* prv_buf() const { return prv_buf_; } + unsigned char* pub_buf() const { return pub_buf_; } + void set_buffers(unsigned char* prv, unsigned char* pub) { + prv_buf_ = prv; + pub_buf_ = pub; + } + + void set_thread_running(bool running) { thread_running_ = running; } + + void set_result(unsigned char* prv, size_t prv_len, + unsigned char* pub, size_t pub_len) { + prv_buf_ = prv; prv_len_ = prv_len; + pub_buf_ = pub; pub_len_ = pub_len; + } + + void set_error(int err) { error_ = err; } + + void try_shrink_buffers() { + if (prv_buf_) { + unsigned char* p = unvoid_cast(realloc(prv_buf_, prv_len_)); + if (p) prv_buf_ = p; + } + if (pub_buf_) { + unsigned char* p = unvoid_cast(realloc(pub_buf_, pub_len_)); + if (p) pub_buf_ = p; + } + } + + void free_and_release_buffers() { + free(prv_buf_); prv_buf_ = null; + free(pub_buf_); pub_buf_ = null; + } + + private: + int bits_; bool thread_running_ = false; + bool destroy_when_done_ = false; unsigned char* prv_buf_ = null; unsigned char* pub_buf_ = null; int error_ = 0; size_t prv_len_ = 0; size_t pub_len_ = 0; - - private: - int bits_; - bool destroy_when_done_ = false; }; uint32 RsaGenerationResourceGroup::on_event(Resource* resource, word data, uint32_t state) { @@ -977,9 +1013,10 @@ PRIMITIVE(rsa_generate_start) { RsaGenerationResource* resource = _new RsaGenerationResource(group, bits); if (!resource) FAIL(MALLOC_FAILED); - resource->prv_buf_ = unvoid_cast(malloc(RSA_PRV_DER_MAX_BYTES)); - resource->pub_buf_ = unvoid_cast(malloc(RSA_PUB_DER_MAX_BYTES)); - if (!resource->prv_buf_ || !resource->pub_buf_) { + resource->set_buffers( + unvoid_cast(malloc(RSA_PRV_DER_MAX_BYTES)), + unvoid_cast(malloc(RSA_PUB_DER_MAX_BYTES))); + if (!resource->prv_buf() || !resource->pub_buf()) { delete resource; FAIL(MALLOC_FAILED); } @@ -996,7 +1033,7 @@ PRIMITIVE(rsa_generate_start) { FAIL(MALLOC_FAILED); } - resource->thread_running_ = true; + resource->set_thread_running(true); bool success = thread->run(resource, [](Resource* r) { RsaGenerationResource* res = static_cast(r); mbedtls_pk_context pk; @@ -1007,8 +1044,8 @@ PRIMITIVE(rsa_generate_start) { } if (ret == 0) { - unsigned char* prv = res->prv_buf_; - unsigned char* pub = res->pub_buf_; + unsigned char* prv = res->prv_buf(); + unsigned char* pub = res->pub_buf(); int prv_len = mbedtls_pk_write_key_der(&pk, prv, RSA_PRV_DER_MAX_BYTES); int pub_len = mbedtls_pk_write_pubkey_der(&pk, pub, RSA_PUB_DER_MAX_BYTES); if (prv_len < 0 || pub_len < 0) { @@ -1030,19 +1067,16 @@ PRIMITIVE(rsa_generate_start) { unsigned char* pub_resized = unvoid_cast(realloc(pub, pub_len)); if (pub_resized) pub = pub_resized; - res->prv_buf_ = prv; - res->prv_len_ = prv_len; - res->pub_buf_ = pub; - res->pub_len_ = pub_len; + res->set_result(prv, prv_len, pub, pub_len); } } mbedtls_pk_free(&pk); - res->error_ = ret; + res->set_error(ret); return (word)1; // Indicate done. }); if (!success) { - resource->thread_running_ = false; + resource->set_thread_running(false); delete resource; FAIL(INVALID_STATE); } @@ -1054,8 +1088,13 @@ PRIMITIVE(rsa_generate_start) { PRIMITIVE(rsa_generate_finish) { ARGS(RsaGenerationResource, resource); - if (resource->error_ != 0) { - int err = resource->error_; + + // Guard: if the worker thread is still running, buffers and lengths are + // not yet finalized. The caller should wait for the resource event first. + if (resource->is_thread_running()) FAIL(INVALID_STATE); + + if (resource->error() != 0) { + int err = resource->error(); resource->resource_group()->unregister_resource(resource); resource_proxy->clear_external_address(); return tls_error(null, process, err); @@ -1064,28 +1103,20 @@ PRIMITIVE(rsa_generate_finish) { // Attempt a best-effort final realloc in case the one in the generation // thread failed due to heap fragmentation at that point in time. // If this also fails, we harmlessly keep the original (larger) buffer. - if (resource->prv_buf_ != null) { - unsigned char* p = unvoid_cast(realloc(resource->prv_buf_, resource->prv_len_)); - if (p != null) resource->prv_buf_ = p; - } - if (resource->pub_buf_ != null) { - unsigned char* p = unvoid_cast(realloc(resource->pub_buf_, resource->pub_len_)); - if (p != null) resource->pub_buf_ = p; - } + resource->try_shrink_buffers(); - // Use the copy pattern (allocate + memcpy). This is safer for GC retries than - // transferring ownership, as the original buffers remain intact until + // Use the copy pattern (allocate + memcpy). This is safer for GC retries than + // transferring ownership, as the original buffers remain intact until // the primitive is guaranteed to succeed. - ByteArray* prv_der = process->allocate_byte_array(resource->prv_len_); - ByteArray* pub_der = process->allocate_byte_array(resource->pub_len_); - + ByteArray* prv_der = process->allocate_byte_array(resource->prv_len()); + ByteArray* pub_der = process->allocate_byte_array(resource->pub_len()); if (!prv_der || !pub_der) { // Do not call unregister_resource or clear_external_address on OOM error paths. FAIL(ALLOCATION_FAILED); } - memcpy(ByteArray::Bytes(prv_der).address(), resource->prv_buf_, resource->prv_len_); - memcpy(ByteArray::Bytes(pub_der).address(), resource->pub_buf_, resource->pub_len_); + memcpy(ByteArray::Bytes(prv_der).address(), resource->prv_buf(), resource->prv_len()); + memcpy(ByteArray::Bytes(pub_der).address(), resource->pub_buf(), resource->pub_len()); Array* pair = process->object_heap()->allocate_array(2, process->null_object()); if (pair == null) { @@ -1093,9 +1124,8 @@ PRIMITIVE(rsa_generate_finish) { FAIL(ALLOCATION_FAILED); } - // Success path: we can now safely free the original buffers as they have been copied. - free(resource->prv_buf_); resource->prv_buf_ = null; - free(resource->pub_buf_); resource->pub_buf_ = null; + // Success path: free the original buffers now that they have been copied. + resource->free_and_release_buffers(); pair->at_put(0, prv_der); pair->at_put(1, pub_der); diff --git a/src/tags.h b/src/tags.h index 065bc45cb..fd7761bd8 100644 --- a/src/tags.h +++ b/src/tags.h @@ -67,7 +67,8 @@ namespace toit { fn(AeadContext) \ fn(TlsHandshakeToken) \ fn(EspNowResource) \ - fn(MbedTlsSocket) + fn(MbedTlsSocket) \ + fn(RsaGenerationResource) \ // When adding a class make sure that they all are subclasses of // the BleCallbackResource. If it isn't update the Min/MaxTag below. @@ -93,6 +94,7 @@ namespace toit { fn(SpiFlashResourceGroup) \ fn(SignalResourceGroup) \ fn(SocketResourceGroup) \ + fn(RsaGenerationResourceGroup) \ fn(TcpResourceGroup) \ fn(TimerResourceGroup) \ fn(RpcResourceGroup) \ @@ -142,9 +144,7 @@ enum StructTag { // Misc. FontTag, ImageOutputStreamTag, - ChannelTag, - RsaGenerationResourceTag, - RsaGenerationResourceGroupTag + ChannelTag }; #undef MAKE_ENUM diff --git a/toolchains/esp32/sdkconfig.defaults b/toolchains/esp32/sdkconfig.defaults index 27408b11b..60180926e 100644 --- a/toolchains/esp32/sdkconfig.defaults +++ b/toolchains/esp32/sdkconfig.defaults @@ -76,3 +76,4 @@ CONFIG_SPI_FLASH_LOG_FAILED_WRITE=y CONFIG_SPI_FLASH_WARN_SETTING_ZERO_TO_ONE=y CONFIG_WS_TRANSPORT=n CONFIG_TOIT_INTERPRETER_HELPERS_IN_IRAM=n +CONFIG_TOIT_CRYPTO_EXTRA=y diff --git a/toolchains/esp32c3/sdkconfig.defaults b/toolchains/esp32c3/sdkconfig.defaults index 78d6e8bd2..c79dac72b 100644 --- a/toolchains/esp32c3/sdkconfig.defaults +++ b/toolchains/esp32c3/sdkconfig.defaults @@ -64,3 +64,4 @@ CONFIG_SPI_FLASH_VERIFY_WRITE=y CONFIG_SPI_FLASH_LOG_FAILED_WRITE=y CONFIG_SPI_FLASH_WARN_SETTING_ZERO_TO_ONE=y CONFIG_WS_TRANSPORT=n +CONFIG_TOIT_CRYPTO_EXTRA=y diff --git a/toolchains/esp32c6/sdkconfig.defaults b/toolchains/esp32c6/sdkconfig.defaults index ab8532068..878ce47eb 100644 --- a/toolchains/esp32c6/sdkconfig.defaults +++ b/toolchains/esp32c6/sdkconfig.defaults @@ -66,3 +66,4 @@ CONFIG_SPI_FLASH_VERIFY_WRITE=y CONFIG_SPI_FLASH_LOG_FAILED_WRITE=y CONFIG_SPI_FLASH_WARN_SETTING_ZERO_TO_ONE=y CONFIG_WS_TRANSPORT=n +CONFIG_TOIT_CRYPTO_EXTRA=y diff --git a/toolchains/esp32s2/sdkconfig.defaults b/toolchains/esp32s2/sdkconfig.defaults index 2ee9212f8..192a459fd 100644 --- a/toolchains/esp32s2/sdkconfig.defaults +++ b/toolchains/esp32s2/sdkconfig.defaults @@ -58,3 +58,4 @@ CONFIG_SPI_FLASH_LOG_FAILED_WRITE=y CONFIG_SPI_FLASH_WARN_SETTING_ZERO_TO_ONE=y CONFIG_WS_TRANSPORT=n CONFIG_TOIT_SPIRAM_HEAP=n +CONFIG_TOIT_CRYPTO_EXTRA=y diff --git a/toolchains/esp32s3/sdkconfig.defaults b/toolchains/esp32s3/sdkconfig.defaults index da3d7a16f..db423f3d0 100644 --- a/toolchains/esp32s3/sdkconfig.defaults +++ b/toolchains/esp32s3/sdkconfig.defaults @@ -70,3 +70,4 @@ CONFIG_SPI_FLASH_VERIFY_WRITE=y CONFIG_SPI_FLASH_LOG_FAILED_WRITE=y CONFIG_SPI_FLASH_WARN_SETTING_ZERO_TO_ONE=y CONFIG_WS_TRANSPORT=n +CONFIG_TOIT_CRYPTO_EXTRA=y