package collector import ( "encoding/binary" "encoding/json" "os" "os/exec" "path/filepath" "strconv" "strings" ) var ( tpmGlob = filepath.Glob tpmStat = os.Stat tpmRun = func(name string, args ...string) ([]byte, error) { return exec.Command(name, args...).Output() } ) // collectTPMPlatformConfig records TPM presence and read-only identity data. // It never provisions the TPM, changes ownership, writes NV storage, or runs // TPM2_SelfTest. func collectTPMPlatformConfig() *json.RawMessage { config := map[string]any{"TpmPresent": false, "TpmEnabled": false} devices, _ := tpmGlob("/sys/class/tpm/tpm*") if len(devices) == 0 { return rawPlatformConfig(config) } config["TpmPresent"] = true config["TpmEnabled"] = true config["TpmDevice"] = filepath.Base(devices[0]) for _, path := range []string{"/dev/tpmrm0", "/dev/tpm0"} { if _, err := tpmStat(path); err == nil { config["TpmInterface"] = path break } } out, err := tpmRun("tpm2_getcap", "properties-fixed") if err != nil { return rawPlatformConfig(config) } properties := parseTPMProperties(string(out)) if value := tpmPropertyValue(properties, "TPM2_PT_FAMILY_INDICATOR"); value != "" { config["TpmVersion"] = value } if value := tpmManufacturer(properties["TPM2_PT_MANUFACTURER"]); value != "" { config["TpmManufacturer"] = value } firmware1 := tpmPropertyRaw(properties, "TPM2_PT_FIRMWARE_VERSION_1") firmware2 := tpmPropertyRaw(properties, "TPM2_PT_FIRMWARE_VERSION_2") if firmware1 != "" || firmware2 != "" { config["TpmFirmwareVersion"] = strings.Trim(strings.Join([]string{firmware1, firmware2}, "/"), "/") } return rawPlatformConfig(config) } type tpmProperty struct { raw string value string } func parseTPMProperties(input string) map[string]tpmProperty { properties := make(map[string]tpmProperty) current := "" for _, line := range strings.Split(input, "\n") { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "TPM2_PT_") && strings.HasSuffix(trimmed, ":") { current = strings.TrimSuffix(trimmed, ":") continue } if current == "" { continue } property := properties[current] switch { case strings.HasPrefix(trimmed, "raw:"): property.raw = strings.TrimSpace(strings.TrimPrefix(trimmed, "raw:")) case strings.HasPrefix(trimmed, "value:"): property.value = strings.Trim(strings.TrimSpace(strings.TrimPrefix(trimmed, "value:")), `"`) default: continue } properties[current] = property } return properties } func tpmPropertyValue(properties map[string]tpmProperty, name string) string { property := properties[name] if property.value != "" { return property.value } return property.raw } func tpmPropertyRaw(properties map[string]tpmProperty, name string) string { return properties[name].raw } func tpmManufacturer(property tpmProperty) string { if property.value != "" { return property.value } raw := strings.TrimPrefix(property.raw, "0x") value, err := strconv.ParseUint(raw, 16, 32) if err != nil { return "" } bytes := make([]byte, 4) binary.BigEndian.PutUint32(bytes, uint32(value)) for _, b := range bytes { if b != 0 && (b < 0x20 || b > 0x7e) { return property.raw } } return strings.TrimRight(string(bytes), "\x00 ") } func rawPlatformConfig(config map[string]any) *json.RawMessage { raw, err := json.Marshal(config) if err != nil { return nil } message := json.RawMessage(raw) return &message }