1.4 Secure MCC MDFU Client Library – ECDSA Signing Operation Example for 32-Bit Devices

A post build script automates the preparation, signing and verification of firmware images for secure application deployment. It integrates Hexmate, Python™ , OpenSSL, and pic32c-objcopy tools to manipulate binary and hex files, embed digital signatures, and ensure the integrity and authenticity of the application firmware before release.

Signing Process Overview:

  1. Insert Blank Signature: This command takes a specific region from the input hex, creates a temporary output and inserts zeros at the signature field, preparing the image for digital signing.
    hexmate r4000-1FFFF,%3\%2.X.production.hex -O%3\temp_original_copy.X.production.hex -FILL=w1:0x00,0x00@0x1FFBC:0x1FFFF
    
    1. r4000-1FFFF: Reads or extracts the address range from 0x4000 to 0x1FFFF from the input hex file. This defines the region of the Flash to operate on.
    2. %3\%2.X.production.hex: The input hex file containing the original application image. %3 and %4 are script variables representing (typically) the project name and output directory, respectively.
    3. -O%3\temp_original_copy.X.production.hex: Specifies the output file, which will be written to %3\temp_original_copy.X.production.hex. This file is a temporary working copy with modifications applied.
    4. -FILL=w1:0x00,0x00@0x1FFBC:0x1FFFF: Fills the specified address range (0x1FFBC to 0x1FFFF) with two consecutive 1-byte values (0x00, 0x00). w1 fills one byte at a time (width = 1 byte). @0x1FFBC:0x1FFFF: applies the 0xFF fill specifically at the end of the Flash (signature location).
  2. Fill Unimplemented Flash Locations: Uninitialized or unused Flash regions in the specified memory range are filled with 0xFF to maintain consistency.
    hexmate r0-FFFFFFFF,%3\temp_original_copy.X.production.hex -O%3\temp_original_copy.X.production.hex -FILL=w1:0xFF@0x4000:0x1FFFF
    1. r0-FFFFFFFF: Reads or extracts the address range from 0x0000 to 0xFFFFFFFF (end of Flash) from the input hex file. This specifies all possible addresses in the hex file for processing.
    2. %3\temp_original_copy.X.production.hex: The input Intel hex file to be processed. %3 is a script variable representing the output or working directory. temp_original_copy.X.production.hex is a temporary working file from earlier script steps.
    3. -O%3\temp_original_copy.X.production.hex: Specifies the output file, overwriting the input file with the modified content. This ensures that the fill operation is preserved in the same temporary hex file.
    4. -FILL=w1:0xFF@0x4000:0x1FFFF: Fills the specified address range (0x4000 to 0x1FFFF) with 0xFF bytes. w1: indicates a fill width of one byte at a time. @0x4000:0x1FFFF: applies the fill from address 0x4000 through 0x1FFFF.
  3. Shift Data and Export Binary: The Hexmate command extracts and shifts the active application range to start at 0x0000 (to align with the application’s execution area). The file is then converted from Intel hex format to binary, generating the actual image for signing.
    hexmate r4000-1FFBBs-4000,%3\temp_original_copy.X.production.hex -O%3\temp_original_copy.X.production.hex
             "%~1\bin\pic32c-objcopy.exe" --remove-section=.gnu.sgstubs -I ihex -O binary %3\temp_original_copy.X.production.hex %3\%2.X.production.bin
    
    1. r4000-1FFBB: Reads or extracts the address range from 0x4000 to 0x1FFBB from the input hex file, targeting the main application data area up to (but not including) the signature field.
    2. s-4000: Shifts the extracted address range down by 0x4000 bytes. This means addresses in the output will start from 0x0000 instead of 0x4000, aligning the application image for signing.
    3. %3\temp_original_copy.X.production.hex: The input Intel HEX file containing the prepared application image. %3 is a script variable representing the output or working directory.
    4. -O%3\temp_original_copy.X.production.hex: Specifies the output file, which will be written to %3\temp_original_copy.X.production.hex — overwriting with shifted content.
    5. "%~1\bin\pic32c-objcopy.exe" --remove-section=.gnu.sgstubs -I ihex -O binary %3\temp_original_copy.X.production.hex %3\%2.X.production.bin : Converts the modified Intel hex file to a pure binary format for signing.
      • %1 is a script variable for the location of the pic32c-objcopy tool
      • -I ihex specifies the input format as Intel hex
      • -O binary specifies the output format as raw binary
      • %3\temp_original_copy.X.production.hex is the input file
      • %3\%2.X.production.bin is the output file for the signed image.
  4. Sign the Binary: This command uses OpenSSL with SHA-256 hashing and a private key to produce and save a DER-format digital signature of the application binary.
    openssl dgst -sha256 -sign .\ec_private_key.pem %3\%2.X.production.bin > %3\%2.X.production.bin.signature.der
    
    1. openssl dgst: Invokes the OpenSSL command-line utility to perform cryptographic digest and signing operations.
    2. -sha256: Specifies the cryptographic hash algorithm (SHA-256) to compute the digest of the input file before signing.
    3. -sign .\ec_private_key.pem: Uses the private key file (ec_private_key.pem) to digitally sign the computed hash of the input file.
    4. %3\%2.X.production.bin: The input file to be signed. %2 is usually the project name, %3 is the target directory.
    5. > %3\%2.X.production.bin.signature.der: Redirects the output (the DER-encoded digital signature) to a file named %2.X.production.bin.signature.der in %3 directory.
  5. Verify the Signature (Optional): This command verifies the SHA-256 signature of the specified binary file using the provided public key and signature, ensuring the binary's authenticity and integrity.
    Tip: This step is optional and recommended for development and debugging.
    openssl dgst -sha256 -verify \"ec_public_key.pem\" -signature %3\%2.X.production.bin.signature.der %3\%2.X.production.bin
    
    1. openssl dgst: Calls the OpenSSL command-line utility to perform digest and verification operations on a file.
    2. -sha256: Specifies the use of the SHA-256 cryptographic hash algorithm. This produces a hash of the input file for verification.
    3. -verify “ec_public_key.pem“: Uses the specified public key file (ec_public_key.pem) to verify the digital signature. The public key must match the private key used for signing.
    4. -signature %3\%2.X.production.bin.signature.der: The DER-encoded file containing the digital signature to be verified (%2: project name, %3: output directory)
    5. %3\%2.X.production.bin: The input file (application binary) whose signature is being verified.
  6. Export Signature as Raw Binary: The DER-formatted signature file is translated to a raw binary format using a Python™ script for compatibility with embedded systems.
    python \"signature_der_to_bin.py\" %3\%2.X.production.bin.signature.der %3\%2.X.production.bin.signature.bin
    
    1. python “signature_der_to_bin.py“: Invokes the Python interpreter to run the signature_der_to_bin.py script, used to convert a DER-encoded ECDSA signature into a raw binary format.
    2. %3\%2.X.production.bin.signature.der: The first argument to the script. This is the path to the input file containing the DER-encoded ECDSA signature to be converted (%2: project name, %3: output directory)
    3. %3\%2.X.production.bin.signature.bin: The second argument to the script. This is the path to the output file where the script will write the ECDSA signature in a raw binary format (concatenated r||s values).
    4. [curve_bytes=32] (optional, not provided here): An optional (third) argument specifying the number of bytes for each component (r and s) in the output file. Defaults to 32, if not specified (suitable for NIST P-256 or secp256r1).

    A sample script for signature_der_to_bin.py is provided below. It can be modified as required.

    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)
    
  7. Convert Signature to Hex: This command converts the raw binary signature into an Intel hex file, enabling it to be embedded into the application firmware image for secure boot or verification purposes.
    "%~1\bin\pic32c-objcopy.exe" --remove-section=.gnu.sgstubs -I binary -O ihex %3\%2.X.production.bin.signature.bin %3\%2.X.production.bin.signature.hex
    1. "%~1\pic2c-objcopy.exe": Runs the pic32c-objcopy utility for converting or reformatting binary files, typically used in the microcontroller development. %1 is a variable pointing to the directory containing the pic32c-objcopy executable.
    2. -I binary: Specifies the input file format as binary. This tells pic32c-objcopy to interpret the source file as a raw binary.
    3. -O ihex: Specifies the output file format as Intel hex. The result will be an Intel hex formatted file, which is suitable for merging or programming into the microcontroller memory.
    4. %3\%2.X.production.bin.signature.bin: The input file for the conversion. This is the raw binary signature produced by the previous script (%2: project name, %3: output directory)
    5. %3\%2.X.production.bin.signature.hex: The output file for the conversion. This is the resulting Intel hex file containing the signature, ready to be merged into the application hex.
  8. Embed Signature into Application Hex: This command uses Hexmate to merge the shifted digital signature, the main application code and upper application space (like CRC/config) into a single output hex file with the digital signature in its designated address range, preparing it for deployment as a signed image.
    hexmate r0-3Fs1FFBC,%3\%2.X.production.bin.signature.hex r0-1FFBB,%3\%2.X.production.hex r1FFFC-FFFFFFFF,%3\%2.X.production.hex -O%3\%2.X.production.hex
    1. hexmate: Invokes the Hexmate tool, used for manipulating and merging Intel hex files for Microchip devices.
    2. r0-3Fs1FFBC,%3\%2.X.production.bin.signature.hex: Reads the address range from 0x00 to 0x3F and then shifts it to start at 0x1FFBC from the input signature hex file.
      1. r0-3F: selects bytes 0x00 to 0x3F from the signature hex (the binary signature).
      2. s1FFBC: shifts this block to be located at 0x1FFBC (the signature area in the final application image).
      3. %3\%2.X.production.bin.signature.hex: the signature hex file (output from previous steps).
    3. r0-1FFBB,%3\%2.X.production.hex: Reads address range from 0x00 to 0x1FFBB from the main application hex file. This is the bulk of the existing application firmware up to (but not including) the signature field.
    4. r1FFFC-FFFFFFFF,%4\%3.X.production.hex: Reads from address 0x1FFFC to 0xFFFFFFFF from the application hex file, which commonly includes metadata such as Cyclic Redundancy Check (CRC), Reset vectors or Configuration bits.
    5. -O%3\%2.X.production.hex: Specifies the output file, writing the final merged and signed hex image back to %3\%2.X.production.hex.
  9. Finalize Unimplemented Flash Range: In final step, fill any remaining unused Flash regions, including CRC locations, with 0xFF for clean and consistent memory. This command fills all unimplemented or unused Flash memory locations in the signed hex file’s address range 0x4000–0x1FFFF with 0xFF, ensuring consistent values for unused application memory areas.
    hexmate r0-FFFFFFFF,%3\%2.X.production.hex -O%3\%2.X.production.hex -FILL=w1:0xFF@0x4000:0x1FFFF
    
    This command fills all unimplemented or unused Flash memory locations in the signed hex file’s address range 0x4000–0x1FFFF with 0xFF, ensuring consistent values for unused application memory areas.
  10. Remove Temporary Files: This command deletes all intermediate files created during the process to conserve storage and maintain workspace cleanliness.
    del %3\%2.X.production.bin.signature.hex 
    del %3\%2.X.production.bin.signature.bin 
    del %3\%2.X.production.bin.signature.der 
    del %3\temp_original_copy.X.production.hex
    del %3\%2.X.production.bin