From 4a160388ebb277029f611710ae0d4962d2acc4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 3 Oct 2026 23:04:15 +0200 Subject: [PATCH 1/6] Seed the SoftDevice RNG from the CRACEN TRNG on every request NRF_EVT_RAND_SEED_REQUEST was answered with FICR DEVICEID XOR the domain 0 GRTC SYSCOUNTER, which is predictable. The SoftDevice RNG feeds LESC key generation. Both the initial seed in begin() and later seed requests now go through seed_softdevice_rng(), which draws from the CRACEN TRNG. The RNG is no longer stopped after seeding, so a reseed cannot cut off key generation running on another task. --- libraries/Bluefruit54Lib/src/bluefruit.cpp | 31 ++++++++-------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/libraries/Bluefruit54Lib/src/bluefruit.cpp b/libraries/Bluefruit54Lib/src/bluefruit.cpp index 182abad..e11a856 100644 --- a/libraries/Bluefruit54Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit54Lib/src/bluefruit.cpp @@ -84,6 +84,14 @@ static void bluefruit_blinky_cb( TimerHandle_t xTimer ) #endif } +// s145 has no entropy source of its own and asks the application for a seed (NRF_EVT_RAND_SEED_REQUEST). +// The RNG is left running: LESC key generation draws from it on another task. +static bool seed_softdevice_rng(void) +{ + uint8_t seed[SD_RAND_SEED_SIZE]; + return nRF54Crypto.begin() && nRF54Crypto.random(seed, sizeof(seed)) && (sd_rand_seed_set(seed) == NRF_SUCCESS); +} + static void nrf_error_cb(uint32_t id, uint32_t pc, uint32_t info) { #if CFG_DEBUG @@ -288,15 +296,8 @@ bool AdafruitBluefruit::begin(uint8_t prph_count, uint8_t central_count) if ( sd_err != NRF_SUCCESS ) sd_isr_forwarding_disable(); VERIFY_STATUS( sd_err, false ); - // s145 asks for a seed (NRF_EVT_RAND_SEED_REQUEST) and sd_ble_enable() fails with INVALID_STATE without one - { - uint8_t seed[SD_RAND_SEED_SIZE]; - nRF54Crypto.begin(); - bool seeded = nRF54Crypto.random(seed, sizeof(seed)); - nRF54Crypto.end(); - VERIFY(seeded, false); - VERIFY_STATUS( sd_rand_seed_set(seed), false ); - } + // sd_ble_enable() fails with INVALID_STATE until the RNG is seeded + VERIFY( seed_softdevice_rng(), false ); /*------------------------------------------------------------------*/ /* SoftDevice Default Configuration depending on the number of @@ -694,17 +695,7 @@ void adafruit_soc_task(void* arg) break; case NRF_EVT_RAND_SEED_REQUEST: - { - // S145 requires the application to seed the RNG - uint8_t seed[SD_RAND_SEED_SIZE]; - // Use FICR device ID and GRTC counter as entropy source - uint32_t* seed32 = (uint32_t*)seed; - for (uint32_t i = 0; i < SD_RAND_SEED_SIZE / 4; i++) - { - seed32[i] = NRF_FICR->INFO.DEVICEID[i & 1] ^ (uint32_t)(NRF_GRTC->SYSCOUNTER[0].SYSCOUNTERL + i); - } - sd_rand_seed_set(seed); - } + seed_softdevice_rng(); break; default: break; From f94ff6e374c383c67ed05ce671fb366ffc317c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 3 Oct 2026 23:11:35 +0200 Subject: [PATCH 2/6] Retry the SoftDevice RNG seed until it succeeds A failed seed attempt on NRF_EVT_RAND_SEED_REQUEST left the SoftDevice RNG unseeded. Retry, restarting the CRACEN RNG between attempts so a generator stuck in its error state is reset. --- libraries/Bluefruit54Lib/src/bluefruit.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/Bluefruit54Lib/src/bluefruit.cpp b/libraries/Bluefruit54Lib/src/bluefruit.cpp index e11a856..72a78b4 100644 --- a/libraries/Bluefruit54Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit54Lib/src/bluefruit.cpp @@ -695,7 +695,12 @@ void adafruit_soc_task(void* arg) break; case NRF_EVT_RAND_SEED_REQUEST: - seed_softdevice_rng(); + // An unanswered request leaves the SoftDevice RNG unseeded. A halted TRNG only recovers through end()/begin(). + while ( !seed_softdevice_rng() ) + { + nRF54Crypto.end(); + delay(1); + } break; default: break; From 0acc127259012459c475fa89d4ed97ef84b4cf61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 4 Oct 2026 09:46:15 +0200 Subject: [PATCH 3/6] Retry a failed RNG seed without blocking SoC event dispatch f94ff6e retried inline in the seed request handler, so a TRNG that kept failing would stop adafruit_soc_task from dispatching any further SoC events, flash completions included. Mark the seed as pending and retry it between event batches, waking every 10 ms while it is outstanding. --- libraries/Bluefruit54Lib/src/bluefruit.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/libraries/Bluefruit54Lib/src/bluefruit.cpp b/libraries/Bluefruit54Lib/src/bluefruit.cpp index 72a78b4..ee621a7 100644 --- a/libraries/Bluefruit54Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit54Lib/src/bluefruit.cpp @@ -673,9 +673,20 @@ void adafruit_soc_task(void* arg) { (void) arg; + // An unanswered seed request leaves the SoftDevice RNG unseeded, so a failed seed is retried between + // event batches rather than inline, where it would hold up flash completions. + bool seed_pending = false; + while (1) { - if ( xSemaphoreTake(Bluefruit._soc_event_sem, portMAX_DELAY) ) + if ( seed_pending ) + { + seed_pending = !seed_softdevice_rng(); + // A halted TRNG only recovers through end()/begin() + if ( seed_pending ) nRF54Crypto.end(); + } + + if ( xSemaphoreTake(Bluefruit._soc_event_sem, seed_pending ? pdMS_TO_TICKS(10) : portMAX_DELAY) ) { uint32_t soc_evt; uint32_t err = ERROR_NONE; @@ -695,12 +706,7 @@ void adafruit_soc_task(void* arg) break; case NRF_EVT_RAND_SEED_REQUEST: - // An unanswered request leaves the SoftDevice RNG unseeded. A halted TRNG only recovers through end()/begin(). - while ( !seed_softdevice_rng() ) - { - nRF54Crypto.end(); - delay(1); - } + seed_pending = true; break; default: break; From 33632238f0654c79991cb918cf2a8a5e0366e9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 4 Oct 2026 11:55:43 +0200 Subject: [PATCH 4/6] Recover a halted CRACEN RNG in place instead of stopping it 0acc127 recovered a failed seed by calling nRF54Crypto.end(), which disables the RNG while LESC key generation may be reading it on another task. cracen_rng_fill() now soft-resets a generator in its error state while it waits for the FIFO, the same reset cracen_rng_start() already applied, so no caller has to stop the RNG to recover it. The SoC task only retries the seed. --- libraries/Bluefruit54Lib/src/bluefruit.cpp | 7 +---- libraries/nRF54Crypto/src/nRF54Crypto.cpp | 35 ++++++++++++++-------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/libraries/Bluefruit54Lib/src/bluefruit.cpp b/libraries/Bluefruit54Lib/src/bluefruit.cpp index ee621a7..3b05fcc 100644 --- a/libraries/Bluefruit54Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit54Lib/src/bluefruit.cpp @@ -679,12 +679,7 @@ void adafruit_soc_task(void* arg) while (1) { - if ( seed_pending ) - { - seed_pending = !seed_softdevice_rng(); - // A halted TRNG only recovers through end()/begin() - if ( seed_pending ) nRF54Crypto.end(); - } + if ( seed_pending ) seed_pending = !seed_softdevice_rng(); if ( xSemaphoreTake(Bluefruit._soc_event_sem, seed_pending ? pdMS_TO_TICKS(10) : portMAX_DELAY) ) { diff --git a/libraries/nRF54Crypto/src/nRF54Crypto.cpp b/libraries/nRF54Crypto/src/nRF54Crypto.cpp index 64a6a8d..2172ce4 100644 --- a/libraries/nRF54Crypto/src/nRF54Crypto.cpp +++ b/libraries/nRF54Crypto/src/nRF54Crypto.cpp @@ -45,18 +45,32 @@ nRF54CryptoClass nRF54Crypto; static bool _rng_started = false; +// Enable with 1x 128-bit block for AES conditioning +static void cracen_rng_control(bool soft_reset) +{ + nrf_cracen_rng_control_t cfg = { 0 }; + cfg.enable = true; + cfg.number_128_blocks = 1; + cfg.soft_reset = soft_reset; + nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); +} + +// A halted generator needs a soft reset before it produces again. Done in place rather than through +// stop/start, so a reader on another task never finds the RNG disabled under it. +static void cracen_rng_reset_if_halted(void) +{ + if (nrf_cracen_rng_fsm_state_get(NRF_CRACENCORE) != NRF_CRACEN_RNG_FSM_STATE_ERROR) return; + cracen_rng_control(true); + cracen_rng_control(false); +} + static bool cracen_rng_start(void) { if (_rng_started) return true; // Enable CRACEN RNG module nrf_cracen_module_enable(NRF_CRACEN, NRF_CRACEN_MODULE_RNG_MASK); - - // Configure RNG: enable with 1x 128-bit block for AES conditioning - nrf_cracen_rng_control_t cfg = { 0 }; - cfg.enable = true; - cfg.number_128_blocks = 1; - nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); + cracen_rng_control(false); // Wait for the FSM to leave RESET/STARTUP; a full FIFO parks it in IDLE_STANDBY (rings off) uint32_t timeout = RNG_TIMEOUT; @@ -68,13 +82,7 @@ static bool cracen_rng_start(void) _rng_started = true; return true; } - if (state == NRF_CRACEN_RNG_FSM_STATE_ERROR) { - // A halted generator needs a soft reset before it restarts - cfg.soft_reset = true; - nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); - cfg.soft_reset = false; - nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); - } + cracen_rng_reset_if_halted(); } return false; } @@ -102,6 +110,7 @@ static bool cracen_rng_fill(uint8_t *dest, size_t len) uint32_t timeout = RNG_TIMEOUT; while (nrf_cracen_rng_fifo_level_get(NRF_CRACENCORE) == 0) { if (--timeout == 0) return false; + cracen_rng_reset_if_halted(); } // Read a 32-bit random word From bf633748ead47cc29e1aa6a2a1b0fac356ba51f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 4 Oct 2026 15:15:08 +0200 Subject: [PATCH 5/6] Serialize CRACEN RNG FIFO reads across tasks SoftDevice seed requests and LESC key generation read the RNG from different tasks. A read by one between the other's FIFO level check and its read, or a soft reset clearing the FIFO there, returned a word that is not random. Suspend the scheduler for the duration of each fill. --- libraries/nRF54Crypto/src/nRF54Crypto.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libraries/nRF54Crypto/src/nRF54Crypto.cpp b/libraries/nRF54Crypto/src/nRF54Crypto.cpp index 2172ce4..513911f 100644 --- a/libraries/nRF54Crypto/src/nRF54Crypto.cpp +++ b/libraries/nRF54Crypto/src/nRF54Crypto.cpp @@ -100,7 +100,7 @@ static void cracen_rng_stop(void) _rng_started = false; } -static bool cracen_rng_fill(uint8_t *dest, size_t len) +static bool cracen_rng_fill_unlocked(uint8_t *dest, size_t len) { if (!_rng_started) return false; @@ -125,6 +125,17 @@ static bool cracen_rng_fill(uint8_t *dest, size_t len) return true; } +// Readers run on different tasks (SoftDevice seed requests, LESC key generation). Another reader, or +// a reset clearing the FIFO, between the level check and the read would hand out a word that is not +// random. Suspending the scheduler is the lock: a fill takes microseconds unless the generator is failing. +static bool cracen_rng_fill(uint8_t *dest, size_t len) +{ + vTaskSuspendAll(); + bool ok = cracen_rng_fill_unlocked(dest, len); + (void) xTaskResumeAll(); + return ok; +} + //--------------------------------------------------------------------+ // default_CSPRNG - called by TinyCrypt uECC //--------------------------------------------------------------------+ From 221a11f307755ee5c0002631932d1a614dc454e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 4 Oct 2026 15:23:18 +0200 Subject: [PATCH 6/6] Limit the CRACEN RNG lock to the FIFO check and read bf63374 suspended the scheduler for a whole fill, including the FIFO polling wait, which can run for RNG_TIMEOUT iterations per word while the generator is failing. Take one word at a time in a critical section that covers only the level check and read, or the reset of a halted generator, and poll outside it. --- libraries/nRF54Crypto/src/nRF54Crypto.cpp | 37 ++++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/libraries/nRF54Crypto/src/nRF54Crypto.cpp b/libraries/nRF54Crypto/src/nRF54Crypto.cpp index 513911f..8218f83 100644 --- a/libraries/nRF54Crypto/src/nRF54Crypto.cpp +++ b/libraries/nRF54Crypto/src/nRF54Crypto.cpp @@ -100,22 +100,34 @@ static void cracen_rng_stop(void) _rng_started = false; } -static bool cracen_rng_fill_unlocked(uint8_t *dest, size_t len) +// Readers run on different tasks (SoftDevice seed requests, LESC key generation). Another reader, or +// a reset clearing the FIFO, between the level check and the read would hand out a word that is not +// random, so the two happen in one critical section. Waiting for data stays outside it. +static bool cracen_rng_word(uint32_t *word) +{ + taskENTER_CRITICAL(); + bool got = nrf_cracen_rng_fifo_level_get(NRF_CRACENCORE) > 0; + if (got) { + *word = nrf_cracen_rng_fifo_get(NRF_CRACENCORE); + } else { + cracen_rng_reset_if_halted(); + } + taskEXIT_CRITICAL(); + return got; +} + +static bool cracen_rng_fill(uint8_t *dest, size_t len) { if (!_rng_started) return false; size_t offset = 0; while (offset < len) { - // Wait for FIFO to have data + uint32_t word; uint32_t timeout = RNG_TIMEOUT; - while (nrf_cracen_rng_fifo_level_get(NRF_CRACENCORE) == 0) { + while (!cracen_rng_word(&word)) { if (--timeout == 0) return false; - cracen_rng_reset_if_halted(); } - // Read a 32-bit random word - uint32_t word = nrf_cracen_rng_fifo_get(NRF_CRACENCORE); - // Copy bytes (handle partial word at end) size_t remaining = len - offset; size_t to_copy = (remaining < 4) ? remaining : 4; @@ -125,17 +137,6 @@ static bool cracen_rng_fill_unlocked(uint8_t *dest, size_t len) return true; } -// Readers run on different tasks (SoftDevice seed requests, LESC key generation). Another reader, or -// a reset clearing the FIFO, between the level check and the read would hand out a word that is not -// random. Suspending the scheduler is the lock: a fill takes microseconds unless the generator is failing. -static bool cracen_rng_fill(uint8_t *dest, size_t len) -{ - vTaskSuspendAll(); - bool ok = cracen_rng_fill_unlocked(dest, len); - (void) xTaskResumeAll(); - return ok; -} - //--------------------------------------------------------------------+ // default_CSPRNG - called by TinyCrypt uECC //--------------------------------------------------------------------+