Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions libraries/Bluefruit54Lib/src/BLESecurity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Expand All @@ -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)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions libraries/Bluefruit54Lib/src/BLESecurity.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion libraries/Bluefruit54Lib/src/bluefruit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
162 changes: 102 additions & 60 deletions libraries/nRF54Crypto/src/nRF54Crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
#include "nrf_cracen.h"
#include "nrf_cracen_rng.h"

#include <FreeRTOS.h>
#include <semphr.h>
#include <task.h>

#include <tinycrypt/ecc.h>
#include <tinycrypt/ecc_dh.h>
#include <tinycrypt/ecc_platform_specific.h>
Expand All @@ -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;
Expand All @@ -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)
Comment thread
cvaldess marked this conversation as resolved.
{
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])
Expand All @@ -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
5 changes: 4 additions & 1 deletion libraries/nRF54Crypto/src/nRF54Crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down
Loading