package platform import ( "context" "fmt" "os" "path/filepath" "strings" "time" ) // tpmDeviceGlob is a seam for tests. It reports the sysfs TPM device nodes // the kernel has registered; an empty result means the platform exposes no // TPM at all (no discrete chip, or firmware/BIOS has it disabled). var tpmDeviceGlob = func() []string { matches, _ := filepath.Glob("/sys/class/tpm/tpm*") return matches } var tpmReadFile = os.ReadFile // TPMPresent reports whether sysfs identifies a registered device as TPM 2.x. // The validation pack uses tpm2-tools, so a TPM 1.2 device is not sufficient. func (s *System) TPMPresent() bool { for _, device := range tpmDeviceGlob() { version, err := tpmReadFile(filepath.Join(device, "tpm_version_major")) if err == nil && strings.TrimSpace(string(version)) == "2" { return true } } return false } // RunTPMValidationPack verifies TPM 2.0 communication using read-only // commands. It deliberately excludes SelfTest, provisioning, NV writes, PCR // changes, key creation, and ownership operations. // // When the platform exposes no TPM device at all, the pack does not run the // tpm2_* tools: without a TCTI device they only ever emit a wall of // "Failed to open ... /dev/tpmrm0" errors that read as a hard failure when // the real situation is "this machine has no TPM". Instead it writes an // UNSUPPORTED summary and returns, the same way the storage pack handles a // host with no drives. func (s *System) RunTPMValidationPack(ctx context.Context, baseDir string, logFunc func(string)) (string, error) { if !s.TPMPresent() { return writeTPMUnsupportedRun(baseDir, logFunc) } return runAcceptancePackCtx(ctx, baseDir, "tpm", tpmValidationJobs(), logFunc) } func writeTPMUnsupportedRun(baseDir string, logFunc func(string)) (string, error) { if strings.TrimSpace(baseDir) == "" { baseDir = "/var/log/bee-sat" } now := time.Now().UTC() runDir := filepath.Join(baseDir, "tpm-"+now.Format("20060102-150405")) if err := os.MkdirAll(runDir, 0755); err != nil { return "", err } if logFunc != nil { logFunc("no TPM 2.x device reported by sysfs; skipping read-only TPM checks") } var summary strings.Builder fmt.Fprintf(&summary, "run_at_utc=%s\n", now.Format(time.RFC3339)) summary.WriteString("tpm_present=false\n") summary.WriteString("skip_reason=no TPM 2.x device reported by sysfs; tpm2-tools are not applicable\n") summary.WriteString("tpm_check_status=UNSUPPORTED\n") summary.WriteString("overall_status=UNSUPPORTED\n") summary.WriteString("job_ok=0\n") summary.WriteString("job_failed=0\n") summary.WriteString("job_unsupported=1\n") summary.WriteString("job_informational_failed=0\n") if err := os.WriteFile(filepath.Join(runDir, "summary.txt"), []byte(summary.String()), 0644); err != nil { return "", err } return runDir, nil } func tpmValidationJobs() []satJob { return []satJob{ {name: "01-properties-fixed.log", cmd: []string{"tpm2_getcap", "properties-fixed"}}, {name: "02-pcr-banks.log", cmd: []string{"tpm2_getcap", "pcrs"}}, {name: "03-pcr-values.log", cmd: []string{"tpm2_pcrread"}}, {name: "04-test-result.log", cmd: []string{"tpm2_gettestresult"}}, } }