From 052e7c2890f9908d85343b09ed3316ce5ce004d5 Mon Sep 17 00:00:00 2001 From: Alberto Escolar Piedras Date: Mon, 13 Jan 2025 17:47:42 +0100 Subject: [PATCH] Add CRACEN CTR DRBG random driver Add a new random CTR DRBG driver using the CRACEN HW TRNG and AES-ECB acceleration. Signed-off-by: Alberto Escolar Piedras --- nrfx/drivers/include/nrfx_cracen.h | 88 +++++ nrfx/drivers/src/nrfx_cracen.c | 542 +++++++++++++++++++++++++++++ 2 files changed, 630 insertions(+) create mode 100644 nrfx/drivers/include/nrfx_cracen.h create mode 100644 nrfx/drivers/src/nrfx_cracen.c diff --git a/nrfx/drivers/include/nrfx_cracen.h b/nrfx/drivers/include/nrfx_cracen.h new file mode 100644 index 00000000..7835d3e2 --- /dev/null +++ b/nrfx/drivers/include/nrfx_cracen.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025, Nordic Semiconductor ASA + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NRFX_CRACEN_H +#define NRFX_CRACEN_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup nrfx_cracen CRACEN driver + * @{ + * @ingroup nrf_cracen + * @brief Cryptographic accelerator engine (CRACEN) peripheral driver + */ + +/** + * @brief Function for initializing the CRACEN CTR_DRBG random generator. + * + * @note This initialization is relatively slow and power consuming. + * + * @note This function assumes exclusive access to the CRACEN TRNG and CryptoMaster, and may + * not be used while any other component is using those peripherals. + * + * @retval NRFX_SUCCESS Initialization was successful. + * @retval NRFX_ERROR_INTERNAL Unexpected error. + * @retval NRFX_ERROR_ALREADY If it was was already initialized. + */ +nrfx_err_t nrfx_cracen_ctr_drbg_init(void); + +/** @brief Function for uninitializing the CRACEN CTR_DRBG random generator. */ +void nrfx_cracen_ctr_drbg_uninit(void); + +/** + * @brief Function for filling the specified /p p_buf buffer with /p size bytes of random data. + * + * @note This function assumes exclusive access to the CRACEN TRNG and CryptoMaster, and may + * not be used while any other component is using those peripherals. + * + * @param[out] p_buf Buffer into which to copy \p size bytes of entropy. + * @param[in] size Number of bytes to copy. + * + * @retval NRFX_SUCCESS Success. + * @retval NRFX_ERROR_INVALID_PARAM Invalid inputs. + * @retval NRFX_ERROR_INTERNAL Unexpected error. + */ +nrfx_err_t nrfx_cracen_ctr_drbg_random_get(uint8_t * p_buf, size_t size); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* NRFX_CRACEN_H */ diff --git a/nrfx/drivers/src/nrfx_cracen.c b/nrfx/drivers/src/nrfx_cracen.c new file mode 100644 index 00000000..d8ece43d --- /dev/null +++ b/nrfx/drivers/src/nrfx_cracen.c @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2025, Nordic Semiconductor ASA + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +/* TRNG HW chosen configuration options */ +#define TRNG_CLK_DIV 0 +#define TRNG_OFF_TIMER_VAL 0 +#define TRNG_INIT_WAIT_VAL 512 +#define TRNG_NUMBER_128BIT_BLOCKS 4 + +#define TRNG_CONDITIONING_KEY_SIZE 4 /* Size of the conditioning key: 4 words, 16 bytes */ + +#define AES_ECB_BLK_SZ 16U /* 128 bits */ + +#define CTR_DRBG_MAX_BYTES_PER_REQUEST (1 << 16) /* NIST.SP.800-90Ar1:Table 3 */ +#define CTR_DRBG_RESEED_INTERVAL ((uint64_t)1 << 48) /* 2^48 as per NIST spec */ +#define CTR_DRBG_KEY_SIZE 32U /* 256 bits AES Key */ +#define CTR_DRBG_ENTROPY_SIZE (CTR_DRBG_KEY_SIZE + AES_ECB_BLK_SZ) /* Seed equals key-len + 16 */ + +/* Return values common between these driver internal functions: */ +typedef enum { + OK = 0, /* The function or operation succeeded */ + ERROR = -1, /* Generic error */ + ERR_TOO_BIG = -2, /* Requested size too large */ + TRNG_RESET_NEEDED = -5, /* TRNG needs to be reset due to a failure */ + HW_PROCESSING = -10, /* Waiting for the hardware to produce data */ +} cracen_ret_t; + +/* Internal status of the CTR_DRBG RNG driver */ +typedef struct { + uint8_t key[CTR_DRBG_KEY_SIZE]; + uint8_t value[AES_ECB_BLK_SZ]; + uint64_t reseed_counter; + nrfx_drv_state_t initialized; + bool trng_conditioning_key_set; +} nrfx_cracen_cb_t; + +static nrfx_cracen_cb_t m_cb; + +/* + * Initialize the TRNG HW and the TRNG driver status. + */ +static void trng_init(void) +{ + m_cb.trng_conditioning_key_set = false; + + /* Disable and softreset the RNG */ + static const nrf_cracen_rng_control_t control_reset = {.soft_reset = true}; + + nrf_cracen_rng_control_set(NRF_CRACENCORE, &control_reset); + + /* Change from configuration defaults to what we prefer: */ + nrf_cracen_rng_off_timer_set(NRF_CRACENCORE, TRNG_OFF_TIMER_VAL); + nrf_cracen_rng_clk_div_set(NRF_CRACENCORE, TRNG_CLK_DIV); + nrf_cracen_rng_init_wait_val_set(NRF_CRACENCORE, TRNG_INIT_WAIT_VAL); + + /* Configure the control register and enable */ + static const nrf_cracen_rng_control_t control_enable = { + .enable = true, + .number_128_blocks = TRNG_NUMBER_128BIT_BLOCKS, + }; + + nrf_cracen_rng_control_set(NRF_CRACENCORE, &control_enable); +} + +/* + * Set the TRNG HW conditioning key. + * + * If there is not yet enough data to do so, return HW_PROCESSING otherwise return OK. + */ +static cracen_ret_t trng_setup_conditioning_key(void) +{ + uint32_t level = nrf_cracen_rng_fifo_level_get(NRF_CRACENCORE); + + if (level < TRNG_CONDITIONING_KEY_SIZE) + { + return HW_PROCESSING; + } + + for (uint8_t i = 0; i < TRNG_CONDITIONING_KEY_SIZE; i++) + { + uint32_t key; + key = nrf_cracen_rng_fifo_get(NRF_CRACENCORE); + nrf_cracen_rng_key_set(NRF_CRACENCORE, i, key); + } + + m_cb.trng_conditioning_key_set = true; + + return OK; +} + +/* + * If the TRNG HW detected the entropy quality was not ok, return TRNG_RESET_NEEDED. + * If the HW is still starting or there is not enough data, return HW_PROCESSING. + * If the conditioning key is not yet setup, attempt to fill it or return HW_PROCESSING if + * we don't have enough data to fill it yet. + * If enough data is ready, fill the /p dst buffer with /p size bytes and return OK. + */ +static cracen_ret_t trng_get(uint8_t * dst, size_t size) +{ + /* Check that startup tests did not fail and we are ready to read data */ + switch (nrf_cracen_rng_fsm_state_get(NRF_CRACENCORE)) + { + case NRF_CRACEN_RNG_FSM_STATE_ERROR: + return TRNG_RESET_NEEDED; + case NRF_CRACEN_RNG_FSM_STATE_RESET: + return HW_PROCESSING; + case NRF_CRACEN_RNG_FSM_STATE_STARTUP: + default: + break; + } + + /* Program random key for the conditioning function */ + if (!m_cb.trng_conditioning_key_set) + { + cracen_ret_t status = trng_setup_conditioning_key(); + + if (status != OK) + { + return status; + } + } + + uint32_t level = nrf_cracen_rng_fifo_level_get(NRF_CRACENCORE); + + if (size > level * 4) /* FIFO level in 4-byte words */ + { + return HW_PROCESSING; + } + + while (size) + { + uint32_t data = nrf_cracen_rng_fifo_get(NRF_CRACENCORE); + + for (int i = 0; i < 4 && size; i++, size--) + { + *dst = (uint8_t)(data & 0xFF); + dst++; + data >>= 8; + } + } + + return OK; +} + +/* + * @brief Function for filling a buffer with entropy from the CRACEN TRNG. + * + * When this function returns OK, \p size random bytes have been written to \p p_buf. + * + * Up to 64 bytes can be requested. + * If more is requested the function will return ERR_TOO_BIG without copying any data. + * + * The entropy generated by this function is NIST800-90B and AIS31 compliant, and can be used to + * seed FIPS 140-2 compliant pseudo random number generators. + * + * @note This function is blocking. It will take around a couple of tenths of microseconds to + * complete depending on the amount of bytes requested. + * (~40 microseconds for an nRF54L15 for the maximum 64 bytes) + * + * @note Note this is a quite power hungry operation. + * + * @note This function will enable and configure the CRACEN TRNG HW, wait until the entropy has been + * generated, copy it to the destination buffer and disable the HW. + * This function is meant as as internal utility of this driver but may be used by others with + * extra care, specially if some other component is using CRACEN. + * + * @param[out] p_buf Buffer into which to copy \p size bytes of entropy. + * @param[in] size Number of bytes to copy. + * + * @return OK on success, ERR_TOO_BIG if the requested size is too big. + */ +static cracen_ret_t trng_entropy_get(uint8_t * p_buf, size_t size) +{ + /* Prevent sizes above the FIFO size to guarantee that the hardware will be able to provide the + * requested bytes in one go. */ + if (size > NRF_CRACEN_RNG_FIFO_SIZE) + { + return ERR_TOO_BIG; + } + + nrf_cracen_module_enable(NRF_CRACEN, NRF_CRACEN_MODULE_RNG_MASK); + + int ret = TRNG_RESET_NEEDED; + + while (true) + { + if (ret == TRNG_RESET_NEEDED) + { + trng_init(); + } + ret = trng_get(p_buf, size); + if (ret == OK) + { + break; + } +#if defined(CONFIG_SOC_SERIES_BSIM_NRFXX) + nrfx_coredep_delay_us(1); +#endif + } + + nrf_cracen_module_disable(NRF_CRACEN, NRF_CRACEN_MODULE_RNG_MASK); + + return OK; +} + +/* + * Check if the CryptoMaster is done. + * + * returns OK if done, HW_PROCESSING if not yet done, and ERROR on error. + */ +static cracen_ret_t cm_done_check(void) +{ + uint32_t ret; + uint32_t busy; + + ret = nrf_cracen_cm_int_pending_get(NRF_CRACENCORE); + + if (ret & (NRF_CRACEN_CM_INT_FETCH_ERROR_MASK | NRF_CRACEN_CM_INT_PUSH_ERROR_MASK)) + { + return ERROR; + } + + busy = nrf_cracen_cm_status_get(NRF_CRACENCORE, + (NRF_CRACEN_CM_STATUS_BUSY_FETCH_MASK | + NRF_CRACEN_CM_STATUS_BUSY_PUSH_MASK | + NRF_CRACEN_CM_STATUS_PUSH_WAITING_MASK)); + + if (busy) + { + return HW_PROCESSING; + } + + return OK; +} + +/* + * Function for encrypting with AES-ECB the input data using the CRACEN CryptoMaster module. + * + * @note The key, input and output data are in big endian/cryptographic order. That is, input[0] + * corresponds to the highest byte of the 128bit input. + * + * @note The only failure one can normally expect are bus failures due to incorrect pointers. + * + * @note This function is meant to be used by the nrfx_random_ctr_drbg driver. + * If using it outside of this driver it must be used with care specially if any other + * component is using CRACEN. + * + * @note The key size needs to be supported by the CRACEN CryptoMaster AES engine. + * + * @param[in] p_key Pointer to the key. + * @param[in] key_size Size of the key in bytes (valid sizes 16, 24 or 32 => 128, 192 or 256 bits). + * @param[in] p_input Pointer to the input data (16 bytes/128 bits). + * @param[in] p_output Pointer to the output data (16 bytes/128 bits). + * + * @return OK on success, ERROR on failure. + */ +static cracen_ret_t cm_aes_ecb(uint8_t * p_key, size_t key_size, uint8_t * p_input, + uint8_t * p_output) +{ + cracen_ret_t ret; + + static const uint32_t aes_config_value = NRF_CRACEN_CM_AES_CONFIG( + NRF_CRACEN_CM_AES_CONFIG_MODE_ECB, + NRF_CRACEN_CM_AES_CONFIG_KEY_SW_PROGRAMMED, + false, false, false); + + struct nrf_cracen_cm_dma_desc in_descs[3]; + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif /* __GNUC__ */ + in_descs[0].p_addr = (uint8_t *)&aes_config_value; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif /* __GNUC__ */ + in_descs[0].length = sizeof(aes_config_value) | NRF_CRACEN_CM_DMA_DESC_LENGTH_REALIGN; + in_descs[0].dmatag = NRF_CRACEN_CM_DMA_TAG_AES_CONFIG(NRF_CRACEN_CM_AES_REG_OFFSET_CONFIG); + in_descs[0].p_next = &in_descs[1]; + + in_descs[1].p_addr = p_key; + in_descs[1].length = key_size | NRF_CRACEN_CM_DMA_DESC_LENGTH_REALIGN; + in_descs[1].dmatag = NRF_CRACEN_CM_DMA_TAG_AES_CONFIG(NRF_CRACEN_CM_AES_REG_OFFSET_KEY); + in_descs[1].p_next = &in_descs[2]; + + in_descs[2].p_addr = p_input; + in_descs[2].length = AES_ECB_BLK_SZ | NRF_CRACEN_CM_DMA_DESC_LENGTH_REALIGN; + in_descs[2].dmatag = NRF_CRACEN_CM_DMA_TAG_LAST | NRF_CRACEN_CM_DMA_TAG_ENGINE_AES + | NRF_CRACEN_CM_DMA_TAG_DATATYPE_AES_PAYLOAD; + in_descs[2].p_next = NRF_CRACEN_CM_DMA_DESC_STOP; + + struct nrf_cracen_cm_dma_desc out_desc; + + out_desc.p_addr = p_output; + out_desc.length = AES_ECB_BLK_SZ | NRF_CRACEN_CM_DMA_DESC_LENGTH_REALIGN; + out_desc.p_next = NRF_CRACEN_CM_DMA_DESC_STOP; + out_desc.dmatag = NRF_CRACEN_CM_DMA_TAG_LAST; + + nrf_cracen_module_enable(NRF_CRACEN, NRF_CRACEN_MODULE_CRYPTOMASTER_MASK); + + nrf_cracen_cm_fetch_addr_set(NRF_CRACENCORE, (void *)in_descs); + nrf_cracen_cm_push_addr_set(NRF_CRACENCORE, (void *)&out_desc); + + nrf_cracen_cm_config_indirect_set(NRF_CRACENCORE,(nrf_cracen_cm_config_indirect_mask_t) + (NRF_CRACEN_CM_CONFIG_INDIRECT_FETCH_MASK | + NRF_CRACEN_CM_CONFIG_INDIRECT_PUSH_MASK)); + + /* Make sure the contents of in_descs and out_desc are updated before starting CryptoMaster. */ + __DMB(); + + nrf_cracen_cm_start(NRF_CRACENCORE); + + do { + /* The HW is so fast that it is better to "busy wait" here than program an + * interrupt. This will normally already succeed in the first try + */ +#if defined(CONFIG_SOC_SERIES_BSIM_NRFXX) + nrfx_coredep_delay_us(1); +#endif + ret = cm_done_check(); + } while (ret == HW_PROCESSING); + + nrf_cracen_cm_softreset(NRF_CRACENCORE); + nrf_cracen_module_disable(NRF_CRACEN, NRF_CRACEN_MODULE_CRYPTOMASTER_MASK); + + return ret; +} + +/* + * Increment by 1 a number stored in memory in big endian representation. + * /p v is a pointer to the first byte storing the number. + * /p size is the size of the number. + */ +static inline void be_incr(unsigned char * v, size_t size) +{ + unsigned int add = 1; + + do { + size--; + add += v[size]; + v[size] = add & 0xFF; + add >>= 8; + } while ((add != 0) && (size > 0)); +} + +/* + * XOR two arrays of /p size bytes. + * /p size must be a multiple of 4. + */ +static inline void xor_array(uint32_t * a, const uint32_t * b, size_t size) +{ + uintptr_t end = (uintptr_t)a + size; + + for (; (uintptr_t)a < end; a++, b++) + { + *a = *a ^ *b; + } +} + +/* + * Implementation of the CTR_DRBG_Update process as described in NIST.SP.800-90Ar1 with ctr_len + * equal to blocklen. + * + * Returns OK on success, ERROR on error. + */ +static cracen_ret_t ctr_drbg_update(uint8_t * data) +{ + int r = 0; + uint8_t temp[CTR_DRBG_ENTROPY_SIZE]; + size_t temp_length = 0; + + while (temp_length < sizeof(temp)) + { + be_incr(m_cb.value, AES_ECB_BLK_SZ); + + r = cm_aes_ecb(m_cb.key, sizeof(m_cb.key), m_cb.value, temp + temp_length); + + if (r != OK) + { + return ERROR; + } + temp_length += AES_ECB_BLK_SZ; + } + + if (data) + { + xor_array((uint32_t *)temp, (uint32_t *)data, sizeof(temp)); + } + + memcpy(m_cb.key, temp, sizeof(m_cb.key)); + memcpy(m_cb.value, temp + sizeof(m_cb.key), sizeof(m_cb.value)); + + return OK; +} + +/* + * Re-seed the CTR_DRBG. + * + * return OK on success, ERROR on error. + */ +static cracen_ret_t ctr_drbg_reseed(void) +{ + int r; + uint8_t entropy[CTR_DRBG_ENTROPY_SIZE]; + + /* Get the entropy used to seed the DRBG */ + r = trng_entropy_get(entropy, sizeof(entropy)); + if (r != OK) + { + return ERROR; + } + + r = ctr_drbg_update(entropy); + if (r != OK) + { + return ERROR; + } + + m_cb.reseed_counter = 1; + + return OK; +} + +nrfx_err_t nrfx_cracen_ctr_drbg_init(void) +{ + int r; + + if (m_cb.initialized == NRFX_DRV_STATE_INITIALIZED) + { + return NRFX_ERROR_ALREADY; + } + + memset(&m_cb, 0, sizeof(m_cb)); + + r = ctr_drbg_reseed(); + if (r != OK) + { + return NRFX_ERROR_INTERNAL; + } + + m_cb.initialized = NRFX_DRV_STATE_INITIALIZED; + return NRFX_SUCCESS; +} + +void nrfx_cracen_ctr_drbg_uninit(void) +{ + NRFX_ASSERT(m_cb.initialized != NRFX_DRV_STATE_UNINITIALIZED); + + m_cb.initialized = NRFX_DRV_STATE_UNINITIALIZED; +} + +nrfx_err_t nrfx_cracen_ctr_drbg_random_get(uint8_t * p_buf, size_t size) +{ + NRFX_ASSERT(m_cb.initialized != NRFX_DRV_STATE_UNINITIALIZED); + + int r = 0; + + if (size > 0 && p_buf == NULL) + { + return NRFX_ERROR_INVALID_PARAM; + } + + if (size > CTR_DRBG_MAX_BYTES_PER_REQUEST ) + { + return NRFX_ERROR_INVALID_PARAM; + } + + if (m_cb.reseed_counter >= CTR_DRBG_RESEED_INTERVAL) + { + r = ctr_drbg_reseed(); + if (r != OK) + { + return NRFX_ERROR_INTERNAL; + } + } + + while (size > 0) + { + uint8_t temp[AES_ECB_BLK_SZ]; + size_t cur_len = (size < AES_ECB_BLK_SZ) ? size : AES_ECB_BLK_SZ; + + be_incr(m_cb.value, AES_ECB_BLK_SZ); + + r = cm_aes_ecb(m_cb.key, sizeof(m_cb.key), m_cb.value, temp); + if (r != OK) + { + return NRFX_ERROR_INTERNAL; + } + + memcpy(p_buf, temp, cur_len); + + size -= cur_len; + p_buf += cur_len; + } + + r = ctr_drbg_update(NULL); + if (r != OK) + { + return NRFX_ERROR_INTERNAL; + } + + m_cb.reseed_counter += 1; + + return NRFX_SUCCESS; +}