tpm.dev.tutorials/Attestation/ek-secret.md
Erik Larsson 6363b9bbdc add example code + basic concept of passing a secret to a TPM just knowing the EK
Signed-off-by: Erik Larsson <who+github@cnackers.org>
2021-06-08 05:19:48 +02:00

7.3 KiB

Passing a secret to a TPM using only the EK

This is example code to pass a secret to a system by just knowing the endorsenment key. We will be using the current (commit 07a92e9fa75548ea102ce90b3b6182093b3f7a73 or later) master branch of https://github.com/tpm2-software/tpm2-pytss

The terms for the systems are client, the system we want to pass the secret to and server, the system which has the secret but doesn't need a TPM. One assumtion that will be made is that you already have the EKpub for the remote system on the local system, and trust it. While we will use the EK in this guide any key accepted by ActivateCredential should work.

Concept

The concept is that the key being activated by a call to ActivateCredential on the remote system doesn't have to be generated on the TPM, as long as the public and private parts are loaded it will succeed. By generating a temporary key pair on the local system we can run MakeCredential with the remote system EK, name of the locally generated key and the secret.

server script

#!/usr/bin/python3

import sys
from tpm2_pytss import *
from tpm2_pytss.makecred import MakeCredential
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, PrivateFormat, NoEncryption

def main(ekpath, publicpath, sensitivepath, credpath, secretpath, oursecret):
    # first read the EK and unmarshal it
    with open(ekpath, 'rb') as ef:
        ekb = ef.read()
    ekpub, _ = TPM2B_PUBLIC.Unmarshal(ekb)

    # Now we generate the temporary key pair
    # We are using ECC keys here as they are generally fast to generate, but RSA should work as well
    # We will use the curve SECP256R1 as it should work on all TPMs
	# One could use a well known/the same pre-generated key for multiple systems
    privatekey = ec.generate_private_key(ec.SECP256R1, backend=default_backend())
    publickey = privatekey.public_key()
    
    # Now it's time to TPM structures from the keys
    # First we need to encode it due to how the tpm2_pytss API currently works
    privateenc = privatekey.private_bytes(Encoding.DER, PrivateFormat.PKCS8, NoEncryption())
    publicenc = publickey.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
    sensitive = TPM2B_SENSITIVE.fromPEM(privateenc)
    # by objectAttributes to 0 we reduce the change that keys will be used for anything
    public = TPM2B_PUBLIC.fromPEM(publicenc, objectAttributes=0)
    # the same applices to authPolicy
    public.publicArea.authPolicy = b"\x00" * 32

    # now it's time to run the MakeCredential part, using the software implementation in tpm2_pytss
    # the API is slight different to the standard, but behaves the same
    credblob, secret = MakeCredential(ekpub, oursecret, bytes(public.getName()))

    # time to marshal the structures and save them to disk so we can send them the remote system
    pubb = public.Marshal()
    with open(publicpath, 'xb') as pubf:
        pubf.write(pubb)
    sensb = sensitive.Marshal()
    with open(sensitivepath, 'xb') as sensf:
        sensf.write(sensb)
    credb = credblob.Marshal()
    with open(credpath, 'xb') as credf:
        credf.write(credb)
    secretb = secret.Marshal()
    with open(secretpath, 'xb') as secretf:
        secretf.write(secretb)

    
if __name__ == '__main__':
    if len(sys.argv) < 6:
        sys.stderr.write(f"usage: {sys.argv[0]} ek-public temp-public temp-sensitive credblob secret\n")
        exit(1)
    main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], b"example secret")

Arguments to the script is the following: ek-public: the path to the public part of the EK temp-public: where to save the public part of the temporary key temp-sensitive: where to save the sensitive part of the temporary key credlob: where to save the encrypted credential generated by MakeCredential secret: where to save the encrypted secret generated by MakeCredential

client script

#!/usr/bin/python3


import sys
from tpm2_pytss import *

def unmarshal_tools_context(ekb):
    ekctx = TPMS_CONTEXT()
    magic = int.from_bytes(ekb[0:4], byteorder='big')
    version = int.from_bytes(ekb[4:8], byteorder='big')
    ekctx.hierarchy = int.from_bytes(ekb[8:12], byteorder='big')
    ekctx.savedHandle = int.from_bytes(ekb[12:16], byteorder='big')
    ekctx.sequence = int.from_bytes(ekb[16:24], byteorder='big')
    ekctx.contextBlob, _ = TPM2B_CONTEXT_DATA.Unmarshal(ekb[24:])
    return ekctx

def eksession(ectx):
    session = ectx.StartAuthSession(
        ESYS_TR.NONE,
        ESYS_TR.NONE,
        None,
        TPM2_SE.POLICY,
        TPMT_SYM_DEF(algorithm=TPM2_ALG.NULL),
        TPM2_ALG.SHA256,
    )

    ectx.PolicySecret(
        ESYS_TR.RH_ENDORSEMENT,
        session,
        TPM2B_NONCE()._cdata,
        TPM2B_DIGEST()._cdata,
        TPM2B_NONCE()._cdata,
        0,
        session1=ESYS_TR.PASSWORD,
    )
    
    return session

def main(ekpath, publicpath, sensitivepath, credpath, secretpath):
    # time to setup a ESAPI context, we will use the default TCTI for the system
    ectx = ESAPI()

    # Time to load the EK context, by using tpm2_createek there is no reason the implement the whole setup in this example code
    with open(ekpath, 'rb') as ekf:
        ekb = ekf.read()
    ekctx = unmarshal_tools_context(ekb)
    ekhandle = ectx.ContextLoad(ekctx)

    # now lets setup the standard EK policy session
    session = eksession(ectx)
    
    # Now we should read, unmarshal and load the temporary key pair
    with open(publicpath, 'rb') as pubf:
        pubb = pubf.read()
    public, _ = TPM2B_PUBLIC.Unmarshal(pubb)
    with open(sensitivepath, 'rb') as sensf:
        sensb = sensf.read()
    sensitive, _ = TPM2B_SENSITIVE.Unmarshal(sensb)
    print(sensitive.sensitiveArea.authValue.size, public.publicArea.authPolicy.size)
    # We will load it under the NULL hierarchy as that is the only hierarchy allowing both the public and private part to be loaded for external keys
    handle = ectx.LoadExternal(sensitive, public, ESYS_TR.RH_NULL)
    
    
    # Time to read and unmarshal the credential and secret for ActivateCredential
    with open(credpath, 'rb') as credf:
        credb = credf.read()
    credblob, _ = TPM2B_ID_OBJECT.Unmarshal(credb)
    with open(secretpath, 'rb') as secretf:
        secretb = secretf.read()
    secret, _ = TPM2B_ENCRYPTED_SECRET.Unmarshal(secretb)
    
    # Well, now there is nothing left but calling ActivateCredential and getting our secret on the remove system!
    oursecret = ectx.ActivateCredential(handle, ekhandle, credblob, secret, session2=session)

    print(f"we got the secret: {bytes(oursecret)}")

if __name__ == '__main__':
    if len(sys.argv) < 6:
        sys.stderr.write(f"usage: {sys.argv[0]} ek-ctx temp-public temp-sensitive credblob secret\n")
        exit(1)
    main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])

Generate the EK context with tpm2_createek -c ek.ctx The arguments are: ek-ctx: the context generated by tpm2_createek temp-public: the temp-public output from the local system script temp-sensitive: the temp-sensitive output from the local system script credblob: the credblob output from the local system script secret: the secret output from the local system script

Issues

there is no TPM based protection against replay attacks in this example