Skip to content

Random Number Generator support #3045

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nuket opened this issue Oct 17, 2016 · 14 comments
Closed

Random Number Generator support #3045

nuket opened this issue Oct 17, 2016 · 14 comments

Comments

@nuket
Copy link
Contributor

nuket commented Oct 17, 2016

Description

  • Type: Enhancement
  • Priority: Major

Target
Nordic nRF5x and any other chip with hardware RNG support


Enhancement

Are there any plans to design / implement an mbed OS API to access the Random Number Generator (RNG) available on Nordic and other chips? I'm trying to seed the mbed TLS CTR-DRBG implementation, but have no access to a good source of entropy. I've tried about all I can to use the hardware RNG unit, but nothing has worked, maybe someone from Nordic can jump in on this, maybe I'm forgetting to power up or enable the particular hardware unit.

Any attempt to perform write operations on the NRF_RNG register, as in the following code, causes the chip to infinitely reset immediately at start (as I try to use the RNG during the static initialization of a cryptographic Salt class):

// Use Nordic Random Number Generator hardware unit.
//
// FIXME: This should be run through mbed TLS CTR-DRBG,
//        there is no guarantee that the Nordic Hardware RNG is 
//        cryptographically strong.
void generate_random_data(uint8_t * output, uint8_t length)
{
    static bool                     initialized = false;
    static mbedtls_ctr_drbg_context context = {0};

    if (!initialized)
    {
        // POWER register is no longer present on nrF52, 
        // use START and STOP tasks.
        // NRF_RNG->POWER    = 1;

        // Disable interrupt on VALRDY.
        NRF_RNG->INTENCLR = 1;

        // mbedtls_ctr_drbg_init(&context);
        // mbedtls_ctr_drbg_seed(&context, nullptr, nullptr, nullptr 0);

        initialized = true;
    }

    // NRF_RNG->TASKS_START   = 1;  // Start RNG
    // NRF_RNG->CONFIG        = 1;  // Enable bias correction.

    for (uint8_t i = 0; i < length; i++)
    {
        // Trying to write this value on an nRF52 causes immediate reset.
        // NRF_RNG->EVENTS_VALRDY = 0;

        // Wait until the value ready event is generated.
        // Roughly 120us, when bias correction is enabled.
        // while ( !NRF_RNG->EVENTS_VALRDY );

        nrf_delay_us(250);

        output[i] = (uint8_t) NRF_RNG->VALUE;
    }

    // NRF_RNG->TASKS_STOP = 1;
}
@pan-
Copy link
Member

pan- commented Oct 17, 2016

Are there any plans to design / implement an mbed OS API to access the Random Number Generator (RNG) available on Nordic and other chips?

Yes!

Any attempt to perform write operations on the NRF_RNG register, as in the following code, causes the chip to infinitely reset immediately at start (as I try to use the RNG during the static initialization of a cryptographic Salt class):

  • Which NRF_RNG cause the hardfault ?
  • Is the softdevice already enabled at this stage ?
  • Did you try to use the functions exposed by the softdevice ?
    • sd_rand_application_pool_capacity_get
    • sd_rand_application_bytes_available_get
    • sd_rand_application_vector_get

@nuket
Copy link
Contributor Author

nuket commented Oct 18, 2016

Awesome.

  • I've been using the NRF52_DK for most of my prototyping work. The NRF51_DK RNG seemed to work, but had other problems (too little memory to run everything + crypto from mbed TLS).
  • The SoftDevice probably is not started at this point, as the code calling into the RNG is getting called from the constructor of one of my classes, which is defined at the head of my main source file. I may try to defer use of the RNG to the point at which the bleInitComplete callback gets called.
  • I have not tried the functions exposed by the SoftDevice, but that is definitely a suggestion I will try.

@pan-
Copy link
Member

pan- commented Oct 18, 2016

@nuket If you download nordic SDK V11, you will see a module handling the RNG. If the softdevice is present (which is always the case for now in mbed-OS), then the module relies on the softdevice primitives otherwise it use the registers available. That might give you inspiration if you look at this module until we add the support in mbed-os.

@nuket
Copy link
Contributor Author

nuket commented Oct 18, 2016

Ahh yes, I was looking at the nrf_drv_rng.c file from SDK 12 yesterday. I'll take a closer look at the sd_rand_* functions.

@nuket
Copy link
Contributor Author

nuket commented Oct 18, 2016

Awesome, the sd_rand_* functions work fine.

The Nordic SoftDevice RNG returns up to 32 bytes at a time. Unsure whether bias correction is enabled. In order to get more than 32 bytes of random data for the CTR-DRBG in SHA-512 mode, when MBEDTLS_CTR_DRBG_ENTROPY_LEN == 48, the entropy-gathering user callback needs to pool bytes, by calling sd_rand_application_vector_get() multiple times, while waiting between calls for the random number pool to refill.

@nuket
Copy link
Contributor Author

nuket commented Oct 19, 2016

Spoke a little too soon.

The SoftDevice RNG seems to work fine, but attempting to seed the CTR-DRBG leads to a Hard Fault (blinkenlights!), which prints this on the console just before dying:

RTX error code: 0x00000001, task ID: 0x200073D8

Which seems to indicate a stack overflow?

$ grep -rn OS_ERR_STK_OVF *
mbed-os/rtos/rtx/TARGET_CORTEX_A/rt_System.c:330:    os_error (OS_ERR_STK_OVF);
mbed-os/rtos/rtx/TARGET_CORTEX_A/RTX_Config.h:37:#define OS_ERR_STK_OVF          1
mbed-os/rtos/rtx/TARGET_CORTEX_M/rt_System.c:318:        os_error (OS_ERR_STK_OVF);
mbed-os/rtos/rtx/TARGET_CORTEX_M/RTX_Config.h:37:#define OS_ERR_STK_OVF          1U
mbed-os/rtos/rtx/TARGET_ARM7/rt_System.c:285:            os_error (OS_ERR_STK_OVF);
mbed-os/rtos/rtx/TARGET_ARM7/RTX_Conf.h:37:#define OS_ERR_STK_OVF          1

Based on comments in #2635, the default RTX stack size looks like it's 200 bytes:

//   <o>Default Thread stack size [bytes] <64-4096:8><#/4>
//   <i> Defines default stack size for threads with osThreadDef stacksz = 0
//   <i> Default: 200
#ifndef OS_STKSIZE
 #define OS_STKSIZE     200
#endif

Problem is that there's an allocation of 384 bytes in mbedtls_ctr_drbg_reseed(), which probably blows everything up:

int mbedtls_ctr_drbg_reseed( mbedtls_ctr_drbg_context *ctx,
                     const unsigned char *additional, size_t len )
{
    unsigned char seed[MBEDTLS_CTR_DRBG_MAX_SEED_INPUT];
    ...
}

I'm going to try setting a larger stack size for my target and see if that fixes the problem.

For reference, I modified the BLE_HeartRate example main.cpp to look like this, to test my code in near-isolation, but with the SoftDevice running:

/* mbed Microcontroller Library
 * Copyright (c) 2006-2015 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <mbed-events/events.h>
#include <mbed.h>

#include "ble/BLE.h"
#include "ble/Gap.h"
#include "ble/services/HeartRateService.h"

#include "nrf_soc.h"

#include <mbedtls/ctr_drbg.h>

DigitalOut led1(LED1, 1);

const static char     DEVICE_NAME[] = "HRM";
static const uint16_t uuid16_list[] = {GattService::UUID_HEART_RATE_SERVICE};

static uint8_t hrmCounter = 100; // init HRM to 100bps
static HeartRateService *hrServicePtr;

static EventQueue eventQueue(
    /* event count */ 16 * /* event size */ 32
);

static int nordic_entropy_source(void * entropyContext, unsigned char * outBuffer, size_t outLength)
{
    const size_t   RNG_POOL_SIZE = 32;

    const size_t   divCount      = outLength / RNG_POOL_SIZE; // Full blocks.
    const size_t   modCount      = outLength % RNG_POOL_SIZE; // Leftover bytes.

    const uint16_t WAIT_LIMIT    = 4000;   // How many cycles can we wait for the entropy pool to fill.

    uint8_t availableBytes;

    // Stats.
    printf("divCount: %d, modCount: %d\r\n", divCount, modCount);

    for (size_t d = 0; d < divCount; d++)
    {
        // Wait until there are enough bytes.
        for (uint16_t w = 0; w < WAIT_LIMIT; w++)
        {
            sd_rand_application_bytes_available_get(&availableBytes);

            if (RNG_POOL_SIZE == availableBytes) 
            {
                sd_rand_application_vector_get(outBuffer, RNG_POOL_SIZE);
                outBuffer += RNG_POOL_SIZE;
                break;
            }
            else
            {
                printf("Waiting for entropy.\r\n");
            }
        }
    }

    // Wait until there are enough bytes.
    for (uint16_t w = 0; w < WAIT_LIMIT; w++)
    {
        sd_rand_application_bytes_available_get(&availableBytes);

        if (modCount <= availableBytes)
        {
            sd_rand_application_vector_get(outBuffer, modCount);
            break;
        }
        else
        {
            printf("Waiting for entropy.\r\n");
        }
    }

    return 0;
}

// Use Nordic Random Number Generator hardware unit.
//
// FIXME: This should be run through mbed TLS CTR-DRBG,
//        there is no guarantee that the Nordic Hardware RNG is 
//        cryptographically strong.
void generate_random_data(uint8_t * output, uint8_t length)
{
    static bool                     initialized = false;
    static mbedtls_ctr_drbg_context context = {0};

    if (!initialized)
    {
        mbedtls_ctr_drbg_init(&context);
        mbedtls_ctr_drbg_seed(&context, nordic_entropy_source, NULL, NULL, 0);

        initialized = true;
    }

    // Use CTR-DRBG.
    mbedtls_ctr_drbg_random(&context, output, length);

    // Use SoftDevice RNG.
    // nordic_entropy_source(NULL, output, length);
}


void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
    BLE::Instance().gap().startAdvertising(); // restart advertising
}

void updateSensorValue() {
    // Do blocking calls or whatever is necessary for sensor polling.
    // In our case, we simply update the HRM measurement.
    hrmCounter++;

    //  100 <= HRM bps <=175
    if (hrmCounter == 175) {
        hrmCounter = 100;
    }

    hrServicePtr->updateHeartRate(hrmCounter);
}

void periodicCallback(void)
{
    led1 = !led1; /* Do blinky on LED1 while we're waiting for BLE events */

    uint8_t output[73] = {0};
    generate_random_data(output, sizeof(output));

    for (size_t j = 0; j < sizeof(output); j++)
    {
        printf("%02x ", output[j]);
    }
    printf("\r\n");

    if (BLE::Instance().getGapState().connected) {
        eventQueue.post(updateSensorValue);
    }
}

void onBleInitError(BLE &ble, ble_error_t error)
{
    (void)ble;
    (void)error;
   /* Initialization error handling should go here */
}

void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
    BLE&        ble   = params->ble;
    ble_error_t error = params->error;

    if (error != BLE_ERROR_NONE) {
        onBleInitError(ble, error);
        return;
    }

    if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
        return;
    }

    ble.gap().onDisconnection(disconnectionCallback);

    /* Setup primary service. */
    hrServicePtr = new HeartRateService(ble, hrmCounter, HeartRateService::LOCATION_FINGER);

    /* Setup advertising. */
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_HEART_RATE_SENSOR);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().setAdvertisingInterval(1000); /* 1000ms */
    ble.gap().startAdvertising();
}

void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext* context) {
    BLE &ble = BLE::Instance();
    eventQueue.post(Callback<void()>(&ble, &BLE::processEvents));
}

int main()
{
    printf("main() starting.\r\n");

    eventQueue.post_every(1000, periodicCallback);

    BLE &ble = BLE::Instance();
    ble.onEventsToProcess(scheduleBleEventsProcessing);
    ble.init(bleInitComplete);

    while (true) {
        eventQueue.dispatch();
    }

    return 0;
}

@nuket
Copy link
Contributor Author

nuket commented Oct 19, 2016

Hmm. Well, that didn't work. Setting OS_IDLESTKSIZE=2000, OS_STKSIZE=2000, OS_MAINSTKSIZE=2000 in mbed_app.json still sees the above program jumping to a HardFault pretty much right after main() starting. is printed.

@nuket
Copy link
Contributor Author

nuket commented Nov 24, 2016

Still unsure why this HardFault is happening.

I instrumented mbedtls_ctr_drbg_reseed to look like:

int mbedtls_ctr_drbg_reseed( mbedtls_ctr_drbg_context *ctx,
                     const unsigned char *additional, size_t len )
{
    unsigned char seed[MBEDTLS_CTR_DRBG_MAX_SEED_INPUT];
    size_t seedlen = 0;

    printf("mbedtls_ctr_drbg_reseed 1\r\n");
    
    if( ctx->entropy_len + len > MBEDTLS_CTR_DRBG_MAX_SEED_INPUT )
        return( MBEDTLS_ERR_CTR_DRBG_INPUT_TOO_BIG );

    memset( seed, 0, MBEDTLS_CTR_DRBG_MAX_SEED_INPUT );

    /*
     * Gather entropy_len bytes of entropy to seed state
     */
    if( 0 != ctx->f_entropy( ctx->p_entropy, seed,
                             ctx->entropy_len ) )
    {
        return( MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED );
    }

    seedlen += ctx->entropy_len;

    printf("mbedtls_ctr_drbg_reseed 2\r\n");
    
    /*
     * Add additional data
     */
    if( additional && len )
    {
        memcpy( seed + seedlen, additional, len );
        seedlen += len;
    }

    /*
     * Reduce to 384 bits
     */
    block_cipher_df( seed, seed, seedlen );

    /*
     * Update state
     */
    ctr_drbg_update_internal( ctx, seed );
    ctx->reseed_counter = 1;

    printf("mbedtls_ctr_drbg_reseed 3\r\n");
    
    return( 0 );
}

But it dies right after printing the third line. Is anyone from Nordic following this issue?

@nvlsianpu
Copy link
Contributor

Hi. Did you have this #2969 included in mbed-os you using for development?

@nvlsianpu
Copy link
Contributor

BTW It is said that nRF52's RNG generates true non-deterministic random numbers based on internal
thermal noise (by nRF52 data sheet). So there is the warrant of strong capabilities.

@nvlsianpu
Copy link
Contributor

Is it possible that softdevice RNG API had been called before bleInitComplete happened?

@seppestas
Copy link
Contributor

Is there a reason why the RNG driver is not available in Nordic SDK v11 included in mbed-os? It seems to be there for the nRF51 and v13 of the SDK (only used for the nRF52840).

@dlfryar-zz
Copy link
Contributor

Mbed OS SDK updates happening here

@marcuschangarm
Copy link
Contributor

New TRNG driver has been merged to master: #6116

@0xc0170 0xc0170 closed this as completed May 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants