130 lines
3.4 KiB
Go
130 lines
3.4 KiB
Go
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
|
|
}
|