espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.32k stars 7.2k forks source link

Example of how to do on-chip key generation with the DS Peripheral? (IDFGH-9985) #11270

Closed torntrousers closed 1 year ago

torntrousers commented 1 year ago

I'm looking at how to do key generation for the Digital Signature Peripheral for use with Mutual TLS connections.

The doc says:

Both the HMAC key and the RSA private key have to be created and stored before the DS peripheral can be used. This needs to be done in software on the ESP32-S2 or alternatively on a host. ... You can find instructions on how to calculate and assemble the private key parameters in ESP32-S2 Technical Reference Manual

which points to here: https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf#digsig

Its quite complicated and hard to understand!

Is there any code sample available that shows how to do this on-chip key generation in code running on the ESP32?

chipweinberger commented 1 year ago

see my code here

https://github.com/espressif/esp_secure_cert_mgr/issues/8

chipweinberger commented 1 year ago

More code for reference.

You'll need to adapt this but it should still be useful.

include "esp_ds.h"
#include "esp_efuse.h"

#include "mbedtls/rsa.h"
#include "mbedtls/rsa_internal.h"
#include "mbedtls/platform_util.h"
#include "mbedtls/error.h"
#include "mbedtls/sha256.h"
#include "mbedtls/bignum.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"

#include "jcommon/system/pd_freertos_utils.h"
#include "jcommon/utils/pd_utils.h"
#include "jcommon/utils/pd_error.h"
#include "jcommon/logs/pd_log.h"
#include "jcommon/logs/pd_verbose.h"

#include "jcommon/hardware_info/pd_efuse.h"
#include "jcommon/hardware_info/pd_efuse_utils.h"
#include "jcommon/hardware_info/pd_hardware_info.h"
#include "jcommon/logs/pd_verbose.h"

#include "pd_espds.h"

static uint16_t TAGn = 0;
static const char* TAG = "pd espds";

// Forward Define 
void pd_espds_virtual_keygen_if_needed(pd_error_t* error);

static void print_mpi(const mbedtls_mpi* mpi, const char* name)
{
    size_t olen = 0;
    uint32_t len = MBEDTLS_MPI_MAX_SIZE * 4 + 1;
    char* pp = (char*)calloc(1, len);
    mbedtls_mpi_write_string(mpi, 16, pp, len, &olen);
    printf("%s: %s\n", name, pp);
    free(pp);
}

void pd_mbedtls_rsa_keygen(mbedtls_rsa_context* rsa, pd_error_t* error)
{
    pd_err_unset(error);

    bool twd_dis = false;

    // allocations
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctrDrbg;
    mbedtls_entropy_init( &entropy );
    mbedtls_ctr_drbg_init( &ctrDrbg );
    {
        ESP_LOGI(TAG, "rsa keygen");

        // the personalize string just adds a litte
        // bit of "guarenteed" entropy, as a backup.
        const char *personalize = "12345";

        // Disable Idle Task WDT
        // keygen takes a long time.
        eTry(,pd_idle_task_wdt_disable,(error));
        twd_dis = true;

        int ret = 0;
        mbTry(mbedtls_ctr_drbg_seed,( &ctrDrbg, mbedtls_entropy_func, &entropy,
            (uint8_t*) personalize, strlen(personalize)));

        int keySize = 4096;
        int exponent = 65537;
        mbTry(mbedtls_rsa_gen_key,(rsa, mbedtls_ctr_drbg_random, &ctrDrbg,
            keySize, exponent));

        mbTry(mbedtls_rsa_complete,(rsa));

        ESP_LOGI(TAG, "rsa keygen complete");

        pd_err_success(error);
    }
end:
    mbedtls_ctr_drbg_free(&ctrDrbg);
    mbedtls_entropy_free(&entropy);
    if (twd_dis){pd_try_idle_task_wdt_enable();}
    return;
}

