espressif/esp-aws-iot

[QUESTION] How to get Private Key in PEM format - Fleet Provisioning by CSR (CA-346)

Opened this issue · 2 comments

Checklist

  • Checked the issue tracker for similar issues to ensure this is not a duplicate.
  • Provided a clear description of your suggestion.
  • Included any relevant context or examples.

Issue or Suggestion Description

I'm trying to adapt the AWS provisioning library to use in my prototype. AWS uses a different library than MQTT and also for cryptographic functions, and I already have all my code implemented using the native MQTT library from the SDK-IDF. I tried to adapt the AWS library to use the standard MQTT library from the SDK-IDF, but I was unsuccessful. The documentation for the fleet provisioning steps lacks some information on how to send the payloads and I'm not that familiar with it. I then took a new approach, generating the certificates using the example, exporting them in PEM format and connecting to the broker using the default lib. It seemed to be the simplest way, but AWS again uses a different lib for PKCS11. I tried in several ways to adapt the code that generates the KEY pair to export the private key in PEM format and I couldn't. Below is the part of the code that I modified in order to try to extract the private key. However, this key is not valid. It doesn't work for me to connect to the broker, giving the following error:
E (33905) esp-tls-mbedtls: mbedtls_pk_parse_keyfile returned -0x8980
E (33905) esp-tls-mbedtls: Failed to set client pki context
E (33905) esp-tls-mbedtls: Failed to set client configurations, returned [0x8019] (ESP_ERR_MBEDTLS_PK_PARSE_KEY_FAILED)
If I try to use the function:
mbedtls_pk_parse_key(&key, (const unsigned char *)client_key, strlen(client_key) + 1, NULL, 0, NULL, 0);
If I save this key on my computer and run:
openssl ec -in eccert.pem -check
it returns the following:
read EC key
EC Key Invalid! 4072CE0102000000:error:0800007B:elliptic curve routines:ossl_ec_key_private_check:invalid private key:crypto/ec/ec_key.c:607:

This indicates that the key is invalid. If I generate a set of keys using the standard SDK-IDF lib, export it and pass it through the same function, it returns that it is a valid key. If anyone can give me some insight, I would be grateful. Here is the code I use to extract the key:
`static int extractEcPublicKey(CK_SESSION_HANDLE p11Session,
mbedtls_ecdsa_context *pEcdsaContext,
CK_OBJECT_HANDLE publicKey)
{
CK_ATTRIBUTE ecTemplate = {0};
int mbedtlsRet = -1;
CK_RV pkcs11ret = CKR_OK;
CK_BYTE ecPoint[67] = {0};
CK_FUNCTION_LIST_PTR pP11FunctionList;

mbedtls_ecdsa_init(pEcdsaContext);
mbedtls_ecp_group_init(&(pEcdsaContext->grp));

pkcs11ret = C_GetFunctionList(&pP11FunctionList);

if (pkcs11ret != CKR_OK)
{
    LogError(("Could not get a PKCS #11 function pointer."));
}
else
{
    ecTemplate.type = CKA_EC_POINT;
    ecTemplate.pValue = ecPoint;
    ecTemplate.ulValueLen = sizeof(ecPoint);
    pkcs11ret = pP11FunctionList->C_GetAttributeValue(p11Session, publicKey, &ecTemplate, 1);

    if (pkcs11ret != CKR_OK)
    {
        LogError(("Failed to extract EC public key. Could not get attribute value. "
                  "C_GetAttributeValue failed with %lu.",
                  pkcs11ret));
    }
}

if (pkcs11ret == CKR_OK)
{
    mbedtlsRet = mbedtls_ecp_group_load(&(pEcdsaContext->grp), MBEDTLS_ECP_DP_SECP256R1);

    if (mbedtlsRet != 0)
    {
        LogError(("Failed creating an EC key. "
                  "mbedtls_ecp_group_load failed: MbedTLS error = %d",
                  mbedtlsRet));
        pkcs11ret = CKR_FUNCTION_FAILED;
    }
    else
    {
        mbedtlsRet = mbedtls_ecp_point_read_binary(&(pEcdsaContext->grp), &(pEcdsaContext->Q), &ecPoint[2], ecTemplate.ulValueLen - 2);

        if (mbedtlsRet != 0)
        {
            LogError(("Failed reading EC public key point. "
                      "mbedtls_ecp_point_read_binary failed: MbedTLS error = %d",
                      mbedtlsRet));
            pkcs11ret = CKR_FUNCTION_FAILED;
        }
        else
        {
            // Buffer for storing the PEM-encoded private key.
            char pemBuffer[1600];
            memset(pemBuffer, 0, sizeof(pemBuffer));

            // Create a PK context to encode the private key.
            mbedtls_pk_context pkContext;
            mbedtls_pk_init(&pkContext);

            // Assign the ECDSA context to the PK context.
            mbedtlsRet = mbedtls_pk_setup(&pkContext, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));
            if (mbedtlsRet == 0)
            {
                // Copy the private key data to the PK context.
                mbedtls_ecp_keypair *ecKeyPair = mbedtls_pk_ec(pkContext);
                mbedtls_ecp_group_copy(&ecKeyPair->grp, &pEcdsaContext->grp);
                mbedtls_ecp_copy(&ecKeyPair->Q, &pEcdsaContext->Q);
                mbedtls_mpi_copy(&ecKeyPair->d, &pEcdsaContext->d);

                // Write the private key to PEM format.
                mbedtlsRet = mbedtls_pk_write_key_pem(&pkContext, (unsigned char *)pemBuffer, sizeof(pemBuffer));

                if (mbedtlsRet == 0)
                {
                    printf("Extracted Private Key (PEM):\n%s\n", pemBuffer);
                }
                else
                {
                    LogError(("Failed to write private key in PEM format. mbedtls_pk_write_key_pem failed: MbedTLS error = %d", mbedtlsRet));
                }

                mbedtls_pk_free(&pkContext);
            }
            else
            {
                LogError(("Failed to set up PK context. mbedtls_pk_setup failed: MbedTLS error = %d", mbedtlsRet));
            }
        }
    }
}

return mbedtlsRet;

}`

Hi @joaoceresoli
In the fleet provisioning example, device's provisioning claiming credentials are kept in the spiffs partition. And device's final key and CSR is generated on the device itself while claiming.
While claiming, private key is generated by generateKeyPairEC in pkcs11_operations.c and extractEcPublicKey is subsequently called to extract the pubkey out of the generated key.

Can you help provide more details on other changes you've made to the standard fleet provisioning example?

The only modification I actually tried to make is the one that extracts the Private Key in PEM format, but I was unsuccessful, because the certificate that is extracted from it is not valid. I understood how provisioning certificates are stored in the spiffs memory. My idea is to generate the certificates by claim (private key and certificate) and store them so that when I restart the device, I do not generate a new certificate each time. However, because I use pkcs11, I cannot access the private key that was generated. I tried to generate it first and then insert it into the pkcs11 module, but I was also unsuccessful.
Why do I want to do it this way?
I want the device to provision itself automatically, but I want to assign a policy later to that certificate generated on the iot core side. For internal reasons, this part of the procedure is interesting to be done manually, but for this I need the device to always use the same one to connect after generating a certificate.

My biggest difficulty is extracting the private key in PEM format, because that's how I'll be able to save it either in the nvs memory or in spiffs for later use. I can access the certificate generated by provisioning and it's already in PEM format, the only problem is the private key itself.