This tutorial demonstrates how a trusted signing process for the Espressif ESP32 controller can be implemented by integrating the SignServer code signing infrastructure and using certificates issued through EJBCA, avoiding storing the private part of the signature key locally and thus preventing the key from being accessed without security protection and proof of authenticity.

ESP32 is popular in the IoT community as a foundation for connected devices and its built-in Secure Boot feature prevents unauthorized code from running on the chip. Secure boot allows the microcontroller to verify the digital signature, which is calculated through the application binary itself, each time it is booted. The ESP32 development environment comes with a toolchain that enables you to generate and integrate signing functionality during your build process on your local machine. For more information about how the workflow is implemented, refer to the Espressif documentation on Security and Secure Boot

The following sections demonstrate how you can use SignServer to sign the binary app for your ESP32 and use a Secure Boot V1 enabled ESP32 MCU board to verify the signature. 

The ESP32 Secure Boot concept is part of the overall security features developed by Espressif systems. For more information, refer to the Espressif documentation on Security.

In this tutorial, you will learn how to:

  • Issue signing certificate using EJBCA
  • Create a signing token in SignServer
  • Configure ESP-IDF and enable Secure Boot
  • Generate the SignServer signature and attach to the application image
  • Upload the image and verify the signature with ESP32

Prerequisites

To take full advantage of this tutorial, you need to have a basic understanding of the Espressif platform and security concepts. It is assumed that you are familiar and experienced with the ESP32 microcontroller, its system architecture, and the software lifecycle process. You should also understand and be able to apply the basic security functions of the ESP32. 

Before you begin, you also need the following:

Part 1 - Create signer and issue signing certificate

The following steps outline how to create a SignServer signer and issue a signing certificate using the EJBCA Certificate Authority (CA).

To learn how to create a signer and issue a signing certificate, you can also follow steps 1-3 in the tutorial SignServer Container Signing with Cosign.

Step 1 - Issue signing certificate using EJBCA

Use your Certificate Authority (CA) to issue a signing certificate. This tutorial uses the EJBCA CA. To learn how to get started with EJBCA, you can follow the Start EJBCA Container with Unauthenticated Network Access.

The following sections describe how to create basic profiles in EJBCA and issue a digital signing certificate.

Create Certificate Profile

Certificate profiles define the constraints of the certificate, for example, what keys it can use, and what the extensions will be.

To create a certificate profile that the certificate will be issued under, follow these steps:

  1. In EJBCA, under CA Functions, click Certificate Profiles.
  2. Click the bottom row of the List of Certificate Profiles, specify the name PlainSigner, and then click Add.
    The newly created  template is displayed in the list of certificate profiles.
  3. Click Edit for the PlainSigner profile and specify the following:
    • For Available Key Algorithms, select ECDSA.
    • For Validity or end date of the certificate, specify 1y for one-year validity.
    • For Key Usage, clear Non-repudiation and Key encipherment to only use Digital Signature.
    • For Extended Key Usage, clear Use to not use any extended key usage.
  4. Leave the rest of the fields as default and click Save to store the profile.

Create End Entity Profile

To create an end entity profile with information about the holder of the certificate, follow these steps:

  1. In EJBCA, under RA Functions, click End Entity Profiles.
  2. In the Add End Entity Profile field, add a name for the new profile, in this example PlainSigner, and click Add profile.
    The newly created profile is displayed in the list of end entity profiles.
  3. Select the PlainSigner profile, and click Edit End Entity Profile.
  4. Update the following to map the end entity profile to the PlainSigner certificate profile:
    • For Available Certificate Profile, select PlainSigner.
    • For Default Certificate Profile, select PlainSigner.
  5. Click Save to store the updated end entity profile.

Now that you have created the profiles, you can continue and issue the certificate in the next step.

Issue Code signing Certificate

To issue the certificate using the EJBCA RA UI:

  1. In EJBCA, click RA Web to access the EJBCA RA UI.
  2. Under Request new certificate, select Make New Request.
  3. For Select Request Template Certificate Type, select PlainSigner.
  4. For Key-pair generation, select By the CA.
  5. For Enrollment code, enter a password.  This will be the same password that is used to protect the certificate once downloaded.
  6. Click Download PKCS#12 to download and save the generated certificate file as ESPSigner.p12.

Your certificate is downloaded as an ESPSigner.p12 file that you can now upload to SignServer.

Step 2 - Create signing token in SignServer

Create Crypto Worker

To add a crypto worker in SignServer for storing the private keys, perform the following steps:

  1. In your terminal, copy the ESPSigner.p12 certificate to the SignServer Docker container:

    docker cp ESPSigner.p12 <CONTAINER-ID>:opt/signserver/res/test/dss10
    CODE

     

  2. Go to the SignServer Administration Web Workers page.
  3. Click Add to add a new crypto worker and choose the method From Template.
  4. In the Load From Template list menu, select the keystore-crypto.properties worker template and click Next.
  5. To update the path to the keystore file to reference the ESPSigner.p12 token, specify "WORKERGENID1.KEYSTOREPATH"=/opt/signserver/res/test/dss10/ESPSigner.p12
  6. Click Apply to add the crypto worker.
  7. Click Activate to activate the newly created "CryptoTokenP12".
  8. Enter the enrollment code, and then click Activate.

The crypto token is now created based on the ESPSigner.p12 file.

Create Signing Worker

To create the signing worker in SignServer, perform the following steps:

  1. First, to copy the alias of the crypto token, do the following:

    • On the SignServer Workers page, select the CryptoTokenP12 and then click Crypto Token.

    • On the Crypto Token page, click the alias in the list to display its details.

    • Right-click the alias value in the list and select Copy.

    • Click Workers in the menu bar to go back to the Workers page.
  2. Next, to create the signing worker, do the following:

    1. On the SignServer Workers page, click Add and choose the method From Template

    2. In the Load From Template list menu, select the plainsigner.properties worker template and click Next

    3. On the Add Worker / Load Configuration page, choose the method From Template.
    4. In the Load From Template list menu, select the plainsigner.properties worker template and click Next.
    5. To update to use the alias from the crypto token copied in the previous step, paste the alias as the value for "WORKERGENID1.DEFAULTKEY"
  3. Click Apply to load the configuration.

  4. The newly created signing worker is listed  and active.

Your SignServer environment is now set up to be able to receive signing requests.

Part 2 - Enable ESP32 Secure Boot and sign application 

Once you have completed all the steps in the first part of this tutorial, you can now use your signer to generate a signature and sign an ESP32 application.

Step 1 - Configure ESP-IDF and enable Secure Boot

The following outlines how to use the Espressif IoT Development Framework (ESP-IDF) to configure the ESP32 controller.

When enabling Secure Boot on the ESP32, it is recommended to configure the partition table with a factory app partition and two over-the-air (OTA) partitions. The "factory app, two OTA definitions" partition table will contain a factory app (a failsafe for when updates fail) and two OTA partitions, which contain the last two firmware updates. Additionally, you should expand the offset of the partition table as described in the following steps. For more information about enabling secure boot, refer to the Espressif documentation on How To Enable Secure Boot.

Enabling secure boot limits your options for further updates of your ESP32. Review the Espressif documentation on Secure Boot to learn more about the implications of enabling secure boot.

To set up your development environment and configure the partition table and offset, do the following:

  1. Run the following to open the ESP-IDF project configuration menu:

    idf.py menuconfig 
    CODE


  2. To activate secure boot on the first boot, navigate to Security features:
    • Under Enable hardware Secure Boot in bootloader, select Select secure boot version (Enable Secure Boot version 1).
    • Disable Sign binaries during build, to avoid having the signing key unprotected on the local build machine.
    • Save the changes.
  3. Using the arrow keys, navigate to Partition Table and for Factory app, two OTA definitions, set the offset to 0x10000.

  4. Save the configuration.
  5. To launch the IDF Monitor to generate a plain ESP32 application binary file and display the application output, run the following terminal command:

    idf.py flash monitor 
    CODE

The image is built and the output indicates that the image signature verification failed which is expected since you disabled signing binaries during build and thus uploaded an unsigned application where the signature cannot be verified. Next, integrate the SignServer signing functionality with the ESP32 by using the provided script in the next step that fetches the plain code application and enables the connection to SignServer.

Step 2 - Generate signature and attach to application image

To integrate SignServer signing into the ESP32 application build process, use the provided Python script to sign the application binary file. The script performs the following:

  • Opens and reads the unsigned generated binary file (OTABasicUnsigned.bin).
  • Sends the file to SignServer by selecting the PlainSigner worker.
  • SignServer hashes the file and generates the signature.
  • Receives the signature and verifies the generated signature with the ESP signer public key.
  • Adds the signature to the binary file and stores it in the build environment of the ESP-IDF framework.

To generate and attach the signature, run the following Python script after a successful build with the idf.py build command:

sign application binary

import hashlib
import struct
import requests
import ecdsa

files = {
    'workerName': (None, 'PlainSigner'),
    'file': open('OTABasicUnsigned.bin', 'rb'),
    'REQUEST_METADATA.SIGNATUREALGORITHM': (None, 'SH256withECDSA'),
    'REQUEST_METADATA.RESPONSE_ENCODED': (None, 'true'),
}

def gen_image_signserver():
    # Open application binary file and read binaries and
    with open('OTABasicUnsigned.bin', "rb") as rawFile:
        binary_content = rawFile.read()
        rawFile.close()

    # Get Binary file and send to signserver
    response = requests.post('http://localhost/signserver/process', files=files)
    with open('OTABasicSignaturServer.bin', 'wb') as sigFile:
        sigFile.write(response.content)
        sigFile.close()
    # Verify Signature
    with open('ESPSignerPub.pem','r') as keyfile:
        vk = ecdsa.VerifyingKey.from_pem(keyfile.read())
        r,s = ecdsa.util.sigdecode_der(response.content,vk.pubkey.order)
        sig_string = ecdsa.util.sigencode_string(r,s,vk.pubkey.order)
        vk.verify(sig_string,binary_content,hashlib.sha256)
    # Attach signature to Application
    with open ('OTABasicSigned.bin', "wb") as binFile:
        binFile.write(binary_content)
        binFile.write(struct.pack("I", 0))
        binFile.write(sig_string)
        binFile.close()

if __name__ == '__main__':
    gen_image_signserver()
PY

The script performs the signature creation and provides the signed package to be loaded into the ESP32.

Step 3 - Upload image and verify signature with ESP32

To upload the image and display the application output and verify the signature, run the following terminal command:

idf.py -p <yourPortID>  monitor 
CODE

The boot process prints something like the following to display that the application image signature is verified and the application is loaded:


You have now implemented a trustworthy code signing process for ESP32 by integrating SignServer code signing into the boot process and thus secured the code-signing keys.

Next steps

In this tutorial, you learned how code signing can be integrated into the ESP32 software build process and code signing signatures can be generated with SignServer for ESP32 firmware, integrating with EJBCA to issue the required code-signing certificates.  

To find out more about how to use SignServer together with Cosign to create signed container images, see Tutorial - SignServer Container Signing with Cosign.

To learn how to get started with code signing in SignServer, see the Code Signing Technical How-to.