static void pd_mbedtls_rsa_rinv_mprime(const mbedtls_mpi* N, 
                                             mbedtls_mpi* rinv,
                                                uint32_t* mprime,
                                              pd_error_t* error)
{
    pd_err_unset(error);

    // allocations
    mbedtls_mpi rr, ls32, a;
    mbedtls_mpi_init(&rr);
    mbedtls_mpi_init(&ls32);
    mbedtls_mpi_init(&a);
    {
        ESP_LOGI(TAG, "calc rinv, mprime");

        // python equivalent code:
        //
        //    key_size = private_key.key_size # in bits
        //
        //    # calculate rinv == Rb
        //    rr = 1 << (key_size * 2)
        //    rinv = rr % pub_numbers.n # RSA r inverse operand
        //
        //    # calculate MPrime
        //    a = rsa._modinv(pub_numbers.n, 1 << 32)
        //    mprime = (a * -1) & 0xFFFFFFFF # RSA M prime operand

        int ret = 0;

        // rr = 1 << (key_size * 2) # in bits
        mbTry(mbedtls_mpi_lset,(&rr, 1));
        mbTry(mbedtls_mpi_shift_l,(&rr, sizeof(pd_4096_bit_t) * 8 * 2));

        // rinv = rr % rsa.N 
        mbTry(mbedtls_mpi_mod_mpi,(rinv, &rr, N));

        // ls32 = 1 << 32
        mbTry(mbedtls_mpi_lset,(&ls32, 1));
        mbTry(mbedtls_mpi_shift_l,(&ls32, 32));

        // a = rsa._modinv(N, 1 << 32)
        mbTry(mbedtls_mpi_inv_mod,(&a, N, &ls32));

        // a32 = a 
        uint32_t a32 = 0;
        mbTry(mbedtls_mpi_write_binary_le,(&a, (uint8_t*) &a32, sizeof(uint32_t)));

        // mprime
        *mprime = ((int32_t) a32 * -1) & 0xFFFFFFFF;

        //
        // Results
        //
        PD_LOGV_IF(TAG,
            print_mpi(N, "N");
            print_mpi(rinv, "RInv");
            printf("mprime: 0x%x\n", *mprime);
        );

        pd_err_success(error);
    }
end:
    // free
    mbedtls_mpi_free(&rr);
    mbedtls_mpi_free(&ls32);
    mbedtls_mpi_free(&a);
    return;
}

static void pd_mbedtls_rinv_mprime_to_ds_params(const mbedtls_mpi* D,
                                                const mbedtls_mpi* N,
                                                const mbedtls_mpi* rinv,
                                                          uint32_t mprime,
                                                  esp_ds_p_data_t* params,
                                                       pd_error_t* error)
{
    pd_err_unset(error);

    {
        ESP_LOGI(TAG, "create ds params");
        int ret = 0;
        mbTry(mbedtls_mpi_write_binary,(D,    (uint8_t*) params->Y, sizeof(params->Y)));
        mbTry(mbedtls_mpi_write_binary,(N,    (uint8_t*) params->M, sizeof(params->M)));
        mbTry(mbedtls_mpi_write_binary,(rinv, (uint8_t*) params->Rb, sizeof(params->Rb)));
        reverseBytes((uint8_t*) params->Y, sizeof(pd_4096_bit_t)); // big to little endian
        reverseBytes((uint8_t*) params->M, sizeof(pd_4096_bit_t)); // big to little endian
        reverseBytes((uint8_t*) params->Rb, sizeof(pd_4096_bit_t));// big to little endian
        params->M_prime = mprime;
        params->length = ESP_DS_RSA_4096;
        pd_err_success(error);
    }
end:
    return;
}

void pd_espds_keygen_if_needed(pd_error_t* error)
{
// call virtual equivalent if in virtual mode
#ifdef CONFIG_EFUSE_VIRTUAL
    pd_espds_virtual_keygen_if_needed(error);
    return;
#endif

    pd_err_unset(error);

    // allocations
    esp_ds_p_data_t* params = NULL;
    esp_ds_data_t* encrypted = NULL;
    mbedtls_mpi rinv;
    mbedtls_rsa_context rsa;
    mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V15, 0);
    mbedtls_mpi_init(&rinv);
    {

        ESP_LOGI(TAG, "keygen - burn if needed");

        bool hasFuses = esp_efuse_get_key_purpose(PD_EFUSE_BLK7_ESP_DS_HMAC) == 
            ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE;

        if(pd_hardware_is_developer_device() == false) {
            // already has fuses?
            if (hasFuses) {
                ESP_LOGI(TAG, "already has fuses. skiping.");
                pd_err_success(error);
                goto end;
            }
        } else {
            // See below. Developer devices use a hardcoded HMAC & IV so
            // they can rewrite RSA Cipher Iv as many times as needed.
            ESP_LOGW(TAG, "developer device. always write new keys");
        }

        // get RSA keypair
        if(pd_espds_privkey_temp_is_available()){
            // Perf: use keypair uploaded by host PC
            eTry(,pd_espds_privkey_temp_get,(&rsa, error));
        } else{
            // generate new keypair (takes 20-80 seconds)
            eTry(,pd_mbedtls_rsa_keygen,(&rsa, error));
        }   

        // rinv, mprime
        uint32_t mprime = 0;
        eTry(,pd_mbedtls_rsa_rinv_mprime,(&rsa.N, &rinv, &mprime, error));

        // alloc
        params = (esp_ds_p_data_t*) calloc_safe(sizeof(esp_ds_p_data_t), 1, HERE);

        // esp_ds params
        eTry(,pd_mbedtls_rinv_mprime_to_ds_params,(&rsa.D, &rsa.N, 
            &rinv, mprime, params, error));

        PD_LOGV_IF(TAG,
            print4096("Y", (const pd_4096_bit_t *) params->Y);
            print4096("M", (const pd_4096_bit_t *) params->M);
            print4096("Rb", (const pd_4096_bit_t *) params->Rb);
            printf("mprime 0x%x\n", params->M_prime);
        );

        // iv & hmac
        uint8_t iv[16];
        uint8_t hmac[32];
        esp_fill_random(iv, 16);
        esp_fill_random(hmac, 32);

        if (pd_hardware_is_developer_device()) {
            //   This is not cryptographically secure - at all.
            // Hardcoded HMAC & iv keys are more convenient for development. It lets us update the 
            // encrypted RSA Keys that are stored in flash. Without HMAC & iv being hardcoded, they're
            // otherwise burned to read-protected fuses, so re-encrypting new RSA keys becomes
            // impossible.
            //    See the ESP-DS documentation for details.
            ESP_LOGW(TAG, "using developer HMAC & iv");
            for(int i = 0; i < 16; i++) {iv[i] = i;}
            for(int i = 0; i < 32; i++) {hmac[i] = i;}
        } else {
            ESP_LOGI(TAG, "using random HMAC & iv");
            esp_fill_random(iv, 16);
            esp_fill_random(hmac, 32);
        }

        esp_efuse_block_t fuseBlock = PD_EFUSE_BLK7_ESP_DS_HMAC;

        //
        // Pubkey
        //

        eTry(,pd_espds_jfactory_set_pubkey_pem,(&rsa, error));

        //
        // Cipher IV (encrypted keys)
        //

        // allocate encrypted data
        encrypted = (esp_ds_data_t*) calloc_safe2(MALLOC_CAP_DMA, sizeof(esp_ds_data_t), 1, HERE);

        // aes encrypt RSA params with IV & HMAC keys
        sTry(esp_err_t err =,esp_ds_encrypt_params,(encrypted, iv, params, hmac));

        pd_espds_cipher_iv_t keys = {
            .cipher = {.bytes = encrypted->c, .len = ESP_DS_C_LEN },
            .iv = {.bytes = iv, .len = ESP_DS_IV_LEN },
            .fuseBlock = fuseBlock
        };

        // write to cipher iv partition
        eTry(,pd_espds_jfactory_cipher_iv_write,(keys, error));

        //
        // Burn Fuses (do this last, it is unreversible)
        //

        if (hasFuses && pd_hardware_is_developer_device()) {

            ESP_LOGW(TAG, "skip burning fuses. is developer device & fuses found.");

        } else {

            // print all keys
            pd_efuse_print_all_keys();

            ESP_LOGI(TAG, "burning fuseBlock: %u", fuseBlock);

            // burn hmac key into fuses
            sTry(err =,esp_efuse_write_key,(fuseBlock,
                ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE, hmac, 32));
        } 

        pd_err_success(error);
    }
end:
    // free
    free_safe((void**)&params);
    free_safe((void**)&encrypted);
    mbedtls_mpi_free(&rinv);
    mbedtls_rsa_free(&rsa);
    return;
}
torntrousers commented 1 year ago

Thanks so much Chip, those look very interesting, will go away and study them. The next step would be to use that key to create either a self signed certificate or a certificate signing request - you don;t happen to have code for that too?

chipweinberger commented 1 year ago

no, but use chatgpt. it should be familiar with mbedtls.

torntrousers commented 1 year ago

