Skip to content

Add salted HMAC session with AES-128-CFB parameter encryption for TPM seal/unseal#5711

Merged
eriknordmark merged 10 commits intolf-edge:masterfrom
shjala:tpm-param-enc-salted-hmac
Apr 20, 2026
Merged

Add salted HMAC session with AES-128-CFB parameter encryption for TPM seal/unseal#5711
eriknordmark merged 10 commits intolf-edge:masterfrom
shjala:tpm-param-enc-salted-hmac

Conversation

@shjala
Copy link
Copy Markdown
Member

@shjala shjala commented Mar 27, 2026

Description

72d915f Protect the disk encryption key on the CPU-TPM bus by using salted HMAC sessions with AES-128-CFB parameter encryption for seal and unseal operations. The session salt is encrypted with the EK public key.

TPMs that do not support AES-128-CFB (probed via TPM2_TestParms) fall back to the legacy unencrypted seal/unseal path, preserving compatibility with all hardware. The sealed blob format is kept compatible with the legacy API to support both upgrade and rollback scenarios.

8929afe fixes silently failing TPM unit tests in CI.

Seal (Create): plaintext vs. encrypted on the wire

The same secret ("secret", 6 bytes) is sealed in two test paths. The difference is visible directly in the Create command bytes captured by the sniffer.

TestSealUnsealLegacy - no parameter encryption:

The legacy path uses a password session (TPM_RS_PW = 0x40000009). The inSensitive is sent in plaintext:

>>> Command: Create (tag=0x8002 size=93)
00000000  80 02 00 00 00 5d 00 00  01 53 81 00 00 02 00 00   |.....]...S......|
00000010  00 09 40 00 00 09 00 00  01 00 00 00 0a 00 00 00   |..@.............|
00000020  06 73 65 63 72 65 74 00  2e 00 08 00 0b 00 00 00   |.secret.........|
          ^^^^^^^^^^^^^^^^^^
          "secret" visible in plaintext at offset 0x21

TestSealUnseal - with AES-128-CFB parameter encryption:

The encrypted path uses an HMAC session with AES-128-CFB. The client encrypts inSensitive before sending.

>>> Command: Create (tag=0x8002 size=141)
00000000  80 02 00 00 00 8d 00 00  01 53 81 00 00 02 00 00   |.........S......|
00000010  00 39 02 00 00 01 00 10  84 4b 68 13 66 60 93 57   |.9.......Kh.f`.W|
00000020  ef 9f 57 7b 1a ae d5 a2  20 00 20 df 56 b7 d9 fe   |..W{.... . .V...|
                                   ^^
                                   session attrs = 0x20 (decrypt bit set)
00000030  4c 83 e6 43 50 58 75 67  73 54 bf c8 52 41 d9 90   |L..CPXugsT..RA..|
00000040  88 a3 66 4e 0f 51 0c 38  ee 6f 84 00 0a 16 db bb   |..fN.Q.8.o......|

"secret" is not visible anywhere in the command.

Unseal response: plaintext vs. ciphertext on the wire

TestSealUnsealLegacy - no parameter encryption:

Unseal command, the outData comes back in plaintext:

>>> Command: Unseal (tag=0x8002 size=27)
00000010  00 39 03 00 00 00 00 00  01 00 00 ...
                              ^^
                              session attrs = 0x01 (no encrypt bit)

<<< Response to Unseal: rc=0x00000000 (tag=0x8002 size=43)
00000000  80 02 00 00 00 2b 00 00  00 00 00 00 00 08 00 06   |.....+..........|
00000010  73 65 63 72 65 74 00 10  96 1d 0d ca 47 f0 1c 5f   |secret......G.._|
          ^^^^^^^^^^^^^^^^^^
          "secret" visible in plaintext at offset 0x10

TestSealUnseal - with AES-128-CFB parameter encryption :

Unseal command, the TPM AES-128-CFB encrypts outData before putting it on the bus.

>>> Command: Unseal (tag=0x8002 size=75)
00000010  00 39 03 00 00 00 00 10  38 25 77 5e 38 83 1b fb   |.9......8%w^8...|
00000020  39 17 41 40 2d 94 bf fc  41 00 20 31 00 80 06 cc   |9.A@-...A. 1....|
                              ^^
                              session attrs = 0x41 (encrypt bit set)

<<< Response to Unseal: rc=0x00000000 (tag=0x8002 size=75)
00000000  80 02 00 00 00 4b 00 00  00 00 00 00 00 08 00 06   |.....K..........|
00000010  ac 35 fe dc f8 7e 00 10  64 7b 10 31 18 ab ba 5d   |.5...~..d{.1...]|
          ^^^^^^^^^^^^^^^^^^
          AES-128-CFB ciphertext at offset 0x10 — "secret" is not visible

PR dependencies

N/A

How to test and validate this PR

Unit tests

The following unit tests in cover the new functionality.

Test Coverage
TestSealUnseal Public SealDiskKey/UnsealDiskKey API round-trip (exercises the AES-CFB dispatch logic)
TestSealUnsealWithParamEnc Encrypted seal/unseal round-trip using sealDiskKeyEncrypted/unsealDiskKeyEncrypted, plus unseal failure after PCR extend
TestSealUnsealLegacy Legacy (unencrypted) seal/unseal round-trip using sealDiskKeyLegacy/unsealDiskKeyLegacy
TestSealLegacyUnsealModern Upgrade path: key sealed with legacy API is successfully unsealed with the new encrypted session API
TestSealModernUnsealLegacy Rollback path: key sealed with new encrypted session API is successfully unsealed with the legacy API

Run with:

./tests/tpm/prep-and-test.sh

Manual validation

  • Deploy this PR on hardware with a discrete TPM that supports AES-128-CFB (most modern TPMs do). Verify the vault key seals and unseals successfully. Check pillar logs for "TPM supports AES-128-CFB, sealing with parameter encryption".
  • Upgrade from a release using the old (unencrypted) seal path to this release. Verify the vault unlocks successfully on first boot and "TPM supports AES-128-CFB" appears in the logs.
  • Rollback from this release to a prior release. Verify the vault unlocks successfully (encrypted-sealed key unsealed with the legacy API).

Changelog notes

Seal/unseal of the disk encryption key now uses AES-128-CFB parameter encryption on the CPU-TPM bus to prevent passive bus snooping, with automatic fallback for TPMs that lack AES support.

PR Backports

N/A

Checklist

  • I've provided a proper description
  • I've added the proper documentation
  • I've tested my PR on amd64 device
  • I've tested my PR on arm64 device
  • I've written the test verification instructions
  • I've set the proper labels to this PR

For backport PRs (remove it if it's not a backport):

  • I've added a reference link to the original PR
  • PR's title follows the template

And the last but not least:

  • I've checked the boxes above, or I've provided a good reason why I didn't
    check them.

Please, check the boxes above after submitting the PR in interactive mode.

@shjala shjala added the enhancement New feature or request label Mar 27, 2026
@shjala
Copy link
Copy Markdown
Member Author

shjala commented Mar 27, 2026

Unit-tests covers the general and upgrade tests, but I need to test this manually too, in addition I want to test this with my snooping script to make sure everything is actually getting encrypted,
so please wait.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 28, 2026

Codecov Report

❌ Patch coverage is 73.17073% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 29.10%. Comparing base (2281599) to head (1ccc6ae).
⚠️ Report is 528 commits behind head on master.

Files with missing lines Patch % Lines
pkg/pillar/evetpm/enc_seal.go 78.32% 31 Missing and 31 partials ⚠️
pkg/pillar/evetpm/tpm.go 38.09% 20 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5711      +/-   ##
==========================================
+ Coverage   19.52%   29.10%   +9.57%     
==========================================
  Files          19       25       +6     
  Lines        3021     4522    +1501     
==========================================
+ Hits          590     1316     +726     
- Misses       2310     2946     +636     
- Partials      121      260     +139     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shjala shjala requested a review from yash-zededa as a code owner March 28, 2026 08:36
@github-actions github-actions Bot requested a review from uncleDecart March 28, 2026 08:36
@shjala shjala force-pushed the tpm-param-enc-salted-hmac branch 11 times, most recently from eac2fb3 to 9882322 Compare March 28, 2026 11:01
@rucoder
Copy link
Copy Markdown
Contributor

rucoder commented Mar 30, 2026

@shjala is there a way to detect that TPM is not a standalone chip? in case of TPM integrated into e.g CPU or ASIC when the bus is not exposed we can reduce complexity and probability of failure if we fallback to unencrypted session. But of course encryption makes power dissipation and similar attacks harder

@shjala
Copy link
Copy Markdown
Member Author

shjala commented Mar 30, 2026

@shjala is there a way to detect that TPM is not a standalone chip? in case of TPM integrated into e.g CPU or ASIC when the bus is not exposed we can reduce complexity and probability of failure if we fallback to unencrypted session. But of course encryption makes power dissipation and similar attacks harder

I don't know of any reliable way to detect this except by maintaining a list of vendors and firmwares. Besides, PR detects if TPM is not compatible and switches to legacy mode.

@shjala shjala force-pushed the tpm-param-enc-salted-hmac branch from 9882322 to 629613a Compare March 31, 2026 08:24
@eriknordmark
Copy link
Copy Markdown
Contributor

Is there something we should add to SECURITY-ARCHITECTURE.md about this?

@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 1, 2026

Is there something we should add to SECURITY-ARCHITECTURE.md about this?

@eriknordmark good idea, I'll add another commit.

@shjala shjala force-pushed the tpm-param-enc-salted-hmac branch from 8513503 to c698545 Compare April 2, 2026 08:37
@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 2, 2026

@eriknordmark Changes:

  • Added a section to the SECURITY-ARCHITECTURE.md‎
  • Added a tool for sniffing TPM (swtpm) communications
  • Added sniffer test result in PR description, showing both seal and unseal data are encrypted in the new mode

@shjala shjala force-pushed the tpm-param-enc-salted-hmac branch from c698545 to 2626abb Compare April 2, 2026 08:58
@eriknordmark
Copy link
Copy Markdown
Contributor

  • Upgrade from a release using the old (unencrypted) seal path to this release. Verify the vault unlocks successfully on first boot (legacy-sealed key unsealed with the new encrypted API).

I don't understand how this can be tested. After an EVE update the PCRs will not match hence the new version of EVE will go through the attestation path and then receive the encrypted vault key from the controller. After that will it not use the encrypted approach to seal it under the new PCRs?

@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 2, 2026

I don't understand how this can be tested. After an EVE update the PCRs will not match hence the new version of EVE will go through the attestation path and then receive the encrypted vault key from the controller.

My mistake, I was thinking about the sealed key format rather than the actual operation requirements.

After that will it not use the encrypted approach to seal it under the new PCRs?

Yes it will, it defaults to encrypted sessions after the upgrade (even during the initial boot after upgrade and receiving the backup key from controller).

@eriknordmark
Copy link
Copy Markdown
Contributor

A yetus thing to fix or silence:
yetus: pkg/pillar/evetpm/enc_seal.go#L264
codespell: EncryptIn ==> encrypting, encrypt in, encryption

@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 6, 2026

A yetus thing to fix or silence: yetus: pkg/pillar/evetpm/enc_seal.go#L264 codespell: EncryptIn ==> encrypting, encrypt in, encryption

That is the parameter name in TPM specification, I don't want to change it. After a second look, it is actually the parameter name in the code :
image

@eriknordmark
Copy link
Copy Markdown
Contributor

This doesn't run eden due to the bug with an attempt to fix in #5806

@shjala shjala force-pushed the tpm-param-enc-salted-hmac branch from 1729674 to a990a67 Compare April 16, 2026 09:53
@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 16, 2026

This doesn't run eden due to the bug with an attempt to fix in #5806

Rebased on master.

@github-actions github-actions Bot requested a review from eriknordmark April 16, 2026 09:54
@eriknordmark
Copy link
Copy Markdown
Contributor

Note that 4/4 eden smoke tests fail in the known FAIL: TestHWInventory

@eriknordmark
Copy link
Copy Markdown
Contributor

@shjala can you update the go.mod for the sniffer tool?

The PR adds a new tests/tpm/sniffer/go.mod that declares go 1.22.5, which the
OSV scanner flags with ~20 stdlib vulnerabilities (GO-2024-3105 through
GO-2025-4007). The latest fix for all of them requires at least Go 1.24.9 (for
GO-2025-4007).

Other go.mod files in the repo are already on Go 1.24.x — so the go directive
in tests/tpm/sniffer/go.mod should be bumped from 1.22.5 to 1.24.9 (or
whatever the current latest is). The golang.org/x/sys indirect dep version may
also need a go mod tidy pass after the version bump.

@europaul
Copy link
Copy Markdown
Contributor

Note that 4/4 eden smoke tests fail in the known FAIL: TestHWInventory

@eriknordmark is this a known error? what's causing it? I don't think I've seen it before

@eriknordmark
Copy link
Copy Markdown
Contributor

@shjala can you update the go.mod for the sniffer tool?

Claude claims the fix is just to change
tests/tpm/sniffer/go.mod — one-line diff:
-go 1.22.5
+go 1.24.9

but it makes sense to manually check that a go mod tidy isn't needed.

@eriknordmark
Copy link
Copy Markdown
Contributor

Note that 4/4 eden smoke tests fail in the known FAIL: TestHWInventory

@eriknordmark is this a known error? what's causing it? I don't think I've seen it before

@europaul 90+% of the current Eden smoke failures I see are like this one.

Copy link
Copy Markdown
Contributor

@eriknordmark eriknordmark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shjala shjala force-pushed the tpm-param-enc-salted-hmac branch from a990a67 to e03dcce Compare April 17, 2026 08:20
@github-actions github-actions Bot requested a review from eriknordmark April 17, 2026 08:21
shjala added 10 commits April 17, 2026 10:21
… seal/unseal

Protect the disk encryption key on the CPU-TPM bus by using salted HMAC
sessions with AES-128-CFB parameter encryption for seal and unseal
operations. The session salt is encrypted with the EK public key.

TPMs that do not support AES-128-CFB (probed via TPM2_TestParms) fall
back to the legacy unencrypted seal/unseal path, preserving compatibility
with all hardware. The sealed blob format is kept compatible with the
legacy API to support both upgrade and rollback scenarios.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
Add the go-tpm library as a vendor dependency.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
TPM unit tests in prep-and-test.sh were silently passing in CI even when
individual tests failed, This commit fixes the root causes and ensures
CI fails when any test fails.

Fixes:
- Build libtpms v0.10.0 and swtpm (commit 732bbd6) from source to match
  pkg/vtpm/Dockerfile, avoiding "Unknown option 'terminate'" and version
  mismatch errors with distro packages
- Build ZFS from source and purge distro ZFS/libtpms before building to
  prevent DSO conflicts (zpool_search_import, libzfs_core missing symbols)
- Create the EK with the standard EK auth policy so both msrv's
  policy-session-based ActivateCredential and vcomlink's plain-password
  ActivateCredential work against the same handle
- Store a self-signed cert in the EK NV index for vcomlink EK cert tests
- Run all TPM tests via a run_test() wrapper that accumulates failures and
  exits non-zero at the end, so CI catches individual failures without
  aborting the rest of the suite

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
Run TPM test before genric go tests.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
Add a TPM sniffer to capture the communication between the TPM and the
host during the tests. This will help us analyze the parameters being
sent to the TPM and identify any potential issues.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
Add sniffer mode to TPM test script for traffic interception and analysis.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
Add script to run TPM tests in a Docker container, this is used
for debugging and development of the TPM tests in envirnonments that
mimic the CI environment.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
Add TPM bus protection details via parameter encryption.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
When UnsealDiskKey fails and we fall back to RecoverDiskKeyPolicyPcr,
the original unseal error was silently discarded. Add a log parameter
to UnsealDiskKeyWithRecovery and log the initial unseal failure as a
warning before attempting recovery.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
Pass the actual PCR digest to PolicyPCR during the trial session so the
policy digest is computed correctly. Also set AdminWithPolicy and the
required null scheme on the keyed hash object as mandated by the TPM spec.

Signed-off-by: Shahriyar Jalayeri <shahriyar@posteo.de>
@shjala shjala force-pushed the tpm-param-enc-salted-hmac branch from e03dcce to 1ccc6ae Compare April 17, 2026 08:21
@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 17, 2026

changes :

  • rebased on latest master
  • updated sniffer go version and deps

@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 17, 2026

@eriknordmark @rene please do not merge, I want to add another commit.

@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 17, 2026

@eriknordmark @rene please do not merge, I want to add another commit.

I initially wanted to secure our Endorsement Key validation against bus interception by storing the trusted EK Name in a permanently locked NV index on first boot. The plan was to read the TPM EK on subsequent boots and compare it securely against this locked index value. But we run into a chicken and egg problem. Because communication between the CPU and TPM is unencrypted by default (we are trying to solve this here), we must use a Salted HMAC Session to encrypt the bus and prevent attacker forging the locked NV read response, in order to do that we must trust EK as it is used for encrypting salt for encrypted communication 🐔🥚.

Anyways, this is ready for final review.

Copy link
Copy Markdown
Contributor

@eriknordmark eriknordmark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@eriknordmark
Copy link
Copy Markdown
Contributor

@eriknordmark @rene please do not merge, I want to add another commit.

Is another commit comming? And what about the CVE scan/go.mod version issue for the snooper?

@shjala
Copy link
Copy Markdown
Member Author

shjala commented Apr 20, 2026

@eriknordmark @rene please do not merge, I want to add another commit.

Is another commit comming? And what about the CVE scan/go.mod version issue for the snooper?

@eriknordmark fix the CVE issue already 6a11283, another commit was this #5711 (comment) which I found it is not going to work.

@eriknordmark eriknordmark merged commit 15dd5b7 into lf-edge:master Apr 20, 2026
41 of 51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants