5.8.1 Updating the Application Using a Bootloader Client With Secure Boot and Secure Device Firmware Update

This section demonstrates how to configure the application for a bootloader client with secure feature

Note: The application builder generates a footer_certificate.c file which needs to be updated with valid addresses depending upon the application start address and size of the execution space.

Description: This section details how to update the data in the footer_certificate.c file and configure the linker settings of the application project for multi-image support.

  1. Create a new MPLAB® X IDE project and open MCC. MCC should open up automatically when a new project is created. If not, click the MCC button in the menu bar to start the MPLAB Code Configurator.

    For the configuration, select a timer, and enable the Interrupt Driven toggle button and set the Requested Timer Period to 200 ms. In the Configuration Manager, configure the setting for the IVTLOCK bit One-way set enable bit to ‘IVTLOCKED bit can be cleared and set repeatedly’. In the Interrupt Manager, the Enable Vectored Interrupt toggle button is enabled. IVT Base Address must be set to 0x8008 (Application Address + Code Offset). Use the Pin Grid View to select the GPIO pins for RB4 (renamed as BTN with Weak Pullup enabled) and RF3 (renamed as LED).

  2. Click on Generate.

  3. Create and open the footer_certificate.c file.

    Note: The application builder automatically generates this file.
  4. Add the following details into the file according to the configuration on the MDFU client UI.

    Note: The execution space will always be 0x0000.
    #define EXECUTION_SPACE (0x0000)
    #define STAGING_SPACE   (0x0001)
    #define BACKUP_SPACE   (0x0002)
    
    #include <stdint.h>
    
    #ifdef __XC8__
    #include <xc.h>
    #endif
    
    volatile const uint16_t
    #ifdef __XC8__
    __at(0xFFAE)
    #endif
    applicationSlotId __attribute__((used, section("application_slot_id"))) = EXECUTION_SPACE;
    volatile const uint32_t
    #ifdef __XC8__
    __at(0xFFB0)
    #endif
    applicationVersion __attribute__((used, section("application_version"))) = 0x00000100;
    volatile const uint32_t
    #ifdef __XC8__
    __at(0xFFB4)
    #endif
    verificationEndAddress __attribute__((used, section("application_verify_end"))) = 0x0000FFBBU;
    volatile const uint32_t
    #ifdef __XC8__
    __at(0xFFB8)
    #endif
    verificationStartAddress __attribute__((used, section("application_verify_start"))) = 0x00008000U;
    volatile const uint32_t
    #ifdef __XC8__
    __at(0xFFBC)
    #endif
    footerSignature[] __attribute__((used, section("atecc608_certificate"))) = {
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0
    };
    volatile const uint32_t
    #ifdef __XC8__
    __at(0xFFFC)
    #endif
    applicationHash __attribute__((used, section("application_hash"))) = 0xFFFFFFFFU;
    Understanding the variables stored in the footer data of the Flash memory:
    Table 5-4. Footer Certificate Data
    VariablePurposeAddress
    applicationSlotIdThis is the location where the current image will be stored.

    Current Example Value: EXECUTION_IMAGE_ID

    Should be: Image ID of the image location to update

    The address in the __at() is calculated by subtracting two from the start address of the applicationVersion.

    The two bytes are used for storing the uint16_t type variable applicationId.

    This value might change for your application.

    applicationVersionThis is the version of the current application.

    Current Example Value: 0x00000100

    Should be: Version of the application

    Warning: Application version cannot be 0 or 4294967295 (0xFFFFFFFF).
    The address in the __at() is calculated by subtracting four from the start address of the verificationEndAddress.

    The four bytes are used for storing the uint32_t type variable applicationVersion.

    If rollback protection is enabled, this value must be updated whenever a new application image needs to be loaded.

    verificationEndAddress

    This is the last address that is considered for the hash calculation mapped into the execution space. The value of this argument must always fall within the execution space.

    Current Example Value: 0x0000FFBBU

    Should be: Execution Image End Address

    The address in the __at() is calculated by subtracting four from the start address of the verificationStartAddress.

    The four bytes are used for storing the uint32_t type variable verificationEndAddress.

    This value might change for your application.

    verificationStartAddress

    This is the first address that is considered for the hash calculation mapped into the execution space. The value of this argument must always fall within the execution space.

    Current Example Value: 0x00008000U

    Should be: Execution Image Start Address

    The address in the __at() is calculated by subtracting four from the footer signature address.

    The four bytes are used for storing the uint32_t type variable verificationStartAddress.

    This value might change for your application.

    footerSignature[]This variable will hold the cryptographic signature appended to the end of the firmware image used by the secure bootloader to verify the integrity and authenticity of the image before executionThe address in the __at() is calculated by subtracting sixty four from the start address of the application hash address.

    The sixty four bytes are used for storing the uint32_t type array 16 spaces array of the footer signature.

    This value might change for your application.

    applicationHashThis variable holds the cryptographic hash of the application imageThe address in the __at() is calculated by subtracting four from the end address of the application image to be verified.

    The four bytes are used for storing the uint32_t type variable applicationHash.

    This value might change for your application.

  5. Add the following code into the main.c file.

    For PIC devices:

    #include "mcc_generated_files/system/system.h"
    #include "mcc_generated_files/timer/tmr0.h"
    /*
        Main application
    */
    void BlinkLED(void)
    {
        LED_Toggle();
    }
    int main(void)
    {
        SYSTEM_Initialize();
        // If using interrupts in PIC18 High/Low Priority mode, enable the Global High and Low Interrupts 
        // If using interrupts in PIC Mid-Range Compatibility mode, enable the Global Interrupts 
        // Use the following macros to: 
    
        // Enable the Global High Interrupts 
        INTERRUPT_GlobalInterruptHighEnable(); 
    
        // Disable the Global High Interrupts 
        //INTERRUPT_GlobalInterruptHighDisable(); 
    
        // Enable the Global Low Interrupts 
        INTERRUPT_GlobalInterruptLowEnable(); 
    
        // Disable the Global Low Interrupts 
        //INTERRUPT_GlobalInterruptLowDisable(); 
    
        Timer0.TimeoutCallbackRegister(&BlinkLED);
    
        while (1)
        {
            if (BTN_GetValue() == 0U)
            {
                RESET();
            }
        }    
    }

    For AVR devices:

  6. In Project Properties, set the Building > Execute this line after build with the following configuration:

  7. Additionally, the linker settings need to be updated to restrict the ROM range to the current application image ID’s Flash range.
    1. For PIC devices:
      Figure 5-202. Compiler - Linker Settings

      Where 8000-FFFF is <applicationId image start address: applicationId image end address>. These values can be updated by referring to the Device Flash Memory Partitions Table in the MDFU Client UI and the applicationId.
    2. For AVR devices:

      For an AVR-based bootloader client with applicationId image range of 0x8000-0xFFFF the linker settings are as follows:

      • XC8 linker settings:

        Open Project Properties>XC8 Linker>Additional Options. In the “Additional Options” text box, add

        -Wl,-u,checksumStart,-u,verificationStartAddress,-u,verificationEndAddress,-u,applicationVersion,-u,applicationId,-u,__TEXT_REGION_LENGTH__=<applicationId image start address: applicationId image end address>,-Ttext=<applicationId image start>

        XC8 Linker Settings

      • GCC Linker Settings
        Open Project Properties>Avr GCC (Global Options)>avr-ld>General. In the “Additional Options” text box, add
        -Wl,-u,checksumStart,-u,verificationStartAddress,-u,verificationEndAddress,-u,applicationVersion,-u,applicationId,-u,__TEXT_REGION_LENGTH__=<applicationId image start address: applicationId image end address>,-Ttext=<applicationId image start>
        Figure 5-203. GCC Linker Settings
  8. Create a new file under Important Files section by right clicking and going to Important Files>New>Empty File. Name the file as postBuildSigning.bat and add the following content.
    @echo off
    REM This post-build script performs a sequence of operations to prepare, sign, and manage firmware images for secure application deployment.
    REM This script uses OpenSSL for two main operations:
    REM 1. Digital Signature Creation
    REM 2. Verification with public key (Optional: Useful while testing with new keys) 
    
    REM This script also uses the pyasn1 python library to convert the signature from DER format to a raw binary format.
    REM _________________________________________________________________________________________________________________________________________________________________________________________________________________________
    
    REM Blank signature location - This step inserts 0x00s to the Flash memory locations where signature is stored 
    REM Generic command description: 
    REM if %1==CRC32/16 hexmate r<verificationStartAdress>-<flash end address>,%4\%3.X.production.hex -O%4\temp_original_copy.X.production.hex -FILL=w1:0x00,0x00@<SignatureStartAddress>:<execution space end address>
    if %1==CRC32 hexmate r8000-1FFFF,%4\%3.X.production.hex -O%4\temp_original_copy.X.production.hex -FILL=w1:0x00,0x00@0xFFBC:0xFFFF
    
    REM Fill in unimplemented Flash locations - Filling the unimplemented Flash locations in application range
    REM Generic command description:
    REM hexmate r0-FFFFFFFF,%4\temp_original_copy.X.production.hex -O%4\temp_original_copy.X.production.hex -FILL=w1:0xFF@verificationStartAdress>:<execution space end address>
    hexmate r0-FFFFFFFF,%4\temp_original_copy.X.production.hex -O%4\temp_original_copy.X.production.hex -FILL=w1:0xFF@0x8000:0xFFFF
    
    REM Generate application binary image - Shifts the hex data back by <verificationStartAddress> bytes and converts the hex file into a binary file
    REM Generic command description:
    REM if %1==CRC32 hexmate r<verificationStartAddress>-<verificationEndAddress>s-<verificationStartAddress>,%4\temp_original_copy.X.production.hex -O%4\temp_original_copy.X.production.hex
    if %1==CRC32 hexmate r8000-FFBBs-8000,%4\temp_original_copy.X.production.hex -O%4\temp_original_copy.X.production.hex
    
    REM This command converts the shifted hex into a binary file
    %2\avr-objcopy -I ihex -O binary %4\temp_original_copy.X.production.hex %4\%3.X.production.bin
    
    REM Sign binary with the correct private key
    openssl dgst -sha256 -sign .\sjcl_random_private_key_2.pem %4\%3.X.production.bin > %4\%3.X.production.bin.signature.der
    
    REM Verify using OpenSSL
    openssl dgst -sha256 -verify "public_key.pem" -signature %4\%3.X.production.bin.signature.der %4\%3.X.production.bin
    
    REM Export signature value
    python "signature_der_to_bin.py" %4\%3.X.production.bin.signature.der %4\%3.X.production.bin.signature.bin
    
    REM This command converts the signature binary into a hex file
    %2\avr-objcopy -I binary -O ihex %4\%3.X.production.bin.signature.bin %4\%3.X.production.bin.signature.hex
    
    REM Copy signature .hex into application .hex file application header
    REM LOGIC: Signature hex (shifted to signature start address) + existing application hex  + existing application hex 2 (verification data - CRC32) ==> new application hex
    REM Generic command description:
    REM if %1==CRC32 hexmate r0-3Fs<verification End Address + 1>,%4\%3.X.production.bin.signature.hex r0-FFBB,%4\%3.X.production.hex r<application Hash address>-FFFFFFFF,%4\%3.X.production.hex -O%4\%3.X.production.hex
    if %1==CRC32 hexmate r0-3FsFFBC,%4\%3.X.production.bin.signature.hex r0-FFBB,%4\%3.X.production.hex rFFFC-FFFFFFFF,%4\%3.X.production.hex -O%4\%3.X.production.hex
    
    REM Fill unimplemented Flash locations in the hex generated above including CRC location
    REM Generic command description:
    REM if %1==CRC32 hexmate r0-FFFFFFFF,%4\%3.X.production.hex -O%4\%3.X.production.hex -FILL=w1:0xFF@0x<verification start Address>:<execution space end address>
    if %1==CRC32 hexmate r0-FFFFFFFF,%4\%3.X.production.hex -O%4\%3.X.production.hex -FILL=w1:0xFF@0x8000:0xFFFF
    
    REM Delete the temporary files
    del %4\%3.X.production.bin.signature.hex 
    del %4\%3.X.production.bin.signature.bin 
    del %4\%3.X.production.bin.signature.der 
    del %4\temp_original_copy.X.production.hex
    del %4\%3.X.production.bin
  9. Create a signature_der_to_bin.py file with the following content.
    from pyasn1.codec.der import decoder
    from pyasn1.type.univ import Sequence
    import sys
    
    def der_to_bin(der_path, bin_path, curve_bytes=32):
        with open(der_path, 'rb') as der_file:
            der_data = der_file.read()
        sig_seq, _ = decoder.decode(der_data, asn1Spec=Sequence())
        # Get r and s integers
        r = int(sig_seq[0])
        s = int(sig_seq[1])
        # Convert to bytes, pad to proper length
        r_bytes = r.to_bytes(curve_bytes, byteorder="big")
        s_bytes = s.to_bytes(curve_bytes, byteorder="big")
        with open(bin_path, 'wb') as bin_file:
            bin_file.write(r_bytes + s_bytes)
        print(f"Converted {der_path} -> {bin_path}")
    
    if __name__ == "__main__":
        if len(sys.argv) < 3:
            print("Usage: python signature_der_to_bin.py signature.der signature.bin [curve_bytes=32]")
            sys.exit(1)
        der_path = sys.argv[1]
        bin_path = sys.argv[2]
        curve_bytes = int(sys.argv[3]) if len(sys.argv) > 3 else 32
        der_to_bin(der_path, bin_path, curve_bytes)
  10. Add the required files for the keys sjcl_random_private_key_2.pem and public_key.pem. This can be user generated or follow the Provisioning Guide which explains how to create these keys using OpenSSL®.
  11. Click Clean and Build to the generate the project hex file.

  12. Create the application image using the pyfwimagebuilder tool guide. The newly-generated application image can be transferred to the target device using the Microchip Device Firmware Update (pymdfu) tool.
    Command:
    pymdfu update --tool serial --image output.img --baudrate 115200 --port <COM PORT name>

    The update will run and finished successfully.