From 5c7fcf4afc51ce2f000ba5d3e82fd82eb2a1374e Mon Sep 17 00:00:00 2001 From: Paul Gaiduk Date: Tue, 14 Jul 2026 15:14:06 +0200 Subject: [PATCH] Introduce pkg/tpm to detect and describe TPMs 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 Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 47 ++++++++++ alias.go | 8 ++ cmd/ghwc/commands/root.go | 1 + cmd/ghwc/commands/tpm.go | 43 +++++++++ host.go | 10 +- pkg/linuxpath/path_linux.go | 2 + pkg/snapshot/clonetree_linux.go | 3 + pkg/tpm/tpm.go | 91 ++++++++++++++++++ pkg/tpm/tpm_linux.go | 146 +++++++++++++++++++++++++++++ pkg/tpm/tpm_linux_test.go | 161 ++++++++++++++++++++++++++++++++ pkg/tpm/tpm_stub.go | 19 ++++ 11 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 cmd/ghwc/commands/tpm.go create mode 100644 pkg/tpm/tpm.go create mode 100644 pkg/tpm/tpm_linux.go create mode 100644 pkg/tpm/tpm_linux_test.go create mode 100644 pkg/tpm/tpm_stub.go diff --git a/README.md b/README.md index 45aeeba6..1a8b71af 100644 --- a/README.md +++ b/README.md @@ -1236,6 +1236,53 @@ Example output from my personal workstation: watchdog present: true ``` +### TPM (Linux only) + +The `ghw.TPM()` function returns a `ghw.TPMInfo` struct that contains +information about the Trusted Platform Module(s) (TPM) on the host system. + +The `ghw.TPMInfo` struct contains a `Devices` field: + +* `ghw.TPMInfo.Devices` is a slice of pointers to `ghw.TPMDevice` structs, one + for each TPM detected under `/sys/class/tpm` + +Each `ghw.TPMDevice` struct contains multiple fields: + +* `ghw.TPMDevice.ManufacturerName` is a string with the human-readable TPM + manufacturer short name (e.g. `AMD`, `IFX` for Infineon, `STM` for + STMicroelectronics) +* `ghw.TPMDevice.ManufacturerVendorID` is a string with the 16-bit TCG Vendor + ID assigned to the manufacturer, when the host exposes it (e.g. `0x1414`) +* `ghw.TPMDevice.FirmwareVersion` is a string with the TPM firmware version, if + exposed by the driver +* `ghw.TPMDevice.SpecVersion` is a string with the TCG specification version the + TPM implements (e.g. `1.2` or `2.0`) + +```go +package main + +import ( + "fmt" + + "github.com/jaypipes/ghw" +) + +func main() { + tpm, err := ghw.TPM() + if err != nil { + fmt.Printf("Error getting TPM info: %v", err) + } + + fmt.Printf("%v\n", tpm) +} +``` + +Example output from my personal workstation: + +``` +tpm manufacturer_name=STM manufacturer_vendor_id= firmware_version= spec_version=2.0 +``` + ## Advanced Usage ### Disabling warning messages diff --git a/alias.go b/alias.go index 62cfaf49..f80f76e7 100644 --- a/alias.go +++ b/alias.go @@ -22,6 +22,7 @@ import ( pciaddress "github.com/jaypipes/ghw/pkg/pci/address" "github.com/jaypipes/ghw/pkg/product" "github.com/jaypipes/ghw/pkg/topology" + "github.com/jaypipes/ghw/pkg/tpm" "github.com/jaypipes/ghw/pkg/usb" "github.com/jaypipes/ghw/pkg/watchdog" ) @@ -206,3 +207,10 @@ type WatchdogInfo = watchdog.Info var ( Watchdog = watchdog.New ) + +type TPMInfo = tpm.Info +type TPMDevice = tpm.Device + +var ( + TPM = tpm.New +) diff --git a/cmd/ghwc/commands/root.go b/cmd/ghwc/commands/root.go index f718fe23..4404306f 100644 --- a/cmd/ghwc/commands/root.go +++ b/cmd/ghwc/commands/root.go @@ -106,6 +106,7 @@ func showAll(cmd *cobra.Command, args []string) error { showAccelerator, showUSB, showWatchdog, + showTPM, } { err := f(cmd, args) if err != nil { diff --git a/cmd/ghwc/commands/tpm.go b/cmd/ghwc/commands/tpm.go new file mode 100644 index 00000000..ff940615 --- /dev/null +++ b/cmd/ghwc/commands/tpm.go @@ -0,0 +1,43 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package commands + +import ( + "fmt" + + "github.com/jaypipes/ghw" + "github.com/spf13/cobra" +) + +// tpmCmd represents the `tpm` command +var tpmCmd = &cobra.Command{ + Use: "tpm", + Short: "Show TPM information for the host system", + RunE: showTPM, +} + +// showTPM shows TPM information for the host system. +func showTPM(cmd *cobra.Command, args []string) error { + tpm, err := ghw.TPM(cmd.Context()) + if err != nil { + return fmt.Errorf("error getting TPM info: %w", err) + } + + switch outputFormat { + case outputFormatHuman: + fmt.Printf("%v\n", tpm) + case outputFormatJSON: + fmt.Printf("%s\n", tpm.JSONString(pretty)) + case outputFormatYAML: + fmt.Printf("%s", tpm.YAMLString()) + } + return nil +} + +func init() { + rootCmd.AddCommand(tpmCmd) +} diff --git a/host.go b/host.go index 5361e627..26bb9e69 100644 --- a/host.go +++ b/host.go @@ -23,6 +23,7 @@ import ( "github.com/jaypipes/ghw/pkg/pci" "github.com/jaypipes/ghw/pkg/product" "github.com/jaypipes/ghw/pkg/topology" + "github.com/jaypipes/ghw/pkg/tpm" "github.com/jaypipes/ghw/pkg/usb" "github.com/jaypipes/ghw/pkg/watchdog" ) @@ -44,6 +45,7 @@ type HostInfo struct { PCI *pci.Info `json:"pci"` USB *usb.Info `json:"usb"` Watchdog *watchdog.Info `json:"watchdog"` + TPM *tpm.Info `json:"tpm"` } // Host returns a pointer to a HostInfo struct that contains fields with @@ -106,6 +108,10 @@ func Host(args ...any) (*HostInfo, error) { if err != nil { return nil, err } + tpmInfo, err := tpm.New(ctx) + if err != nil { + return nil, err + } return &HostInfo{ CPU: cpuInfo, @@ -122,6 +128,7 @@ func Host(args ...any) (*HostInfo, error) { PCI: pciInfo, USB: usbInfo, Watchdog: watchdogInfo, + TPM: tpmInfo, }, nil } @@ -129,7 +136,7 @@ func Host(args ...any) (*HostInfo, error) { // structs' String-ified output func (info *HostInfo) String() string { return fmt.Sprintf( - "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", info.Block.String(), info.CPU.String(), info.GPU.String(), @@ -144,6 +151,7 @@ func (info *HostInfo) String() string { info.PCI.String(), info.USB.String(), info.Watchdog.String(), + info.TPM.String(), ) } diff --git a/pkg/linuxpath/path_linux.go b/pkg/linuxpath/path_linux.go index b0973874..bd118d63 100644 --- a/pkg/linuxpath/path_linux.go +++ b/pkg/linuxpath/path_linux.go @@ -78,6 +78,7 @@ type Paths struct { SysClassDRM string SysClassDMI string SysClassNet string + SysClassTPM string SysClassWatchdog string SysFirmwareDeviceTree string RunUdevData string @@ -105,6 +106,7 @@ func New(ctx context.Context) *Paths { SysClassDRM: filepath.Join(chroot, roots.Sys, "class", "drm"), SysClassDMI: filepath.Join(chroot, roots.Sys, "class", "dmi"), SysClassNet: filepath.Join(chroot, roots.Sys, "class", "net"), + SysClassTPM: filepath.Join(chroot, roots.Sys, "class", "tpm"), SysClassWatchdog: filepath.Join(chroot, roots.Sys, "class", "watchdog"), SysFirmwareDeviceTree: filepath.Join(chroot, roots.Sys, "firmware", "devicetree", "base"), RunUdevData: filepath.Join(chroot, roots.Run, "udev", "data"), diff --git a/pkg/snapshot/clonetree_linux.go b/pkg/snapshot/clonetree_linux.go index 5d1dba0e..b174bef3 100644 --- a/pkg/snapshot/clonetree_linux.go +++ b/pkg/snapshot/clonetree_linux.go @@ -52,6 +52,9 @@ func ExpectedCloneStaticContent() []string { "/sys/devices/system/node/node*/memory*", "/sys/devices/system/node/node*/hugepages/hugepages-*/*", "/sys/class/watchdog/*", + "/sys/class/tpm/tpm*/caps", + "/sys/class/tpm/tpm*/tpm_version_major", + "/sys/class/tpm/tpm*/device/vendor", } } diff --git a/pkg/tpm/tpm.go b/pkg/tpm/tpm.go new file mode 100644 index 00000000..a41703aa --- /dev/null +++ b/pkg/tpm/tpm.go @@ -0,0 +1,91 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package tpm + +import ( + "fmt" + + "github.com/jaypipes/ghw/internal/config" + "github.com/jaypipes/ghw/pkg/marshal" +) + +// Device describes a single Trusted Platform Module (TPM) present on the host +// system. +type Device struct { + // ManufacturerName is the human-readable name of the TPM manufacturer, + // decoded from the four-character short name the TPM reports (e.g. "AMD", + // "IFX" for Infineon, "STM" for STMicroelectronics). + ManufacturerName string `json:"manufacturer_name"` + // ManufacturerVendorID is the 16-bit TCG Vendor ID assigned to the TPM + // manufacturer by the Trusted Computing Group. Note that this is a + // separate namespace from the PCI vendor ID. It may be empty when the + // host only exposes the manufacturer's short name. + // See: https://trustedcomputinggroup.org/resource/vendor-id-registry/ + ManufacturerVendorID string `json:"manufacturer_vendor_id"` + // SpecVersion is the TCG TPM library (specification) version the device + // implements, i.e. the ISO/IEC 11889 version, e.g. "1.2" or "2.0". + SpecVersion string `json:"spec_version"` + // FirmwareVersion is the TPM firmware version, when the host exposes it. + FirmwareVersion string `json:"firmware_version"` +} + +// String returns a short string describing the TPM device. +func (d *Device) String() string { + return fmt.Sprintf( + "manufacturer_name=%s manufacturer_vendor_id=%s firmware_version=%s spec_version=%s", + d.ManufacturerName, d.ManufacturerVendorID, d.FirmwareVersion, d.SpecVersion, + ) +} + +// Info describes the Trusted Platform Module(s) on the host system. +type Info struct { + // Devices are the TPM devices detected under /sys/class/tpm. A host can, + // in principle, expose more than one TPM device. + Devices []*Device `json:"devices"` +} + +// String returns a short string with summary information about the TPM(s) on +// the host system. +func (i *Info) String() string { + switch len(i.Devices) { + case 0: + return "tpm (no devices)" + case 1: + return "tpm " + i.Devices[0].String() + default: + return fmt.Sprintf("tpm (%d devices)", len(i.Devices)) + } +} + +// New returns a pointer to an Info struct that contains information about +// the TPM(s) on the host system. +func New(args ...any) (*Info, error) { + ctx := config.ContextFromArgs(args...) + info := &Info{} + if err := info.load(ctx); err != nil { + return nil, err + } + return info, nil +} + +// simple private struct used to encapsulate TPM information in a top-level +// "tpm" YAML/JSON map/object key +type tpmPrinter struct { + Info *Info `json:"tpm"` +} + +// YAMLString returns a string with the TPM information formatted as YAML +// under a top-level "tpm:" key +func (i *Info) YAMLString() string { + return marshal.SafeYAML(tpmPrinter{i}) +} + +// JSONString returns a string with the TPM information formatted as JSON +// under a top-level "tpm:" key +func (i *Info) JSONString(indent bool) string { + return marshal.SafeJSON(tpmPrinter{i}, indent) +} diff --git a/pkg/tpm/tpm_linux.go b/pkg/tpm/tpm_linux.go new file mode 100644 index 00000000..7b982269 --- /dev/null +++ b/pkg/tpm/tpm_linux.go @@ -0,0 +1,146 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package tpm + +import ( + "bufio" + "context" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/jaypipes/ghw/pkg/linuxpath" + "github.com/jaypipes/ghw/pkg/util" +) + +func (i *Info) load(ctx context.Context) error { + paths := linuxpath.New(ctx) + + entries, err := os.ReadDir(paths.SysClassTPM) + if err != nil { + // No /sys/class/tpm directory at all means no TPM devices. + return nil + } + + for _, entry := range entries { + // TPM device directories are named tpm0, tpm1, ... A system can + // expose more than one TPM device, so we enumerate all of them + // rather than assuming a single "tpm0". + if !isTPMDeviceName(entry.Name()) { + continue + } + dev := &Device{} + dev.load(ctx, filepath.Join(paths.SysClassTPM, entry.Name())) + i.Devices = append(i.Devices, dev) + } + return nil +} + +// isTPMDeviceName reports whether name looks like a TPM device directory +// (tpm0, tpm1, ...). It deliberately excludes the resource-manager entries +// (e.g. tpmrm0) that share the tpm prefix. +func isTPMDeviceName(name string) bool { + digits, ok := strings.CutPrefix(name, "tpm") + if !ok || digits == "" { + return false + } + _, err := strconv.Atoi(digits) + return err == nil +} + +// load populates the Device from the sysfs directory of a single TPM device +// (e.g. /sys/class/tpm/tpm0). sysfs has never been standardized in this area, +// so every source consulted here is best-effort. +func (d *Device) load(ctx context.Context, tpmPath string) { + // Both TPM 1.2 and (some) TPM 2.0 drivers may expose a `caps` file with + // manufacturer, spec and firmware version information. On TPM 2.0, when a + // `caps` file is present, it does not report the spec version; that is + // filled in separately below. + if file, err := os.Open(filepath.Join(tpmPath, "caps")); err == nil { + defer util.SafeClose(file) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + val := strings.TrimSpace(parts[1]) + + switch key { + case "Manufacturer": + d.setManufacturer(val) + case "TCG version": + d.SpecVersion = val + case "Firmware version": + d.FirmwareVersion = val + } + } + } + + if d.ManufacturerName == "" && d.ManufacturerVendorID == "" { + if b, err := os.ReadFile(filepath.Join(tpmPath, "device", "vendor")); err == nil { + d.setManufacturer(strings.TrimSpace(string(b))) + } + } + + // TPM 2.0 devices expose the major spec version separately (kernel >= 5.5) + // and generally do not report it via the `caps` file. + if d.SpecVersion == "" { + versionMajor := util.SafeIntFromFile( + ctx, filepath.Join(tpmPath, "tpm_version_major"), + ) + if versionMajor > 0 { + d.SpecVersion = strconv.Itoa(versionMajor) + ".0" + } + } +} + +// setManufacturer parses a raw manufacturer value read from sysfs and records +// it as either a human-readable name or a TCG Vendor ID. +// +// The kernel exposes this value inconsistently. It may be: +// - a hex-encoded 32-bit value that is really a four-character ASCII short +// name, e.g. "0x414D4400" -> "AMD" or "0x49465800" -> "IFX" (Infineon); +// - a hex-encoded 16-bit value that is the TCG Vendor ID, e.g. "0x1414" +// (Microsoft) or "0x15D1" (Infineon); +// - a plain string name, e.g. "STM". +func (d *Device) setManufacturer(raw string) { + if raw == "" { + return + } + hexStr := strings.TrimPrefix(strings.TrimPrefix(raw, "0x"), "0X") + if n, err := strconv.ParseUint(hexStr, 16, 64); err == nil { + switch len(hexStr) { + case 4: // 16 bits: the TCG Vendor ID + d.ManufacturerVendorID = "0x" + strings.ToUpper(hexStr) + return + case 8: // 32 bits: a four-character ASCII short name + if name := fourCC(uint32(n)); name != "" { + d.ManufacturerName = name + return + } + } + } + // Fall back to treating the value as a plain manufacturer name. + d.ManufacturerName = raw +} + +// fourCC decodes a 32-bit value as a four-character ASCII string, trimming +// trailing NUL and space padding. It returns "" if the bytes are not printable +// ASCII, in which case the value is not a short name. +func fourCC(v uint32) string { + b := []byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)} + for _, c := range b { + if c != 0 && (c < 0x20 || c > 0x7e) { + return "" + } + } + return strings.TrimRight(string(b), " \x00") +} diff --git a/pkg/tpm/tpm_linux_test.go b/pkg/tpm/tpm_linux_test.go new file mode 100644 index 00000000..d1ca893a --- /dev/null +++ b/pkg/tpm/tpm_linux_test.go @@ -0,0 +1,161 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package tpm_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/jaypipes/ghw" + "github.com/jaypipes/ghw/pkg/tpm" +) + +// makeTPMDir creates /sys/class/tpm/ and returns its path. +func makeTPMDir(t *testing.T, root, name string) string { + t.Helper() + tpmDir := filepath.Join(root, "sys", "class", "tpm", name) + if err := os.MkdirAll(tpmDir, 0700); err != nil { + t.Fatalf("failed to create tpm sysfs dir: %v", err) + } + return tpmDir +} + +func TestTPM12Caps(t *testing.T) { + root := t.TempDir() + tpmDir := makeTPMDir(t, root, "tpm0") + + // 0x49465800 is the hex-encoded ASCII short name "IFX" (Infineon). + caps := `Manufacturer: 0x49465800 +TCG version: 1.2 +Firmware version: 6.40 +` + if err := os.WriteFile(filepath.Join(tpmDir, "caps"), []byte(caps), 0600); err != nil { + t.Fatalf("failed to write caps: %v", err) + } + + info, err := tpm.New(ghw.WithChroot(root)) + if err != nil { + t.Fatalf("expected nil err, but got %v", err) + } + if len(info.Devices) != 1 { + t.Fatalf("expected 1 TPM device, but got %d", len(info.Devices)) + } + dev := info.Devices[0] + if dev.ManufacturerName != "IFX" { + t.Errorf("unexpected manufacturer name: %q", dev.ManufacturerName) + } + if dev.SpecVersion != "1.2" { + t.Errorf("unexpected spec version: %q", dev.SpecVersion) + } + if dev.FirmwareVersion != "6.40" { + t.Errorf("unexpected firmware version: %q", dev.FirmwareVersion) + } +} + +func TestTPM20VersionMajor(t *testing.T) { + root := t.TempDir() + tpmDir := makeTPMDir(t, root, "tpm0") + + // TPM 2.0 exposes no caps file; the manufacturer comes from the device + // vendor attribute and the spec version from tpm_version_major. + deviceDir := filepath.Join(tpmDir, "device") + if err := os.MkdirAll(deviceDir, 0700); err != nil { + t.Fatalf("failed to create device dir: %v", err) + } + if err := os.WriteFile(filepath.Join(deviceDir, "vendor"), []byte("STM\n"), 0600); err != nil { + t.Fatalf("failed to write vendor: %v", err) + } + if err := os.WriteFile(filepath.Join(tpmDir, "tpm_version_major"), []byte("2\n"), 0600); err != nil { + t.Fatalf("failed to write tpm_version_major: %v", err) + } + + info, err := tpm.New(ghw.WithChroot(root)) + if err != nil { + t.Fatalf("expected nil err, but got %v", err) + } + if len(info.Devices) != 1 { + t.Fatalf("expected 1 TPM device, but got %d", len(info.Devices)) + } + dev := info.Devices[0] + if dev.ManufacturerName != "STM" { + t.Errorf("unexpected manufacturer name: %q", dev.ManufacturerName) + } + if dev.SpecVersion != "2.0" { + t.Errorf("unexpected spec version: %q", dev.SpecVersion) + } +} + +func TestTPM20VendorID(t *testing.T) { + root := t.TempDir() + tpmDir := makeTPMDir(t, root, "tpm0") + + // A 16-bit hex-encoded vendor attribute is the TCG Vendor ID (0x1414 is + // Microsoft), not a manufacturer short name. + deviceDir := filepath.Join(tpmDir, "device") + if err := os.MkdirAll(deviceDir, 0700); err != nil { + t.Fatalf("failed to create device dir: %v", err) + } + if err := os.WriteFile(filepath.Join(deviceDir, "vendor"), []byte("0x1414\n"), 0600); err != nil { + t.Fatalf("failed to write vendor: %v", err) + } + if err := os.WriteFile(filepath.Join(tpmDir, "tpm_version_major"), []byte("2\n"), 0600); err != nil { + t.Fatalf("failed to write tpm_version_major: %v", err) + } + + info, err := tpm.New(ghw.WithChroot(root)) + if err != nil { + t.Fatalf("expected nil err, but got %v", err) + } + if len(info.Devices) != 1 { + t.Fatalf("expected 1 TPM device, but got %d", len(info.Devices)) + } + dev := info.Devices[0] + if dev.ManufacturerVendorID != "0x1414" { + t.Errorf("unexpected manufacturer vendor id: %q", dev.ManufacturerVendorID) + } + if dev.ManufacturerName != "" { + t.Errorf("expected empty manufacturer name, but got %q", dev.ManufacturerName) + } +} + +func TestMultipleTPMDevices(t *testing.T) { + root := t.TempDir() + // A tpm0 with a version file plus a bare tpm1: both should be enumerated. + tpm0 := makeTPMDir(t, root, "tpm0") + if err := os.WriteFile(filepath.Join(tpm0, "tpm_version_major"), []byte("2\n"), 0600); err != nil { + t.Fatalf("failed to write tpm_version_major: %v", err) + } + makeTPMDir(t, root, "tpm1") + // A resource-manager entry sharing the tpm prefix must be ignored. + makeTPMDir(t, root, "tpmrm0") + + info, err := tpm.New(ghw.WithChroot(root)) + if err != nil { + t.Fatalf("expected nil err, but got %v", err) + } + if len(info.Devices) != 2 { + t.Fatalf("expected 2 TPM devices, but got %d", len(info.Devices)) + } +} + +func TestTPMAbsent(t *testing.T) { + root := t.TempDir() + + // An empty tpm class directory without any tpmN entry means no TPM. + if err := os.MkdirAll(filepath.Join(root, "sys", "class", "tpm"), 0700); err != nil { + t.Fatalf("failed to create tpm sysfs dir: %v", err) + } + + info, err := tpm.New(ghw.WithChroot(root)) + if err != nil { + t.Fatalf("expected nil err, but got %v", err) + } + if len(info.Devices) != 0 { + t.Errorf("expected no TPM devices, but got %d", len(info.Devices)) + } +} diff --git a/pkg/tpm/tpm_stub.go b/pkg/tpm/tpm_stub.go new file mode 100644 index 00000000..9ef88ad4 --- /dev/null +++ b/pkg/tpm/tpm_stub.go @@ -0,0 +1,19 @@ +//go:build !linux +// +build !linux + +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package tpm + +import ( + "context" + "errors" + "runtime" +) + +func (i *Info) load(ctx context.Context) error { + return errors.New("tpm load not implemented on " + runtime.GOOS) +}