-
Notifications
You must be signed in to change notification settings - Fork 0
Windows 10 TPM 2.0
A Trusted Platform Module (TPM) meeting specification version 2.0 is also known as a TPM 2.0 module/chip/processor.
On Windows 10, a TPM 2.0 processor that is Ready
for both attestation and storage can do things.
Recent versions of Windows take ownership of a TPM during boot if it is available, so the old way of doing things doesn't apply. As a result, there is a new way of doing things but it is virtually undocumented and apparently no-one knows how to do things the new way.
This page is to detail very verbosely how to do the following:
- 1. Create an RSA-2048 keypair using the TPM that can be used for either signing other keys or encrypting some bytes. The private key should only be usable by the TPM, but it shouldn't take up a slot in the TPM (i.e. it should be an encrypted by the TPM and saved as a file).
- 2. Verify the RSA key created in (1), so that it can be determined that a TPM created the keypair, the TPM used is in the current device, and the TPM is real.
- 3. Repeat (1) and (2), but with the created keypair being bound/sealed to PCR[7] and PCR[11].
- 4. Encrypt some bytes using the keypair created in (3), with the encrypted data being bound/sealed to PCR[7] and PCR[11].
- 5. Decrypt the cyphertext created in (4).
These are some rather simple cryptographic functions, but the question of implementing them is how?
As with SCSI/SPTI, there are several ways to approach this.
The main sources of information available are:
- The latest (revision 01.59) TPM 2.0 specification from the TCG, which is (ignoring any errors/omissions) the definition of not only how a TPM works, but how to make sure the TPM you are designing meets the definition of a TPM.
- Documentation for the OS/library detailing the APIs that directly communicate with a TPM, such as the Win32 TPM Base Services (TBS) API
- Source code for libraries that can be used to talk to a TPM, such as microsoft/TSS.MSR. These tend to talk to the TPM using the OS APIs, and provide an abstraction of the API for developers (i.e. the OS API might provide a function that covers numerous commands, whereas the library API might give each command its own function).
- Documentation and examples from the operating system manufacturer detailing operating system specific nuances, such as Automated provisioning and management of the TPM and the PDF
Using the Windows 8 Platform Crypto Provider and Associated TPM Functionality.pdf
from the TPM Platform Crypto-Provider Toolkit. - Documentation of other OS/library APIs that utilise a TPM, but the APIs are so far removed from the TPM the functions (and their names) have a narrow purpose and look nothing like the commands in the TPM specification. An example of this are the Win32 APIs that take
MS_PLATFORM_CRYPTO_PROVIDER
(L"Microsoft Platform Crypto Provider"
) as a parameter, such asNCryptOpenStorageProvider()
. - Online publications of questions/answers/blogs/how-tos covering the subject, such as the very detailed (but unanswered) StackOverflow question How to encrypt bytes using the TPM (Trusted Platform Module).
This is going to take a fair amount of research and reading to dissect how to even begin. In theory there is a Win32 API that can do this, but I want to make sure that such an API does what I am trying to do.
A TPM 2.0 device has an asymmetric key (and hopefully a certificate) that is created and stored in the TPM during manufacturing. There is a bit more to what an EK is than the following quote, but for our purposes we have a TPM and Windows says our TPM is ready for storage and attestation, so we know our TPM has an EK.
With TPM 2.0 the EK doesn't need to be permanently stored in the TPM because it is deterministically derived from the Endorsement Primary Seed (EPS). The EPS is either created by the TPM during manufacturing or is injected into the TPM during manufacturing, and is a random seed that is at least twice as long as the length of the longest algorithm the TPM supports. The EK credential (EKcert), a certificate containing the EKpub (EK's public key), does need to be stored in the TPM's non-volatile memory because it is signed by the TPM/computer manufacturer.
The Endorsement Key (EK) is an asymmetric key pair consisting of a public and private key stored in a Shielded Location on the TPM. The public part of the EK can be read from the TPM while the private part MUST never be exposed. The public key of the EK is included in the EK certificate.
—TPM 2.0 EK and EK Credential, TCG EK Credential Profile (Specification Version 2.1), TCG.
We are a consumer with an LTO drive installed in a personal computer. Our computer also has a TPM in its CPU. The rules of a User Device TPM are therefore applicable rather than the looser rules for a server.
User device TPMs are TPMs that are associated with a human user, typically in PC Client or Mobile platforms. If the EK is certified by a trusted entity, the EK SHOULD NOT be used for signing operations due to privacy concerns. In this case, the EK SHOULD be defined as a restricted decryption (Storage) key.
—TPM 2.0 EK and EK Credential, TCG EK Credential Profile (Specification Version 2.1), TCG.
Our EK is a Storage key. Let's come back to that later.
Our EKcert is signed by the manufacturer and our manufacturer's TPM-EK-signing certificate chain is in Microsoft's .cab
file of manufacturers of totally real TPMs. I'm stating this as a fact for my Ryzen CPU's TPM even though the only certificate chain I've checked is for the TPM in my other computer.
TPM 2.0 does not limit the number of EKcerts a TPM can derive from the EPS, and non-volatile storage limitations mean these additional EKcerts may be stored wrapped outside of the TPM (i.e. a binary blob encrypted by the TPM for exporting that can only be decrypted when it is back within the TPM). There are some rules about such additional EKcerts, but they don't apply because we only have the one EKcert.
The TPM's non-volatile (NV) memory has some rules on where things should be stored, with some well-known NV indices defined in the TPM 2.0 specification for where EKs should be stored to meet Windows TPM requirements, including a few MUST
s, SHOULD
s, and SHALL NOT
s. This is mostly irrelevant for us because we don't want to store anything in the TPM, although we might need to come back to this if we need to store something in the TPM temporarily if it can't be done in volatile memory.
There are certain TPM commands that require the TPM be initialised. If the TPM is initialised with a passphrase (owner password), some of those commands require the passphrase. No passphrase, no successful command.
As I said in a comment on issue watfordjc/LTO-Encryption-Manager#1, "Microsoft have basically said [changing the registry so the authentication credential is stored] is a bad idea, you don't need to do this, you don't need [the ownerAuth
/auth
] credential, if someone enters the wrong PIN 32 times do what everyone else has to do and wait 2 hours for attempt 33. As a result, Windows now takes ownership of the TPM on every boot, and that credential is destroyed almost as soon as it exists."
Starting with Windows 10 and Windows 11, the operating system automatically initializes and takes ownership of the TPM. This means that in most cases, we recommend that you avoid configuring the TPM through the TPM management console, TPM.msc. There are a few exceptions, mostly related to resetting or performing a clean installation on a PC.
—Automatic initialization of the TPM with Windows, Trusted Platform Module Technology Overview, Microsoft Documentation.
Starting with Windows 10, version 1607, or Windows 11, Windows will not retain the TPM owner password when provisioning the TPM. The password will be set to a random high entropy value and then discarded.
Only one owner password exists for each TPM. The TPM owner password allows the ability to enable, disable, or clear the TPM without having physical access to the computer, for example, by using the command-line tools remotely. The TPM owner password also allows manipulation of the TPM dictionary attack logic. Taking ownership of the TPM is performed by Windows as part of the provisioning process on each boot. Ownership can change when you share the password or clear your ownership of the TPM so someone else can initialize it.
Without the owner password you can still perform all the preceding actions by means of a physical presence confirmation from UEFI.
—Change the TPM owner password, Information protection, Microsoft Documentation.
From the sound of it, Microsoft believe that the only authenticated TPM commands are those for enabling/disabling/clearing the TPM.
Taking ownership of the TPM, among other things, creates a new Storage Root Key (SRK). The Storage Root Key is an asymmetric key created within the TPM. Because the SRK is both the root chain of trust and the thing that wraps keys that can be exported from the TPM, the SRK itself never leaves the TPM.
When the SRK is created, owner authentication data is involved that defines the credential needed to identify as the owner of the TPM and to perform operations using the SRK that require authentication. Presumably, authenticated SRK commands are those that involve the SRK chain of trust.
Win32_Tpm.ConvertToOwnerAuth()
says the following in terms of the owner authorisation value used in TPM-related methods such as TakeOwnership()
and ResetAuthLockOut()
:
A Unicode UTF-16LE encoded string is converted to the 20-byte TPM owner authorization value by taking the SHA-1 hash of the string's binary representation. The null termination of the Unicode string is not included in the hash. No salt is used in the SHA-1 hash.
—ConvertToOwnerAuth method of the Win32_Tpm class, Win32, Microsoft Documentation.
The following observations can now be made:
- There is only one owner password.
- TPMs use 20 byte secrets.
- Windows uses a SHA-1 hash of an entered passphrase if a user takes ownership of a TPM.
- Windows uses a SHA-1 hash of a user's logon password for encrypting a user's Data Protection API (DPAPI) master keys.
- Windows uses the SHA-1 hash of the LocalMachine's/SYSTEM's credentials(?) for encrypting local machine Data Protection API (DPAPI) master keys.
Under all the obfuscation and things about super randomness on each boot, is the method used to generate a 20 byte shared TPM secret (by a Windows device that automatically provisions a TPM) the same method used to generate a SHA-1 hash for the DPAPI LocalMachine
scope? Are the hashes identical? It would make sense, the LSA is already storing it in (potentially protected) memory for DPAPI, and SYSTEM's password (I'm assuming it is a password) is supposedly created during the Windows installation process and is fixed throughout the life of that Windows installation.
The SRK is used to bind/seal data to PCRs. It is also used to create "trusted" keypairs that are part of the SRK's chain of trust. The TPM can be told that the public key for the SRK can be read without owner authentication, and a bit of Googling suggests using the SRK with owner auth involves some form of HMAC operation to create a signature of the TPM object being interacted with. There are still things I am unclear on with the Storage Root Key.
- If the Storage Root Key is a long-lived RSA key, what exactly is Windows doing when it provisions the TPM on each boot?
- Is the "random high entropy value" Windows sets the owner password to really random, or is PBKDF2 involved?
- Is the TPM owner password and the SRK owner authentication data the same thing? If not, where is the SRK auth data being stored?
- How exactly is the SRK used by the TPM?
Of these questions, I think I'll look at (4) first. The TPM 2.0 specification (unsure which part) will define the SRK or at least something that sounds like it. Looking at the definitions in Part 1 of the specification, there are two definitions starting with "Storage":
key used to provide integrity and confidentiality protection for descendant keys that are stored off of the TPM
Storage Key that is acting as a parent key
—Trusted Platform Module Library - Part 1: Architecture, TPM 2.0 (revision 01.59), TCG.
A bit further into Part 1 of the specification are the three types of authorisation roles: USER
, ADMIN
, and DUP
.
The TPM supports three different authorization roles. The role and attributes determine whether a password or HMAC can be used for authorization. A policy (if not the Empty Policy) can always be used.
- USER – this authorization role is used for the normal uses of a key (e.g., signing with a signing key, or loading the child of a Storage Key). Methods are defined to allow USER role authorization to be provided either with an authorization value (authValue) or a policy. If userWithAuth is SET, then USER role authorization may be provided with a password authorization or an HMAC session. If userWithAuth is CLEAR, then a password and HMAC authorizations may not be used to provide USER role authorizations. A policy session that satisfies the authPolicy of the entity may be used regardless of the setting of userWithAuth.
NOTE 1 For USER role, an authPolicy is satisfied when the policyDigest of a policy session matches the value of the authPolicy value of the object.
NOTE 2 If use of an object is to be gated based on PCR values, a policy session is required (see 19.7). If the intent is that different Users have access to the object but only if the PCR are correct, then it is likely that authorization with the authValue will be disabled; otherwise, the caller could circumvent PCR protections simply by providing the authValue.
—19.2 Authorization Roles, Trusted Platform Module Library - Part 1: Architecture, TPM 2.0 (revision 01.59), TCG.
Storage Root Keys are derived from a Storage Primary Seed (SPS), much like Endorsement Keys are derived from an Endorsement Primary Seed:
The SPS is used to generate hierarchies controlled by the platform owner. This seed generates the keys that serve as Storage Root Keys for normal OS and application use.
The TPM creates the SPS whenever it is powered on and no SPS is present. TPM2_Clear() may be used to change the SPS if the TPM owner wants to ensure that no previously generated keys in the Storage hierarchy may be used in the future.
Changing the SPS invalidates all objects in the Storage Hierarchy and they cannot be recreated. Changing the SPS also invalidates all objects in the Endorsement Hierarchy and only the Primary Objects in the Endorsement Hierarchy may be recreated.
—Trusted Platform Module Library - Part 1: Architecture, TPM 2.0 (revision 01.59), TCG.
The difference between the EPS and the SPS is that an EPS is usually permanent, whereas a new SPS is created whenever the TPM is powered on if one does not exist.
Windows doesn't invalidate every key (and encrypted file) on a reboot because that would be stupid. The SPS is, therefore, not reset when Windows takes ownership of the TPM during boot. I therefore assume that TPM2_Clear()
(clearing the TPM) does not need to occur before taking ownership ("inserting authorization values for the ownerAuth
, endorsementAuth
, and lockoutAuth
"). The list of things that occur when calling TPM2_Clear()
(releasing ownership) includes "replace the existing SPS with a new value from the RNG", which obviously doesn't happen on a reboot.
Part 3 of the specification says the following of the TPM2_CreatePrimary()
command:
The TPM will derive the object from the Primary Seed indicated in primaryHandle using an approved KDF. All of the bits of the template are used in the creation of the Primary Key. Methods for creating a Primary Object from a Primary Seed are described in TPM 2.0 Part 1 and implemented in TPM 2.0 Part 4.
If this command is called multiple times with the same inPublic parameter, inSensitive.data, and Primary Seed, the TPM shall produce the same Primary Object.
NOTE 4 If the Primary Seed is changed, the Primary Objects generated with the new seed shall be statistically unique even if the parameters of the call are the same.
This command requires authorization. Authorization for a Primary Object attached to the Platform Primary Seed (PPS) shall be provided by platformAuth or platformPolicy. Authorization for a Primary Object attached to the Storage Primary Seed (SPS) shall be provided by ownerAuth or ownerPolicy. Authorization for a Primary Key attached to the Endorsement Primary Seed (EPS) shall be provided by endorsementAuth or endorsementPolicy.
—24.1 TPM2_CreatePrimary, Trusted Platform Module Library - Part 3: Commands, TPM 2.0 (revision 01.59), TCG.
The SRK is deterministically derived from the SPS, with the SPS fed into a DRBG (deterministic random bit generator). Let's take a look at the specification's sample C function for the command TPM2_CreatePrimary()
.
TPM_RC TPM2_CreatePrimary(CreatePrimary_In *in, CreatePrimary_Out *out) { }
There are some defined Error Returns for the TPM_RC
type return code, and the function contains a local TPM_RC result
variable that is initialised to TPM_RC_SUCCESS
. The result =
the returned TPM_RC
of other functions called within TPM2_CreatePrimary() { }
, with equality checks for success/failure (i.e. if result
is/isn't equal to TPM_RC_SUCCESS
after each function call).
Part 4 of the specification contains the following definitions:
typedef struct OBJECT
{
// The attributes field is required to be first followed by the publicArea.
// This allows the overlay of the object structure and a sequence structure
OBJECT_ATTRIBUTES attributes; // object attributes
TPMT_PUBLIC publicArea; // public area of an object
TPMT_SENSITIVE sensitive; // sensitive area of an object
TPM2B_NAME qualifiedName; // object qualified name
TPMI_DH_OBJECT evictHandle; // if the object is an evict object,
// the original handle is kept here.
// The 'working' handle will be the
// handle of an object slot.
TPM2B_NAME name; // Name of the object name. Kept here
// to avoid repeatedly computing it.
} OBJECT;
#if CC_CreatePrimary
#include CreatePrimary_fp.h
typedef TPM_RC (CreatePrimary_Entry)(
CreatePrimary_In *in,
CreatePrimary_Out *out
);
typedef const struct {
CreatePrimary_Entry *entry;
UINT16 inSize;
UINT16 outSize;
UINT16 offsetOfTypes;
UINT16 paramOffsets[9];
BYTE types[13];
} CreatePrimary_COMMAND_DESCRIPTOR_t;
CreatePrimary_COMMAND_DESCRIPTOR_t _CreatePrimaryData = {
/* entry */ &TPM2_CreatePrimary,
/* inSize */ (UINT16)(sizeof(CreatePrimary_In)),
/* outSize */ (UINT16)(sizeof(CreatePrimary_Out)),
/* offsetOfTypes */ offsetof(CreatePrimary_COMMAND_DESCRIPTOR_t, types),
/* offsets */ {(UINT16)(offsetof(CreatePrimary_In, inSensitive)),
(UINT16)(offsetof(CreatePrimary_In, inPublic)),
(UINT16)(offsetof(CreatePrimary_In, outsideInfo)),
(UINT16)(offsetof(CreatePrimary_In, creationPCR)),
(UINT16)(offsetof(CreatePrimary_Out, outPublic)),
(UINT16)(offsetof(CreatePrimary_Out, creationData)),
(UINT16)(offsetof(CreatePrimary_Out, creationHash)),
(UINT16)(offsetof(CreatePrimary_Out, creationTicket)),
(UINT16)(offsetof(CreatePrimary_Out, name))},
/* types */ {TPMI_RH_HIERARCHY_H_UNMARSHAL + ADD_FLAG,
TPM2B_SENSITIVE_CREATE_P_UNMARSHAL,
TPM2B_PUBLIC_P_UNMARSHAL,
TPM2B_DATA_P_UNMARSHAL,
TPML_PCR_SELECTION_P_UNMARSHAL,
END_OF_LIST,
TPM_HANDLE_H_MARSHAL,
TPM2B_PUBLIC_P_MARSHAL,
TPM2B_CREATION_DATA_P_MARSHAL,
TPM2B_DIGEST_P_MARSHAL,
TPMT_TK_CREATION_P_MARSHAL,
TPM2B_NAME_P_MARSHAL,
END_OF_LIST}
};
#define _CreatePrimaryDataAddress (&_CreatePrimaryData)
#else
#define _CreatePrimaryDataAddress 0
#endif // CC_CreatePrimary
Part 3 of the specification defines the following:
Type | Name | Description |
---|---|---|
TPMI_ST_COMMAND_TAG | tag | TPM_ST_SESSIONS |
UINT32 | commandSize | |
TPM_CC | commandCode | TPM_CC_CreatePrimary |
TPMI_RH_HIERARCHY+ | @primaryHandle | TPM_RH_ENDORSEMENT, TPM_RH_OWNER, TPM_RH_PLATFORM+{PP}, or TPM_RH_NULL. Auth Index: 1; Auth Role: USER |
TPM2B_SENSITIVE_CREATE | inSensitive | the sensitive data, see TPM 2.0 Part 1 Sensitive Values |
TPM2B_PUBLIC | inPublic | the public template |
TPM2B_DATA | outsideInfo | data that will be included in the creation data for this object to provide permanent, verifiable linkage between this object and some object owner data |
TPML_PCR_SELECTION | creationPCR | PCR that will be used in creation data |
Type | Name | Description |
---|---|---|
TPM_ST | tag | see clause 6 |
UINT32 | responseSize | |
TPM_RC | responseCode | |
TPM_HANDLE | objectHandle | handle of type TPM_HT_TRANSIENT for created Primary Object |
TPM2B_PUBLIC | outPublic | the public portion of the created object |
TPM2B_CREATION_DATA | creationData | contains a TPMT_CREATION_DATA |
TPM2B_DIGEST | creationHash | digest of creationData using nameAlg of outPublic |
TPMT_TK_CREATION | creationTicket | ticket used by TPM2_CertifyCreation() to validate that the creation data was produced by the TPM |
TPM2B_NAME | name | the name of the created object |
TPM2_CreatePrimary()
initialises the following local variables:
TPM_RC result = TPM_RC_SUCCESS;
TPMT_PUBLIC *publicArea;
DRBG_STATE rand;
OBJECT *newObject;
TPM2B_NAME name;
-
newObject
is set usingFindEmptyObjectSlot(&out->objectHandle)
. This checks there is enough free space in the TPM to create the object. -
publicArea
is set to&newObject->publicArea
and*publicArea
is set toin->inPublic.publicArea
. - Some checks are conducted on the input public area, and some common create/load attributes and values are validated.
- The sensitive area values are validated.
- The rest of the function follows:
result = DRBG_InstantiateSeeded(&rand, // IN/OUT: buffer to hold the state
&HierarchyGetPrimarySeed(in->primaryHandle)->b, // IN: the seed to use
PRIMARY_OBJECT_CREATION, // IN: a label for the generation process.
(TPM2B *)PublicMarshalAndComputeName(publicArea, &name), // IN: name of the object
&in->inSensitive.sensitive.data.b); // IN: additional data
if(result == TPM_RC_SUCCESS)
{
newObject->attributes.primary = SET;
if(in->primaryHandle == TPM_RH_ENDORSEMENT)
newObject->attributes.epsHierarchy = SET;
// Create the primary object.
result = CryptCreateObject(newObject, &in->inSensitive.sensitive,
(RAND_STATE *)&rand);
}
if(result != TPM_RC_SUCCESS)
return result;
// Set the publicArea and name from the computed values
out->outPublic.publicArea = newObject->publicArea;
out->name = newObject->name;
// Fill in creation data
FillInCreationData(in->primaryHandle, publicArea->nameAlg,
&in->creationPCR, &in->outsideInfo, &out->creationData,
&out->creationHash);
// Compute creation ticket
TicketComputeCreation(EntityGetHierarchy(in->primaryHandle), &out->name,
&out->creationHash, &out->creationTicket);
// Set the remaining attributes for a loaded object
ObjectSetLoadedAttributes(newObject, in->primaryHandle);
return result;
Translating that...
-
HierarchyGetPrimarySeed(in->primaryHandle)->b
means get theTPM2B_SEED->b
of the primary seed for hierarchyin->primaryHandle
, wherein->primaryHandle
is:-
TPM_RH_PLATFORM
for PPS, -
TPM_RH_OWNER
for SPS, -
TPM_RH_ENDORSEMENT
for EPS, and -
NULL
is for the null seed.
-
-
PRIMARY_OBJECT_CREATION
is a constant string:"Primary Object Creation"
. -
PublicMarshalAndComputeName()
computes an object's name from its public area. -
&in->inSensitive.sensitive.data.b
isCreatePrimary_In.TPM2B_SENSITIVE_CREATE.TPMS_SENSITIVE_CREATE.TPM2B_SENSITIVE_DATA.TPM2B
.TPM2B
is a union of a type specific valuet
and a generic valueb
. In the case ofb
being aBYTE
array,t
is the length. -
DRBG_InstantiateSeeded()
instantiates an RNG based on a seed value. The first parameter is used as a buffer, which is zeroed and then the second field of the structure (UINT64; UINT32; DRBG_SEED; UINT32;
) set toDRBG_MAGIC
, (i.e.(UINT32) 0x47425244
), and then the four input parameters and the buffer go through some maths resulting in the DRBG (&rand
) being reseeded with a seed deterministically created from the four input values. - If
DRBG_InstantiateSeeded()
succeeds, the object has theprimary
attributeSET
. If an EPS seed is being used, the object has theepsHierarchy
attributeSET
. -
CryptCreateObject()
is then called with three parameters: the object, the sensitive data, and the DRBG's state. -
CreatePrimary_Out
gets updated, a creation ticket is created and added, any other attributes get added, and the primary object now exists.