diff --git a/libraries/Bluefruit54Lib/src/BLESecurity.cpp b/libraries/Bluefruit54Lib/src/BLESecurity.cpp index 757a9ac..a65fdb7 100644 --- a/libraries/Bluefruit54Lib/src/BLESecurity.cpp +++ b/libraries/Bluefruit54Lib/src/BLESecurity.cpp @@ -90,11 +90,24 @@ BLESecurity::BLESecurity(void) _passkey_req_cb = NULL; _complete_cb = NULL; _secured_cb = NULL; +#if LESC_CRYPTO_AVAILABLE + _lesc_keys = false; +#endif } bool BLESecurity::begin(void) { #if LESC_CRYPTO_AVAILABLE + // If this fails, the next pairing tries again before it needs the key pair + _lesc_keys = _genLescKeys(); +#endif + + return true; +} + +#if LESC_CRYPTO_AVAILABLE +bool BLESecurity::_genLescKeys(void) +{ #ifdef NRF_CRYPTOCELL // Initalize Crypto lib for LESC (safe to call multiple times) nRFCrypto.begin(); @@ -118,7 +131,7 @@ bool BLESecurity::begin(void) // Generate P-256 key pair (raw big-endian bytes) uint8_t pubkey_raw[64]; - nRF54Crypto.genKeyPair(_private_key, pubkey_raw); + if ( !nRF54Crypto.genKeyPair(_private_key, pubkey_raw) ) return false; // Copy raw pubkey into _pubkey (skip header byte at [0]) _pubkey[0] = 0x04; // uncompressed point header @@ -129,10 +142,10 @@ bool BLESecurity::begin(void) // Public Key = 32-byte N1 + 32-byte N2 swap_endian(_pubkey+1, 32); swap_endian(_pubkey+1+32, 32); -#endif return true; } +#endif void BLESecurity::setIOCaps(bool display, bool yes_no, bool keyboard) { @@ -310,6 +323,15 @@ void BLESecurity::_eventHandler(ble_evt_t* evt) }; #if LESC_CRYPTO_AVAILABLE + // No key pair from begin(): try again, and refuse the pairing if there is still none + if ( !_lesc_keys ) _lesc_keys = _genLescKeys(); + if ( !_lesc_keys ) + { + LOG_LV1("PAIR", "No LESC key pair, refusing to pair"); + sd_ble_gap_sec_params_reply(conn_hdl, BLE_GAP_SEC_STATUS_UNSPECIFIED, NULL, NULL); + break; + } + keyset.keys_own.p_pk = (ble_gap_lesc_p256_pk_t*) (_pubkey+1); keyset.keys_peer.p_pk = (ble_gap_lesc_p256_pk_t*) (_peer_pubkey+1); #endif @@ -357,6 +379,7 @@ void BLESecurity::_eventHandler(ble_evt_t* evt) swap_endian(_peer_pubkey+1+32, 32); ble_gap_lesc_dhkey_t dhkey; + uint8_t dh_status = BLE_GAP_SEC_STATUS_SUCCESS; #ifdef NRF_CRYPTOCELL // Raw public key from peer device is uncompressed @@ -372,9 +395,11 @@ void BLESecurity::_eventHandler(ble_evt_t* evt) peerPublickKey.end(); #else - // CRACEN path: use TinyCrypt uECC for ECDH + // CRACEN path: use TinyCrypt uECC for ECDH. It fails when the TRNG can't supply its random + // initial Z, or when the result is the point at infinity (a bad peer key). bool ecdh_ok = nRF54Crypto.sharedSecret(_peer_pubkey+1, _private_key, dhkey.key); LOG_LV2("PAIR", "ECDH shared secret %s", ecdh_ok ? "ok" : "FAILED"); + if ( !ecdh_ok ) dh_status = BLE_GAP_SEC_STATUS_DHKEY_FAILURE; #endif // Swap Endian before sending to air @@ -386,8 +411,8 @@ void BLESecurity::_eventHandler(ble_evt_t* evt) // S145 v9 added a sec_status parameter before the dhkey pointer. // BLE_GAP_SEC_STATUS_SUCCESS accepts the derivation; non-success - // codes (BLE_GAP_SEC_STATUS_INVALID_PARAMS etc.) reject pairing. - uint32_t dh_err = sd_ble_gap_lesc_dhkey_reply(conn_hdl, BLE_GAP_SEC_STATUS_SUCCESS, &dhkey); + // codes (BLE_GAP_SEC_STATUS_INVALID_PARAMS etc.) reject pairing, and the dhkey is ignored. + uint32_t dh_err = sd_ble_gap_lesc_dhkey_reply(conn_hdl, dh_status, &dhkey); LOG_LV2("PAIR", "DHKey reply err %lu", dh_err); (void) dh_err; } diff --git a/libraries/Bluefruit54Lib/src/BLESecurity.h b/libraries/Bluefruit54Lib/src/BLESecurity.h index b7c6e6d..fa36e63 100644 --- a/libraries/Bluefruit54Lib/src/BLESecurity.h +++ b/libraries/Bluefruit54Lib/src/BLESecurity.h @@ -89,6 +89,9 @@ class BLESecurity #endif uint8_t _pubkey[1+BLE_GAP_LESC_P256_PK_LEN]; // our public key: 1 header + 64 data uint8_t _peer_pubkey[1+BLE_GAP_LESC_P256_PK_LEN]; // peer public key when using LESC + bool _lesc_keys; // _private_key and _pubkey hold a generated key pair + + bool _genLescKeys(void); #endif bond_keys_t _bond_keys; // Shared keys with bonded device during securing connection, size ~ 80 bytes diff --git a/libraries/Bluefruit54Lib/src/bluefruit.cpp b/libraries/Bluefruit54Lib/src/bluefruit.cpp index 6408faa..0d3c369 100644 --- a/libraries/Bluefruit54Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit54Lib/src/bluefruit.cpp @@ -85,7 +85,7 @@ static void bluefruit_blinky_cb( TimerHandle_t xTimer ) } // 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. +// nRF54Crypto runs the TRNG only for each draw, LESC key generation's included. static bool seed_softdevice_rng(void) { uint8_t seed[SD_RAND_SEED_SIZE]; diff --git a/libraries/nRF54Crypto/src/nRF54Crypto.cpp b/libraries/nRF54Crypto/src/nRF54Crypto.cpp index 8218f83..a0f4ee0 100644 --- a/libraries/nRF54Crypto/src/nRF54Crypto.cpp +++ b/libraries/nRF54Crypto/src/nRF54Crypto.cpp @@ -29,6 +29,10 @@ #include "nrf_cracen.h" #include "nrf_cracen_rng.h" +#include +#include +#include + #include #include #include @@ -37,89 +41,119 @@ // Timeout for RNG FIFO fill (in loop iterations) #define RNG_TIMEOUT 100000 +// Words taken from the FIFO for the AES conditioning key (4 words, 128 bits) +#define RNG_KEY_WORDS 4 + nRF54CryptoClass nRF54Crypto; //--------------------------------------------------------------------+ // CRACEN RNG //--------------------------------------------------------------------+ -static bool _rng_started = false; +static bool _rng_key_loaded; -// Enable with 1x 128-bit block for AES conditioning -static void cracen_rng_control(bool soft_reset) +// Soft reset, then enable with 1x 128-bit block for AES conditioning. As in +// nrfx_cracen_entropy_get(), the first words out of the FIFO after each reset become the new +// conditioning key (cracen_rng_word()). +static void cracen_rng_reset(void) { nrf_cracen_rng_control_t cfg = { 0 }; + cfg.soft_reset = true; + nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); + + cfg.soft_reset = false; 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); + _rng_key_loaded = false; } -static bool cracen_rng_start(void) +// CRACEN->ENABLE also holds the CRYPTOMASTER and PKE/IKG bits, which an application may be using +// through nrfx; the read-modify-write must not interleave with one of its own. +static void cracen_rng_module_set(bool on) { - if (_rng_started) return true; - - // Enable CRACEN RNG module - nrf_cracen_module_enable(NRF_CRACEN, NRF_CRACEN_MODULE_RNG_MASK); - 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; - while (timeout--) { - nrf_cracen_rng_fsm_state_t state = nrf_cracen_rng_fsm_state_get(NRF_CRACENCORE); - if (state == NRF_CRACEN_RNG_FSM_STATE_IDLE_READY || - state == NRF_CRACEN_RNG_FSM_STATE_IDLE_STANDBY || - state == NRF_CRACEN_RNG_FSM_STATE_FILL_FIFO) { - _rng_started = true; - return true; - } - cracen_rng_reset_if_halted(); + taskENTER_CRITICAL(); + if (on) { + nrf_cracen_module_enable(NRF_CRACEN, NRF_CRACEN_MODULE_RNG_MASK); + } else { + nrf_cracen_module_disable(NRF_CRACEN, NRF_CRACEN_MODULE_RNG_MASK); } - return false; + taskEXIT_CRITICAL(); } -static void cracen_rng_stop(void) +// Turns the generator and its module off +static void cracen_rng_disable(void) { - if (!_rng_started) return; - - // Disable RNG nrf_cracen_rng_control_t cfg = { 0 }; cfg.enable = false; nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); - nrf_cracen_module_disable(NRF_CRACEN, NRF_CRACEN_MODULE_RNG_MASK); - _rng_started = false; + cracen_rng_module_set(false); +} + +// Every draw starts from a reset, so it gets its own conditioning key +static void cracen_rng_start(void) +{ + cracen_rng_module_set(true); + cracen_rng_reset(); +} + +// One draw at a time, and the TRNG runs only for the draw. The mutex is taken only while the +// scheduler runs: before it starts no other task can get in, and blocking there, or while the +// scheduler is suspended, would trip FreeRTOS's assert. With the scheduler suspended the draw fails +// if another task holds the mutex: that task may be between the FIFO level check and the read, and +// turning the TRNG off under it would hand it a word that isn't random. +static StaticSemaphore_t _rng_lock_buf; +static SemaphoreHandle_t _rng_lock = xSemaphoreCreateMutexStatic(&_rng_lock_buf); + +static bool rng_lock(bool *locked) +{ + *locked = false; + switch (xTaskGetSchedulerState()) { + case taskSCHEDULER_RUNNING: + (void) xSemaphoreTake(_rng_lock, portMAX_DELAY); + *locked = true; + return true; + case taskSCHEDULER_SUSPENDED: + return uxSemaphoreGetCount(_rng_lock) != 0; + default: // not started yet + return true; + } +} + +static void rng_unlock(bool locked) +{ + if (locked) (void) xSemaphoreGive(_rng_lock); } -// 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. +// Takes one word from the FIFO, or returns false while there is none yet. After a reset the first +// RNG_KEY_WORDS words load the conditioning key instead. A generator halted on an error is reset and +// starts again with a new key. 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(); + if (nrf_cracen_rng_fsm_state_get(NRF_CRACENCORE) == NRF_CRACEN_RNG_FSM_STATE_ERROR) { + cracen_rng_reset(); + return false; } - taskEXIT_CRITICAL(); - return got; + + uint32_t level = nrf_cracen_rng_fifo_level_get(NRF_CRACENCORE); + if (!_rng_key_loaded) { + if (level < RNG_KEY_WORDS) return false; + for (uint8_t i = 0; i < RNG_KEY_WORDS; i++) { + nrf_cracen_rng_key_set(NRF_CRACENCORE, i, nrf_cracen_rng_fifo_get(NRF_CRACENCORE)); + } + _rng_key_loaded = true; + level -= RNG_KEY_WORDS; + } + + if (level == 0) return false; + *word = nrf_cracen_rng_fifo_get(NRF_CRACENCORE); + return true; } static bool cracen_rng_fill(uint8_t *dest, size_t len) { - if (!_rng_started) return false; - size_t offset = 0; while (offset < len) { uint32_t word; @@ -141,30 +175,38 @@ static bool cracen_rng_fill(uint8_t *dest, size_t len) // default_CSPRNG - called by TinyCrypt uECC //--------------------------------------------------------------------+ +static bool rng_draw(uint8_t *dest, size_t len) +{ + bool locked; + if (!rng_lock(&locked)) return false; + + cracen_rng_start(); + bool ok = cracen_rng_fill(dest, len); + cracen_rng_disable(); + + rng_unlock(locked); + return ok; +} + extern "C" int default_CSPRNG(uint8_t *dest, unsigned int size) { - // uECC also draws randomness during ECDH, which may run long after end() stopped the RNG - if (!_rng_started && !cracen_rng_start()) return 0; - return cracen_rng_fill(dest, size) ? 1 : 0; + return rng_draw(dest, size) ? 1 : 0; } //--------------------------------------------------------------------+ // nRF54CryptoClass //--------------------------------------------------------------------+ +// The TRNG starts and stops around each draw, and uECC already defaults to default_CSPRNG +// (ecc_platform_specific.h), so begin() and end() have nothing to do. Both stay for the callers that +// pair them. bool nRF54CryptoClass::begin(void) { - if (!cracen_rng_start()) return false; - - // Register our RNG with uECC - uECC_set_rng(default_CSPRNG); - return true; } void nRF54CryptoClass::end(void) { - cracen_rng_stop(); } bool nRF54CryptoClass::genKeyPair(uint8_t privkey[32], uint8_t pubkey[64]) @@ -181,7 +223,7 @@ bool nRF54CryptoClass::sharedSecret(const uint8_t peer_pubkey[64], bool nRF54CryptoClass::random(uint8_t* dest, size_t len) { - return cracen_rng_fill(dest, len); + return rng_draw(dest, len); } #endif // NRF_CRACEN diff --git a/libraries/nRF54Crypto/src/nRF54Crypto.h b/libraries/nRF54Crypto/src/nRF54Crypto.h index 8cd4597..c3dfd6d 100644 --- a/libraries/nRF54Crypto/src/nRF54Crypto.h +++ b/libraries/nRF54Crypto/src/nRF54Crypto.h @@ -40,7 +40,10 @@ class nRF54CryptoClass { const uint8_t privkey[32], uint8_t dhkey[32]); - // Fill buffer with random bytes from CRACEN TRNG + // Fill buffer with random bytes from CRACEN TRNG. Call from a task, or before the scheduler + // starts; not from an interrupt or inside a critical section, where waiting for another + // task's draw cannot work. With the scheduler suspended it returns false while another task + // is drawing. bool random(uint8_t* dest, size_t len); };