google / go-tpm

Apache License 2.0
560 stars 162 forks source link

remote session: using go-tpm in a tpm-stack with tpm2-abrmd and swtpm #234

Closed tobuh closed 3 years ago

tobuh commented 3 years ago

I have following setup:

swtpm socket --tpmstate dir=/some/dir --tpm2 --ctrl type=tcp,port=2322 --server type=tcp,port=2321 --flags startup-clear
tpm2-abrmd --allow-root --tcti=swtpm
socat unix-listen:/path/to/socket,reuseaddr,fork,mode=777 system:'tpm2_send --tcti=tabrmd'

Reasons for this setup:

Additionally, I already created a SRK:

tpm2_createprimary --hierarchy=owner --key-algorithm=rsa2048:aes128cfb --hash-algorithm=sha256 --key-context=prim.ctx -a 'restricted|decrypt|fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda'
tpm2_evictcontrol --object-context=prim.ctx --hierarchy=owner 0x81000001

I succesfully ran tpm2-tools in this setup:

tpm2_create --tcti="cmd:nc -U /path/to/socket" -C 0x81000001 -u tpm.pub -r tpm.priv
tpm2_load --tcti="cmd:nc -U /path/to/socket" -C 0x81000001 -u tpm.pub -r tpm.priv -c key.ctx

key.ctx has content! So the setup should generally work ...

Now to my problem. I have following demo code:

package main

import (
    "io/ioutil"

    "github.com/google/go-tpm/tpm2"
    "github.com/google/go-tpm/tpmutil"
)

var (
    tpmSrkHandle        = 0x81000001
    tpmPcrSelection     = tpm2.PCRSelection{Hash: tpm2.AlgSHA256, PCRs: []int{}}
    tpmDefaultKeyParams = tpm2.Public{
        Type:       tpm2.AlgRSA,
        NameAlg:    tpm2.AlgSHA256,
        Attributes: tpm2.FlagDecrypt | tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagNoDA,
        AuthPolicy: []byte{},
        RSAParameters: &tpm2.RSAParams{
            Symmetric: &tpm2.SymScheme{
                Alg:  tpm2.AlgNull,
                Mode: tpm2.AlgUnknown,
            },
            KeyBits: 2048,
        },
    }
)

func main() {
    rwc, tpmerr := tpm2.OpenTPM("/path/to/socket")
    if tpmerr != nil {
        return
    }
    defer rwc.Close()

    handle := tpmutil.Handle(tpmSrkHandle)
    privKey, pubKey, _, _, _, err := tpm2.CreateKey(rwc, handle, tpmPcrSelection, "", "", tpmDefaultKeyParams)
    tpmKeyHandle, _, err := tpm2.Load(rwc, handle, "", pubKey, privKey)
    if err != nil {
        return
    }
    defer tpm2.FlushContext(rwc, tpmKeyHandle)
    ekhBytes, err := tpm2.ContextSave(rwc, tpmKeyHandle)
    err = ioutil.WriteFile("key.ctx", ekhBytes, 0644)
}

Socket is opening, CreateKey gives reasonable return values, but on ContextSave abrmd (with G_MESSAGES_DEBUG=all) gives follwing warning:

** (tpm2-abrmd:19261): DEBUG: 16:43:58.362: resource_manager_load_handles: for 1 handles in command handle area
** (tpm2-abrmd:19261): DEBUG: 16:43:58.362: processing TPM2_HT_TRANSIENT: 0x800000ff
** (tpm2-abrmd:19261): DEBUG: 16:43:58.362: processing TPM2_HT_TRANSIENT: 0x800000ff
** (tpm2-abrmd:19261): DEBUG: 16:43:58.362: handle 0x800000ff is virtual TPM2_HT_TRANSIENT, loading
** (tpm2-abrmd:19261): WARNING **: 16:43:58.362: No HandleMapEntry for vhandle: 0x800000ff

key.ctx is empty in this case! This is observed with every "key-handle" function, e.g. ReadPublic ...

When I ommit abrmd, using swtpm directly with

swtpm socket --tpmstate dir=/some/dir --tpm2 --ctrl type=tcp,port=2322 --server type=unixio,path=/path/to/socket --flags startup-clear

it works without problems.

But as I ran tpm2-tools successfully on this setup, it should work.

Any ideas, suggestions? Am I missing something?

Thanks for your help.

twitchy-jsonp commented 3 years ago

Does tpm2.CreateKey or tpm2.Load error? It does not appear that you are handling/printing/checking errors, that would be a good starting point to seeing any issues.

fwiw, I've found that most TPM simulators emit a ton of console messages, and not all warnings are erroneous.

One possible explanation is that Load is erroring, and then your deferred call to tpm2.FlushContext(rwc, tpmKeyHandle) is using an invalid transient handle (hence causing that message).

tobuh commented 3 years ago

Of course I forgot to mention the error code. It's stripped down debugging code, so I did not do proper error handling, in sense of printing, but at least in sense of debug watches ...

ContextSave gives a tpm2.HandleError, with Code: 4 and Handle: RC1 (1)

CreateKey and Load are totally fine ... The tpmKeyHandle is "0x800000ff"

I can clearly watch while debugging, that ContextSave is causing this error.

To emphasize, what makes me helpless is: The same procedure is working with tpm2-tools ...

twitchy-jsonp commented 3 years ago

Whats the intent behind doing ContextSave ?

If you are trying to match the behavior of tpm2-tools, it might be useful to somehow dump the bytes on the tpm command socket so you can see the difference between what tpm2-tools is doing, and what your code is doing. If I had to guess:

tobuh commented 3 years ago

I do not want to match exactly tpm2-tools. It was just to show that the basic setup with tabrmd, socat, etc. generally works. But it does unfortunately not work with go-tpm.

It is not only a misbehavior of ContextSave. I observed this also with other functions, that need the handle of a loaded key, like ReadPublic.

As I described in my initial post, in the error output of tabrmd, the translation of a vhandle to a phandle does not work. I cannot imagine why? Do you have an idea?

twitchy-jsonp commented 3 years ago

go-tpm is extremely simple in this regard: we take the command and serialize it into a byte stream the TPM expects. These methods work fine for me (though ive never used ContextSave), so would be surprised if we were encoding the handles wrong. Are you sure there is no bug in tabrmd ?

The only thing I can think of is the length prefix for the handles: Theres some subtlety in which encoding you use on the wire. Maybe theres some wierd edge case in tabrmd there, or possibly go-tpm? If you could get a dump of the command bytes, it would be a lot easier to tell.

Out of curiosity, is there a reason yous are using tabrmd instead of the resource manager built into the operating system?

chrisfenner commented 3 years ago

Disclaimer: I'm not an expert on TPM resource managers in general, or abrmd in particular. If I were writing a TPM resource manager, I would not support the ContextSave/ContextLoad commands, as it would drastically increase the complexity of my resource manager for little-to-no benefit (those commands exist so resource managers can exist).

I wouldn't expect abrmd to support ContextSave/ContextLoad based on this comment: https://github.com/tpm2-software/tpm2-abrmd/blob/b170475e6006fe95461d0c58770931eb151f4772/src/resource-manager.c#L1100

tobuh commented 3 years ago

@twitchy-jsonp Would be log output of swtpm sufficient? There is a lot of traffic of course ... Which part do you exactly need?

I use tabrmd because of a recommendation from one of the main developers of tpm2-tss. And another opinion from here: https://github.com/tpm2-software/tpm2-abrmd/issues/730#issuecomment-674950210 I do not use a very recent kernel. So I thought it might be better to use tabrmd. Anyway, I somehow need to simulate a RM as in my (virtual) test setup environment. And third, I have a LSM, that gives access to /dev/tpm0 and /dev/tpmrm0 simultaneously. I cannot separately restrict /dev/tpm0. So this is also a further encapsulation method for TPM access.

@chrisfenner Thanks for the hint, this makes me wonder, if I need to readjust my setup. Anyway, the exact same use-case works with tpm2-tools.

chrisfenner commented 3 years ago

Sorry for my reading comprehension, I see more clearly how tpm-tools seems to be saving the context successfully with an identical setup.

I'm curious how tabrmd tracks different callers so that it can reload state and recognize their contexts (caller A's key 0x800000ff is a totally other thing than caller B's key 0x800000ff). I wonder if the TPM writing pattern into the socket differs between go-tpm and tpm2-tools.

tobuh commented 3 years ago

I investigated a bit further on this topic. I noticed there are (perhaps similar?) errors with my in-kernel RM (kernel 4.19). If I run on /dev/tpm0 directly, everything is working fine. Let me ask you one question, just to be sure: Do you successfully use this library in an environment with a RM with regard to functions that needs TPM contexts/sessions, like ContextSave, ReadPublic and so on?

I looked at the debugging output of tabrmd, and noticed a significant difference. On using go-tpm, the only messages that are in context with sessions is "resource_manager_remove_connection: flushing session contexts". So, I think, on every TPM connection close (on every TPM command?), there is this connection close, thus flushing contexts, thus no possibility any more to readdress the relationship between pHandle and vHandle.

Whereas the tpm2-tools with tpm2-tss, are making use of sessions extensively. There are a lot of session related messages. To give you an impression, some extracts:

create_context_mapping_session: handle is a session, creating entry for SessionList and SessionList
session_entry_new
resource_manager_load_session_from_handle: mapped session handle 0x02000000 to SessionEntry
resource_manager_flush_context handle: 0x2000000
resource_manager_flush_context: handle 0x02000000is a session, removing from SessionList

So, are there any commands missing, that I need to use from go-tpm, to enable such session handling?

If necessary I can provide detailed output tomorrow. I still need to do some simplified testing ...

tobuh commented 3 years ago

Finally, found the origin of the problem, as I also noticed a lot of "received CONNECTION_REMOVED message for connection" from the tabrmd when using go-tpm, after each command.

As I am connecting to unix-sockets (instantiated by socat), go-tpm is using "EmulatorReadWriteCloser" which is always closing and opening the connection to the TPM, thus automatically flushing every contexts. I don't think this behaviour is correct. It is a special implementation for TPM emulator. I rewrote the "EmulatorReadWriteCloser" implementation, without close/reopening, and it works in my use case.

I will think about a proper solution here. Suggestions welcome.

tobuh commented 3 years ago

Finally, easy one! Implemented my own OpenTPM function ... everything working as expected