tpm2-software / tpm2-pytss

Python bindings for TSS
https://tpm2-pytss.readthedocs.io/en/latest/
BSD 2-Clause "Simplified" License
61 stars 45 forks source link

What is the key creation template used when FAPI.create_key is called? #253

Open gaetanww opened 2 years ago

gaetanww commented 2 years ago

I want to use a primary key with both ESAPI and FAPI with the following workflow:

  1. create primary key in ESAPI and perform some operations
  2. recreate key in FAPI and use it there

However the key created in FAPI and ESAPI are not the same. I assume that's because the templates used during key creation are different. However, I can't find how to make them match.

Here's my test code:

from tpm2_pytss import *
tcti = 'swtpm'
FAPIConfig(profile_name='P_ECCP256SHA256', tcti=tcti, ek_cert_less='yes')
# I create a key in FAPI to retrieve its template
with FAPI() as fapi:
    fapi.provision()
    fapi.create_key('HE/KEY', 'system, sign, restricted')
    (blob_key, _) = fapi.get_esys_blob('HE/KEY')
# Now I create a new key with the same pub value
with ESAPI(tcti=TCTILdr(tcti)) as esapi:
    old_key = esapi.load_blob(blob_key)
    old_pub = esapi.read_public(old_key)[0]
    esapi.flush_context(old_key)
    # this deep copies the template
    new_pub = TPM2B_PUBLIC.unmarshal(old_pub.marshal())[0]
    # I assume the original template had empty unique
    new_pub.publicArea.unique.ecc.x = b''
    new_pub.publicArea.unique.ecc.y = b''
    new_key_handle, new_key_pub, _, _, _ = esapi.create_primary(TPM2B_SENSITIVE_CREATE(), new_pub, ESYS_TR.ENDORSEMENT, '', '')
    # This fails but I expected it to pass
    assert(bytes(new_key_pub.publicArea.unique.ecc.x) == bytes(old_pub.publicArea.unique.ecc.x))
whooo commented 2 years ago

Does have to be a primary key? I suspect that FAPI.create_key doesn't create a primary key. My recommendation would be to create and store the key with FAPI and use get_esys_blob when you need to use it with ESAPI

gaetanww commented 2 years ago

I suspect that FAPI.create_key doesn't create a primary key.

It does if the path of the key is directly under a hierarchy (e.g. '/HE/my-key'). There's a mention of it in the specs but it's a bit underdocumented. Anyway, it's present in the C code.

My recommendation would be to create and store the key with FAPI and use get_esys_blob when you need to use it with ESAPI

I was trying to avoid that to not have the FAPI dependency for the first step. Another solution would maybe be to FAPI.Import a primary key created with ESAPI. Do you think #240 would allow exporting a key from ESAPI and import it to FAPI

whooo commented 2 years ago

I suspect that FAPI.create_key doesn't create a primary key.

It does if the path of the key is directly under a hierarchy (e.g. '/HE/my-key'). There's a mention of it in the specs but it's a bit underdocumented. Anyway, it's present in the C code.

Ah, I see.

My recommendation would be to create and store the key with FAPI and use get_esys_blob when you need to use it with ESAPI

I was trying to avoid that to not have the FAPI dependency for the first step. Another solution would maybe be to FAPI.Import a primary key created with ESAPI. Do you think #240 would allow exporting a key from ESAPI and import it to FAPI

Long term I hope so, my plan is to have something the implements parts of the JSON format, those without FAPI requirements, but that will probably take a while.

    fapi.create_key('HE/ZAK', 'system, sign, restricted')
    (blob_key, _) = fapi.get_esys_blob('HE/KEY')

Just to verify, shouldn't HE/KEY be HE/ZAK in this case?

gaetanww commented 2 years ago

Just to verify, shouldn't HE/KEY be HE/ZAK in this case?

Yes you're right, I corrected it. It was right when I tested it though :)

whooo commented 2 years ago