Ha, I had actually tried that just before and its quite good for the basic code of an mbedtls self sign cert, but didn't know how to get it signed by the ESP DS periperal.

chipweinberger commented 1 year ago

after my code to generate the keypair and configure the espds peripheral with the private key, you could call esp_ds_start_sign to generate a signature for the certificate data with the esp-ds peripheral. You could also do this step just using mbedtls and the private key still stored in ram.

And then somehow add that signature to the certificate itself, using mbedtls.

chipweinberger commented 1 year ago

A certificate is just ASN.1 encoded text data. The certificate signature is just appended after the certificate data, along with which algo was used to generate the signature. mbedtls should have functions for doing all of this.

Screenshot 2023-04-25 at 5 10 15 PM
torntrousers commented 1 year ago

RIght yes, it slipped my mind that the private key was there in memory not just in the DS Peripheral, so it all seems quite dooable. I do exactly this hand crafting the ASN1 for a certificate on a different MCU and with ECDSA instead of RSA. Will give it a try.

While you're here, would you have any thoughts on this question about the advantages of the DS Peripheral - https://esp32.com/viewtopic.php?f=13&t=33332

chipweinberger commented 1 year ago

With those, the keys could just be stored in an encrypted partition and no one could get at them, could they?

Indeed. But storing private key in ESP-DS is much more secure because the keys are entirely inaccessible to software. 'C' code usually has plenty of software exploits, and a motivated attacker can often gain cpu execution privileges.

Also, esp-ds is hardware accelerated, so it is much faster at generating signatures.

torntrousers commented 1 year ago

Hi @chipweinberger , can you give me a pointer where those jcommon includes come from in the example code you showed in https://github.com/espressif/esp-idf/issues/11270#issuecomment-1521371130?

chipweinberger commented 1 year ago

I haven't provided the code for then but you shouldn't need them! :)

The mbTry, sTry, eTry macros just check the error and goto end.

And the pd_error_t stuff is just how I do error handling. You can delete it.

AdityaHPatwardhan commented 1 year ago

@torntrousers We already have a a pip tool to do the chip generation on host machine https://pypi.org/project/esp-secure-cert-tool/

As for the on-chip key generation I think the sample code mention by @chipweinberger looks like it can do the job for you.

Basically the steps are as follows: 1) Generate the RSA key on chip. This requires some cryptographic library for key generation. mbedTLS is most popular and easy to use. 2) Generate an HMAC key randomly. 3) Feed to required data to esp_ds_encrypt_params and generate the cipher text. 4) Store the cipher text and feed it to esp_ds_sign for performing sign operation using the key.

If you are looking for doing the on-chip key generation for products then Espressif offers Secure Pre-provisioning service where modules are shipped with pre-programmed keys and certificates with all the relevant security features enabled.

AdityaHPatwardhan commented 1 year ago

Here is one more reference for the parameter encryption.

I am closing the issue since relevant information is already available in the above discussion. Please feel free to re-open the issue if you are still having any problem.

liliumjsn commented 5 months ago

Hi @chipweinberger ,how did you manage to generate and write the device certificate? There is no documentation at all for this. Is Espressif just promoting the pre-provision service? Because it doesn't make any sense to not have an on-device provisioning API for the esp_secure_cert utility.

AdityaHPatwardhan commented 5 months ago

@liliumjsn Please note, the ESP-IDF does not provide any mechanism for generating the private keys or certificates. It is provided by the crypto library. Hence you would find the documentation regarding generating the private keys or certificates in mbedTLS repository or any other relevant crypto library. esp_secure_cert utility is strictly used for managing the available private keys/certs or other private data and storing them in convenient manner with simple APIs for storage and retrieval. It does not offer any APIs related to the parameters generation.

liliumjsn commented 5 months ago

@AdityaHPatwardhan esp_secure_cert can only be used on the device to read ds storage data and not write them. In order to write data to ds partition you will either have to use a host and esp_secure_cert or generate manually tlv format data and write them to flash ds partition? Am I wrong?

AdityaHPatwardhan commented 5 months ago

@liliumjsn You are right, At the moment the write option is only supported through external python interface. The firmware at the moment is only able to read the data. We currently have a MR in progress that also enables the write operations for the esp_secure_cert_mgr. Once that is merged the firmware shall write to the esp_secure_cert_partition.

liliumjsn commented 5 months ago

@AdityaHPatwardhan Is this the latest we've come so far?