Digital Signature

The Digital Signature (DS) module provides hardware acceleration of signing messages based on RSA. It uses pre-encrypted parameters to calculate a signature. The parameters are encrypted using HMAC as a key-derivation function. In turn, the HMAC uses eFuses as input key. The whole process happens in hardware so that neither the decryption key for the RSA parameters nor the input key for the HMAC key derivation function can be seen by the software while calculating the signature.

Look into the ESP32-S2 Technical Reference Manual (PDF) for more detailed information about the involved hardware during the signature calculation process and the used registers.

Private Key Parameters

The private key parameters for the RSA signature are stored in flash. To prevent unauthorized access, they are AES-encrypted. The HMAC module is used as a key-derivation function to calculate the AES encryption key for the private key parameters. In turn, the HMAC module uses a key from the eFuses key block which can be read-protected to prevent unauthorized access as well.

Upon signature calculation invocation, the software only specifies which eFuse key to use, the corresponding eFuse key purpose, the location of the encrypted RSA parameters and the message.

Key Generation

Both the HMAC key and the RSA private key have to be created and stored before the DS module can be used. This needs to be done in software on the ESP32-S2 or alternatively on a host. For this context, the IDF provides esp_efuse_write_block() to set the HMAC key and esp_hmac_calculate() to encrypt the private RSA key parameters.

Instructions on how to calculate and assemble the private key parameters are described in the ESP32-S2 Technical Reference Manual.

Signature Calculation with IDF

For thorough information about involved registers and the workflow, please have a look at the ESP32-S2 Technical Reference Manual.

Three parameters need to be prepared to calculate the digital signature:

  1. the eFuse key block ID which is used as key for the HMAC,

  2. the location of the encrypted private key parameters,

  3. and the message to be signed.

Since the signature calculation takes some time, there are two possible API versions to use in IDF. The first one is esp_ds_sign() and simply blocks until the calculation is finished. If software needs to do something else during the calculation, esp_ds_start_sign() can be called, followed by periodic calls to esp_ds_is_busy() to check when the calculation has finished. Once the calculation has finished, esp_ds_finish_sign() can be called to get the resulting signature.

Note

Note that this is only the basic DS building block, the message length is fixed. To create signatures of arbitrary messages, the input is normally a hash of the actual message, padded up to the required length. An API to do this is planned in the future.

API Reference

Functions

esp_err_t esp_ds_sign(const void *message, const esp_ds_data_t *data, hmac_key_id_t key_id, void *signature)

Sign the message.

This function is a wrapper around esp_ds_finish_sign() and esp_ds_start_sign(), so do not use them in parallel. It blocks until the signing is finished and then returns the signature.

Note

This function locks the HMAC, SHA, AES and RSA components during its entire execution time.

Return

  • ESP_OK if successful, the signature was written to the parameter signature.

  • ESP_ERR_INVALID_ARG if one of the parameters is NULL or data->rsa_length is too long or 0

  • ESP_ERR_HW_CRYPTO_DS_HMAC_FAIL if there was an HMAC failure during retrieval of the decryption key

  • ESP_ERR_NO_MEM if there hasn’t been enough memory to allocate the context object

  • ESP_ERR_HW_CRYPTO_DS_INVALID_KEY if there’s a problem with passing the HMAC key to the DS component

  • ESP_ERR_HW_CRYPTO_DS_INVALID_DIGEST if the message digest didn’t match; the signature is invalid.

  • ESP_ERR_HW_CRYPTO_DS_INVALID_PADDING if the message padding is incorrect, the signature can be read though since the message digest matches.

Parameters
  • message: the message to be signed; its length is determined by data->rsa_length

  • data: the encrypted signing key data (AES encrypted RSA key + IV)

  • key_id: the HMAC key ID determining the HMAC key of the HMAC which will be used to decrypt the signing key data

  • signature: the destination of the signature, should be (data->rsa_length + 1)*4 bytes long

esp_err_t esp_ds_start_sign(const void *message, const esp_ds_data_t *data, hmac_key_id_t key_id, esp_ds_context_t **esp_ds_ctx)

Start the signing process.

This function yields a context object which needs to be passed to esp_ds_finish_sign() to finish the signing process.

Note

This function locks the HMAC, SHA, AES and RSA components, so the user has to ensure to call esp_ds_finish_sign() in a timely manner.

Return

  • ESP_OK if successful, the ds operation was started now and has to be finished with esp_ds_finish_sign()

  • ESP_ERR_INVALID_ARG if one of the parameters is NULL or data->rsa_length is too long or 0

  • ESP_ERR_HW_CRYPTO_DS_HMAC_FAIL if there was an HMAC failure during retrieval of the decryption key

  • ESP_ERR_NO_MEM if there hasn’t been enough memory to allocate the context object

  • ESP_ERR_HW_CRYPTO_DS_INVALID_KEY if there’s a problem with passing the HMAC key to the DS component

