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
7 changes: 7 additions & 0 deletions ports/stm32/boards/Passport/board_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@
#include "camera-ovm7690.h"
#include "frequency.h"
#include "gpio.h"
#include "pprng.h"
#include "se.h"

void rng_fatal_error(void) {
// Entropy checks can fail before the display is initialized.
passport_reset();
}

#ifndef PASSPORT_DEBUG_STACK
#define PASSPORT_DEBUG_STACK 0
#endif
Expand All @@ -34,6 +40,7 @@ void Passport_board_init(void) {

gpio_init();
frequency_turbo(true);
rng_setup();
display_init(false);
camera_init();
adc_init();
Expand Down
15 changes: 15 additions & 0 deletions ports/stm32/boards/Passport/bootloader/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,21 @@ void random_boot_delay() {
delay_ms(ms_to_delay);
}

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() {
// Validate the internal firmware
secresult result = verify_current_firmware(true);
Expand Down
129 changes: 102 additions & 27 deletions ports/stm32/boards/Passport/common/pprng.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,142 @@
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
* and is covered by GPLv3 license found in COPYING.
*/
#include <stdbool.h>
#include <string.h>

#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;
// Bound the number of polling attempts.
// Firmware and bootloader configure a 480 MHz CPU. Target roughly 10 ms using
// an unmeasured estimate of 10 CPU cycles per no-data poll: 480 MHz * 10 ms / 10.
// This is not a calibrated timeout; MMIO stalls, interrupts, and the longer
// zero/duplicate retry path affect elapsed time.
#define RNG_MAX_POLL_ATTEMPTS 480000U
#define RNG_MAX_RECOVERY_ATTEMPTS 3U

static bool rng_recover_seed_error(uint32_t* recovery_attempts) {
while (*recovery_attempts < RNG_MAX_RECOVERY_ATTEMPTS) {
(*recovery_attempts)++;

// ST's seed-error recovery sequence (RM0433 section 34.3.7): clear
// SEIS and flush 12 words. These are raw discard reads; do not wait
// for DRDY or consume any of the values.
RNG->SR &= ~RNG_SR_SEIS;
for (unsigned int i = 0; i < 12; i++) {
(void)RNG->DR;
}

// SEIS must remain clear after flushing. If it is set again, retry
// recovery within the remaining budget before reporting failure.
if (!(RNG->SR & RNG_SR_SEIS)) {
return true;
}
}
return false;
}

// Enable the RNG clock
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
// Restart the generator at image startup.
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();
RNG->SR &= ~RNG_SR_CEIS;
uint32_t recovery_attempts = 0;
// Persistent seed errors leave the RNG output untrustworthy. Stop if
// the startup recovery budget is exhausted.
if (!rng_recover_seed_error(&recovery_attempts)) {
rng_fatal_error();
}

// 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) {
static uint32_t last_rng_result;
bool rng_try_sample(uint32_t* result) {
static uint32_t last_rng_result = 0;

if (result == NULL) {
return false;
}
const uint32_t seed_error_mask = RNG_SR_SECS | RNG_SR_SEIS;
uint32_t recovery_attempts = 0;

for (uint32_t attempt = 0; attempt < RNG_MAX_POLL_ATTEMPTS; attempt++) {
uint32_t status = RNG->SR;
// Clock errors do not invalidate available data (RM0433 section
// 34.3.7). Clear CEIS; CECS clears in hardware when the clock recovers.
if (status & RNG_SR_CEIS) {
RNG->SR &= ~RNG_SR_CEIS;
}
if (status & seed_error_mask) {
if (!rng_recover_seed_error(&recovery_attempts)) {
return false;
}
continue;
}

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.
if (!(status & RNG_SR_DRDY)) {
continue;
}

// Get the new number
uint32_t rv = RNG->DR;

if (rv != last_rng_result && rv) {
// Recheck status for errors that arrived during the data read.
status = RNG->SR;
if (status & RNG_SR_CEIS) {
RNG->SR &= ~RNG_SR_CEIS;
}

// On STM32H753, zero from RNG_DR indicates invalid data and can signal
// a late seed error (RM0433 section 34.7.3). Discard the sample and
// recover on either indication, sharing the same per-call budget.
if (rv == 0 || (status & seed_error_mask)) {
if (!rng_recover_seed_error(&recovery_attempts)) {
return false;
}
continue;
}

// Never return the same value twice in succession.
if (rv != last_rng_result) {
last_rng_result = rv;
*result = rv;

return rv;
return true;
}

// keep trying if not a new number
// A duplicate may be transient. Keep trying within the same
// polling limit; a stuck source will exhaust it 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;
Expand Down
3 changes: 3 additions & 0 deletions ports/stm32/boards/Passport/include/pprng.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
*/
#pragma once

#include <stdbool.h>
#include <stdint.h>

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));
14 changes: 8 additions & 6 deletions ports/stm32/boards/Passport/modpassport-noise.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "adc.h"
#include "noise.h"
#include "pprng.h"
#include "stm32h7xx_hal.h"

/// package: passport
Expand Down Expand Up @@ -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};
Expand All @@ -53,9 +54,10 @@ STATIC mp_obj_t mod_passport_Noise_read(mp_obj_t self) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_passport_Noise_read_obj, mod_passport_Noise_read);

/// def random_bytes(self, buf: buffer, sources: int) -> (int, int):
/// def random_bytes(self, buf: buffer, sources: int) -> None:
/// """
/// Read random bytes from multiple noise sources.
/// Fill buf with random bytes from the selected noise sources.
/// Entropy failure invokes the fatal handler and does not return.
/// """
STATIC mp_obj_t mod_passport_Noise_random_bytes(mp_obj_t self,
mp_obj_t buf_obj,
Expand All @@ -67,10 +69,10 @@ 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;
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_passport_Noise_random_bytes_obj, mod_passport_Noise_random_bytes);

Expand Down Expand Up @@ -99,4 +101,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,
};
};
1 change: 1 addition & 0 deletions ports/stm32/boards/Passport/modules/tasks/new_seed_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

async def new_seed_task(on_done, seed_length):
seed = bytearray(32)
# Entropy failures invoke the fatal handler before this call can return.
common.noise.random_bytes(seed, common.noise.ALL)

# Hash to mitigate any potential bias in RNG sources
Expand Down
6 changes: 6 additions & 0 deletions ports/stm32/boards/Passport/mpconfigboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//

#include <stdint.h>

#define MICROPY_HW_BOARD_NAME "Passport"
#define MICROPY_HW_MCU_NAME "STM32H753"

Expand Down Expand Up @@ -49,6 +51,10 @@ void Passport_board_early_init(void);
#define MICROPY_BOARD_INIT Passport_board_init
void Passport_board_init(void);

// Use Passport's checked RNG for MicroPython's random-number consumers.
#define MICROPY_BOARD_RNG_GET rng_sample
uint32_t rng_sample(void);

/**
* The following two macros disable interrupts preserving interrupt state
* and then properly handle getting the keypad controller to pulse the
Expand Down
8 changes: 4 additions & 4 deletions ports/stm32/boards/Passport/noise.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions ports/stm32/rng.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
#define RNG_TIMEOUT_MS (10)

uint32_t rng_get(void) {
#ifdef MICROPY_BOARD_RNG_GET
return MICROPY_BOARD_RNG_GET();
#else
// Enable the RNG peripheral if it's not already enabled
if (!(RNG->CR & RNG_CR_RNGEN)) {
#if defined(STM32H7)
Expand All @@ -53,6 +56,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.
Expand Down