Skip to content

Introduce pkg/tpm to detect and describe TPMs#470

Open
europaul wants to merge 1 commit into
jaypipes:mainfrom
europaul:add-tpm
Open

Introduce pkg/tpm to detect and describe TPMs#470
europaul wants to merge 1 commit into
jaypipes:mainfrom
europaul:add-tpm

Conversation

@europaul

@europaul europaul commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Adds a new pkg/tpm package that reports whether the host system has a Trusted Platform
Module and, when available, describes it:

  • TPMInfo.Present is true when /sys/class/tpm/tpm0 exists.
  • TPMInfo.Manufacturer, TPMInfo.SpecVersion and TPMInfo.FirmwareVersion are parsed
    from the TPM 1.2 driver's caps file. TPM 2.0 devices expose no caps file, so the
    manufacturer falls back to the device vendor attribute and the spec version to the
    tpm_version_major attribute (kernel >= 5.5), yielding e.g. 2.0.
  • SysClassTpm is added to linuxpath, and the tpm class attributes are added to the
    snapshot clone content so detection also works against ghw snapshots.
  • Wired into HostInfo, the ghw package aliases (ghw.TPM(), ghw.TPMInfo), a new
    ghwc tpm command (including the default human-format output), and the README.

Testing

  • New chroot-based unit tests cover TPM 1.2 caps parsing, the TPM 2.0
    vendor/tpm_version_major fallback, and the absent case.
  • go build ./... && go vet ./... && go test ./... pass on Linux; cross-compiles for
    windows/darwin.
  • Verified on a real machine with a TPM 2.0: ghwc tpm prints
    tpm manufacturer= firmware_version= spec_version=2.0.

Add a new tpm package that reports whether the host system has a
Trusted Platform Module and, when available, its manufacturer,
firmware version and TCG specification version.

Detection checks for /sys/class/tpm/tpm0. TPM 1.2 details are parsed
from the driver's caps file; for TPM 2.0 devices, which expose no caps
file, the manufacturer falls back to the device vendor attribute and
the spec version to the tpm_version_major attribute (kernel >= 5.5).

The tpm class attributes are added to the snapshot clone content so
detection works against ghw snapshots. TPMInfo is wired into HostInfo,
the ghw package aliases, and a new `ghwc tpm` command.

Signed-off-by: Paul Gaiduk <paulg@zededa.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread pkg/tpm/tpm.go

// Info describes the Trusted Platform Module (TPM) on the host system.
type Info struct {
Present bool `json:"present"`

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Instead of a Present boolean field, let's make a Device struct and populate a Info.Devices field of type []Device similar to how the pci module works. This way we keep the interface between the two modules similar (plus, technically a system can have >1 TPM device, so we make this interface more future-proof)

Comment thread pkg/tpm/tpm.go
// Info describes the Trusted Platform Module (TPM) on the host system.
type Info struct {
Present bool `json:"present"`
Manufacturer string `json:"manufacturer"`

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Let's name this field ManufacturerName and add a separate ManufacturerVendorID field to store the 16-bit TCG Vendor ID. I wish that TCG just used the PCI vendor ID but alas, they did not :)

Comment thread pkg/tpm/tpm.go
type Info struct {
Present bool `json:"present"`
Manufacturer string `json:"manufacturer"`
FirmwareVersion string `json:"firmware_version"`

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This is only relevant for fTPMs, not dTPMs, sTPMs, iTPMs or vTPMs. I think we should add a Type field to the Device struct that indicates whether the TPM is a Firmware, Discrete, Software, Integrated or Virtual TPM type. Thoughts?

On Linux, right now there's not a great way to determine the type other than to just say everything is a Firmware TPM unless it's Infineon, which is a discrete TPM. On Windows, there's better ways to determine the TPM type...

Comment thread pkg/tpm/tpm.go
Present bool `json:"present"`
Manufacturer string `json:"manufacturer"`
FirmwareVersion string `json:"firmware_version"`
SpecVersion string `json:"spec_version"`

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Please add a little docstring above these fields explaining what they contain. In this case, it is the TCG TPM Library version (ISO/IEC 11889).

Comment thread pkg/tpm/tpm_linux.go
Comment on lines +32 to +33
if file, err := os.Open(filepath.Join(tpmPath, "caps")); err == nil {
defer file.Close()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

To get rid of the linter failure, use util.SafeClose()

Comment thread pkg/tpm/tpm_linux.go

func (i *Info) load(ctx context.Context) error {
paths := linuxpath.New(ctx)
tpmPath := filepath.Join(paths.SysClassTpm, "tpm0")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

As mentioned earlier, there can be more than one TPM device on a system, so here we will want to loop over any devices not just check for "tpm0".

Comment thread pkg/tpm/tpm_linux.go

switch key {
case "Manufacturer":
i.Manufacturer = val

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This will be the hex-encoded string value of the Manufacturer's string name. For example, 0x414D4400 for "AMD". I think we should convert the hex-encoded string value to ASCII here.

Comment thread pkg/tpm/tpm_linux.go

if i.Manufacturer == "" {
if b, err := os.ReadFile(filepath.Join(tpmPath, "device", "vendor")); err == nil {
i.Manufacturer = strings.TrimSpace(string(b))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

For TPM 2.0 devices, this may be the 16-bit TCG Vendor ID, hex-encoded string value. For example, 0x1414 for Microsoft or 0x15D1 for Infineon. Or it may be a 32-bit hex-encoded string value, for example 0x414D4400 for "AMD" (the underlying type is a 32-bit integer in the Linux TPM module header).

To deal with this mess, it's probably best to do a check here (and potentially above on line 46) to see whether the length of the hex-encoded string is 8 or 16. If it's 8, then this is the 16-bit TCG Vendor ID. If it's 16, this is the 32-bit Manufacturer short name. Otherwise, it's a long-form Manufacturer string.

There has unfortunately not been standards applied to sysfs in this area :(

Comment thread pkg/tpm/tpm_linux.go
}
i.Present = true

// TPM 1.2 drivers expose a caps file with manufacturer, spec and

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

TPM 2.0 drivers may also expose this caps file... it's unfortunately not been standardized. But, you catch this below in the block that looks for no SpecVersion being set, so we're good (just update the code comment above)

Comment thread pkg/tpm/tpm_linux.go
Comment on lines +61 to +62
// TPM 2.0 devices have no caps file; the major spec version is exposed
// separately (kernel >= 5.5).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Technically, TPM 2.0 devices can have a caps file exposed via sysfs. But the SpecVersion field in that file will not be set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants