Skip to content

Instantly share code, notes, and snippets.

@salrashid123
Last active August 26, 2026 15:47
Show Gist options
  • Select an option

  • Save salrashid123/458d7fe79f53a4975ba97421fb422074 to your computer and use it in GitHub Desktop.

Select an option

Save salrashid123/458d7fe79f53a4975ba97421fb422074 to your computer and use it in GitHub Desktop.
openbao-tpm-plugin

TPM Key Wrapping

Plugin provides the ability to use a Trusted Platform Module (TPM) to seal and unseal keys

The sealed data can only get recovered on the same TPM it was sealed against.

This library also supports sealing data and binding it to a certain passphrase or TPM PCRs values

For an example of standalone usage, see the example below.

Environment Variable Override

You can override the configuration values provided in the file by using the following env-var overrides

Option Description
TPM_PATH Path to the TPM device (default: /dev/tpmrm0)
TPM_PCRVALUES Comma separated PCR banks to bind the key to. See Policy Constrains section (default: ``)
TPM_USERAUTH Password to bind the key to. See Policy Constrains section (default: ``)
TPM_HIERARCHYAUTH Password of the TPM hierarchy (default: "")
TPM_KEY (optional) Seal/unseal will load this AES PEM formatted key instead of creating one (default: "")

Configuration Variables

You can specify the same values in the config file variables:

Option Description
tpm_path Path to the TPM device (default: /dev/tpmrm0)
pcr_values Comma separated PCR banks to bind the key to. See Policy Constrains section (default: ``)
user_auth Password to bind the key to. See Policy Constrains section (default: ``)
hierarchy_auth Password of the TPM hierarchy (default: "")
key (optional) Seal/unseal will load this AES PEM formatted key instead of creating one (default: "")

for example,

seal "tpm" {
  tpm_path = "/dev/tpmrm0"
  tpm_pcrvalues = "23:0000000000000000000000000000000000000000000000000000000000000000"
  tpm_userauth = "foo"
}

TPM Policy Constraints

TPM wrapper support the following constraints set while sealing which must be fulfilled while unsealing

UserAuth

The userauth constraints basically a passphrase which must be provided when sealing the data and the same passphrase for unsealing.

export TPM_USERAUTH=foo

or

seal "tpm" {
  tpm_path = "/dev/tpmrm0"
  user_auth=foo
} 

PCR

For PCR binding, specify the PCR index and sha256 hex value to bind to.

Each pcr bank must comma separated and formatted as int(index):hex(sha256(pcr_value)).

For example, to bind to pcrs 15, 23 which has the following values,

$ tpm2_pcrread sha256:15,23
 sha256:
	15: 0x0000000000000000000000000000000000000000000000000000000000000000
	23: 0xF5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B

the string format for to specify would be the following. Note the index value should be ascending and the 0x from tpm2_pcrread format is omitted.

export TPM_PCRVALUES=15:0000000000000000000000000000000000000000000000000000000000000000,23:F5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B

or

seal "tpm" {
  tpm_path = "/dev/tpmrm0"
  pcr_values=15:0000000000000000000000000000000000000000000000000000000000000000,23:F5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B
} 

External Key

By default, this plugin will generate a new AES256-CTR key for a new seal operation and the same key will be used for unsealing.

If you would rather specify a key you generated externally, you can set the key parameter in the config or environment variable.

The key prameter must be the PEM encoded formate for an AES-CTR256 key with the PCRPolicy and PolicyAuthValue set and the parent must be the H2 Primary

For example,

export TPM2TOOLS_TCTI="swtpm:port=2321"

## create an H2 primary
printf '\x00\x00' > unique.dat
tpm2_createprimary -C o -G ecc  -g sha256  -c primary.ctx -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|noda|restricted|decrypt" -u unique.dat

## setup the trial policy
tpm2_startauthsession -S session.dat
tpm2_pcrread sha256:23 -o pcr23_val.bin
tpm2_policypcr -S session.dat -l sha256:23  -L policy.dat -f pcr23_val.bin
tpm2_policyauthvalue -S session.dat -L policy.dat
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l  

