Skip to content

efitcg2 — EFI_TCG2_PROTOCOL transport

github.com/go-tpm2/efitcg2 is a pure-Go TPM 2.0 transport backed by the UEFI EFI_TCG2_PROTOCOL. It implements common.Transport so the tpm2 command layer (Quote, GetCapability, seal/unseal, …) can drive a firmware TPM while the machine is still in Boot Services — for example a TamaGo + UEFI loader running under OVMF, where the TPM is owned by firmware and raw TIS/CRB MMIO is the wrong interface. The protocol is the TCG EFI Protocol Specification, Family 2.0, clause EFI_TCG2_PROTOCOL. v0.2.0.

Firmware-validated

The cloud-boot loader extends PCR4 through this transport's MeasureToPCR (HashLogExtendEvent) on real x86 OVMF firmware (Fedora OVMF.stateless.fd + tpm-crb + swtpm), confirmed in the firmware DEBUG log. v0.1.1 fixed the SubmitCommand output block to respect the firmware's MaxResponseSize (the CRB ceiling is 3968 = 0x1000−0x80, not 4096) — found by that real-firmware run.

Why not TIS/CRB here?

Under a pre-ExitBootServices loader the firmware already owns the TPM: localities, active PCR banks, and the TCG event log are established, and firmware is still servicing the device. EFI_TCG2_PROTOCOL.SubmitCommand is the supported path for a marshaled TPM 2.0 command, and HashLogExtendEvent is the supported measured-boot primitive (it extends a PCR and appends an event-log record in one firmware call). Poking TIS/CRB registers directly at that point races firmware and is incorrect.

Dependency discipline

go-tpm2 stays UEFI/TamaGo-dependency-free. This package never imports an EFI runtime, never dereferences a protocol pointer, and never uses package unsafe. It takes an injected firmware-call mechanism — a Caller the loader implements — exactly the way tis and crb take an injected common.Regs. The loader owns the protocol pointer and the EFI calling convention; efitcg2 only marshals buffers and interprets results.

// Caller is the entire UEFI-facing surface of efitcg2. The loader holds the
// EFI_TCG2_PROTOCOL pointer + the platform efiCall and performs the actual
// firmware invocation; efitcg2 hands it marshaled buffers and reads the
// returned EFI_STATUS.
type Caller interface {
    SubmitCommand(inputBlock []byte, output []byte) (status uintptr, err error)
    HashLogExtendEvent(flags uint64, dataToHash []byte, event []byte) (status uintptr, err error)
}

status is the raw EFI_STATUS as a uintptr, so the platform's native word width carries the high "error" bit unchanged. efitcg2 maps it: EFI_SUCCESS → nil, and EFI_INVALID_PARAMETER / EFI_BUFFER_TOO_SMALL / EFI_DEVICE_ERROR / EFI_NOT_FOUND / any-other-error to typed sentinels.

Event log (v0.2.0)

GetEventLog fetches the firmware-maintained TCG event log (via the optional EventLogCaller) so attest can ParseEventLog + ReplayPCRs it and confirm the replay matches the firmware's PCRs — the attestation payoff of a measured boot. Note the event log is little-endian, unlike the big-endian TPM command wire.

Conventions

Pure Go, CGO_ENABLED=0, no unsafe, big-endian TPM wire (via common), GOWORK=off, BSD-3-Clause on every file, 100% statement coverage.