copensslpublic-keyelliptic-curvetpm

How to create ECC public key in OpenSSL's "EVP_PKEY" format from the TPM-specific "TPMT_PUBLIC" data structure?


I'm working on a project where I need to work with a TPM (Trusted Platform Module) and I need to verify an ECC signature that was generated by the TPM. The verification needs to be done in software , i.e. outside of TPM, and we want to use OpenSSL (libcrypto) for this purpose. For this reason, the ECC public key of the TPM must be converted from the TPM-specific TPMT_PUBLIC data structure, in which it is provided by the TPM (cannot change this!), to the EVP_PKEY format that can be used with the OpenSSL (libcrypto) API for signature validations.


The TPM provides the ECC public key in the following structure, which is not defined by me, but was defined by the TPM software stack (TSS), and which I have to work with:

Generic public key structure, that I get from the TPM (only showing the relevant field):

/* Definition of TPMT_PUBLIC Structure */
typedef struct TPMT_PUBLIC TPMT_PUBLIC;
struct TPMT_PUBLIC {
    /* ... */
    TPMU_PUBLIC_ID unique; /* For an asymmetric key this would be the public key. */
};

The key-type specific structure that is in the "unique" field from above:

/* Definition of TPMU_PUBLIC_ID Union <INOUT S> */
typedef union TPMU_PUBLIC_ID TPMU_PUBLIC_ID;
union TPMU_PUBLIC_ID {
    TPM2B_DIGEST keyedHash;
    TPM2B_DIGEST sym;
    TPM2B_PUBLIC_KEY_RSA rsa;
    TPMS_ECC_POINT ecc; /* <-- This is what is used for ECC key !!! */
    TPMS_DERIVE derive;
};

The actual ECC public key, as provided by the TPM, as in the "ecc" field from above:

/* Definition of ECC TPMS_ECC_POINT Structure */
typedef struct TPMS_ECC_POINT TPMS_ECC_POINT;
struct TPMS_ECC_POINT {
    TPM2B_ECC_PARAMETER x; /* X coordinate */
    TPM2B_ECC_PARAMETER y; /* Y coordinate */
};

The x and y coordinates are stored as:

/* Definition of ECC TPM2B_ECC_PARAMETER Structure */
typedef struct TPM2B_ECC_PARAMETER TPM2B_ECC_PARAMETER;
struct TPM2B_ECC_PARAMETER {
    UINT16 size;
    BYTE buffer[128];
};

More details (not my code) can be found here:
https://github.com/tpm2-software/tpm2-tss/blob/master/include/tss2/tss2_tpm2_types.h


So, effectively, we get the "x" and "y" coordinates of the ECC public key out of the TPM, each one provided as a "raw" byte array. This is not some format, like PEM-encoded public key, that OpenSSL can use directly! So how can I convert this into an EVP_PKEY instance, which OpenSSL will be able to process, e.g. by EVP_PKEY_verify() function? Unfortunately, I have not been able to find an example of how an ECC key in EVP_PKEY format can be constructed from "raw" values (x and y coordinates). Neither in the OpenSSL docs nor elsewhere...

I have already successfully constructed an RSA key as EVP_PKEY, from the "raw" RSA modulus and exponent values, so I think it should be possible to do a very similar thing, but with an ECC key:
https://pastebin.com/VBdVBt2C

Thank you for any advice!


Solution

  • It's not well documented, but the o2i_ECPublicKey can be used to convert an octet stream (i.e. a sequence of bytes) to an EC public key of type EC_KEY.

    The ec.h header file contains the following:

    /** Decodes a ec public key from a octet string.
     *  \param  key  a pointer to a EC_KEY object which should be used
     *  \param  in   memory buffer with the encoded public key
     *  \param  len  length of the encoded public key
     *  \return EC_KEY object with decoded public key or NULL if an error
     *          occurred.
     */
    EC_KEY *o2i_ECPublicKey(EC_KEY **key, const unsigned char **in, long len);
    

    The first parameter needs to be initialized, for example with EC_KEY_new_by_curve_name. The input buffer is expected to be a single byte with the value 4, followed by the X coordinate of the point then the Y coordinate of the point.

    For example:

    TPMS_ECC_POINT *tpms_point = /* load as appropriate */
    // assumes curve secp521r1, set as appropriate
    EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp521r1);  
    
    int keylen = tpms_point->x.size + tpms_point->y.size + 1;
    unsigned char buffer[keylen];
    const unsigned char *p = buf;
    
    buffer[0] = 4;
    memcpy(buffer + 1, tpms_point->x.buffer, tpms_point->x.size);
    memcpy(buffer + 1 + tpms_point->x.size, tpms_point->y.buffer, tpms_point->y.size);
    
    if (!o2i_ECPublicKey(&key, &p, keylen)) {
        printf("key load failed\n");
    }
    

    Once you've got the EC_KEY loaded, you can use EVP_PKEY_new to create a new EVP_PKEY, then use EVP_PKEY_set1_EC_KEY to load the EC_KEY into the EC_KEY.