devtpm

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: BSD-3-Clause Imports: 3 Imported by: 0

README

go-tpm2/devtpm

go-tpm2/devtpm

CI Go Reference Coverage License

A pure-Go TPM 2.0 transport over the Linux kernel TPM character device (/dev/tpmrm0). v0.1.0.

devtpm implements github.com/go-tpm2/common's Transport interface over a Linux TPM character device. It is the node-side host-TPM transport for weft remote attestation: on a real Linux node it gives the attest Node side a channel to the host's hardware (or firmware/vTPM) TPM.

Sibling repos: common (interfaces + codec), crb and tis (the MMIO register transports for firmware/bare-metal), tpm2 (the command layer that rides on this Transport), attest (remote attestation), and validate (live swtpm validation).

Install

go get github.com/go-tpm2/devtpm

Device model

The Linux tpm subsystem (drivers/char/tpm) exposes a TPM through two character devices, both with the same framing:

Device Role
/dev/tpmrm0 Resource-manager channel (DefaultDevice). Each open fd is an independent, multiplexed command channel; the kernel RM virtualizes the TPM's scarce transient-object/session slots and flushes a client's context on close. Prefer this.
/dev/tpm0 Raw channel. Single-open, unmediated, no handle virtualization — a leaked transient handle can wedge the whole TPM.

The framing this package relies on: one write() delivers exactly one complete command, and the matching read() returns exactly one complete response (the driver buffers the response and returns it whole). Send therefore does one Write of the full command and one Read of the full response — no length prefix, no chunking.

Because that contract is just "raw TPM2 bytes, one command per write, one response per read", New also accepts any io.ReadWriteCloser that honors it — most usefully a unix socket to swtpm's --server data channel, which speaks the identical raw TPM2 protocol. The validate harness uses exactly that to drive this transport against a real swtpm on a host with no /dev/tpmrm0.

Usage

import (
    "github.com/go-tpm2/attest"
    "github.com/go-tpm2/devtpm"
    "github.com/go-tpm2/tpm2"
)

// Open the host's resource-manager TPM channel.
dev, err := devtpm.Open(devtpm.DefaultDevice) // "/dev/tpmrm0"
if err != nil {
    // device missing, or insufficient privilege (root / tss group)
}
defer dev.Close()

// *devtpm.Transport satisfies common.Transport, so it plugs straight into
// the go-tpm2/tpm2 command layer and the attestation Node:
tpm := tpm2.New(dev)
node, err := attest.NewNode(tpm, pcrSel)
// node.Quote(nonce) … etc.

Or drive raw command buffers directly:

cmd := common.BuildCommand(uint16(common.TagNoSessions),
    uint32(common.CCGetRandom), []byte{0x00, 0x02})
rsp, err := dev.Send(cmd) // common.Transport.Send

Send framing

  1. Write the entire command in one Write. A short count means the command was not delivered intact → ErrShortWrite (never retried; a re-issued tail would be misframed as a new command).
  2. Read the response in one Read into a 4096-byte buffer (the TPM 2.0 maximum); the driver returns the whole buffered response.
  3. Validate at least a TPM 2.0 header was returned (common.HeaderSize) → else ErrShortResponse.

Underlying read/write errors are returned unwrapped so callers can inspect the concrete os/syscall error.

Security

/dev/tpmrm0 is privileged (root or the tss group). Any process that can open it can ask the TPM to sign with usable keys and can read every PCR; treat the descriptor as a sensitive capability and keep it inside the node agent. This transport carries opaque bytes only — all attestation policy (which PCRs, which key, freshness) lives in attest.

Conventions

Pure Go, CGO_ENABLED=0, no assembly, big-endian TPM wire (via common), BSD-3-Clause, 100% statement coverage (GOWORK=off go test -cover), GOWORK=off.

References

  • Linux kernel drivers/char/tpmtpm_dev_common.c (one-write-one-command / one-read-one-response framing) and tpm2-space.c (the /dev/tpmrm0 in-kernel resource manager).
  • TCG TPM 2.0 Library, Parts 1–4 (wire format, via common).

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package devtpm implements a pure-Go github.com/go-tpm2/common.Transport over a Linux kernel TPM character device — by default /dev/tpmrm0, the in-kernel TPM2 resource-manager channel.

Device model

The Linux tpm subsystem (drivers/char/tpm) exposes a TPM through two character devices:

  • /dev/tpm0 — the raw device: a single-open, unmediated TPM2 command channel with no handle virtualization.
  • /dev/tpmrm0 — the resource-manager device (DefaultDevice): each open file descriptor is an independent, multiplexed command channel; the kernel resource manager virtualizes the TPM's scarce transient-object and session slots and flushes a client's transient context when the descriptor is closed.

Both devices share the same framing, and it is the framing this package depends on: a single write() delivers exactly one complete TPM 2.0 command to the TPM, and the matching read() returns exactly one complete response (the driver buffers the response and returns it whole). Send therefore performs one Write of the full command and one Read of the full response — no length prefix, no chunking, no partial-write retry.

Because that contract is just "raw TPM2 bytes, one command per write, one response per read", New also accepts any io.ReadWriteCloser that honors it: most usefully a unix-domain socket to swtpm's --server data channel, which speaks the identical raw TPM2 protocol. The validate harness uses exactly that to exercise this transport against a real swtpm on a host that has no /dev/tpmrm0.

Security and usage (weft attestation)

