tpm2-software / tpm2-tss

OSS implementation of the TCG TPM2 Software Stack (TSS2)
https://tpm2-software.github.io
BSD 2-Clause "Simplified" License
755 stars 366 forks source link

Compute the audit trail on the client so we can compare it to the TPM's #2326

Open LoupVaillant opened 2 years ago

LoupVaillant commented 2 years ago

The Enhanced System API seems to be missing a core functionality: computing the audit trail of a session whenever we open one. Right now we can only ask the audit trail from the TPM, but we have no way to check it. Thus, nothing would stop a Man in the Middle to just forward the TPM's audit trail, we'll never know if the communications have been tampered with. To prevent that, we need to compute the audit trail on the client side, then compare it to what we get from the TPM.

Note: it seems the job is already partly done in iesys_crypto_pHash(), defined in the src/tss2-esys/esys_crypto.c file: it already computes the hashes of the command and response buffers we're interested in, the only thing left to do is implement the chaining hash on top of that to get the audit trail. For reference:

audit_trail = all_zeroes
for each sucessful command:
    cp_hash     = HASH(command_buffer)   // more or less
    rp_hash     = HASH(response_buffer)  // more or less
    audit_trail = HASH(audit_trail || cp_hash || rp_hash)

(Note: the command that asks for the audit trail is not included in the audit trail it gives —I believe it's not possible.)

iesys_crypto_pHash() already computes cp_hash and rp_hash correctly, and I've checked that the rest of the computation does indeed computes the audit trail. The main difficulty I have here is determining which command are part of the audited session, and which aren't. I can hack something for myself in the short term for the provisioning script I'm currently working on, but I don't know the proper way to do this just yet (which is why this is an issue, and not a pull request).

AndreasFuchsTPM commented 2 years ago

So far we did not have command audit as a use case appear for Esys. With Sys, it's pretty easy using Tss2_Sys_GetCp/RpBuffer().

IMHO, we should try to figure out a nice way to do this, since we now have a user/use case; no idea however on how we want to do this without burning too much RAM (I don't want to store the CP buffer until after the response parsing).

Maybe we can have an Esys_Execute_WithBuffers() that will write CP and RP buffers to user-provided memory ?

LoupVaillant commented 2 years ago

User provided buffers would work, and sounds like the most flexible option.

Still, there’s a way to limit RAM use: you don’t have to retain the command buffer, only its hash. The most straightforward implementation only requires a buffer that can contain 3 hashes:

// Init
uint8_t audit_hash[MAX_HASH_SIZE*3];
memset(audit_hash, 0, HASH_SIZE);

// When a command buffer comes our way
hash(audit_hash + HASH_SIZE, command_buffer, command_size);

// When a response buffer comes our way
hash(audit_hash + HASH_SIZE * 2, response_buffer, response_size);
hash(audit_hash, audit_hash, HASH_SIZE * 3);

Alternatively, you could store the buffers separately, and use the init/update/final interface of your hash:

// Init
uint8_t audit_hash  [MAX_HASH_SIZE];
uint8_t command_hash[MAX_HASH_SIZE];
memset(audit_hash, 0, HASH_SIZE);

// When a command buffer comes our way
hash(command_hash, command_buffer, command_size);

// When a response buffer comes our way
uint8_t response_hash[MAX_HASH_SIZE];
hash(response_hash, response_buffer, response_size);
hash_ctx ctx;
hash_init(&hash_ctx);
hash_update(&hash_ctx, audit_hash,    HASH_SIZE)
hash_update(&hash_ctx, command_hash,  HASH_SIZE)
hash_update(&hash_ctx, response_hash, HASH_SIZE)
hash_final(&hash_ctx, audit_hash);

Regarding an ideal Esys interface, the best for me would be to have the Esys API automatically start computing the session’s audit trail when I give the TPMA_SESSION_AUDIT attribute to Esys_TRSess_SetAttributes(), and automatically compare the current audit trail (and fail if they don’t match) with what the TPM sends us when we request it.

I’m suggesting this because that’s what I actually expected when I first used the Esys API. I thought it was obvious enough that of course the audit trail that guarantees the integrity of the transmissions has been locally verified, just like any Message Authentication Code. I strongly suspect other users have (or will) make the same mistake. Making it not-a-mistake would be great.


Allowing users to provide buffers to get the command & response would be very nice, but that’s a separate use case: recording the plaintext transcript between the TPM and the client for further analysis. Though it’s a use case I also have, I believe it is much more niche, when you want to have an extremely diligent level of logging. I expect that most of the time, it is enough to trust the client program with commands, responses, and the audit trail.