Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feedback] Function tr_from_tpmpublic is not easy enough to discover #298

Open
niooss-ledger opened this issue Dec 21, 2021 · 2 comments
Open

Comments

@niooss-ledger
Copy link
Contributor

Hello,

Here is some feedback about what it felt like to use tpm2-pytss 1.0.0-rc0 to perform operations such as reading a SRK or a NV index. Let's start with trying to read a Storage Root Key stored at 0x81000000. In a shell, this is easy:

$ tpm2_readpublic -c 0x81000000
...
name-alg:
  value: sha256
  raw: 0xb
attributes:
  value: fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|decrypt
  raw: 0x30072
type:
  value: rsa
  raw: 0x1
exponent: 65537
bits: 2048
...

But with tpm2-pytss, this is not as straightforward:

>>> from tpm2_pytss import *
>>> ectx = ESAPI()
>>> ectx.read_public(0x81000000)
Traceback (most recent call last):
...
TypeError: expected object_handle to be type ESYS_TR, got <class 'int'>

>>> ectx.read_public(ESYS_TR(0x81000000))
ERROR:esys:src/tss2-esys/esys_iutil.c:1095:esys_GetResourceObject() Error: Esys handle does not exist (70018). 
ERROR:esys:src/tss2-esys/api/Esys_ReadPublic.c:170:Esys_ReadPublic_Async() objectHandle unknown. ErrorCode (0x00070018) 
ERROR:esys:src/tss2-esys/api/Esys_ReadPublic.c:81:Esys_ReadPublic() Error in async function ErrorCode (0x00070018) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/git/tpm2_pytss/ESAPI.py", line 933, in read_public
    _chkrc(
  File "/git/tpm2_pytss/internal/utils.py", line 24, in _chkrc
    raise TSS2_Exception(rc)
tpm2_pytss.TSS2_Exception.TSS2_Exception: esapi:The ESYS_TR resource object is bad

Reading the documentation of read_public does not help. And it is actually wrong!

def read_public(
self,
object_handle: ESYS_TR,
session1: ESYS_TR = ESYS_TR.NONE,
session2: ESYS_TR = ESYS_TR.NONE,
session3: ESYS_TR = ESYS_TR.NONE,
) -> Tuple[TPM2B_PUBLIC, TPM2B_NAME, TPM2B_NAME]:
"""Invoke the TPM2_ReadPublic command.
This function invokes the TPM2_ReadPublic command in a one-call
variant. This means the function will block until the TPM response is
available
Args:
in_private (TPM2B_PRIVATE): The private portion of the object.
in_public (TPM2B_PUBLIC): The public portion of the object.
hierarchy (ESYS_TR): Hierarchy with which the object area is associated.
session1 (ESYS_TR): A session for securing the TPM command (optional). Defaults to ESYS_TR.NONE.
session2 (ESYS_TR): A session for securing the TPM command (optional). Defaults to ESYS_TR.NONE.
session3 (ESYS_TR): A session for securing the TPM command (optional). Defaults to ESYS_TR.NONE.

This documentation describes args in_private and in_public, which do not exist, and does not describe object_handle.

After more research, I discovered that tpm2-tss uses internal resource handlers and that tr_from_tpmpublic can be used to map a TPM handle to such a resource:

>>> object_handle = ectx.tr_from_tpmpublic(0x81000000)
>>> hex(object_handle)
'0x40418477'
>>> ectx.read_public(object_handle)
(<tpm2_pytss.types.TPM2B_PUBLIC object at 0x7f67583c8340>, <tpm2_pytss.types.TPM2B_NAME object at 0x7f67583d22b0>, <tpm2_pytss.types.TPM2B_NAME object at 0x7f67583d23a0>)
>>> pub = _[0].publicArea
>>> ectx.tr_close(object_handle)

>>> pub.type
0
>>> str(pub.type)
'error'

>>> hex(pub.objectAttributes)
'0xf57385ea'

>>> pub.parameters.rsaDetail.keyBits
2048
>>> bytes(pub.unique.rsa).hex()
...

The type and objectAttributes field contain wrong values (and I do not know why, but my intuition tells me this feels like a use-after-free issue) but I managed to recover my SRK in Python.

On the same TPM, I also have an EK certificate stored at NV index 0x01c00002. To read it using ectx.nv_read_public and ectx.nv_read, I also needed to call ectx.tr_from_tpmpublic(0x01c00002) first.

How are users of tpm2-pytss expected to discover they need to call tr_from_tpmpublic (and tr_close) when using objects through TPM handles? Currently neither the code, the documentation nor the tests contain references to these use-cases, which seem natural for people coming from tpm2-tools.

I suggest adding some words about this use-case in the documentation of functions read_public and nv_read_public (and maybe in every nv_... function too), such as:

object_handle(ESYS_TR): Handle of the object. It can be created from a TPM handle using tr_from_tpmpublic/tr_close.

I also suggest adding a test case which defines a SRK at a defined handle (such as 0x81000000) and reads it using ectx.tr_from_tpmpublic, and another one which does the same with a NV index. What do you think?

@whooo
Copy link
Contributor

whooo commented Dec 21, 2021

Here is some feedback about what it felt like to use tpm2-pytss 1.0.0-rc0 to perform operations such as reading a SRK or a NV index. Let's start with trying to read a Storage Root Key stored at 0x81000000. In a shell, this is easy:

$ tpm2_readpublic -c 0x81000000
...
name-alg:
  value: sha256
  raw: 0xb
attributes:
  value: fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|decrypt
  raw: 0x30072
type:
  value: rsa
  raw: 0x1
exponent: 65537
bits: 2048
...

But with tpm2-pytss, this is not as straightforward:

>>> from tpm2_pytss import *
>>> ectx = ESAPI()
>>> ectx.read_public(0x81000000)
Traceback (most recent call last):
...
TypeError: expected object_handle to be type ESYS_TR, got <class 'int'>

>>> ectx.read_public(ESYS_TR(0x81000000))
ERROR:esys:src/tss2-esys/esys_iutil.c:1095:esys_GetResourceObject() Error: Esys handle does not exist (70018). 
ERROR:esys:src/tss2-esys/api/Esys_ReadPublic.c:170:Esys_ReadPublic_Async() objectHandle unknown. ErrorCode (0x00070018) 
ERROR:esys:src/tss2-esys/api/Esys_ReadPublic.c:81:Esys_ReadPublic() Error in async function ErrorCode (0x00070018) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/git/tpm2_pytss/ESAPI.py", line 933, in read_public
    _chkrc(
  File "/git/tpm2_pytss/internal/utils.py", line 24, in _chkrc
    raise TSS2_Exception(rc)
tpm2_pytss.TSS2_Exception.TSS2_Exception: esapi:The ESYS_TR resource object is bad

tpm2_readpublic can make sensible assumptions about it's arguments , I don't think we can using ESAPI.

After more research, I discovered that tpm2-tss uses internal resource handlers and that tr_from_tpmpublic can be used to map a TPM handle to such a resource:
Yes, that's according the to the ESAPI specification.

>>> object_handle = ectx.tr_from_tpmpublic(0x81000000)
>>> hex(object_handle)
'0x40418477'
>>> ectx.read_public(object_handle)
(<tpm2_pytss.types.TPM2B_PUBLIC object at 0x7f67583c8340>, <tpm2_pytss.types.TPM2B_NAME object at 0x7f67583d22b0>, <tpm2_pytss.types.TPM2B_NAME object at 0x7f67583d23a0>)
>>> pub = _[0].publicArea
>>> ectx.tr_close(object_handle)

>>> pub.type
0
>>> str(pub.type)
'error'

>>> hex(pub.objectAttributes)
'0xf57385ea'

>>> pub.parameters.rsaDetail.keyBits
2048
>>> bytes(pub.unique.rsa).hex()
...

I'm not able to reproduce this issue, which version/from which commit are you using? Also could you try:

from tpm2_pytss.internal import type_mapping

If it's OK, you should not get an error.

On the same TPM, I also have an EK certificate stored at NV index 0x01c00002. To read it using ectx.nv_read_public and ectx.nv_read, I also needed to call ectx.tr_from_tpmpublic(0x01c00002) first.

How are users of tpm2-pytss expected to discover they need to call tr_from_tpmpublic (and tr_close) when using objects through TPM handles? Currently neither the code, the documentation nor the tests contain references to these use-cases, which seem natural for people coming from tpm2-tools.
The primary difference is that tpm2-tools implements tools, while tpm2-pytss provide APIs, I don't think there is any way around having some familiarity with ESAPI (or FAPI) when using tpm2-pytss.

I suggest adding some words about this use-case in the documentation of functions read_public and nv_read_public (and maybe in every nv_... function too), such as:

object_handle(ESYS_TR): Handle of the object. It can be created from a TPM handle using tr_from_tpmpublic/tr_close.

I think adding some examples would be better (outside the method documentation that is.
I'm already thinking of writing some example code touching different parts of the different APIs and will keep your input in mind

I also suggest adding a test case which defines a SRK at a defined handle (such as 0x81000000) and reads it using ectx.tr_from_tpmpublic, and another one which does the same with a NV index. What do you think?

While we don't have any tests using tr_from_tpmpublic for a NV index, there is test_evict_control in test/test_esapi.py

@niooss-ledger
Copy link
Contributor Author

tpm2_readpublic can make sensible assumptions about it's arguments , I don't think we can using ESAPI.

All right, tpm2_readpublic tools performs some "under-the-hood magic" which is not as straightforward with ESAPI.read_public function. Nevertheless my point was that it would be helpful to have something which tells new users of tpm2-pytss bindings how to use ESAPI.read_public in a way which mimics tpm2_readpublic. More precisely it would help migrating projects using subprocess.check_output("tpm2_readpublic ...") to tpm2-pytss.

I'm not able to reproduce this issue, which version/from which commit are you using?

I reported the issue in another GitHub issue (#299), which was fixed in #300.

I think adding some examples would be better (outside the method documentation that is.
I'm already thinking of writing some example code touching different parts of the different APIs and will keep your input in mind

Great!

While we don't have any tests using tr_from_tpmpublic for a NV index, there is test_evict_control in test/test_esapi.py

I can work on extending a test using NV index (such as test_plain_nv_define_write_read_undefine) to use tr_from_tpmpublic too.

niooss-ledger added a commit to niooss-ledger/tpm2-pytss that referenced this issue Jan 6, 2022
In order to read a NV index from a "TPM index", the function
`tr_from_tpmpublic` needs to be used first. Add such a use-case in the
test suite.

This was discussed in tpm2-software#298.

Signed-off-by: Nicolas Iooss <nicolas.iooss@ledger.fr>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants