Files
bee/audit/internal/collector/tpm.go
T

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
}