Skip to content

Introduce pkg/tpm to detect and describe TPMs - #470

Merged
jaypipes merged 1 commit into
jaypipes:mainfrom
europaul:add-tpm
Jul 14, 2026
Merged

Introduce pkg/tpm to detect and describe TPMs#470
jaypipes merged 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.

Comment thread pkg/tpm/tpm.go Outdated

// 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 Outdated
// 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
Comment thread pkg/tpm/tpm.go Outdated
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 Outdated
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 Outdated

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 Outdated

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 Outdated

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 Outdated
}
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 Outdated
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.

europaul added a commit to europaul/ghw that referenced this pull request Jul 13, 2026
Rework pkg/tpm in response to the review on jaypipes#470:

- Model TPMs as Info.Devices []*Device, mirroring pkg/pci, so the
  interface is consistent and hosts with more than one TPM are handled.
  load() now enumerates every tpm[0-9]+ entry under /sys/class/tpm
  instead of assuming a single tpm0 (tpmrm* entries are skipped).
- Split the manufacturer into ManufacturerName and ManufacturerVendorID
  (the 16-bit TCG Vendor ID) and decode the hex-encoded sysfs value: a
  32-bit value is a four-character ASCII short name, a 16-bit value is
  the TCG Vendor ID, otherwise it is treated as a literal name.
- Document every Device field; SpecVersion is the TCG TPM library
  (ISO/IEC 11889) version.
- Use util.SafeClose() instead of a bare deferred Close().
- Correct the caps comments: TPM 2.0 devices may also expose a caps
  file, just without a spec version.
- Add the ghw.TPMDevice alias and update the README accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
europaul added a commit to europaul/ghw that referenced this pull request Jul 13, 2026
Rework pkg/tpm in response to the review on jaypipes#470:

- Model TPMs as Info.Devices []*Device, mirroring pkg/pci, so the
  interface is consistent and hosts with more than one TPM are handled.
  load() now enumerates every tpm[0-9]+ entry under /sys/class/tpm
  instead of assuming a single tpm0 (tpmrm* entries are skipped).
- Split the manufacturer into ManufacturerName and ManufacturerVendorID
  (the 16-bit TCG Vendor ID) and decode the hex-encoded sysfs value: a
  32-bit value is a four-character ASCII short name, a 16-bit value is
  the TCG Vendor ID, otherwise it is treated as a literal name.
- Document every Device field; SpecVersion is the TCG TPM library
  (ISO/IEC 11889) version.
- Use util.SafeClose() instead of a bare deferred Close().
- Correct the caps comments: TPM 2.0 devices may also expose a caps
  file, just without a spec version.
- Add the ghw.TPMDevice alias and update the README accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Paul Gaiduk <paulg@zededa.com>
europaul added a commit to europaul/ghw that referenced this pull request Jul 13, 2026
Rework pkg/tpm in response to the review on jaypipes#470:

- Model TPMs as Info.Devices []*Device, mirroring pkg/pci, so the
  interface is consistent and hosts with more than one TPM are handled.
  load() now enumerates every tpm[0-9]+ entry under /sys/class/tpm
  instead of assuming a single tpm0 (tpmrm* entries are skipped).
- Split the manufacturer into ManufacturerName and ManufacturerVendorID
  (the 16-bit TCG Vendor ID) and decode the hex-encoded sysfs value: a
  32-bit value is a four-character ASCII short name, a 16-bit value is
  the TCG Vendor ID, otherwise it is treated as a literal name.
- Document every Device field; SpecVersion is the TCG TPM library
  (ISO/IEC 11889) version.
- Use util.SafeClose() instead of a bare deferred Close().
- Correct the caps comments: TPM 2.0 devices may also expose a caps
  file, just without a spec version.
- Add the ghw.TPMDevice alias and update the README accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Paul Gaiduk <paulg@zededa.com>
@europaul

Copy link
Copy Markdown
Contributor Author

@jaypipes I think I addressed the rest of the comments. Please have another look.

@jaypipes jaypipes left a comment

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.

One teeny nit to address, then all good! Thanks @europaul :)

Comment thread pkg/linuxpath/path_linux.go Outdated
SysClassDRM string
SysClassDMI string
SysClassNet string
SysClassTpm string

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.

@europaul sorry, just noticed this... please make this SysClassTPM.

See here about recommended Go variable names involving initialisms.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done 👍 I also squashed the commit to one now so everything is clean

Add a new tpm package that reports whether the host system has any
Trusted Platform Modules and, when available, each device's
manufacturer, firmware version and TCG specification version.

TPMs are modelled as Info.Devices []*Device, mirroring pkg/pci, so
hosts with more than one TPM are handled. Detection enumerates every
tpm[0-9]+ entry under /sys/class/tpm (tpmrm* entries are skipped).

TPM 1.2 details are parsed from the driver's caps file; for TPM 2.0
devices, which expose no spec version in caps, the manufacturer falls
back to the device vendor attribute and the spec version to the
tpm_version_major attribute (kernel >= 5.5). The manufacturer is split
into ManufacturerName and ManufacturerVendorID (the 16-bit TCG Vendor
ID): a 32-bit sysfs value is decoded as a four-character ASCII short
name, a 16-bit value as the TCG Vendor ID, otherwise it is treated as
a literal name.

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 (including ghw.TPMDevice), and a new
`ghwc tpm` command. The README is updated accordingly.

Signed-off-by: Paul Gaiduk <paulg@zededa.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@jaypipes jaypipes left a comment

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.

Brilliant, thank you @europaul :)

@jaypipes
jaypipes merged commit 3f73db1 into jaypipes:main Jul 14, 2026
15 checks passed
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