Skip to content

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

Open
shjala wants to merge 8 commits intolf-edge:masterfrom
shjala:tpm-param-enc-salted-hmac
Open

Add salted HMAC session with AES-128-CFB parameter encryption for TPM seal/unseal#5711
shjala wants to merge 8 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

  1. Deploy 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".
  2. 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).
  3. 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 74.64286% with 71 lines in your changes missing coverage. Please review.
✅ Project coverage is 29.50%. Comparing base (2281599) to head (629613a).
⚠️ Report is 379 commits behind head on master.

Files with missing lines Patch % Lines
pkg/pillar/evetpm/enc_seal.go 78.13% 27 Missing and 27 partials ⚠️
pkg/pillar/evetpm/tpm.go 48.48% 11 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5711      +/-   ##
==========================================
+ Coverage   19.52%   29.50%   +9.97%     
==========================================
  Files          19       25       +6     
  Lines        3021     4480    +1459     
==========================================
+ Hits          590     1322     +732     
- Misses       2310     2900     +590     
- Partials      121      258     +137     

☔ 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 added 4 commits March 31, 2026 10:24
… 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>
@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.

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>
@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 added 3 commits April 2, 2026 10:58
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>
@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?

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.

3 participants