## create the key
tpm2_create -g sha256 -G aes256ctr -u key.pub -r key.prv -C primary.ctx -L policy.dat  -p foo
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l  

## now encode it as PEM and:
tpm2_encodeobject -C primary.ctx -u key.pub -r  key.prv -o private.pem -p

$ cat private.pem 
-----BEGIN TSS2 PRIVATE KEY-----
MIIBDAYGZ4EFCgEDoAMBAQECBEAAAAEEVABSACUACwAGAHIAIFdonSCswgZqeft1
2oXASbTDMv/u/x+E9n+Oa9gVtMmUAAYBAABAACDaYRUZfv/VjWXNqKydy1W6A8ke
UyU3VCTtt6Uzt/eZYwSBoACeACB8KqcsrhIV6vj+zYNY8qn51hiB/A1f6wfdnLRY
D5JhsAAQYXHuKO+iHa8/JyXCco2/3z7tdinL95LbeY35Khzkb1Su4BCwpXN0mqqM
0+aGhAAXcqZTjfbEPyOMS7v6KKxecjT3MF1BU5k4WqaZzaGEk6hfyTpcg6hSLSop
FiVG9F+cupwPWhle2caSq2GtFgMrbjVGuwfFxu+19Ig=
-----END TSS2 PRIVATE KEY-----


## to test encryption/decryption
tpm2_load -C primary.ctx -u key.pub -r key.prv -n key.name -c aes.ctx

echo "foo" > secret.dat
openssl rand  -out iv.bin 16

tpm2_startauthsession --policy-session -S session.dat
tpm2_pcrread sha256:23 -o pcr23_val.bin
tpm2_policypcr -S session.dat -l sha256:23  -L policy.dat -f pcr23_val.bin
tpm2_policyauthvalue -S session.dat -L policy.dat

tpm2_encryptdecrypt -Q --iv iv.bin  -c aes.ctx -o cipher.out   secret.dat  -p"session:session.dat+foo"
tpm2_flushcontext -t && tpm2_flushcontext -s && tpm2_flushcontext -l  

## redo the policy again to decrypt
tpm2_startauthsession --policy-session -S session.dat
tpm2_pcrread sha256:23 -o pcr23_val.bin
tpm2_policypcr -S session.dat -l sha256:23  -L policy.dat -f pcr23_val.bin
tpm2_policyauthvalue -S session.dat -L policy.dat

tpm2_encryptdecrypt -Q --iv iv.bin  -c aes.ctx -d -o plain.out cipher.out  -p"session:session.dat+foo" 

which means the config file would be something like:

seal "tpm" {
  tpm_path = "127.0.0.1:2321"
  pcr_values = "23:0000000000000000000000000000000000000000000000000000000000000000"
  user_auth = "foo"
  key = "-----BEGIN TSS2 PRIVATE KEY-----\nMIIBDAYGZ4EFCgEDoAMBAQECBEAAAAEEVABSACUACwAGAHIAIFdonSCswgZqeft1\n2oXASbTDMv/u/x+E9n+Oa9gVtMmUAAYBAABAACDaYRUZfv/VjWXNqKydy1W6A8ke\nUyU3VCTtt6Uzt/eZYwSBoACeACB8KqcsrhIV6vj+zYNY8qn51hiB/A1f6wfdnLRY\nD5JhsAAQYXHuKO+iHa8/JyXCco2/3z7tdinL95LbeY35Khzkb1Su4BCwpXN0mqqM\n0+aGhAAXcqZTjfbEPyOMS7v6KKxecjT3MF1BU5k4WqaZzaGEk6hfyTpcg6hSLSop\nFiVG9F+cupwPWhle2caSq2GtFgMrbjVGuwfFxu+19Ig=\n-----END TSS2 PRIVATE KEY-----"
}

Common Errors

  • Invalid Password

Indicates the passphrased used during sealing does not match what is set for unsealing.

[WARN]  failed to unseal core: error="fetching stored unseal keys failed: failed to decrypt keys from storage: go-tpm-wrapping: error executing unseal: TPM_RC_AUTH_FAIL (session 1): the authorization HMAC check failed and DA counter incremented"

Please note that repeated attempts with an incorrect phassphrase can result in a TPM Dictionary Lockout (TPM_RC_LOCKOUT)

You can reset the lockout using tpm2_dictionarylockout command from tpm2_tools

tpm2_dictionarylockout --clear-lockout
  • Invalid PCR

Likely indicates the PCR value used to seal the key does not match match what is in the config file or in the actual PCR bank:

The following indicates the PCR value provided to the wrapper does not match the existing value of the PCR banks

[WARN]  failed to unseal core: error="fetching stored unseal keys failed: failed to decrypt keys from storage: go-tpm-wrapping: error executing PolicyPCR: TPM_RC_VALUE (parameter 1): value is out of range or is not correct for the context"

The following indicates the PCR values provided to the wrapper matches what the TPM values are but does not match what the key was initially bound to

[WARN]  failed to unseal core: error="fetching stored unseal keys failed: failed to decrypt keys from storage: go-tpm-wrapping: error executing unseal: TPM_RC_POLICY_FAIL (session 1): a policy check failed"
  • Invalid Encrypted Key for TPM

Indicates the TPM based sealing key cannot be decrypted by the given TPM.

Likley the TPM that was used to encrypt the sealing key is not the same one currently being used.

[WARN]  failed to unseal core: error="fetching stored unseal keys failed: failed to decrypt keys from storage: go-tpm-wrapping:  error executing Load: TPM_RC_INTEGRITY (parameter 1): integrity check failed"

Testing

These tests uses a softwareTPM which can be found here.

First initialize it:

rm -rf /tmp/myvtpm && \
mkdir /tmp/myvtpm && \
swtpm_setup --tpmstate /tmp/myvtpm --tpm2 --create-ek-cert && swtpm socket --tpmstate dir=/tmp/myvtpm --tpm2 --server type=tcp,port=2321 --ctrl type=tcp,port=2322 --flags not-need-init,startup-clear --log level=5

export TPM2TOOLS_TCTI="swtpm:port=2321"

Now that you've completed the required setup, you can run the tests via:

export VAULT_ACC=true
go test -v

You should see

$ go test -v
=== RUN   TestDisableEnv
--- PASS: TestDisableEnv (0.01s)
=== RUN   TestTPMSeal
=== RUN   TestTPMSeal/config
--- PASS: TestTPMSeal (0.00s)
    --- PASS: TestTPMSeal/config (0.00s)
=== RUN   TestTPMSeal_Lifecycle
--- PASS: TestTPMSeal_Lifecycle (0.01s)
=== RUN   TestTPMSeal_LifecycleExternalKey
--- PASS: TestTPMSeal_LifecycleExternalKey (0.01s)
=== RUN   TestTPMSeal_Lifecycle_UserAuth_Pass
--- PASS: TestTPMSeal_Lifecycle_UserAuth_Pass (0.01s)
=== RUN   TestTPMSeal_Lifecycle_UserAuth_Fail
--- PASS: TestTPMSeal_Lifecycle_UserAuth_Fail (0.01s)
=== RUN   TestTPMSeal_Lifecycle_PCR_Pass
--- PASS: TestTPMSeal_Lifecycle_PCR_Pass (0.01s)
=== RUN   TestTPMSeal_Lifecycle_PCR_Fail
--- PASS: TestTPMSeal_Lifecycle_PCR_Fail (0.01s)
PASS
ok  	github.com/openbao/go-kms-wrapping/wrappers/tpm/v2	0.087s

Note that the TestTPMSeal_Lifecycle_PCR_Fail changes the value of PCR=23. What that means is you must restart the software TPM again to rerun the test suite. Alternatively, you can skip just that one:

go test -v -count=1  -skip '^(TestTPMSeal_Lifecycle_PCR_Fail)'

You can also just use the Dockerfile to run the tests above

docker build  --progress=plain   -f wrappers/tpm/Dockerfile .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment