1.3 Secure MCC Melody MDFU Client Library – ECDSA Signing Operation Example

A post build script automates the preparation, signing and verification of firmware images for secure application deployment. It integrates Hexmate, Python™ , OpenSSL, and avr-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 r8000-1FFFF,%4\%3.X.production.hex -O%4\temp_original_copy.X.production.hex -FILL=w1:0x00,0x00@0xFFBC:0xFFFF
    
    1. r8000-1FFFF: Reads or extracts the address range from 0x8000 to 0x1FFFF from the input hex file. This defines the region of the Flash to operate on.
    2. %4\%3.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%4\temp_original_copy.X.production.hex: Specifies the output file, which will be written to %4\temp_original_copy.X.production.hex. This file is a temporary working copy with modifications applied.
    4. -FILL=w1:0x00,0x00@0xFFBC:0xFFFF: Fills the specified address range (0xFFBC to 0xFFFF) with two consecutive 1-byte values (0x00, 0x00). w1: fill as one byte at a time (width = 1 byte). @0xFFBC:0xFFFF: 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,%4\temp_original_copy.X.production.hex -O%4\temp_original_copy.X.production.hex -FILL=w1:0xFF@0x8000:0xFFFF
    
    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. %4\temp_original_copy.X.production.hex: The input Intel hex file to be processed. %4 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%4\temp_original_copy.X.production.hex: Specifies the output file, overwriting the input file with the modified content. This ensures the fill operation is preserved in the same temporary hex file.
    4. -FILL=w1:0xFF@0x8000:0xFFFF: Fills the specified address range (0x8000 to 0xFFFF) with 0xFF bytes. w1: indicates a fill width of one byte at a time. @0x8000:0xFFFF: applies the fill from address 0x8000 through 0xFFFF.
  3. Shift Data and Export Binary: The Hexmate command extracts and shifts the active application range so it starts at 0x0000 (to align with the application’s execution area), and the file is then converted from Intel hex format to binary; this generates the actual image for signing.
    hexmate r8000-FFBBs-8000,%4\temp_original_copy.X.production.hex -O%4\temp_original_copy.X.production.hex
             %2\avr-objcopy -I ihex -O binary %4\temp_original_copy.X.production.hex %4\%3.X.production.bin
    
    1. r8000-FFBB: Reads or extracts the address range from 0x8000 to 0xFFBB from the input hex file, targeting the main application data area up to (but not including) the signature field.
    2. s-8000: Shifts the extracted address range down by 0x8000 bytes. This means addresses in the output will start from 0x0000 instead of 0x8000, aligning the application image for signing.
    3. %4\temp_original_copy.X.production.hex: The input Intel HEX file containing the prepared application image. %4 is a script variable representing the output or working directory.
    4. -O%4\temp_original_copy.X.production.hex: Specifies the output file, which will be written to %4\temp_original_copy.X.production.hex — overwriting with shifted content.
    5. %2\avr-objcopy -I ihex -O binary %4\temp_original_copy.X.production.hex %4\%3.X.production.bin: Converts the modified Intel hex file to a pure binary format for signing.
      • %2 is a script variable for the location of avr-objcopy tool
      • -I ihex specifies the input format as Intel hex
      • -O binary specifies the output format as raw binary
      • %4\temp_original_copy.X.production.hex is the input file
      • %4\%3.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 .\sjcl_random_private_key_2.pem %4\%3.X.production.bin > %4\%3.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 .\sjcl_random_private_key_2.pem: Uses the private key file (sjcl_random_private_key_2.pem) to digitally sign the computed hash of the input file.
    4. %4\%3.X.production.bin: The input file to be signed. %3 is generally the project name, %4 is the target directory.
    5. > %4\%3.X.production.bin.signature.der: Redirects the output (the DER-encoded digital signature) to a file named %3.X.production.bin.signature.der in directory %4.
  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 useful for development and debugging.
    openssl dgst -sha256 -verify \"public_key.pem\" -signature %4\%3.X.production.bin.signature.der %4\%3.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 “public_key.pem“: Uses the specified public key file (public_key.pem) to verify the digital signature. The public key must match the private key used for signing.
    4. -signature %4\%3.X.production.bin.signature.der: The DER-encoded file containing the digital signature to be verified (%3: project name, %4: output directory)
    5. %4\%3.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\" %4\%3.X.production.bin.signature.der %4\%3.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, which is used to convert a DER-encoded ECDSA signature into a raw binary format.
    2. %4\%3.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 (%3: project name, %4: output directory)
    3. %4\%3.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 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.
    %2\avr-objcopy -I binary -O ihex %4\%3.X.production.bin.signature.bin %4\%3.X.production.bin.signature.hex
    
    1. %2\avr-objcopy: Runs the avr-objcopy utility for converting or reformatting binary files, typically used in microcontroller development. %2 is a variable pointing to the directory containing the avr-objcopy executable.
    2. -I binary: Specifies the input file format as binary. This tells avr-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 microcontroller memory.
    4. %4\%3.X.production.bin.signature.bin: The input file for the conversion. This is the raw binary signature produced by the previous script (%3: project name, %4: output directory)
    5. %4\%3.X.production.bin.signature.hex: The output file for the conversion. This is the resulting Intel hex file containing the signature, ready for merging 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-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
    
    1. hexmate: Invokes the Hexmate tool, which is used for manipulating and merging Intel hex files for Microchip devices.
    2. r0-3FsFFBC,%4\%3.X.production.bin.signature.hex: Reads the address range from 0x00 to 0x3F and then shifts it to start at 0xFFBC from the input signature hex file. - r0-3F: selects bytes 0x00 to 0x3F from the signature hex (the binary signature). - sFFBC: shifts this block to be located at 0xFFBC (the signature area in the final application image). - %4\%3.X.production.bin.signature.hex: the signature hex file (output from previous steps).
    3. r0-FFBB,%4\%3.X.production.hex: Reads address range from 0x00 to 0xFFBB from the main application hex file. This is the bulk of the existing application firmware up to (but not including) the signature field.
    4. rFFFC-FFFFFFFF,%4\%3.X.production.hex: Reads from address 0xFFFC to 0xFFFFFFFF from the application hex file, which commonly includes metadata such as Cyclic Redundancy Check (CRC), reset vectors or Configuration bits.
    5. -O%4\%3.X.production.hex: Specifies the output file, writing the final merged and signed hex image back to %4\%3.X.production.hex.
  9. Finalize Unimplemented Flash Range: Final step is to 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 0x8000–0xFFFF with 0xFF, ensuring consistent values for unused application memory areas.
    hexmate r0-FFFFFFFF,%4\%3.X.production.hex -O%4\%3.X.production.hex -FILL=w1:0xFF@0x8000:0xFFFF
    
    This command fills all unimplemented or unused Flash memory locations in the signed hex file’s address range 0x8000–0xFFFF 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 %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