nrf_crypto_ecdsa_verify fails when verifying signature in custom bootloader

Hi,

I am implementing a custom bootloader on an nRF52840. The firmware update is downloaded while the main application is running. The flow is as follows:

  1. The application receives update information over MQTT (version, firmware size, hash, signature, and URL).

  2. The application downloads the new firmware from the given URL into external flash.

  3. The metadata (hash + signature + version info) is stored in the bootloader settings area.

  4. The device is reset.

  5. On startup, the bootloader checks if new firmware exists in the external flash region.

  6. The bootloader calculates the hash of the firmware and compares it with the stored hash.

  7. If the hash matches, the bootloader attempts to verify the signature using nrf_crypto_ecdsa_verify().

Issue:

The call to nrf_crypto_ecdsa_verify() always fails. I am using curve SECP256R1.
I have tried:

  • Swapping public key and signature endianness (both LE and BE).

  • Splitting the signature and key into two 32-byte components and swapping individually.

  • Confirming the hash is correct. (Comparing the saved and calculated hash succeeds)

To validate the correctness of hash, signature, and public key, I wrote a Python script using cryptography.hazmat.primitives.asymmetric.ec with SECP256R1, and there the verification succeeds. So the signature and data are correct.

Questions:

  1. Does nrf_crypto_ecdsa_verify expect the hash, signature, and key in raw big-endian or little-endian format?

  2. For the public key, should it be passed as a concatenated buffer [X || Y] or in some other internal representation?

  3. For the signature, should it be provided as raw 64 bytes (r || s), or DER encoded?

Parents
  • Hello,

    Thanks for creating this ticket. Is this a fully customized bootloader, or can I assume it’s mostly the same as the one from the nRF5 SDK with the addition of external flash support? Also, was the init command with the signature created using nrfutil (the *.dat file)? If so, it should be sufficient to store the entire contents of that file in the .init_command member of the settings struct and let the bootloader handle the decoding and retrieval of the signature key using the stored_init_cmd_decode() function.

    Does nrf_crypto_ecdsa_verify expect the hash, signature, and key in raw big-endian or little-endian format?

    Our bootloader expects little endian.

    For the public key, should it be passed as a concatenated buffer [X || Y] or in some other internal representation?

    Please see this post  ECDSA with nrfutil generated key  

    Best regards,

    Vidar

  • Okay, first thanks for answering :)


    Here is how i understand the flow should be in my case:
    I use these commands to generate: key, signature and hash:

    - nrfutil keys generate private.pem (private key)

    - nrfutil keys display --key pk --format code private.pem --out_file public_key.c (public key in BE)

    - nrfutil pkg generate --application slow_71.hex --application-version 1 --hw-version 52 --sd-req 0x00 --key-file private.pem app_signed_dfu.zip

    - nrfutil pkg display app_signed_dfu.zip (hash and signature both in LE)



    In the bootloader this is the flow:


    Calculate new hash on the firmware using:

    nrf_crypto_hash_init, 
    nrf_crypto_hash_update,
    nrf_crypto_hash_finalize,

    Where i expect the output hash to be in BE, so in need to swap endian (nrf_crypto_internal_swap_endian) to match it with the saved meta data hash. This succeds.


    Then handling the key that i generated:
    First i swap it with nrf_crypto_internal_double_swap_endian, then i use nrf_crypto_ecc_public_key_from_raw just like the crypto_init in the other post you referenced to.


    Then to verifying the signature:

    I use the swapped hash (BE), i swap the signature with nrf_crypto_internal_double_swap_endian so its also BE and i take the &pub_key i got from nrf_crypto_ecc_public_key_from_raw.

    This is inserted into:
    nrf_crypto_ecdsa_verify(&verify_ctx, &pub_key, hash_be, HASH_LEN, signature_be, SIGNATURE_LEN);

    And that fails. Feel like i have tried every combination of LE and BE of each of the 3 (hash, signature and key)
  • It sounds like you are using the hash digest of the new FW image? Please note that the signature included in the init command (content of the *.data file created by nrfutil, see init packet) is for the init command itself. This means you need to calculate the hash digest of the init packet and not the FW to validate the signature. You can see how this is done by the nrf_dfu_validation_signature_check() function in the SDK bootloader.

    (Ref. https://docs.nordicsemi.com/bundle/sdk_nrf5_v17.1.0/page/lib_secure_boot.html

  • Were you able to make any progress on this after validating the signature of the init command instead of the FW image itself?

Reply Children
  • I have been working on building and flashing the bootloader with a newer development setup in VScode we have, and are not fully there yet. My plan was to go back to using more of the NRF bootloader example and then adding on the functionality that i need. From what i understand i should be able to save the content of the nrf zip package in the xflash and then handle that in the bootloader

  • Thanks for the update. Yes, the original nRF5 SDK bootloader doesn’t include support for external memory, but it’s easier for me at least to help troubleshoot if you’re using this project as a starting point. I’m not sure if you’ve tried modifying the original version to validate the init command instead of the firmware image. nrfutil is signing the content of the *.dat file contained in the zip.

  • Thanks for the help! I believe I understand the theory and have some insight into what might be causing the issue with confirming the signature using the public key.

    Practically, however, I’m still a bit stuck and unsure about how to implement it correctly. After discussing with my tech lead, we’ve decided to take a step back and start from the secure bootloader example provided by Nordic. From there, we hope to collaborate with an expert to plan the best path forward for achieving the functionality we need in our custom bootloader.

    Thanks again for your guidance so far.

  • It sounds like you’ve already made good progress. Since you are using our bootloader now I created a small test demo based on it where I use the onboard QSPI flash on the nRF52840 DK to stage the firmware update, and then have the bootloader validate and activate it.

    nrf5_sdk_ext_flash_dfu.zip

  • Hi!

    Thanks again for the help. The ZIP file you provided was very useful. I’ve now successfully completed the following:

    1. Placed the combined HEX in the external flash

    2. Started the bootloader

    3. Bootloader verifies the init package

    4. Bootloader copies the firmware from external flash into Bank 0

    5. Bootloader jumps to the application

    6. The application runs correctly

    I did run into a small issue in the postvalidate_ext_flash function.
    I had to change:

    uint32_t ext_addr = INIT_COMMAND_MAX_SIZE + 4;

    to:

    uint32_t ext_addr = init_cmd_length + 4;

    so that the firmware is copied from the correct address.


    Now we’re looking into what options we have for always keeping a factory firmware stored and ready to boot into if a newly updated firmware crashes repeatedly.

    Our first thought was to use Bank 0 and Bank 1 alternately—e.g., if Bank 0 was last active and a new firmware update arrives, place it into Bank 1 and run it from there. However, I ran into the issue that the firmware build requires a linker script configured for the exact address where the firmware will be placed. Some things that depend on the linker script include:

    • Code/text section (absolute jumps like BL, B, LDR PC)

    • Static/global variables (.data/.bss) – RAM placement

    • Literal pools / constant tables

    Because of that, we’re now considering a slightly different approach:

    • Keep a known-good factory firmware permanently stored in either Bank 0 or Bank 1

    • Use the other bank as the active “updatable” firmware

    • If the new firmware fails (e.g., repeated crashes), the bootloader can fall back to the factory firmware automatically

    This would give us a “bulletproof” recovery option, since our bootloader differs from the original design where new firmware is downloaded directly by the bootloader.

    What do you think of this approach?
    Is there a recommended pattern for maintaining a permanent factory firmware alongside an updatable application when the bootloader does not handle the download process itself?

    Thanks again for your assistance!

Related