diff --git a/ports/stm32/boards/Passport/board_init.c b/ports/stm32/boards/Passport/board_init.c index 793879524..2742ed4fa 100644 --- a/ports/stm32/boards/Passport/board_init.c +++ b/ports/stm32/boards/Passport/board_init.c @@ -11,8 +11,15 @@ #include "camera-ovm7690.h" #include "frequency.h" #include "gpio.h" +#include "pprng.h" #include "se.h" +extern void __attribute__((noreturn)) __fatal_error(const char* msg); + +void rng_fatal_error(void) { + __fatal_error("Entropy source failure"); +} + #ifndef PASSPORT_DEBUG_STACK #define PASSPORT_DEBUG_STACK 0 #endif @@ -34,6 +41,7 @@ void Passport_board_init(void) { gpio_init(); frequency_turbo(true); + rng_setup(); display_init(false); camera_init(); adc_init(); diff --git a/ports/stm32/boards/Passport/bootloader/main.c b/ports/stm32/boards/Passport/bootloader/main.c index 8b44fb2ce..9de1c2bee 100644 --- a/ports/stm32/boards/Passport/bootloader/main.c +++ b/ports/stm32/boards/Passport/bootloader/main.c @@ -574,8 +574,24 @@ static void microsd_firmware_recovery(void) { void random_boot_delay() { // Random delay to make cold-boot stepping attacks harder: 0 - 50ms - uint32_t ms_to_delay = rng_sample() % 50; - delay_ms(ms_to_delay); + uint32_t random_delay = 0; + (void)rng_try_sample(&random_delay); + delay_ms(random_delay % 50); +} + +void rng_fatal_error(void) { + // The first entropy checks run before the normal display initialization. + // Bring up only the UI hardware required to show a permanent fatal error. + display_init(true); + gpio_init(); + keypad_init(); + backlight_init(); + backlight_intensity(100); + ui_show_fatal_error("Entropy source failure."); + + // ui_show_fatal_error() does not return, but retain a hard fail-safe if its + // implementation ever changes. + LOCKUP_FOREVER(); } void do_verify_current_firmware() { diff --git a/ports/stm32/boards/Passport/common/pprng.c b/ports/stm32/boards/Passport/common/pprng.c index c63e9bcb1..8113c9e09 100644 --- a/ports/stm32/boards/Passport/common/pprng.c +++ b/ports/stm32/boards/Passport/common/pprng.c @@ -8,67 +8,115 @@ * (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard * and is covered by GPLv3 license found in COPYING. */ +#include #include -#include "stm32h7xx_hal_conf.h" +#include "stm32h7xx_hal.h" #include "delay.h" #include "pprng.h" #include "utils.h" -void rng_setup(void) { - if (RNG->CR & RNG_CR_RNGEN) { - // already setup - return; +#define RNG_TIMEOUT_MS 10U + +static bool rng_cycle_counter_setup(void) { + if (DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk) { + return true; } - // Enable the RNG clock + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->LAR = 0xc5acce55; + DWT->CYCCNT = 0; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + + return (DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk) != 0; +} + +void rng_setup(void) { + // Enable the peripheral clock even if an earlier boot stage left RNGEN set. __HAL_RCC_RNG_CLK_ENABLE(); - // Enable the RNG + // Start each image from a known peripheral state. Clearing the latched + // interrupt flags and restarting the generator is the recovery sequence + // recommended by ST after a seed error. A persistent current error is + // still caught by rng_try_sample() below and fails closed. + RNG->SR &= ~(RNG_SR_SEIS | RNG_SR_CEIS); + RNG->CR &= ~RNG_CR_RNGEN; RNG->CR |= RNG_CR_RNGEN; - // Sample twice to be sure that we have a - // valid RNG result. - uint32_t chk = rng_sample(); - uint32_t chk2 = rng_sample(); - - // die if we are clearly not getting random values - if (chk == 0 || chk == ~0 || chk2 == 0 || chk2 == ~0 || chk == chk2) { - while (1) - ; + // Always sample twice, even if an earlier boot stage enabled the + // peripheral, so each image verifies the source before using it. + uint32_t sample; + if (!rng_try_sample(&sample) || !rng_try_sample(&sample)) { + rng_fatal_error(); } } -uint32_t rng_sample(void) { +bool rng_try_sample(uint32_t* result) { static uint32_t last_rng_result; + static bool have_last_rng_result; + + if (result == NULL) { + return false; + } + if (!rng_cycle_counter_setup()) { + return false; + } + + const uint32_t error_mask = RNG_SR_SECS | RNG_SR_CECS | RNG_SR_SEIS | RNG_SR_CEIS; + const uint32_t timeout_cycles = (SystemCoreClock / 1000U) * RNG_TIMEOUT_MS; + const uint32_t start_cycle = DWT->CYCCNT; - while (1) { - // Check if data register contains valid random data - while (!(RNG->SR & RNG_SR_DRDY)) { - // busy wait; okay to get stuck here... better than failing. + while ((DWT->CYCCNT - start_cycle) < timeout_cycles) { + // Check both current error status and latched error flags. A flagged + // sample is a hard failure; callers must not silently degrade. + uint32_t status = RNG->SR; + if (status & error_mask) { + return false; + } + + if (!(status & RNG_SR_DRDY)) { + continue; } // Get the new number uint32_t rv = RNG->DR; - if (rv != last_rng_result && rv) { + // Catch an error that arrived between the status check and the data + // read. The value must not be used in that case. + if (RNG->SR & error_mask) { + return false; + } + + // Continuous test: never return the same value twice in succession. + if (!have_last_rng_result || rv != last_rng_result) { last_rng_result = rv; + have_last_rng_result = true; + *result = rv; - return rv; + return true; } - // keep trying if not a new number + // A duplicate may be transient. Keep trying within the same bounded + // interval; a stuck source will time out and fail closed. } - // NOT-REACHED + return false; +} + +uint32_t rng_sample(void) { + uint32_t result; + if (!rng_try_sample(&result)) { + rng_fatal_error(); + } + return result; } void rng_buffer(uint8_t* result, int len) { while (len > 0) { - uint32_t t = rng_sample(); + uint32_t sample = rng_sample(); - memcpy(result, &t, MIN(4, len)); + memcpy(result, &sample, MIN(4, len)); len -= 4; result += 4; diff --git a/ports/stm32/boards/Passport/dispatch.c b/ports/stm32/boards/Passport/dispatch.c index 949a877ef..d531d7c6e 100644 --- a/ports/stm32/boards/Passport/dispatch.c +++ b/ports/stm32/boards/Passport/dispatch.c @@ -74,8 +74,9 @@ int se_dispatch( // printf("se_dispatch() method_num=%d\n", method_num); // Random small delay to make cold-boot stepping attacks harder: 0 - 10,000us - uint32_t us_to_delay = rng_sample() % 10000; - delay_us(us_to_delay); + uint32_t us_to_delay = 0; + (void)rng_try_sample(&us_to_delay); + delay_us(us_to_delay % 10000); switch (method_num) { case CMD_IS_BRICKED: diff --git a/ports/stm32/boards/Passport/include/pprng.h b/ports/stm32/boards/Passport/include/pprng.h index 13f912e47..24a76b592 100644 --- a/ports/stm32/boards/Passport/include/pprng.h +++ b/ports/stm32/boards/Passport/include/pprng.h @@ -10,8 +10,11 @@ */ #pragma once +#include #include void rng_setup(void); +bool rng_try_sample(uint32_t* result); uint32_t rng_sample(void); void rng_buffer(uint8_t* result, int len); +void rng_fatal_error(void) __attribute__((noreturn)); diff --git a/ports/stm32/boards/Passport/modpassport-noise.h b/ports/stm32/boards/Passport/modpassport-noise.h index 1ea942a6a..bc446dc21 100644 --- a/ports/stm32/boards/Passport/modpassport-noise.h +++ b/ports/stm32/boards/Passport/modpassport-noise.h @@ -6,6 +6,7 @@ #include "adc.h" #include "noise.h" +#include "pprng.h" #include "stm32h7xx_hal.h" /// package: passport @@ -38,7 +39,7 @@ STATIC mp_obj_t mod_passport_Noise_make_new(const mp_obj_type_t* type, /// directly as a tuple of two ints. /// """ STATIC mp_obj_t mod_passport_Noise_read(mp_obj_t self) { - HAL_StatusTypeDef ret = 0; + int ret = 0; uint32_t noise1 = 0; uint32_t noise2 = 0; mp_obj_t tuple[2] = {0}; @@ -67,7 +68,7 @@ STATIC mp_obj_t mod_passport_Noise_random_bytes(mp_obj_t self, sources = mp_obj_get_int(sources_obj); if (!noise_get_random_bytes(sources, buf_info.buf, buf_info.len)) { - return mp_const_false; + rng_fatal_error(); } return mp_const_true; @@ -99,4 +100,4 @@ const mp_obj_type_t mod_passport_Noise_type = { .name = MP_QSTR_Noise, .make_new = mod_passport_Noise_make_new, .locals_dict = (void*)&mod_passport_Noise_locals_dict, -}; \ No newline at end of file +}; diff --git a/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py b/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py index 9b4c2db45..92fc12f40 100644 --- a/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py @@ -10,7 +10,9 @@ async def new_seed_task(on_done, seed_length): seed = bytearray(32) - common.noise.random_bytes(seed, common.noise.ALL) + if not common.noise.random_bytes(seed, common.noise.ALL): + await on_done(None, 'Unable to collect entropy for the new seed.') + return # Hash to mitigate any potential bias in RNG sources seed = trezorcrypto.sha256(seed).digest() diff --git a/ports/stm32/boards/Passport/noise.c b/ports/stm32/boards/Passport/noise.c index 45d9a2790..f608f926a 100644 --- a/ports/stm32/boards/Passport/noise.c +++ b/ports/stm32/boards/Passport/noise.c @@ -38,10 +38,10 @@ void noise_disable() { } bool noise_get_random_uint16(uint16_t* result) { - HAL_StatusTypeDef ret; - uint32_t noise1 = 0; - uint32_t noise2 = 0; - uint16_t r = 0; + int ret; + uint32_t noise1 = 0; + uint32_t noise2 = 0; + uint16_t r = 0; for (int i = 0; i < 4; i++) { r = r << 4; diff --git a/ports/stm32/rng.c b/ports/stm32/rng.c index eea02f726..8c9a6ec6f 100644 --- a/ports/stm32/rng.c +++ b/ports/stm32/rng.c @@ -27,11 +27,20 @@ #include "rtc.h" #include "rng.h" +#if defined(MICROPY_PASSPORT) +#include "pprng.h" +#endif + #if MICROPY_HW_ENABLE_RNG #define RNG_TIMEOUT_MS (10) uint32_t rng_get(void) { + #if defined(MICROPY_PASSPORT) + // Keep pyb.rng(), os.urandom(), and MicroPython's initial PRNG seed on the + // same status-checked hardware path as Passport's cryptographic consumers. + return rng_sample(); + #else // Enable the RNG peripheral if it's not already enabled if (!(RNG->CR & RNG_CR_RNGEN)) { #if defined(STM32H7) @@ -53,6 +62,7 @@ uint32_t rng_get(void) { // Get and return the new random number return RNG->DR; + #endif } // Return a 30-bit hardware generated random number.