Parameters
  • message: the message to be signed; its length is determined by data->rsa_length

  • data: the encrypted signing key data (AES encrypted RSA key + IV)

  • key_id: the HMAC key ID determining the HMAC key of the HMAC which will be used to decrypt the signing key data

  • esp_ds_ctx: the context object which is needed for finishing the signing process later

bool esp_ds_is_busy(void)

Return true if the DS peripheral is busy, otherwise false.

Note

Only valid if esp_ds_start_sign() was called before.

esp_err_t esp_ds_finish_sign(void *signature, esp_ds_context_t *esp_ds_ctx)

Finish the signing process.

Return

  • ESP_OK if successful, the ds operation has been finished and the result is written to signature.

  • ESP_ERR_INVALID_ARG if one of the parameters is NULL

  • ESP_ERR_HW_CRYPTO_DS_INVALID_DIGEST if the message digest didn’t match; the signature is invalid.

  • ESP_ERR_HW_CRYPTO_DS_INVALID_PADDING if the message padding is incorrect, the signature can be read though since the message digest matches.

Parameters
  • signature: the destination of the signature, should be (data->rsa_length + 1)*4 bytes long

  • esp_ds_ctx: the context object retreived by esp_ds_start_sign()

esp_err_t esp_ds_encrypt_params(esp_ds_data_t *data, const void *iv, const esp_ds_p_data_t *p_data, const void *key)

Encrypt the private key parameters.

Return

  • ESP_OK if successful, the ds operation has been finished and the result is written to signature.

  • ESP_ERR_INVALID_ARG if one of the parameters is NULL or p_data->rsa_length is too long

Parameters
  • data: Output buffer to store encrypted data, suitable for later use generating signatures. The allocated memory must be in internal memory and word aligned since it’s filled by DMA. Both is asserted at run time.

  • iv: Pointer to 16 byte IV buffer, will be copied into ‘data’. Should be randomly generated bytes each time.

  • p_data: Pointer to input plaintext key data. The expectation is this data will be deleted after this process is done and ‘data’ is stored.

  • key: Pointer to 32 bytes of key data. Type determined by key_type parameter. The expectation is the corresponding HMAC key will be stored to efuse and then permanently erased.

Structures

struct esp_digital_signature_data

Encrypted private key data. Recommended to store in flash in this format.

Note

This struct has to match to one from the ROM code! This documentation is mostly taken from there.

Public Members

esp_digital_signature_length_t rsa_length

RSA LENGTH register parameters (number of words in RSA key & operands, minus one).

Max value 127 (for RSA 4096).

This value must match the length field encrypted and stored in ‘c’, or invalid results will be returned. (The DS peripheral will always use the value in ‘c’, not this value, so an attacker can’t alter the DS peripheral results this way, it will just truncate or extend the message and the resulting signature in software.)

Note

In IDF, the enum type length is the same as of type unsigned, so they can be used interchangably. See the ROM code for the original declaration of struct ets_ds_data_t.

uint8_t iv[ESP_DS_IV_LEN]

IV value used to encrypt ‘c’

uint8_t c[ESP_DS_C_LEN]

Encrypted Digital Signature parameters. Result of AES-CBC encryption of plaintext values. Includes an encrypted message digest.

struct esp_ds_p_data_t

Plaintext parameters used by Digital Signature.

Not used for signing with DS peripheral, but can be encrypted in-device by calling esp_ds_encrypt_params()

Note

This documentation is mostly taken from the ROM code.

Public Members

uint32_t Y[4096 / 32]

RSA exponent.

uint32_t M[4096 / 32]

RSA modulus.

uint32_t Rb[4096 / 32]

RSA r inverse operand.

uint32_t M_prime

RSA M prime operand.

esp_digital_signature_length_t length

RSA length.

Macros

ESP_ERR_HW_CRYPTO_DS_BASE

Starting number of HW cryptography module error codes

ESP_ERR_HW_CRYPTO_DS_HMAC_FAIL

HMAC peripheral problem

ESP_ERR_HW_CRYPTO_DS_INVALID_KEY

given HMAC key isn’t correct, HMAC peripheral problem

ESP_ERR_HW_CRYPTO_DS_INVALID_DIGEST

message digest check failed, result is invalid

ESP_ERR_HW_CRYPTO_DS_INVALID_PADDING

padding check failed, but result is produced anyway and can be read

ESP_DS_IV_LEN
ESP_DS_C_LEN

Type Definitions

typedef struct esp_ds_context esp_ds_context_t
typedef struct esp_digital_signature_data esp_ds_data_t

Encrypted private key data. Recommended to store in flash in this format.

Note

This struct has to match to one from the ROM code! This documentation is mostly taken from there.

Enumerations

enum esp_digital_signature_length_t

Values:

ESP_DS_RSA_1024 = (1024 / 32) - 1
ESP_DS_RSA_2048 = (2048 / 32) - 1
ESP_DS_RSA_3072 = (3072 / 32) - 1
ESP_DS_RSA_4096 = (4096 / 32) - 1