Introduce pkg/tpm to detect and describe TPMs - #470
Conversation
|
|
||
| // Info describes the Trusted Platform Module (TPM) on the host system. | ||
| type Info struct { | ||
| Present bool `json:"present"` |
There was a problem hiding this comment.
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)
| // Info describes the Trusted Platform Module (TPM) on the host system. | ||
| type Info struct { | ||
| Present bool `json:"present"` | ||
| Manufacturer string `json:"manufacturer"` |
There was a problem hiding this comment.
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 :)
| Present bool `json:"present"` | ||
| Manufacturer string `json:"manufacturer"` | ||
| FirmwareVersion string `json:"firmware_version"` | ||
| SpecVersion string `json:"spec_version"` |
There was a problem hiding this comment.
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).
| if file, err := os.Open(filepath.Join(tpmPath, "caps")); err == nil { | ||
| defer file.Close() |
There was a problem hiding this comment.
To get rid of the linter failure, use util.SafeClose()
|
|
||
| func (i *Info) load(ctx context.Context) error { | ||
| paths := linuxpath.New(ctx) | ||
| tpmPath := filepath.Join(paths.SysClassTpm, "tpm0") |
There was a problem hiding this comment.
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".
|
|
||
| switch key { | ||
| case "Manufacturer": | ||
| i.Manufacturer = val |
There was a problem hiding this comment.
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.
|
|
||
| if i.Manufacturer == "" { | ||
| if b, err := os.ReadFile(filepath.Join(tpmPath, "device", "vendor")); err == nil { | ||
| i.Manufacturer = strings.TrimSpace(string(b)) |
There was a problem hiding this comment.
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 :(
| } | ||
| i.Present = true | ||
|
|
||
| // TPM 1.2 drivers expose a caps file with manufacturer, spec and |
There was a problem hiding this comment.
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)
| // TPM 2.0 devices have no caps file; the major spec version is exposed | ||
| // separately (kernel >= 5.5). |
There was a problem hiding this comment.
Technically, TPM 2.0 devices can have a caps file exposed via sysfs. But the SpecVersion field in that file will not be set.
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>
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>
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>
|
@jaypipes I think I addressed the rest of the comments. Please have another look. |
| SysClassDRM string | ||
| SysClassDMI string | ||
| SysClassNet string | ||
| SysClassTpm string |
There was a problem hiding this comment.
@europaul sorry, just noticed this... please make this SysClassTPM.
See here about recommended Go variable names involving initialisms.
There was a problem hiding this comment.
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>
Adds a new
pkg/tpmpackage that reports whether the host system has a Trusted PlatformModule and, when available, describes it:
TPMInfo.Presentis true when/sys/class/tpm/tpm0exists.TPMInfo.Manufacturer,TPMInfo.SpecVersionandTPMInfo.FirmwareVersionare parsedfrom the TPM 1.2 driver's
capsfile. TPM 2.0 devices expose nocapsfile, so themanufacturer falls back to the device
vendorattribute and the spec version to thetpm_version_majorattribute (kernel >= 5.5), yielding e.g.2.0.SysClassTpmis added tolinuxpath, and the tpm class attributes are added to thesnapshot clone content so detection also works against ghw snapshots.
HostInfo, theghwpackage aliases (ghw.TPM(),ghw.TPMInfo), a newghwc tpmcommand (including the default human-format output), and the README.Testing
capsparsing, the TPM 2.0vendor/
tpm_version_majorfallback, and the absent case.go build ./... && go vet ./... && go test ./...pass on Linux; cross-compiles forwindows/darwin.
ghwc tpmprintstpm manufacturer= firmware_version= spec_version=2.0.