Introduce pkg/tpm to detect and describe TPMs#470
Conversation
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>
|
|
||
| // 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 :)
| type Info struct { | ||
| Present bool `json:"present"` | ||
| Manufacturer string `json:"manufacturer"` | ||
| FirmwareVersion string `json:"firmware_version"` |
There was a problem hiding this comment.
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...
| 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.
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.