I was able to get your example working by using fapi.get_tpm_blobs instead of get_esys_blob, I'm not sure why fapi.get_esys_blob doesn't work tho

williamcroberts commented 2 years ago

What does fapi.get_esys_blob return? Did you check return index 0 for the type, it should either be a FAPI_ESYSBLOB_CONTEXTLOAD or a FAPI_ESYSBLOB_DESERIALIZE which need to be handled differently.

load_blob is designed to handle that, but it has a default selector set to FAPI_ESYSBLOB_CONTEXTLOAD, my guess is it's handing back the FAPI_ESYSBLOB_DESERIALIZE which needs the ESYS_TR unmarshal called on it.

gaetanww commented 2 years ago

I have update the code but I still get the same error.

Here's the new code:

from tpm2_pytss import *
tcti = 'swtpm'
FAPIConfig(profile_name='P_ECCP256SHA256', tcti=tcti, ek_cert_less='yes')
# I create a key in FAPI to retrieve its template
with FAPI() as fapi:
    fapi.provision()
    fapi.create_key('HE/KEY', 'system, sign, restricted')
    blob_key, blob_slct = fapi.get_esys_blob('HE/KEY')
# Now I create a new key with the same pub value
with ESAPI(tcti=TCTILdr(tcti)) as esapi:
    old_key = esapi.load_blob(blob_key, blob_slct)
    old_pub = esapi.read_public(old_key)[0]
    esapi.flush_context(old_key)
    # this deep copies the template
    new_pub = TPM2B_PUBLIC.unmarshal(old_pub.marshal())[0]
    # I assume the original template had empty unique
    new_pub.publicArea.unique.ecc.x = b''
    new_pub.publicArea.unique.ecc.y = b''
    new_key_handle, new_key_pub, _, _, _ = esapi.create_primary(TPM2B_SENSITIVE_CREATE(), new_pub, ESYS_TR.ENDORSEMENT, '', '')
    # This fails but I expected it to pass
    assert(bytes(new_key_pub.publicArea.unique.ecc.x) == bytes(old_pub.publicArea.unique.ecc.x))

Another strange thing is that the FAPI template contains an error value. I'm not sure if that's supposed to happen:

In [31]: TPM2_ALG.to_string(old_pub.publicArea.parameters.eccDetail.symmetric.mode.sym)
Out[31]: 'TPM2_ALG.ERROR'
gaetanww commented 2 years ago

I was able to get your example working by using fapi.get_tpm_blobs instead of get_esys_blob, I'm not sure why fapi.get_esys_blob doesn't work tho

Oh you did, that's great :) Could you paste your snippet with the tpm blobs?

williamcroberts commented 2 years ago

TPM2_ALG.ERROR is 0. So when a template is initialized to 0, the TPM if it reads a spot it expected to be a valid algorithm will complain.

If you want a deep copy, you can just do:

new_pub = TPM2B_PUBLIC(old_pub)

For why theyre not equal, have you thought about modifying tpm2-tss to dump the template fields out and ensure it's working as expected?

You could modify Fapi_CreateKey.c to add some print statements or something. I could try this later if I have time. However, alas, at the moment I do not.

williamcroberts commented 2 years ago

You could also dump it with the pcap tcti and wireshark it, but I don't think it walks through the individual command fields yet. Only the common header.

gaetanww commented 2 years ago

TPM2_ALG.ERROR is 0. So when a template is initialized to 0, the TPM if it reads a spot it expected to be a valid algorithm will complain.

Ok, thanks for the info. Does that mean FAPI creates keys with invalid algorithms? This key was created with 'system, sign, restricted'.

For why theyre not equal, have you thought about modifying tpm2-tss to dump the template fields out and ensure it's working as expected?

You could modify Fapi_CreateKey.c to add some print statements or something. I could try this later if I have time. However, alas, at the moment I do not.

Yes, I guess that's the next step. Thanks for helping me! :)

whooo commented 2 years ago

I was able to get your example working by using fapi.get_tpm_blobs instead of get_esys_blob, I'm not sure why fapi.get_esys_blob doesn't work tho

Oh you did, that's great :) Could you paste your snippet with the tpm blobs?

Here it is, I mostly added some code. I don't remember why get_tpm_blobs returns cdata, it should probably return the types.

from tpm2_pytss import *
import sys
tcti = 'swtpm'
FAPIConfig(profile_name='P_ECCP256SHA256', tcti=tcti, ek_cert_less='yes')
# I create a key in FAPI to retrieve its template
with FAPI() as fapi:
    fapi.provision()
    sys.stderr.write("fapi create\n")
    fapi.create_key('HE/KEY', 'system, sign, restricted')
    sys.stderr.write("fapi post create\n")
    (blob_key, _) = fapi.get_esys_blob('HE/KEY')
    (fpub, fpriv, _) = fapi.get_tpm_blobs("HE/KEY")
# Now I create a new key with the same pub value
with ESAPI(tcti=TCTILdr(tcti)) as esapi:
    old_key = esapi.load_blob(blob_key)
    old_pub = esapi.read_public(old_key)[0]
    esapi.flush_context(old_key)
    old_pub = TPM2B_PUBLIC(_cdata=fpub)
    # this deep copies the template
    new_pub = TPM2B_PUBLIC.unmarshal(old_pub.marshal())[0]
    # I assume the original template had empty unique
    new_pub.publicArea.unique.ecc.x = b''
    new_pub.publicArea.unique.ecc.y = b''
    sys.stderr.write("esapi create\n")
    new_key_handle, new_key_pub, _, _, _ = esapi.create_primary(TPM2B_SENSITIVE_CREATE(), new_pub, ESYS_TR.ENDORSEMENT, '', '')
    sys.stderr.write("esapi post create\n")
    # This fails but I expected it to pass
    print(new_key_pub.publicArea.unique.ecc.x)
    print(old_pub.publicArea.unique.ecc.x)
    assert(new_key_pub.publicArea.authPolicy == old_pub.publicArea.authPolicy)
    assert(new_key_pub.publicArea.unique.ecc.x.size == old_pub.publicArea.unique.ecc.x.size)
    assert(new_key_pub.publicArea.unique.ecc.x == old_pub.publicArea.unique.ecc.x)
    #assert(new_pub.publicArea.objectAttributes == old_pub.publicArea.objectAttributes)
williamcroberts commented 2 years ago

Thats odd that it's returning the raw cdata types, I don't think that's right. Also, the return signature of Tuple[Any, Any, Str] is suspect. It should really be Tuple[TPM2B_PUBLIC, TPM2B_PRIVATE, str ].

The tests could also be fortified to use them through esapi.

whooo commented 2 years ago

@gaetanww, are you happy with https://github.com/tpm2-software/tpm2-pytss/commit/85cd8724de6a9bb966419caaefb9f9ecd7ea1f9f as a solution to this issue?

gaetanww commented 2 years ago

I think the original issue about the template is solved.

However, we unearthed a bug in the get_esys_blob -> load_blob workflow. So I suggest we could close this issue and open a new one, either here or in tpm2-tss or both. What do you think?

whooo commented 2 years ago

I think the original issue about the template is solved.

However, we unearthed a bug in the get_esys_blob -> load_blob workflow. So I suggest we could close this issue and open a new one, either here or in tpm2-tss or both. What do you think?

Have you tried reproducing the bug using the C API?

gaetanww commented 2 years ago

No I haven't, unfortunately I don't have the bandwidth for the next few weeks, sorry...

whooo commented 2 years ago

I was able to recreate the issue using the C API (3.1.0), https://gist.github.com/whooo/d18c4ae5460ae22b5a71c281f6b0cfd0

I was also able to reproduce the issue using tpm2-tss master.