This is the node-side host-TPM transport for weft remote attestation. On a real Linux node, the node agent opens the host's hardware (or firmware/vTPM) TPM via devtpm.Open(devtpm.DefaultDevice), layers the go-tpm2/tpm2 command API on top, and drives go-tpm2/attest's Node side: reading PCRs, loading/creating the attestation key, and producing the quote the verifier checks.

Operational notes:

  • Prefer /dev/tpmrm0. The resource manager prevents a misbehaving or crashing client from leaking transient handles and wedging the shared TPM, and it isolates concurrent users of the same TPM.
  • Access to /dev/tpmrm0 is privileged (typically root or the "tss" group). Any process that can open it can ask the TPM to sign with keys it can use and can read every PCR; treat the descriptor as a sensitive capability and keep it inside the node agent.
  • This transport carries opaque bytes only; it performs no policy, authorization, or measurement itself. The attestation semantics (which PCRs, which key, freshness/nonce) live in go-tpm2/attest.

Conventions

Pure Go, CGO_ENABLED=0, no architecture-specific assembly, BSD-3-Clause on every file, 100% statement coverage (GOWORK=off go test -cover), and GOWORK=off. The package consumes github.com/go-tpm2/common's Transport contract and HeaderSize/Error helpers and nothing else.

Index

Constants

View Source
const (
	// ErrShortWrite is returned when the device accepts fewer bytes than
	// the full command. The Linux tpm character device treats each
	// write() as one whole command and never performs a partial write of
	// a valid command, so a short write means the command was not
	// delivered intact and must not be paired with a read.
	ErrShortWrite = common.Error("devtpm: device accepted a short write of the command")
	// ErrShortResponse is returned when a read() returns fewer bytes than
	// a TPM 2.0 response header (common.HeaderSize). Such a buffer cannot
	// contain a parseable response.
	ErrShortResponse = common.Error("devtpm: response shorter than a TPM 2.0 header")
)

Error sentinels for the devtpm transport, typed as common.Error so callers may compare with ==.

View Source
const DefaultDevice = "/dev/tpmrm0"

DefaultDevice is the Linux kernel TPM *resource-manager* character device. Each open file descriptor on /dev/tpmrm0 is an independent TPM2 command channel multiplexed by the in-kernel resource manager (the "tpm2-space" / kernel-space RM): it virtualizes transient-object and session handle slots so concurrent users do not exhaust the TPM's scarce volatile memory, and it flushes a client's transient context when that client closes the device.

The raw, non-resource-managed device is /dev/tpm0: it speaks the same one-write-one-command / one-read-one-response framing but with no handle virtualization and a single-open exclusivity constraint, so a caller that leaks a transient handle can wedge the whole TPM. Prefer /dev/tpmrm0 unless you specifically need raw, unmediated access.

Linux exposes both through drivers/char/tpm: tpm_dev_common.c implements the file_operations such that a write() delivers exactly one complete command to the TPM and the matching read() returns exactly one complete response (the driver buffers the response and hands it back whole), which is the framing this transport relies on.

Variables

This section is empty.

Functions

This section is empty.

Types

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

Transport is a github.com/go-tpm2/common.Transport over a Linux TPM character device (typically /dev/tpmrm0, the kernel resource-manager channel). It carries the raw TPM 2.0 command/response byte stream: one Write delivers one complete command and one Read returns the whole response, matching the drivers/char/tpm file-operations contract.

The wrapped io.ReadWriteCloser is normally the *os.File returned by Open, but New accepts any io.ReadWriteCloser that honors the same one-write-one-command / one-read-one-response framing — for example a unix-domain socket to swtpm's --server data channel, which speaks the identical raw TPM2 protocol the character device carries.

func New

func New(rwc io.ReadWriteCloser) *Transport

New wraps an existing io.ReadWriteCloser as a Transport. It exists for tests and for non-file transports that nonetheless honor the one-write-one-command / one-read-one-response framing — such as a unix socket to swtpm's raw-TPM2 data channel — letting them ride the same code path as the real character device.

func Open

func Open(path string) (*Transport, error)

Open opens the TPM character device at path for reading and writing and wraps it in a Transport. Pass DefaultDevice for the resource-manager channel (/dev/tpmrm0).

It opens with os.O_RDWR and no O_CREAT: the device node must already exist (the kernel tpm driver creates it), so a nonexistent path is an error rather than a freshly created regular file.

func (*Transport) Close

func (t *Transport) Close() error

Close closes the underlying device. For /dev/tpmrm0 this releases the resource-manager session, flushing any transient objects and sessions the kernel RM created on this client's behalf.

func (*Transport) Send

func (t *Transport) Send(cmd []byte) (rsp []byte, err error)

Send transmits one fully-marshaled TPM 2.0 command buffer and returns the full response buffer (header + parameters). It satisfies common.Transport.

The Linux tpm character device frames each command as a single write() and each response as a single read() (drivers/char/tpm, tpm_dev_common.c): Send therefore writes the whole command in one Write and reads the whole response in one Read.

  1. Write the entire command in one Write. The driver does not perform partial writes of a valid command, so a short count means the command was not delivered intact; that is reported as ErrShortWrite rather than silently retried, since a re-issued tail would be misframed as a new command.
  2. Read the response in one Read into a maxResponse buffer. The driver returns the entire buffered response in a single read().
  3. Validate that at least a TPM 2.0 header was returned (common.HeaderSize); otherwise return ErrShortResponse.

Write and read errors from the underlying device are returned unwrapped so callers can inspect the concrete os/syscall error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL