Rollback protection #33
Replies: 3 comments 3 replies
-
cc @diabonas who might have some good insight here |
Beta Was this translation helpful? Give feedback.
-
Actually, my overly complicated scheme doesn't work at all. NV indexes are referred to in auth policy hashes by the hash of their public area, and the public area includes their auth policy hash, so you can't use an NV index in its own auth policy. Fortunately there's a much simpler approach: just use an TPM_NT_EXTEND index in place of a counter. These are basically persistent PCRs that start out with an all-zero value, so an OS vendor can define how they will extend it for each new version (e.g. writing a known version string) and check its value in the policies they sign. The primary drawback compared to a counter (aside from being rollbackable by the owner auth, and needing to manage that) is that it's pretty inconvenient to specify a range of values to allow for limited rollbacks, though you could use PolicyOR or just sign a policy for each value. |
Beta Was this translation helpful? Give feedback.
-
Can a rollback protection strategy be expressed in terms of TPM2_ClockSet and TPM2_ClockRead? In that sense we can have something that can be like a shared counter between different TPMs |
Beta Was this translation helpful? Give feedback.
-
(This is largely a response to part of this blog post)
All this is true, but I think it misses an important subtlety about how TPM counters work. In the TPM2 spec, part 3 (Commands), section 31.2 (NV Counters) we have
This seems to exist to prevent the counter from being rolled back using the owner authorization, which can delete and recreate NV indexes (oddly there seems to be no similar protection for the TPM_NT_BITS or TPM_NT_EXTEND types, which present similar issues for their most obvious uses). I haven't checked if this is actually implemented by TPMs as stated, but let's assume it is.
This means we can't assume that a new counter can be set to any specific value during setup, because other users of the TPM may have incremented another counter past that point. This holds for the lifetime of the TPM, so we can't assume this even if we've just cleared the TPM on a new install. This seems to kill any possibility of using counters in a uniform policy intended to apply across many devices. Frustratingly, there seem to be many similar situations that do work:
But if you're an OS vendor who has to do stuff in the owner hierarchy, and leave it available for other usage, and want sign things per OS version rather then per install, and want to block rollbacks without clearing the TPM, you just seem to be stuck.
I haven't been able to find a scheme that gets around this issue entirely. If you use counters you get this issue, but if you use any other kind of NV index the owner authorization can be used to clear it and then whatever was used to update it originally can be replayed. Probably the best option available is to accept that the owner authorization can be used to authorize rollbacks and treat it like the disk encryption recovery key i.e. require it to be set to a high entropy value during setup (or a low entropy value with dictionary attack prevention) and not be used in normal operation. This likely also requires storing the primary storage key persistently on the TPM so applications and the OS can use them without the owner authorization, and defining a standard for this so applications know how to use it (or referring to an existing one, if it exists).
In detail, it would look like this:During OS developmentWhenever a new rollback barrier is desired, a new signing key for TPM secrets is generatedEach OS build has its PCR values signed with its key and all previous keysDuring setupOwner authorization is requested by the OS installer, and set if it is not already setGenerate the primary storage key and store it persistently on the TPMAllocate an NV index at a fixed location withtype TPM_NT_ORDINARYlength long enough to hold an authorization policy hashattributes TPMA_NV_POLICYWRITE, TPMA_NV_WRITE_STCLEAR, TPMA_NV_AUTHREAD, TPMA_NV_NO_DAan empty authValue (so anyone can read it, which is required to authorize against its contents)authPolicy of PolicyOR(PolicyNvWritten(false), PolicyAuthorizeNV(this NV index, while not write locked))This allows the index to be written once during setup, and otherwise whenever the policy hash in the contents of the NV index is matchedWrite to the NV index the authorization policy hash for PolicySigned(signing key for TPM secrets for current OS version)Seal secrets (e.g. disk encryption key, systemd credential root key) to PolicyAuthorizeNV(this NV index, write locked) under the persistently stored primary storage keyDuring early boot (e.g. in the initrd):Check the value of the NV index to figure out which signature to useIf it doesn't correspond to the latest key, authorize using a signature from the older key and write the new policy hashCall NV_WriteLock on the NV index, authorized using the signature from latest keyThis blocks regular applications with TPM access from changing the policy. The write lock is cleared on next boot.Unseal secrets using the signature from latest key and the persistent primary storage keyIf signatures for several different stages of boot with different PCRs are desired, multiple pairs of signing keys and NV indexes can be used (but it might be better to use a TPM_NT_BITS NV index with the attribute TPMA_NV_CLEAR_STCLEAR to reset it on reboot for the same purpose).This means authorized OS versions can update the policy to lock out older versions and access secrets, non-authorized OSes can't gain access to secrets or change the policy, and regular applications running under authorized OSes can access secrets but can't change policy, but the owner authorization can override all of this, for better or for worse.(Separately from most of this, it also seems like we could use an OS/Application API for TPM usage that keeps applications from stepping on each other or needing to know about broader OS policies)
Beta Was this translation helpful? Give feedback.
All reactions