From 29572013604e7ea368dd5914b9f415c1df275345 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:48:44 -0400 Subject: [PATCH 01/29] TEMP: specs: Extend specs with vTPM support Signed-off-by: Stefan Berger --- .../runtime-spec/specs-go/config.go | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index 854290da205..112c3337cf6 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -451,6 +451,24 @@ type LinuxRdma struct { HcaObjects *uint32 `json:"hcaObjects,omitempty"` } +// LinuxVTPM for vTPM definition +type LinuxVTPM struct { + // Path on host where vTPM writes state to + StatePath string `json:"statePath,omitempty"` + // Whether runc is allowed to delete the 'Statepath' once the TPM is destroyed + StatePathIsManaged bool `json:"statePathIsManaged,omitempty"` + // Version of the TPM that is emulated + TPMVersion string `json:"vtpmVersion,omitempty"` + // Whether to create certificates upon first start of vTPM + CreateCertificates bool `json:"createCerts,omitempty"` + // The PCR banks to enable + PcrBanks string `json:"pcrBanks,omitempty"` + // Under what user to run the vTPM process + RunAs string `json:"runAs,omitempty"` + // The password to derive the encryption key from + EncryptionPassword string `json:"encryptionPassword,omitempty"` +} + // LinuxResources has container runtime resource constraints type LinuxResources struct { // Devices configures the device allowlist. @@ -473,12 +491,16 @@ type LinuxResources struct { Rdma map[string]LinuxRdma `json:"rdma,omitempty"` // Unified resources. Unified map[string]string `json:"unified,omitempty"` + // VTPM configuration + VTPMs []LinuxVTPM `json:"vtpms,omitempty"` } // LinuxDevice represents the mknod information for a Linux special device file type LinuxDevice struct { // Path to the device. Path string `json:"path"` + // Path of passed-through device on host + Devpath string `json:"devpath"` // Device type, block, char, etc. Type string `json:"type"` // Major is the device's major number. From fc2b859597837d779ad3a91d5e7f9d0b7ebdf1d4 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:48:48 -0400 Subject: [PATCH 02/29] Add vTPM support for Linux This patch adds vTPM support for Linux to libcontainer. The functionality is based on a recently added vtpm_proxy driver, which is becoming available in Linux 4.8. The driver provides /dev/vtpmx, on which an ioctl is called that spawns a TPM device in the host's /dev directory and returns an anonymous file-descriptor on which a TPM emulator can listen for TPM commands. If we for example created /dev/tpm12 on the host we make this device available as /dev/tpm0 inside the container. We also add its major and minor numbers to the device cgroup. We implement a VTPM class that allows us to create the device and starts a TPM emulator 'swtpm', to which it passes the anonymous file descriptor. Besides that, the user can choose to have the vTPM create certificates in a step that simulates TPM manufacturing. We do this by calling the external swtpm_setup program, which is part of the swtpm project. VTPM support is added inside the JSON configuration as follows: [...] "linux": { "resources": { "devices": [ { "allow": false, "access": "rwm" } ] , "vtpms": [ { "statePath": "/tmp/tpm-1", "createCerts": true }, ] }, [...] This JSON markup makes a single TPM available inside the created container. o The statPath parameter indicates the directory where the TPM emulator 'swtpm' writes the state of the TPM device to. o The createCerts parameter indicates that certificates for the TPM are to be created. The current implementation does not support checkpointing, so checkpointing of a container with an attached vTPM is prevented. The swtpm project is available here : https://github.com/stefanberger/swtpm The libtpms project is available here: https://github.com/stefanberger/libtpms Signed-off-by: Stefan Berger --- libcontainer/configs/config.go | 4 + libcontainer/criu_linux.go | 4 + libcontainer/rootfs_linux.go | 10 +- libcontainer/specconv/spec_linux.go | 4 + libcontainer/state_linux.go | 3 + libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 116 ++++ libcontainer/vtpm/vtpm.go | 613 ++++++++++++++++++ utils_linux.go | 63 +- .../cgroups/devices/config/device.go | 4 + 9 files changed, 818 insertions(+), 3 deletions(-) create mode 100644 libcontainer/vtpm/vtpm-helper/vtpm_helper.go create mode 100644 libcontainer/vtpm/vtpm.go diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go index 07216cb81cd..5b75e901b2f 100644 --- a/libcontainer/configs/config.go +++ b/libcontainer/configs/config.go @@ -13,6 +13,7 @@ import ( "time" "unsafe" + "github.com/opencontainers/runc/libcontainer/vtpm" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" @@ -238,6 +239,9 @@ type Config struct { // ExecCPUAffinity is CPU affinity for a non-init process to be run in the container. ExecCPUAffinity *CPUAffinity `json:"exec_cpu_affinity,omitempty"` + + // VTPM configuration + VTPMs []*vtpm.VTPM `json:"vtpms"` } // Scheduler is based on the Linux sched_setattr(2) syscall. diff --git a/libcontainer/criu_linux.go b/libcontainer/criu_linux.go index dbeb722a2eb..6f97f0ad98f 100644 --- a/libcontainer/criu_linux.go +++ b/libcontainer/criu_linux.go @@ -288,6 +288,10 @@ func (c *Container) Checkpoint(criuOpts *CriuOpts) error { // support for doing unprivileged dumps, but the setup of // rootless containers might make this complicated. + if len(c.config.VTPMs) > 0 { + return fmt.Errorf("Checkpointing with attached vTPM is not supported") + } + // We are relying on the CRIU version RPC which was introduced with CRIU 3.0.0 if err := c.checkCriuVersion(30000); err != nil { return err diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go index fd9fdddcd05..28527f191e1 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -949,7 +949,15 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error { // The node only exists for cgroup reasons, ignore it here. return nil } - dest, err := securejoin.SecureJoin(rootfs, node.Path) + + var path string + if node.Devpath != "" { + path = node.Devpath + } else { + path = node.Path + } + + dest, err := securejoin.SecureJoin(rootfs, path) if err != nil { return err } diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index ec417ed37e2..baadb7768ce 100644 --- a/libcontainer/specconv/spec_linux.go +++ b/libcontainer/specconv/spec_linux.go @@ -22,6 +22,7 @@ import ( "github.com/opencontainers/runc/libcontainer/internal/userns" "github.com/opencontainers/runc/libcontainer/seccomp" libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" + "github.com/opencontainers/runc/libcontainer/vtpm" "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" @@ -344,6 +345,7 @@ type CreateOpts struct { Spec *specs.Spec RootlessEUID bool RootlessCgroups bool + VTPMs []*vtpm.VTPM } // CreateLibcontainerConfig creates a new libcontainer configuration from a @@ -378,6 +380,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) { NoNewKeyring: opts.NoNewKeyring, RootlessEUID: opts.RootlessEUID, RootlessCgroups: opts.RootlessCgroups, + VTPMs: opts.VTPMs, } for _, m := range spec.Mounts { @@ -1009,6 +1012,7 @@ next: Minor: d.Minor, }, Path: d.Path, + Devpath: d.Devpath, FileMode: filemode, Uid: uid, Gid: gid, diff --git a/libcontainer/state_linux.go b/libcontainer/state_linux.go index 2b7b8b5bc36..559fd9bf08e 100644 --- a/libcontainer/state_linux.go +++ b/libcontainer/state_linux.go @@ -7,7 +7,9 @@ import ( "github.com/opencontainers/cgroups" "github.com/opencontainers/runc/libcontainer/configs" + vtpmhelper "github.com/opencontainers/runc/libcontainer/vtpm/vtpm-helper" "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/sys/unix" ) @@ -36,6 +38,7 @@ type containerState interface { } func destroy(c *Container) error { + vtpmhelper.DestroyVTPMs(c.config.VTPMs) // Usually, when a container init is gone, all other processes in its // cgroup are killed by the kernel. This is not the case for a shared // PID namespace container, which may have some processes left after diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go new file mode 100644 index 00000000000..ebbbc620bcb --- /dev/null +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -0,0 +1,116 @@ +// + build linux + +package vtpmhelper + +import ( + "fmt" + "os" + "syscall" + + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/vtpm" + + "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/sys/unix" +) + +// addVTPMDevice adds a device and cgroup entry to the spec +func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint32) { + var filemode os.FileMode = 0600 + + device := specs.LinuxDevice{ + Path: hostpath, + Devpath: devpath, + Type: "c", + Major: int64(major), + Minor: int64(minor), + FileMode: &filemode, + } + spec.Linux.Devices = append(spec.Linux.Devices, device) + + major_p := new(int64) + *major_p = int64(major) + minor_p := new(int64) + *minor_p = int64(minor) + + ld := &specs.LinuxDeviceCgroup{ + Allow: true, + Type: "c", + Major: major_p, + Minor: minor_p, + Access: "rwm", + } + spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld) +} + +// CreateVTPM create a VTPM proxy device and starts the TPM emulator with it +func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM, devnum int) (*vtpm.VTPM, error) { + + vtpm, err := vtpm.NewVTPM(vtpmdev.StatePath, vtpmdev.StatePathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.RunAs, vtpmdev.PcrBanks) + if err != nil { + return nil, err + } + + // Start the vTPM process; once stopped, the device pair will also disappear + vtpm.CreatedStatepath, err = vtpm.Start() + if err != nil { + return nil, err + } + + hostdev := vtpm.GetTPMDevname() + major, minor := vtpm.GetMajorMinor() + + devpath := fmt.Sprintf("/dev/tpm%d", devnum) + addVTPMDevice(spec, hostdev, devpath, major, minor) + + // for TPM 2: check if /dev/vtpmrm%d is available + host_tpmrm := fmt.Sprintf("/dev/tpmrm%d", vtpm.GetTPMDevNum()) + if fileInfo, err := os.Lstat(host_tpmrm); err == nil { + if stat_t, ok := fileInfo.Sys().(*syscall.Stat_t); ok { + devNumber := stat_t.Rdev + devpath = fmt.Sprintf("/dev/tpmrm%d", devnum) + addVTPMDevice(spec, host_tpmrm, devpath, unix.Major(devNumber), unix.Minor(devNumber)) + } + } + + return vtpm, nil +} + +func setVTPMHostDevOwner(vtpm *vtpm.VTPM, uid, gid int) error { + hostdev := vtpm.GetTPMDevname() + // adapt ownership of the device since only root can access it + if err := os.Chown(hostdev, uid, gid); err != nil { + return err + } + + host_tpmrm := fmt.Sprintf("/dev/tpmrm%d", vtpm.GetTPMDevNum()) + if _, err := os.Lstat(host_tpmrm); err == nil { + // adapt ownership of the device since only root can access it + if err := os.Chown(host_tpmrm, uid, gid); err != nil { + return err + } + } + + return nil +} + +// SetVTPMHostDevsOwner sets the owner of the host devices to the +// container root's mapped user id; if root inside the container is +// uid 1000 on the host, the devices will be owned by uid 1000. +func SetVTPMHostDevsOwner(config *configs.Config, uid, gid int) error { + if uid != 0 { + for _, vtpm := range config.VTPMs { + if err := setVTPMHostDevOwner(vtpm, uid, gid); err != nil { + return err + } + } + } + return nil +} + +// DestroyVTPMs stops all VTPMs and cleans up the state directory if necessary +func DestroyVTPMs(vtpms []*vtpm.VTPM) { + for _, vtpm := range vtpms { + vtpm.Stop(vtpm.CreatedStatepath) + } +} diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go new file mode 100644 index 00000000000..548da13108b --- /dev/null +++ b/libcontainer/vtpm/vtpm.go @@ -0,0 +1,613 @@ +// + build linux + +package vtpm + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "os/user" + "path" + "path/filepath" + "strconv" + "syscall" + "time" + "unsafe" + + "github.com/sirupsen/logrus" +) + +// object +type VTPM struct { + // The path where the TPM emulator writes the TPM state to + StatePath string `json:"statePath"` + + // Whether we are allowed to delete the TPM's state path upon + // destroying the TPM or an outside mgmt. stack will do that + StatePathIsManaged bool `json:"statePathIsManaged"` + + // whether the device state path was created or already existed + CreatedStatepath bool + + // Whether to create a certificate for the VTPM + CreateCerts bool `json:"createCerts"` + + // Version of the TPM + Vtpmversion string `json:"vtpmversion"` + + // Set of active PCR banks + PcrBanks string `json:"pcrbanks"` + + // The user under which to run the TPM emulator + user string + + // The TPM device number as returned from /dev/vtpmx ioctl + Tpm_dev_num uint32 `json:"tpm_dev_num"` + + // The backend file descriptor + fd int32 + + // The major number of the created device + major uint32 + + // The minor number of the created device + minor uint32 + + // process id of this vtpm + Pid int +} + +// ioctl +type vtpm_proxy_new_dev struct { + flags uint32 + tpm_dev_num uint32 + fd int32 + major uint32 + minor uint32 +} + +const ( + ILLEGAL_FD = -1 + VTPM_DEV_NUM_INVALID = 0xffffffff + + VTPM_PROXY_IOC_NEW_DEV = 0xc014a100 + + VTPM_VERSION_1_2 = "1.2" + VTPM_VERSION_2 = "2" + + VTPM_FLAG_TPM2 = 1 +) + +func translateUser(username string) (*user.User, error) { + usr, err := user.Lookup(username) + if err != nil { + usr, err = user.LookupId(username) + } + if err != nil { + return nil, fmt.Errorf("User '%s' not available: %v", username, err) + } + return usr, nil +} + +func ioctl(fd, cmd, msg uintptr) error { + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, msg) + if errno != 0 { + err := errno + return err + } + + return nil +} + +func vtpmx_ioctl(cmd, msg uintptr) error { + vtpmx, err := os.Open("/dev/vtpmx") + if err != nil { + logrus.Warnf("Could not open /dev/vtpmx: %v", err) + return err + } + defer vtpmx.Close() + + if err := ioctl(uintptr(vtpmx.Fd()), cmd, msg); err != nil { + return fmt.Errorf("VTPM: vtpmx ioctl failed: %v", err) + } + + return nil +} + +// Create a new VTPM object +// +// @statepath: directory where the vTPM's state will be written into +// @statepathismanaged: whether we are allowed to delete the TPM's state +// path upon destroying the vTPM +// @vtpmversion: The TPM version +// @createcerts: whether to create certificates for the vTPM (on first start) +// @runas: the account under which to run the swtpm; TPM 1.2 should be run +// with account tss; TPM 2 has more flexibility +// +// After successful creation of the object the Start() method can be called +func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, createcerts bool, runas string, pcrbanks string) (*VTPM, error) { + if len(statepath) == 0 { + return nil, fmt.Errorf("Missing required statpath for vTPM.") + } + + if len(vtpmversion) == 0 { + vtpmversion = VTPM_VERSION_2 + } + if vtpmversion != VTPM_VERSION_1_2 && vtpmversion != VTPM_VERSION_2 { + return nil, fmt.Errorf("Unsupported VTPM version '%s'.", vtpmversion) + } + + if _, err := os.Stat("/dev/vtpmx"); err != nil { + exec.Command("modprobe", "tpm_vtpm_proxy").Run() + if _, err := os.Stat("/dev/vtpmx"); err != nil { + return nil, fmt.Errorf("VTPM device driver not available.") + } + } + + if runas == "" { + runas = "root" + } + // TPM 1.2 choices are only 'root' and 'tss' users due to tcsd + if vtpmversion == VTPM_VERSION_1_2 && runas != "root" && runas != "tss" { + runas = "root" + } + + usr, err := translateUser(runas) + if err != nil { + return nil, err + } + runas = usr.Uid + + return &VTPM{ + user: runas, + StatePath: statepath, + StatePathIsManaged: statepathismanaged, + Vtpmversion: vtpmversion, + CreateCerts: createcerts, + PcrBanks: pcrbanks, + Tpm_dev_num: VTPM_DEV_NUM_INVALID, + fd: ILLEGAL_FD, + }, nil +} + +// createDev creates the vTPM proxy device using an ioctl on /dev/vtpmx. +// The ioctl returns the major and minor number of the /dev/tpm%d device +// that was created and the device number to indicate which /dev/tpm%d +// is the device. A file descriptor is also returned that must be passed +// to the TPM emulator for it to read the TPM commands from and write +// TPM response to. +func (vtpm *VTPM) createDev() error { + var ( + vtpm_proxy_new_dev vtpm_proxy_new_dev + ) + + if vtpm.Tpm_dev_num != VTPM_DEV_NUM_INVALID { + logrus.Info("Device already exists") + return nil + } + + if vtpm.Vtpmversion == VTPM_VERSION_2 { + vtpm_proxy_new_dev.flags = VTPM_FLAG_TPM2 + } + + err := vtpmx_ioctl(VTPM_PROXY_IOC_NEW_DEV, uintptr(unsafe.Pointer(&vtpm_proxy_new_dev))) + if err != nil { + return err + } + + vtpm.Tpm_dev_num = vtpm_proxy_new_dev.tpm_dev_num + vtpm.fd = vtpm_proxy_new_dev.fd + vtpm.major = vtpm_proxy_new_dev.major + vtpm.minor = vtpm_proxy_new_dev.minor + + return nil +} + +// getPidFile creates the full path of the TPM emulator PID file +func (vtpm *VTPM) getPidFile() string { + return path.Join(vtpm.StatePath, "swtpm.pid") +} + +// getLogFile creates the full path of the TPM emulator log file +func (vtpm *VTPM) getLogFile() string { + return path.Join(vtpm.StatePath, "swtpm.log") +} + +// getPidFromFile: Get the PID from the PID file +func (vtpm *VTPM) getPidFromFile() (int, error) { + d, err := ioutil.ReadFile(vtpm.getPidFile()) + if err != nil { + return -1, err + } + if len(d) == 0 { + return -1, fmt.Errorf("Empty pid file") + } + + pid, err := strconv.Atoi(string(d)) + if err != nil { + return -1, fmt.Errorf("Could not parse pid from file: %s", string(d)) + } + return pid, nil +} + +// waitForPidFile: wait for the PID file to appear and read the PID from it +func (vtpm *VTPM) waitForPidFile(loops int) (int, error) { + for loops >= 0 { + pid, err := vtpm.getPidFromFile() + if pid > 0 && err == nil { + return pid, nil + } + time.Sleep(time.Millisecond * 100) + loops -= 1 + } + logrus.Error("PID file did not appear") + return -1, fmt.Errorf("swtpm's pid file did not appear") +} + +// sendShutdown sends the TPM2_Shutdown command to a TPM 2; no command is +// sent in case of a TPM 1.2 +func (vtpm *VTPM) sendShutdown() error { + var err error = nil + + if vtpm.Tpm_dev_num != VTPM_DEV_NUM_INVALID && vtpm.Vtpmversion == VTPM_VERSION_2 { + devname := vtpm.GetTPMDevname() + dev, err := os.OpenFile(devname, os.O_RDWR, 0666) + if err != nil { + return err + } + defer dev.Close() + + sd := []byte{0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x01, 0x45, 0x00, 0x00} + n, err := dev.Write(sd) + if err != nil || n != len(sd) { + logrus.Errorf("Could not write shutdown to %s: %v", devname, err) + } + } + return err +} + +// stopByPidFile: Stop the vTPM by its PID file +func (vtpm *VTPM) stopByPidFile() error { + + vtpm.sendShutdown() + + pid, err := vtpm.getPidFromFile() + if err != nil { + return err + } + + p, err := os.FindProcess(pid) + if err != nil { + return err + } + + err = p.Signal(syscall.SIGTERM) + + return err +} + +func (vtpm *VTPM) modifyModePath(dirPath string, mask, set os.FileMode) error { + for { + fileInfo, err := os.Stat(dirPath) + if err != nil { + return err + } + if !fileInfo.IsDir() { + continue + } + + mode := (fileInfo.Mode() & mask) | set + if err := os.Chmod(dirPath, mode); err != nil { + return err + } + + dirPath = filepath.Dir(dirPath) + if dirPath == "/" { + break + } + } + return nil +} + +// DeleteStatePath deletes the directory where the TPM emulator writes its state +// into unless the state path is managed by a higher layer application, in which +// case the state path is not removed +func (vtpm *VTPM) DeleteStatePath() error { + if !vtpm.StatePathIsManaged { + return os.RemoveAll(vtpm.StatePath) + } + return nil +} + +// createStatePath creates the TPM directory where the TPM writes its state +// into; it also makes the directory accessible to the 'runas' user +// +// This method returns true in case the path was created, false in case the +// path already existed +func (vtpm *VTPM) createStatePath() (bool, error) { + created := false + if _, err := os.Stat(vtpm.StatePath); err != nil { + if err := os.MkdirAll(vtpm.StatePath, 0770); err != nil { + return false, fmt.Errorf("Could not create directory %s: %v", vtpm.StatePath, err) + } + created = true + } + + err := vtpm.chownStatePath() + if err != nil { + if created { + vtpm.DeleteStatePath() + } + return false, err + } + return created, nil +} + +func (vtpm *VTPM) chownStatePath() error { + usr, err := translateUser(vtpm.user) + if err != nil { + return err + } + + uid, err := strconv.Atoi(usr.Uid) + if err != nil { + return fmt.Errorf("Error parsing Uid %s: %v", usr.Uid, err) + } + + gid, err := strconv.Atoi(usr.Gid) + if err != nil { + return fmt.Errorf("Error parsing Gid %s: %v", usr.Gid, err) + } + + err = filepath.Walk(vtpm.StatePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() && path != vtpm.StatePath { + return filepath.SkipDir + } + if err := os.Chown(path, uid, gid); err != nil { + return fmt.Errorf("Could not change ownership of file %s: %v", path, err) + } + return nil + }) + if err != nil { + return err + } + + if uid != 0 { + if err := vtpm.modifyModePath(vtpm.StatePath, 0777, 0010); err != nil { + return fmt.Errorf("Could not chmod path to %s: %v", vtpm.StatePath, err) + } + } + + return nil +} + +// runSwtpmSetup runs swtpm_setup to simulate TPM manufacturing by creating +// EK and platform certificates and enabling TPM 2 PCR banks +func (vtpm *VTPM) runSwtpmSetup() error { + // if state already exists, --not-overwrite will not overwrite it + cmd := exec.Command("swtpm_setup", "--tpm-state", vtpm.StatePath, "--createek", + "--logfile", vtpm.getLogFile(), "--not-overwrite") + if vtpm.Vtpmversion == VTPM_VERSION_1_2 { + cmd.Args = append(cmd.Args, "--runas", vtpm.user) + } else if vtpm.Vtpmversion == VTPM_VERSION_2 { + // when creating certs we need root access to create lock files + if !vtpm.CreateCerts { + cmd.Args = append(cmd.Args, "--runas", vtpm.user) + } + } + if vtpm.CreateCerts { + cmd.Args = append(cmd.Args, "--create-ek-cert", "--create-platform-cert", "--lock-nvram") + } + + if vtpm.Vtpmversion == VTPM_VERSION_2 { + cmd.Args = append(cmd.Args, "--tpm2") + if len(vtpm.PcrBanks) > 0 { + cmd.Args = append(cmd.Args, "--pcr-banks", vtpm.PcrBanks) + } + } + + // need to explicitly set TMPDIR + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "TMPDIR=/tmp") + + output, err := cmd.CombinedOutput() + if err != nil { + logrus.Errorf("swtpm_setup failed: %s", string(output)) + return fmt.Errorf("swtpm_setup failed: %s\nlog: %s", string(output), vtpm.ReadLog()) + } + + return nil +} + +// waitForTPMDevice: Wait for /dev/tpm%d to appear and while waiting +// check whether the swtpm is still alive by checking its PID file +func (vtpm *VTPM) waitForTPMDevice(loops int) error { + devname := vtpm.GetTPMDevname() + pidfile := vtpm.getPidFile() + + for loops >= 0 { + if _, err := os.Stat(pidfile); err != nil { + logrus.Errorf("swtpm process has terminated") + return err + } + + if _, err := os.Stat(devname); err == nil { + return nil + } + time.Sleep(time.Millisecond * 100) + loops -= 1 + } + return fmt.Errorf("TPM device %s did not appear", devname) +} + +// startSwtpm creates the VTPM proxy device and start the swtpm process +func (vtpm *VTPM) startSwtpm() error { + err := vtpm.createDev() + if err != nil { + return err + } + + tpmstate := fmt.Sprintf("dir=%s", vtpm.StatePath) + pidfile := fmt.Sprintf("file=%s", vtpm.getPidFile()) + logfile := fmt.Sprintf("file=%s", vtpm.getLogFile()) + + // child will get first passed fd as '3' + cmd := exec.Command("swtpm", "chardev", "--tpmstate", tpmstate, + "--daemon", "--fd", "3", "--pid", pidfile, "--log", logfile, + "--runas", vtpm.user, "--flags", "not-need-init", + "--locality", "reject-locality-4,allow-set-locality") + if vtpm.Vtpmversion == VTPM_VERSION_2 { + cmd.Args = append(cmd.Args, "--tpm2") + } + file := os.NewFile(uintptr(vtpm.fd), "[vtpm]") + cmd.ExtraFiles = append(cmd.ExtraFiles, file) + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("swtpm failed on fd %d: %s\nlog: %s", vtpm.fd, string(output), vtpm.ReadLog()) + } + + vtpm.Pid, err = vtpm.waitForPidFile(10) + if err != nil { + return err + } + + err = vtpm.waitForTPMDevice(50) + if err != nil { + return err + } + return nil +} + +// runSwtpmBios runs swtpm_bios to initialize the TPM +func (vtpm *VTPM) runSwtpmBios() error { + tpmname := vtpm.GetTPMDevname() + + cmd := exec.Command("swtpm_bios", "-n", "-cs", "-u", "--tpm-device", tpmname) + if vtpm.Vtpmversion == VTPM_VERSION_2 { + cmd.Args = append(cmd.Args, "--tpm2") + } else { + // make sure the TPM 1.2 is activated + cmd.Args = append(cmd.Args, "-ea") + } + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("swtpm_bios failed: %s", output) + } + return nil +} + +// Start starts the vTPM (swtpm) +// +// - ensure any still running vTPM, which wrote its PID into a file in its state path, is terminated +// the swtpm will, upon normal termination, remove its PID file +// - setup the state path +// - if the state path was created ( = swtpm runs for the first time) also create the certificates +// - create the device pair +// - start the swtpm process +// - run swtpm_bios on it to initialize the vTPM as firmware would +// - if return code is 129, restart the vTPM to activate it and run swtpm_bios again +// +// After this method ran successfully, the TPM device (/dev/tpm%d) is available for use +func (vtpm *VTPM) Start() (bool, error) { + + vtpm.Stop(false) + + createdStatePath, err := vtpm.createStatePath() + if err != nil { + return false, err + } + defer func() { + if err != nil { + vtpm.Stop(createdStatePath) + } + }() + + err = vtpm.runSwtpmSetup() + if err != nil { + return false, err + } + // set the directory accesses for vtpm.user after swtpm_setup may have needed higher + // privileges + err = vtpm.chownStatePath() + if err != nil { + return false, err + } + + err = vtpm.startSwtpm() + if err != nil { + return false, err + } + + err = vtpm.runSwtpmBios() + if err != nil { + return false, err + } + + return createdStatePath, nil +} + +// Stop stops a running vTPM; this method can be called at any time also +// to do partial cleanups; After this method ran, Start() can be called again +func (vtpm *VTPM) Stop(deleteStatePath bool) error { + + err := vtpm.stopByPidFile() + + vtpm.CloseServer() + + vtpm.Tpm_dev_num = VTPM_DEV_NUM_INVALID + + if deleteStatePath { + vtpm.DeleteStatePath() + } + + return err +} + +// Get the TPM device name; this method can be called after successful Start() +func (vtpm *VTPM) GetTPMDevname() string { + return fmt.Sprintf("/dev/tpm%d", vtpm.Tpm_dev_num) +} + +// GetTPMDevNum returns the TPM device number; this would return 10 in case +// /dev/tpm10 was created on the host; this method can be called after +// sucessful Start() +func (vtpm *VTPM) GetTPMDevNum() uint32 { + return vtpm.Tpm_dev_num +} + +// Get the major and minor numbers of the created TPM device; +// This method can be called after successful Start() +func (vtpm *VTPM) GetMajorMinor() (uint32, uint32) { + return vtpm.major, vtpm.minor +} + +// ReadLog reads the vTPM's log file and returns the contents as a string +// This method can be called after Start() +func (vtpm *VTPM) ReadLog() string { + output, err := ioutil.ReadFile(vtpm.getLogFile()) + if err != nil { + return "" + } + return string(output) +} + +// CloseServer closes the server side file descriptor; this will remove the +// /dev/tpm%d and /dev/tpmrm%d (in case of TPM 2) on the host if the file +// descriptor is the last one holding the device open; also use this function +// after passing the file +// This method can be called after Start() +func (vtpm *VTPM) CloseServer() error { + + if vtpm.fd != ILLEGAL_FD { + os.NewFile(uintptr(vtpm.fd), "[vtpm]").Close() + vtpm.fd = ILLEGAL_FD + } + return nil +} diff --git a/utils_linux.go b/utils_linux.go index bd45bf05937..06dd3a6630d 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -10,6 +10,8 @@ import ( "strconv" "github.com/coreos/go-systemd/v22/activation" + "github.com/opencontainers/runc/libcontainer/vtpm" + vtpmhelper "github.com/opencontainers/runc/libcontainer/vtpm/vtpm-helper" "github.com/opencontainers/runtime-spec/specs-go" selinux "github.com/opencontainers/selinux/go-selinux" "github.com/sirupsen/logrus" @@ -179,7 +181,7 @@ func createPidFile(path string, process *libcontainer.Process) error { return os.Rename(tmpName, path) } -func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcontainer.Container, error) { +func createContainer(context *cli.Context, id string, spec *specs.Spec, vtpms []*vtpm.VTPM) (*libcontainer.Container, error) { rootlessCg, err := shouldUseRootlessCgroupManager(context) if err != nil { return nil, err @@ -192,11 +194,17 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcon Spec: spec, RootlessEUID: os.Geteuid() != 0, RootlessCgroups: rootlessCg, + VTPMs: vtpms, }) if err != nil { return nil, err } + err = setHostDevsOwner(config) + if err != nil { + return nil, err + } + root := context.GlobalString("root") return libcontainer.Create(root, id, config) } @@ -378,7 +386,17 @@ func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.C notifySocket.setupSpec(spec) } - container, err := createContainer(context, id, spec) + vtpms, err := createVTPMs(spec) + if err != nil { + return -1, err + } + defer func() { + if err != nil { + destroyVTPMs(vtpms) + } + }() + + container, err := createContainer(context, id, spec, vtpms) if err != nil { return -1, err } @@ -450,3 +468,44 @@ func maybeLogCgroupWarning(op string, err error) { logrus.Warn("runc " + op + " failure might be caused by lack of full access to cgroups") } } + +func createVTPMs(spec *specs.Spec) ([]*vtpm.VTPM, error) { + var vtpms []*vtpm.VTPM + + r := spec.Linux.Resources + if r == nil { + return vtpms, nil + } + + devnum := 0 + for _, vtpm := range r.VTPMs { + v, err := vtpmhelper.CreateVTPM(spec, &vtpm, devnum) + if err != nil { + destroyVTPMs(vtpms) + return vtpms, err + } + vtpms = append(vtpms, v) + devnum++ + } + return vtpms, nil +} + +func destroyVTPMs(vtpms []*vtpm.VTPM) { + vtpmhelper.DestroyVTPMs(vtpms) +} + +func setVTPMHostDevsOwner(config *configs.Config) error { + rootUID, err := config.HostRootUID() + if err != nil { + return err + } + rootGID, err := config.HostRootGID() + if err != nil { + return err + } + return vtpmhelper.SetVTPMHostDevsOwner(config, rootUID, rootGID) +} + +func setHostDevsOwner(config *configs.Config) error { + return setVTPMHostDevsOwner(config) +} diff --git a/vendor/github.com/opencontainers/cgroups/devices/config/device.go b/vendor/github.com/opencontainers/cgroups/devices/config/device.go index 295575cbfa7..464c4496a47 100644 --- a/vendor/github.com/opencontainers/cgroups/devices/config/device.go +++ b/vendor/github.com/opencontainers/cgroups/devices/config/device.go @@ -16,6 +16,10 @@ type Device struct { // Path to the device. Path string `json:"path"` + // the name of the device inside the container (optional) + // allows a host device to appear under different name inside container + Devpath string `json:"devpath"` + // FileMode permission bits for the device. FileMode os.FileMode `json:"file_mode"` From 585f24fb235f42b2b246e6deffc7e86c5fa689a7 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:49:08 -0400 Subject: [PATCH 03/29] vtpm: Run swtpm with an AppArmor profile Create an AppArmor profile and apply it so that swtpm runs with an AppArmor profile. Signed-off-by: Stefan Berger --- libcontainer/vtpm/vtpm.go | 95 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index 548da13108b..a8de0d6b698 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -15,6 +15,8 @@ import ( "time" "unsafe" + "github.com/opencontainers/runc/libcontainer/apparmor" + "github.com/sirupsen/logrus" ) @@ -56,6 +58,9 @@ type VTPM struct { // process id of this vtpm Pid int + + // The AppArmor profile's full path + aaprofile string } // ioctl @@ -452,6 +457,11 @@ func (vtpm *VTPM) startSwtpm() error { return err } + err = vtpm.setupAppArmor() + if err != nil { + return err + } + tpmstate := fmt.Sprintf("dir=%s", vtpm.StatePath) pidfile := fmt.Sprintf("file=%s", vtpm.getPidFile()) logfile := fmt.Sprintf("file=%s", vtpm.getLogFile()) @@ -481,6 +491,9 @@ func (vtpm *VTPM) startSwtpm() error { if err != nil { return err } + + vtpm.resetAppArmor() + return nil } @@ -561,6 +574,8 @@ func (vtpm *VTPM) Stop(deleteStatePath bool) error { vtpm.CloseServer() + vtpm.teardownAppArmor() + vtpm.Tpm_dev_num = VTPM_DEV_NUM_INVALID if deleteStatePath { @@ -609,5 +624,85 @@ func (vtpm *VTPM) CloseServer() error { os.NewFile(uintptr(vtpm.fd), "[vtpm]").Close() vtpm.fd = ILLEGAL_FD } + + return nil +} + +// setupAppArmor creates an apparmor profile for swtpm if AppArmor is enabled and +// compiles it using apparmor_parser -r and activates it for the next +// exec. +func (vtpm *VTPM) setupAppArmor() error { + var statefilepattern string + + if !apparmor.IsEnabled() { + return nil + } + + profilename := fmt.Sprintf("runc_%d_swtpm_tpm%d", os.Getpid(), vtpm.GetTPMDevNum()) + if vtpm.Vtpmversion == VTPM_VERSION_1_2 { + statefilepattern = path.Join(vtpm.StatePath, "tpm-00.*") + } else { + statefilepattern = path.Join(vtpm.StatePath, "tpm2-00.*") + } + + profile := fmt.Sprintf("\n#include \n"+ + "profile %s {\n"+ + " #include \n"+ + " capability setgid,\n"+ + " capability setuid,\n"+ + " /dev/tpm[0-9]* rw,\n"+ + " owner /etc/group r,\n"+ + " owner /etc/nsswitch.conf r,\n"+ + " owner /etc/passwd r,\n"+ + " %s/.lock wk,\n"+ + " %s w,\n"+ + " %s rw,\n"+ + " %s rw,\n"+ + "}\n", + profilename, + vtpm.StatePath, + vtpm.getLogFile(), + vtpm.getPidFile(), + statefilepattern) + + vtpm.aaprofile = path.Join(vtpm.StatePath, "swtpm.apparmor") + + err := ioutil.WriteFile(vtpm.aaprofile, []byte(profile), 0600) + if err != nil { + return err + } + defer func() { + if err != nil { + vtpm.teardownAppArmor() + } + }() + + cmd := exec.Command("/sbin/apparmor_parser", "-r", vtpm.aaprofile) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("apparmor_parser -r failed: %s", string(output)) + } + + err = apparmor.ApplyProfileThread(profilename) + if err != nil { + return err + } + return nil } + +func (vtpm *VTPM) resetAppArmor() { + apparmor.ApplyProfileThread("unconfined") +} + +// teardownAppArmor removes the AppArmor profile from the system and ensures +// that the next time the process exec's no swtpm related profile is applied +func (vtpm *VTPM) teardownAppArmor() { + vtpm.resetAppArmor() + if len(vtpm.aaprofile) > 0 { + cmd := exec.Command("/sbin/apparmor_parser", "-R", vtpm.aaprofile) + cmd.Run() + os.Remove(vtpm.aaprofile) + vtpm.aaprofile = "" + } +} From ece24f787c74654ae0525379167f3bbed14ff651 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:40:10 +0000 Subject: [PATCH 04/29] use ApplyProfile instead ApplyProfileThread Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index a8de0d6b698..a28a5dd4e48 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -124,11 +124,14 @@ func vtpmx_ioctl(cmd, msg uintptr) error { // // @statepath: directory where the vTPM's state will be written into // @statepathismanaged: whether we are allowed to delete the TPM's state -// path upon destroying the vTPM +// +// path upon destroying the vTPM +// // @vtpmversion: The TPM version // @createcerts: whether to create certificates for the vTPM (on first start) // @runas: the account under which to run the swtpm; TPM 1.2 should be run -// with account tss; TPM 2 has more flexibility +// +// with account tss; TPM 2 has more flexibility // // After successful creation of the object the Start() method can be called func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, createcerts bool, runas string, pcrbanks string) (*VTPM, error) { @@ -430,7 +433,8 @@ func (vtpm *VTPM) runSwtpmSetup() error { } // waitForTPMDevice: Wait for /dev/tpm%d to appear and while waiting -// check whether the swtpm is still alive by checking its PID file +// +// check whether the swtpm is still alive by checking its PID file func (vtpm *VTPM) waitForTPMDevice(loops int) error { devname := vtpm.GetTPMDevname() pidfile := vtpm.getPidFile() @@ -518,13 +522,13 @@ func (vtpm *VTPM) runSwtpmBios() error { // Start starts the vTPM (swtpm) // -// - ensure any still running vTPM, which wrote its PID into a file in its state path, is terminated -// the swtpm will, upon normal termination, remove its PID file -// - setup the state path -// - if the state path was created ( = swtpm runs for the first time) also create the certificates -// - create the device pair -// - start the swtpm process -// - run swtpm_bios on it to initialize the vTPM as firmware would +// - ensure any still running vTPM, which wrote its PID into a file in its state path, is terminated +// the swtpm will, upon normal termination, remove its PID file +// - setup the state path +// - if the state path was created ( = swtpm runs for the first time) also create the certificates +// - create the device pair +// - start the swtpm process +// - run swtpm_bios on it to initialize the vTPM as firmware would // - if return code is 129, restart the vTPM to activate it and run swtpm_bios again // // After this method ran successfully, the TPM device (/dev/tpm%d) is available for use @@ -683,7 +687,7 @@ func (vtpm *VTPM) setupAppArmor() error { return fmt.Errorf("apparmor_parser -r failed: %s", string(output)) } - err = apparmor.ApplyProfileThread(profilename) + err = apparmor.ApplyProfile(profilename) if err != nil { return err } @@ -692,7 +696,7 @@ func (vtpm *VTPM) setupAppArmor() error { } func (vtpm *VTPM) resetAppArmor() { - apparmor.ApplyProfileThread("unconfined") + apparmor.ApplyProfile("unconfined") } // teardownAppArmor removes the AppArmor profile from the system and ensures From c529f8da82ff1f5245258f5c6f8987b8f11ee4a0 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:49:15 -0400 Subject: [PATCH 05/29] vtpm: Run swtpm with an SELinux label On systems supporting SELinux run swtpm with an SELinux label applied. Also label the required files in the state directory. Signed-off-by: Stefan Berger --- libcontainer/vtpm/vtpm.go | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index a28a5dd4e48..b3fc41ed1c1 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -16,6 +16,7 @@ import ( "unsafe" "github.com/opencontainers/runc/libcontainer/apparmor" + selinux "github.com/opencontainers/selinux/go-selinux" "github.com/sirupsen/logrus" ) @@ -465,6 +466,10 @@ func (vtpm *VTPM) startSwtpm() error { if err != nil { return err } + err = vtpm.setupSELinux() + if err != nil { + return err + } tpmstate := fmt.Sprintf("dir=%s", vtpm.StatePath) pidfile := fmt.Sprintf("file=%s", vtpm.getPidFile()) @@ -496,6 +501,7 @@ func (vtpm *VTPM) startSwtpm() error { return err } + vtpm.resetSELinux() vtpm.resetAppArmor() return nil @@ -578,6 +584,7 @@ func (vtpm *VTPM) Stop(deleteStatePath bool) error { vtpm.CloseServer() + vtpm.teardownSELinux() vtpm.teardownAppArmor() vtpm.Tpm_dev_num = VTPM_DEV_NUM_INVALID @@ -710,3 +717,52 @@ func (vtpm *VTPM) teardownAppArmor() { vtpm.aaprofile = "" } } + +// setupSELinux labels the swtpm files with SELinux labels if SELinux is enabled +func (vtpm *VTPM) setupSELinux() error { + if !selinux.GetEnabled() { + return nil + } + + processLabel, fileLabel := selinux.ContainerLabels() + if len(processLabel) == 0 || len(fileLabel) == 0 { + return nil + } + + err := filepath.Walk(vtpm.StatePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() && path != vtpm.StatePath { + return filepath.SkipDir + } + return selinux.SetFileLabel(path, fileLabel) + }) + + err = selinux.SetFSCreateLabel(fileLabel) + if err != nil { + return err + } + err = ioutil.WriteFile("/sys/fs/selinux/context", []byte(processLabel), 0000) + if err != nil { + return err + } + err = selinux.SetExecLabel(processLabel) + if err != nil { + return err + } + + return nil +} + +// resetSELinux resets the prepared SELinux labels +func (vtpm *VTPM) resetSELinux() { + selinux.SetExecLabel("") + selinux.SetFSCreateLabel("") + ioutil.WriteFile("/sys/fs/selinux/context", []byte(""), 0000) +} + +// teardownSELinux cleans up SELinux for next spawned process +func (vtpm *VTPM) teardownSELinux() { + vtpm.resetSELinux() +} From dfb2c14f30b4a28f57c94e89e032b12270f68833 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:49:22 -0400 Subject: [PATCH 06/29] vtpm: Add test cases Add test cases for testing TPM 1.2 and TPM 2 by creating, stopping, restarting, and destroying it. Signed-off-by: Stefan Berger --- .../vtpm/vtpm-helper/vtpm_helper_test.go | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go new file mode 100644 index 00000000000..6b43dcc2191 --- /dev/null +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go @@ -0,0 +1,112 @@ +// + build linux + +package vtpmhelper + +import ( + "io/ioutil" + "os" + "os/exec" + "path" + "strings" + "testing" + + "github.com/opencontainers/runc/libcontainer/vtpm" + "github.com/opencontainers/runtime-spec/specs-go" +) + +func TestCreateVTPMFail(t *testing.T) { + vtpmdev := specs.LinuxVTPM{} + + _, err := CreateVTPM(&specs.Spec{}, &vtpmdev, 0) + if err == nil { + t.Fatalf("Could create vTPM without statepath %v", err) + } +} + +// check prerequisites for starting a vTPM +func checkPrerequisites(t *testing.T) { + if os.Getuid() != 0 { + t.Skip("Need to be root to run this test") + } + + for _, executable := range []string{"swtpm_setup", "swtpm"} { + if err := exec.Command(executable, "--help").Run(); err != nil { + t.Skipf("Could not run %s --help: %v", executable, err) + } + } +} + +func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas string) *vtpm.VTPM { + + checkPrerequisites(t) + + workdir, err := ioutil.TempDir("", "runctest") + if err != nil { + t.Fatalf("Could not create tmp dir: %s", err) + } + defer os.Remove(workdir) + + tpmdirname := path.Join(workdir, "myvtpm") + + spec := &specs.Spec{ + Linux: &specs.Linux{ + Devices: []specs.LinuxDevice{}, + Resources: &specs.LinuxResources{}, + }, + } + vtpmdev := &specs.LinuxVTPM{ + StatePath: tpmdirname, + TPMVersion: tpmversion, + CreateCertificates: createCertificates, + RunAs: runas, + } + + myvtpm, err := CreateVTPM(spec, vtpmdev, 0) + if err != nil { + if strings.Contains(err.Error(), "VTPM device driver not available") { + t.Skipf("%v", err) + } else { + t.Fatalf("Could not create VTPM device: %v", err) + } + } + return myvtpm +} + +func destroyVTPM(t *testing.T, myvtpm *vtpm.VTPM) { + tpmdirname := myvtpm.StatePath + + DestroyVTPMs([]*vtpm.VTPM{myvtpm}) + + if _, err := os.Stat(tpmdirname); !os.IsNotExist(err) { + t.Fatalf("State directory should have been removed since it was created by vtpm-helpers") + } +} + +func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificates bool, runas string) { + myvtpm := createVTPM(t, tpmversion, createCertificates, runas) + + err := myvtpm.Stop(false) + if err != nil { + t.Fatalf("VTPM could not be stopped cleanly: %v", err) + } + + createdStatePath, err := myvtpm.Start() + if err != nil { + t.Fatalf("VTPM could not be started: %v", err) + } + if createdStatePath { + t.Fatalf("VTPM Start() should not have created the state path at this time") + } + + destroyVTPM(t, myvtpm) +} + +func TestCreateVTPM2(t *testing.T) { + createRestartDestroyVTPM(t, "", true, "root") + createRestartDestroyVTPM(t, "", false, "0") + createRestartDestroyVTPM(t, "2", true, "0") +} + +func TestCreateVTPM12(t *testing.T) { + createRestartDestroyVTPM(t, "1.2", true, "root") +} From 9c424ecab6fa1f2e337c360b4159bc9c11f7dbac Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:49:37 -0400 Subject: [PATCH 07/29] vtpm: Get the supported capabilities of swtpm and swtpm_setup Call 'swtpm chardev --print-capabilities' to get the supported capabilites from swtpm. An JSON object is printed by swtpm that we unmarshal and we pick the 'features' part from it that is an array of strings indicating what this version of swtpm supports. This option was added in v0.2. For older versions of swtpm we return an empty array. Signed-off-by: Stefan Berger --- libcontainer/vtpm/vtpm.go | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index b3fc41ed1c1..8845744196b 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -3,6 +3,7 @@ package vtpm import ( + "encoding/json" "fmt" "io/ioutil" "os" @@ -62,6 +63,12 @@ type VTPM struct { // The AppArmor profile's full path aaprofile string + + // swtpm_setup capabilities + swtpmSetupCaps []string + + // swtpm capabilities + swtpmCaps []string } // ioctl @@ -121,6 +128,49 @@ func vtpmx_ioctl(cmd, msg uintptr) error { return nil } +// getCapabilities gets the capabilities map of an executable by invoking it with +// --print-capabilities. It returns the array of feature strings. +// This function returns an empty array if the executable does not support --print-capabilities. +// Expected output looks like this: +// { "type": "swtpm_setup", "features": [ "cmdarg-keyfile-fd", "cmdarg-pwdfile-fd" ] } +func getCapabilities(cmd *exec.Cmd) ([]string, error) { + caps := make(map[string]interface{}) + + output, err := cmd.Output() + if err != nil { + return nil, nil + } + + err = json.Unmarshal([]byte(output), &caps) + if err != nil { + return nil, fmt.Errorf("Could not unmarshal output: %s: %v\n", output, err) + } + + features, _ := caps["features"].([]interface{}) + res := make([]string, 0) + for _, f := range features { + res = append(res, f.(string)) + } + return res, nil +} + +func getSwtpmSetupCapabilities() ([]string, error) { + return getCapabilities(exec.Command("swtpm_setup", "--print-capabilities")) +} + +func getSwtpmCapabilities() ([]string, error) { + return getCapabilities(exec.Command("swtpm", "chardev", "--print-capabilities")) +} + +func hasCapability(capabilities []string, capability string) bool { + for _, c := range capabilities { + if capability == c { + return true + } + } + return false +} + // Create a new VTPM object // // @statepath: directory where the vTPM's state will be written into @@ -168,6 +218,15 @@ func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, crea } runas = usr.Uid + swtpmSetupCaps, err := getSwtpmSetupCapabilities() + if err != nil { + return nil, err + } + swtpmCaps, err := getSwtpmCapabilities() + if err != nil { + return nil, err + } + return &VTPM{ user: runas, StatePath: statepath, @@ -177,6 +236,8 @@ func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, crea PcrBanks: pcrbanks, Tpm_dev_num: VTPM_DEV_NUM_INVALID, fd: ILLEGAL_FD, + swtpmSetupCaps: swtpmSetupCaps, + swtpmCaps: swtpmCaps, }, nil } From 67ddc378024882edb8dd27a0e6e6c098d5aacbb4 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:49:44 -0400 Subject: [PATCH 08/29] vtpm: Pass startup-clear as part of flags to avoid kernel logging We need to startup the TPM as part of starting swtpm so that the Linux driver can successfully send its initial command to the vTPM and does not log a failure and then do the startup itself. Signed-off-by: Stefan Berger --- libcontainer/vtpm/vtpm.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index 8845744196b..c7b1f4677ba 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -536,10 +536,15 @@ func (vtpm *VTPM) startSwtpm() error { pidfile := fmt.Sprintf("file=%s", vtpm.getPidFile()) logfile := fmt.Sprintf("file=%s", vtpm.getLogFile()) + flags := "not-need-init" + if hasCapability(vtpm.swtpmCaps, "flags-opt-startup") { + flags += ",startup-clear" + } + // child will get first passed fd as '3' cmd := exec.Command("swtpm", "chardev", "--tpmstate", tpmstate, "--daemon", "--fd", "3", "--pid", pidfile, "--log", logfile, - "--runas", vtpm.user, "--flags", "not-need-init", + "--runas", vtpm.user, "--flags", flags, "--locality", "reject-locality-4,allow-set-locality") if vtpm.Vtpmversion == VTPM_VERSION_2 { cmd.Args = append(cmd.Args, "--tpm2") From 5154c8e06dbf8d38ee819696a9ac6e938d9e6c5c Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:49:58 -0400 Subject: [PATCH 09/29] vtpm: Add support for encrypted vTPM state This patch adds support for encrypting the vTPM state by allowing a user to pass a password to swtpm_setup and swtpm. Signed-off-by: Stefan Berger --- libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 41 +++++++++++- .../vtpm/vtpm-helper/vtpm_helper_test.go | 60 +++++++++++++++-- libcontainer/vtpm/vtpm.go | 65 ++++++++++++++++++- 3 files changed, 157 insertions(+), 9 deletions(-) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index ebbbc620bcb..dcd4cf4ca22 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -4,7 +4,10 @@ package vtpmhelper import ( "fmt" + "io/ioutil" "os" + "strconv" + "strings" "syscall" "github.com/opencontainers/runc/libcontainer/configs" @@ -43,10 +46,46 @@ func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld) } +// getEncryptionPassword gets the plain password from the caller +// valid formats passed to this function are: +// - +// - pass= +// - fd= +// - file= +func getEncryptionPassword(pwdString string) ([]byte, error) { + if strings.HasPrefix(pwdString, "file=") { + return ioutil.ReadFile(pwdString[5:]) + } else if strings.HasPrefix(pwdString, "pass=") { + return []byte(pwdString[5:]), nil + } else if strings.HasPrefix(pwdString, "fd=") { + fdStr := pwdString[3:] + fd, err := strconv.Atoi(fdStr) + if err != nil { + return nil, fmt.Errorf("could not parse file descriptor %s", fdStr) + } + f := os.NewFile(uintptr(fd), "pwdfile") + if f == nil { + return nil, fmt.Errorf("%s is not a valid file descriptor", fdStr) + } + defer f.Close() + pwd := make([]byte, 1024) + n, err := f.Read(pwd) + if err != nil { + return nil, fmt.Errorf("could not read from file descriptor: %v", err) + } + return pwd[:n], nil + } + return []byte(pwdString), nil +} + // CreateVTPM create a VTPM proxy device and starts the TPM emulator with it func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM, devnum int) (*vtpm.VTPM, error) { + encryptionPassword, err := getEncryptionPassword(vtpmdev.EncryptionPassword) + if err != nil { + return nil, err + } - vtpm, err := vtpm.NewVTPM(vtpmdev.StatePath, vtpmdev.StatePathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.RunAs, vtpmdev.PcrBanks) + vtpm, err := vtpm.NewVTPM(vtpmdev.StatePath, vtpmdev.StatePathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.RunAs, vtpmdev.PcrBanks, encryptionPassword) if err != nil { return nil, err } diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go index 6b43dcc2191..cbee06fa5d3 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go @@ -3,6 +3,7 @@ package vtpmhelper import ( + "fmt" "io/ioutil" "os" "os/exec" @@ -36,7 +37,7 @@ func checkPrerequisites(t *testing.T) { } } -func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas string) *vtpm.VTPM { +func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword string) *vtpm.VTPM { checkPrerequisites(t) @@ -59,6 +60,7 @@ func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas TPMVersion: tpmversion, CreateCertificates: createCertificates, RunAs: runas, + EncryptionPassword: encryptionPassword, } myvtpm, err := CreateVTPM(spec, vtpmdev, 0) @@ -82,8 +84,8 @@ func destroyVTPM(t *testing.T, myvtpm *vtpm.VTPM) { } } -func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificates bool, runas string) { - myvtpm := createVTPM(t, tpmversion, createCertificates, runas) +func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword string) { + myvtpm := createVTPM(t, tpmversion, createCertificates, runas, encryptionPassword) err := myvtpm.Stop(false) if err != nil { @@ -102,11 +104,55 @@ func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificate } func TestCreateVTPM2(t *testing.T) { - createRestartDestroyVTPM(t, "", true, "root") - createRestartDestroyVTPM(t, "", false, "0") - createRestartDestroyVTPM(t, "2", true, "0") + createRestartDestroyVTPM(t, "", true, "root", "") + createRestartDestroyVTPM(t, "", false, "0", "") + createRestartDestroyVTPM(t, "2", true, "0", "") } func TestCreateVTPM12(t *testing.T) { - createRestartDestroyVTPM(t, "1.2", true, "root") + createRestartDestroyVTPM(t, "1.2", true, "root", "") +} + +func TestCreateEncryptedVTPM_Pipe(t *testing.T) { + checkPrerequisites(t) + + piper, pipew, err := os.Pipe() + if err != nil { + t.Fatalf("Could not create pipe") + } + defer piper.Close() + + password := "123456" + + // pass password via write to pipe + go func() { + n, err := pipew.Write([]byte(password)) + if err != nil { + t.Fatalf("Could not write to pipe: %v", err) + } + if n != len(password) { + t.Fatalf("Could not write all data to pipe") + } + pipew.Close() + }() + createRestartDestroyVTPM(t, "", true, "root", fmt.Sprintf("fd=%d", piper.Fd())) +} + +func TestCreateEncryptedVTPM_File(t *testing.T) { + fil, err := ioutil.TempFile("", "passwordfile") + if err != nil { + t.Fatalf("Could not create temporary file: %v", err) + } + defer os.Remove(fil.Name()) + + _, err = fil.WriteString("123456") + if err != nil { + t.Fatalf("Could not write to temporary file: %v", err) + } + createRestartDestroyVTPM(t, "", true, "root", fmt.Sprintf("file=%s", fil.Name())) +} + +func TestCreateEncryptedVTPM_Direct(t *testing.T) { + createRestartDestroyVTPM(t, "", true, "root", "pass=123456") + createRestartDestroyVTPM(t, "", true, "root", "123456") } diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index c7b1f4677ba..c57707387ae 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -43,6 +43,12 @@ type VTPM struct { // Set of active PCR banks PcrBanks string `json:"pcrbanks"` + // plain text encryption password used by vTPM + encryptionPassword []byte + + // whether an error occurred writing the password to the pipe + passwordPipeError error + // The user under which to run the TPM emulator user string @@ -185,7 +191,7 @@ func hasCapability(capabilities []string, capability string) bool { // with account tss; TPM 2 has more flexibility // // After successful creation of the object the Start() method can be called -func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, createcerts bool, runas string, pcrbanks string) (*VTPM, error) { +func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, createcerts bool, runas string, pcrbanks string, encryptionpassword []byte) (*VTPM, error) { if len(statepath) == 0 { return nil, fmt.Errorf("Missing required statpath for vTPM.") } @@ -234,6 +240,7 @@ func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, crea Vtpmversion: vtpmversion, CreateCerts: createcerts, PcrBanks: pcrbanks, + encryptionPassword: encryptionpassword, Tpm_dev_num: VTPM_DEV_NUM_INVALID, fd: ILLEGAL_FD, swtpmSetupCaps: swtpmSetupCaps, @@ -456,6 +463,34 @@ func (vtpm *VTPM) chownStatePath() error { return nil } +// setup the password pipe so that we can transfer the TPM state encryption via +// a pipe where the read-end is passed to swtpm / swtpm_setup as a file descriptor +func (vtpm *VTPM) setupPasswordPipe(password []byte) (*os.File, error) { + if !hasCapability(vtpm.swtpmSetupCaps, "cmdarg-pwdfile-fd") { + return nil, fmt.Errorf("Requiring newer version of swtpm for state encryption; needs cmdarg-pwd-fd feature") + } + + piper, pipew, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("Could not create pipe") + } + vtpm.passwordPipeError = nil + + go func() { + tot := 0 + for tot < len(password) { + var n int + n, vtpm.passwordPipeError = pipew.Write(password) + if vtpm.passwordPipeError != nil { + break + } + tot = tot + n + } + pipew.Close() + }() + return piper, nil +} + // runSwtpmSetup runs swtpm_setup to simulate TPM manufacturing by creating // EK and platform certificates and enabling TPM 2 PCR banks func (vtpm *VTPM) runSwtpmSetup() error { @@ -473,6 +508,16 @@ func (vtpm *VTPM) runSwtpmSetup() error { if vtpm.CreateCerts { cmd.Args = append(cmd.Args, "--create-ek-cert", "--create-platform-cert", "--lock-nvram") } + if len(vtpm.encryptionPassword) > 0 { + piper, err := vtpm.setupPasswordPipe(vtpm.encryptionPassword) + if err != nil { + return err + } + cmd.ExtraFiles = append(cmd.ExtraFiles, piper) + pwdfile_fd := fmt.Sprintf("%d", 3+len(cmd.ExtraFiles)-1) + cmd.Args = append(cmd.Args, "--cipher", "aes-256-cbc", "--pwdfile-fd", pwdfile_fd) + defer piper.Close() + } if vtpm.Vtpmversion == VTPM_VERSION_2 { cmd.Args = append(cmd.Args, "--tpm2") @@ -491,6 +536,10 @@ func (vtpm *VTPM) runSwtpmSetup() error { return fmt.Errorf("swtpm_setup failed: %s\nlog: %s", string(output), vtpm.ReadLog()) } + if vtpm.passwordPipeError != nil { + return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError) + } + return nil } @@ -552,10 +601,24 @@ func (vtpm *VTPM) startSwtpm() error { file := os.NewFile(uintptr(vtpm.fd), "[vtpm]") cmd.ExtraFiles = append(cmd.ExtraFiles, file) + if len(vtpm.encryptionPassword) > 0 { + piper, err := vtpm.setupPasswordPipe(vtpm.encryptionPassword) + if err != nil { + return err + } + cmd.ExtraFiles = append(cmd.ExtraFiles, piper) + cmd.Args = append(cmd.Args, "--key", + fmt.Sprintf("pwdfd=%d,mode=aes-256-cbc,kdf=pbkdf2", 3+len(cmd.ExtraFiles)-1)) + defer piper.Close() + } + output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("swtpm failed on fd %d: %s\nlog: %s", vtpm.fd, string(output), vtpm.ReadLog()) } + if vtpm.passwordPipeError != nil { + return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError) + } vtpm.Pid, err = vtpm.waitForPidFile(10) if err != nil { From ef1f30f306caa0ce2e48d4f4bd6428cdae9f10d3 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:50:01 -0400 Subject: [PATCH 10/29] maskedPaths: Add /sys/devices/virtual/tpm Add /sys/devices/virtual/tpm to the mask paths to avoid isolation issues via sysfs for TPM 1.2 Signed-off-by: Stefan Berger --- libcontainer/specconv/example.go | 1 + 1 file changed, 1 insertion(+) diff --git a/libcontainer/specconv/example.go b/libcontainer/specconv/example.go index 9e639f19784..de3a36bc955 100644 --- a/libcontainer/specconv/example.go +++ b/libcontainer/specconv/example.go @@ -112,6 +112,7 @@ func Example() *specs.Spec { "/proc/sched_debug", "/sys/firmware", "/proc/scsi", + "/sys/devices/virtual/tpm", }, ReadonlyPaths: []string{ "/proc/bus", From fefeeed40fae1bfc6c7bd7438c9fd5100924e272 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 6 Jul 2020 11:50:21 -0400 Subject: [PATCH 11/29] vtpm: Put vTPMs into container's cgroup Put vTPMs into a container's cgroup to limit their CPU usage. Signed-off-by: Stefan Berger --- libcontainer/container_linux.go | 6 ++++++ libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index cfb3ccf5cd1..00a304bf54f 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -15,6 +15,7 @@ import ( "sync" "time" + vtpmhelper "github.com/opencontainers/runc/libcontainer/vtpm/vtpm-helper" "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink/nl" @@ -371,6 +372,11 @@ func (c *Container) start(process *Process) (retErr error) { return err } } + if len(c.config.VTPMs) > 0 { + if err := vtpmhelper.ApplyCGroupVTPMs(c.config.VTPMs, c.cgroupManager); err != nil { + return err + } + } } return nil } diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index dcd4cf4ca22..842b2ada52d 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -10,6 +10,7 @@ import ( "strings" "syscall" + "github.com/opencontainers/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/vtpm" @@ -153,3 +154,13 @@ func DestroyVTPMs(vtpms []*vtpm.VTPM) { vtpm.Stop(vtpm.CreatedStatepath) } } + +// ApplyCGroupVTPMs puts all VTPMs into the given Cgroup manager's cgroup +func ApplyCGroupVTPMs(vtpms []*vtpm.VTPM, cgroupManager cgroups.Manager) error { + for _, vtpm := range vtpms { + if err := cgroupManager.Apply(vtpm.Pid); err != nil { + return fmt.Errorf("cGroupManager failed to apply vtpm with pid %d: %v", vtpm.Pid, err) + } + } + return nil +} From baa0c946b92be36521aee0d829c6e53dbc3b7b3f Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:43:09 +0000 Subject: [PATCH 12/29] add tpm implementation Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 57 ++--------- libcontainer/vtpm/vtpm.go | 94 ++++++++++--------- utils_linux.go | 66 +++++++++++-- .../runtime-spec/specs-go/config.go | 6 +- 4 files changed, 121 insertions(+), 102 deletions(-) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index 842b2ada52d..198e7912ede 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -8,44 +8,16 @@ import ( "os" "strconv" "strings" - "syscall" "github.com/opencontainers/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/vtpm" "github.com/opencontainers/runtime-spec/specs-go" - "golang.org/x/sys/unix" + // "golang.org/x/sys/unix" + // "github.com/sirupsen/logrus" ) -// addVTPMDevice adds a device and cgroup entry to the spec -func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint32) { - var filemode os.FileMode = 0600 - - device := specs.LinuxDevice{ - Path: hostpath, - Devpath: devpath, - Type: "c", - Major: int64(major), - Minor: int64(minor), - FileMode: &filemode, - } - spec.Linux.Devices = append(spec.Linux.Devices, device) - - major_p := new(int64) - *major_p = int64(major) - minor_p := new(int64) - *minor_p = int64(minor) - - ld := &specs.LinuxDeviceCgroup{ - Allow: true, - Type: "c", - Major: major_p, - Minor: minor_p, - Access: "rwm", - } - spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld) -} // getEncryptionPassword gets the plain password from the caller // valid formats passed to this function are: @@ -80,13 +52,14 @@ func getEncryptionPassword(pwdString string) ([]byte, error) { } // CreateVTPM create a VTPM proxy device and starts the TPM emulator with it -func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM, devnum int) (*vtpm.VTPM, error) { +func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM) (*vtpm.VTPM, error) { + encryptionPassword, err := getEncryptionPassword(vtpmdev.EncryptionPassword) if err != nil { return nil, err } - vtpm, err := vtpm.NewVTPM(vtpmdev.StatePath, vtpmdev.StatePathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.RunAs, vtpmdev.PcrBanks, encryptionPassword) + vtpm, err := vtpm.NewVTPM(vtpmdev, encryptionPassword) if err != nil { return nil, err } @@ -97,33 +70,17 @@ func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM, devnum int) (*vtpm.V return nil, err } - hostdev := vtpm.GetTPMDevname() - major, minor := vtpm.GetMajorMinor() - - devpath := fmt.Sprintf("/dev/tpm%d", devnum) - addVTPMDevice(spec, hostdev, devpath, major, minor) - - // for TPM 2: check if /dev/vtpmrm%d is available - host_tpmrm := fmt.Sprintf("/dev/tpmrm%d", vtpm.GetTPMDevNum()) - if fileInfo, err := os.Lstat(host_tpmrm); err == nil { - if stat_t, ok := fileInfo.Sys().(*syscall.Stat_t); ok { - devNumber := stat_t.Rdev - devpath = fmt.Sprintf("/dev/tpmrm%d", devnum) - addVTPMDevice(spec, host_tpmrm, devpath, unix.Major(devNumber), unix.Minor(devNumber)) - } - } - return vtpm, nil } func setVTPMHostDevOwner(vtpm *vtpm.VTPM, uid, gid int) error { - hostdev := vtpm.GetTPMDevname() + hostdev := vtpm.GetTPMDevpath() // adapt ownership of the device since only root can access it if err := os.Chown(hostdev, uid, gid); err != nil { return err } - host_tpmrm := fmt.Sprintf("/dev/tpmrm%d", vtpm.GetTPMDevNum()) + host_tpmrm := fmt.Sprintf("/dev/tpmrm%s", vtpm.Tpm_dev_num) if _, err := os.Lstat(host_tpmrm); err == nil { // adapt ownership of the device since only root can access it if err := os.Chown(host_tpmrm, uid, gid); err != nil { diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index c57707387ae..a337e7274d1 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -17,13 +17,18 @@ import ( "unsafe" "github.com/opencontainers/runc/libcontainer/apparmor" + "github.com/opencontainers/runtime-spec/specs-go" selinux "github.com/opencontainers/selinux/go-selinux" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) // object type VTPM struct { + // name of vtpm + VtpmName string `json:"vtpmName"` + // The path where the TPM emulator writes the TPM state to StatePath string `json:"statePath"` @@ -53,7 +58,7 @@ type VTPM struct { user string // The TPM device number as returned from /dev/vtpmx ioctl - Tpm_dev_num uint32 `json:"tpm_dev_num"` + Tpm_dev_num string `json:"tpm_dev_num"` // The backend file descriptor fd int32 @@ -80,15 +85,16 @@ type VTPM struct { // ioctl type vtpm_proxy_new_dev struct { flags uint32 - tpm_dev_num uint32 + tpm_dev_num string fd int32 major uint32 minor uint32 } const ( - ILLEGAL_FD = -1 - VTPM_DEV_NUM_INVALID = 0xffffffff + ILLEGAL_FD = -1 + + VTPM_DEV_NUM_INVALID = "xxxxxxx" VTPM_PROXY_IOC_NEW_DEV = 0xc014a100 @@ -191,7 +197,15 @@ func hasCapability(capabilities []string, capability string) bool { // with account tss; TPM 2 has more flexibility // // After successful creation of the object the Start() method can be called -func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, createcerts bool, runas string, pcrbanks string, encryptionpassword []byte) (*VTPM, error) { +func NewVTPM(vtpmdev *specs.LinuxVTPM, encryptionpassword []byte) (*VTPM, error) { + vtpmname := vtpmdev.VTPMName + statepath := vtpmdev.StatePath + vtpmversion := vtpmdev.VTPMVersion + runas := vtpmdev.RunAs + createcerts := vtpmdev.CreateCertificates + statepathismanaged := vtpmdev.StatePathIsManaged + pcrbanks := vtpmdev.PcrBanks + if len(statepath) == 0 { return nil, fmt.Errorf("Missing required statpath for vTPM.") } @@ -203,13 +217,6 @@ func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, crea return nil, fmt.Errorf("Unsupported VTPM version '%s'.", vtpmversion) } - if _, err := os.Stat("/dev/vtpmx"); err != nil { - exec.Command("modprobe", "tpm_vtpm_proxy").Run() - if _, err := os.Stat("/dev/vtpmx"); err != nil { - return nil, fmt.Errorf("VTPM device driver not available.") - } - } - if runas == "" { runas = "root" } @@ -245,6 +252,7 @@ func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, crea fd: ILLEGAL_FD, swtpmSetupCaps: swtpmSetupCaps, swtpmCaps: swtpmCaps, + VtpmName: vtpmname, }, nil } @@ -283,7 +291,7 @@ func (vtpm *VTPM) createDev() error { // getPidFile creates the full path of the TPM emulator PID file func (vtpm *VTPM) getPidFile() string { - return path.Join(vtpm.StatePath, "swtpm.pid") + return path.Join(vtpm.StatePath, vtpm.VtpmName+"-swtpm.pid") } // getLogFile creates the full path of the TPM emulator log file @@ -328,8 +336,8 @@ func (vtpm *VTPM) sendShutdown() error { var err error = nil if vtpm.Tpm_dev_num != VTPM_DEV_NUM_INVALID && vtpm.Vtpmversion == VTPM_VERSION_2 { - devname := vtpm.GetTPMDevname() - dev, err := os.OpenFile(devname, os.O_RDWR, 0666) + devpath := vtpm.GetTPMDevpath() + dev, err := os.OpenFile(devpath, os.O_RDWR, 0666) if err != nil { return err } @@ -339,7 +347,7 @@ func (vtpm *VTPM) sendShutdown() error { 0x00, 0x00, 0x01, 0x45, 0x00, 0x00} n, err := dev.Write(sd) if err != nil || n != len(sd) { - logrus.Errorf("Could not write shutdown to %s: %v", devname, err) + logrus.Errorf("Could not write shutdown to %s: %v", devpath, err) } } return err @@ -547,7 +555,7 @@ func (vtpm *VTPM) runSwtpmSetup() error { // // check whether the swtpm is still alive by checking its PID file func (vtpm *VTPM) waitForTPMDevice(loops int) error { - devname := vtpm.GetTPMDevname() + devpath := vtpm.GetTPMDevpath() pidfile := vtpm.getPidFile() for loops >= 0 { @@ -556,23 +564,35 @@ func (vtpm *VTPM) waitForTPMDevice(loops int) error { return err } - if _, err := os.Stat(devname); err == nil { + if _, err := os.Stat(devpath); err == nil { return nil } time.Sleep(time.Millisecond * 100) loops -= 1 } - return fmt.Errorf("TPM device %s did not appear", devname) + return fmt.Errorf("TPM device %s did not appear", devpath) } -// startSwtpm creates the VTPM proxy device and start the swtpm process -func (vtpm *VTPM) startSwtpm() error { - err := vtpm.createDev() - if err != nil { +func (vtpm *VTPM) GetDeviceNum() error { + if fileInfo, err := os.Lstat(vtpm.GetTPMDevpath()); err == nil { + if stat_t, ok := fileInfo.Sys().(*syscall.Stat_t); ok { + devNumber := stat_t.Rdev + vtpm.major = unix.Major(devNumber) + vtpm.minor = unix.Minor(devNumber) + } + } else { return err } + logrus.Infof("device major num: %d", vtpm.major) + logrus.Infof("device minor num: %d", vtpm.minor) + return nil +} + +// startSwtpm creates the VTPM proxy device and start the swtpm process +func (vtpm *VTPM) startSwtpm() error { + tpm_dev_name := fmt.Sprintf("tpm%s", vtpm.VtpmName) - err = vtpm.setupAppArmor() + err := vtpm.setupAppArmor() if err != nil { return err } @@ -590,16 +610,13 @@ func (vtpm *VTPM) startSwtpm() error { flags += ",startup-clear" } - // child will get first passed fd as '3' - cmd := exec.Command("swtpm", "chardev", "--tpmstate", tpmstate, - "--daemon", "--fd", "3", "--pid", pidfile, "--log", logfile, - "--runas", vtpm.user, "--flags", flags, + cmd := exec.Command("swtpm_cuse", "--tpmstate", tpmstate, + "-n", tpm_dev_name, "--pid", pidfile, "--log", logfile, + "--flags", flags, "--locality", "reject-locality-4,allow-set-locality") if vtpm.Vtpmversion == VTPM_VERSION_2 { cmd.Args = append(cmd.Args, "--tpm2") } - file := os.NewFile(uintptr(vtpm.fd), "[vtpm]") - cmd.ExtraFiles = append(cmd.ExtraFiles, file) if len(vtpm.encryptionPassword) > 0 { piper, err := vtpm.setupPasswordPipe(vtpm.encryptionPassword) @@ -611,7 +628,6 @@ func (vtpm *VTPM) startSwtpm() error { fmt.Sprintf("pwdfd=%d,mode=aes-256-cbc,kdf=pbkdf2", 3+len(cmd.ExtraFiles)-1)) defer piper.Close() } - output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("swtpm failed on fd %d: %s\nlog: %s", vtpm.fd, string(output), vtpm.ReadLog()) @@ -638,7 +654,7 @@ func (vtpm *VTPM) startSwtpm() error { // runSwtpmBios runs swtpm_bios to initialize the TPM func (vtpm *VTPM) runSwtpmBios() error { - tpmname := vtpm.GetTPMDevname() + tpmname := vtpm.GetTPMDevpath() cmd := exec.Command("swtpm_bios", "-n", "-cs", "-u", "--tpm-device", tpmname) if vtpm.Vtpmversion == VTPM_VERSION_2 { @@ -681,12 +697,6 @@ func (vtpm *VTPM) Start() (bool, error) { } }() - err = vtpm.runSwtpmSetup() - if err != nil { - return false, err - } - // set the directory accesses for vtpm.user after swtpm_setup may have needed higher - // privileges err = vtpm.chownStatePath() if err != nil { return false, err @@ -726,14 +736,14 @@ func (vtpm *VTPM) Stop(deleteStatePath bool) error { } // Get the TPM device name; this method can be called after successful Start() -func (vtpm *VTPM) GetTPMDevname() string { - return fmt.Sprintf("/dev/tpm%d", vtpm.Tpm_dev_num) +func (vtpm *VTPM) GetTPMDevpath() string { + return fmt.Sprintf("/dev/tpm%s", vtpm.VtpmName) } // GetTPMDevNum returns the TPM device number; this would return 10 in case // /dev/tpm10 was created on the host; this method can be called after // sucessful Start() -func (vtpm *VTPM) GetTPMDevNum() uint32 { +func (vtpm *VTPM) GetTPMDevNum() string { return vtpm.Tpm_dev_num } @@ -778,7 +788,7 @@ func (vtpm *VTPM) setupAppArmor() error { return nil } - profilename := fmt.Sprintf("runc_%d_swtpm_tpm%d", os.Getpid(), vtpm.GetTPMDevNum()) + profilename := fmt.Sprintf("runc_%d_swtpm_tpm%s", os.Getpid(), vtpm.Tpm_dev_num) if vtpm.Vtpmversion == VTPM_VERSION_1_2 { statefilepattern = path.Join(vtpm.StatePath, "tpm-00.*") } else { diff --git a/utils_linux.go b/utils_linux.go index 06dd3a6630d..69258321a74 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "strconv" + "syscall" "github.com/coreos/go-systemd/v22/activation" "github.com/opencontainers/runc/libcontainer/vtpm" @@ -469,6 +470,35 @@ func maybeLogCgroupWarning(op string, err error) { } } +// addVTPMDevice adds a device and cgroup entry to the spec +func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint32) { + var filemode os.FileMode = 0600 + + device := specs.LinuxDevice{ + Path: hostpath, + Devpath: devpath, + Type: "c", + Major: int64(major), + Minor: int64(minor), + FileMode: &filemode, + } + spec.Linux.Devices = append(spec.Linux.Devices, device) + + major_p := new(int64) + *major_p = int64(major) + minor_p := new(int64) + *minor_p = int64(minor) + + ld := &specs.LinuxDeviceCgroup{ + Allow: true, + Type: "c", + Major: major_p, + Minor: minor_p, + Access: "rwm", + } + spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld) +} + func createVTPMs(spec *specs.Spec) ([]*vtpm.VTPM, error) { var vtpms []*vtpm.VTPM @@ -477,21 +507,41 @@ func createVTPMs(spec *specs.Spec) ([]*vtpm.VTPM, error) { return vtpms, nil } - devnum := 0 for _, vtpm := range r.VTPMs { - v, err := vtpmhelper.CreateVTPM(spec, &vtpm, devnum) - if err != nil { - destroyVTPMs(vtpms) - return vtpms, err + var major uint32 + var minor uint32 + var fileInfo os.FileInfo + var err error + hostdev := "/dev/tpm" + vtpm.VTPMName + if fileInfo, err = os.Lstat(hostdev); err != nil { + v, err := vtpmhelper.CreateVTPM(spec, &vtpm) + if err != nil { + destroyVTPMs(vtpms) + return vtpms, err + } + vtpms = append(vtpms, v) + } + if fileInfo, err = os.Lstat(hostdev); err == nil { + if stat_t, ok := fileInfo.Sys().(*syscall.Stat_t); ok { + devNumber := stat_t.Rdev + major = unix.Major(devNumber) + minor = unix.Minor(devNumber) + } + logrus.Infof("device major num: %d", major) + logrus.Infof("device minor num: %d", minor) + + devpath := hostdev + addVTPMDevice(spec, hostdev, devpath, major, minor) } - vtpms = append(vtpms, v) - devnum++ } + return vtpms, nil } func destroyVTPMs(vtpms []*vtpm.VTPM) { - vtpmhelper.DestroyVTPMs(vtpms) + if len(vtpms) > 0 { + vtpmhelper.DestroyVTPMs(vtpms) + } } func setVTPMHostDevsOwner(config *configs.Config) error { diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index 112c3337cf6..4902adfe3c5 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -458,7 +458,7 @@ type LinuxVTPM struct { // Whether runc is allowed to delete the 'Statepath' once the TPM is destroyed StatePathIsManaged bool `json:"statePathIsManaged,omitempty"` // Version of the TPM that is emulated - TPMVersion string `json:"vtpmVersion,omitempty"` + VTPMVersion string `json:"vtpmVersion,omitempty"` // Whether to create certificates upon first start of vTPM CreateCertificates bool `json:"createCerts,omitempty"` // The PCR banks to enable @@ -467,6 +467,8 @@ type LinuxVTPM struct { RunAs string `json:"runAs,omitempty"` // The password to derive the encryption key from EncryptionPassword string `json:"encryptionPassword,omitempty"` + // Name of the vtpm + VTPMName string `json:"vtpmName,omitempty"` } // LinuxResources has container runtime resource constraints @@ -491,7 +493,7 @@ type LinuxResources struct { Rdma map[string]LinuxRdma `json:"rdma,omitempty"` // Unified resources. Unified map[string]string `json:"unified,omitempty"` - // VTPM configuration + // Linux configuration VTPMs []LinuxVTPM `json:"vtpms,omitempty"` } From 9099e248f10242308d05f69ce86f13e61a0b15ef Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:44:12 +0000 Subject: [PATCH 13/29] fix: use WriteCgroupProc func to apply swtpm pid to container's cgroup Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index 198e7912ede..2c558345afc 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -18,7 +18,6 @@ import ( // "github.com/sirupsen/logrus" ) - // getEncryptionPassword gets the plain password from the caller // valid formats passed to this function are: // - @@ -53,7 +52,6 @@ func getEncryptionPassword(pwdString string) ([]byte, error) { // CreateVTPM create a VTPM proxy device and starts the TPM emulator with it func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM) (*vtpm.VTPM, error) { - encryptionPassword, err := getEncryptionPassword(vtpmdev.EncryptionPassword) if err != nil { return nil, err @@ -115,8 +113,11 @@ func DestroyVTPMs(vtpms []*vtpm.VTPM) { // ApplyCGroupVTPMs puts all VTPMs into the given Cgroup manager's cgroup func ApplyCGroupVTPMs(vtpms []*vtpm.VTPM, cgroupManager cgroups.Manager) error { for _, vtpm := range vtpms { - if err := cgroupManager.Apply(vtpm.Pid); err != nil { - return fmt.Errorf("cGroupManager failed to apply vtpm with pid %d: %v", vtpm.Pid, err) + pathes := cgroupManager.GetPaths() + for subsys, path := range pathes { + if err := cgroups.WriteCgroupProc(path, vtpm.Pid); err != nil { + return fmt.Errorf("cGroupManager failed to apply vtpm %s subsys with pid %d: %v", subsys, vtpm.Pid, err) + } } } return nil From 1ba8bdf2e5f31a7e6f1612bf79c3fd6aaf0d7904 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:44:36 +0000 Subject: [PATCH 14/29] set correct apparmor profile Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index a337e7274d1..18ec1515860 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -800,10 +800,14 @@ func (vtpm *VTPM) setupAppArmor() error { " #include \n"+ " capability setgid,\n"+ " capability setuid,\n"+ + " capability sys_nice,\n"+ " /dev/tpm[0-9]* rw,\n"+ " owner /etc/group r,\n"+ " owner /etc/nsswitch.conf r,\n"+ " owner /etc/passwd r,\n"+ + " /dev/cuse rw,\n"+ + " %s/ rw,\n"+ + " %s/TMP2-00.permall rw,\n"+ " %s/.lock wk,\n"+ " %s w,\n"+ " %s rw,\n"+ @@ -811,6 +815,8 @@ func (vtpm *VTPM) setupAppArmor() error { "}\n", profilename, vtpm.StatePath, + vtpm.StatePath, + vtpm.StatePath, vtpm.getLogFile(), vtpm.getPidFile(), statefilepattern) From 70e205cc89fbe0b54c3c9300e7d29f2e5a073322 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:45:02 +0000 Subject: [PATCH 15/29] fix: apparmor rule to tmp state file Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index 18ec1515860..d34cc92387c 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -783,6 +783,7 @@ func (vtpm *VTPM) CloseServer() error { // exec. func (vtpm *VTPM) setupAppArmor() error { var statefilepattern string + var tmpStateFilePattern string if !apparmor.IsEnabled() { return nil @@ -795,6 +796,14 @@ func (vtpm *VTPM) setupAppArmor() error { statefilepattern = path.Join(vtpm.StatePath, "tpm2-00.*") } + // We do not set backup as option to tpmstate dir, tmpfile (TMP{2}.*) will be used as backup. + // Link to SWTPM_NVRAM_GetFilenameForName function: https://github.com/stefanberger/swtpm/blob/master/src/swtpm/swtpm_nvstore.c#L273 + if vtpm.Vtpmversion == VTPM_VERSION_1_2 { + tmpStateFilePattern = path.Join(vtpm.StatePath, "TMP-00.*") + } else { + tmpStateFilePattern = path.Join(vtpm.StatePath, "TMP2-00.*") + } + profile := fmt.Sprintf("\n#include \n"+ "profile %s {\n"+ " #include \n"+ @@ -807,19 +816,19 @@ func (vtpm *VTPM) setupAppArmor() error { " owner /etc/passwd r,\n"+ " /dev/cuse rw,\n"+ " %s/ rw,\n"+ - " %s/TMP2-00.permall rw,\n"+ " %s/.lock wk,\n"+ " %s w,\n"+ " %s rw,\n"+ " %s rw,\n"+ + " %s rw,\n"+ "}\n", profilename, vtpm.StatePath, vtpm.StatePath, - vtpm.StatePath, vtpm.getLogFile(), vtpm.getPidFile(), - statefilepattern) + statefilepattern, + tmpStateFilePattern) vtpm.aaprofile = path.Join(vtpm.StatePath, "swtpm.apparmor") From 2acbb340277939ae8c4aa87fbd83243b358d57f7 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:45:50 +0000 Subject: [PATCH 16/29] fix: run vtpm tests in a docker container Signed-off-by: Efim Verzakov --- Dockerfile | 24 +++++++++ Makefile | 6 +++ .../vtpm/vtpm-helper/vtpm_helper_test.go | 42 +++++++++++++-- libcontainer/vtpm/vtpm.go | 27 ++++++++-- script/swtpm.sh | 52 +++++++++++++++++++ .../runtime-spec/specs-go/config.go | 4 ++ 6 files changed, 149 insertions(+), 6 deletions(-) create mode 100755 script/swtpm.sh diff --git a/Dockerfile b/Dockerfile index 5c3b933b38e..d5c91586ab3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ ARG GO_VERSION=1.23 ARG BATS_VERSION=v1.11.0 ARG LIBSECCOMP_VERSION=2.5.6 +ARG SWTPM_VERSION=0.10.1 +ARG LIBTPMS_VERSION=0.10.0 FROM golang:${GO_VERSION}-bookworm ARG DEBIAN_FRONTEND=noninteractive @@ -36,6 +38,23 @@ RUN KEYFILE=/usr/share/keyrings/criu-repo-keyring.gpg; \ gcc-powerpc64le-linux-gnu libc-dev-ppc64el-cross \ gcc-s390x-linux-gnu libc-dev-s390x-cross \ gcc-riscv64-linux-gnu libc-dev-riscv64-cross \ + && apt-get install -y --no-install-recommends \ + automake \ + autoconf \ + libtool \ + libssl-dev \ + dh-exec \ + && apt-get install -y --no-install-recommends \ + dh-autoreconf \ + libtasn1-6-dev \ + net-tools \ + libgnutls28-dev \ + libjson-glib-dev \ + expect \ + socat \ + libseccomp-dev \ + libfuse-dev \ + libglib2.0-dev \ && apt-get clean \ && rm -rf /var/cache/apt /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list @@ -63,6 +82,11 @@ ENV LIBSECCOMP_VERSION=$LIBSECCOMP_VERSION ENV LD_LIBRARY_PATH=/opt/libseccomp/lib ENV PKG_CONFIG_PATH=/opt/libseccomp/lib/pkgconfig +ARG SWTPM_VERSION +ARG LIBTPMS_VERSION +COPY script/swtpm.sh /tmp/script/ +RUN /tmp/script/swtpm.sh "$SWTPM_VERSION" "$LIBTPMS_VERSION" + # Prevent the "fatal: detected dubious ownership in repository" git complain during build. RUN git config --global --add safe.directory /go/src/github.com/opencontainers/runc diff --git a/Makefile b/Makefile index 8369d9ac748..fc778b79883 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,9 @@ GO_BUILD_STATIC := $(GO) build $(TRIMPATH) $(GO_BUILDMODE_STATIC) \ GPG_KEYID ?= asarai@suse.de +RUN_IN_CONTAINER_MAJOR := 100 +RUN_IN_CONTAINER_MINOR := 1 + # Some targets need cgo, which is disabled by default when cross compiling. # Enable cgo explicitly for those. # Both runc and libcontainer/integration need libcontainer/nsenter. @@ -155,6 +158,9 @@ unittest: runcimage -t --privileged --rm \ -v /lib/modules:/lib/modules:ro \ -v $(CURDIR):/go/src/$(PROJECT) \ + --device=/dev/cuse --device-cgroup-rule "c $(RUN_IN_CONTAINER_MAJOR):$(RUN_IN_CONTAINER_MINOR) rwm" \ + -e "RUN_IN_CONTAINER_MAJOR=$(RUN_IN_CONTAINER_MAJOR)" \ + -e "RUN_IN_CONTAINER_MINOR=$(RUN_IN_CONTAINER_MINOR)" \ $(RUNC_IMAGE) make localunittest TESTFLAGS="$(TESTFLAGS)" .PHONY: localunittest diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go index cbee06fa5d3..db51ce3e466 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path" + "strconv" "strings" "testing" @@ -18,7 +19,7 @@ import ( func TestCreateVTPMFail(t *testing.T) { vtpmdev := specs.LinuxVTPM{} - _, err := CreateVTPM(&specs.Spec{}, &vtpmdev, 0) + _, err := CreateVTPM(&specs.Spec{}, &vtpmdev) if err == nil { t.Fatalf("Could create vTPM without statepath %v", err) } @@ -37,6 +38,30 @@ func checkPrerequisites(t *testing.T) { } } +const ( + majorEnvName = "RUN_IN_CONTAINER_MAJOR" + minorEnvName = "RUN_IN_CONTAINER_MINOR" +) + +func getDefaultMajorMinorDevices() (uint32, uint32, error) { + var major, minor uint32 + if val := os.Getenv(majorEnvName); len(val) > 0 { + converted, err := strconv.Atoi(val) + if err != nil { + return 0, 0, fmt.Errorf("can not use %s as a device major: %s", val, err) + } + major = uint32(converted) + } + if val := os.Getenv(minorEnvName); len(val) > 0 { + converted, err := strconv.Atoi(val) + if err != nil { + return 0, 0, fmt.Errorf("can not use %s as a device minor: %s", val, err) + } + minor = uint32(converted) + } + return major, minor, nil +} + func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword string) *vtpm.VTPM { checkPrerequisites(t) @@ -55,15 +80,23 @@ func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, Resources: &specs.LinuxResources{}, }, } + + major, minor, err := getDefaultMajorMinorDevices() + if err != nil { + t.Fatalf("Could not get default device major, minor: %s", err) + } + vtpmdev := &specs.LinuxVTPM{ StatePath: tpmdirname, - TPMVersion: tpmversion, + VTPMVersion: tpmversion, CreateCertificates: createCertificates, RunAs: runas, EncryptionPassword: encryptionPassword, + VTPMMajor: major, + VTPMMinor: minor, } - myvtpm, err := CreateVTPM(spec, vtpmdev, 0) + myvtpm, err := CreateVTPM(spec, vtpmdev) if err != nil { if strings.Contains(err.Error(), "VTPM device driver not available") { t.Skipf("%v", err) @@ -82,6 +115,9 @@ func destroyVTPM(t *testing.T, myvtpm *vtpm.VTPM) { if _, err := os.Stat(tpmdirname); !os.IsNotExist(err) { t.Fatalf("State directory should have been removed since it was created by vtpm-helpers") } + if err := os.Remove(myvtpm.GetTPMDevpath()); err != nil && !os.IsNotExist(err) { + t.Fatalf("While testing the docker container, we should remove device ourselves: %s", err) + } } func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword string) { diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index d34cc92387c..9c8de194e52 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -253,6 +253,8 @@ func NewVTPM(vtpmdev *specs.LinuxVTPM, encryptionpassword []byte) (*VTPM, error) swtpmSetupCaps: swtpmSetupCaps, swtpmCaps: swtpmCaps, VtpmName: vtpmname, + major: uint32(vtpmdev.VTPMMajor), + minor: uint32(vtpmdev.VTPMMinor), }, nil } @@ -570,6 +572,15 @@ func (vtpm *VTPM) waitForTPMDevice(loops int) error { time.Sleep(time.Millisecond * 100) loops -= 1 } + // if we testing in the docker container, we should create devices ourselves + if vtpm.major != 0 && vtpm.minor != 0 { + fileMode := 0o666 | unix.S_IFCHR + dev := unix.Mkdev(vtpm.major, vtpm.minor) + if err := unix.Mknod(devpath, uint32(fileMode), int(dev)); err != nil { + return &os.PathError{Op: "mknod", Path: devpath, Err: err} + } + return nil + } return fmt.Errorf("TPM device %s did not appear", devpath) } @@ -609,11 +620,21 @@ func (vtpm *VTPM) startSwtpm() error { if hasCapability(vtpm.swtpmCaps, "flags-opt-startup") { flags += ",startup-clear" } - - cmd := exec.Command("swtpm_cuse", "--tpmstate", tpmstate, + args := []string{ + "--tpmstate", tpmstate, "-n", tpm_dev_name, "--pid", pidfile, "--log", logfile, "--flags", flags, - "--locality", "reject-locality-4,allow-set-locality") + "--locality", "reject-locality-4,allow-set-locality"} + + if vtpm.major != 0 { + args = append(args, fmt.Sprintf("--maj=%d", vtpm.major)) + } + + if vtpm.minor != 0 { + args = append(args, fmt.Sprintf("--min=%d", vtpm.minor)) + } + + cmd := exec.Command("swtpm_cuse", args...) if vtpm.Vtpmversion == VTPM_VERSION_2 { cmd.Args = append(cmd.Args, "--tpm2") } diff --git a/script/swtpm.sh b/script/swtpm.sh new file mode 100755 index 00000000000..e8444d753a5 --- /dev/null +++ b/script/swtpm.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -e -u -o pipefail + +# shellcheck source=./script/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +function build_libtpms() { + local libtpms_ver="$1" + wget "https://github.com/stefanberger/libtpms/archive/refs/tags/v${libtpms_ver}.tar.gz" + + local tpms_srcdir + tpms_srcdir="$(mktemp -d)" + tar xf "v${libtpms_ver}.tar.gz" -C "$tpms_srcdir" + echo $(ls -la $tpms_srcdir) + pushd "$tpms_srcdir/libtpms-${libtpms_ver}" || return + ./autogen.sh --with-openssl --enable-debug + make dist + mv debian/source debian/source.old + dpkg-buildpackage -us -uc -j4 + pushd "$tpms_srcdir" || return + dpkg -i "libtpms0_${libtpms_ver}_amd64.deb" "libtpms-dev_${libtpms_ver}_amd64.deb" + popd || return + popd || return +} + +function build_swtpm() { + local swtpm_ver="$1" + local libtpms_ver="$2" + + build_libtpms $libtpms_ver + + wget "https://github.com/stefanberger/swtpm/archive/refs/tags/v${swtpm_ver}.tar.gz" + + local swtpm_srcdir + swtpm_srcdir="$(mktemp -d)" + tar xf "v${swtpm_ver}.tar.gz" -C "$swtpm_srcdir" + echo $(ls -la $swtpm_srcdir) + pushd "$swtpm_srcdir/swtpm-${swtpm_ver}" || return + ./autogen.sh --with-openssl --with-cuse --prefix=/usr --enable-debug + make -j4 + # make -j4 check + make install + popd || return +} + +if [ $# -lt 2 ]; then + echo "Usage: swtpm.sh " >&2 + exit 1 +fi + +build_swtpm "$@" diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index 4902adfe3c5..fe144c863d4 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -469,6 +469,10 @@ type LinuxVTPM struct { EncryptionPassword string `json:"encryptionPassword,omitempty"` // Name of the vtpm VTPMName string `json:"vtpmName,omitempty"` + // Device's major to be created + VTPMMajor uint32 `json:"vtpmMajor,omitempty"` + // Device's minor to be created + VTPMMinor uint32 `json:"vtpmMinor,omitempty"` } // LinuxResources has container runtime resource constraints From 4659d2e342dd92fee7120e684b3623e528f61f02 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:46:10 +0000 Subject: [PATCH 17/29] cleancode: remove unused code Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 9 -- libcontainer/vtpm/vtpm.go | 139 +------------------ 2 files changed, 2 insertions(+), 146 deletions(-) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index 2c558345afc..b9985c1c926 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -77,15 +77,6 @@ func setVTPMHostDevOwner(vtpm *vtpm.VTPM, uid, gid int) error { if err := os.Chown(hostdev, uid, gid); err != nil { return err } - - host_tpmrm := fmt.Sprintf("/dev/tpmrm%s", vtpm.Tpm_dev_num) - if _, err := os.Lstat(host_tpmrm); err == nil { - // adapt ownership of the device since only root can access it - if err := os.Chown(host_tpmrm, uid, gid); err != nil { - return err - } - } - return nil } diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index 9c8de194e52..5eee7188a2a 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -14,7 +14,6 @@ import ( "strconv" "syscall" "time" - "unsafe" "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runtime-spec/specs-go" @@ -57,12 +56,6 @@ type VTPM struct { // The user under which to run the TPM emulator user string - // The TPM device number as returned from /dev/vtpmx ioctl - Tpm_dev_num string `json:"tpm_dev_num"` - - // The backend file descriptor - fd int32 - // The major number of the created device major uint32 @@ -82,26 +75,9 @@ type VTPM struct { swtpmCaps []string } -// ioctl -type vtpm_proxy_new_dev struct { - flags uint32 - tpm_dev_num string - fd int32 - major uint32 - minor uint32 -} - const ( - ILLEGAL_FD = -1 - - VTPM_DEV_NUM_INVALID = "xxxxxxx" - - VTPM_PROXY_IOC_NEW_DEV = 0xc014a100 - VTPM_VERSION_1_2 = "1.2" VTPM_VERSION_2 = "2" - - VTPM_FLAG_TPM2 = 1 ) func translateUser(username string) (*user.User, error) { @@ -115,31 +91,6 @@ func translateUser(username string) (*user.User, error) { return usr, nil } -func ioctl(fd, cmd, msg uintptr) error { - _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, msg) - if errno != 0 { - err := errno - return err - } - - return nil -} - -func vtpmx_ioctl(cmd, msg uintptr) error { - vtpmx, err := os.Open("/dev/vtpmx") - if err != nil { - logrus.Warnf("Could not open /dev/vtpmx: %v", err) - return err - } - defer vtpmx.Close() - - if err := ioctl(uintptr(vtpmx.Fd()), cmd, msg); err != nil { - return fmt.Errorf("VTPM: vtpmx ioctl failed: %v", err) - } - - return nil -} - // getCapabilities gets the capabilities map of an executable by invoking it with // --print-capabilities. It returns the array of feature strings. // This function returns an empty array if the executable does not support --print-capabilities. @@ -248,8 +199,6 @@ func NewVTPM(vtpmdev *specs.LinuxVTPM, encryptionpassword []byte) (*VTPM, error) CreateCerts: createcerts, PcrBanks: pcrbanks, encryptionPassword: encryptionpassword, - Tpm_dev_num: VTPM_DEV_NUM_INVALID, - fd: ILLEGAL_FD, swtpmSetupCaps: swtpmSetupCaps, swtpmCaps: swtpmCaps, VtpmName: vtpmname, @@ -258,39 +207,6 @@ func NewVTPM(vtpmdev *specs.LinuxVTPM, encryptionpassword []byte) (*VTPM, error) }, nil } -// createDev creates the vTPM proxy device using an ioctl on /dev/vtpmx. -// The ioctl returns the major and minor number of the /dev/tpm%d device -// that was created and the device number to indicate which /dev/tpm%d -// is the device. A file descriptor is also returned that must be passed -// to the TPM emulator for it to read the TPM commands from and write -// TPM response to. -func (vtpm *VTPM) createDev() error { - var ( - vtpm_proxy_new_dev vtpm_proxy_new_dev - ) - - if vtpm.Tpm_dev_num != VTPM_DEV_NUM_INVALID { - logrus.Info("Device already exists") - return nil - } - - if vtpm.Vtpmversion == VTPM_VERSION_2 { - vtpm_proxy_new_dev.flags = VTPM_FLAG_TPM2 - } - - err := vtpmx_ioctl(VTPM_PROXY_IOC_NEW_DEV, uintptr(unsafe.Pointer(&vtpm_proxy_new_dev))) - if err != nil { - return err - } - - vtpm.Tpm_dev_num = vtpm_proxy_new_dev.tpm_dev_num - vtpm.fd = vtpm_proxy_new_dev.fd - vtpm.major = vtpm_proxy_new_dev.major - vtpm.minor = vtpm_proxy_new_dev.minor - - return nil -} - // getPidFile creates the full path of the TPM emulator PID file func (vtpm *VTPM) getPidFile() string { return path.Join(vtpm.StatePath, vtpm.VtpmName+"-swtpm.pid") @@ -332,34 +248,9 @@ func (vtpm *VTPM) waitForPidFile(loops int) (int, error) { return -1, fmt.Errorf("swtpm's pid file did not appear") } -// sendShutdown sends the TPM2_Shutdown command to a TPM 2; no command is -// sent in case of a TPM 1.2 -func (vtpm *VTPM) sendShutdown() error { - var err error = nil - - if vtpm.Tpm_dev_num != VTPM_DEV_NUM_INVALID && vtpm.Vtpmversion == VTPM_VERSION_2 { - devpath := vtpm.GetTPMDevpath() - dev, err := os.OpenFile(devpath, os.O_RDWR, 0666) - if err != nil { - return err - } - defer dev.Close() - - sd := []byte{0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, - 0x00, 0x00, 0x01, 0x45, 0x00, 0x00} - n, err := dev.Write(sd) - if err != nil || n != len(sd) { - logrus.Errorf("Could not write shutdown to %s: %v", devpath, err) - } - } - return err -} - // stopByPidFile: Stop the vTPM by its PID file func (vtpm *VTPM) stopByPidFile() error { - vtpm.sendShutdown() - pid, err := vtpm.getPidFromFile() if err != nil { return err @@ -651,7 +542,7 @@ func (vtpm *VTPM) startSwtpm() error { } output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("swtpm failed on fd %d: %s\nlog: %s", vtpm.fd, string(output), vtpm.ReadLog()) + return fmt.Errorf("swtpm failed on fd %s: %s\nlog: %s", tpm_dev_name, string(output), vtpm.ReadLog()) } if vtpm.passwordPipeError != nil { return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError) @@ -742,13 +633,9 @@ func (vtpm *VTPM) Stop(deleteStatePath bool) error { err := vtpm.stopByPidFile() - vtpm.CloseServer() - vtpm.teardownSELinux() vtpm.teardownAppArmor() - vtpm.Tpm_dev_num = VTPM_DEV_NUM_INVALID - if deleteStatePath { vtpm.DeleteStatePath() } @@ -761,13 +648,6 @@ func (vtpm *VTPM) GetTPMDevpath() string { return fmt.Sprintf("/dev/tpm%s", vtpm.VtpmName) } -// GetTPMDevNum returns the TPM device number; this would return 10 in case -// /dev/tpm10 was created on the host; this method can be called after -// sucessful Start() -func (vtpm *VTPM) GetTPMDevNum() string { - return vtpm.Tpm_dev_num -} - // Get the major and minor numbers of the created TPM device; // This method can be called after successful Start() func (vtpm *VTPM) GetMajorMinor() (uint32, uint32) { @@ -784,21 +664,6 @@ func (vtpm *VTPM) ReadLog() string { return string(output) } -// CloseServer closes the server side file descriptor; this will remove the -// /dev/tpm%d and /dev/tpmrm%d (in case of TPM 2) on the host if the file -// descriptor is the last one holding the device open; also use this function -// after passing the file -// This method can be called after Start() -func (vtpm *VTPM) CloseServer() error { - - if vtpm.fd != ILLEGAL_FD { - os.NewFile(uintptr(vtpm.fd), "[vtpm]").Close() - vtpm.fd = ILLEGAL_FD - } - - return nil -} - // setupAppArmor creates an apparmor profile for swtpm if AppArmor is enabled and // compiles it using apparmor_parser -r and activates it for the next // exec. @@ -810,7 +675,7 @@ func (vtpm *VTPM) setupAppArmor() error { return nil } - profilename := fmt.Sprintf("runc_%d_swtpm_tpm%s", os.Getpid(), vtpm.Tpm_dev_num) + profilename := fmt.Sprintf("runc_%d_swtpm_tpm%s", os.Getpid(), vtpm.VtpmName) if vtpm.Vtpmversion == VTPM_VERSION_1_2 { statefilepattern = path.Join(vtpm.StatePath, "tpm-00.*") } else { From 5b718a92b90a3a8bcce7f093aee8951d22945bd2 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:46:37 +0000 Subject: [PATCH 18/29] tests: add integration test v1 Signed-off-by: Efim Verzakov --- Makefile | 5 +- go.mod | 3 + go.sum | 2 + tests/cmd/tpm-helper/main.go | 82 + tests/integration/swtpm.bats | 129 + vendor/github.com/google/go-tpm/LICENSE | 202 ++ .../google/go-tpm/legacy/tpm2/README.md | 35 + .../google/go-tpm/legacy/tpm2/constants.go | 576 ++++ .../google/go-tpm/legacy/tpm2/error.go | 362 +++ .../google/go-tpm/legacy/tpm2/kdf.go | 116 + .../google/go-tpm/legacy/tpm2/open_other.go | 57 + .../google/go-tpm/legacy/tpm2/open_windows.go | 39 + .../google/go-tpm/legacy/tpm2/structures.go | 1112 ++++++++ .../google/go-tpm/legacy/tpm2/tpm2.go | 2354 +++++++++++++++++ .../github.com/google/go-tpm/tpm/commands.go | 474 ++++ .../github.com/google/go-tpm/tpm/constants.go | 331 +++ vendor/github.com/google/go-tpm/tpm/errors.go | 234 ++ .../google/go-tpm/tpm/open_other.go | 53 + .../google/go-tpm/tpm/open_windows.go | 37 + vendor/github.com/google/go-tpm/tpm/pcrs.go | 203 ++ .../google/go-tpm/tpm/structures.go | 428 +++ .../github.com/google/go-tpm/tpm/testing.md | 43 + vendor/github.com/google/go-tpm/tpm/tpm.go | 1662 ++++++++++++ vendor/github.com/google/go-tpm/tpm/verify.go | 150 ++ .../google/go-tpm/tpmutil/encoding.go | 211 ++ .../google/go-tpm/tpmutil/poll_other.go | 10 + .../google/go-tpm/tpmutil/poll_unix.go | 32 + .../github.com/google/go-tpm/tpmutil/run.go | 113 + .../google/go-tpm/tpmutil/run_other.go | 111 + .../google/go-tpm/tpmutil/run_windows.go | 84 + .../google/go-tpm/tpmutil/structures.go | 195 ++ .../google/go-tpm/tpmutil/tbs/tbs_windows.go | 267 ++ vendor/modules.txt | 7 + 33 files changed, 9718 insertions(+), 1 deletion(-) create mode 100644 tests/cmd/tpm-helper/main.go create mode 100644 tests/integration/swtpm.bats create mode 100644 vendor/github.com/google/go-tpm/LICENSE create mode 100644 vendor/github.com/google/go-tpm/legacy/tpm2/README.md create mode 100644 vendor/github.com/google/go-tpm/legacy/tpm2/constants.go create mode 100644 vendor/github.com/google/go-tpm/legacy/tpm2/error.go create mode 100644 vendor/github.com/google/go-tpm/legacy/tpm2/kdf.go create mode 100644 vendor/github.com/google/go-tpm/legacy/tpm2/open_other.go create mode 100644 vendor/github.com/google/go-tpm/legacy/tpm2/open_windows.go create mode 100644 vendor/github.com/google/go-tpm/legacy/tpm2/structures.go create mode 100644 vendor/github.com/google/go-tpm/legacy/tpm2/tpm2.go create mode 100644 vendor/github.com/google/go-tpm/tpm/commands.go create mode 100644 vendor/github.com/google/go-tpm/tpm/constants.go create mode 100644 vendor/github.com/google/go-tpm/tpm/errors.go create mode 100644 vendor/github.com/google/go-tpm/tpm/open_other.go create mode 100644 vendor/github.com/google/go-tpm/tpm/open_windows.go create mode 100644 vendor/github.com/google/go-tpm/tpm/pcrs.go create mode 100644 vendor/github.com/google/go-tpm/tpm/structures.go create mode 100644 vendor/github.com/google/go-tpm/tpm/testing.md create mode 100644 vendor/github.com/google/go-tpm/tpm/tpm.go create mode 100644 vendor/github.com/google/go-tpm/tpm/verify.go create mode 100644 vendor/github.com/google/go-tpm/tpmutil/encoding.go create mode 100644 vendor/github.com/google/go-tpm/tpmutil/poll_other.go create mode 100644 vendor/github.com/google/go-tpm/tpmutil/poll_unix.go create mode 100644 vendor/github.com/google/go-tpm/tpmutil/run.go create mode 100644 vendor/github.com/google/go-tpm/tpmutil/run_other.go create mode 100644 vendor/github.com/google/go-tpm/tpmutil/run_windows.go create mode 100644 vendor/github.com/google/go-tpm/tpmutil/structures.go create mode 100644 vendor/github.com/google/go-tpm/tpmutil/tbs/tbs_windows.go diff --git a/Makefile b/Makefile index fc778b79883..41d3725c343 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,8 @@ ifneq (,$(filter $(BUILDTAGS),seccomp)) seccompagent: export CGO_ENABLED=1 endif +tpm-helper: export CGO_ENABLED=0 + .DEFAULT: runc .PHONY: runc @@ -90,7 +92,7 @@ TESTBINDIR := tests/cmd/_bin $(TESTBINDIR): mkdir $(TESTBINDIR) -TESTBINS := recvtty sd-helper seccompagent fs-idmap pidfd-kill remap-rootfs key_label +TESTBINS := recvtty sd-helper seccompagent fs-idmap pidfd-kill remap-rootfs key_label tpm-helper .PHONY: test-binaries $(TESTBINS) test-binaries: $(TESTBINS) $(TESTBINS): $(TESTBINDIR) @@ -173,6 +175,7 @@ integration: runcimage -t --privileged --rm \ -v /lib/modules:/lib/modules:ro \ -v $(CURDIR):/go/src/$(PROJECT) \ + --device=/dev/cuse --device-cgroup-rule "c $(RUN_IN_CONTAINER_MAJOR):$(RUN_IN_CONTAINER_MINOR) rwm" \ $(RUNC_IMAGE) make localintegration TESTPATH="$(TESTPATH)" .PHONY: localintegration diff --git a/go.mod b/go.mod index bf28e63b986..dfe8fca2ad9 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/cyphar/filepath-securejoin v0.4.1 github.com/docker/go-units v0.5.0 github.com/godbus/dbus/v5 v5.1.0 + github.com/google/go-tpm v0.0.0-00010101000000-000000000000 github.com/moby/sys/capability v0.4.0 github.com/moby/sys/mountinfo v0.7.2 github.com/moby/sys/user v0.4.0 @@ -32,3 +33,5 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect ) + +replace github.com/google/go-tpm => github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049 diff --git a/go.sum b/go.sum index 00a6f65b121..a078a409713 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049 h1:TOFYpbcgHw260lHT+hW7k97Ri7i/+UfEuQW7/3GBb8E= +github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= diff --git a/tests/cmd/tpm-helper/main.go b/tests/cmd/tpm-helper/main.go new file mode 100644 index 00000000000..bcecb8654ed --- /dev/null +++ b/tests/cmd/tpm-helper/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "time" + + "github.com/google/go-tpm/legacy/tpm2" + "github.com/google/go-tpm/tpm" + "github.com/opencontainers/runc/libcontainer/vtpm" +) + +var ( + deviceVersion = flag.String("deviceVersion", "2", "version of TPM to use") + devicePath = flag.String("devicePath", "/dev/tpm0", "path to the device") +) + +const ( + RandomDataLen = 16 +) + +func WorkWithTPM2(devicePath string, dataCh chan []byte, errCh chan error) { + rwc, err := tpm2.OpenTPM(devicePath) + if err != nil { + errCh <- fmt.Errorf("Can not open tpm2 device path %s: %+v", devicePath, err) + return + } + data, err := tpm2.GetRandom(rwc, RandomDataLen) + if err != nil { + errCh <- fmt.Errorf("Can not get random data from tpm2 device path %s: %+v", devicePath, err) + return + } + dataCh <- data +} + +func WorkWithTPM12(devicePath string, dataCh chan []byte, errCh chan error) { + rwc, err := tpm.OpenTPM(devicePath) + if err != nil { + errCh <- fmt.Errorf("Can not open tpm12 device path %s: %+v", devicePath, err) + return + } + data, err := tpm.GetRandom(rwc, RandomDataLen) + if err != nil { + errCh <- fmt.Errorf("Can not get random data from tpm12 device path %s: %+v", devicePath, err) + return + } + dataCh <- data +} + +func main() { + flag.Parse() + ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) + dataCh := make(chan []byte, RandomDataLen) + errCh := make(chan error) + switch *deviceVersion { + case vtpm.VTPM_VERSION_2: + go WorkWithTPM2(*devicePath, dataCh, errCh) + select { + case <-dataCh: + break + case err := <-errCh: + log.Fatalf("Got an error while working with TPM2 device: %+v", err) + case <-ctx.Done(): + log.Fatalf("Time exceed to get random from TPM2 device") + } + case vtpm.VTPM_VERSION_1_2: + go WorkWithTPM12(*devicePath, dataCh, errCh) + select { + case <-dataCh: + break + case err := <-errCh: + log.Fatalf("Got an error while working with TPM12 device: %+v", err) + case <-ctx.Done(): + log.Fatalf("Time exceed to get random from TPM12 device") + } + default: + log.Fatalf("Wrong TPM version %s", *deviceVersion) + } + +} diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats new file mode 100644 index 00000000000..6854689060a --- /dev/null +++ b/tests/integration/swtpm.bats @@ -0,0 +1,129 @@ +#!/usr/bin/env bats +load helpers + +function setup() { + requires root + rm /dev/tpmtpmm2 || true + rm /dev/tpmtpmm12 || true + rm /dev/tpmtpmstop || true + rm /dev/tpmtpmkill || true + rm /dev/tpmtpmdelete || true + rm /dev/tpmtpmforcekill || true + setup_debian +} + +function teardown() { + teardown_bundle +} + +@test "runc run (with no tpm device)" { + HELPER="tpm-helper" + cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + update_config ' .process.args = ["/bin/'"$HELPER"'"]' + runc run tst + [ "$status" -ne 0 ] +} + +@test "runc run (with one tpm2 device)" { + HELPER="tpm-helper" + cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/'"$HELPER"'", "-devicePath=/dev/tpmtpmm2"] + | .linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmm2", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run tst + [ "$status" -eq 0 ] +} + +@test "runc run (with one tpm1.2 device)" { + HELPER="tpm-helper" + cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/'"$HELPER"'", "-devicePath=/dev/tpmtpmm12", "-deviceVersion=1.2"] + | .linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "1.2", "vtpmname" : "tpmm12", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run tst + [ "$status" -eq 0 ] +} + +@test "runc pause/resume container with vtpm device" { + HELPER="tpm-helper" + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmstop", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + wait_for_container 10 1 tst + + runc pause tst + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmstop -deviceVersion=2 || ret=$? + if [ "$ret" -ne 1 ]; then + fail "should not be able to read from swtpm paused container" + fi + + runc resume tst + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmstop -deviceVersion=2 + ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from swtpm resumed container" + fi +} + +@test "runc kill/delete container with vtpm device" { + HELPER="tpm-helper" + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmkill", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + wait_for_container 10 1 tst + + runc kill tst KILL + [ "$status" -eq 0 ] + wait_for_container 10 1 tst stopped + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmkill -deviceVersion=2 + ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from swtpm killed container" + fi + + runc delete tst + [ "$status" -eq 0 ] + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmkill -deviceVersion=2 || ret=$? + if [ "$ret" -ne 1 ]; then + fail "should not be able to read from swtpm deleted container" + fi +} + +@test "runc force delete container with vtpm device" { + HELPER="tpm-helper" + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmdelete", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + wait_for_container 10 1 tst + + runc delete --force tst + [ "$status" -eq 0 ] + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmdelete -deviceVersion=2 || ret=$? + if [ "$ret" -ne 1 ]; then + fail "should not be able to read from swtpm deleted container" + fi +} + + +@test "runc kill swtpm process" { + HELPER="tpm-helper" + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmforcekill", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + wait_for_container 10 1 tst + + swtpm_pid=$(cat $vtpm_path/tpmforcekill-swtpm.pid) + kill -9 "$swtpm_pid" + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmforcekill -deviceVersion=2 || ret=$? + if [ "$ret" -ne 1 ]; then + fail "should not be able to read from killed swtpm" + fi +} \ No newline at end of file diff --git a/vendor/github.com/google/go-tpm/LICENSE b/vendor/github.com/google/go-tpm/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/vendor/github.com/google/go-tpm/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/google/go-tpm/legacy/tpm2/README.md b/vendor/github.com/google/go-tpm/legacy/tpm2/README.md new file mode 100644 index 00000000000..4d0ff8befa7 --- /dev/null +++ b/vendor/github.com/google/go-tpm/legacy/tpm2/README.md @@ -0,0 +1,35 @@ +# TPM 2.0 client library + +## Tests + +This library contains unit tests in `github.com/google/go-tpm/tpm2`, which just +tests that various encoding and error checking functions work correctly. It also +contains more comprehensive integration tests in +`github.com/google/go-tpm/tpm2/test`, which run actual commands on a TPM. + +By default, these integration tests are run against the +[`go-tpm-tools`](https://github.com/google/go-tpm-tools) +simulator, which is baesed on the +[Microsoft Reference TPM2 code](https://github.com/microsoft/ms-tpm-20-ref). To +run both the unit and integration tests, run (in this directory) +```bash +go test . ./test +``` + +These integration tests can also be run against a real TPM device. This is +slightly more complex as the tests often need to be built as a normal user and +then executed as root. For example, +```bash +# Build the test binary without running it +go test -c github.com/google/go-tpm/tpm2/test +# Execute the test binary as root +sudo ./test.test --tpm-path=/dev/tpmrm0 +``` +On Linux, The `--tpm-path` causes the integration tests to be run against a +real TPM located at that path (usually `/dev/tpmrm0` or `/dev/tpm0`). On Windows, the story is similar, execept that +the `--use-tbs` flag is used instead. + +Tip: if your TPM host is remote and you don't want to install Go on it, this +same two-step process can be used. The test binary can be copied to a remote +host and run without extra installation (as the test binary has very few +*runtime* dependancies). diff --git a/vendor/github.com/google/go-tpm/legacy/tpm2/constants.go b/vendor/github.com/google/go-tpm/legacy/tpm2/constants.go new file mode 100644 index 00000000000..1357370aa2f --- /dev/null +++ b/vendor/github.com/google/go-tpm/legacy/tpm2/constants.go @@ -0,0 +1,576 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm2 + +import ( + "crypto" + "crypto/elliptic" + "fmt" + "strings" + + // Register the relevant hash implementations to prevent a runtime failure. + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + + "github.com/google/go-tpm/tpmutil" +) + +var hashInfo = []struct { + alg Algorithm + hash crypto.Hash +}{ + {AlgSHA1, crypto.SHA1}, + {AlgSHA256, crypto.SHA256}, + {AlgSHA384, crypto.SHA384}, + {AlgSHA512, crypto.SHA512}, + {AlgSHA3_256, crypto.SHA3_256}, + {AlgSHA3_384, crypto.SHA3_384}, + {AlgSHA3_512, crypto.SHA3_512}, +} + +// MAX_DIGEST_BUFFER is the maximum size of []byte request or response fields. +// Typically used for chunking of big blobs of data (such as for hashing or +// encryption). +const maxDigestBuffer = 1024 + +// Algorithm represents a TPM_ALG_ID value. +type Algorithm uint16 + +// HashToAlgorithm looks up the TPM2 algorithm corresponding to the provided crypto.Hash +func HashToAlgorithm(hash crypto.Hash) (Algorithm, error) { + for _, info := range hashInfo { + if info.hash == hash { + return info.alg, nil + } + } + return AlgUnknown, fmt.Errorf("go hash algorithm #%d has no TPM2 algorithm", hash) +} + +// IsNull returns true if a is AlgNull or zero (unset). +func (a Algorithm) IsNull() bool { + return a == AlgNull || a == AlgUnknown +} + +// UsesCount returns true if a signature algorithm uses count value. +func (a Algorithm) UsesCount() bool { + return a == AlgECDAA +} + +// UsesHash returns true if the algorithm requires the use of a hash. +func (a Algorithm) UsesHash() bool { + return a == AlgOAEP +} + +// Hash returns a crypto.Hash based on the given TPM_ALG_ID. +// An error is returned if the given algorithm is not a hash algorithm or is not available. +func (a Algorithm) Hash() (crypto.Hash, error) { + for _, info := range hashInfo { + if info.alg == a { + if !info.hash.Available() { + return crypto.Hash(0), fmt.Errorf("go hash algorithm #%d not available", info.hash) + } + return info.hash, nil + } + } + return crypto.Hash(0), fmt.Errorf("hash algorithm not supported: 0x%x", a) +} + +func (a Algorithm) String() string { + var s strings.Builder + var err error + switch a { + case AlgUnknown: + _, err = s.WriteString("AlgUnknown") + case AlgRSA: + _, err = s.WriteString("RSA") + case AlgSHA1: + _, err = s.WriteString("SHA1") + case AlgHMAC: + _, err = s.WriteString("HMAC") + case AlgAES: + _, err = s.WriteString("AES") + case AlgKeyedHash: + _, err = s.WriteString("KeyedHash") + case AlgXOR: + _, err = s.WriteString("XOR") + case AlgSHA256: + _, err = s.WriteString("SHA256") + case AlgSHA384: + _, err = s.WriteString("SHA384") + case AlgSHA512: + _, err = s.WriteString("SHA512") + case AlgNull: + _, err = s.WriteString("AlgNull") + case AlgRSASSA: + _, err = s.WriteString("RSASSA") + case AlgRSAES: + _, err = s.WriteString("RSAES") + case AlgRSAPSS: + _, err = s.WriteString("RSAPSS") + case AlgOAEP: + _, err = s.WriteString("OAEP") + case AlgECDSA: + _, err = s.WriteString("ECDSA") + case AlgECDH: + _, err = s.WriteString("ECDH") + case AlgECDAA: + _, err = s.WriteString("ECDAA") + case AlgKDF2: + _, err = s.WriteString("KDF2") + case AlgECC: + _, err = s.WriteString("ECC") + case AlgSymCipher: + _, err = s.WriteString("SymCipher") + case AlgSHA3_256: + _, err = s.WriteString("SHA3_256") + case AlgSHA3_384: + _, err = s.WriteString("SHA3_384") + case AlgSHA3_512: + _, err = s.WriteString("SHA3_512") + case AlgCTR: + _, err = s.WriteString("CTR") + case AlgOFB: + _, err = s.WriteString("OFB") + case AlgCBC: + _, err = s.WriteString("CBC") + case AlgCFB: + _, err = s.WriteString("CFB") + case AlgECB: + _, err = s.WriteString("ECB") + default: + return fmt.Sprintf("Alg?<%d>", int(a)) + } + if err != nil { + return fmt.Sprintf("Writing to string builder failed: %v", err) + } + return s.String() +} + +// Supported Algorithms. +const ( + AlgUnknown Algorithm = 0x0000 + AlgRSA Algorithm = 0x0001 + AlgSHA1 Algorithm = 0x0004 + AlgHMAC Algorithm = 0x0005 + AlgAES Algorithm = 0x0006 + AlgKeyedHash Algorithm = 0x0008 + AlgXOR Algorithm = 0x000A + AlgSHA256 Algorithm = 0x000B + AlgSHA384 Algorithm = 0x000C + AlgSHA512 Algorithm = 0x000D + AlgNull Algorithm = 0x0010 + AlgRSASSA Algorithm = 0x0014 + AlgRSAES Algorithm = 0x0015 + AlgRSAPSS Algorithm = 0x0016 + AlgOAEP Algorithm = 0x0017 + AlgECDSA Algorithm = 0x0018 + AlgECDH Algorithm = 0x0019 + AlgECDAA Algorithm = 0x001A + AlgKDF2 Algorithm = 0x0021 + AlgECC Algorithm = 0x0023 + AlgSymCipher Algorithm = 0x0025 + AlgSHA3_256 Algorithm = 0x0027 + AlgSHA3_384 Algorithm = 0x0028 + AlgSHA3_512 Algorithm = 0x0029 + AlgCTR Algorithm = 0x0040 + AlgOFB Algorithm = 0x0041 + AlgCBC Algorithm = 0x0042 + AlgCFB Algorithm = 0x0043 + AlgECB Algorithm = 0x0044 +) + +// HandleType defines a type of handle. +type HandleType uint8 + +// Supported handle types +const ( + HandleTypePCR HandleType = 0x00 + HandleTypeNVIndex HandleType = 0x01 + HandleTypeHMACSession HandleType = 0x02 + HandleTypeLoadedSession HandleType = 0x02 + HandleTypePolicySession HandleType = 0x03 + HandleTypeSavedSession HandleType = 0x03 + HandleTypePermanent HandleType = 0x40 + HandleTypeTransient HandleType = 0x80 + HandleTypePersistent HandleType = 0x81 +) + +// SessionType defines the type of session created in StartAuthSession. +type SessionType uint8 + +// Supported session types. +const ( + SessionHMAC SessionType = 0x00 + SessionPolicy SessionType = 0x01 + SessionTrial SessionType = 0x03 +) + +// SessionAttributes represents an attribute of a session. +type SessionAttributes byte + +// Session Attributes (Structures 8.4 TPMA_SESSION) +const ( + AttrContinueSession SessionAttributes = 1 << iota + AttrAuditExclusive + AttrAuditReset + _ // bit 3 reserved + _ // bit 4 reserved + AttrDecrypt + AttrEcrypt + AttrAudit +) + +// EmptyAuth represents the empty authorization value. +var EmptyAuth []byte + +// KeyProp is a bitmask used in Attributes field of key templates. Individual +// flags should be OR-ed to form a full mask. +type KeyProp uint32 + +// Key properties. +const ( + FlagFixedTPM KeyProp = 0x00000002 + FlagStClear KeyProp = 0x00000004 + FlagFixedParent KeyProp = 0x00000010 + FlagSensitiveDataOrigin KeyProp = 0x00000020 + FlagUserWithAuth KeyProp = 0x00000040 + FlagAdminWithPolicy KeyProp = 0x00000080 + FlagNoDA KeyProp = 0x00000400 + FlagRestricted KeyProp = 0x00010000 + FlagDecrypt KeyProp = 0x00020000 + FlagSign KeyProp = 0x00040000 + + FlagSealDefault = FlagFixedTPM | FlagFixedParent + FlagSignerDefault = FlagSign | FlagRestricted | FlagFixedTPM | + FlagFixedParent | FlagSensitiveDataOrigin | FlagUserWithAuth + FlagStorageDefault = FlagDecrypt | FlagRestricted | FlagFixedTPM | + FlagFixedParent | FlagSensitiveDataOrigin | FlagUserWithAuth +) + +// TPMProp represents a Property Tag (TPM_PT) used with calls to GetCapability(CapabilityTPMProperties). +type TPMProp uint32 + +// TPM Capability Properties, see TPM 2.0 Spec, Rev 1.38, Table 23. +// Fixed TPM Properties (PT_FIXED) +const ( + FamilyIndicator TPMProp = 0x100 + iota + SpecLevel + SpecRevision + SpecDayOfYear + SpecYear + Manufacturer + VendorString1 + VendorString2 + VendorString3 + VendorString4 + VendorTPMType + FirmwareVersion1 + FirmwareVersion2 + InputMaxBufferSize + TransientObjectsMin + PersistentObjectsMin + LoadedObjectsMin + ActiveSessionsMax + PCRCount + PCRSelectMin + ContextGapMax + _ // (PT_FIXED + 21) is skipped + NVCountersMax + NVIndexMax + MemoryMethod + ClockUpdate + ContextHash + ContextSym + ContextSymSize + OrderlyCount + CommandMaxSize + ResponseMaxSize + DigestMaxSize + ObjectContextMaxSize + SessionContextMaxSize + PSFamilyIndicator + PSSpecLevel + PSSpecRevision + PSSpecDayOfYear + PSSpecYear + SplitSigningMax + TotalCommands + LibraryCommands + VendorCommands + NVMaxBufferSize + TPMModes + CapabilityMaxBufferSize +) + +// Variable TPM Properties (PT_VAR) +const ( + TPMAPermanent TPMProp = 0x200 + iota + TPMAStartupClear + HRNVIndex + HRLoaded + HRLoadedAvail + HRActive + HRActiveAvail + HRTransientAvail + CurrentPersistent + AvailPersistent + NVCounters + NVCountersAvail + AlgorithmSet + LoadedCurves + LockoutCounter + MaxAuthFail + LockoutInterval + LockoutRecovery + NVWriteRecovery + AuditCounter0 + AuditCounter1 +) + +// Allowed ranges of different kinds of Handles (TPM_HANDLE) +// These constants have type TPMProp for backwards compatibility. +const ( + PCRFirst TPMProp = 0x00000000 + HMACSessionFirst TPMProp = 0x02000000 + LoadedSessionFirst TPMProp = 0x02000000 + PolicySessionFirst TPMProp = 0x03000000 + ActiveSessionFirst TPMProp = 0x03000000 + TransientFirst TPMProp = 0x80000000 + PersistentFirst TPMProp = 0x81000000 + PersistentLast TPMProp = 0x81FFFFFF + PlatformPersistent TPMProp = 0x81800000 + NVIndexFirst TPMProp = 0x01000000 + NVIndexLast TPMProp = 0x01FFFFFF + PermanentFirst TPMProp = 0x40000000 + PermanentLast TPMProp = 0x4000010F +) + +// Reserved Handles. +const ( + HandleOwner tpmutil.Handle = 0x40000001 + iota + HandleRevoke + HandleTransport + HandleOperator + HandleAdmin + HandleEK + HandleNull + HandleUnassigned + HandlePasswordSession + HandleLockout + HandleEndorsement + HandlePlatform +) + +// Capability identifies some TPM property or state type. +type Capability uint32 + +// TPM Capabilities. +const ( + CapabilityAlgs Capability = iota + CapabilityHandles + CapabilityCommands + CapabilityPPCommands + CapabilityAuditCommands + CapabilityPCRs + CapabilityTPMProperties + CapabilityPCRProperties + CapabilityECCCurves + CapabilityAuthPolicies +) + +// TPM Structure Tags. Tags are used to disambiguate structures, similar to Alg +// values: tag value defines what kind of data lives in a nested field. +const ( + TagNull tpmutil.Tag = 0x8000 + TagNoSessions tpmutil.Tag = 0x8001 + TagSessions tpmutil.Tag = 0x8002 + TagAttestCertify tpmutil.Tag = 0x8017 + TagAttestQuote tpmutil.Tag = 0x8018 + TagAttestCreation tpmutil.Tag = 0x801a + TagAuthSecret tpmutil.Tag = 0x8023 + TagHashCheck tpmutil.Tag = 0x8024 + TagAuthSigned tpmutil.Tag = 0x8025 +) + +// StartupType instructs the TPM on how to handle its state during Shutdown or +// Startup. +type StartupType uint16 + +// Startup types +const ( + StartupClear StartupType = iota + StartupState +) + +// EllipticCurve identifies specific EC curves. +type EllipticCurve uint16 + +// ECC curves supported by TPM 2.0 spec. +const ( + CurveNISTP192 = EllipticCurve(iota + 1) + CurveNISTP224 + CurveNISTP256 + CurveNISTP384 + CurveNISTP521 + + CurveBNP256 = EllipticCurve(iota + 10) + CurveBNP638 + + CurveSM2P256 = EllipticCurve(0x0020) +) + +var toGoCurve = map[EllipticCurve]elliptic.Curve{ + CurveNISTP224: elliptic.P224(), + CurveNISTP256: elliptic.P256(), + CurveNISTP384: elliptic.P384(), + CurveNISTP521: elliptic.P521(), +} + +// Supported TPM operations. +const ( + CmdNVUndefineSpaceSpecial tpmutil.Command = 0x0000011F + CmdEvictControl tpmutil.Command = 0x00000120 + CmdUndefineSpace tpmutil.Command = 0x00000122 + CmdClear tpmutil.Command = 0x00000126 + CmdHierarchyChangeAuth tpmutil.Command = 0x00000129 + CmdDefineSpace tpmutil.Command = 0x0000012A + CmdPCRAllocate tpmutil.Command = 0x0000012B + CmdCreatePrimary tpmutil.Command = 0x00000131 + CmdIncrementNVCounter tpmutil.Command = 0x00000134 + CmdWriteNV tpmutil.Command = 0x00000137 + CmdWriteLockNV tpmutil.Command = 0x00000138 + CmdDictionaryAttackLockReset tpmutil.Command = 0x00000139 + CmdDictionaryAttackParameters tpmutil.Command = 0x0000013A + CmdPCREvent tpmutil.Command = 0x0000013C + CmdPCRReset tpmutil.Command = 0x0000013D + CmdSequenceComplete tpmutil.Command = 0x0000013E + CmdStartup tpmutil.Command = 0x00000144 + CmdShutdown tpmutil.Command = 0x00000145 + CmdActivateCredential tpmutil.Command = 0x00000147 + CmdCertify tpmutil.Command = 0x00000148 + CmdCertifyCreation tpmutil.Command = 0x0000014A + CmdReadNV tpmutil.Command = 0x0000014E + CmdReadLockNV tpmutil.Command = 0x0000014F + CmdPolicySecret tpmutil.Command = 0x00000151 + CmdCreate tpmutil.Command = 0x00000153 + CmdECDHZGen tpmutil.Command = 0x00000154 + CmdImport tpmutil.Command = 0x00000156 + CmdLoad tpmutil.Command = 0x00000157 + CmdQuote tpmutil.Command = 0x00000158 + CmdRSADecrypt tpmutil.Command = 0x00000159 + CmdSequenceUpdate tpmutil.Command = 0x0000015C + CmdSign tpmutil.Command = 0x0000015D + CmdUnseal tpmutil.Command = 0x0000015E + CmdPolicySigned tpmutil.Command = 0x00000160 + CmdContextLoad tpmutil.Command = 0x00000161 + CmdContextSave tpmutil.Command = 0x00000162 + CmdECDHKeyGen tpmutil.Command = 0x00000163 + CmdEncryptDecrypt tpmutil.Command = 0x00000164 + CmdFlushContext tpmutil.Command = 0x00000165 + CmdLoadExternal tpmutil.Command = 0x00000167 + CmdMakeCredential tpmutil.Command = 0x00000168 + CmdReadPublicNV tpmutil.Command = 0x00000169 + CmdPolicyCommandCode tpmutil.Command = 0x0000016C + CmdPolicyOr tpmutil.Command = 0x00000171 + CmdReadPublic tpmutil.Command = 0x00000173 + CmdRSAEncrypt tpmutil.Command = 0x00000174 + CmdStartAuthSession tpmutil.Command = 0x00000176 + CmdGetCapability tpmutil.Command = 0x0000017A + CmdGetRandom tpmutil.Command = 0x0000017B + CmdHash tpmutil.Command = 0x0000017D + CmdPCRRead tpmutil.Command = 0x0000017E + CmdPolicyPCR tpmutil.Command = 0x0000017F + CmdReadClock tpmutil.Command = 0x00000181 + CmdPCRExtend tpmutil.Command = 0x00000182 + CmdEventSequenceComplete tpmutil.Command = 0x00000185 + CmdHashSequenceStart tpmutil.Command = 0x00000186 + CmdPolicyGetDigest tpmutil.Command = 0x00000189 + CmdPolicyPassword tpmutil.Command = 0x0000018C + CmdEncryptDecrypt2 tpmutil.Command = 0x00000193 +) + +// Regular TPM 2.0 devices use 24-bit mask (3 bytes) for PCR selection. +const sizeOfPCRSelect = 3 + +const defaultRSAExponent = 1<<16 + 1 + +// NVAttr is a bitmask used in Attributes field of NV indexes. Individual +// flags should be OR-ed to form a full mask. +type NVAttr uint32 + +// NV Attributes +const ( + AttrPPWrite NVAttr = 0x00000001 + AttrOwnerWrite NVAttr = 0x00000002 + AttrAuthWrite NVAttr = 0x00000004 + AttrPolicyWrite NVAttr = 0x00000008 + AttrPolicyDelete NVAttr = 0x00000400 + AttrWriteLocked NVAttr = 0x00000800 + AttrWriteAll NVAttr = 0x00001000 + AttrWriteDefine NVAttr = 0x00002000 + AttrWriteSTClear NVAttr = 0x00004000 + AttrGlobalLock NVAttr = 0x00008000 + AttrPPRead NVAttr = 0x00010000 + AttrOwnerRead NVAttr = 0x00020000 + AttrAuthRead NVAttr = 0x00040000 + AttrPolicyRead NVAttr = 0x00080000 + AttrNoDA NVAttr = 0x02000000 + AttrOrderly NVAttr = 0x04000000 + AttrClearSTClear NVAttr = 0x08000000 + AttrReadLocked NVAttr = 0x10000000 + AttrWritten NVAttr = 0x20000000 + AttrPlatformCreate NVAttr = 0x40000000 + AttrReadSTClear NVAttr = 0x80000000 +) + +var permMap = map[NVAttr]string{ + AttrPPWrite: "PPWrite", + AttrOwnerWrite: "OwnerWrite", + AttrAuthWrite: "AuthWrite", + AttrPolicyWrite: "PolicyWrite", + AttrPolicyDelete: "PolicyDelete", + AttrWriteLocked: "WriteLocked", + AttrWriteAll: "WriteAll", + AttrWriteDefine: "WriteDefine", + AttrWriteSTClear: "WriteSTClear", + AttrGlobalLock: "GlobalLock", + AttrPPRead: "PPRead", + AttrOwnerRead: "OwnerRead", + AttrAuthRead: "AuthRead", + AttrPolicyRead: "PolicyRead", + AttrNoDA: "No Do", + AttrOrderly: "Oderly", + AttrClearSTClear: "ClearSTClear", + AttrReadLocked: "ReadLocked", + AttrWritten: "Writte", + AttrPlatformCreate: "PlatformCreate", + AttrReadSTClear: "ReadSTClear", +} + +// String returns a textual representation of the set of NVAttr +func (p NVAttr) String() string { + var retString strings.Builder + for iterator, item := range permMap { + if (p & iterator) != 0 { + retString.WriteString(item + " + ") + } + } + if retString.String() == "" { + return "Permission/s not found" + } + return strings.TrimSuffix(retString.String(), " + ") + +} diff --git a/vendor/github.com/google/go-tpm/legacy/tpm2/error.go b/vendor/github.com/google/go-tpm/legacy/tpm2/error.go new file mode 100644 index 00000000000..e1983356fe7 --- /dev/null +++ b/vendor/github.com/google/go-tpm/legacy/tpm2/error.go @@ -0,0 +1,362 @@ +package tpm2 + +import ( + "fmt" + + "github.com/google/go-tpm/tpmutil" +) + +type ( + // RCFmt0 holds Format 0 error codes + RCFmt0 uint8 + + // RCFmt1 holds Format 1 error codes + RCFmt1 uint8 + + // RCWarn holds error codes used in warnings + RCWarn uint8 + + // RCIndex is used to reference arguments, handles and sessions in errors + RCIndex uint8 +) + +// Format 0 error codes. +const ( + RCInitialize RCFmt0 = 0x00 + RCFailure RCFmt0 = 0x01 + RCSequence RCFmt0 = 0x03 + RCPrivate RCFmt0 = 0x0B + RCHMAC RCFmt0 = 0x19 + RCDisabled RCFmt0 = 0x20 + RCExclusive RCFmt0 = 0x21 + RCAuthType RCFmt0 = 0x24 + RCAuthMissing RCFmt0 = 0x25 + RCPolicy RCFmt0 = 0x26 + RCPCR RCFmt0 = 0x27 + RCPCRChanged RCFmt0 = 0x28 + RCUpgrade RCFmt0 = 0x2D + RCTooManyContexts RCFmt0 = 0x2E + RCAuthUnavailable RCFmt0 = 0x2F + RCReboot RCFmt0 = 0x30 + RCUnbalanced RCFmt0 = 0x31 + RCCommandSize RCFmt0 = 0x42 + RCCommandCode RCFmt0 = 0x43 + RCAuthSize RCFmt0 = 0x44 + RCAuthContext RCFmt0 = 0x45 + RCNVRange RCFmt0 = 0x46 + RCNVSize RCFmt0 = 0x47 + RCNVLocked RCFmt0 = 0x48 + RCNVAuthorization RCFmt0 = 0x49 + RCNVUninitialized RCFmt0 = 0x4A + RCNVSpace RCFmt0 = 0x4B + RCNVDefined RCFmt0 = 0x4C + RCBadContext RCFmt0 = 0x50 + RCCPHash RCFmt0 = 0x51 + RCParent RCFmt0 = 0x52 + RCNeedsTest RCFmt0 = 0x53 + RCNoResult RCFmt0 = 0x54 + RCSensitive RCFmt0 = 0x55 +) + +var fmt0Msg = map[RCFmt0]string{ + RCInitialize: "TPM not initialized by TPM2_Startup or already initialized", + RCFailure: "commands not being accepted because of a TPM failure", + RCSequence: "improper use of a sequence handle", + RCPrivate: "not currently used", + RCHMAC: "not currently used", + RCDisabled: "the command is disabled", + RCExclusive: "command failed because audit sequence required exclusivity", + RCAuthType: "authorization handle is not correct for command", + RCAuthMissing: "5 command requires an authorization session for handle and it is not present", + RCPolicy: "policy failure in math operation or an invalid authPolicy value", + RCPCR: "PCR check fail", + RCPCRChanged: "PCR have changed since checked", + RCUpgrade: "TPM is in field upgrade mode unless called via TPM2_FieldUpgradeData(), then it is not in field upgrade mode", + RCTooManyContexts: "context ID counter is at maximum", + RCAuthUnavailable: "authValue or authPolicy is not available for selected entity", + RCReboot: "a _TPM_Init and Startup(CLEAR) is required before the TPM can resume operation", + RCUnbalanced: "the protection algorithms (hash and symmetric) are not reasonably balanced; the digest size of the hash must be larger than the key size of the symmetric algorithm", + RCCommandSize: "command commandSize value is inconsistent with contents of the command buffer; either the size is not the same as the octets loaded by the hardware interface layer or the value is not large enough to hold a command header", + RCCommandCode: "command code not supported", + RCAuthSize: "the value of authorizationSize is out of range or the number of octets in the Authorization Area is greater than required", + RCAuthContext: "use of an authorization session with a context command or another command that cannot have an authorization session", + RCNVRange: "NV offset+size is out of range", + RCNVSize: "Requested allocation size is larger than allowed", + RCNVLocked: "NV access locked", + RCNVAuthorization: "NV access authorization fails in command actions", + RCNVUninitialized: "an NV Index is used before being initialized or the state saved by TPM2_Shutdown(STATE) could not be restored", + RCNVSpace: "insufficient space for NV allocation", + RCNVDefined: "NV Index or persistent object already defined", + RCBadContext: "context in TPM2_ContextLoad() is not valid", + RCCPHash: "cpHash value already set or not correct for use", + RCParent: "handle for parent is not a valid parent", + RCNeedsTest: "some function needs testing", + RCNoResult: "returned when an internal function cannot process a request due to an unspecified problem; this code is usually related to invalid parameters that are not properly filtered by the input unmarshaling code", + RCSensitive: "the sensitive area did not unmarshal correctly after decryption", +} + +// Format 1 error codes. +const ( + RCAsymmetric = 0x01 + RCAttributes = 0x02 + RCHash = 0x03 + RCValue = 0x04 + RCHierarchy = 0x05 + RCKeySize = 0x07 + RCMGF = 0x08 + RCMode = 0x09 + RCType = 0x0A + RCHandle = 0x0B + RCKDF = 0x0C + RCRange = 0x0D + RCAuthFail = 0x0E + RCNonce = 0x0F + RCPP = 0x10 + RCScheme = 0x12 + RCSize = 0x15 + RCSymmetric = 0x16 + RCTag = 0x17 + RCSelector = 0x18 + RCInsufficient = 0x1A + RCSignature = 0x1B + RCKey = 0x1C + RCPolicyFail = 0x1D + RCIntegrity = 0x1F + RCTicket = 0x20 + RCReservedBits = 0x21 + RCBadAuth = 0x22 + RCExpired = 0x23 + RCPolicyCC = 0x24 + RCBinding = 0x25 + RCCurve = 0x26 + RCECCPoint = 0x27 +) + +var fmt1Msg = map[RCFmt1]string{ + RCAsymmetric: "asymmetric algorithm not supported or not correct", + RCAttributes: "inconsistent attributes", + RCHash: "hash algorithm not supported or not appropriate", + RCValue: "value is out of range or is not correct for the context", + RCHierarchy: "hierarchy is not enabled or is not correct for the use", + RCKeySize: "key size is not supported", + RCMGF: "mask generation function not supported", + RCMode: "mode of operation not supported", + RCType: "the type of the value is not appropriate for the use", + RCHandle: "the handle is not correct for the use", + RCKDF: "unsupported key derivation function or function not appropriate for use", + RCRange: "value was out of allowed range", + RCAuthFail: "the authorization HMAC check failed and DA counter incremented", + RCNonce: "invalid nonce size or nonce value mismatch", + RCPP: "authorization requires assertion of PP", + RCScheme: "unsupported or incompatible scheme", + RCSize: "structure is the wrong size", + RCSymmetric: "unsupported symmetric algorithm or key size, or not appropriate for instance", + RCTag: "incorrect structure tag", + RCSelector: "union selector is incorrect", + RCInsufficient: "the TPM was unable to unmarshal a value because there were not enough octets in the input buffer", + RCSignature: "the signature is not valid", + RCKey: "key fields are not compatible with the selected use", + RCPolicyFail: "a policy check failed", + RCIntegrity: "integrity check failed", + RCTicket: "invalid ticket", + RCReservedBits: "reserved bits not set to zero as required", + RCBadAuth: "authorization failure without DA implications", + RCExpired: "the policy has expired", + RCPolicyCC: "the commandCode in the policy is not the commandCode of the command or the command code in a policy command references a command that is not implemented", + RCBinding: "public and sensitive portions of an object are not cryptographically bound", + RCCurve: "curve not supported", + RCECCPoint: "point is not on the required curve", +} + +// Warning codes. +const ( + RCContextGap RCWarn = 0x01 + RCObjectMemory RCWarn = 0x02 + RCSessionMemory RCWarn = 0x03 + RCMemory RCWarn = 0x04 + RCSessionHandles RCWarn = 0x05 + RCObjectHandles RCWarn = 0x06 + RCLocality RCWarn = 0x07 + RCYielded RCWarn = 0x08 + RCCanceled RCWarn = 0x09 + RCTesting RCWarn = 0x0A + RCReferenceH0 RCWarn = 0x10 + RCReferenceH1 RCWarn = 0x11 + RCReferenceH2 RCWarn = 0x12 + RCReferenceH3 RCWarn = 0x13 + RCReferenceH4 RCWarn = 0x14 + RCReferenceH5 RCWarn = 0x15 + RCReferenceH6 RCWarn = 0x16 + RCReferenceS0 RCWarn = 0x18 + RCReferenceS1 RCWarn = 0x19 + RCReferenceS2 RCWarn = 0x1A + RCReferenceS3 RCWarn = 0x1B + RCReferenceS4 RCWarn = 0x1C + RCReferenceS5 RCWarn = 0x1D + RCReferenceS6 RCWarn = 0x1E + RCNVRate RCWarn = 0x20 + RCLockout RCWarn = 0x21 + RCRetry RCWarn = 0x22 + RCNVUnavailable RCWarn = 0x23 +) + +var warnMsg = map[RCWarn]string{ + RCContextGap: "gap for context ID is too large", + RCObjectMemory: "out of memory for object contexts", + RCSessionMemory: "out of memory for session contexts", + RCMemory: "out of shared object/session memory or need space for internal operations", + RCSessionHandles: "out of session handles", + RCObjectHandles: "out of object handles", + RCLocality: "bad locality", + RCYielded: "the TPM has suspended operation on the command; forward progress was made and the command may be retried", + RCCanceled: "the command was canceled", + RCTesting: "TPM is performing self-tests", + RCReferenceH0: "the 1st handle in the handle area references a transient object or session that is not loaded", + RCReferenceH1: "the 2nd handle in the handle area references a transient object or session that is not loaded", + RCReferenceH2: "the 3rd handle in the handle area references a transient object or session that is not loaded", + RCReferenceH3: "the 4th handle in the handle area references a transient object or session that is not loaded", + RCReferenceH4: "the 5th handle in the handle area references a transient object or session that is not loaded", + RCReferenceH5: "the 6th handle in the handle area references a transient object or session that is not loaded", + RCReferenceH6: "the 7th handle in the handle area references a transient object or session that is not loaded", + RCReferenceS0: "the 1st authorization session handle references a session that is not loaded", + RCReferenceS1: "the 2nd authorization session handle references a session that is not loaded", + RCReferenceS2: "the 3rd authorization session handle references a session that is not loaded", + RCReferenceS3: "the 4th authorization session handle references a session that is not loaded", + RCReferenceS4: "the 5th authorization session handle references a session that is not loaded", + RCReferenceS5: "the 6th authorization session handle references a session that is not loaded", + RCReferenceS6: "the 7th authorization session handle references a session that is not loaded", + RCNVRate: "the TPM is rate-limiting accesses to prevent wearout of NV", + RCLockout: "authorizations for objects subject to DA protection are not allowed at this time because the TPM is in DA lockout mode", + RCRetry: "the TPM was not able to start the command", + RCNVUnavailable: "the command may require writing of NV and NV is not current accessible", +} + +// Indexes for arguments, handles and sessions. +const ( + RC1 RCIndex = iota + 1 + RC2 + RC3 + RC4 + RC5 + RC6 + RC7 + RC8 + RC9 + RCA + RCB + RCC + RCD + RCE + RCF +) + +const unknownCode = "unknown error code" + +// Error is returned for all Format 0 errors from the TPM. It is used for general +// errors not specific to a parameter, handle or session. +type Error struct { + Code RCFmt0 +} + +func (e Error) Error() string { + msg := fmt0Msg[e.Code] + if msg == "" { + msg = unknownCode + } + return fmt.Sprintf("error code 0x%x : %s", e.Code, msg) +} + +// VendorError represents a vendor-specific error response. These types of responses +// are not decoded and Code contains the complete response code. +type VendorError struct { + Code uint32 +} + +func (e VendorError) Error() string { + return fmt.Sprintf("vendor error code 0x%x", e.Code) +} + +// Warning is typically used to report transient errors. +type Warning struct { + Code RCWarn +} + +func (w Warning) Error() string { + msg := warnMsg[w.Code] + if msg == "" { + msg = unknownCode + } + return fmt.Sprintf("warning code 0x%x : %s", w.Code, msg) +} + +// ParameterError describes an error related to a parameter, and the parameter number. +type ParameterError struct { + Code RCFmt1 + Parameter RCIndex +} + +func (e ParameterError) Error() string { + msg := fmt1Msg[e.Code] + if msg == "" { + msg = unknownCode + } + return fmt.Sprintf("parameter %d, error code 0x%x : %s", e.Parameter, e.Code, msg) +} + +// HandleError describes an error related to a handle, and the handle number. +type HandleError struct { + Code RCFmt1 + Handle RCIndex +} + +func (e HandleError) Error() string { + msg := fmt1Msg[e.Code] + if msg == "" { + msg = unknownCode + } + return fmt.Sprintf("handle %d, error code 0x%x : %s", e.Handle, e.Code, msg) +} + +// SessionError describes an error related to a session, and the session number. +type SessionError struct { + Code RCFmt1 + Session RCIndex +} + +func (e SessionError) Error() string { + msg := fmt1Msg[e.Code] + if msg == "" { + msg = unknownCode + } + return fmt.Sprintf("session %d, error code 0x%x : %s", e.Session, e.Code, msg) +} + +// Decode a TPM2 response code and return the appropriate error. Logic +// according to the "Response Code Evaluation" chart in Part 1 of the TPM 2.0 +// spec. +func decodeResponse(code tpmutil.ResponseCode) error { + if code == tpmutil.RCSuccess { + return nil + } + if code&0x180 == 0 { // Bits 7:8 == 0 is a TPM1 error + return fmt.Errorf("response status 0x%x", code) + } + if code&0x80 == 0 { // Bit 7 unset + if code&0x400 > 0 { // Bit 10 set, vendor specific code + return VendorError{uint32(code)} + } + if code&0x800 > 0 { // Bit 11 set, warning with code in bit 0:6 + return Warning{RCWarn(code & 0x7f)} + } + // error with code in bit 0:6 + return Error{RCFmt0(code & 0x7f)} + } + if code&0x40 > 0 { // Bit 6 set, code in 0:5, parameter number in 8:11 + return ParameterError{RCFmt1(code & 0x3f), RCIndex((code & 0xf00) >> 8)} + } + if code&0x800 == 0 { // Bit 11 unset, code in 0:5, handle in 8:10 + return HandleError{RCFmt1(code & 0x3f), RCIndex((code & 0x700) >> 8)} + } + // Code in 0:5, Session in 8:10 + return SessionError{RCFmt1(code & 0x3f), RCIndex((code & 0x700) >> 8)} +} diff --git a/vendor/github.com/google/go-tpm/legacy/tpm2/kdf.go b/vendor/github.com/google/go-tpm/legacy/tpm2/kdf.go new file mode 100644 index 00000000000..3a22e8be778 --- /dev/null +++ b/vendor/github.com/google/go-tpm/legacy/tpm2/kdf.go @@ -0,0 +1,116 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm2 + +import ( + "crypto" + "crypto/hmac" + "encoding/binary" + "hash" +) + +// KDFa implements TPM 2.0's default key derivation function, as defined in +// section 11.4.9.2 of the TPM revision 2 specification part 1. +// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ +// The key & label parameters must not be zero length. +// The label parameter is a non-null-terminated string. +// The contextU & contextV parameters are optional. +// Deprecated: Use KDFaHash. +func KDFa(hashAlg Algorithm, key []byte, label string, contextU, contextV []byte, bits int) ([]byte, error) { + h, err := hashAlg.Hash() + if err != nil { + return nil, err + } + return KDFaHash(h, key, label, contextU, contextV, bits), nil +} + +// KDFe implements TPM 2.0's ECDH key derivation function, as defined in +// section 11.4.9.3 of the TPM revision 2 specification part 1. +// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ +// The z parameter is the x coordinate of one party's private ECC key multiplied +// by the other party's public ECC point. +// The use parameter is a non-null-terminated string. +// The partyUInfo and partyVInfo are the x coordinates of the initiator's and +// Deprecated: Use KDFeHash. +func KDFe(hashAlg Algorithm, z []byte, use string, partyUInfo, partyVInfo []byte, bits int) ([]byte, error) { + h, err := hashAlg.Hash() + if err != nil { + return nil, err + } + return KDFeHash(h, z, use, partyUInfo, partyVInfo, bits), nil +} + +// KDFaHash implements TPM 2.0's default key derivation function, as defined in +// section 11.4.9.2 of the TPM revision 2 specification part 1. +// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ +// The key & label parameters must not be zero length. +// The label parameter is a non-null-terminated string. +// The contextU & contextV parameters are optional. +func KDFaHash(h crypto.Hash, key []byte, label string, contextU, contextV []byte, bits int) []byte { + mac := hmac.New(h.New, key) + + out := kdf(mac, bits, func() { + mac.Write([]byte(label)) + mac.Write([]byte{0}) // Terminating null character for C-string. + mac.Write(contextU) + mac.Write(contextV) + binary.Write(mac, binary.BigEndian, uint32(bits)) + }) + return out +} + +// KDFeHash implements TPM 2.0's ECDH key derivation function, as defined in +// section 11.4.9.3 of the TPM revision 2 specification part 1. +// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ +// The z parameter is the x coordinate of one party's private ECC key multiplied +// by the other party's public ECC point. +// The use parameter is a non-null-terminated string. +// The partyUInfo and partyVInfo are the x coordinates of the initiator's and +// the responder's ECC points, respectively. +func KDFeHash(h crypto.Hash, z []byte, use string, partyUInfo, partyVInfo []byte, bits int) []byte { + hash := h.New() + + out := kdf(hash, bits, func() { + hash.Write(z) + hash.Write([]byte(use)) + hash.Write([]byte{0}) // Terminating null character for C-string. + hash.Write(partyUInfo) + hash.Write(partyVInfo) + }) + return out +} + +func kdf(h hash.Hash, bits int, update func()) []byte { + bytes := (bits + 7) / 8 + out := []byte{} + + for counter := 1; len(out) < bytes; counter++ { + h.Reset() + binary.Write(h, binary.BigEndian, uint32(counter)) + update() + + out = h.Sum(out) + } + // out's length is a multiple of hash size, so there will be excess + // bytes if bytes isn't a multiple of hash size. + out = out[:bytes] + + // As mentioned in the KDFa and KDFe specs mentioned above, + // the unused bits of the most significant octet are masked off. + if maskBits := uint8(bits % 8); maskBits > 0 { + out[0] &= (1 << maskBits) - 1 + } + return out +} diff --git a/vendor/github.com/google/go-tpm/legacy/tpm2/open_other.go b/vendor/github.com/google/go-tpm/legacy/tpm2/open_other.go new file mode 100644 index 00000000000..7d6d9a31b4a --- /dev/null +++ b/vendor/github.com/google/go-tpm/legacy/tpm2/open_other.go @@ -0,0 +1,57 @@ +//go:build !windows + +// Copyright (c) 2019, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm2 + +import ( + "errors" + "fmt" + "io" + "os" + + "github.com/google/go-tpm/tpmutil" +) + +// OpenTPM opens a channel to the TPM at the given path. If the file is a +// device, then it treats it like a normal TPM device, and if the file is a +// Unix domain socket, then it opens a connection to the socket. +// +// This function may also be invoked with no paths, as tpm2.OpenTPM(). In this +// case, the default paths on Linux (/dev/tpmrm0 then /dev/tpm0), will be used. +func OpenTPM(path ...string) (tpm io.ReadWriteCloser, err error) { + switch len(path) { + case 0: + tpm, err = tpmutil.OpenTPM("/dev/tpmrm0") + if errors.Is(err, os.ErrNotExist) { + tpm, err = tpmutil.OpenTPM("/dev/tpm0") + } + case 1: + tpm, err = tpmutil.OpenTPM(path[0]) + default: + return nil, errors.New("cannot specify multiple paths to tpm2.OpenTPM") + } + if err != nil { + return nil, err + } + + // Make sure this is a TPM 2.0 + _, err = GetManufacturer(tpm) + if err != nil { + tpm.Close() + return nil, fmt.Errorf("open %s: device is not a TPM 2.0", path) + } + return tpm, nil +} diff --git a/vendor/github.com/google/go-tpm/legacy/tpm2/open_windows.go b/vendor/github.com/google/go-tpm/legacy/tpm2/open_windows.go new file mode 100644 index 00000000000..ad37a602134 --- /dev/null +++ b/vendor/github.com/google/go-tpm/legacy/tpm2/open_windows.go @@ -0,0 +1,39 @@ +//go:build windows + +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm2 + +import ( + "fmt" + "io" + + "github.com/google/go-tpm/tpmutil" + "github.com/google/go-tpm/tpmutil/tbs" +) + +// OpenTPM opens a channel to the TPM. +func OpenTPM() (io.ReadWriteCloser, error) { + info, err := tbs.GetDeviceInfo() + if err != nil { + return nil, err + } + + if info.TPMVersion != tbs.TPMVersion20 { + return nil, fmt.Errorf("openTPM: device is not a TPM 2.0") + } + + return tpmutil.OpenTPM() +} diff --git a/vendor/github.com/google/go-tpm/legacy/tpm2/structures.go b/vendor/github.com/google/go-tpm/legacy/tpm2/structures.go new file mode 100644 index 00000000000..6df9f7f0d7e --- /dev/null +++ b/vendor/github.com/google/go-tpm/legacy/tpm2/structures.go @@ -0,0 +1,1112 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm2 + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "encoding/binary" + "errors" + "fmt" + "math/big" + "reflect" + + "github.com/google/go-tpm/tpmutil" +) + +// NVPublic contains the public area of an NV index. +type NVPublic struct { + NVIndex tpmutil.Handle + NameAlg Algorithm + Attributes NVAttr + AuthPolicy tpmutil.U16Bytes + DataSize uint16 +} + +type tpmsSensitiveCreate struct { + UserAuth tpmutil.U16Bytes + Data tpmutil.U16Bytes +} + +// PCRSelection contains a slice of PCR indexes and a hash algorithm used in +// them. +type PCRSelection struct { + Hash Algorithm + PCRs []int +} + +type tpmsPCRSelection struct { + Hash Algorithm + Size byte + PCRs tpmutil.RawBytes +} + +// Public contains the public area of an object. +type Public struct { + Type Algorithm + NameAlg Algorithm + Attributes KeyProp + AuthPolicy tpmutil.U16Bytes + + // Exactly one of the following fields should be set + // When encoding/decoding, one will be picked based on Type. + + // RSAParameters contains both [rsa]parameters and [rsa]unique. + RSAParameters *RSAParams + // ECCParameters contains both [ecc]parameters and [ecc]unique. + ECCParameters *ECCParams + // SymCipherParameters contains both [sym]parameters and [sym]unique. + SymCipherParameters *SymCipherParams + // KeyedHashParameters contains both [keyedHash]parameters and [keyedHash]unique. + KeyedHashParameters *KeyedHashParams +} + +// Encode serializes a Public structure in TPM wire format. +func (p Public) Encode() ([]byte, error) { + head, err := tpmutil.Pack(p.Type, p.NameAlg, p.Attributes, p.AuthPolicy) + if err != nil { + return nil, fmt.Errorf("encoding Type, NameAlg, Attributes, AuthPolicy: %v", err) + } + var params []byte + switch p.Type { + case AlgRSA: + params, err = p.RSAParameters.encode() + case AlgKeyedHash: + params, err = p.KeyedHashParameters.encode() + case AlgECC: + params, err = p.ECCParameters.encode() + case AlgSymCipher: + params, err = p.SymCipherParameters.encode() + default: + err = fmt.Errorf("unsupported type in TPMT_PUBLIC: 0x%x", p.Type) + } + if err != nil { + return nil, fmt.Errorf("encoding RSAParameters, ECCParameters, SymCipherParameters or KeyedHash: %v", err) + } + return concat(head, params) +} + +// Key returns the (public) key from the public area of an object. +func (p Public) Key() (crypto.PublicKey, error) { + var pubKey crypto.PublicKey + switch p.Type { + case AlgRSA: + // Endianness of big.Int.Bytes/SetBytes and modulus in the TPM is the same + // (big-endian). + pubKey = &rsa.PublicKey{N: p.RSAParameters.Modulus(), E: int(p.RSAParameters.Exponent())} + case AlgECC: + curve, ok := toGoCurve[p.ECCParameters.CurveID] + if !ok { + return nil, fmt.Errorf("can't map TPM EC curve ID 0x%x to Go elliptic.Curve value", p.ECCParameters.CurveID) + } + pubKey = &ecdsa.PublicKey{ + X: p.ECCParameters.Point.X(), + Y: p.ECCParameters.Point.Y(), + Curve: curve, + } + default: + return nil, fmt.Errorf("unsupported public key type 0x%x", p.Type) + } + return pubKey, nil +} + +// Name computes the Digest-based Name from the public area of an object. +func (p Public) Name() (Name, error) { + pubEncoded, err := p.Encode() + if err != nil { + return Name{}, err + } + hash, err := p.NameAlg.Hash() + if err != nil { + return Name{}, err + } + nameHash := hash.New() + nameHash.Write(pubEncoded) + return Name{ + Digest: &HashValue{ + Alg: p.NameAlg, + Value: nameHash.Sum(nil), + }, + }, nil +} + +// MatchesTemplate checks if the Public area has the same algorithms and +// parameters as the provided template. Note that this does not necessarily +// mean that the key was created from this template, as the Unique field is +// both provided in the template and overridden in the key creation process. +func (p Public) MatchesTemplate(template Public) bool { + if p.Type != template.Type || + p.NameAlg != template.NameAlg || + p.Attributes != template.Attributes || + !bytes.Equal(p.AuthPolicy, template.AuthPolicy) { + return false + } + switch p.Type { + case AlgRSA: + return p.RSAParameters.matchesTemplate(template.RSAParameters) + case AlgECC: + return p.ECCParameters.matchesTemplate(template.ECCParameters) + case AlgSymCipher: + return p.SymCipherParameters.matchesTemplate(template.SymCipherParameters) + case AlgKeyedHash: + return p.KeyedHashParameters.matchesTemplate(template.KeyedHashParameters) + default: + return true + } +} + +// DecodePublic decodes a TPMT_PUBLIC message. No error is returned if +// the input has extra trailing data. +func DecodePublic(buf []byte) (Public, error) { + in := bytes.NewBuffer(buf) + var pub Public + var err error + if err = tpmutil.UnpackBuf(in, &pub.Type, &pub.NameAlg, &pub.Attributes, &pub.AuthPolicy); err != nil { + return pub, fmt.Errorf("decoding TPMT_PUBLIC: %v", err) + } + + switch pub.Type { + case AlgRSA: + pub.RSAParameters, err = decodeRSAParams(in) + case AlgECC: + pub.ECCParameters, err = decodeECCParams(in) + case AlgSymCipher: + pub.SymCipherParameters, err = decodeSymCipherParams(in) + case AlgKeyedHash: + pub.KeyedHashParameters, err = decodeKeyedHashParams(in) + default: + err = fmt.Errorf("unsupported type in TPMT_PUBLIC: 0x%x", pub.Type) + } + return pub, err +} + +// RSAParams represents parameters of an RSA key pair: +// both the TPMS_RSA_PARMS and the TPM2B_PUBLIC_KEY_RSA. +// +// Symmetric and Sign may be nil, depending on key Attributes in Public. +// +// ExponentRaw and ModulusRaw are the actual data encoded in the template, which +// is useful for templates that differ in zero-padding, for example. +type RSAParams struct { + Symmetric *SymScheme + Sign *SigScheme + KeyBits uint16 + ExponentRaw uint32 + ModulusRaw tpmutil.U16Bytes +} + +// Exponent returns the RSA exponent value represented by ExponentRaw, handling +// the fact that an exponent of 0 represents a value of 65537 (2^16 + 1). +func (p *RSAParams) Exponent() uint32 { + if p.ExponentRaw == 0 { + return defaultRSAExponent + } + return p.ExponentRaw +} + +// Modulus returns the RSA modulus value represented by ModulusRaw, handling the +// fact that the same modulus value can have multiple different representations. +func (p *RSAParams) Modulus() *big.Int { + return new(big.Int).SetBytes(p.ModulusRaw) +} + +func (p *RSAParams) matchesTemplate(t *RSAParams) bool { + return reflect.DeepEqual(p.Symmetric, t.Symmetric) && + reflect.DeepEqual(p.Sign, t.Sign) && + p.KeyBits == t.KeyBits && p.ExponentRaw == t.ExponentRaw +} + +func (p *RSAParams) encode() ([]byte, error) { + if p == nil { + return nil, nil + } + sym, err := p.Symmetric.encode() + if err != nil { + return nil, fmt.Errorf("encoding Symmetric: %v", err) + } + sig, err := p.Sign.encode() + if err != nil { + return nil, fmt.Errorf("encoding Sign: %v", err) + } + rest, err := tpmutil.Pack(p.KeyBits, p.ExponentRaw, p.ModulusRaw) + if err != nil { + return nil, fmt.Errorf("encoding KeyBits, Exponent, Modulus: %v", err) + } + return concat(sym, sig, rest) +} + +func decodeRSAParams(in *bytes.Buffer) (*RSAParams, error) { + var params RSAParams + var err error + + if params.Symmetric, err = decodeSymScheme(in); err != nil { + return nil, fmt.Errorf("decoding Symmetric: %v", err) + } + if params.Sign, err = decodeSigScheme(in); err != nil { + return nil, fmt.Errorf("decoding Sign: %v", err) + } + if err := tpmutil.UnpackBuf(in, ¶ms.KeyBits, ¶ms.ExponentRaw, ¶ms.ModulusRaw); err != nil { + return nil, fmt.Errorf("decoding KeyBits, Exponent, Modulus: %v", err) + } + return ¶ms, nil +} + +// ECCParams represents parameters of an ECC key pair: +// both the TPMS_ECC_PARMS and the TPMS_ECC_POINT. +// +// Symmetric, Sign and KDF may be nil, depending on key Attributes in Public. +type ECCParams struct { + Symmetric *SymScheme + Sign *SigScheme + CurveID EllipticCurve + KDF *KDFScheme + Point ECPoint +} + +// ECPoint represents a ECC coordinates for a point using byte buffers. +type ECPoint struct { + XRaw, YRaw tpmutil.U16Bytes +} + +// X returns the X Point value reprsented by XRaw. +func (p ECPoint) X() *big.Int { + return new(big.Int).SetBytes(p.XRaw) +} + +// Y returns the Y Point value reprsented by YRaw. +func (p ECPoint) Y() *big.Int { + return new(big.Int).SetBytes(p.YRaw) +} + +func (p *ECCParams) matchesTemplate(t *ECCParams) bool { + return reflect.DeepEqual(p.Symmetric, t.Symmetric) && + reflect.DeepEqual(p.Sign, t.Sign) && + p.CurveID == t.CurveID && reflect.DeepEqual(p.KDF, t.KDF) +} + +func (p *ECCParams) encode() ([]byte, error) { + if p == nil { + return nil, nil + } + sym, err := p.Symmetric.encode() + if err != nil { + return nil, fmt.Errorf("encoding Symmetric: %v", err) + } + sig, err := p.Sign.encode() + if err != nil { + return nil, fmt.Errorf("encoding Sign: %v", err) + } + curve, err := tpmutil.Pack(p.CurveID) + if err != nil { + return nil, fmt.Errorf("encoding CurveID: %v", err) + } + kdf, err := p.KDF.encode() + if err != nil { + return nil, fmt.Errorf("encoding KDF: %v", err) + } + point, err := tpmutil.Pack(p.Point.XRaw, p.Point.YRaw) + if err != nil { + return nil, fmt.Errorf("encoding Point: %v", err) + } + return concat(sym, sig, curve, kdf, point) +} + +func decodeECCParams(in *bytes.Buffer) (*ECCParams, error) { + var params ECCParams + var err error + + if params.Symmetric, err = decodeSymScheme(in); err != nil { + return nil, fmt.Errorf("decoding Symmetric: %v", err) + } + if params.Sign, err = decodeSigScheme(in); err != nil { + return nil, fmt.Errorf("decoding Sign: %v", err) + } + if err := tpmutil.UnpackBuf(in, ¶ms.CurveID); err != nil { + return nil, fmt.Errorf("decoding CurveID: %v", err) + } + if params.KDF, err = decodeKDFScheme(in); err != nil { + return nil, fmt.Errorf("decoding KDF: %v", err) + } + if err := tpmutil.UnpackBuf(in, ¶ms.Point.XRaw, ¶ms.Point.YRaw); err != nil { + return nil, fmt.Errorf("decoding Point: %v", err) + } + return ¶ms, nil +} + +// SymCipherParams represents parameters of a symmetric block cipher TPM object: +// both the TPMS_SYMCIPHER_PARMS and the TPM2B_DIGEST (hash of the key). +type SymCipherParams struct { + Symmetric *SymScheme + Unique tpmutil.U16Bytes +} + +func (p *SymCipherParams) matchesTemplate(t *SymCipherParams) bool { + return reflect.DeepEqual(p.Symmetric, t.Symmetric) +} + +func (p *SymCipherParams) encode() ([]byte, error) { + sym, err := p.Symmetric.encode() + if err != nil { + return nil, fmt.Errorf("encoding Symmetric: %v", err) + } + unique, err := tpmutil.Pack(p.Unique) + if err != nil { + return nil, fmt.Errorf("encoding Unique: %v", err) + } + return concat(sym, unique) +} + +func decodeSymCipherParams(in *bytes.Buffer) (*SymCipherParams, error) { + var params SymCipherParams + var err error + + if params.Symmetric, err = decodeSymScheme(in); err != nil { + return nil, fmt.Errorf("decoding Symmetric: %v", err) + } + if err := tpmutil.UnpackBuf(in, ¶ms.Unique); err != nil { + return nil, fmt.Errorf("decoding Unique: %v", err) + } + return ¶ms, nil +} + +// KeyedHashParams represents parameters of a keyed hash TPM object: +// both the TPMS_KEYEDHASH_PARMS and the TPM2B_DIGEST (hash of the key). +type KeyedHashParams struct { + Alg Algorithm + Hash Algorithm + KDF Algorithm + Unique tpmutil.U16Bytes +} + +func (p *KeyedHashParams) matchesTemplate(t *KeyedHashParams) bool { + if p.Alg != t.Alg { + return false + } + switch p.Alg { + case AlgHMAC: + return p.Hash == t.Hash + case AlgXOR: + return p.Hash == t.Hash && p.KDF == t.KDF + default: + return true + } +} + +func (p *KeyedHashParams) encode() ([]byte, error) { + if p == nil { + return tpmutil.Pack(AlgNull, tpmutil.U16Bytes(nil)) + } + var params []byte + var err error + switch p.Alg { + case AlgNull: + params, err = tpmutil.Pack(p.Alg) + case AlgHMAC: + params, err = tpmutil.Pack(p.Alg, p.Hash) + case AlgXOR: + params, err = tpmutil.Pack(p.Alg, p.Hash, p.KDF) + default: + err = fmt.Errorf("unsupported KeyedHash Algorithm: 0x%x", p.Alg) + } + if err != nil { + return nil, fmt.Errorf("encoding Alg Params: %v", err) + } + unique, err := tpmutil.Pack(p.Unique) + if err != nil { + return nil, fmt.Errorf("encoding Unique: %v", err) + } + return concat(params, unique) +} + +func decodeKeyedHashParams(in *bytes.Buffer) (*KeyedHashParams, error) { + var p KeyedHashParams + var err error + if err = tpmutil.UnpackBuf(in, &p.Alg); err != nil { + return nil, fmt.Errorf("decoding Alg: %v", err) + } + switch p.Alg { + case AlgNull: + err = nil + case AlgHMAC: + err = tpmutil.UnpackBuf(in, &p.Hash) + case AlgXOR: + err = tpmutil.UnpackBuf(in, &p.Hash, &p.KDF) + default: + err = fmt.Errorf("unsupported KeyedHash Algorithm: 0x%x", p.Alg) + } + if err != nil { + return nil, fmt.Errorf("decoding Alg Params: %v", err) + } + if err = tpmutil.UnpackBuf(in, &p.Unique); err != nil { + return nil, fmt.Errorf("decoding Unique: %v", err) + } + return &p, nil +} + +// SymScheme represents a symmetric encryption scheme. +// Known in the specification by TPMT_SYM_DEF_OBJECT. +type SymScheme struct { + Alg Algorithm + KeyBits uint16 + Mode Algorithm +} + +func (s *SymScheme) encode() ([]byte, error) { + if s == nil || s.Alg.IsNull() { + return tpmutil.Pack(AlgNull) + } + return tpmutil.Pack(s.Alg, s.KeyBits, s.Mode) +} + +func decodeSymScheme(in *bytes.Buffer) (*SymScheme, error) { + var scheme SymScheme + if err := tpmutil.UnpackBuf(in, &scheme.Alg); err != nil { + return nil, fmt.Errorf("decoding Alg: %v", err) + } + if scheme.Alg == AlgNull { + return nil, nil + } + if err := tpmutil.UnpackBuf(in, &scheme.KeyBits, &scheme.Mode); err != nil { + return nil, fmt.Errorf("decoding KeyBits, Mode: %v", err) + } + return &scheme, nil +} + +// AsymScheme represents am asymmetric encryption scheme. +type AsymScheme struct { + Alg Algorithm + Hash Algorithm +} + +func (s *AsymScheme) encode() ([]byte, error) { + if s == nil || s.Alg.IsNull() { + return tpmutil.Pack(AlgNull) + } + if s.Alg.UsesHash() { + return tpmutil.Pack(s.Alg, s.Hash) + } + return tpmutil.Pack(s.Alg) +} + +// SigScheme represents a signing scheme. +type SigScheme struct { + Alg Algorithm + Hash Algorithm + Count uint32 +} + +func (s *SigScheme) encode() ([]byte, error) { + if s == nil || s.Alg.IsNull() { + return tpmutil.Pack(AlgNull) + } + if s.Alg.UsesCount() { + return tpmutil.Pack(s.Alg, s.Hash, s.Count) + } + return tpmutil.Pack(s.Alg, s.Hash) +} + +func decodeSigScheme(in *bytes.Buffer) (*SigScheme, error) { + var scheme SigScheme + if err := tpmutil.UnpackBuf(in, &scheme.Alg); err != nil { + return nil, fmt.Errorf("decoding Alg: %v", err) + } + if scheme.Alg == AlgNull { + return nil, nil + } + if err := tpmutil.UnpackBuf(in, &scheme.Hash); err != nil { + return nil, fmt.Errorf("decoding Hash: %v", err) + } + if scheme.Alg.UsesCount() { + if err := tpmutil.UnpackBuf(in, &scheme.Count); err != nil { + return nil, fmt.Errorf("decoding Count: %v", err) + } + } + return &scheme, nil +} + +// KDFScheme represents a KDF (Key Derivation Function) scheme. +type KDFScheme struct { + Alg Algorithm + Hash Algorithm +} + +func (s *KDFScheme) encode() ([]byte, error) { + if s == nil || s.Alg.IsNull() { + return tpmutil.Pack(AlgNull) + } + return tpmutil.Pack(s.Alg, s.Hash) +} + +func decodeKDFScheme(in *bytes.Buffer) (*KDFScheme, error) { + var scheme KDFScheme + if err := tpmutil.UnpackBuf(in, &scheme.Alg); err != nil { + return nil, fmt.Errorf("decoding Alg: %v", err) + } + if scheme.Alg == AlgNull { + return nil, nil + } + if err := tpmutil.UnpackBuf(in, &scheme.Hash); err != nil { + return nil, fmt.Errorf("decoding Hash: %v", err) + } + return &scheme, nil +} + +// Signature combines all possible signatures from RSA and ECC keys. Only one +// of RSA or ECC will be populated. +type Signature struct { + Alg Algorithm + RSA *SignatureRSA + ECC *SignatureECC +} + +// Encode serializes a Signature structure in TPM wire format. +func (s Signature) Encode() ([]byte, error) { + head, err := tpmutil.Pack(s.Alg) + if err != nil { + return nil, fmt.Errorf("encoding Alg: %v", err) + } + var signature []byte + switch s.Alg { + case AlgRSASSA, AlgRSAPSS: + if signature, err = tpmutil.Pack(s.RSA); err != nil { + return nil, fmt.Errorf("encoding RSA: %v", err) + } + case AlgECDSA: + signature, err = tpmutil.Pack(s.ECC.HashAlg, tpmutil.U16Bytes(s.ECC.R.Bytes()), tpmutil.U16Bytes(s.ECC.S.Bytes())) + if err != nil { + return nil, fmt.Errorf("encoding ECC: %v", err) + } + } + return concat(head, signature) +} + +// DecodeSignature decodes a serialized TPMT_SIGNATURE structure. +func DecodeSignature(in *bytes.Buffer) (*Signature, error) { + var sig Signature + if err := tpmutil.UnpackBuf(in, &sig.Alg); err != nil { + return nil, fmt.Errorf("decoding Alg: %v", err) + } + switch sig.Alg { + case AlgRSASSA, AlgRSAPSS: + sig.RSA = new(SignatureRSA) + if err := tpmutil.UnpackBuf(in, sig.RSA); err != nil { + return nil, fmt.Errorf("decoding RSA: %v", err) + } + case AlgECDSA: + sig.ECC = new(SignatureECC) + var r, s tpmutil.U16Bytes + if err := tpmutil.UnpackBuf(in, &sig.ECC.HashAlg, &r, &s); err != nil { + return nil, fmt.Errorf("decoding ECC: %v", err) + } + sig.ECC.R = big.NewInt(0).SetBytes(r) + sig.ECC.S = big.NewInt(0).SetBytes(s) + default: + return nil, fmt.Errorf("unsupported signature algorithm 0x%x", sig.Alg) + } + return &sig, nil +} + +// SignatureRSA is an RSA-specific signature value. +type SignatureRSA struct { + HashAlg Algorithm + Signature tpmutil.U16Bytes +} + +// SignatureECC is an ECC-specific signature value. +type SignatureECC struct { + HashAlg Algorithm + R *big.Int + S *big.Int +} + +// Private contains private section of a TPM key. +type Private struct { + Type Algorithm + AuthValue tpmutil.U16Bytes + SeedValue tpmutil.U16Bytes + Sensitive tpmutil.U16Bytes +} + +// Encode serializes a Private structure in TPM wire format. +func (p Private) Encode() ([]byte, error) { + if p.Type.IsNull() { + return nil, nil + } + return tpmutil.Pack(p) +} + +// AttestationData contains data attested by TPM commands (like Certify). +type AttestationData struct { + Magic uint32 + Type tpmutil.Tag + QualifiedSigner Name + ExtraData tpmutil.U16Bytes + ClockInfo ClockInfo + FirmwareVersion uint64 + AttestedCertifyInfo *CertifyInfo + AttestedQuoteInfo *QuoteInfo + AttestedCreationInfo *CreationInfo +} + +// DecodeAttestationData decode a TPMS_ATTEST message. No error is returned if +// the input has extra trailing data. +func DecodeAttestationData(in []byte) (*AttestationData, error) { + buf := bytes.NewBuffer(in) + + var ad AttestationData + if err := tpmutil.UnpackBuf(buf, &ad.Magic, &ad.Type); err != nil { + return nil, fmt.Errorf("decoding Magic/Type: %v", err) + } + // All attestation structures have the magic prefix + // TPMS_GENERATED_VALUE to symbolize they were created by + // the TPM when signed with an AK. + if ad.Magic != 0xff544347 { + return nil, fmt.Errorf("incorrect magic value: %x", ad.Magic) + } + + n, err := DecodeName(buf) + if err != nil { + return nil, fmt.Errorf("decoding QualifiedSigner: %v", err) + } + ad.QualifiedSigner = *n + if err := tpmutil.UnpackBuf(buf, &ad.ExtraData, &ad.ClockInfo, &ad.FirmwareVersion); err != nil { + return nil, fmt.Errorf("decoding ExtraData/ClockInfo/FirmwareVersion: %v", err) + } + + // The spec specifies several other types of attestation data. We only need + // parsing of Certify & Creation attestation data for now. If you need + // support for other attestation types, add them here. + switch ad.Type { + case TagAttestCertify: + if ad.AttestedCertifyInfo, err = decodeCertifyInfo(buf); err != nil { + return nil, fmt.Errorf("decoding AttestedCertifyInfo: %v", err) + } + case TagAttestCreation: + if ad.AttestedCreationInfo, err = decodeCreationInfo(buf); err != nil { + return nil, fmt.Errorf("decoding AttestedCreationInfo: %v", err) + } + case TagAttestQuote: + if ad.AttestedQuoteInfo, err = decodeQuoteInfo(buf); err != nil { + return nil, fmt.Errorf("decoding AttestedQuoteInfo: %v", err) + } + default: + return nil, fmt.Errorf("only Quote, Certify & Creation attestation structures are supported, got type 0x%x", ad.Type) + } + + return &ad, nil +} + +// Encode serializes an AttestationData structure in TPM wire format. +func (ad AttestationData) Encode() ([]byte, error) { + head, err := tpmutil.Pack(ad.Magic, ad.Type) + if err != nil { + return nil, fmt.Errorf("encoding Magic, Type: %v", err) + } + signer, err := ad.QualifiedSigner.Encode() + if err != nil { + return nil, fmt.Errorf("encoding QualifiedSigner: %v", err) + } + tail, err := tpmutil.Pack(ad.ExtraData, ad.ClockInfo, ad.FirmwareVersion) + if err != nil { + return nil, fmt.Errorf("encoding ExtraData, ClockInfo, FirmwareVersion: %v", err) + } + + var info []byte + switch ad.Type { + case TagAttestCertify: + if info, err = ad.AttestedCertifyInfo.encode(); err != nil { + return nil, fmt.Errorf("encoding AttestedCertifyInfo: %v", err) + } + case TagAttestCreation: + if info, err = ad.AttestedCreationInfo.encode(); err != nil { + return nil, fmt.Errorf("encoding AttestedCreationInfo: %v", err) + } + case TagAttestQuote: + if info, err = ad.AttestedQuoteInfo.encode(); err != nil { + return nil, fmt.Errorf("encoding AttestedQuoteInfo: %v", err) + } + default: + return nil, fmt.Errorf("only Quote, Certify & Creation attestation structures are supported, got type 0x%x", ad.Type) + } + + return concat(head, signer, tail, info) +} + +// CreationInfo contains Creation-specific data for TPMS_ATTEST. +type CreationInfo struct { + Name Name + // Most TPM2B_Digest structures contain a TPMU_HA structure + // and get parsed to HashValue. This is never the case for the + // digest in TPMS_CREATION_INFO. + OpaqueDigest tpmutil.U16Bytes +} + +func decodeCreationInfo(in *bytes.Buffer) (*CreationInfo, error) { + var ci CreationInfo + + n, err := DecodeName(in) + if err != nil { + return nil, fmt.Errorf("decoding Name: %v", err) + } + ci.Name = *n + + if err := tpmutil.UnpackBuf(in, &ci.OpaqueDigest); err != nil { + return nil, fmt.Errorf("decoding Digest: %v", err) + } + + return &ci, nil +} + +func (ci CreationInfo) encode() ([]byte, error) { + n, err := ci.Name.Encode() + if err != nil { + return nil, fmt.Errorf("encoding Name: %v", err) + } + + d, err := tpmutil.Pack(ci.OpaqueDigest) + if err != nil { + return nil, fmt.Errorf("encoding Digest: %v", err) + } + + return concat(n, d) +} + +// CertifyInfo contains Certify-specific data for TPMS_ATTEST. +type CertifyInfo struct { + Name Name + QualifiedName Name +} + +func decodeCertifyInfo(in *bytes.Buffer) (*CertifyInfo, error) { + var ci CertifyInfo + + n, err := DecodeName(in) + if err != nil { + return nil, fmt.Errorf("decoding Name: %v", err) + } + ci.Name = *n + + n, err = DecodeName(in) + if err != nil { + return nil, fmt.Errorf("decoding QualifiedName: %v", err) + } + ci.QualifiedName = *n + + return &ci, nil +} + +func (ci CertifyInfo) encode() ([]byte, error) { + n, err := ci.Name.Encode() + if err != nil { + return nil, fmt.Errorf("encoding Name: %v", err) + } + qn, err := ci.QualifiedName.Encode() + if err != nil { + return nil, fmt.Errorf("encoding QualifiedName: %v", err) + } + return concat(n, qn) +} + +// QuoteInfo represents a TPMS_QUOTE_INFO structure. +type QuoteInfo struct { + PCRSelection PCRSelection + PCRDigest tpmutil.U16Bytes +} + +func decodeQuoteInfo(in *bytes.Buffer) (*QuoteInfo, error) { + var out QuoteInfo + sel, err := decodeOneTPMLPCRSelection(in) + if err != nil { + return nil, fmt.Errorf("decoding PCRSelection: %v", err) + } + out.PCRSelection = sel + + if err := tpmutil.UnpackBuf(in, &out.PCRDigest); err != nil { + return nil, fmt.Errorf("decoding PCRDigest: %v", err) + } + return &out, nil +} + +func (qi QuoteInfo) encode() ([]byte, error) { + sel, err := encodeTPMLPCRSelection(qi.PCRSelection) + if err != nil { + return nil, fmt.Errorf("encoding PCRSelection: %v", err) + } + + digest, err := tpmutil.Pack(qi.PCRDigest) + if err != nil { + return nil, fmt.Errorf("encoding PCRDigest: %v", err) + } + + return concat(sel, digest) +} + +// IDObject represents an encrypted credential bound to a TPM object. +type IDObject struct { + IntegrityHMAC tpmutil.U16Bytes + // EncIdentity is packed raw, as the bytes representing the size + // of the credential value are present within the encrypted blob. + EncIdentity tpmutil.RawBytes +} + +// CreationData describes the attributes and environment for an object created +// on the TPM. This structure encodes/decodes to/from TPMS_CREATION_DATA. +type CreationData struct { + PCRSelection PCRSelection + PCRDigest tpmutil.U16Bytes + Locality byte + ParentNameAlg Algorithm + ParentName Name + ParentQualifiedName Name + OutsideInfo tpmutil.U16Bytes +} + +// EncodeCreationData encodes byte array to TPMS_CREATION_DATA message. +func (cd *CreationData) EncodeCreationData() ([]byte, error) { + sel, err := encodeTPMLPCRSelection(cd.PCRSelection) + if err != nil { + return nil, fmt.Errorf("encoding PCRSelection: %v", err) + } + d, err := tpmutil.Pack(cd.PCRDigest, cd.Locality, cd.ParentNameAlg) + if err != nil { + return nil, fmt.Errorf("encoding PCRDigest, Locality, ParentNameAlg: %v", err) + } + pn, err := cd.ParentName.Encode() + if err != nil { + return nil, fmt.Errorf("encoding ParentName: %v", err) + } + pqn, err := cd.ParentQualifiedName.Encode() + if err != nil { + return nil, fmt.Errorf("encoding ParentQualifiedName: %v", err) + } + o, err := tpmutil.Pack(cd.OutsideInfo) + if err != nil { + return nil, fmt.Errorf("encoding OutsideInfo: %v", err) + } + return concat(sel, d, pn, pqn, o) +} + +// DecodeCreationData decodes a TPMS_CREATION_DATA message. No error is +// returned if the input has extra trailing data. +func DecodeCreationData(buf []byte) (*CreationData, error) { + in := bytes.NewBuffer(buf) + var out CreationData + + sel, err := decodeOneTPMLPCRSelection(in) + if err != nil { + return nil, fmt.Errorf("decodeOneTPMLPCRSelection returned error %v", err) + } + out.PCRSelection = sel + + if err := tpmutil.UnpackBuf(in, &out.PCRDigest, &out.Locality, &out.ParentNameAlg); err != nil { + return nil, fmt.Errorf("decoding PCRDigest, Locality, ParentNameAlg: %v", err) + } + + n, err := DecodeName(in) + if err != nil { + return nil, fmt.Errorf("decoding ParentName: %v", err) + } + out.ParentName = *n + if n, err = DecodeName(in); err != nil { + return nil, fmt.Errorf("decoding ParentQualifiedName: %v", err) + } + out.ParentQualifiedName = *n + + if err := tpmutil.UnpackBuf(in, &out.OutsideInfo); err != nil { + return nil, fmt.Errorf("decoding OutsideInfo: %v", err) + } + + return &out, nil +} + +// Name represents a TPM2B_NAME, a name for TPM entities. Only one of +// Handle or Digest should be set. +type Name struct { + Handle *tpmutil.Handle + Digest *HashValue +} + +// DecodeName deserializes a Name hash from the TPM wire format. +func DecodeName(in *bytes.Buffer) (*Name, error) { + var nameBuf tpmutil.U16Bytes + if err := tpmutil.UnpackBuf(in, &nameBuf); err != nil { + return nil, err + } + + name := new(Name) + switch len(nameBuf) { + case 0: + // No name is present. + case 4: + name.Handle = new(tpmutil.Handle) + if err := tpmutil.UnpackBuf(bytes.NewBuffer(nameBuf), name.Handle); err != nil { + return nil, fmt.Errorf("decoding Handle: %v", err) + } + default: + var err error + name.Digest, err = decodeHashValue(bytes.NewBuffer(nameBuf)) + if err != nil { + return nil, fmt.Errorf("decoding Digest: %v", err) + } + } + return name, nil +} + +// Encode serializes a Name hash into the TPM wire format. +func (n Name) Encode() ([]byte, error) { + var buf []byte + var err error + switch { + case n.Handle != nil: + if buf, err = tpmutil.Pack(*n.Handle); err != nil { + return nil, fmt.Errorf("encoding Handle: %v", err) + } + case n.Digest != nil: + if buf, err = n.Digest.Encode(); err != nil { + return nil, fmt.Errorf("encoding Digest: %v", err) + } + default: + // Name is empty, which is valid. + } + return tpmutil.Pack(tpmutil.U16Bytes(buf)) +} + +// MatchesPublic compares Digest in Name against given Public structure. Note: +// this only works for regular Names, not Qualified Names. +func (n Name) MatchesPublic(p Public) (bool, error) { + if n.Digest == nil { + return false, errors.New("Name doesn't have a Digest, can't compare to Public") + } + expected, err := p.Name() + if err != nil { + return false, err + } + // No secrets, so no constant-time comparison + return bytes.Equal(expected.Digest.Value, n.Digest.Value), nil +} + +// HashValue is an algorithm-specific hash value. +type HashValue struct { + Alg Algorithm + Value tpmutil.U16Bytes +} + +func decodeHashValue(in *bytes.Buffer) (*HashValue, error) { + var hv HashValue + if err := tpmutil.UnpackBuf(in, &hv.Alg); err != nil { + return nil, fmt.Errorf("decoding Alg: %v", err) + } + hfn, err := hv.Alg.Hash() + if err != nil { + return nil, err + } + hv.Value = make(tpmutil.U16Bytes, hfn.Size()) + if _, err := in.Read(hv.Value); err != nil { + return nil, fmt.Errorf("decoding Value: %v", err) + } + return &hv, nil +} + +// Encode represents the given hash value as a TPMT_HA structure. +func (hv HashValue) Encode() ([]byte, error) { + return tpmutil.Pack(hv.Alg, tpmutil.RawBytes(hv.Value)) +} + +// ClockInfo contains TPM state info included in AttestationData. +type ClockInfo struct { + Clock uint64 + ResetCount uint32 + RestartCount uint32 + Safe byte +} + +// AlgorithmAttributes represents a TPMA_ALGORITHM value. +type AlgorithmAttributes uint32 + +// AlgorithmDescription represents a TPMS_ALGORITHM_DESCRIPTION structure. +type AlgorithmDescription struct { + ID Algorithm + Attributes AlgorithmAttributes +} + +// TaggedProperty represents a TPMS_TAGGED_PROPERTY structure. +type TaggedProperty struct { + Tag TPMProp + Value uint32 +} + +// Ticket represents evidence the TPM previously processed +// information. +type Ticket struct { + Type tpmutil.Tag + Hierarchy tpmutil.Handle + Digest tpmutil.U16Bytes +} + +// AuthCommand represents a TPMS_AUTH_COMMAND. This structure encapsulates parameters +// which authorize the use of a given handle or parameter. +type AuthCommand struct { + Session tpmutil.Handle + Nonce tpmutil.U16Bytes + Attributes SessionAttributes + Auth tpmutil.U16Bytes +} + +// TPMLDigest represents the TPML_Digest structure +// It is used to convey a list of digest values. +// This type is used in TPM2_PolicyOR() and in TPM2_PCR_Read() +type TPMLDigest struct { + Digests []tpmutil.U16Bytes +} + +// Encode converts the TPMLDigest structure into a byte slice +func (list *TPMLDigest) Encode() ([]byte, error) { + res, err := tpmutil.Pack(uint32(len(list.Digests))) + if err != nil { + return nil, err + } + for _, item := range list.Digests { + b, err := tpmutil.Pack(item) + if err != nil { + return nil, err + } + res = append(res, b...) + + } + return res, nil +} + +// DecodeTPMLDigest decodes a TPML_Digest part of a message. +func DecodeTPMLDigest(buf []byte) (*TPMLDigest, error) { + in := bytes.NewBuffer(buf) + var tpmld TPMLDigest + var count uint32 + if err := binary.Read(in, binary.BigEndian, &count); err != nil { + return nil, fmt.Errorf("decoding TPML_Digest: %v", err) + } + for in.Len() > 0 { + var hash tpmutil.U16Bytes + if err := hash.TPMUnmarshal(in); err != nil { + return nil, err + } + tpmld.Digests = append(tpmld.Digests, hash) + } + if count != uint32(len(tpmld.Digests)) { + return nil, fmt.Errorf("expected size and read size does not match") + } + return &tpmld, nil +} diff --git a/vendor/github.com/google/go-tpm/legacy/tpm2/tpm2.go b/vendor/github.com/google/go-tpm/legacy/tpm2/tpm2.go new file mode 100644 index 00000000000..0105c372946 --- /dev/null +++ b/vendor/github.com/google/go-tpm/legacy/tpm2/tpm2.go @@ -0,0 +1,2354 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tpm2 supports direct communication with a TPM 2.0 device under Linux. +package tpm2 + +import ( + "bytes" + "crypto" + "fmt" + "io" + + "github.com/google/go-tpm/tpmutil" +) + +// GetRandom gets random bytes from the TPM. +func GetRandom(rw io.ReadWriter, size uint16) ([]byte, error) { + resp, err := runCommand(rw, TagNoSessions, CmdGetRandom, size) + if err != nil { + return nil, err + } + + var randBytes tpmutil.U16Bytes + if _, err := tpmutil.Unpack(resp, &randBytes); err != nil { + return nil, err + } + return randBytes, nil +} + +// FlushContext removes an object or session under handle to be removed from +// the TPM. This must be called for any loaded handle to avoid out-of-memory +// errors in TPM. +func FlushContext(rw io.ReadWriter, handle tpmutil.Handle) error { + _, err := runCommand(rw, TagNoSessions, CmdFlushContext, handle) + return err +} + +func encodeTPMLPCRSelection(sel ...PCRSelection) ([]byte, error) { + if len(sel) == 0 { + return tpmutil.Pack(uint32(0)) + } + + if len(sel) == 1 && len(sel[0].PCRs) == 0 && sel[0].Hash == 0 { + return tpmutil.Pack(uint32(0)) + } + + // PCR selection is a variable-size bitmask, where position of a set bit is + // the selected PCR index. + // Size of the bitmask in bytes is pre-pended. It should be at least + // sizeOfPCRSelect. + // + // For example, selecting PCRs 3 and 9 looks like: + // size(3) mask mask mask + // 00000011 00000000 00000001 00000100 + var retBytes []byte + for _, s := range sel { + ts := tpmsPCRSelection{ + Hash: s.Hash, + Size: sizeOfPCRSelect, + PCRs: make(tpmutil.RawBytes, sizeOfPCRSelect), + } + + // s[i].PCRs parameter is indexes of PCRs, convert that to set bits. + for _, n := range s.PCRs { + if n >= 8*sizeOfPCRSelect { + return nil, fmt.Errorf("PCR index %d is out of range (exceeds maximum value %d)", n, 8*sizeOfPCRSelect-1) + } + byteNum := n / 8 + bytePos := byte(1 << byte(n%8)) + ts.PCRs[byteNum] |= bytePos + } + + tmpBytes, err := tpmutil.Pack(ts) + if err != nil { + return nil, err + } + + retBytes = append(retBytes, tmpBytes...) + } + tmpSize, err := tpmutil.Pack(uint32(len(sel))) + if err != nil { + return nil, err + } + retBytes = append(tmpSize, retBytes...) + + return retBytes, nil +} + +func decodeTPMLPCRSelection(buf *bytes.Buffer) ([]PCRSelection, error) { + var count uint32 + var sel []PCRSelection + + // This unpacks buffer which is of type TPMLPCRSelection + // and returns the count of TPMSPCRSelections. + if err := tpmutil.UnpackBuf(buf, &count); err != nil { + return sel, err + } + + var ts tpmsPCRSelection + for i := 0; i < int(count); i++ { + var s PCRSelection + if err := tpmutil.UnpackBuf(buf, &ts.Hash, &ts.Size); err != nil { + return sel, err + } + ts.PCRs = make(tpmutil.RawBytes, ts.Size) + if _, err := buf.Read(ts.PCRs); err != nil { + return sel, err + } + s.Hash = ts.Hash + for j := 0; j < int(ts.Size); j++ { + for k := 0; k < 8; k++ { + set := ts.PCRs[j] & byte(1< 0, nil + case CapabilityAlgs: + var numAlgs uint32 + if err := tpmutil.UnpackBuf(buf, &numAlgs); err != nil { + return nil, false, fmt.Errorf("could not unpack algorithm count: %v", err) + } + + var algs []interface{} + for i := 0; i < int(numAlgs); i++ { + var alg AlgorithmDescription + if err := tpmutil.UnpackBuf(buf, &alg); err != nil { + return nil, false, fmt.Errorf("could not unpack algorithm description: %v", err) + } + algs = append(algs, alg) + } + return algs, moreData > 0, nil + case CapabilityTPMProperties: + var numProps uint32 + if err := tpmutil.UnpackBuf(buf, &numProps); err != nil { + return nil, false, fmt.Errorf("could not unpack fixed properties count: %v", err) + } + + var props []interface{} + for i := 0; i < int(numProps); i++ { + var prop TaggedProperty + if err := tpmutil.UnpackBuf(buf, &prop); err != nil { + return nil, false, fmt.Errorf("could not unpack tagged property: %v", err) + } + props = append(props, prop) + } + return props, moreData > 0, nil + + case CapabilityPCRs: + var pcrss []interface{} + pcrs, err := decodeTPMLPCRSelection(buf) + if err != nil { + return nil, false, fmt.Errorf("could not unpack pcr selection: %v", err) + } + for i := 0; i < len(pcrs); i++ { + pcrss = append(pcrss, pcrs[i]) + } + + return pcrss, moreData > 0, nil + + default: + return nil, false, fmt.Errorf("unsupported capability %v", capReported) + } +} + +// GetCapability returns various information about the TPM state. +// +// Currently only CapabilityHandles (list active handles) and CapabilityAlgs +// (list supported algorithms) are supported. CapabilityHandles will return +// a []tpmutil.Handle for vals, CapabilityAlgs will return +// []AlgorithmDescription. +// +// moreData is true if the TPM indicated that more data is available. Follow +// the spec for the capability in question on how to query for more data. +func GetCapability(rw io.ReadWriter, capa Capability, count, property uint32) (vals []interface{}, moreData bool, err error) { + resp, err := runCommand(rw, TagNoSessions, CmdGetCapability, capa, property, count) + if err != nil { + return nil, false, err + } + return decodeGetCapability(resp) +} + +// GetManufacturer returns the manufacturer ID +func GetManufacturer(rw io.ReadWriter) ([]byte, error) { + caps, _, err := GetCapability(rw, CapabilityTPMProperties, 1, uint32(Manufacturer)) + if err != nil { + return nil, err + } + + prop := caps[0].(TaggedProperty) + return tpmutil.Pack(prop.Value) +} + +func encodeAuthArea(sections ...AuthCommand) ([]byte, error) { + var res tpmutil.RawBytes + for _, s := range sections { + buf, err := tpmutil.Pack(s) + if err != nil { + return nil, err + } + res = append(res, buf...) + } + + size, err := tpmutil.Pack(uint32(len(res))) + if err != nil { + return nil, err + } + + return concat(size, res) +} + +func encodePCREvent(pcr tpmutil.Handle, eventData []byte) ([]byte, error) { + ha, err := tpmutil.Pack(pcr) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: EmptyAuth}) + if err != nil { + return nil, err + } + event, err := tpmutil.Pack(tpmutil.U16Bytes(eventData)) + if err != nil { + return nil, err + } + return concat(ha, auth, event) +} + +// PCREvent writes an update to the specified PCR. +func PCREvent(rw io.ReadWriter, pcr tpmutil.Handle, eventData []byte) error { + Cmd, err := encodePCREvent(pcr, eventData) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdPCREvent, tpmutil.RawBytes(Cmd)) + return err +} + +func encodeSensitiveArea(s tpmsSensitiveCreate) ([]byte, error) { + // TPMS_SENSITIVE_CREATE + buf, err := tpmutil.Pack(s) + if err != nil { + return nil, err + } + // TPM2B_SENSITIVE_CREATE + return tpmutil.Pack(tpmutil.U16Bytes(buf)) +} + +// encodeCreate works for both TPM2_Create and TPM2_CreatePrimary. +func encodeCreate(owner tpmutil.Handle, sel PCRSelection, auth AuthCommand, ownerPassword string, sensitiveData []byte, pub Public, outsideInfo []byte) ([]byte, error) { + parent, err := tpmutil.Pack(owner) + if err != nil { + return nil, err + } + encodedAuth, err := encodeAuthArea(auth) + if err != nil { + return nil, err + } + inSensitive, err := encodeSensitiveArea(tpmsSensitiveCreate{ + UserAuth: []byte(ownerPassword), + Data: sensitiveData, + }) + if err != nil { + return nil, err + } + inPublic, err := pub.Encode() + if err != nil { + return nil, err + } + publicBlob, err := tpmutil.Pack(tpmutil.U16Bytes(inPublic)) + if err != nil { + return nil, err + } + outsideInfoBlob, err := tpmutil.Pack(tpmutil.U16Bytes(outsideInfo)) + if err != nil { + return nil, err + } + creationPCR, err := encodeTPMLPCRSelection(sel) + if err != nil { + return nil, err + } + return concat( + parent, + encodedAuth, + inSensitive, + publicBlob, + outsideInfoBlob, + creationPCR, + ) +} + +func decodeCreatePrimary(in []byte) (handle tpmutil.Handle, public, creationData, creationHash tpmutil.U16Bytes, ticket Ticket, creationName tpmutil.U16Bytes, err error) { + var paramSize uint32 + + buf := bytes.NewBuffer(in) + // Handle and auth data. + if err := tpmutil.UnpackBuf(buf, &handle, ¶mSize); err != nil { + return 0, nil, nil, nil, Ticket{}, nil, fmt.Errorf("decoding handle, paramSize: %v", err) + } + + if err := tpmutil.UnpackBuf(buf, &public, &creationData, &creationHash, &ticket, &creationName); err != nil { + return 0, nil, nil, nil, Ticket{}, nil, fmt.Errorf("decoding public, creationData, creationHash, ticket, creationName: %v", err) + } + + if _, err := DecodeCreationData(creationData); err != nil { + return 0, nil, nil, nil, Ticket{}, nil, fmt.Errorf("parsing CreationData: %v", err) + } + return handle, public, creationData, creationHash, ticket, creationName, err +} + +// CreatePrimary initializes the primary key in a given hierarchy. +// The second return value is the public part of the generated key. +func CreatePrimary(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, p Public) (tpmutil.Handle, crypto.PublicKey, error) { + hnd, public, _, _, _, _, err := CreatePrimaryEx(rw, owner, sel, parentPassword, ownerPassword, p) + if err != nil { + return 0, nil, err + } + + pub, err := DecodePublic(public) + if err != nil { + return 0, nil, fmt.Errorf("parsing public: %v", err) + } + + pubKey, err := pub.Key() + if err != nil { + return 0, nil, fmt.Errorf("extracting cryto.PublicKey from Public part of primary key: %v", err) + } + + return hnd, pubKey, err +} + +// CreatePrimaryEx initializes the primary key in a given hierarchy. +// This function differs from CreatePrimary in that all response elements +// are returned, and they are returned in relatively raw form. +func CreatePrimaryEx(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, pub Public) (keyHandle tpmutil.Handle, public, creationData, creationHash []byte, ticket Ticket, creationName []byte, err error) { + auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} + Cmd, err := encodeCreate(owner, sel, auth, ownerPassword, nil /*inSensitive*/, pub, nil /*OutsideInfo*/) + if err != nil { + return 0, nil, nil, nil, Ticket{}, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdCreatePrimary, tpmutil.RawBytes(Cmd)) + if err != nil { + return 0, nil, nil, nil, Ticket{}, nil, err + } + + return decodeCreatePrimary(resp) +} + +// CreatePrimaryRawTemplate is CreatePrimary, but with the public template +// (TPMT_PUBLIC) provided pre-encoded. This is commonly used with key templates +// stored in NV RAM. +func CreatePrimaryRawTemplate(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, public []byte) (tpmutil.Handle, crypto.PublicKey, error) { + pub, err := DecodePublic(public) + if err != nil { + return 0, nil, fmt.Errorf("parsing input template: %v", err) + } + return CreatePrimary(rw, owner, sel, parentPassword, ownerPassword, pub) +} + +func decodeReadPublic(in []byte) (Public, []byte, []byte, error) { + var resp struct { + Public tpmutil.U16Bytes + Name tpmutil.U16Bytes + QualifiedName tpmutil.U16Bytes + } + if _, err := tpmutil.Unpack(in, &resp); err != nil { + return Public{}, nil, nil, err + } + pub, err := DecodePublic(resp.Public) + if err != nil { + return Public{}, nil, nil, err + } + return pub, resp.Name, resp.QualifiedName, nil +} + +// ReadPublic reads the public part of the object under handle. +// Returns the public data, name and qualified name. +func ReadPublic(rw io.ReadWriter, handle tpmutil.Handle) (Public, []byte, []byte, error) { + resp, err := runCommand(rw, TagNoSessions, CmdReadPublic, handle) + if err != nil { + return Public{}, nil, nil, err + } + + return decodeReadPublic(resp) +} + +func decodeCreate(in []byte) (private, public, creationData, creationHash tpmutil.U16Bytes, creationTicket Ticket, err error) { + buf := bytes.NewBuffer(in) + var paramSize uint32 + if err := tpmutil.UnpackBuf(buf, ¶mSize, &private, &public, &creationData, &creationHash, &creationTicket); err != nil { + return nil, nil, nil, nil, Ticket{}, fmt.Errorf("decoding Handle, Private, Public, CreationData, CreationHash, CreationTicket: %v", err) + } + if err != nil { + return nil, nil, nil, nil, Ticket{}, fmt.Errorf("decoding CreationTicket: %v", err) + } + if _, err := DecodeCreationData(creationData); err != nil { + return nil, nil, nil, nil, Ticket{}, fmt.Errorf("decoding CreationData: %v", err) + } + return private, public, creationData, creationHash, creationTicket, nil +} + +func create(rw io.ReadWriter, parentHandle tpmutil.Handle, auth AuthCommand, objectPassword string, sensitiveData []byte, pub Public, pcrSelection PCRSelection, outsideInfo []byte) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { + cmd, err := encodeCreate(parentHandle, pcrSelection, auth, objectPassword, sensitiveData, pub, outsideInfo) + if err != nil { + return nil, nil, nil, nil, Ticket{}, err + } + resp, err := runCommand(rw, TagSessions, CmdCreate, tpmutil.RawBytes(cmd)) + if err != nil { + return nil, nil, nil, nil, Ticket{}, err + } + return decodeCreate(resp) +} + +// CreateKey creates a new key pair under the owner handle. +// Returns private key and public key blobs as well as the +// creation data, a hash of said data and the creation ticket. +func CreateKey(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, pub Public) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { + auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} + return create(rw, owner, auth, ownerPassword, nil /*inSensitive*/, pub, sel, nil /*OutsideInfo*/) +} + +// CreateKeyUsingAuth creates a new key pair under the owner handle using the +// provided AuthCommand. Returns private key and public key blobs as well as +// the creation data, a hash of said data, and the creation ticket. +func CreateKeyUsingAuth(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, auth AuthCommand, ownerPassword string, pub Public) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { + return create(rw, owner, auth, ownerPassword, nil /*inSensitive*/, pub, sel, nil /*OutsideInfo*/) +} + +// CreateKeyWithSensitive is very similar to CreateKey, except +// that it can take in a piece of sensitive data. +func CreateKeyWithSensitive(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, pub Public, sensitive []byte) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { + auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} + return create(rw, owner, auth, ownerPassword, sensitive, pub, sel, nil /*OutsideInfo*/) +} + +// CreateKeyWithOutsideInfo is very similar to CreateKey, except +// that it returns the outside information. +func CreateKeyWithOutsideInfo(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, pub Public, outsideInfo []byte) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { + auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} + return create(rw, owner, auth, ownerPassword, nil /*inSensitive*/, pub, sel, outsideInfo) +} + +// Seal creates a data blob object that seals the sensitive data under a parent and with a +// password and auth policy. Access to the parent must be available with a simple password. +// Returns private and public portions of the created object. +func Seal(rw io.ReadWriter, parentHandle tpmutil.Handle, parentPassword, objectPassword string, objectAuthPolicy []byte, sensitiveData []byte) ([]byte, []byte, error) { + inPublic := Public{ + Type: AlgKeyedHash, + NameAlg: AlgSHA256, + Attributes: FlagFixedTPM | FlagFixedParent, + AuthPolicy: objectAuthPolicy, + } + auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} + private, public, _, _, _, err := create(rw, parentHandle, auth, objectPassword, sensitiveData, inPublic, PCRSelection{}, nil /*OutsideInfo*/) + if err != nil { + return nil, nil, err + } + return private, public, nil +} + +func encodeImport(parentHandle tpmutil.Handle, auth AuthCommand, publicBlob, privateBlob, symSeed, encryptionKey tpmutil.U16Bytes, sym *SymScheme) ([]byte, error) { + ph, err := tpmutil.Pack(parentHandle) + if err != nil { + return nil, err + } + encodedAuth, err := encodeAuthArea(auth) + if err != nil { + return nil, err + } + data, err := tpmutil.Pack(encryptionKey, publicBlob, privateBlob, symSeed) + if err != nil { + return nil, err + } + encodedScheme, err := sym.encode() + if err != nil { + return nil, err + } + + return concat(ph, encodedAuth, data, encodedScheme) +} + +func decodeImport(resp []byte) ([]byte, error) { + var paramSize uint32 + var outPrivate tpmutil.U16Bytes + _, err := tpmutil.Unpack(resp, ¶mSize, &outPrivate) + return outPrivate, err +} + +// Import allows a user to import a key created on a different computer +// or in a different TPM. The publicBlob and privateBlob must always be +// provided. symSeed should be non-nil iff an "outer wrapper" is used. Both of +// encryptionKey and sym should be non-nil iff an "inner wrapper" is used. +func Import(rw io.ReadWriter, parentHandle tpmutil.Handle, auth AuthCommand, publicBlob, privateBlob, symSeed, encryptionKey []byte, sym *SymScheme) ([]byte, error) { + Cmd, err := encodeImport(parentHandle, auth, publicBlob, privateBlob, symSeed, encryptionKey, sym) + if err != nil { + return nil, err + } + resp, err := runCommand(rw, TagSessions, CmdImport, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, err + } + return decodeImport(resp) +} + +func encodeLoad(parentHandle tpmutil.Handle, auth AuthCommand, publicBlob, privateBlob tpmutil.U16Bytes) ([]byte, error) { + ah, err := tpmutil.Pack(parentHandle) + if err != nil { + return nil, err + } + encodedAuth, err := encodeAuthArea(auth) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(privateBlob, publicBlob) + if err != nil { + return nil, err + } + return concat(ah, encodedAuth, params) +} + +func decodeLoad(in []byte) (tpmutil.Handle, []byte, error) { + var handle tpmutil.Handle + var paramSize uint32 + var name tpmutil.U16Bytes + + if _, err := tpmutil.Unpack(in, &handle, ¶mSize, &name); err != nil { + return 0, nil, err + } + + // Re-encode the name as a TPM2B_NAME so it can be parsed by DecodeName(). + b := &bytes.Buffer{} + if err := name.TPMMarshal(b); err != nil { + return 0, nil, err + } + return handle, b.Bytes(), nil +} + +// Load loads public/private blobs into an object in the TPM. +// Returns loaded object handle and its name. +func Load(rw io.ReadWriter, parentHandle tpmutil.Handle, parentAuth string, publicBlob, privateBlob []byte) (tpmutil.Handle, []byte, error) { + auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentAuth)} + return LoadUsingAuth(rw, parentHandle, auth, publicBlob, privateBlob) +} + +// LoadUsingAuth loads public/private blobs into an object in the TPM using the +// provided AuthCommand. Returns loaded object handle and its name. +func LoadUsingAuth(rw io.ReadWriter, parentHandle tpmutil.Handle, auth AuthCommand, publicBlob, privateBlob []byte) (tpmutil.Handle, []byte, error) { + Cmd, err := encodeLoad(parentHandle, auth, publicBlob, privateBlob) + if err != nil { + return 0, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdLoad, tpmutil.RawBytes(Cmd)) + if err != nil { + return 0, nil, err + } + return decodeLoad(resp) +} + +func encodeLoadExternal(pub Public, private Private, hierarchy tpmutil.Handle) ([]byte, error) { + privateBlob, err := private.Encode() + if err != nil { + return nil, err + } + publicBlob, err := pub.Encode() + if err != nil { + return nil, err + } + + return tpmutil.Pack(tpmutil.U16Bytes(privateBlob), tpmutil.U16Bytes(publicBlob), hierarchy) +} + +func decodeLoadExternal(in []byte) (tpmutil.Handle, []byte, error) { + var handle tpmutil.Handle + var name tpmutil.U16Bytes + + if _, err := tpmutil.Unpack(in, &handle, &name); err != nil { + return 0, nil, err + } + return handle, name, nil +} + +// LoadExternal loads a public (and optionally a private) key into an object in +// the TPM. Returns loaded object handle and its name. +func LoadExternal(rw io.ReadWriter, pub Public, private Private, hierarchy tpmutil.Handle) (tpmutil.Handle, []byte, error) { + Cmd, err := encodeLoadExternal(pub, private, hierarchy) + if err != nil { + return 0, nil, err + } + resp, err := runCommand(rw, TagNoSessions, CmdLoadExternal, tpmutil.RawBytes(Cmd)) + if err != nil { + return 0, nil, err + } + handle, name, err := decodeLoadExternal(resp) + if err != nil { + return 0, nil, err + } + return handle, name, nil +} + +// PolicyPassword sets password authorization requirement on the object. +func PolicyPassword(rw io.ReadWriter, handle tpmutil.Handle) error { + _, err := runCommand(rw, TagNoSessions, CmdPolicyPassword, handle) + return err +} + +func encodePolicySecret(entityHandle tpmutil.Handle, entityAuth AuthCommand, policyHandle tpmutil.Handle, policyNonce, cpHash, policyRef tpmutil.U16Bytes, expiry int32) ([]byte, error) { + auth, err := encodeAuthArea(entityAuth) + if err != nil { + return nil, err + } + handles, err := tpmutil.Pack(entityHandle, policyHandle) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(policyNonce, cpHash, policyRef, expiry) + if err != nil { + return nil, err + } + return concat(handles, auth, params) +} + +func decodePolicySecret(in []byte) ([]byte, *Ticket, error) { + buf := bytes.NewBuffer(in) + + var paramSize uint32 + var timeout tpmutil.U16Bytes + if err := tpmutil.UnpackBuf(buf, ¶mSize, &timeout); err != nil { + return nil, nil, fmt.Errorf("decoding timeout: %v", err) + } + var t Ticket + if err := tpmutil.UnpackBuf(buf, &t); err != nil { + return nil, nil, fmt.Errorf("decoding ticket: %v", err) + } + return timeout, &t, nil +} + +// PolicySecret sets a secret authorization requirement on the provided entity. +func PolicySecret(rw io.ReadWriter, entityHandle tpmutil.Handle, entityAuth AuthCommand, policyHandle tpmutil.Handle, policyNonce, cpHash, policyRef []byte, expiry int32) ([]byte, *Ticket, error) { + Cmd, err := encodePolicySecret(entityHandle, entityAuth, policyHandle, policyNonce, cpHash, policyRef, expiry) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdPolicySecret, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, nil, err + } + return decodePolicySecret(resp) +} + +func encodePolicySigned(validationKeyHandle tpmutil.Handle, policyHandle tpmutil.Handle, policyNonce, cpHash, policyRef tpmutil.U16Bytes, expiry int32, auth []byte) ([]byte, error) { + handles, err := tpmutil.Pack(validationKeyHandle, policyHandle) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(policyNonce, cpHash, policyRef, expiry, auth) + if err != nil { + return nil, err + } + return concat(handles, params) +} + +func decodePolicySigned(in []byte) ([]byte, *Ticket, error) { + buf := bytes.NewBuffer(in) + + var timeout tpmutil.U16Bytes + if err := tpmutil.UnpackBuf(buf, &timeout); err != nil { + return nil, nil, fmt.Errorf("decoding timeout: %v", err) + } + var t Ticket + if err := tpmutil.UnpackBuf(buf, &t); err != nil { + return nil, nil, fmt.Errorf("decoding ticket: %v", err) + } + return timeout, &t, nil +} + +// PolicySigned sets a signed authorization requirement on the provided policy. +func PolicySigned(rw io.ReadWriter, validationKeyHandle tpmutil.Handle, policyHandle tpmutil.Handle, policyNonce, cpHash, policyRef []byte, expiry int32, signedAuth []byte) ([]byte, *Ticket, error) { + Cmd, err := encodePolicySigned(validationKeyHandle, policyHandle, policyNonce, cpHash, policyRef, expiry, signedAuth) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagNoSessions, CmdPolicySigned, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, nil, err + } + return decodePolicySigned(resp) +} + +func encodePolicyPCR(session tpmutil.Handle, expectedDigest tpmutil.U16Bytes, sel PCRSelection) ([]byte, error) { + params, err := tpmutil.Pack(session, expectedDigest) + if err != nil { + return nil, err + } + pcrs, err := encodeTPMLPCRSelection(sel) + if err != nil { + return nil, err + } + return concat(params, pcrs) +} + +// PolicyPCR sets PCR state binding for authorization on a session. +// +// expectedDigest is optional. When specified, it's compared against the digest +// of PCRs matched by sel. +// +// Note that expectedDigest must be a *digest* of the expected PCR value. You +// must compute the digest manually. ReadPCR returns raw PCR values, not their +// digests. +// If you wish to select multiple PCRs, concatenate their values before +// computing the digest. See "TPM 2.0 Part 1, Selecting Multiple PCR". +func PolicyPCR(rw io.ReadWriter, session tpmutil.Handle, expectedDigest []byte, sel PCRSelection) error { + Cmd, err := encodePolicyPCR(session, expectedDigest, sel) + if err != nil { + return err + } + _, err = runCommand(rw, TagNoSessions, CmdPolicyPCR, tpmutil.RawBytes(Cmd)) + return err +} + +// PolicyOr compares PolicySession→Digest against the list of provided values. +// If the current Session→Digest does not match any value in the list, +// the TPM shall return TPM_RC_VALUE. Otherwise, the TPM will reset policySession→Digest +// to a Zero Digest. Then policySession→Digest is extended by the concatenation of +// TPM_CC_PolicyOR and the concatenation of all of the digests. +func PolicyOr(rw io.ReadWriter, session tpmutil.Handle, digests TPMLDigest) error { + d, err := digests.Encode() + if err != nil { + return err + } + data, err := tpmutil.Pack(session, d) + if err != nil { + return err + } + _, err = runCommand(rw, TagNoSessions, CmdPolicyOr, data) + return err +} + +// PolicyGetDigest returns the current policyDigest of the session. +func PolicyGetDigest(rw io.ReadWriter, handle tpmutil.Handle) ([]byte, error) { + resp, err := runCommand(rw, TagNoSessions, CmdPolicyGetDigest, handle) + if err != nil { + return nil, err + } + + var digest tpmutil.U16Bytes + _, err = tpmutil.Unpack(resp, &digest) + return digest, err +} + +func encodeStartAuthSession(tpmKey, bindKey tpmutil.Handle, nonceCaller, secret tpmutil.U16Bytes, se SessionType, sym, hashAlg Algorithm) ([]byte, error) { + ha, err := tpmutil.Pack(tpmKey, bindKey) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(nonceCaller, secret, se, sym, hashAlg) + if err != nil { + return nil, err + } + return concat(ha, params) +} + +func decodeStartAuthSession(in []byte) (tpmutil.Handle, []byte, error) { + var handle tpmutil.Handle + var nonce tpmutil.U16Bytes + if _, err := tpmutil.Unpack(in, &handle, &nonce); err != nil { + return 0, nil, err + } + return handle, nonce, nil +} + +// StartAuthSession initializes a session object. +// Returns session handle and the initial nonce from the TPM. +func StartAuthSession(rw io.ReadWriter, tpmKey, bindKey tpmutil.Handle, nonceCaller, secret []byte, se SessionType, sym, hashAlg Algorithm) (tpmutil.Handle, []byte, error) { + Cmd, err := encodeStartAuthSession(tpmKey, bindKey, nonceCaller, secret, se, sym, hashAlg) + if err != nil { + return 0, nil, err + } + resp, err := runCommand(rw, TagNoSessions, CmdStartAuthSession, tpmutil.RawBytes(Cmd)) + if err != nil { + return 0, nil, err + } + return decodeStartAuthSession(resp) +} + +func encodeUnseal(sessionHandle, itemHandle tpmutil.Handle, password string) ([]byte, error) { + ha, err := tpmutil.Pack(itemHandle) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: sessionHandle, Attributes: AttrContinueSession, Auth: []byte(password)}) + if err != nil { + return nil, err + } + return concat(ha, auth) +} + +func decodeUnseal(in []byte) ([]byte, error) { + var paramSize uint32 + var unsealed tpmutil.U16Bytes + + if _, err := tpmutil.Unpack(in, ¶mSize, &unsealed); err != nil { + return nil, err + } + return unsealed, nil +} + +// Unseal returns the data for a loaded sealed object. +func Unseal(rw io.ReadWriter, itemHandle tpmutil.Handle, password string) ([]byte, error) { + return UnsealWithSession(rw, HandlePasswordSession, itemHandle, password) +} + +// UnsealWithSession returns the data for a loaded sealed object. +func UnsealWithSession(rw io.ReadWriter, sessionHandle, itemHandle tpmutil.Handle, password string) ([]byte, error) { + Cmd, err := encodeUnseal(sessionHandle, itemHandle, password) + if err != nil { + return nil, err + } + resp, err := runCommand(rw, TagSessions, CmdUnseal, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, err + } + return decodeUnseal(resp) +} + +func encodeQuote(signingHandle tpmutil.Handle, signerAuth string, toQuote tpmutil.U16Bytes, sel PCRSelection, sigAlg Algorithm) ([]byte, error) { + ha, err := tpmutil.Pack(signingHandle) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(signerAuth)}) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(toQuote, sigAlg) + if err != nil { + return nil, err + } + pcrs, err := encodeTPMLPCRSelection(sel) + if err != nil { + return nil, err + } + return concat(ha, auth, params, pcrs) +} + +func decodeQuote(in []byte) ([]byte, []byte, error) { + buf := bytes.NewBuffer(in) + var paramSize uint32 + if err := tpmutil.UnpackBuf(buf, ¶mSize); err != nil { + return nil, nil, err + } + buf.Truncate(int(paramSize)) + var attest tpmutil.U16Bytes + if err := tpmutil.UnpackBuf(buf, &attest); err != nil { + return nil, nil, err + } + return attest, buf.Bytes(), nil +} + +// Quote returns a quote of PCR values. A quote is a signature of the PCR +// values, created using a signing TPM key. +// +// Returns attestation data and the decoded signature. +func Quote(rw io.ReadWriter, signingHandle tpmutil.Handle, signerAuth, unused string, toQuote []byte, sel PCRSelection, sigAlg Algorithm) ([]byte, *Signature, error) { + // TODO: Remove "unused" parameter on next breaking change. + attest, sigRaw, err := QuoteRaw(rw, signingHandle, signerAuth, unused, toQuote, sel, sigAlg) + if err != nil { + return nil, nil, err + } + sig, err := DecodeSignature(bytes.NewBuffer(sigRaw)) + if err != nil { + return nil, nil, err + } + return attest, sig, nil +} + +// QuoteRaw is very similar to Quote, except that it will return +// the raw signature in a byte array without decoding. +func QuoteRaw(rw io.ReadWriter, signingHandle tpmutil.Handle, signerAuth, _ string, toQuote []byte, sel PCRSelection, sigAlg Algorithm) ([]byte, []byte, error) { + // TODO: Remove "unused" parameter on next breaking change. + Cmd, err := encodeQuote(signingHandle, signerAuth, toQuote, sel, sigAlg) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdQuote, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, nil, err + } + return decodeQuote(resp) +} + +func encodeActivateCredential(auth []AuthCommand, activeHandle tpmutil.Handle, keyHandle tpmutil.Handle, credBlob, secret tpmutil.U16Bytes) ([]byte, error) { + ha, err := tpmutil.Pack(activeHandle, keyHandle) + if err != nil { + return nil, err + } + a, err := encodeAuthArea(auth...) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(credBlob, secret) + if err != nil { + return nil, err + } + return concat(ha, a, params) +} + +func decodeActivateCredential(in []byte) ([]byte, error) { + var paramSize uint32 + var certInfo tpmutil.U16Bytes + + if _, err := tpmutil.Unpack(in, ¶mSize, &certInfo); err != nil { + return nil, err + } + return certInfo, nil +} + +// ActivateCredential associates an object with a credential. +// Returns decrypted certificate information. +func ActivateCredential(rw io.ReadWriter, activeHandle, keyHandle tpmutil.Handle, activePassword, protectorPassword string, credBlob, secret []byte) ([]byte, error) { + return ActivateCredentialUsingAuth(rw, []AuthCommand{ + {Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(activePassword)}, + {Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(protectorPassword)}, + }, activeHandle, keyHandle, credBlob, secret) +} + +// ActivateCredentialUsingAuth associates an object with a credential, using the +// given set of authorizations. Two authorization must be provided. +// Returns decrypted certificate information. +func ActivateCredentialUsingAuth(rw io.ReadWriter, auth []AuthCommand, activeHandle, keyHandle tpmutil.Handle, credBlob, secret []byte) ([]byte, error) { + if len(auth) != 2 { + return nil, fmt.Errorf("len(auth) = %d, want 2", len(auth)) + } + + Cmd, err := encodeActivateCredential(auth, activeHandle, keyHandle, credBlob, secret) + if err != nil { + return nil, err + } + resp, err := runCommand(rw, TagSessions, CmdActivateCredential, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, err + } + return decodeActivateCredential(resp) +} + +func encodeMakeCredential(protectorHandle tpmutil.Handle, credential, activeName tpmutil.U16Bytes) ([]byte, error) { + ha, err := tpmutil.Pack(protectorHandle) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(credential, activeName) + if err != nil { + return nil, err + } + return concat(ha, params) +} + +func decodeMakeCredential(in []byte) ([]byte, []byte, error) { + var credBlob, encryptedSecret tpmutil.U16Bytes + + if _, err := tpmutil.Unpack(in, &credBlob, &encryptedSecret); err != nil { + return nil, nil, err + } + return credBlob, encryptedSecret, nil +} + +// MakeCredential creates an encrypted credential for use in MakeCredential. +// Returns encrypted credential and wrapped secret used to encrypt it. +func MakeCredential(rw io.ReadWriter, protectorHandle tpmutil.Handle, credential, activeName []byte) ([]byte, []byte, error) { + Cmd, err := encodeMakeCredential(protectorHandle, credential, activeName) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagNoSessions, CmdMakeCredential, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, nil, err + } + return decodeMakeCredential(resp) +} + +func encodeEvictControl(ownerAuth string, owner, objectHandle, persistentHandle tpmutil.Handle) ([]byte, error) { + ha, err := tpmutil.Pack(owner, objectHandle) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(ownerAuth)}) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(persistentHandle) + if err != nil { + return nil, err + } + return concat(ha, auth, params) +} + +// EvictControl toggles persistence of an object within the TPM. +func EvictControl(rw io.ReadWriter, ownerAuth string, owner, objectHandle, persistentHandle tpmutil.Handle) error { + Cmd, err := encodeEvictControl(ownerAuth, owner, objectHandle, persistentHandle) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdEvictControl, tpmutil.RawBytes(Cmd)) + return err +} + +func encodeClear(handle tpmutil.Handle, auth AuthCommand) ([]byte, error) { + ah, err := tpmutil.Pack(handle) + if err != nil { + return nil, err + } + encodedAuth, err := encodeAuthArea(auth) + if err != nil { + return nil, err + } + return concat(ah, encodedAuth) +} + +// Clear clears lockout, endorsement and owner hierarchy authorization values +func Clear(rw io.ReadWriter, handle tpmutil.Handle, auth AuthCommand) error { + Cmd, err := encodeClear(handle, auth) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdClear, tpmutil.RawBytes(Cmd)) + return err +} + +func encodePCRAllocate(handle tpmutil.Handle, auth AuthCommand, pcrSelection []PCRSelection) ([]byte, error) { + ah, err := tpmutil.Pack(handle) + if err != nil { + return nil, err + } + authEncoded, err := encodeAuthArea(auth) + if err != nil { + return nil, err + } + + pcrEncoded, err := encodeTPMLPCRSelection(pcrSelection...) + if err != nil { + return nil, err + } + return concat(ah, authEncoded, pcrEncoded) +} + +// PCRAllocate sets the desired PCR allocation of PCR and algorithms. +// The changes take effect once the TPM is restarted. +func PCRAllocate(rw io.ReadWriter, handle tpmutil.Handle, auth AuthCommand, pcrSelection []PCRSelection) error { + Cmd, err := encodePCRAllocate(handle, auth, pcrSelection) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdPCRAllocate, tpmutil.RawBytes(Cmd)) + return err +} + +func encodeHierarchyChangeAuth(handle tpmutil.Handle, auth AuthCommand, newAuth string) ([]byte, error) { + ah, err := tpmutil.Pack(handle) + if err != nil { + return nil, err + } + encodedAuth, err := encodeAuthArea(auth) + if err != nil { + return nil, err + } + param, err := tpmutil.Pack(tpmutil.U16Bytes(newAuth)) + if err != nil { + return nil, err + } + return concat(ah, encodedAuth, param) +} + +// HierarchyChangeAuth changes the authorization values for a hierarchy or for the lockout authority +func HierarchyChangeAuth(rw io.ReadWriter, handle tpmutil.Handle, auth AuthCommand, newAuth string) error { + Cmd, err := encodeHierarchyChangeAuth(handle, auth, newAuth) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdHierarchyChangeAuth, tpmutil.RawBytes(Cmd)) + return err +} + +// ContextSave returns an encrypted version of the session, object or sequence +// context for storage outside of the TPM. The handle references context to +// store. +func ContextSave(rw io.ReadWriter, handle tpmutil.Handle) ([]byte, error) { + return runCommand(rw, TagNoSessions, CmdContextSave, handle) +} + +// ContextLoad reloads context data created by ContextSave. +func ContextLoad(rw io.ReadWriter, saveArea []byte) (tpmutil.Handle, error) { + resp, err := runCommand(rw, TagNoSessions, CmdContextLoad, tpmutil.RawBytes(saveArea)) + if err != nil { + return 0, err + } + var handle tpmutil.Handle + _, err = tpmutil.Unpack(resp, &handle) + return handle, err +} + +func encodeIncrementNV(handle tpmutil.Handle, authString string) ([]byte, error) { + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(authString)}) + if err != nil { + return nil, err + } + out, err := tpmutil.Pack(handle, handle) + if err != nil { + return nil, err + } + return concat(out, auth) +} + +// NVIncrement increments a counter in NVRAM. +func NVIncrement(rw io.ReadWriter, handle tpmutil.Handle, authString string) error { + Cmd, err := encodeIncrementNV(handle, authString) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdIncrementNVCounter, tpmutil.RawBytes(Cmd)) + return err +} + +// NVUndefineSpace removes an index from TPM's NV storage. +func NVUndefineSpace(rw io.ReadWriter, ownerAuth string, owner, index tpmutil.Handle) error { + authArea := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(ownerAuth)} + return NVUndefineSpaceEx(rw, owner, index, authArea) +} + +// NVUndefineSpaceEx removes an index from NVRAM. Unlike, NVUndefineSpace(), custom command +// authorization can be provided. +func NVUndefineSpaceEx(rw io.ReadWriter, owner, index tpmutil.Handle, authArea AuthCommand) error { + out, err := tpmutil.Pack(owner, index) + if err != nil { + return err + } + auth, err := encodeAuthArea(authArea) + if err != nil { + return err + } + cmd, err := concat(out, auth) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdUndefineSpace, tpmutil.RawBytes(cmd)) + return err +} + +// NVUndefineSpaceSpecial This command allows removal of a platform-created NV Index that has TPMA_NV_POLICY_DELETE SET. +// The policy to authorize NV index access needs to be created with PolicyCommandCode(rw, sessionHandle, CmdNVUndefineSpaceSpecial) function +// nvAuthCmd takes the session handle for the policy and the AuthValue (which can be emptyAuth) for the authorization. +// platformAuth takes either a sessionHandle for the platform policy or HandlePasswordSession and the platformAuth value for authorization. +func NVUndefineSpaceSpecial(rw io.ReadWriter, nvIndex tpmutil.Handle, nvAuth, platformAuth AuthCommand) error { + authBytes, err := encodeAuthArea(nvAuth, platformAuth) + if err != nil { + return err + } + auth, err := tpmutil.Pack(authBytes) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdNVUndefineSpaceSpecial, nvIndex, HandlePlatform, tpmutil.RawBytes(auth)) + return err +} + +// NVDefineSpace creates an index in TPM's NV storage. +func NVDefineSpace(rw io.ReadWriter, owner, handle tpmutil.Handle, ownerAuth, authString string, policy []byte, attributes NVAttr, dataSize uint16) error { + nvPub := NVPublic{ + NVIndex: handle, + NameAlg: AlgSHA1, + Attributes: attributes, + AuthPolicy: policy, + DataSize: dataSize, + } + authArea := AuthCommand{ + Session: HandlePasswordSession, + Attributes: AttrContinueSession, + Auth: []byte(ownerAuth), + } + return NVDefineSpaceEx(rw, owner, authString, nvPub, authArea) +} + +// NVDefineSpaceEx accepts NVPublic structure and AuthCommand, allowing more flexibility. +func NVDefineSpaceEx(rw io.ReadWriter, owner tpmutil.Handle, authVal string, pubInfo NVPublic, authArea AuthCommand) error { + ha, err := tpmutil.Pack(owner) + if err != nil { + return err + } + auth, err := encodeAuthArea(authArea) + if err != nil { + return err + } + publicInfo, err := tpmutil.Pack(pubInfo) + if err != nil { + return err + } + params, err := tpmutil.Pack(tpmutil.U16Bytes(authVal), tpmutil.U16Bytes(publicInfo)) + if err != nil { + return err + } + cmd, err := concat(ha, auth, params) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdDefineSpace, tpmutil.RawBytes(cmd)) + return err +} + +// NVWrite writes data into the TPM's NV storage. +func NVWrite(rw io.ReadWriter, authHandle, nvIndex tpmutil.Handle, authString string, data tpmutil.U16Bytes, offset uint16) error { + auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(authString)} + return NVWriteEx(rw, authHandle, nvIndex, auth, data, offset) +} + +// NVWriteEx does the same as NVWrite with the exception of letting the user take care of the AuthCommand before calling the function. +// This allows more flexibility and does not limit the AuthCommand to PasswordSession. +func NVWriteEx(rw io.ReadWriter, authHandle, nvIndex tpmutil.Handle, authArea AuthCommand, data tpmutil.U16Bytes, offset uint16) error { + h, err := tpmutil.Pack(authHandle, nvIndex) + if err != nil { + return err + } + authEnc, err := encodeAuthArea(authArea) + if err != nil { + return err + } + + d, err := tpmutil.Pack(data, offset) + if err != nil { + return err + } + + b, err := concat(h, authEnc, d) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdWriteNV, tpmutil.RawBytes(b)) + return err +} + +func encodeLockNV(owner, handle tpmutil.Handle, authString string) ([]byte, error) { + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(authString)}) + if err != nil { + return nil, err + } + out, err := tpmutil.Pack(owner, handle) + if err != nil { + return nil, err + } + return concat(out, auth) +} + +// NVWriteLock inhibits further writes on the given NV index if at least one of +// the AttrWriteSTClear or AttrWriteDefine bits is set. +// +// AttrWriteSTClear causes the index to be locked until the TPM is restarted +// (see the Startup function). +// +// AttrWriteDefine causes the index to be locked permanently if data has been +// written to the index; otherwise the lock is removed on startup. +// +// NVWriteLock returns an error if neither bit is set. +// +// It is not an error to call NVWriteLock for an index that is already locked +// for writing. +func NVWriteLock(rw io.ReadWriter, owner, handle tpmutil.Handle, authString string) error { + Cmd, err := encodeLockNV(owner, handle, authString) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdWriteLockNV, tpmutil.RawBytes(Cmd)) + return err +} + +func decodeNVReadPublic(in []byte) (NVPublic, error) { + var pub NVPublic + var buf tpmutil.U16Bytes + if _, err := tpmutil.Unpack(in, &buf); err != nil { + return pub, err + } + _, err := tpmutil.Unpack(buf, &pub) + return pub, err +} + +// NVReadPublic reads the public data of an NV index. +func NVReadPublic(rw io.ReadWriter, index tpmutil.Handle) (NVPublic, error) { + // Read public area to determine data size. + resp, err := runCommand(rw, TagNoSessions, CmdReadPublicNV, index) + if err != nil { + return NVPublic{}, err + } + return decodeNVReadPublic(resp) +} + +func decodeNVRead(in []byte) ([]byte, error) { + var paramSize uint32 + var data tpmutil.U16Bytes + if _, err := tpmutil.Unpack(in, ¶mSize, &data); err != nil { + return nil, err + } + return data, nil +} + +func encodeNVRead(nvIndex, authHandle tpmutil.Handle, password string, offset, dataSize uint16) ([]byte, error) { + handles, err := tpmutil.Pack(authHandle, nvIndex) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(password)}) + if err != nil { + return nil, err + } + + params, err := tpmutil.Pack(dataSize, offset) + if err != nil { + return nil, err + } + + return concat(handles, auth, params) +} + +// NVRead reads a full data blob from an NV index. This function is +// deprecated; use NVReadEx instead. +func NVRead(rw io.ReadWriter, index tpmutil.Handle) ([]byte, error) { + return NVReadEx(rw, index, index, "", 0) +} + +// NVReadEx reads a full data blob from an NV index, using the given +// authorization handle. NVRead commands are done in blocks of blockSize. +// If blockSize is 0, the TPM is queried for TPM_PT_NV_BUFFER_MAX, and that +// value is used. +func NVReadEx(rw io.ReadWriter, index, authHandle tpmutil.Handle, password string, blockSize int) ([]byte, error) { + if blockSize == 0 { + readBuff, _, err := GetCapability(rw, CapabilityTPMProperties, 1, uint32(NVMaxBufferSize)) + if err != nil { + return nil, fmt.Errorf("GetCapability for TPM_PT_NV_BUFFER_MAX failed: %v", err) + } + if len(readBuff) != 1 { + return nil, fmt.Errorf("could not determine NVRAM read/write buffer size") + } + rb, ok := readBuff[0].(TaggedProperty) + if !ok { + return nil, fmt.Errorf("GetCapability returned unexpected type: %T, expected TaggedProperty", readBuff[0]) + } + blockSize = int(rb.Value) + } + + // Read public area to determine data size. + pub, err := NVReadPublic(rw, index) + if err != nil { + return nil, fmt.Errorf("decoding NV_ReadPublic response: %v", err) + } + + // Read the NVRAM area in blocks. + outBuff := make([]byte, 0, int(pub.DataSize)) + for len(outBuff) < int(pub.DataSize) { + readSize := blockSize + if readSize > (int(pub.DataSize) - len(outBuff)) { + readSize = int(pub.DataSize) - len(outBuff) + } + + Cmd, err := encodeNVRead(index, authHandle, password, uint16(len(outBuff)), uint16(readSize)) + if err != nil { + return nil, fmt.Errorf("building NV_Read command: %v", err) + } + resp, err := runCommand(rw, TagSessions, CmdReadNV, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, fmt.Errorf("running NV_Read command (cursor=%d,size=%d): %v", len(outBuff), readSize, err) + } + data, err := decodeNVRead(resp) + if err != nil { + return nil, fmt.Errorf("decoding NV_Read command: %v", err) + } + outBuff = append(outBuff, data...) + } + return outBuff, nil +} + +// NVReadLock inhibits further reads of the given NV index if AttrReadSTClear +// is set. After the TPM is restarted the index can be read again (see the +// Startup function). +// +// NVReadLock returns an error if the AttrReadSTClear bit is not set. +// +// It is not an error to call NVReadLock for an index that is already locked +// for reading. +func NVReadLock(rw io.ReadWriter, owner, handle tpmutil.Handle, authString string) error { + Cmd, err := encodeLockNV(owner, handle, authString) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdReadLockNV, tpmutil.RawBytes(Cmd)) + return err +} + +// decodeHash unpacks a successful response to TPM2_Hash, returning the computed digest and +// validation ticket. +func decodeHash(resp []byte) ([]byte, *Ticket, error) { + var digest tpmutil.U16Bytes + var validation Ticket + + buf := bytes.NewBuffer(resp) + if err := tpmutil.UnpackBuf(buf, &digest, &validation); err != nil { + return nil, nil, err + } + return digest, &validation, nil +} + +// Hash computes a hash of data in buf using TPM2_Hash, returning the computed +// digest and validation ticket. The validation ticket serves as confirmation +// from the TPM that the data in buf did not begin with TPM_GENERATED_VALUE. +// NOTE: TPM2_Hash can only accept data up to MAX_DIGEST_BUFFER in size, which +// is implementation-dependent, but guaranteed to be at least 1024 octets. +func Hash(rw io.ReadWriter, alg Algorithm, buf tpmutil.U16Bytes, hierarchy tpmutil.Handle) (digest []byte, validation *Ticket, err error) { + resp, err := runCommand(rw, TagNoSessions, CmdHash, buf, alg, hierarchy) + if err != nil { + return nil, nil, err + } + return decodeHash(resp) +} + +// HashSequenceStart starts a hash or an event sequence. If hashAlg is an +// implemented hash, then a hash sequence is started. If hashAlg is +// TPM_ALG_NULL, then an event sequence is started. +func HashSequenceStart(rw io.ReadWriter, sequenceAuth string, hashAlg Algorithm) (seqHandle tpmutil.Handle, err error) { + resp, err := runCommand(rw, TagNoSessions, CmdHashSequenceStart, tpmutil.U16Bytes(sequenceAuth), hashAlg) + if err != nil { + return 0, err + } + var handle tpmutil.Handle + _, err = tpmutil.Unpack(resp, &handle) + return handle, err +} + +func encodeSequenceUpdate(sequenceAuth string, seqHandle tpmutil.Handle, buf tpmutil.U16Bytes) ([]byte, error) { + ha, err := tpmutil.Pack(seqHandle) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(sequenceAuth)}) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(buf) + if err != nil { + return nil, err + } + return concat(ha, auth, params) +} + +// SequenceUpdate is used to add data to a hash or HMAC sequence. +func SequenceUpdate(rw io.ReadWriter, sequenceAuth string, seqHandle tpmutil.Handle, buffer []byte) error { + cmd, err := encodeSequenceUpdate(sequenceAuth, seqHandle, buffer) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdSequenceUpdate, tpmutil.RawBytes(cmd)) + return err +} + +func decodeSequenceComplete(resp []byte) ([]byte, *Ticket, error) { + var digest tpmutil.U16Bytes + var validation Ticket + var paramSize uint32 + + if _, err := tpmutil.Unpack(resp, ¶mSize, &digest, &validation); err != nil { + return nil, nil, err + } + return digest, &validation, nil +} + +func encodeSequenceComplete(sequenceAuth string, seqHandle, hierarchy tpmutil.Handle, buf tpmutil.U16Bytes) ([]byte, error) { + ha, err := tpmutil.Pack(seqHandle) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(sequenceAuth)}) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(buf, hierarchy) + if err != nil { + return nil, err + } + return concat(ha, auth, params) +} + +// SequenceComplete adds the last part of data, if any, to a hash/HMAC sequence +// and returns the result. +func SequenceComplete(rw io.ReadWriter, sequenceAuth string, seqHandle, hierarchy tpmutil.Handle, buffer []byte) (digest []byte, validation *Ticket, err error) { + cmd, err := encodeSequenceComplete(sequenceAuth, seqHandle, hierarchy, buffer) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdSequenceComplete, tpmutil.RawBytes(cmd)) + if err != nil { + return nil, nil, err + } + return decodeSequenceComplete(resp) +} + +func encodeEventSequenceComplete(auths []AuthCommand, pcrHandle, seqHandle tpmutil.Handle, buf tpmutil.U16Bytes) ([]byte, error) { + ha, err := tpmutil.Pack(pcrHandle, seqHandle) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(auths...) + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(buf) + if err != nil { + return nil, err + } + return concat(ha, auth, params) +} + +func decodeEventSequenceComplete(resp []byte) ([]*HashValue, error) { + var paramSize uint32 + var hashCount uint32 + var err error + + buf := bytes.NewBuffer(resp) + if err := tpmutil.UnpackBuf(buf, ¶mSize, &hashCount); err != nil { + return nil, err + } + + buf.Truncate(int(paramSize)) + digests := make([]*HashValue, hashCount) + for i := uint32(0); i < hashCount; i++ { + if digests[i], err = decodeHashValue(buf); err != nil { + return nil, err + } + } + + return digests, nil +} + +// EventSequenceComplete adds the last part of data, if any, to an Event +// Sequence and returns the result in a digest list. If pcrHandle references a +// PCR and not AlgNull, then the returned digest list is processed in the same +// manner as the digest list input parameter to PCRExtend() with the pcrHandle +// in each bank extended with the associated digest value. +func EventSequenceComplete(rw io.ReadWriter, pcrAuth, sequenceAuth string, pcrHandle, seqHandle tpmutil.Handle, buffer []byte) (digests []*HashValue, err error) { + auth := []AuthCommand{ + {Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(pcrAuth)}, + {Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(sequenceAuth)}, + } + cmd, err := encodeEventSequenceComplete(auth, pcrHandle, seqHandle, buffer) + if err != nil { + return nil, err + } + resp, err := runCommand(rw, TagSessions, CmdEventSequenceComplete, tpmutil.RawBytes(cmd)) + if err != nil { + return nil, err + } + return decodeEventSequenceComplete(resp) +} + +// Startup initializes a TPM (usually done by the OS). +func Startup(rw io.ReadWriter, typ StartupType) error { + _, err := runCommand(rw, TagNoSessions, CmdStartup, typ) + return err +} + +// Shutdown shuts down a TPM (usually done by the OS). +func Shutdown(rw io.ReadWriter, typ StartupType) error { + _, err := runCommand(rw, TagNoSessions, CmdShutdown, typ) + return err +} + +// nullTicket is a hard-coded null ticket of type TPMT_TK_HASHCHECK. +// It is for Sign commands that do not require the TPM to verify that the digest +// is not from data that started with TPM_GENERATED_VALUE. +var nullTicket = Ticket{ + Type: TagHashCheck, + Hierarchy: HandleNull, + Digest: tpmutil.U16Bytes{}, +} + +func encodeSign(sessionHandle, key tpmutil.Handle, password string, digest tpmutil.U16Bytes, sigScheme *SigScheme, validation *Ticket) ([]byte, error) { + ha, err := tpmutil.Pack(key) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: sessionHandle, Attributes: AttrContinueSession, Auth: []byte(password)}) + if err != nil { + return nil, err + } + d, err := tpmutil.Pack(digest) + if err != nil { + return nil, err + } + s, err := sigScheme.encode() + if err != nil { + return nil, err + } + if validation == nil { + validation = &nullTicket + } + v, err := tpmutil.Pack(validation) + if err != nil { + return nil, err + } + + return concat(ha, auth, d, s, v) +} + +func decodeSign(buf []byte) (*Signature, error) { + in := bytes.NewBuffer(buf) + var paramSize uint32 + if err := tpmutil.UnpackBuf(in, ¶mSize); err != nil { + return nil, err + } + return DecodeSignature(in) +} + +// SignWithSession computes a signature for digest using a given loaded key. Signature +// algorithm depends on the key type. Used for keys with non-password authorization policies. +// If 'key' references a Restricted Decryption key, 'validation' must be a valid hash verification +// ticket from the TPM, which can be obtained by using Hash() to hash the data with the TPM. +// If 'validation' is nil, a NULL ticket is passed to TPM2_Sign. +func SignWithSession(rw io.ReadWriter, sessionHandle, key tpmutil.Handle, password string, digest []byte, validation *Ticket, sigScheme *SigScheme) (*Signature, error) { + Cmd, err := encodeSign(sessionHandle, key, password, digest, sigScheme, validation) + if err != nil { + return nil, err + } + resp, err := runCommand(rw, TagSessions, CmdSign, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, err + } + return decodeSign(resp) +} + +// Sign computes a signature for digest using a given loaded key. Signature +// algorithm depends on the key type. +// If 'key' references a Restricted Decryption key, 'validation' must be a valid hash verification +// ticket from the TPM, which can be obtained by using Hash() to hash the data with the TPM. +// If 'validation' is nil, a NULL ticket is passed to TPM2_Sign. +func Sign(rw io.ReadWriter, key tpmutil.Handle, password string, digest []byte, validation *Ticket, sigScheme *SigScheme) (*Signature, error) { + return SignWithSession(rw, HandlePasswordSession, key, password, digest, validation, sigScheme) +} + +func encodeCertify(objectAuth, signerAuth string, object, signer tpmutil.Handle, qualifyingData tpmutil.U16Bytes) ([]byte, error) { + ha, err := tpmutil.Pack(object, signer) + if err != nil { + return nil, err + } + + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(objectAuth)}, AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(signerAuth)}) + if err != nil { + return nil, err + } + + scheme := SigScheme{Alg: AlgRSASSA, Hash: AlgSHA256} + // Use signing key's scheme. + s, err := scheme.encode() + if err != nil { + return nil, err + } + data, err := tpmutil.Pack(qualifyingData) + if err != nil { + return nil, err + } + return concat(ha, auth, data, s) +} + +// This function differs from encodeCertify in that it takes the scheme to be used as an additional argument. +func encodeCertifyEx(objectAuth, signerAuth string, object, signer tpmutil.Handle, qualifyingData tpmutil.U16Bytes, scheme SigScheme) ([]byte, error) { + ha, err := tpmutil.Pack(object, signer) + if err != nil { + return nil, err + } + + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(objectAuth)}, AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(signerAuth)}) + if err != nil { + return nil, err + } + + s, err := scheme.encode() + if err != nil { + return nil, err + } + data, err := tpmutil.Pack(qualifyingData) + if err != nil { + return nil, err + } + return concat(ha, auth, data, s) +} + +func decodeCertify(resp []byte) ([]byte, []byte, error) { + var paramSize uint32 + var attest tpmutil.U16Bytes + + buf := bytes.NewBuffer(resp) + if err := tpmutil.UnpackBuf(buf, ¶mSize); err != nil { + return nil, nil, err + } + buf.Truncate(int(paramSize)) + if err := tpmutil.UnpackBuf(buf, &attest); err != nil { + return nil, nil, err + } + return attest, buf.Bytes(), nil +} + +// Certify generates a signature of a loaded TPM object with a signing key +// signer. This function calls encodeCertify which makes use of the hardcoded +// signing scheme {AlgRSASSA, AlgSHA256}. Returned values are: attestation data (TPMS_ATTEST), +// signature and error, if any. +func Certify(rw io.ReadWriter, objectAuth, signerAuth string, object, signer tpmutil.Handle, qualifyingData []byte) ([]byte, []byte, error) { + cmd, err := encodeCertify(objectAuth, signerAuth, object, signer, qualifyingData) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdCertify, tpmutil.RawBytes(cmd)) + if err != nil { + return nil, nil, err + } + return decodeCertify(resp) +} + +// CertifyEx generates a signature of a loaded TPM object with a signing key +// signer. This function differs from Certify in that it takes the scheme +// to be used as an additional argument and calls encodeCertifyEx instead +// of encodeCertify. Returned values are: attestation data (TPMS_ATTEST), +// signature and error, if any. +func CertifyEx(rw io.ReadWriter, objectAuth, signerAuth string, object, signer tpmutil.Handle, qualifyingData []byte, scheme SigScheme) ([]byte, []byte, error) { + cmd, err := encodeCertifyEx(objectAuth, signerAuth, object, signer, qualifyingData, scheme) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdCertify, tpmutil.RawBytes(cmd)) + if err != nil { + return nil, nil, err + } + return decodeCertify(resp) +} + +func encodeCertifyCreation(objectAuth string, object, signer tpmutil.Handle, qualifyingData, creationHash tpmutil.U16Bytes, scheme SigScheme, ticket Ticket) ([]byte, error) { + handles, err := tpmutil.Pack(signer, object) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(objectAuth)}) + if err != nil { + return nil, err + } + s, err := scheme.encode() + if err != nil { + return nil, err + } + params, err := tpmutil.Pack(qualifyingData, creationHash, tpmutil.RawBytes(s), ticket) + if err != nil { + return nil, err + } + return concat(handles, auth, params) +} + +// CertifyCreation generates a signature of a newly-created & +// loaded TPM object, using signer as the signing key. +func CertifyCreation(rw io.ReadWriter, objectAuth string, object, signer tpmutil.Handle, qualifyingData, creationHash []byte, sigScheme SigScheme, creationTicket Ticket) (attestation, signature []byte, err error) { + Cmd, err := encodeCertifyCreation(objectAuth, object, signer, qualifyingData, creationHash, sigScheme, creationTicket) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdCertifyCreation, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, nil, err + } + return decodeCertify(resp) +} + +func runCommand(rw io.ReadWriter, tag tpmutil.Tag, Cmd tpmutil.Command, in ...interface{}) ([]byte, error) { + resp, code, err := tpmutil.RunCommand(rw, tag, Cmd, in...) + if err != nil { + return nil, err + } + if code != tpmutil.RCSuccess { + return nil, decodeResponse(code) + } + return resp, decodeResponse(code) +} + +// concat is a helper for encoding functions that separately encode handle, +// auth and param areas. A nil error is always returned, so that callers can +// simply return concat(a, b, c). +func concat(chunks ...[]byte) ([]byte, error) { + return bytes.Join(chunks, nil), nil +} + +func encodePCRExtend(pcr tpmutil.Handle, hashAlg Algorithm, hash tpmutil.RawBytes, password string) ([]byte, error) { + ha, err := tpmutil.Pack(pcr) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(password)}) + if err != nil { + return nil, err + } + pcrCount := uint32(1) + extend, err := tpmutil.Pack(pcrCount, hashAlg, hash) + if err != nil { + return nil, err + } + return concat(ha, auth, extend) +} + +// PCRExtend extends a value into the selected PCR +func PCRExtend(rw io.ReadWriter, pcr tpmutil.Handle, hashAlg Algorithm, hash []byte, password string) error { + Cmd, err := encodePCRExtend(pcr, hashAlg, hash, password) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdPCRExtend, tpmutil.RawBytes(Cmd)) + return err +} + +// ReadPCR reads the value of the given PCR. +func ReadPCR(rw io.ReadWriter, pcr int, hashAlg Algorithm) ([]byte, error) { + pcrSelection := PCRSelection{ + Hash: hashAlg, + PCRs: []int{pcr}, + } + pcrVals, err := ReadPCRs(rw, pcrSelection) + if err != nil { + return nil, fmt.Errorf("unable to read PCRs from TPM: %v", err) + } + pcrVal, present := pcrVals[pcr] + if !present { + return nil, fmt.Errorf("PCR %d value missing from response", pcr) + } + return pcrVal, nil +} + +func encodePCRReset(pcr tpmutil.Handle) ([]byte, error) { + ha, err := tpmutil.Pack(pcr) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: EmptyAuth}) + if err != nil { + return nil, err + } + return concat(ha, auth) +} + +// PCRReset resets the value of the given PCR. Usually, only PCR 16 (Debug) and +// PCR 23 (Application) are resettable on the default locality. +func PCRReset(rw io.ReadWriter, pcr tpmutil.Handle) error { + Cmd, err := encodePCRReset(pcr) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdPCRReset, tpmutil.RawBytes(Cmd)) + return err +} + +// EncryptSymmetric encrypts data using a symmetric key. +// +// WARNING: This command performs low-level cryptographic operations. +// Secure use of this command is subtle and requires careful analysis. +// Please consult with experts in cryptography for how to use it securely. +// +// The iv is the initialization vector. The iv must not be empty and its size depends on the +// details of the symmetric encryption scheme. +// +// The data may be longer than block size, EncryptSymmetric will chain +// multiple TPM calls to encrypt the entire blob. +// +// Key handle should point at SymCipher object which is a child of the key (and +// not e.g. RSA key itself). +func EncryptSymmetric(rw io.ReadWriteCloser, keyAuth string, key tpmutil.Handle, iv, data []byte) ([]byte, error) { + return encryptDecryptSymmetric(rw, keyAuth, key, iv, data, false) +} + +// DecryptSymmetric decrypts data using a symmetric key. +// +// WARNING: This command performs low-level cryptographic operations. +// Secure use of this command is subtle and requires careful analysis. +// Please consult with experts in cryptography for how to use it securely. +// +// The iv is the initialization vector. The iv must not be empty and its size +// depends on the details of the symmetric encryption scheme. +// +// The data may be longer than block size, DecryptSymmetric will chain multiple +// TPM calls to decrypt the entire blob. +// +// Key handle should point at SymCipher object which is a child of the key (and +// not e.g. RSA key itself). +func DecryptSymmetric(rw io.ReadWriteCloser, keyAuth string, key tpmutil.Handle, iv, data []byte) ([]byte, error) { + return encryptDecryptSymmetric(rw, keyAuth, key, iv, data, true) +} + +func encodeEncryptDecrypt(keyAuth string, key tpmutil.Handle, iv, data tpmutil.U16Bytes, decrypt bool) ([]byte, error) { + ha, err := tpmutil.Pack(key) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(keyAuth)}) + if err != nil { + return nil, err + } + // Use encryption key's mode. + params, err := tpmutil.Pack(decrypt, AlgNull, iv, data) + if err != nil { + return nil, err + } + return concat(ha, auth, params) +} + +func encodeEncryptDecrypt2(keyAuth string, key tpmutil.Handle, iv, data tpmutil.U16Bytes, decrypt bool) ([]byte, error) { + ha, err := tpmutil.Pack(key) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(keyAuth)}) + if err != nil { + return nil, err + } + // Use encryption key's mode. + params, err := tpmutil.Pack(data, decrypt, AlgNull, iv) + if err != nil { + return nil, err + } + return concat(ha, auth, params) +} + +func decodeEncryptDecrypt(resp []byte) ([]byte, []byte, error) { + var paramSize uint32 + var out, nextIV tpmutil.U16Bytes + if _, err := tpmutil.Unpack(resp, ¶mSize, &out, &nextIV); err != nil { + return nil, nil, err + } + return out, nextIV, nil +} + +func encryptDecryptBlockSymmetric(rw io.ReadWriteCloser, keyAuth string, key tpmutil.Handle, iv, data []byte, decrypt bool) ([]byte, []byte, error) { + Cmd, err := encodeEncryptDecrypt2(keyAuth, key, iv, data, decrypt) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagSessions, CmdEncryptDecrypt2, tpmutil.RawBytes(Cmd)) + if err != nil { + fmt0Err, ok := err.(Error) + if ok && fmt0Err.Code == RCCommandCode { + // If TPM2_EncryptDecrypt2 is not supported, fall back to + // TPM2_EncryptDecrypt. + Cmd, _ := encodeEncryptDecrypt(keyAuth, key, iv, data, decrypt) + resp, err = runCommand(rw, TagSessions, CmdEncryptDecrypt, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, nil, err + } + } + } + if err != nil { + return nil, nil, err + } + return decodeEncryptDecrypt(resp) +} + +func encryptDecryptSymmetric(rw io.ReadWriteCloser, keyAuth string, key tpmutil.Handle, iv, data []byte, decrypt bool) ([]byte, error) { + var out, block []byte + var err error + + for rest := data; len(rest) > 0; { + if len(rest) > maxDigestBuffer { + block, rest = rest[:maxDigestBuffer], rest[maxDigestBuffer:] + } else { + block, rest = rest, nil + } + block, iv, err = encryptDecryptBlockSymmetric(rw, keyAuth, key, iv, block, decrypt) + if err != nil { + return nil, err + } + out = append(out, block...) + } + + return out, nil +} + +func encodeRSAEncrypt(key tpmutil.Handle, message tpmutil.U16Bytes, scheme *AsymScheme, label string) ([]byte, error) { + ha, err := tpmutil.Pack(key) + if err != nil { + return nil, err + } + m, err := tpmutil.Pack(message) + if err != nil { + return nil, err + } + s, err := scheme.encode() + if err != nil { + return nil, err + } + if label != "" { + label += "\x00" + } + l, err := tpmutil.Pack(tpmutil.U16Bytes(label)) + if err != nil { + return nil, err + } + return concat(ha, m, s, l) +} + +func decodeRSAEncrypt(resp []byte) ([]byte, error) { + var out tpmutil.U16Bytes + _, err := tpmutil.Unpack(resp, &out) + return out, err +} + +// RSAEncrypt performs RSA encryption in the TPM according to RFC 3447. The key must be +// a (public) key loaded into the TPM beforehand. Note that when using OAEP with a label, +// a null byte is appended to the label and the null byte is included in the padding +// scheme. +func RSAEncrypt(rw io.ReadWriter, key tpmutil.Handle, message []byte, scheme *AsymScheme, label string) ([]byte, error) { + Cmd, err := encodeRSAEncrypt(key, message, scheme, label) + if err != nil { + return nil, err + } + resp, err := runCommand(rw, TagNoSessions, CmdRSAEncrypt, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, err + } + return decodeRSAEncrypt(resp) +} + +func encodeRSADecrypt(sessionHandle, key tpmutil.Handle, password string, message tpmutil.U16Bytes, scheme *AsymScheme, label string) ([]byte, error) { + ha, err := tpmutil.Pack(key) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: sessionHandle, Attributes: AttrContinueSession, Auth: []byte(password)}) + if err != nil { + return nil, err + } + m, err := tpmutil.Pack(message) + if err != nil { + return nil, err + } + s, err := scheme.encode() + if err != nil { + return nil, err + } + if label != "" { + label += "\x00" + } + l, err := tpmutil.Pack(tpmutil.U16Bytes(label)) + if err != nil { + return nil, err + } + return concat(ha, auth, m, s, l) +} + +func decodeRSADecrypt(resp []byte) ([]byte, error) { + var out tpmutil.U16Bytes + var paramSize uint32 + _, err := tpmutil.Unpack(resp, ¶mSize, &out) + return out, err +} + +// RSADecrypt performs RSA decryption in the TPM according to RFC 3447. The key must be +// a private RSA key in the TPM with FlagDecrypt set. Note that when using OAEP with a +// label, a null byte is appended to the label and the null byte is included in the +// padding scheme. +func RSADecrypt(rw io.ReadWriter, key tpmutil.Handle, password string, message []byte, scheme *AsymScheme, label string) ([]byte, error) { + return RSADecryptWithSession(rw, HandlePasswordSession, key, password, message, scheme, label) +} + +// RSADecryptWithSession performs RSA decryption in the TPM according to RFC 3447. The key must be +// a private RSA key in the TPM with FlagDecrypt set. Note that when using OAEP with a +// label, a null byte is appended to the label and the null byte is included in the +// padding scheme. +func RSADecryptWithSession(rw io.ReadWriter, sessionHandle, key tpmutil.Handle, password string, message []byte, scheme *AsymScheme, label string) ([]byte, error) { + Cmd, err := encodeRSADecrypt(sessionHandle, key, password, message, scheme, label) + if err != nil { + return nil, err + } + resp, err := runCommand(rw, TagSessions, CmdRSADecrypt, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, err + } + return decodeRSADecrypt(resp) +} + +func encodeECDHKeyGen(key tpmutil.Handle) ([]byte, error) { + return tpmutil.Pack(key) +} + +func decodeECDHKeyGen(resp []byte) (*ECPoint, *ECPoint, error) { + // Unpack z and pub as TPM2B_ECC_POINT, which is a TPMS_ECC_POINT with a total size prepended. + var z2B, pub2B tpmutil.U16Bytes + _, err := tpmutil.Unpack(resp, &z2B, &pub2B) + if err != nil { + return nil, nil, err + } + var zPoint, pubPoint ECPoint + _, err = tpmutil.Unpack(z2B, &zPoint.XRaw, &zPoint.YRaw) + if err != nil { + return nil, nil, err + } + _, err = tpmutil.Unpack(pub2B, &pubPoint.XRaw, &pubPoint.YRaw) + if err != nil { + return nil, nil, err + } + return &zPoint, &pubPoint, nil +} + +// ECDHKeyGen generates an ephemeral ECC key, calculates the ECDH point multiplcation of the +// ephemeral private key and a loaded public key, and returns the public ephemeral point along with +// the coordinates of the resulting point. +func ECDHKeyGen(rw io.ReadWriter, key tpmutil.Handle) (zPoint, pubPoint *ECPoint, err error) { + Cmd, err := encodeECDHKeyGen(key) + if err != nil { + return nil, nil, err + } + resp, err := runCommand(rw, TagNoSessions, CmdECDHKeyGen, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, nil, err + } + return decodeECDHKeyGen(resp) +} + +func encodeECDHZGen(key tpmutil.Handle, password string, inPoint ECPoint) ([]byte, error) { + ha, err := tpmutil.Pack(key) + if err != nil { + return nil, err + } + auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(password)}) + if err != nil { + return nil, err + } + p, err := tpmutil.Pack(inPoint) + if err != nil { + return nil, err + } + // Pack the TPMS_ECC_POINT as a TPM2B_ECC_POINT. + p2B, err := tpmutil.Pack(tpmutil.U16Bytes(p)) + if err != nil { + return nil, err + } + return concat(ha, auth, p2B) +} + +func decodeECDHZGen(resp []byte) (*ECPoint, error) { + var paramSize uint32 + // Unpack a TPM2B_ECC_POINT, which is a TPMS_ECC_POINT with a total size prepended. + var z2B tpmutil.U16Bytes + _, err := tpmutil.Unpack(resp, ¶mSize, &z2B) + if err != nil { + return nil, err + } + var zPoint ECPoint + _, err = tpmutil.Unpack(z2B, &zPoint.XRaw, &zPoint.YRaw) + if err != nil { + return nil, err + } + return &zPoint, nil +} + +// ECDHZGen performs ECDH point multiplication between a private key held in the TPM and a given +// public point, returning the coordinates of the resulting point. The key must have FlagDecrypt +// set. +func ECDHZGen(rw io.ReadWriter, key tpmutil.Handle, password string, inPoint ECPoint) (zPoint *ECPoint, err error) { + Cmd, err := encodeECDHZGen(key, password, inPoint) + if err != nil { + return nil, err + } + resp, err := runCommand(rw, TagSessions, CmdECDHZGen, tpmutil.RawBytes(Cmd)) + if err != nil { + return nil, err + } + return decodeECDHZGen(resp) +} + +// DictionaryAttackLockReset cancels the effect of a TPM lockout due to a number +// of successive authorization failures, by setting the lockout counter to zero. +// The command requires Lockout Authorization and only one lockoutAuth authorization +// failure is allowed for this command during a lockoutRecovery interval. +// Lockout Authorization value by default is empty and can be changed via +// a call to HierarchyChangeAuth(HandleLockout). +func DictionaryAttackLockReset(rw io.ReadWriter, auth AuthCommand) error { + ha, err := tpmutil.Pack(HandleLockout) + if err != nil { + return err + } + encodedAuth, err := encodeAuthArea(auth) + if err != nil { + return err + } + Cmd, err := concat(ha, encodedAuth) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdDictionaryAttackLockReset, tpmutil.RawBytes(Cmd)) + return err +} + +// DictionaryAttackParameters changes the lockout parameters. +// The command requires Lockout Authorization and has same authorization policy +// as in DictionaryAttackLockReset. +func DictionaryAttackParameters(rw io.ReadWriter, auth AuthCommand, maxTries, recoveryTime, lockoutRecovery uint32) error { + ha, err := tpmutil.Pack(HandleLockout) + if err != nil { + return err + } + encodedAuth, err := encodeAuthArea(auth) + if err != nil { + return err + } + params, err := tpmutil.Pack(maxTries, recoveryTime, lockoutRecovery) + if err != nil { + return err + } + Cmd, err := concat(ha, encodedAuth, params) + if err != nil { + return err + } + _, err = runCommand(rw, TagSessions, CmdDictionaryAttackParameters, tpmutil.RawBytes(Cmd)) + return err +} + +// PolicyCommandCode indicates that the authorization will be limited to a specific command code +func PolicyCommandCode(rw io.ReadWriter, session tpmutil.Handle, cc tpmutil.Command) error { + data, err := tpmutil.Pack(session, cc) + if err != nil { + return err + } + _, err = runCommand(rw, TagNoSessions, CmdPolicyCommandCode, data) + return err +} diff --git a/vendor/github.com/google/go-tpm/tpm/commands.go b/vendor/github.com/google/go-tpm/tpm/commands.go new file mode 100644 index 00000000000..ca30e3b952e --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/commands.go @@ -0,0 +1,474 @@ +// Copyright (c) 2014, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm + +import ( + "encoding/binary" + "errors" + "io" + + "github.com/google/go-tpm/tpmutil" +) + +// submitTPMRequest sends a structure to the TPM device file and gets results +// back, interpreting them as a new provided structure. +func submitTPMRequest(rw io.ReadWriter, tag uint16, ord uint32, in []interface{}, out []interface{}) (uint32, error) { + resp, code, err := tpmutil.RunCommand(rw, tpmutil.Tag(tag), tpmutil.Command(ord), in...) + if err != nil { + return 0, err + } + if code != tpmutil.RCSuccess { + return uint32(code), tpmError(code) + } + + _, err = tpmutil.Unpack(resp, out...) + return 0, err +} + +// oiap sends an OIAP command to the TPM and gets back an auth value and a +// nonce. +func oiap(rw io.ReadWriter) (*oiapResponse, error) { + var resp oiapResponse + out := []interface{}{&resp} + // In this case, we don't need to check ret, since all the information is + // contained in err. + if _, err := submitTPMRequest(rw, tagRQUCommand, ordOIAP, nil, out); err != nil { + return nil, err + } + + return &resp, nil +} + +// osap sends an OSAPCommand to the TPM and gets back authentication +// information in an OSAPResponse. +func osap(rw io.ReadWriter, osap *osapCommand) (*osapResponse, error) { + in := []interface{}{osap} + var resp osapResponse + out := []interface{}{&resp} + // In this case, we don't need to check the ret value, since all the + // information is contained in err. + if _, err := submitTPMRequest(rw, tagRQUCommand, ordOSAP, in, out); err != nil { + return nil, err + } + + return &resp, nil +} + +// seal performs a seal operation on the TPM. +func seal(rw io.ReadWriter, sc *sealCommand, pcrs *pcrInfoLong, data tpmutil.U32Bytes, ca *commandAuth) (*tpmStoredData, *responseAuth, uint32, error) { + pcrsize := binary.Size(pcrs) + if pcrsize < 0 { + return nil, nil, 0, errors.New("couldn't compute the size of a pcrInfoLong") + } + + // TODO(tmroeder): special-case pcrInfoLong in pack/unpack so we don't have + // to write out the length explicitly here. + in := []interface{}{sc, uint32(pcrsize), pcrs, data, ca} + + var tsd tpmStoredData + var ra responseAuth + out := []interface{}{&tsd, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordSeal, in, out) + if err != nil { + return nil, nil, 0, err + } + + return &tsd, &ra, ret, nil +} + +// unseal data sealed by the TPM. +func unseal(rw io.ReadWriter, keyHandle tpmutil.Handle, sealed *tpmStoredData, ca1 *commandAuth, ca2 *commandAuth) ([]byte, *responseAuth, *responseAuth, uint32, error) { + in := []interface{}{keyHandle, sealed, ca1, ca2} + var outb tpmutil.U32Bytes + var ra1 responseAuth + var ra2 responseAuth + out := []interface{}{&outb, &ra1, &ra2} + ret, err := submitTPMRequest(rw, tagRQUAuth2Command, ordUnseal, in, out) + if err != nil { + return nil, nil, nil, 0, err + } + + return outb, &ra1, &ra2, ret, nil +} + +// authorizeMigrationKey authorizes a public key for migrations. +func authorizeMigrationKey(rw io.ReadWriter, migrationScheme MigrationScheme, migrationKey pubKey, ca *commandAuth) ([]byte, *responseAuth, uint32, error) { + in := []interface{}{migrationScheme, migrationKey, ca} + var ra responseAuth + var migrationAuth migrationKeyAuth + out := []interface{}{&migrationAuth, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordAuthorizeMigrationKey, in, out) + if err != nil { + return nil, nil, 0, err + } + authBlob, err := tpmutil.Pack(migrationAuth) + if err != nil { + return nil, nil, 0, err + } + + return authBlob, &ra, ret, nil +} + +// createMigrationBlob migrates a key from the TPM. +func createMigrationBlob(rw io.ReadWriter, parentHandle tpmutil.Handle, migrationScheme MigrationScheme, migrationKey []byte, encData tpmutil.U32Bytes, ca1 *commandAuth, ca2 *commandAuth) ([]byte, []byte, *responseAuth, *responseAuth, uint32, error) { + in := []interface{}{parentHandle, migrationScheme, migrationKey, encData, ca1, ca2} + var rand tpmutil.U32Bytes + var outData tpmutil.U32Bytes + var ra1 responseAuth + var ra2 responseAuth + out := []interface{}{&rand, &outData, &ra1, &ra2} + ret, err := submitTPMRequest(rw, tagRQUAuth2Command, ordCreateMigrationBlob, in, out) + if err != nil { + return nil, nil, nil, nil, 0, err + } + + return rand, outData, &ra1, &ra2, ret, nil +} + +// flushSpecific removes a handle from the TPM. Note that removing a handle +// doesn't require any authentication. +func flushSpecific(rw io.ReadWriter, handle tpmutil.Handle, resourceType uint32) error { + // In this case, all the information is in err, so we don't check the + // specific return-value details. + _, err := submitTPMRequest(rw, tagRQUCommand, ordFlushSpecific, []interface{}{handle, resourceType}, nil) + return err +} + +// loadKey2 loads a key into the TPM. It's a tagRQUAuth1Command, so it only +// needs one auth parameter. +// TODO(tmroeder): support key12, too. +func loadKey2(rw io.ReadWriter, k *key, ca *commandAuth) (tpmutil.Handle, *responseAuth, uint32, error) { + // We always load our keys with the SRK as the parent key. + in := []interface{}{khSRK, k, ca} + var keyHandle tpmutil.Handle + var ra responseAuth + out := []interface{}{&keyHandle, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordLoadKey2, in, out) + if err != nil { + return 0, nil, 0, err + } + + return keyHandle, &ra, ret, nil +} + +// getPubKey gets a public key from the TPM +func getPubKey(rw io.ReadWriter, keyHandle tpmutil.Handle, ca *commandAuth) (*pubKey, *responseAuth, uint32, error) { + in := []interface{}{keyHandle, ca} + var pk pubKey + var ra responseAuth + out := []interface{}{&pk, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordGetPubKey, in, out) + if err != nil { + return nil, nil, 0, err + } + + return &pk, &ra, ret, nil +} + +// getCapability reads the requested capability and sub-capability from the TPM +func getCapability(rw io.ReadWriter, cap, subcap uint32) ([]byte, error) { + subCapBytes, err := tpmutil.Pack(subcap) + if err != nil { + return nil, err + } + var b tpmutil.U32Bytes + in := []interface{}{cap, tpmutil.U32Bytes(subCapBytes)} + out := []interface{}{&b} + if _, err := submitTPMRequest(rw, tagRQUCommand, ordGetCapability, in, out); err != nil { + return nil, err + } + return b, nil +} + +// nvDefineSpace allocates space in NVRAM +func nvDefineSpace(rw io.ReadWriter, nvData NVDataPublic, enc Digest, ca *commandAuth) (*responseAuth, uint32, error) { + var ra responseAuth + in := []interface{}{nvData, enc} + if ca != nil { + in = append(in, ca) + } + out := []interface{}{&ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordNVDefineSpace, in, out) + if err != nil { + return nil, 0, err + } + return &ra, ret, nil + +} + +// nvReadValue reads from the NVRAM +// If TPM isn't locked, and for some nv permission no authentication is needed. +// See TPM-Main-Part-3-Commands-20.4 +func nvReadValue(rw io.ReadWriter, index, offset, len uint32, ca *commandAuth) ([]byte, *responseAuth, uint32, error) { + var b tpmutil.U32Bytes + var ra responseAuth + var ret uint32 + var err error + in := []interface{}{index, offset, len} + out := []interface{}{&b} + // Auth is needed + if ca != nil { + in = append(in, ca) + out = append(out, &ra) + ret, err = submitTPMRequest(rw, tagRQUAuth1Command, ordNVReadValue, in, out) + } else { + // Auth is not needed + ret, err = submitTPMRequest(rw, tagRQUCommand, ordNVReadValue, in, out) + } + if err != nil { + return nil, nil, 0, err + } + return b, &ra, ret, nil +} + +// nvReadValueAuth reads nvram with enforced authentication. +// No Owner needs to be present. +// See TPM-Main-Part-3-Commands-20.5 +func nvReadValueAuth(rw io.ReadWriter, index, offset, len uint32, ca *commandAuth) ([]byte, *responseAuth, uint32, error) { + var b tpmutil.U32Bytes + var ra responseAuth + in := []interface{}{index, offset, len, ca} + out := []interface{}{&b, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordNVReadValueAuth, in, out) + if err != nil { + return nil, nil, 0, err + } + return b, &ra, ret, nil +} + +// nvWriteValue writes to the NVRAM +// If TPM isn't locked, no authentication is needed. +// See TPM-Main-Part-3-Commands-20.2 +func nvWriteValue(rw io.ReadWriter, index, offset, len uint32, data []byte, ca *commandAuth) ([]byte, *responseAuth, uint32, error) { + var b tpmutil.U32Bytes + var ra responseAuth + in := []interface{}{index, offset, len, data} + if ca != nil { + in = append(in, ca) + } + out := []interface{}{&b, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordNVWriteValue, in, out) + if err != nil { + return nil, nil, 0, err + } + return b, &ra, ret, nil +} + +// nvWriteValue writes to the NVRAM +// If TPM isn't locked, no authentication is needed. +// See TPM-Main-Part-3-Commands-20.3 +func nvWriteValueAuth(rw io.ReadWriter, index, offset, len uint32, data []byte, ca *commandAuth) ([]byte, *responseAuth, uint32, error) { + var b tpmutil.U32Bytes + var ra responseAuth + in := []interface{}{index, offset, len, data, ca} + out := []interface{}{&b, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordNVWriteValueAuth, in, out) + if err != nil { + return nil, nil, 0, err + } + return b, &ra, ret, nil +} + +// quote2 signs arbitrary data under a given set of PCRs and using a key +// specified by keyHandle. It returns information about the PCRs it signed +// under, the signature, auth information, and optionally information about the +// TPM itself. Note that the input to quote2 must be exactly 20 bytes, so it is +// normally the SHA1 hash of the data. +func quote2(rw io.ReadWriter, keyHandle tpmutil.Handle, hash [20]byte, pcrs *pcrSelection, addVersion byte, ca *commandAuth) (*pcrInfoShort, *CapVersionInfo, []byte, []byte, *responseAuth, uint32, error) { + in := []interface{}{keyHandle, hash, pcrs, addVersion, ca} + var pcrShort pcrInfoShort + var capInfo CapVersionInfo + var capBytes tpmutil.U32Bytes + var sig tpmutil.U32Bytes + var ra responseAuth + out := []interface{}{&pcrShort, &capBytes, &sig, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordQuote2, in, out) + if err != nil { + return nil, nil, nil, nil, nil, 0, err + } + + // Deserialize the capInfo, if any. + if len([]byte(capBytes)) == 0 { + return &pcrShort, nil, capBytes, sig, &ra, ret, nil + } + + err = capInfo.Decode(capBytes) + if err != nil { + return nil, nil, nil, nil, nil, 0, err + } + + return &pcrShort, &capInfo, capBytes, sig, &ra, ret, nil +} + +// quote performs a TPM 1.1 quote operation: it signs data using the +// TPM_QUOTE_INFO structure for the current values of a selected set of PCRs. +func quote(rw io.ReadWriter, keyHandle tpmutil.Handle, hash [20]byte, pcrs *pcrSelection, ca *commandAuth) (*pcrComposite, []byte, *responseAuth, uint32, error) { + in := []interface{}{keyHandle, hash, pcrs, ca} + var pcrc pcrComposite + var sig tpmutil.U32Bytes + var ra responseAuth + out := []interface{}{&pcrc, &sig, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordQuote, in, out) + if err != nil { + return nil, nil, nil, 0, err + } + + return &pcrc, sig, &ra, ret, nil +} + +// makeIdentity requests that the TPM create a new AIK. It returns the handle to +// this new key. +func makeIdentity(rw io.ReadWriter, encAuth Digest, idDigest Digest, k *key, ca1 *commandAuth, ca2 *commandAuth) (*key, []byte, *responseAuth, *responseAuth, uint32, error) { + in := []interface{}{encAuth, idDigest, k, ca1, ca2} + var aik key + var sig tpmutil.U32Bytes + var ra1 responseAuth + var ra2 responseAuth + out := []interface{}{&aik, &sig, &ra1, &ra2} + ret, err := submitTPMRequest(rw, tagRQUAuth2Command, ordMakeIdentity, in, out) + if err != nil { + return nil, nil, nil, nil, 0, err + } + + return &aik, sig, &ra1, &ra2, ret, nil +} + +// activateIdentity provides the TPM with an EK encrypted challenge and asks it to +// decrypt the challenge and return the secret (symmetric key). +func activateIdentity(rw io.ReadWriter, keyHandle tpmutil.Handle, blob tpmutil.U32Bytes, ca1 *commandAuth, ca2 *commandAuth) (*symKey, *responseAuth, *responseAuth, uint32, error) { + in := []interface{}{keyHandle, blob, ca1, ca2} + var symkey symKey + var ra1 responseAuth + var ra2 responseAuth + out := []interface{}{&symkey, &ra1, &ra2} + ret, err := submitTPMRequest(rw, tagRQUAuth2Command, ordActivateIdentity, in, out) + if err != nil { + return nil, nil, nil, 0, err + } + + return &symkey, &ra1, &ra2, ret, nil +} + +// resetLockValue resets the dictionary-attack lock in the TPM, using owner +// auth. +func resetLockValue(rw io.ReadWriter, ca *commandAuth) (*responseAuth, uint32, error) { + in := []interface{}{ca} + var ra responseAuth + out := []interface{}{&ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordResetLockValue, in, out) + if err != nil { + return nil, 0, err + } + + return &ra, ret, nil +} + +// ownerReadInternalPub uses owner auth and OSAP to read either the endorsement +// key (using khEK) or the SRK (using khSRK). +func ownerReadInternalPub(rw io.ReadWriter, kh tpmutil.Handle, ca *commandAuth) (*pubKey, *responseAuth, uint32, error) { + in := []interface{}{kh, ca} + var pk pubKey + var ra responseAuth + out := []interface{}{&pk, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordOwnerReadInternalPub, in, out) + if err != nil { + return nil, nil, 0, err + } + + return &pk, &ra, ret, nil +} + +// readPubEK requests the public part of the endorsement key from the TPM. Note +// that this call can only be made when there is no owner in the TPM. Once an +// owner is established, the endorsement key can be retrieved using +// ownerReadInternalPub. +func readPubEK(rw io.ReadWriter, n Nonce) (*pubKey, Digest, uint32, error) { + in := []interface{}{n} + var pk pubKey + var d Digest + out := []interface{}{&pk, &d} + ret, err := submitTPMRequest(rw, tagRQUCommand, ordReadPubEK, in, out) + if err != nil { + return nil, d, 0, err + } + + return &pk, d, ret, nil +} + +// ownerClear uses owner auth to clear the TPM. After this operation, a caller +// can take ownership of the TPM with TPM_TakeOwnership. +func ownerClear(rw io.ReadWriter, ca *commandAuth) (*responseAuth, uint32, error) { + in := []interface{}{ca} + var ra responseAuth + out := []interface{}{&ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordOwnerClear, in, out) + if err != nil { + return nil, 0, err + } + + return &ra, ret, nil +} + +// takeOwnership takes ownership of the TPM and establishes a new SRK and +// owner auth. This operation can only be performed if there is no owner. The +// TPM can be put into this state using TPM_OwnerClear. The encOwnerAuth and +// encSRKAuth values must be encrypted using the endorsement key. +func takeOwnership(rw io.ReadWriter, encOwnerAuth tpmutil.U32Bytes, encSRKAuth tpmutil.U32Bytes, srk *key, ca *commandAuth) (*key, *responseAuth, uint32, error) { + in := []interface{}{pidOwner, encOwnerAuth, encSRKAuth, srk, ca} + var k key + var ra responseAuth + out := []interface{}{&k, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordTakeOwnership, in, out) + if err != nil { + return nil, nil, 0, err + } + + return &k, &ra, ret, nil +} + +// Creates a wrapped key under the SRK. +func createWrapKey(rw io.ReadWriter, encUsageAuth Digest, encMigrationAuth Digest, keyInfo *key, ca *commandAuth) (*key, *responseAuth, uint32, error) { + in := []interface{}{khSRK, encUsageAuth, encMigrationAuth, keyInfo, ca} + var k key + var ra responseAuth + out := []interface{}{&k, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordCreateWrapKey, in, out) + if err != nil { + return nil, nil, 0, err + } + + return &k, &ra, ret, nil +} + +func sign(rw io.ReadWriter, keyHandle tpmutil.Handle, data tpmutil.U32Bytes, ca *commandAuth) ([]byte, *responseAuth, uint32, error) { + in := []interface{}{keyHandle, data, ca} + var signature tpmutil.U32Bytes + var ra responseAuth + out := []interface{}{&signature, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordSign, in, out) + if err != nil { + return nil, nil, 0, err + } + + return signature, &ra, ret, nil +} + +func pcrReset(rw io.ReadWriter, pcrs *pcrSelection) error { + _, err := submitTPMRequest(rw, tagRQUCommand, ordPcrReset, []interface{}{pcrs}, nil) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/google/go-tpm/tpm/constants.go b/vendor/github.com/google/go-tpm/tpm/constants.go new file mode 100644 index 00000000000..3ca96791a5e --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/constants.go @@ -0,0 +1,331 @@ +// Copyright (c) 2014, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm + +import ( + "fmt" + "strings" + + "github.com/google/go-tpm/tpmutil" +) + +// Supported TPM commands. +const ( + tagPCRInfoLong uint16 = 0x06 + tagNVAttributes uint16 = 0x0017 + tagNVDataPublic uint16 = 0x0018 + tagRQUCommand uint16 = 0x00C1 + tagRQUAuth1Command uint16 = 0x00C2 + tagRQUAuth2Command uint16 = 0x00C3 + tagRSPCommand uint16 = 0x00C4 + tagRSPAuth1Command uint16 = 0x00C5 + tagRSPAuth2Command uint16 = 0x00C6 +) + +// Supported TPM operations. +const ( + ordOIAP uint32 = 0x0000000A + ordOSAP uint32 = 0x0000000B + ordTakeOwnership uint32 = 0x0000000D + ordExtend uint32 = 0x00000014 + ordPCRRead uint32 = 0x00000015 + ordQuote uint32 = 0x00000016 + ordSeal uint32 = 0x00000017 + ordUnseal uint32 = 0x00000018 + ordCreateWrapKey uint32 = 0x0000001F + ordGetPubKey uint32 = 0x00000021 + ordCreateMigrationBlob uint32 = 0x00000028 + ordAuthorizeMigrationKey uint32 = 0x0000002b + ordSign uint32 = 0x0000003C + ordQuote2 uint32 = 0x0000003E + ordResetLockValue uint32 = 0x00000040 + ordLoadKey2 uint32 = 0x00000041 + ordGetRandom uint32 = 0x00000046 + ordOwnerClear uint32 = 0x0000005B + ordForceClear uint32 = 0x0000005D + ordGetCapability uint32 = 0x00000065 + ordCreateEndorsementKeyPair uint32 = 0x00000078 + ordMakeIdentity uint32 = 0x00000079 + ordActivateIdentity uint32 = 0x0000007A + ordReadPubEK uint32 = 0x0000007C + ordOwnerReadInternalPub uint32 = 0x00000081 + ordStartup uint32 = 0x00000099 + ordFlushSpecific uint32 = 0x000000BA + ordNVDefineSpace uint32 = 0x000000CC + ordPcrReset uint32 = 0x000000C8 + ordNVWriteValue uint32 = 0x000000CD + ordNVWriteValueAuth uint32 = 0x000000CE + ordNVReadValue uint32 = 0x000000CF + ordNVReadValueAuth uint32 = 0x000000D0 +) + +// Capability types. +const ( + CapAlg uint32 = 0x00000002 + CapProperty uint32 = 0x00000005 + CapFlag uint32 = 0x00000004 + CapNVList uint32 = 0x0000000D + CapNVIndex uint32 = 0x00000011 + CapHandle uint32 = 0x00000014 + CapVersion uint32 = 0x0000001A +) + +// SubCapabilities +const ( + SubCapPropManufacturer uint32 = 0x00000103 + SubCapFlagPermanent uint32 = 0x00000108 +) + +// Permission type +type Permission uint32 + +// NV Permissions and Operations +// Note: Permissions are summable +const ( + NVPerPPWrite Permission = 0x00000001 + NVPerOwnerWrite Permission = 0x00000002 + NVPerAuthWrite Permission = 0x00000004 + NVPerWriteAll Permission = 0x00000800 + // Warning: The Value 0x00001000 is + // defined in the spec as + // TPM_NV_PER_WRITEDEFINE, but it is + // not included directly in this + // code because it locks the given + // NV Index permanently if used + // incorrectly. This operation can't + // be undone in any way. Do not use + // this value unless you know what + // you're doing! + NVPerWriteSTClear Permission = 0x00002000 + NVPerGlobalLock Permission = 0x00004000 + NVPerPPRead Permission = 0x00008000 + NVPerOwnerRead Permission = 0x00100000 + NVPerAuthRead Permission = 0x00200000 + NVPerReadSTClear Permission = 0x80000000 +) + +// permMap : Map of TPM_NV_Permissions to its strings for convenience +var permMap = map[Permission]string{ + NVPerPPWrite: "PPWrite", + NVPerOwnerWrite: "OwnerWrite", + NVPerAuthWrite: "AuthWrite", + NVPerWriteAll: "WriteAll", + NVPerWriteSTClear: " WriteSTClear", + NVPerGlobalLock: "GlobalLock", + NVPerPPRead: "PPRead", + NVPerOwnerRead: "OwnerRead", + NVPerAuthRead: "AuthRead", + NVPerReadSTClear: "ReadSTClear", +} + +// String returns a textual representation of the set of permissions +func (p Permission) String() string { + var retString strings.Builder + for iterator, item := range permMap { + if (p & iterator) != 0 { + retString.WriteString(item + " + ") + } + } + if retString.String() == "" { + return "Permission/s not found" + } + return strings.TrimSuffix(retString.String(), " + ") + +} + +// Entity types. The LSB gives the entity type, and the MSB (currently fixed to +// 0x00) gives the ADIP type. ADIP type 0x00 is XOR. +const ( + _ uint16 = iota + etKeyHandle + etOwner + etData + etSRK + etKey + etRevoke +) + +// Resource types. +const ( + _ uint32 = iota + rtKey + rtAuth + rtHash + rtTrans +) + +// Locality type +type Locality byte + +// Values of locality +// Note: Localities are summable +const ( + LocZero Locality = 1 << iota + LocOne + LocTwo + LocThree + LocFour +) + +// LocaMap maps Locality values to strings for convenience +var locaMap = map[Locality]string{ + LocZero: "Locality 0", + LocOne: "Locality 1", + LocTwo: "Locality 2", + LocThree: "Locality 3", + LocFour: "Locality 4", +} + +// // String returns a textual representation of the set of Localities +func (l Locality) String() string { + var retString strings.Builder + for iterator, item := range locaMap { + if l&iterator != 0 { + retString.WriteString(item + " + ") + } + } + if retString.String() == "" { + return fmt.Sprintf("locality %d", int(l)) + } + return strings.TrimSuffix(retString.String(), " + ") +} + +// Entity values. +const ( + khSRK tpmutil.Handle = 0x40000000 + khOwner tpmutil.Handle = 0x40000001 + khRevokeTrust tpmutil.Handle = 0x40000002 + khEK tpmutil.Handle = 0x40000006 +) + +// Protocol IDs. +const ( + _ uint16 = iota + pidOIAP + pidOSAP + pidADIP + pidADCP + pidOwner + pidDSAP + pidTransport +) + +// Algorithm type for more convenient handling. +// see Algorithm ID for possible values. +type Algorithm uint32 + +// Algorithm ID values. +const ( + _ Algorithm = iota + AlgRSA + _ // was DES + _ // was 3DES in EDE mode + AlgSHA + AlgHMAC + AlgAES128 + AlgMGF1 + AlgAES192 + AlgAES256 + AlgXOR +) + +// AlgMap Map of Algorithms to Strings for nicer output and comparisons, etc. +var AlgMap = map[Algorithm]string{ + AlgRSA: "RSA", + AlgSHA: "SHA1", + AlgHMAC: "HMAC", + AlgAES128: "AER128", + AlgMGF1: "MFG1", + AlgAES192: "AES192", + AlgAES256: "AES256", +} + +func (a Algorithm) String() string { + n, ok := AlgMap[a] + if !ok { + return "unknown_algorithm" + } + return n +} + +// Encryption schemes. The values esNone and the two that contain the string +// "RSA" are only valid under AlgRSA. The other two are symmetric encryption +// schemes. +const ( + _ uint16 = iota + esNone + esRSAEsPKCSv15 + esRSAEsOAEPSHA1MGF1 + esSymCTR + esSymOFB + esSymCBCPKCS5 = 0xff // esSymCBCPKCS5 was taken from go-tspi +) + +// Signature schemes. These are only valid under AlgRSA. +const ( + _ uint16 = iota + ssNone + ssRSASaPKCS1v15SHA1 + ssRSASaPKCS1v15DER + ssRSASaPKCS1v15INFO +) + +// KeyUsage types for TPM_KEY (the key type). +const ( + keySigning uint16 = 0x0010 + keyStorage uint16 = 0x0011 + keyIdentity uint16 = 0x0012 + keyAuthChange uint16 = 0x0013 + keyBind uint16 = 0x0014 + keyLegacy uint16 = 0x0015 + keyMigrate uint16 = 0x0016 +) + +const ( + authNever byte = 0x00 + authAlways byte = 0x01 + authPrivUseOnly byte = 0x03 +) + +// KeyFlags represents TPM_KEY_FLAGS. +type KeyFlags uint32 + +const ( + keyRedirection KeyFlags = 0x00000001 + keyMigratable KeyFlags = 0x00000002 + keyIsVolatile KeyFlags = 0x00000004 + keyPcrIgnoredOnRead KeyFlags = 0x00000008 + keyMigrateAuthority KeyFlags = 0x00000010 +) + +// MigrationScheme represents TPM_MIGRATE_SCHEME. +type MigrationScheme uint16 + +const ( + msMigrate MigrationScheme = 0x0001 + msRewrap MigrationScheme = 0x0002 + msMaint MigrationScheme = 0x0003 + msRestrictMigrate MigrationScheme = 0x0004 + msRestrictApprove MigrationScheme = 0x0005 +) + +// fixedQuote is the fixed constant string used in quoteInfo. +var fixedQuote = [4]byte{byte('Q'), byte('U'), byte('O'), byte('T')} + +// quoteVersion is the fixed version string for quoteInfo. +const quoteVersion uint32 = 0x01010000 + +// oaepLabel is the label used for OEAP encryption in esRSAEsOAEPSHA1MGF1 +var oaepLabel = []byte{byte('T'), byte('C'), byte('P'), byte('A')} diff --git a/vendor/github.com/google/go-tpm/tpm/errors.go b/vendor/github.com/google/go-tpm/tpm/errors.go new file mode 100644 index 00000000000..3877cb49025 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/errors.go @@ -0,0 +1,234 @@ +// Copyright (c) 2014, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm + +import ( + "strconv" +) + +// A tpmError is an error value from the TPM. +type tpmError uint32 + +// Error produces a string for the given TPM Error code +func (o tpmError) Error() string { + if s, ok := tpmErrMsgs[o]; ok { + return "tpm: " + s + } + + return "tpm: unknown error code " + strconv.Itoa(int(o)) +} + +// These are the TPM error codes from the spec. +const ( + _ = iota + errAuthFail tpmError = iota + errBadIndex + errBadParameter + errAuditFailure + errClearDisabled + errDeactivated + errDisabled + errDisabledCmd + errFail + errBadOrdinal + errInstallDisabled + errInvalidKeyHandle + errKeyNotFound + errInappropriateEnc + errMigrateFail + errInvalidPCRInfo + errNoSpace + errNoSRK + errNotSealedBlob + errOwnerSet + errResources + errShortRandom + errSize + errWrongPCRVal + errBadParamSize + errSHAThread + errSHAError + errFailedSelfTest + errAuth2Fail + errBadTag + errIOError + errEncryptError + errDecryptError + errInvalidAuthHandle + errNoEndorsement + errInvalidKeyUsage + errWrongEntityType + errInvalidPostInit + errInappropriateSig + errBadKeyProperty + errBadMigration + errBadScheme + errBadDatasize + errBadMode + errBadPresence + errBadVersion + errNoWrapTransport + errAuditFailUnsuccessful + errAuditFailSuccessful + errNotResettable + errNotLocal + errBadType + errInvalidResource + errNotFIPS + errInvalidFamily + errNoNVPermission + errRequiresSign + errKeyNotSupported + errAuthConflict + errAreaLocked + errBadLocality + errReadOnly + errPerNoWrite + errFamilyCount + errWriteLocked + errBadAttributes + errInvalidStructure + errKeyOwnerControl + errBadCounter + errNotFullWrite + errContextGap + errMaxNVWrites + errNoOperator + errResourceMissing + errDelegateLock + errDelegateFamily + errDelegateAdmin + errTransportNotExclusive + errOwnerControl + errDAAResources + errDAAInputData0 + errDAAInputData1 + errDAAIssuerSettings + errDAASettings + errDAAState + errDAAIssuerValidity + errDAAWrongW + errBadHandle + errBadDelegate + errBadContext + errTooManyContexts + errMATicketSignature + errMADestination + errMASource + errMAAuthority +) + +// Extra messages the TPM might return. +const errDefendLockRunning tpmError = 2051 + +// tpmErrMsgs maps tpmError codes to their associated error strings. +var tpmErrMsgs = map[tpmError]string{ + errAuthFail: "authentication failed", + errBadIndex: "the index to a PCR, DIR or other register is incorrect", + errBadParameter: "one or more parameter is bad", + errAuditFailure: "an operation completed successfully but the auditing of that operation failed", + errClearDisabled: "the clear disable flag is set and all clear operations now require physical access", + errDeactivated: "the TPM is deactivated", + errDisabled: "the TPM is disabled", + errDisabledCmd: "the target command has been disabled", + errFail: "the operation failed", + errBadOrdinal: "the ordinal was unknown or inconsistent", + errInstallDisabled: "the ability to install an owner is disabled", + errInvalidKeyHandle: "the key handle can not be interpreted", + errKeyNotFound: "the key handle points to an invalid key", + errInappropriateEnc: "unacceptable encryption scheme", + errMigrateFail: "migration authorization failed", + errInvalidPCRInfo: "PCR information could not be interpreted", + errNoSpace: "no room to load key", + errNoSRK: "there is no SRK set", + errNotSealedBlob: "an encrypted blob is invalid or was not created by this TPM", + errOwnerSet: "there is already an Owner", + errResources: "the TPM has insufficient internal resources to perform the requested action", + errShortRandom: "a random string was too short", + errSize: "the TPM does not have the space to perform the operation", + errWrongPCRVal: "the named PCR value does not match the current PCR value", + errBadParamSize: "the paramSize argument to the command has the incorrect value", + errSHAThread: "there is no existing SHA-1 thread", + errSHAError: "the calculation is unable to proceed because the existing SHA-1 thread has already encountered an error", + errFailedSelfTest: "self-test has failed and the TPM has shutdown", + errAuth2Fail: "the authorization for the second key in a 2 key function failed authorization", + errBadTag: "the tag value sent to for a command is invalid", + errIOError: "an IO error occurred transmitting information to the TPM", + errEncryptError: "the encryption process had a problem", + errDecryptError: "the decryption process had a problem", + errInvalidAuthHandle: "an invalid handle was used", + errNoEndorsement: "the TPM does not have an EK installed", + errInvalidKeyUsage: "the usage of a key is not allowed", + errWrongEntityType: "the submitted entity type is not allowed", + errInvalidPostInit: "the command was received in the wrong sequence relative to Init and a subsequent Startup", + errInappropriateSig: "signed data cannot include additional DER information", + errBadKeyProperty: "the key properties in KEY_PARAMs are not supported by this TPM", + errBadMigration: "the migration properties of this key are incorrect", + errBadScheme: "the signature or encryption scheme for this key is incorrect or not permitted in this situation", + errBadDatasize: "the size of the data (or blob) parameter is bad or inconsistent with the referenced key", + errBadMode: "a mode parameter is bad, such as capArea or subCapArea for GetCapability, physicalPresence parameter for PhysicalPresence, or migrationType for CreateMigrationBlob", + errBadPresence: "either the physicalPresence or physicalPresenceLock bits have the wrong value", + errBadVersion: "the TPM cannot perform this version of the capability", + errNoWrapTransport: "the TPM does not allow for wrapped transport sessions", + errAuditFailUnsuccessful: "TPM audit construction failed and the underlying command was returning a failure code also", + errAuditFailSuccessful: "TPM audit construction failed and the underlying command was returning success", + errNotResettable: "attempt to reset a PCR register that does not have the resettable attribute", + errNotLocal: "attempt to reset a PCR register that requires locality and locality modifier not part of command transport", + errBadType: "make identity blob not properly typed", + errInvalidResource: "when saving context identified resource type does not match actual resource", + errNotFIPS: "the TPM is attempting to execute a command only available when in FIPS mode", + errInvalidFamily: "the command is attempting to use an invalid family ID", + errNoNVPermission: "the permission to manipulate the NV storage is not available", + errRequiresSign: "the operation requires a signed command", + errKeyNotSupported: "wrong operation to load an NV key", + errAuthConflict: "NV_LoadKey blob requires both owner and blob authorization", + errAreaLocked: "the NV area is locked and not writeable", + errBadLocality: "the locality is incorrect for the attempted operation", + errReadOnly: "the NV area is read only and can't be written to", + errPerNoWrite: "there is no protection on the write to the NV area", + errFamilyCount: "the family count value does not match", + errWriteLocked: "the NV area has already been written to", + errBadAttributes: "the NV area attributes conflict", + errInvalidStructure: "the structure tag and version are invalid or inconsistent", + errKeyOwnerControl: "the key is under control of the TPM Owner and can only be evicted by the TPM Owner", + errBadCounter: "the counter handle is incorrect", + errNotFullWrite: "the write is not a complete write of the area", + errContextGap: "the gap between saved context counts is too large", + errMaxNVWrites: "the maximum number of NV writes without an owner has been exceeded", + errNoOperator: "no operator AuthData value is set", + errResourceMissing: "the resource pointed to by context is not loaded", + errDelegateLock: "the delegate administration is locked", + errDelegateFamily: "attempt to manage a family other than the delegated family", + errDelegateAdmin: "delegation table management not enabled", + errTransportNotExclusive: "there was a command executed outside of an exclusive transport session", + errOwnerControl: "attempt to context save a owner evict controlled key", + errDAAResources: "the DAA command has no resources available to execute the command", + errDAAInputData0: "the consistency check on DAA parameter inputData0 has failed", + errDAAInputData1: "the consistency check on DAA parameter inputData1 has failed", + errDAAIssuerSettings: "the consistency check on DAA_issuerSettings has failed", + errDAASettings: "the consistency check on DAA_tpmSpecific has failed", + errDAAState: "the atomic process indicated by the submitted DAA command is not the expected process", + errDAAIssuerValidity: "the issuer's validity check has detected an inconsistency", + errDAAWrongW: "the consistency check on w has failed", + errBadHandle: "the handle is incorrect", + errBadDelegate: "delegation is not correct", + errBadContext: "the context blob is invalid", + errTooManyContexts: "too many contexts held by the TPM", + errMATicketSignature: "migration authority signature validation failure", + errMADestination: "migration destination not authenticated", + errMASource: "migration source incorrect", + errMAAuthority: "incorrect migration authority", + errDefendLockRunning: "the TPM is defending against dictionary attacks and is in some time-out period", +} diff --git a/vendor/github.com/google/go-tpm/tpm/open_other.go b/vendor/github.com/google/go-tpm/tpm/open_other.go new file mode 100644 index 00000000000..a357a17f89c --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/open_other.go @@ -0,0 +1,53 @@ +//go:build !windows + +// Copyright (c) 2019, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm + +import ( + "fmt" + "io" + + "github.com/google/go-tpm/tpmutil" +) + +// OpenTPM opens a channel to the TPM at the given path. If the file is a +// device, then it treats it like a normal TPM device, and if the file is a +// Unix domain socket, then it opens a connection to the socket. +func OpenTPM(path string) (io.ReadWriteCloser, error) { + return openAndStartupTPM(path, false) +} + +// openAndStartupTPM opens the TPM and optionally runs TPM_Startup if needed. +// This feature is implemented only for testing. +func openAndStartupTPM(path string, doStartup bool) (io.ReadWriteCloser, error) { + rwc, err := tpmutil.OpenTPM(path) + if err != nil { + return nil, err + } + + // Make sure this is a TPM 1.2 + _, err = GetManufacturer(rwc) + if doStartup && err == tpmError(errInvalidPostInit) { + if err = startup(rwc); err == nil { + _, err = GetManufacturer(rwc) + } + } + if err != nil { + rwc.Close() + return nil, fmt.Errorf("open %s: device is not a TPM 1.2: %v", path, err) + } + return rwc, nil +} diff --git a/vendor/github.com/google/go-tpm/tpm/open_windows.go b/vendor/github.com/google/go-tpm/tpm/open_windows.go new file mode 100644 index 00000000000..5032a2ad71f --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/open_windows.go @@ -0,0 +1,37 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm + +import ( + "fmt" + "io" + + "github.com/google/go-tpm/tpmutil" + "github.com/google/go-tpm/tpmutil/tbs" +) + +// OpenTPM opens a channel to the TPM. +func OpenTPM() (io.ReadWriteCloser, error) { + info, err := tbs.GetDeviceInfo() + if err != nil { + return nil, err + } + + if info.TPMVersion != tbs.TPMVersion12 { + return nil, fmt.Errorf("openTPM: device is not a TPM 1.2") + } + + return tpmutil.OpenTPM() +} diff --git a/vendor/github.com/google/go-tpm/tpm/pcrs.go b/vendor/github.com/google/go-tpm/tpm/pcrs.go new file mode 100644 index 00000000000..9f84d2aabb8 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/pcrs.go @@ -0,0 +1,203 @@ +// Copyright (c) 2014, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm + +import ( + "crypto/sha1" + "errors" + "fmt" + "io" + "strconv" + + "github.com/google/go-tpm/tpmutil" +) + +// setPCR sets a PCR value as selected in a given mask. +func (pm *pcrMask) setPCR(i int) error { + if i >= 24 || i < 0 { + return errors.New("can't set PCR " + strconv.Itoa(i)) + } + + (*pm)[i/8] |= 1 << uint(i%8) + return nil +} + +// isPCRSet checks to see if a given PCR is included in this mask. +func (pm pcrMask) isPCRSet(i int) (bool, error) { + if i >= 24 || i < 0 { + return false, errors.New("can't check PCR " + strconv.Itoa(i)) + } + + n := byte(1 << uint(i%8)) + return pm[i/8]&n == n, nil +} + +// String returns a string representation of a pcrSelection +func (p pcrSelection) String() string { + return fmt.Sprintf("pcrSelection{Size: %x, Mask: % x}", p.Size, p.Mask) +} + +// newPCRSelection creates a new pcrSelection for the given set of PCRs. +func newPCRSelection(pcrVals []int) (*pcrSelection, error) { + pcrs := &pcrSelection{Size: 3} + for _, v := range pcrVals { + if err := pcrs.Mask.setPCR(v); err != nil { + return nil, err + } + } + + return pcrs, nil +} + +// createPCRComposite composes a set of PCRs by prepending a pcrSelection and a +// length, then computing the SHA1 hash and returning its output. +func createPCRComposite(mask pcrMask, pcrs []byte) ([]byte, error) { + if len(pcrs)%PCRSize != 0 { + return nil, errors.New("pcrs must be a multiple of " + strconv.Itoa(PCRSize)) + } + + pcrc := pcrComposite{ + Selection: pcrSelection{3, mask}, + Values: pcrs, + } + b, err := tpmutil.Pack(pcrc) + if err != nil { + return nil, err + } + + h := sha1.Sum(b) + return h[:], nil +} + +// String returns a string representation of a pcrInfoLong. +func (pcri pcrInfoLong) String() string { + return fmt.Sprintf("pcrInfoLong{Tag: %x, LocAtCreation: %x, LocAtRelease: %x, PCRsAtCreation: %s, PCRsAtRelease: %s, DigestAtCreation: % x, DigestAtRelease: % x}", pcri.Tag, pcri.LocAtCreation, pcri.LocAtRelease, pcri.PCRsAtCreation, pcri.PCRsAtRelease, pcri.DigestAtCreation, pcri.DigestAtRelease) +} + +// String returns a string representation of a pcrInfoShort. +func (pcri pcrInfoShort) String() string { + return fmt.Sprintf("pcrInfoShort{LocAtRelease: %x, PCRsAtRelease: %s, DigestAtRelease: % x}", pcri.LocAtRelease, pcri.PCRsAtRelease, pcri.DigestAtRelease) +} + +// createPCRInfoLong creates a pcrInfoLong structure from a mask and some PCR +// values that match this mask, along with a TPM locality. +func createPCRInfoLong(loc Locality, mask pcrMask, pcrVals []byte) (*pcrInfoLong, error) { + d, err := createPCRComposite(mask, pcrVals) + if err != nil { + return nil, err + } + + pcri := &pcrInfoLong{ + Tag: tagPCRInfoLong, + LocAtCreation: loc, + LocAtRelease: loc, + PCRsAtCreation: pcrSelection{3, mask}, + PCRsAtRelease: pcrSelection{3, mask}, + } + + copy(pcri.DigestAtRelease[:], d) + copy(pcri.DigestAtCreation[:], d) + + return pcri, nil +} + +// createPCRInfoShort creates a pcrInfoShort structure from a mask and some PCR +// values that match this mask, alon with a TPM locality +func createPCRInfoShort(loc Locality, mask pcrMask, pcrVals []byte) (*pcrInfoShort, error) { + d, err := createPCRComposite(mask, pcrVals) + if err != nil { + return nil, err + } + + pcri := &pcrInfoShort{ + PCRsAtRelease: pcrSelection{3, mask}, + LocAtRelease: loc, + } + copy(pcri.DigestAtRelease[:], d) + + return pcri, nil +} + +// newPCRInfoLong creates and returns a pcrInfoLong structure for the given PCR +// values. +func newPCRInfoLong(rw io.ReadWriter, loc Locality, pcrNums []int) (*pcrInfoLong, error) { + var mask pcrMask + for _, pcr := range pcrNums { + if err := mask.setPCR(pcr); err != nil { + return nil, err + } + } + + pcrVals, err := FetchPCRValues(rw, pcrNums) + if err != nil { + return nil, err + } + + return createPCRInfoLong(loc, mask, pcrVals) +} + +func newPCRInfoShort(rw io.ReadWriter, loc Locality, pcrNums []int) (*pcrInfoShort, error) { + var mask pcrMask + for _, pcr := range pcrNums { + if err := mask.setPCR(pcr); err != nil { + return nil, err + } + } + pcrVals, err := FetchPCRValues(rw, pcrNums) + if err != nil { + return nil, err + } + return createPCRInfoShort(loc, mask, pcrVals) +} + +func newPCRInfo(rw io.ReadWriter, pcrNums []int) (*pcrInfo, error) { + var mask pcrMask + for _, pcr := range pcrNums { + if err := mask.setPCR(pcr); err != nil { + return nil, err + } + } + + pcrVals, err := FetchPCRValues(rw, pcrNums) + if err != nil { + return nil, err + } + d, err := createPCRComposite(mask, pcrVals) + if err != nil { + return nil, err + } + pcri := &pcrInfo{ + PcrSelection: pcrSelection{3, mask}, + } + copy(pcri.DigestAtRelease[:], d) + copy(pcri.DigestAtCreation[:], d) + + return pcri, nil +} + +// newPCRInfoLongWithHashes creates and returns a pcrInfoLong structure for the +// given PCRs and hashes. +func newPCRInfoLongWithHashes(loc Locality, pcrs map[int][]byte) (*pcrInfoLong, error) { + var mask pcrMask + var hashes []byte + for index, hash := range pcrs { + if err := mask.setPCR(index); err != nil { + return nil, err + } + hashes = append(hashes, hash...) + } + + return createPCRInfoLong(loc, mask, hashes) +} diff --git a/vendor/github.com/google/go-tpm/tpm/structures.go b/vendor/github.com/google/go-tpm/tpm/structures.go new file mode 100644 index 00000000000..5589d88933f --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/structures.go @@ -0,0 +1,428 @@ +// Copyright (c) 2014, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm + +import ( + "bytes" + "crypto" + "crypto/rsa" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + + "github.com/google/go-tpm/tpmutil" +) + +// A pcrValue is the fixed-size value of a PCR. +type pcrValue [20]byte + +// PCRSize gives the fixed size (20 bytes) of a PCR. +const PCRSize int = 20 + +// A pcrMask represents a set of PCR choices, one bit per PCR out of the 24 +// possible PCR values. +type pcrMask [3]byte + +// A pcrSelection is the first element in the input a PCR composition, which is +// A pcrSelection, followed by the combined length of the PCR values, +// followed by the PCR values, all hashed under SHA-1. +type pcrSelection struct { + Size uint16 + Mask pcrMask +} + +// pcrInfoLong stores detailed information about PCRs. +type pcrInfoLong struct { + Tag uint16 + LocAtCreation Locality + LocAtRelease Locality + PCRsAtCreation pcrSelection + PCRsAtRelease pcrSelection + DigestAtCreation Digest + DigestAtRelease Digest +} + +// pcrInfoShort stores detailed information about PCRs. +type pcrInfoShort struct { + PCRsAtRelease pcrSelection + LocAtRelease Locality + DigestAtRelease Digest +} + +type pcrInfo struct { + PcrSelection pcrSelection + DigestAtRelease Digest + DigestAtCreation Digest +} + +type capVersionByte byte + +// A capVersionInfo contains information about the TPM itself. Note that this +// is deserialized specially, since it has a variable-length byte array but no +// length. It is preceded with a length in the response to the Quote2 command. + +type capVersion struct { + Major capVersionByte + Minor capVersionByte + RevMajor byte + RevMinor byte +} + +// CapVersionInfo implements TPM_CAP_VERSION_INFO from spec. Part 2 - Page 174 +type CapVersionInfo struct { + Tag tpmutil.Tag + Version capVersion + SpecLevel uint16 + ErrataRev byte + TPMVendorID [4]byte + VendorSpecific tpmutil.U16Bytes +} + +// Decode reads TPM_CAP_VERSION_INFO into CapVersionInfo. +func (c *CapVersionInfo) Decode(data []byte) error { + var cV capVersion + buf := bytes.NewReader(data) + err := binary.Read(buf, binary.LittleEndian, &c.Tag) + if err != nil { + return err + } + err = binary.Read(buf, binary.LittleEndian, &cV.Major) + if err != nil { + return err + } + err = binary.Read(buf, binary.LittleEndian, &cV.Minor) + if err != nil { + return err + } + err = binary.Read(buf, binary.LittleEndian, &cV.RevMajor) + if err != nil { + return err + } + err = binary.Read(buf, binary.LittleEndian, &cV.RevMinor) + if err != nil { + return err + } + + c.Version = cV + + err = binary.Read(buf, binary.LittleEndian, &c.SpecLevel) + if err != nil { + return err + } + err = binary.Read(buf, binary.LittleEndian, &c.ErrataRev) + if err != nil { + return err + } + err = binary.Read(buf, binary.LittleEndian, &c.TPMVendorID) + if err != nil { + return err + } + var venspecificLen uint16 + err = binary.Read(buf, binary.LittleEndian, &venspecificLen) + if err != nil { + return err + } + venSpecData := make([]byte, venspecificLen) + err = binary.Read(buf, binary.LittleEndian, &venSpecData) + if err != nil { + return err + } + c.VendorSpecific = venSpecData + + return nil + +} + +// PermanentFlags contains persistent TPM properties +type PermanentFlags struct { + Tag uint16 + Disable bool + Ownership bool + Deactivated bool + ReadPubEK bool + DisableOwnerClear bool + AllowMaintenance bool + PhysicalPresenceLifetimeLock bool + PhysicalPresenceHWEnable bool + PhysicalPresenceCMDEnable bool + CEKPUsed bool + TPMPost bool + TPMPostLock bool + FIPS bool + Operator bool + EnableRevokeEK bool + NVLocked bool + ReadSRKPub bool + TPMEstablished bool + MaintenanceDone bool + DisableFullDALogicInfo bool +} + +// nvAttributes implements struct of TPM_NV_ATTRIBUTES +// See: TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011, P.140 +type nvAttributes struct { + Tag uint16 + Attributes Permission +} + +// NVDataPublic implements the structure of TPM_NV_DATA_PUBLIC +// as described in TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011, P. 142 +type NVDataPublic struct { + Tag uint16 + NVIndex uint32 + PCRInfoRead pcrInfoShort + PCRInfoWrite pcrInfoShort + Permission nvAttributes + ReadSTClear bool + WriteSTClear bool + WriteDefine bool + Size uint32 +} + +// CloseKey flushes the key associated with the tpmutil.Handle. +func CloseKey(rw io.ReadWriter, h tpmutil.Handle) error { + return flushSpecific(rw, h, rtKey) +} + +// A Nonce is a 20-byte value. +type Nonce [20]byte + +// An oiapResponse is a response to an OIAP command. +type oiapResponse struct { + AuthHandle tpmutil.Handle + NonceEven Nonce +} + +// String returns a string representation of an oiapResponse. +func (opr oiapResponse) String() string { + return fmt.Sprintf("oiapResponse{AuthHandle: %x, NonceEven: % x}", opr.AuthHandle, opr.NonceEven) +} + +// Close flushes the auth handle associated with an OIAP session. +func (opr *oiapResponse) Close(rw io.ReadWriter) error { + return flushSpecific(rw, opr.AuthHandle, rtAuth) +} + +// An osapCommand is a command sent for OSAP authentication. +type osapCommand struct { + EntityType uint16 + EntityValue tpmutil.Handle + OddOSAP Nonce +} + +// String returns a string representation of an osapCommand. +func (opc osapCommand) String() string { + return fmt.Sprintf("osapCommand{EntityType: %x, EntityValue: %x, OddOSAP: % x}", opc.EntityType, opc.EntityValue, opc.OddOSAP) +} + +// An osapResponse is a TPM reply to an osapCommand. +type osapResponse struct { + AuthHandle tpmutil.Handle + NonceEven Nonce + EvenOSAP Nonce +} + +// String returns a string representation of an osapResponse. +func (opr osapResponse) String() string { + return fmt.Sprintf("osapResponse{AuthHandle: %x, NonceEven: % x, EvenOSAP: % x}", opr.AuthHandle, opr.NonceEven, opr.EvenOSAP) +} + +// Close flushes the AuthHandle associated with an OSAP session. +func (opr *osapResponse) Close(rw io.ReadWriter) error { + return flushSpecific(rw, opr.AuthHandle, rtAuth) +} + +// A Digest is a 20-byte SHA1 value. +type Digest [20]byte + +// An AuthValue is a 20-byte value used for authentication. +type authValue [20]byte + +const authSize uint32 = 20 + +// A sealCommand is the command sent to the TPM to seal data. +type sealCommand struct { + KeyHandle tpmutil.Handle + EncAuth authValue +} + +// String returns a string representation of a sealCommand. +func (sc sealCommand) String() string { + return fmt.Sprintf("sealCommand{KeyHandle: %x, EncAuth: % x}", sc.KeyHandle, sc.EncAuth) +} + +// commandAuth stores the auth information sent with a command. Commands with +// tagRQUAuth1Command tags use one of these auth structures, and commands with +// tagRQUAuth2Command use two. +type commandAuth struct { + AuthHandle tpmutil.Handle + NonceOdd Nonce + ContSession byte + Auth authValue +} + +// String returns a string representation of a sealCommandAuth. +func (ca commandAuth) String() string { + return fmt.Sprintf("commandAuth{AuthHandle: %x, NonceOdd: % x, ContSession: %x, Auth: % x}", ca.AuthHandle, ca.NonceOdd, ca.ContSession, ca.Auth) +} + +// responseAuth contains the auth information returned from a command. +type responseAuth struct { + NonceEven Nonce + ContSession byte + Auth authValue +} + +// String returns a string representation of a responseAuth. +func (ra responseAuth) String() string { + return fmt.Sprintf("responseAuth{NonceEven: % x, ContSession: %x, Auth: % x}", ra.NonceEven, ra.ContSession, ra.Auth) +} + +// These are the parameters of a TPM key. +type keyParams struct { + AlgID Algorithm + EncScheme uint16 + SigScheme uint16 + Params tpmutil.U32Bytes // Serialized rsaKeyParams or symmetricKeyParams. +} + +// An rsaKeyParams encodes the length of the RSA prime in bits, the number of +// primes in its factored form, and the exponent used for public-key +// encryption. +type rsaKeyParams struct { + KeyLength uint32 + NumPrimes uint32 + Exponent tpmutil.U32Bytes +} + +type symmetricKeyParams struct { + KeyLength uint32 + BlockSize uint32 + IV tpmutil.U32Bytes +} + +// A key is a TPM representation of a key. +type key struct { + Version uint32 + KeyUsage uint16 + KeyFlags KeyFlags + AuthDataUsage byte + AlgorithmParams keyParams + PCRInfo tpmutil.U32Bytes + PubKey tpmutil.U32Bytes + EncData tpmutil.U32Bytes +} + +// A key12 is a newer TPM representation of a key. +type key12 struct { + Tag uint16 + Zero uint16 // Always all 0. + KeyUsage uint16 + KeyFlags uint32 + AuthDataUsage byte + AlgorithmParams keyParams + PCRInfo tpmutil.U32Bytes // This must be a serialization of a pcrInfoLong. + PubKey tpmutil.U32Bytes + EncData tpmutil.U32Bytes +} + +// A pubKey represents a public key known to the TPM. +type pubKey struct { + AlgorithmParams keyParams + Key tpmutil.U32Bytes +} + +// A migrationKeyAuth represents the target of a migration. +type migrationKeyAuth struct { + MigrationKey pubKey + MigrationScheme MigrationScheme + Digest Digest +} + +// A symKey is a TPM representation of a symmetric key. +type symKey struct { + AlgID Algorithm + EncScheme uint16 + Key tpmutil.U16Bytes // TPM_SYMMETRIC_KEY uses a 16-bit header for Key data +} + +// A tpmStoredData holds sealed data from the TPM. +type tpmStoredData struct { + Version uint32 + Info tpmutil.U32Bytes + Enc tpmutil.U32Bytes +} + +// String returns a string representation of a tpmStoredData. +func (tsd tpmStoredData) String() string { + return fmt.Sprintf("tpmStoreddata{Version: %x, Info: % x, Enc: % x\n", tsd.Version, tsd.Info, tsd.Enc) +} + +// A quoteInfo structure is the structure signed by the TPM. +type quoteInfo struct { + // The Version must be 0x01010000 + Version uint32 + + // Fixed is always 'QUOT'. + Fixed [4]byte + + // The CompositeDigest is computed by ComputePCRComposite. + CompositeDigest Digest + + // The Nonce is either a random Nonce or the SHA1 hash of data to sign. + Nonce Nonce +} + +// A pcrComposite stores a selection of PCRs with the selected PCR values. +type pcrComposite struct { + Selection pcrSelection + Values tpmutil.U32Bytes +} + +// convertPubKey converts a public key into TPM form. Currently, this function +// only supports 2048-bit RSA keys. +func convertPubKey(pk crypto.PublicKey) (*pubKey, error) { + pkRSA, ok := pk.(*rsa.PublicKey) + if !ok { + return nil, errors.New("the provided Privacy CA public key was not an RSA key") + } + if pkRSA.N.BitLen() != 2048 { + return nil, errors.New("The provided Privacy CA RSA public key was not a 2048-bit key") + } + + rsakp := rsaKeyParams{ + KeyLength: 2048, + NumPrimes: 2, + Exponent: big.NewInt(int64(pkRSA.E)).Bytes(), + } + rsakpb, err := tpmutil.Pack(rsakp) + if err != nil { + return nil, err + } + kp := keyParams{ + AlgID: AlgRSA, + EncScheme: esNone, + SigScheme: ssRSASaPKCS1v15SHA1, + Params: rsakpb, + } + pubKey := pubKey{ + AlgorithmParams: kp, + Key: pkRSA.N.Bytes(), + } + + return &pubKey, nil +} diff --git a/vendor/github.com/google/go-tpm/tpm/testing.md b/vendor/github.com/google/go-tpm/tpm/testing.md new file mode 100644 index 00000000000..3fc91f93008 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/testing.md @@ -0,0 +1,43 @@ +# Testing TPM 1.2 Functionality + +**TODO(https://github.com/google/go-tpm/issues/91):** Support for testing the TPM 1.2 stack against +a simulator is a work in progress. Today, it requires several manual steps. + +## Overview + +As TPM 1.2s are phased out of common developer devices, testing changes to the TPM 1.2 stack is +difficult without special hardware. To support development on the TPM 1.2 stack without special +hardware, a TPM 1.2 simulator or emulator may be used. This document discusses how to use +[IBM's TPM 1.2 simulator](http://ibmswtpm.sourceforge.net) (on a Linux or Mac OS device, Windows is +not yet supported) to run the go-tpm TPM 1.2 tests (in the `go-tpm/tpm/` directory). + +## Downloading, building, and using the IBM TPM 1.2 Simulator + +* Download the latest release of the +[IBM TPM 1.2 Simulator](https://sourceforge.net/projects/ibmswtpm/), unpack the tarball, and `cd` +into it. +* Add `-DTPM_UNIX_DOMAIN_SOCKET` to `tpm/makefile-en-ac`. +* Build the simulator with `make -f tpm/makefile-en-ac` +* Set `TEMP_TPM=/tmp/tpm` or some other suitable temporary location for the TPM state files and Unix + domain socket. +* Start the simulator with `TPM_PATH=${TEMP_TPM} TPM_PORT=${TEMP_TPM}/tpm.sock` + +## Running the TPM 1.2 tests against the IBM TPM 1.2 Simulator + +* Comment out the line `t.Skip()` in `TestTakeOwnership`. This test normally does not work on + physical TPMs, so it is normally disabled. +* Use `TestTakeOwnership` to take ownership of the simulated TPM with `TPM_PATH=${TEMP_TPM}/tpm.sock + go test -v ./tpm/... -run TestTakeOwnership -count=1` +* Run the full test suite with `TPM_PATH=${TEMP_TPM}/tpm.sock go test -v ./tpm/...` + +## Future Improvements + +* Add setup logic to the TPM 1.2 tests to take ownership of an unowned TPM under test. +* Wrap a TPM 1.2 simulator somewhere (possibly in https://github.com/google/go-tpm-tools) and + integrate it into test setup for the TPM 1.2 tests. +* Resolve issues that necessitated the use of `t.Skip()` in current tests. + * Either add an informative comment along with a skip when a test fails for an expected reason, or + remove the test. +* Resolve issues with current tests that fail on the simulator (such as `TestGetAlgs`). +* Automate the use of a simulator in a Continuous Integration environment that is accessible to + GitHub. \ No newline at end of file diff --git a/vendor/github.com/google/go-tpm/tpm/tpm.go b/vendor/github.com/google/go-tpm/tpm/tpm.go new file mode 100644 index 00000000000..fa70a711467 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/tpm.go @@ -0,0 +1,1662 @@ +// Copyright (c) 2014, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tpm supports direct communication with a tpm device under Linux. +package tpm + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "encoding/binary" + "errors" + "fmt" + "io" + + "github.com/google/go-tpm/tpmutil" +) + +// GetKeys gets the list of handles for currently-loaded TPM keys. +func GetKeys(rw io.ReadWriter) ([]tpmutil.Handle, error) { + b, err := getCapability(rw, CapHandle, rtKey) + if err != nil { + return nil, err + } + var handles []tpmutil.Handle + if _, err := tpmutil.Unpack(b, &handles); err != nil { + return nil, err + } + return handles, err +} + +// PcrExtend extends a value into the right PCR by index. +func PcrExtend(rw io.ReadWriter, pcrIndex uint32, pcr pcrValue) ([]byte, error) { + in := []interface{}{pcrIndex, pcr} + var d pcrValue + out := []interface{}{&d} + if _, err := submitTPMRequest(rw, tagRQUCommand, ordExtend, in, out); err != nil { + return nil, err + } + + return d[:], nil +} + +// ReadPCR reads a PCR value from the TPM. +func ReadPCR(rw io.ReadWriter, pcrIndex uint32) ([]byte, error) { + in := []interface{}{pcrIndex} + var v pcrValue + out := []interface{}{&v} + // There's no need to check the ret value here, since the err value contains + // all the necessary information. + if _, err := submitTPMRequest(rw, tagRQUCommand, ordPCRRead, in, out); err != nil { + return nil, err + } + + return v[:], nil +} + +// FetchPCRValues gets a given sequence of PCR values. +func FetchPCRValues(rw io.ReadWriter, pcrVals []int) ([]byte, error) { + var pcrs []byte + for _, v := range pcrVals { + pcr, err := ReadPCR(rw, uint32(v)) + if err != nil { + return nil, err + } + + pcrs = append(pcrs, pcr...) + } + + return pcrs, nil +} + +// GetRandom gets random bytes from the TPM. +func GetRandom(rw io.ReadWriter, size uint32) ([]byte, error) { + var b tpmutil.U32Bytes + in := []interface{}{size} + out := []interface{}{&b} + // There's no need to check the ret value here, since the err value + // contains all the necessary information. + if _, err := submitTPMRequest(rw, tagRQUCommand, ordGetRandom, in, out); err != nil { + return nil, err + } + + return b, nil +} + +// LoadKey2 loads a key blob (a serialized TPM_KEY or TPM_KEY12) into the TPM +// and returns a handle for this key. +func LoadKey2(rw io.ReadWriter, keyBlob []byte, srkAuth []byte) (tpmutil.Handle, error) { + // Deserialize the keyBlob as a key + var k key + if _, err := tpmutil.Unpack(keyBlob, &k); err != nil { + return 0, err + } + + // Run OSAP for the SRK, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. LoadKey2 needs an + // OSAP session for the SRK because the private part of a TPM_KEY or + // TPM_KEY12 is sealed against the SRK. + sharedSecret, osapr, err := newOSAPSession(rw, etSRK, khSRK, srkAuth) + if err != nil { + return 0, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + authIn := []interface{}{ordLoadKey2, k} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return 0, err + } + + handle, ra, ret, err := loadKey2(rw, &k, ca) + if err != nil { + return 0, err + } + + // Check the response authentication. + raIn := []interface{}{ret, ordLoadKey2} + if err := ra.verify(ca.NonceOdd, sharedSecret[:], raIn); err != nil { + return 0, err + } + + return handle, nil +} + +// Quote2 performs a quote operation on the TPM for the given data, +// under the key associated with the handle and for the pcr values +// specified in the call. +func Quote2(rw io.ReadWriter, handle tpmutil.Handle, data []byte, pcrVals []int, addVersion byte, aikAuth []byte) ([]byte, error) { + // Run OSAP for the handle, reading a random OddOSAP for our initial + // command and getting back a secret and a response. + sharedSecret, osapr, err := newOSAPSession(rw, etKeyHandle, handle, aikAuth) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + // Hash the data to get the value to pass to quote2. + hash := sha1.Sum(data) + pcrSel, err := newPCRSelection(pcrVals) + if err != nil { + return nil, err + } + authIn := []interface{}{ordQuote2, hash, pcrSel, addVersion} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + // TODO(tmroeder): use the returned CapVersion. + pcrShort, _, capBytes, sig, ra, ret, err := quote2(rw, handle, hash, pcrSel, addVersion, ca) + if err != nil { + return nil, err + } + + // Check response authentication. + raIn := []interface{}{ret, ordQuote2, pcrShort, tpmutil.U32Bytes(capBytes), tpmutil.U32Bytes(sig)} + if err := ra.verify(ca.NonceOdd, sharedSecret[:], raIn); err != nil { + return nil, err + } + + return sig, nil +} + +// GetPubKey retrieves an opaque blob containing a public key corresponding to +// a handle from the TPM. +func GetPubKey(rw io.ReadWriter, keyHandle tpmutil.Handle, srkAuth []byte) ([]byte, error) { + // Run OSAP for the handle, reading a random OddOSAP for our initial + // command and getting back a secret and a response. + sharedSecret, osapr, err := newOSAPSession(rw, etKeyHandle, keyHandle, srkAuth) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + authIn := []interface{}{ordGetPubKey} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + pk, ra, ret, err := getPubKey(rw, keyHandle, ca) + if err != nil { + return nil, err + } + + // Check response authentication for TPM_GetPubKey. + raIn := []interface{}{ret, ordGetPubKey, pk} + if err := ra.verify(ca.NonceOdd, sharedSecret[:], raIn); err != nil { + return nil, err + } + + b, err := tpmutil.Pack(*pk) + if err != nil { + return nil, err + } + return b, err +} + +// newOSAPSession starts a new OSAP session and derives a shared key from it. +func newOSAPSession(rw io.ReadWriter, entityType uint16, entityValue tpmutil.Handle, srkAuth []byte) ([20]byte, *osapResponse, error) { + osapc := &osapCommand{ + EntityType: entityType, + EntityValue: entityValue, + } + + var sharedSecret [20]byte + if _, err := rand.Read(osapc.OddOSAP[:]); err != nil { + return sharedSecret, nil, err + } + + osapr, err := osap(rw, osapc) + if err != nil { + return sharedSecret, nil, err + } + + // A shared secret is computed as + // + // sharedSecret = HMAC-SHA1(srkAuth, evenosap||oddosap) + // + // where srkAuth is the hash of the SRK authentication (which hash is all 0s + // for the well-known SRK auth value) and even and odd OSAP are the + // values from the OSAP protocol. + osapData, err := tpmutil.Pack(osapr.EvenOSAP, osapc.OddOSAP) + if err != nil { + return sharedSecret, nil, err + } + + hm := hmac.New(sha1.New, srkAuth) + hm.Write(osapData) + // Note that crypto/hash.Sum returns a slice rather than an array, so we + // have to copy this into an array to make sure that serialization doesn't + // prepend a length in tpmutil.Pack(). + sharedSecretBytes := hm.Sum(nil) + copy(sharedSecret[:], sharedSecretBytes) + return sharedSecret, osapr, nil +} + +// newCommandAuth creates a new commandAuth structure over the given +// parameters, using the given secret and the given odd nonce, if provided, +// for the HMAC. If no odd nonce is provided, one is randomly generated. +func newCommandAuth(authHandle tpmutil.Handle, nonceEven Nonce, nonceOdd *Nonce, key []byte, params []interface{}) (*commandAuth, error) { + // Auth = HMAC-SHA1(key, SHA1(params) || NonceEven || NonceOdd || ContSession) + digestBytes, err := tpmutil.Pack(params...) + if err != nil { + return nil, err + } + digest := sha1.Sum(digestBytes) + + // Use the passed-in nonce if non-nil, otherwise generate it now. + var odd Nonce + if nonceOdd != nil { + odd = *nonceOdd + } else { + if _, err := rand.Read(odd[:]); err != nil { + return nil, err + } + } + + ca := &commandAuth{ + AuthHandle: authHandle, + NonceOdd: odd, + } + + authBytes, err := tpmutil.Pack(digest, nonceEven, ca.NonceOdd, ca.ContSession) + if err != nil { + return nil, err + } + + hm2 := hmac.New(sha1.New, key) + hm2.Write(authBytes) + auth := hm2.Sum(nil) + copy(ca.Auth[:], auth[:]) + return ca, nil +} + +// verify checks that the response authentication was correct. +// It computes the SHA1 of params, and computes the HMAC-SHA1 of this digest +// with the authentication parameters of ra along with the given odd nonce. +func (ra *responseAuth) verify(nonceOdd Nonce, key []byte, params []interface{}) error { + // Auth = HMAC-SHA1(key, SHA1(params) || ra.NonceEven || NonceOdd || ra.ContSession) + digestBytes, err := tpmutil.Pack(params...) + if err != nil { + return err + } + + digest := sha1.Sum(digestBytes) + authBytes, err := tpmutil.Pack(digest, ra.NonceEven, nonceOdd, ra.ContSession) + if err != nil { + return err + } + + hm2 := hmac.New(sha1.New, key) + hm2.Write(authBytes) + auth := hm2.Sum(nil) + + if !hmac.Equal(ra.Auth[:], auth) { + return errors.New("the computed response HMAC didn't match the provided HMAC") + } + + return nil +} + +// zeroBytes zeroes a byte array. +func zeroBytes(b []byte) { + for i := range b { + b[i] = 0 + } +} + +func sealHelper(rw io.ReadWriter, pcrInfo *pcrInfoLong, data []byte, srkAuth []byte) ([]byte, error) { + // Run OSAP for the SRK, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. + sharedSecret, osapr, err := newOSAPSession(rw, etSRK, khSRK, srkAuth) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + // EncAuth for a seal command is computed as + // + // encAuth = XOR(srkAuth, SHA1(sharedSecret || )) + // + // In this case, the last even nonce is NonceEven from OSAP. + xorData, err := tpmutil.Pack(sharedSecret, osapr.NonceEven) + if err != nil { + return nil, err + } + defer zeroBytes(xorData) + + encAuthData := sha1.Sum(xorData) + sc := &sealCommand{KeyHandle: khSRK} + for i := range sc.EncAuth { + sc.EncAuth[i] = srkAuth[i] ^ encAuthData[i] + } + + // The digest input for seal authentication is + // + // digest = SHA1(ordSeal || encAuth || binary.Size(pcrInfo) || pcrInfo || + // len(data) || data) + // + authIn := []interface{}{ordSeal, sc.EncAuth, uint32(binary.Size(pcrInfo)), pcrInfo, tpmutil.U32Bytes(data)} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + sealed, ra, ret, err := seal(rw, sc, pcrInfo, data, ca) + if err != nil { + return nil, err + } + + // Check the response authentication. + raIn := []interface{}{ret, ordSeal, sealed} + if err := ra.verify(ca.NonceOdd, sharedSecret[:], raIn); err != nil { + return nil, err + } + + sealedBytes, err := tpmutil.Pack(*sealed) + if err != nil { + return nil, err + } + + return sealedBytes, nil +} + +// Seal encrypts data against a given locality and PCRs and returns the sealed data. +func Seal(rw io.ReadWriter, loc Locality, pcrs []int, data []byte, srkAuth []byte) ([]byte, error) { + pcrInfo, err := newPCRInfoLong(rw, loc, pcrs) + if err != nil { + return nil, err + } + return sealHelper(rw, pcrInfo, data, srkAuth) +} + +// Reseal takes a pre-calculated PCR map and locality in order to seal data +// with a srkAuth. This function is necessary for PCR pre-calculation and later +// sealing to provide a way of updating software which is part of a measured +// boot process. +func Reseal(rw io.ReadWriter, loc Locality, pcrs map[int][]byte, data []byte, srkAuth []byte) ([]byte, error) { + pcrInfo, err := newPCRInfoLongWithHashes(loc, pcrs) + if err != nil { + return nil, err + } + return sealHelper(rw, pcrInfo, data, srkAuth) +} + +// Unseal decrypts data encrypted by the TPM. +func Unseal(rw io.ReadWriter, sealed []byte, srkAuth []byte) ([]byte, error) { + // Run OSAP for the SRK, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. + sharedSecret, osapr, err := newOSAPSession(rw, etSRK, khSRK, srkAuth) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + // The unseal command needs an OIAP session in addition to the OSAP session. + oiapr, err := oiap(rw) + if err != nil { + return nil, err + } + defer oiapr.Close(rw) + + // Convert the sealed value into a tpmStoredData. + var tsd tpmStoredData + if _, err := tpmutil.Unpack(sealed, &tsd); err != nil { + return nil, errors.New("couldn't convert the sealed data into a tpmStoredData struct") + } + + // The digest for auth1 and auth2 for the unseal command is computed as + // digest = SHA1(ordUnseal || tsd) + authIn := []interface{}{ordUnseal, tsd} + + // The first commandAuth uses the shared secret as an HMAC key. + ca1, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + // The second commandAuth is based on OIAP instead of OSAP and uses the + // SRK auth value as an HMAC key instead of the shared secret. + ca2, err := newCommandAuth(oiapr.AuthHandle, oiapr.NonceEven, nil, srkAuth, authIn) + if err != nil { + return nil, err + } + + unsealed, ra1, ra2, ret, err := unseal(rw, khSRK, &tsd, ca1, ca2) + if err != nil { + return nil, err + } + + // Check the response authentication. + raIn := []interface{}{ret, ordUnseal, tpmutil.U32Bytes(unsealed)} + if err := ra1.verify(ca1.NonceOdd, sharedSecret[:], raIn); err != nil { + return nil, err + } + + if err := ra2.verify(ca2.NonceOdd, srkAuth, raIn); err != nil { + return nil, err + } + + return unsealed, nil +} + +// Quote produces a TPM quote for the given data under the given PCRs. It uses +// AIK auth and a given AIK handle. +func Quote(rw io.ReadWriter, handle tpmutil.Handle, data []byte, pcrNums []int, aikAuth []byte) ([]byte, []byte, error) { + // Run OSAP for the handle, reading a random OddOSAP for our initial + // command and getting back a secret and a response. + sharedSecret, osapr, err := newOSAPSession(rw, etKeyHandle, handle, aikAuth) + if err != nil { + return nil, nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + // Hash the data to get the value to pass to quote2. + hash := sha1.Sum(data) + pcrSel, err := newPCRSelection(pcrNums) + if err != nil { + return nil, nil, err + } + authIn := []interface{}{ordQuote, hash, pcrSel} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, nil, err + } + + pcrc, sig, ra, ret, err := quote(rw, handle, hash, pcrSel, ca) + if err != nil { + return nil, nil, err + } + + // Check response authentication. + raIn := []interface{}{ret, ordQuote, pcrc, tpmutil.U32Bytes(sig)} + if err := ra.verify(ca.NonceOdd, sharedSecret[:], raIn); err != nil { + return nil, nil, err + } + + return sig, pcrc.Values, nil +} + +// MakeIdentity creates a new AIK with the given new auth value, and the given +// parameters for the privacy CA that will be used to attest to it. +// If both pk and label are nil, then the TPM_CHOSENID_HASH is set to all 0s as +// a special case. MakeIdentity returns a key blob for the newly-created key. +// The caller must be authorized to use the SRK, since the private part of the +// AIK is sealed against the SRK. +// TODO(tmroeder): currently, this code can only create 2048-bit RSA keys. +func MakeIdentity(rw io.ReadWriter, srkAuth []byte, ownerAuth []byte, aikAuth []byte, pk crypto.PublicKey, label []byte) ([]byte, error) { + // Run OSAP for the SRK, reading a random OddOSAP for our initial command + // and getting back a secret and a handle. + sharedSecretSRK, osaprSRK, err := newOSAPSession(rw, etSRK, khSRK, srkAuth) + if err != nil { + return nil, err + } + defer osaprSRK.Close(rw) + defer zeroBytes(sharedSecretSRK[:]) + + // Run OSAP for the Owner, reading a random OddOSAP for our initial command + // and getting back a secret and a handle. + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownerAuth) + if err != nil { + return nil, err + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + + // EncAuth for a MakeIdentity command is computed as + // + // encAuth = XOR(aikAuth, SHA1(sharedSecretOwn || )) + // + // In this case, the last even nonce is NonceEven from OSAP for the Owner. + xorData, err := tpmutil.Pack(sharedSecretOwn, osaprOwn.NonceEven) + if err != nil { + return nil, err + } + defer zeroBytes(xorData) + + encAuthData := sha1.Sum(xorData) + var encAuth Digest + for i := range encAuth { + encAuth[i] = aikAuth[i] ^ encAuthData[i] + } + + var caDigest Digest + if (pk != nil) != (label != nil) { + return nil, errors.New("inconsistent null values between the pk and the label") + } + + if pk != nil { + pubKey, err := convertPubKey(pk) + if err != nil { + return nil, err + } + + // We can't pack the pair of values directly, since the label is + // included directly as bytes, without any length. + fullpkb, err := tpmutil.Pack(pubKey) + if err != nil { + return nil, err + } + + caDigestBytes := append(label, fullpkb...) + caDigest = sha1.Sum(caDigestBytes) + } + + rsaAIKParams := rsaKeyParams{ + KeyLength: 2048, + NumPrimes: 2, + //Exponent: big.NewInt(0x10001).Bytes(), // 65537. Implicit? + } + packedParams, err := tpmutil.Pack(rsaAIKParams) + if err != nil { + return nil, err + } + + aikParams := keyParams{ + AlgID: AlgRSA, + EncScheme: esNone, + SigScheme: ssRSASaPKCS1v15SHA1, + Params: packedParams, + } + + aik := &key{ + Version: 0x01010000, + KeyUsage: keyIdentity, + KeyFlags: 0, + AuthDataUsage: authAlways, + AlgorithmParams: aikParams, + } + + // The digest input for MakeIdentity authentication is + // + // digest = SHA1(ordMakeIdentity || encAuth || caDigest || aik) + // + authIn := []interface{}{ordMakeIdentity, encAuth, caDigest, aik} + ca1, err := newCommandAuth(osaprSRK.AuthHandle, osaprSRK.NonceEven, nil, sharedSecretSRK[:], authIn) + if err != nil { + return nil, err + } + + ca2, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) + if err != nil { + return nil, err + } + + k, sig, ra1, ra2, ret, err := makeIdentity(rw, encAuth, caDigest, aik, ca1, ca2) + if err != nil { + return nil, err + } + + // Check response authentication. + raIn := []interface{}{ret, ordMakeIdentity, k, tpmutil.U32Bytes(sig)} + if err := ra1.verify(ca1.NonceOdd, sharedSecretSRK[:], raIn); err != nil { + return nil, err + } + + if err := ra2.verify(ca2.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return nil, err + } + + // TODO(tmroeder): check the signature against the pubEK. + blob, err := tpmutil.Pack(k) + if err != nil { + return nil, err + } + + return blob, nil +} + +func unloadTrspiCred(blob []byte) ([]byte, error) { + /* + * Trousers expects the asym blob to have an additional data in the header. + * The relevant data is duplicated in the TPM_SYMMETRIC_KEY struct so we parse + * and throw the header away. + * TODO(dkarch): Trousers is not doing credential activation correctly. We should + * remove this and instead expose the asymmetric decryption and symmetric decryption + * so that anyone generating a challenge for Trousers can unload the header themselves + * and send us a correctly formatted challenge. + */ + + var header struct { + Credsize uint32 + AlgID uint32 + EncScheme uint16 + SigScheme uint16 + Parmsize uint32 + } + + symbuf := bytes.NewReader(blob) + if err := binary.Read(symbuf, binary.BigEndian, &header); err != nil { + return nil, err + } + // Unload the symmetric key parameters. + parms := make([]byte, header.Parmsize) + if err := binary.Read(symbuf, binary.BigEndian, parms); err != nil { + return nil, err + } + // Unload and return the symmetrically encrypted secret. + cred := make([]byte, header.Credsize) + if err := binary.Read(symbuf, binary.BigEndian, cred); err != nil { + return nil, err + } + return cred, nil +} + +// ActivateIdentity asks the TPM to decrypt an EKPub encrypted symmetric session key +// which it uses to decrypt the symmetrically encrypted secret. +func ActivateIdentity(rw io.ReadWriter, aikAuth []byte, ownerAuth []byte, aik tpmutil.Handle, asym, sym []byte) ([]byte, error) { + // Run OIAP for the AIK. + oiaprAIK, err := oiap(rw) + if err != nil { + return nil, fmt.Errorf("failed to start OIAP session: %v", err) + } + + // Run OSAP for the owner, reading a random OddOSAP for our initial command + // and getting back a secret and a handle. + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownerAuth) + if err != nil { + return nil, fmt.Errorf("failed to start OSAP session: %v", err) + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + + authIn := []interface{}{ordActivateIdentity, tpmutil.U32Bytes(asym)} + ca1, err := newCommandAuth(oiaprAIK.AuthHandle, oiaprAIK.NonceEven, nil, aikAuth, authIn) + if err != nil { + return nil, fmt.Errorf("newCommandAuth failed: %v", err) + } + ca2, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) + if err != nil { + return nil, fmt.Errorf("newCommandAuth failed: %v", err) + } + + symkey, ra1, ra2, ret, err := activateIdentity(rw, aik, asym, ca1, ca2) + if err != nil { + return nil, fmt.Errorf("activateIdentity failed: %v", err) + } + + // Check response authentication. + raIn := []interface{}{ret, ordActivateIdentity, symkey} + if err := ra1.verify(ca1.NonceOdd, aikAuth, raIn); err != nil { + return nil, fmt.Errorf("aik resAuth failed to verify: %v", err) + } + + if err := ra2.verify(ca2.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return nil, fmt.Errorf("owner resAuth failed to verify: %v", err) + } + + cred, err := unloadTrspiCred(sym) + if err != nil { + return nil, fmt.Errorf("unloadTrspiCred failed: %v", err) + } + var ( + block cipher.Block + iv []byte + ciphertxt []byte + secret []byte + ) + switch id := symkey.AlgID; id { + case AlgAES128: + block, err = aes.NewCipher(symkey.Key) + if err != nil { + return nil, fmt.Errorf("aes.NewCipher failed: %v", err) + } + iv = cred[:aes.BlockSize] + ciphertxt = cred[aes.BlockSize:] + secret = ciphertxt + default: + return nil, fmt.Errorf("%v is not a supported session key algorithm", id) + } + switch es := symkey.EncScheme; es { + case esSymCTR: + stream := cipher.NewCTR(block, iv) + stream.XORKeyStream(secret, ciphertxt) + case esSymOFB: + stream := cipher.NewOFB(block, iv) + stream.XORKeyStream(secret, ciphertxt) + case esSymCBCPKCS5: + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(secret, ciphertxt) + // Remove PKCS5 padding. + padlen := int(secret[len(secret)-1]) + secret = secret[:len(secret)-padlen] + default: + return nil, fmt.Errorf("%v is not a supported encryption scheme", es) + } + + return secret, nil +} + +// ResetLockValue resets the dictionary-attack value in the TPM; this allows the +// TPM to start working again after authentication errors without waiting for +// the dictionary-attack defenses to time out. This requires owner +// authentication. +func ResetLockValue(rw io.ReadWriter, ownerAuth Digest) error { + // Run OSAP for the Owner, reading a random OddOSAP for our initial command + // and getting back a secret and a handle. + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownerAuth[:]) + if err != nil { + return err + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + + // The digest input for ResetLockValue auth is + // + // digest = SHA1(ordResetLockValue) + // + authIn := []interface{}{ordResetLockValue} + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) + if err != nil { + return err + } + + ra, ret, err := resetLockValue(rw, ca) + if err != nil { + return err + } + + // Check response authentication. + raIn := []interface{}{ret, ordResetLockValue} + if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return err + } + + return nil +} + +// ownerReadInternalHelper sets up command auth and checks response auth for +// OwnerReadInternalPub. It's not exported because OwnerReadInternalPub only +// supports two fixed key handles: khEK and khSRK. +func ownerReadInternalHelper(rw io.ReadWriter, kh tpmutil.Handle, ownerAuth Digest) (*pubKey, error) { + // Run OSAP for the Owner, reading a random OddOSAP for our initial command + // and getting back a secret and a handle. + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownerAuth[:]) + if err != nil { + return nil, err + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + + // The digest input for OwnerReadInternalPub is + // + // digest = SHA1(ordOwnerReadInternalPub || kh) + // + authIn := []interface{}{ordOwnerReadInternalPub, kh} + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) + if err != nil { + return nil, err + } + + pk, ra, ret, err := ownerReadInternalPub(rw, kh, ca) + if err != nil { + return nil, err + } + + // Check response authentication. + raIn := []interface{}{ret, ordOwnerReadInternalPub, pk} + if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return nil, err + } + + return pk, nil +} + +// OwnerReadSRK uses owner auth to get a blob representing the SRK. +func OwnerReadSRK(rw io.ReadWriter, ownerAuth Digest) ([]byte, error) { + pk, err := ownerReadInternalHelper(rw, khSRK, ownerAuth) + if err != nil { + return nil, err + } + + return tpmutil.Pack(pk) +} + +// ReadEKCert reads the EKCert from the NVRAM. +// The TCG PC Client specifies additional headers that are to be stored with the EKCert, we parse them +// here and return only the DER encoded certificate. +// TCG PC Client Specific Implementation Specification for Conventional BIOS 7.4.4 +// https://www.trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientImplementation_1-21_1_00.pdf +func ReadEKCert(rw io.ReadWriter, ownAuth Digest) ([]byte, error) { + const ( + certIndex = 0x1000f000 // TPM_NV_INDEX_EKCert (TPM Main Part 2 TPM Structures 19.1.2) + certTagPCClientStoredCert = 0x1001 // TCG_TAG_PCCLIENT_STORED_CERT + certTagPCClientFullCert = 0x1002 // TCG_TAG_PCCLIENT_FULL_CERT + tcgFullCert = 0 // TCG_FULL_CERT + tcgPartialSmallCert = 1 // TCG_PARTIAL_SMALL_CERT + ) + offset := uint32(0) + var header struct { + Tag uint16 + CertType uint8 + CertSize uint16 + } + + data, err := NVReadValue(rw, certIndex, offset, uint32(binary.Size(header)), []byte(ownAuth[:])) + if err != nil { + return nil, err + } + offset = offset + uint32(binary.Size(header)) + buff := bytes.NewReader(data) + + if err := binary.Read(buff, binary.BigEndian, &header); err != nil { + return nil, err + } + + if header.Tag != certTagPCClientStoredCert { + return nil, fmt.Errorf("invalid certificate") + } + + var bufSize uint32 + switch header.CertType { + case tcgFullCert: + var tag uint16 + data, err := NVReadValue(rw, certIndex, offset, uint32(binary.Size(tag)), []byte(ownAuth[:])) + if err != nil { + return nil, err + } + bufSize = uint32(header.CertSize) + offset + offset = offset + uint32(binary.Size(tag)) + buff = bytes.NewReader(data) + + if err := binary.Read(buff, binary.BigEndian, &tag); err != nil { + return nil, err + } + + if tag != certTagPCClientFullCert { + return nil, fmt.Errorf("certificate type and tag do not match") + } + case tcgPartialSmallCert: + return nil, fmt.Errorf("certType is not TCG_FULL_CERT: currently do not support partial certs") + default: + return nil, fmt.Errorf("invalid certType: 0x%x", header.CertType) + } + + var ekbuf []byte + for offset < bufSize { + length := bufSize - offset + // TPMs can only read so much memory per command so we read in 128byte chunks. + // 128 was taken from go-tspi. The actual max read seems to be platform dependent + // but cannot be queried on TPM1.2 (and does not seem to appear in any documentation). + if length > 128 { + length = 128 + } + data, err = NVReadValue(rw, certIndex, offset, length, []byte(ownAuth[:])) + if err != nil { + return nil, err + } + + ekbuf = append(ekbuf, data...) + offset += length + } + + return ekbuf, nil +} + +// NVDefineSpace implements the reservation of NVRAM as specified in: +// TPM-Main-Part-3-Commands_v1.2_rev116_01032011, P. 212 +func NVDefineSpace(rw io.ReadWriter, nvData NVDataPublic, ownAuth []byte) error { + var ra *responseAuth + var ret uint32 + if ownAuth == nil { + } else { + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownAuth[:]) + if err != nil { + return fmt.Errorf("failed to start new auth session: %v", err) + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + + // encAuth: NV_Define_Space is a special case where no encryption is used. + // See spec: TPM-Main-Part-1-Design-Principles_v1.2_rev116_01032011, P. 81 + xorData, err := tpmutil.Pack(sharedSecretOwn, osaprOwn.NonceEven) + if err != nil { + return err + } + defer zeroBytes(xorData) + + encAuthData := sha1.Sum(xorData) + + authIn := []interface{}{ordNVDefineSpace, nvData, encAuthData} + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) + if err != nil { + return err + } + ra, ret, err = nvDefineSpace(rw, nvData, encAuthData, ca) + if err != nil { + return fmt.Errorf("failed to define space in NVRAM: %v", err) + } + raIn := []interface{}{ret, ordNVDefineSpace} + if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return fmt.Errorf("failed to verify authenticity of response: %v", err) + } + } + return nil +} + +// NVReadValue returns the value from a given index, offset, and length in NVRAM. +// See TPM-Main-Part-2-TPM-Structures 19.1. +// If TPM isn't locked, no authentication is needed. +// This is for platform suppliers only. +// See TPM-Main-Part-3-Commands-20.4 +func NVReadValue(rw io.ReadWriter, index, offset, len uint32, ownAuth []byte) ([]byte, error) { + if ownAuth == nil { + data, _, _, err := nvReadValue(rw, index, offset, len, nil) + if err != nil { + return nil, fmt.Errorf("failed to read from NVRAM: %v", err) + } + return data, nil + } + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownAuth[:]) + if err != nil { + return nil, fmt.Errorf("failed to start new auth session: %v", err) + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + authIn := []interface{}{ordNVReadValue, index, offset, len} + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) + if err != nil { + return nil, fmt.Errorf("failed to construct owner auth fields: %v", err) + } + data, ra, ret, err := nvReadValue(rw, index, offset, len, ca) + if err != nil { + return nil, fmt.Errorf("failed to read from NVRAM: %v", err) + } + raIn := []interface{}{ret, ordNVReadValue, tpmutil.U32Bytes(data)} + if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return nil, fmt.Errorf("failed to verify authenticity of response: %v", err) + } + + return data, nil +} + +// NVReadValueAuth returns the value from a given index, offset, and length in NVRAM. +// See TPM-Main-Part-2-TPM-Structures 19.1. +// If TPM is locked, authentication is mandatory. +// See TPM-Main-Part-3-Commands-20.5 +func NVReadValueAuth(rw io.ReadWriter, index, offset, len uint32, auth []byte) ([]byte, error) { + if auth == nil { + return nil, fmt.Errorf("no auth value given but mandatory") + } + sharedSecret, osapr, err := newOSAPSession(rw, etOwner, khOwner, auth[:]) + if err != nil { + return nil, fmt.Errorf("failed to start new auth session: %v", err) + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + authIn := []interface{}{ordNVReadValueAuth, index, offset, len} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, fmt.Errorf("failed to construct auth fields: %v", err) + } + data, ra, ret, err := nvReadValue(rw, index, offset, len, ca) + if err != nil { + return nil, fmt.Errorf("failed to read from NVRAM: %v", err) + } + raIn := []interface{}{ret, ordNVReadValueAuth, tpmutil.U32Bytes(data)} + if err := ra.verify(ca.NonceOdd, sharedSecret[:], raIn); err != nil { + return nil, fmt.Errorf("failed to verify authenticity of response: %v", err) + } + + return data, nil +} + +// NVWriteValue for writing to the NVRAM. Needs a index for a defined space in NVRAM. +// See TPM-Main-Part-3-Commands_v1.2_rev116_01032011, P216 +func NVWriteValue(rw io.ReadWriter, index, offset uint32, data []byte, ownAuth []byte) error { + if ownAuth == nil { + if _, _, _, err := nvWriteValue(rw, index, offset, uint32(len(data)), data, nil); err != nil { + return fmt.Errorf("failed to write to NVRAM: %v", err) + } + return nil + } + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownAuth[:]) + if err != nil { + return fmt.Errorf("failed to start new auth session: %v", err) + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + authIn := []interface{}{ordNVWriteValue, index, offset, len(data), data} + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) + if err != nil { + return fmt.Errorf("failed to construct owner auth fields: %v", err) + } + data, ra, ret, err := nvWriteValue(rw, index, offset, uint32(len(data)), data, ca) + if err != nil { + return fmt.Errorf("failed to write to NVRAM: %v", err) + } + raIn := []interface{}{ret, ordNVWriteValue, tpmutil.U32Bytes(data)} + if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return fmt.Errorf("failed to verify authenticity of response: %v", err) + } + return nil +} + +// NVWriteValueAuth for authenticated writing to the NVRAM. +// Needs a index of a defined space in NVRAM. +// See TPM-Main-Part-2-TPM-Structures 19.1. +// If TPM is locked, authentification is mandatory. +// See TPM-Main-Part-3-Commands_v1.2_rev116_01032011, P216 +func NVWriteValueAuth(rw io.ReadWriter, index, offset uint32, data []byte, auth []byte) error { + if auth == nil { + return fmt.Errorf("no auth value given but mandatory") + } + sharedSecret, osapr, err := newOSAPSession(rw, etOwner, khOwner, auth[:]) + if err != nil { + return fmt.Errorf("failed to start new auth session: %v", err) + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + authIn := []interface{}{ordNVWriteValueAuth, index, offset, len(data), data} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return fmt.Errorf("failed to construct auth fields: %v", err) + } + data, ra, ret, err := nvWriteValue(rw, index, offset, uint32(len(data)), data, ca) + if err != nil { + return fmt.Errorf("failed to write to NVRAM: %v", err) + } + raIn := []interface{}{ret, ordNVWriteValueAuth, tpmutil.U32Bytes(data)} + if err := ra.verify(ca.NonceOdd, sharedSecret[:], raIn); err != nil { + return fmt.Errorf("failed to verify authenticity of response: %v", err) + } + return nil +} + +// OwnerReadPubEK uses owner auth to get a blob representing the public part of the +// endorsement key. +func OwnerReadPubEK(rw io.ReadWriter, ownerAuth Digest) ([]byte, error) { + pk, err := ownerReadInternalHelper(rw, khEK, ownerAuth) + if err != nil { + return nil, err + } + + return tpmutil.Pack(pk) +} + +// ReadPubEK reads the public part of the endorsement key when no owner is +// established. +func ReadPubEK(rw io.ReadWriter) ([]byte, error) { + var n Nonce + if _, err := rand.Read(n[:]); err != nil { + return nil, err + } + + pk, d, _, err := readPubEK(rw, n) + if err != nil { + return nil, err + } + + // Recompute the hash of the pk and the nonce to defend against replay + // attacks. + b, err := tpmutil.Pack(pk, n) + if err != nil { + return nil, err + } + + s := sha1.Sum(b) + // There's no need for constant-time comparison of these hash values, + // since no secret is involved. + if !bytes.Equal(s[:], d[:]) { + return nil, errors.New("the ReadPubEK operation failed the replay check") + } + + return tpmutil.Pack(pk) +} + +// GetManufacturer returns the manufacturer ID +func GetManufacturer(rw io.ReadWriter) ([]byte, error) { + return getCapability(rw, CapProperty, SubCapPropManufacturer) +} + +// GetPermanentFlags returns the TPM_PERMANENT_FLAGS structure. +func GetPermanentFlags(rw io.ReadWriter) (PermanentFlags, error) { + var ret PermanentFlags + + raw, err := getCapability(rw, CapFlag, SubCapFlagPermanent) + if err != nil { + return ret, err + } + + _, err = tpmutil.Unpack(raw, &ret) + return ret, err +} + +// GetAlgs returns a list of algorithms supported by the TPM device. +func GetAlgs(rw io.ReadWriter) ([]Algorithm, error) { + var algs []Algorithm + for i := AlgRSA; i <= AlgXOR; i++ { + buf, err := getCapability(rw, CapAlg, uint32(i)) + if err != nil { + return nil, err + } + if uint8(buf[0]) > 0 { + algs = append(algs, Algorithm(i)) + } + + } + return algs, nil +} + +// GetCapVersionVal returns the decoded contents of TPM_CAP_VERSION_INFO. +func GetCapVersionVal(rw io.ReadWriter) (*CapVersionInfo, error) { + var capVer CapVersionInfo + buf, err := getCapability(rw, CapVersion, 0) + if err != nil { + return nil, err + } + if err := capVer.Decode(buf); err != nil { + return nil, err + } + return &capVer, nil +} + +// GetNVList returns a list of TPM_NV_INDEX values that +// are currently allocated NV storage through TPM_NV_DefineSpace. +func GetNVList(rw io.ReadWriter) ([]uint32, error) { + buf, err := getCapability(rw, CapNVList, 0) + if err != nil { + return nil, err + } + nvList := make([]uint32, len(buf)/4) + for i := range nvList { + nvList[i] = uint32(binary.BigEndian.Uint32(buf[i*4 : (i+1)*4])) + } + + return nvList, err +} + +// GetNVIndex returns the structure of NVDataPublic which contains +// information about the requested NV Index. +// See: TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011, P.167 +func GetNVIndex(rw io.ReadWriter, nvIndex uint32) (*NVDataPublic, error) { + var nvInfo NVDataPublic + buf, _ := getCapability(rw, CapNVIndex, nvIndex) + if _, err := tpmutil.Unpack(buf, &nvInfo); err != nil { + return &nvInfo, err + } + return &nvInfo, nil +} + +// GetCapabilityRaw reads the requested capability and sub-capability from the +// TPM and returns it as a []byte. Where possible, prefer the convenience +// functions above, which return higher-level structs for easier handling. +func GetCapabilityRaw(rw io.ReadWriter, cap, subcap uint32) ([]byte, error) { + return getCapability(rw, cap, subcap) +} + +// OwnerClear uses owner auth to clear the TPM. After this operation, the TPM +// can change ownership. +func OwnerClear(rw io.ReadWriter, ownerAuth Digest) error { + // Run OSAP for the Owner, reading a random OddOSAP for our initial command + // and getting back a secret and a handle. + sharedSecretOwn, osaprOwn, err := newOSAPSession(rw, etOwner, khOwner, ownerAuth[:]) + if err != nil { + return err + } + defer osaprOwn.Close(rw) + defer zeroBytes(sharedSecretOwn[:]) + + // The digest input for OwnerClear is + // + // digest = SHA1(ordOwnerClear) + // + authIn := []interface{}{ordOwnerClear} + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) + if err != nil { + return err + } + + ra, ret, err := ownerClear(rw, ca) + if err != nil { + return err + } + + // Check response authentication. + raIn := []interface{}{ret, ordOwnerClear} + if err := ra.verify(ca.NonceOdd, sharedSecretOwn[:], raIn); err != nil { + return err + } + + return nil +} + +// TakeOwnership takes over a TPM and inserts a new owner auth value and +// generates a new SRK, associating it with a new SRK auth value. This +// operation can only be performed if there isn't already an owner for the TPM. +// The pub EK blob can be acquired by calling ReadPubEK if there is no owner, or +// OwnerReadPubEK if there is. +func TakeOwnership(rw io.ReadWriter, newOwnerAuth Digest, newSRKAuth Digest, pubEK []byte) error { + + // Encrypt the owner and SRK auth with the endorsement key. + ek, err := UnmarshalPubRSAPublicKey(pubEK) + if err != nil { + return err + } + encOwnerAuth, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, ek, newOwnerAuth[:], oaepLabel) + if err != nil { + return err + } + encSRKAuth, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, ek, newSRKAuth[:], oaepLabel) + if err != nil { + return err + } + + // The params for the SRK have very tight requirements: + // - KeyLength must be 2048 + // - alg must be RSA + // - Enc must be OAEP SHA1 MGF1 + // - Sig must be None + // - Key usage must be Storage + // - Key must not be migratable + srkRSAParams := rsaKeyParams{ + KeyLength: 2048, + NumPrimes: 2, + } + srkpb, err := tpmutil.Pack(srkRSAParams) + if err != nil { + return err + } + srkParams := keyParams{ + AlgID: AlgRSA, + EncScheme: esRSAEsOAEPSHA1MGF1, + SigScheme: ssNone, + Params: srkpb, + } + srk := &key{ + Version: 0x01010000, + KeyUsage: keyStorage, + KeyFlags: 0, + AuthDataUsage: authAlways, + AlgorithmParams: srkParams, + } + + // Get command auth using OIAP with the new owner auth. + oiapr, err := oiap(rw) + if err != nil { + return err + } + defer oiapr.Close(rw) + + // The digest for TakeOwnership is + // + // SHA1(ordTakeOwnership || pidOwner || encOwnerAuth || encSRKAuth || srk) + authIn := []interface{}{ordTakeOwnership, pidOwner, tpmutil.U32Bytes(encOwnerAuth), tpmutil.U32Bytes(encSRKAuth), srk} + ca, err := newCommandAuth(oiapr.AuthHandle, oiapr.NonceEven, nil, newOwnerAuth[:], authIn) + if err != nil { + return err + } + + k, ra, ret, err := takeOwnership(rw, encOwnerAuth, encSRKAuth, srk, ca) + if err != nil { + return err + } + + raIn := []interface{}{ret, ordTakeOwnership, k} + return ra.verify(ca.NonceOdd, newOwnerAuth[:], raIn) +} + +func createWrapKeyHelper(rw io.ReadWriter, srkAuth []byte, keyFlags KeyFlags, usageAuth Digest, migrationAuth Digest, pcrs []int) (*key, error) { + // Run OSAP for the SRK, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. + sharedSecret, osapr, err := newOSAPSession(rw, etSRK, khSRK, srkAuth) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + xorData, err := tpmutil.Pack(sharedSecret, osapr.NonceEven) + if err != nil { + return nil, err + } + defer zeroBytes(xorData) + + // We have to come up with NonceOdd early to encrypt the migration auth. + var nonceOdd Nonce + if _, err := rand.Read(nonceOdd[:]); err != nil { + return nil, err + } + + // ADIP (Authorization Data Insertion Protocol) is based on NonceEven for the first auth value + // encrypted by the protocol, and NonceOdd for the second auth value. This is so that the two + // keystreams are independent - otherwise, an eavesdropping attacker could XOR the two encrypted + // values together to cancel out the key and calculate (usageAuth ^ migrationAuth). + xorData2, err := tpmutil.Pack(sharedSecret, nonceOdd) + if err != nil { + return nil, err + } + defer zeroBytes(xorData2) + + encAuthDataKey := sha1.Sum(xorData) + defer zeroBytes(encAuthDataKey[:]) + encAuthDataKey2 := sha1.Sum(xorData2) + defer zeroBytes(encAuthDataKey2[:]) + + var encUsageAuth Digest + for i := range usageAuth { + encUsageAuth[i] = encAuthDataKey[i] ^ usageAuth[i] + } + var encMigrationAuth Digest + for i := range migrationAuth { + encMigrationAuth[i] = encAuthDataKey2[i] ^ migrationAuth[i] + } + + rParams := rsaKeyParams{ + KeyLength: 2048, + NumPrimes: 2, + } + rParamsPacked, err := tpmutil.Pack(&rParams) + if err != nil { + return nil, err + } + + var pcrInfoBytes []byte + if len(pcrs) > 0 { + pcrInfo, err := newPCRInfo(rw, pcrs) + if err != nil { + return nil, err + } + pcrInfoBytes, err = tpmutil.Pack(pcrInfo) + if err != nil { + return nil, err + } + } + + keyInfo := &key{ + Version: 0x01010000, + KeyUsage: keySigning, + KeyFlags: keyFlags, + AuthDataUsage: authAlways, + AlgorithmParams: keyParams{ + AlgID: AlgRSA, + EncScheme: esNone, + SigScheme: ssRSASaPKCS1v15DER, + Params: rParamsPacked, + }, + PCRInfo: pcrInfoBytes, + } + + authIn := []interface{}{ordCreateWrapKey, encUsageAuth, encMigrationAuth, keyInfo} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, &nonceOdd, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + k, ra, ret, err := createWrapKey(rw, encUsageAuth, encMigrationAuth, keyInfo, ca) + if err != nil { + return nil, err + } + + raIn := []interface{}{ret, ordCreateWrapKey, k} + if err = ra.verify(ca.NonceOdd, sharedSecret[:], raIn); err != nil { + return nil, err + } + + return k, nil +} + +// CreateWrapKey creates a new RSA key for signatures inside the TPM. It is +// wrapped by the SRK (which is to say, the SRK is the parent key). The key can +// be bound to the specified PCR numbers so that it can only be used for +// signing if the PCR values of those registers match. The pcrs parameter can +// be nil in which case the key is not bound to any PCRs. The usageAuth +// parameter defines the auth key for using this new key. The migrationAuth +// parameter would be used for authorizing migration of the key (although this +// code currently disables migration). +func CreateWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migrationAuth Digest, pcrs []int) ([]byte, error) { + k, err := createWrapKeyHelper(rw, srkAuth, 0, usageAuth, migrationAuth, pcrs) + if err != nil { + return nil, err + } + keyblob, err := tpmutil.Pack(k) + if err != nil { + return nil, err + } + return keyblob, nil +} + +// CreateMigratableWrapKey creates a new RSA key as in CreateWrapKey, but the +// key is migratable (with the given migration auth). +// Returns the loadable KeyBlob as well as just the encrypted private part, for +// migration. +func CreateMigratableWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migrationAuth Digest, pcrs []int) ([]byte, []byte, error) { + k, err := createWrapKeyHelper(rw, srkAuth, keyMigratable, usageAuth, migrationAuth, pcrs) + if err != nil { + return nil, nil, err + } + keyblob, err := tpmutil.Pack(k) + if err != nil { + return nil, nil, err + } + return keyblob, k.EncData, nil +} + +// AuthorizeMigrationKey authorizes a given public key for use in migrating +// migratable keys. The scheme is REWRAP. +func AuthorizeMigrationKey(rw io.ReadWriter, ownerAuth Digest, migrationKey crypto.PublicKey) ([]byte, error) { + // Run OSAP for the OwnerAuth, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. + sharedSecret, osapr, err := newOSAPSession(rw, etOwner, khOwner, ownerAuth[:]) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + var pub *pubKey + if migrationKey != nil { + pub, err = convertPubKey(migrationKey) + if err != nil { + return nil, err + } + // convertPubKey is designed for signing keys. + pub.AlgorithmParams.EncScheme = esRSAEsOAEPSHA1MGF1 + pub.AlgorithmParams.SigScheme = ssNone + rsaParams := rsaKeyParams{ + KeyLength: 2048, + NumPrimes: 2, + //Exponent: default (omit) + } + pub.AlgorithmParams.Params, err = tpmutil.Pack(rsaParams) + if err != nil { + return nil, err + } + } + + scheme := msRewrap + + // The digest for auth for the authorizeMigrationKey command is computed as + // SHA1(ordAuthorizeMigrationkey || migrationScheme || migrationKey) + authIn := []interface{}{ordAuthorizeMigrationKey, scheme, pub} + + // The commandAuth uses the shared secret as an HMAC key. + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + migrationAuth, _, _, err := authorizeMigrationKey(rw, scheme, *pub, ca) + if err != nil { + return nil, err + } + + // For now, ignore the response authentication. + return migrationAuth, nil +} + +// CreateMigrationBlob performs a Rewrap migration of the given key blob. +func CreateMigrationBlob(rw io.ReadWriter, srkAuth Digest, migrationAuth Digest, keyBlob []byte, migrationKeyBlob []byte) ([]byte, error) { + // Run OSAP for the SRK, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. + sharedSecret, osapr, err := newOSAPSession(rw, etSRK, khSRK, srkAuth[:]) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + // The createMigrationBlob command needs an OIAP session in addition to the + // OSAP session. + oiapr, err := oiap(rw) + if err != nil { + return nil, err + } + defer oiapr.Close(rw) + + encData := tpmutil.U32Bytes(keyBlob) + + // The digest for auth1 and auth2 for the createMigrationBlob command is + // SHA1(ordCreateMigrationBlob || migrationScheme || migrationKeyBlob || encData) + authIn := []interface{}{ordCreateMigrationBlob, msRewrap, migrationKeyBlob, encData} + + // The first commandAuth uses the shared secret as an HMAC key. + ca1, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + // The second commandAuth is based on OIAP instead of OSAP and uses the + // migration auth as the HMAC key. + ca2, err := newCommandAuth(oiapr.AuthHandle, oiapr.NonceEven, nil, migrationAuth[:], authIn) + if err != nil { + return nil, err + } + + _, outData, _, _, _, err := createMigrationBlob(rw, khSRK, msRewrap, migrationKeyBlob, encData, ca1, ca2) + if err != nil { + return nil, err + } + + // For now, ignore the response authenticatino. + return outData, nil +} + +// https://golang.org/src/crypto/rsa/pkcs1v15.go?s=8762:8862#L204 +var hashPrefixes = map[crypto.Hash][]byte{ + crypto.MD5: {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10}, + crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}, + crypto.SHA224: {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c}, + crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}, + crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}, + crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}, + crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix. + crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14}, +} + +// Sign will sign a digest using the supplied key handle. Uses PKCS1v15 signing, which means the hash OID is prefixed to the +// hash before it is signed. Therefore the hash used needs to be passed as the hash parameter to determine the right +// prefix. +func Sign(rw io.ReadWriter, keyAuth []byte, keyHandle tpmutil.Handle, hash crypto.Hash, hashed []byte) ([]byte, error) { + prefix, ok := hashPrefixes[hash] + if !ok { + return nil, errors.New("Unsupported hash") + } + data := append(prefix, hashed...) + + // Run OSAP for the SRK, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. + sharedSecret, osapr, err := newOSAPSession(rw, etKeyHandle, keyHandle, keyAuth) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + authIn := []interface{}{ordSign, tpmutil.U32Bytes(data)} + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + signature, ra, ret, err := sign(rw, keyHandle, data, ca) + if err != nil { + return nil, err + } + + raIn := []interface{}{ret, ordSign, tpmutil.U32Bytes(signature)} + err = ra.verify(ca.NonceOdd, sharedSecret[:], raIn) + if err != nil { + return nil, err + } + + return signature, nil +} + +// PcrReset resets the given PCRs. Given typical locality restrictions, this can usually only be 16 or 23. +func PcrReset(rw io.ReadWriter, pcrs []int) error { + pcrSelect, err := newPCRSelection(pcrs) + if err != nil { + return err + } + err = pcrReset(rw, pcrSelect) + if err != nil { + return err + } + return nil +} + +// ForceClear is normally used by firmware but on some platforms +// vendors got it wrong and didn't call TPM_DisableForceClear. +// It removes forcefully the ownership of the TPM. +func ForceClear(rw io.ReadWriter) error { + in := []interface{}{} + out := []interface{}{} + _, err := submitTPMRequest(rw, tagRQUCommand, ordForceClear, in, out) + + return err +} + +// Startup performs TPM_Startup(TPM_ST_CLEAR) to initialize the TPM. +func startup(rw io.ReadWriter) error { + var typ uint16 = 0x0001 // TPM_ST_CLEAR + in := []interface{}{typ} + out := []interface{}{} + _, err := submitTPMRequest(rw, tagRQUCommand, ordStartup, in, out) + + return err +} + +// createEK performs TPM_CreateEndorsementKeyPair to create the EK in the TPM. +func createEK(rw io.ReadWriter) error { + antiReplay := Nonce{} + keyInfo := []byte{ + 0x00, 0x00, 0x00, 0x01, // Algorithm = RSA + 0x00, 0x03, // EncScheme = OAEP + 0x00, 0x01, // SigScheme = None + 0x00, 0x00, 0x00, 0x0c, // ParamsSize = 12 + 0x00, 0x00, 0x08, 0x00, // KeyLength = 2048 + 0x00, 0x00, 0x00, 0x02, // NumPrimes = 2 + 0x00, 0x00, 0x00, 0x00, // ExponentSize = 0 (default 65537 exponent) + } + in := []interface{}{antiReplay, keyInfo} + out := []interface{}{} + _, err := submitTPMRequest(rw, tagRQUCommand, ordCreateEndorsementKeyPair, in, out) + + return err +} diff --git a/vendor/github.com/google/go-tpm/tpm/verify.go b/vendor/github.com/google/go-tpm/tpm/verify.go new file mode 100644 index 00000000000..e5782241860 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm/verify.go @@ -0,0 +1,150 @@ +// Copyright (c) 2014, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpm + +import ( + "crypto" + "crypto/rsa" + "crypto/sha1" + "errors" + "math/big" + + "github.com/google/go-tpm/tpmutil" +) + +// This file provides functions to extract a crypto/rsa public key from a key +// blob or a TPM_KEY of the right type. It also provides a function for +// verifying a quote value given a public key for the key it was signed with. + +// UnmarshalRSAPublicKey takes in a blob containing a serialized RSA TPM_KEY and +// converts it to a crypto/rsa.PublicKey. +func UnmarshalRSAPublicKey(keyBlob []byte) (*rsa.PublicKey, error) { + // Parse the blob as a key. + var k key + if _, err := tpmutil.Unpack(keyBlob, &k); err != nil { + return nil, err + } + + return k.unmarshalRSAPublicKey() +} + +// unmarshalRSAPublicKey unmarshals a TPM key into a crypto/rsa.PublicKey. +func (k *key) unmarshalRSAPublicKey() (*rsa.PublicKey, error) { + // Currently, we only support algRSA + if k.AlgorithmParams.AlgID != AlgRSA { + return nil, errors.New("only TPM_ALG_RSA is supported") + } + + // This means that k.AlgorithmsParams.Params is an rsaKeyParams, which is + // enough to create the exponent, and k.PubKey contains the key. + var rsakp rsaKeyParams + if _, err := tpmutil.Unpack(k.AlgorithmParams.Params, &rsakp); err != nil { + return nil, err + } + + // Make sure that the exponent will fit into an int before using it blindly. + if len(rsakp.Exponent) > 4 { + return nil, errors.New("exponent value doesn't fit into an int") + } + pk := &rsa.PublicKey{ + N: new(big.Int).SetBytes(k.PubKey), + // The exponent isn't set here, but it's fixed to 0x10001 + E: 0x10001, + } + return pk, nil +} + +// UnmarshalPubRSAPublicKey takes in a blob containing a serialized RSA +// TPM_PUBKEY and converts it to a crypto/rsa.PublicKey. +func UnmarshalPubRSAPublicKey(keyBlob []byte) (*rsa.PublicKey, error) { + // Parse the blob as a key. + var pk pubKey + if _, err := tpmutil.Unpack(keyBlob, &pk); err != nil { + return nil, err + } + + return pk.unmarshalRSAPublicKey() +} + +// unmarshalRSAPublicKey unmarshals a TPM pub key into a crypto/rsa.PublicKey. +// This is almost identical to the identically named function for a TPM key. +func (pk *pubKey) unmarshalRSAPublicKey() (*rsa.PublicKey, error) { + // Currently, we only support AlgRSA + if pk.AlgorithmParams.AlgID != AlgRSA { + return nil, errors.New("only TPM_ALG_RSA is supported") + } + + // This means that pk.AlgorithmsParams.Params is an rsaKeyParams, which is + // enough to create the exponent, and pk.Key contains the key. + var rsakp rsaKeyParams + if _, err := tpmutil.Unpack(pk.AlgorithmParams.Params, &rsakp); err != nil { + return nil, err + } + + // Make sure that the exponent will fit into an int before using it blindly. + if len(rsakp.Exponent) > 4 { + return nil, errors.New("exponent value doesn't fit into an int") + } + rsapk := &rsa.PublicKey{ + N: new(big.Int).SetBytes(pk.Key), + // The exponent isn't set here, but it's fixed to 0x10001 + E: 0x10001, + } + return rsapk, nil +} + +// NewQuoteInfo computes a quoteInfo structure for a given pair of data and PCR +// values. +func NewQuoteInfo(data []byte, pcrNums []int, pcrs []byte) ([]byte, error) { + // Compute the composite hash for these PCRs. + pcrSel, err := newPCRSelection(pcrNums) + if err != nil { + return nil, err + } + + comp, err := createPCRComposite(pcrSel.Mask, pcrs) + if err != nil { + return nil, err + } + + qi := "eInfo{ + Version: quoteVersion, + Fixed: fixedQuote, + Nonce: sha1.Sum(data), + } + copy(qi.CompositeDigest[:], comp) + + return tpmutil.Pack(qi) +} + +// VerifyQuote verifies a quote against a given set of PCRs. +func VerifyQuote(pk *rsa.PublicKey, data []byte, quote []byte, pcrNums []int, pcrs []byte) error { + p, err := NewQuoteInfo(data, pcrNums, pcrs) + if err != nil { + return err + } + + s := sha1.Sum(p) + + // Try to do a direct encryption to reverse the value and see if it's padded + // with PKCS1v1.5. + return rsa.VerifyPKCS1v15(pk, crypto.SHA1, s[:], quote) +} + +// TODO(tmroeder): add VerifyQuote2 instead of VerifyQuote. This means I'll +// probably have to look at the signature scheme and use that to choose how to +// verify the signature, whether PKCS1v1.5 or OAEP. And this will have to be set +// on the key before it's passed to ordQuote2 +// TODO(tmroeder): handle key12 diff --git a/vendor/github.com/google/go-tpm/tpmutil/encoding.go b/vendor/github.com/google/go-tpm/tpmutil/encoding.go new file mode 100644 index 00000000000..5983cc215cc --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpmutil/encoding.go @@ -0,0 +1,211 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpmutil + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "reflect" +) + +var ( + selfMarshalerType = reflect.TypeOf((*SelfMarshaler)(nil)).Elem() + handlesAreaType = reflect.TypeOf((*[]Handle)(nil)) +) + +// packWithHeader takes a header and a sequence of elements that are either of +// fixed length or slices of fixed-length types and packs them into a single +// byte array using binary.Write. It updates the CommandHeader to have the right +// length. +func packWithHeader(ch commandHeader, cmd ...interface{}) ([]byte, error) { + hdrSize := binary.Size(ch) + body, err := Pack(cmd...) + if err != nil { + return nil, fmt.Errorf("couldn't pack message body: %v", err) + } + bodySize := len(body) + ch.Size = uint32(hdrSize + bodySize) + header, err := Pack(ch) + if err != nil { + return nil, fmt.Errorf("couldn't pack message header: %v", err) + } + return append(header, body...), nil +} + +// Pack encodes a set of elements into a single byte array, using +// encoding/binary. This means that all the elements must be encodeable +// according to the rules of encoding/binary. +// +// It has one difference from encoding/binary: it encodes byte slices with a +// prepended length, to match how the TPM encodes variable-length arrays. If +// you wish to add a byte slice without length prefix, use RawBytes. +func Pack(elts ...interface{}) ([]byte, error) { + buf := new(bytes.Buffer) + if err := packType(buf, elts...); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// tryMarshal attempts to use a TPMMarshal() method defined on the type +// to pack v into buf. True is returned if the method exists and the +// marshal was attempted. +func tryMarshal(buf io.Writer, v reflect.Value) (bool, error) { + t := v.Type() + if t.Implements(selfMarshalerType) { + if v.Kind() == reflect.Ptr && v.IsNil() { + return true, fmt.Errorf("cannot call TPMMarshal on a nil pointer of type %T", v) + } + return true, v.Interface().(SelfMarshaler).TPMMarshal(buf) + } + + // We might have a non-pointer struct field, but we dont have a + // pointer with which to implement the interface. + // If the pointer of the type implements the interface, we should be + // able to construct a value to call TPMMarshal() with. + // TODO(awly): Try and avoid blowing away private data by using Addr() instead of Set() + if reflect.PtrTo(t).Implements(selfMarshalerType) { + tmp := reflect.New(t) + tmp.Elem().Set(v) + return true, tmp.Interface().(SelfMarshaler).TPMMarshal(buf) + } + + return false, nil +} + +func packValue(buf io.Writer, v reflect.Value) error { + if v.Type() == handlesAreaType { + v = v.Convert(reflect.TypeOf((*handleList)(nil))) + } + if canMarshal, err := tryMarshal(buf, v); canMarshal { + return err + } + + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + return fmt.Errorf("cannot pack nil %s", v.Type().String()) + } + return packValue(buf, v.Elem()) + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + if err := packValue(buf, f); err != nil { + return err + } + } + default: + return binary.Write(buf, binary.BigEndian, v.Interface()) + } + return nil +} + +func packType(buf io.Writer, elts ...interface{}) error { + for _, e := range elts { + if err := packValue(buf, reflect.ValueOf(e)); err != nil { + return err + } + } + + return nil +} + +// tryUnmarshal attempts to use TPMUnmarshal() to perform the +// unpack, if the given value implements SelfMarshaler. +// True is returned if v implements SelfMarshaler & TPMUnmarshal +// was called, along with an error returned from TPMUnmarshal. +func tryUnmarshal(buf io.Reader, v reflect.Value) (bool, error) { + t := v.Type() + if t.Implements(selfMarshalerType) { + if v.Kind() == reflect.Ptr && v.IsNil() { + return true, fmt.Errorf("cannot call TPMUnmarshal on a nil pointer") + } + return true, v.Interface().(SelfMarshaler).TPMUnmarshal(buf) + } + + // We might have a non-pointer struct field, which is addressable, + // If the pointer of the type implements the interface, and the + // value is addressable, we should be able to call TPMUnmarshal(). + if v.CanAddr() && reflect.PtrTo(t).Implements(selfMarshalerType) { + return true, v.Addr().Interface().(SelfMarshaler).TPMUnmarshal(buf) + } + + return false, nil +} + +// Unpack is a convenience wrapper around UnpackBuf. Unpack returns the number +// of bytes read from b to fill elts and error, if any. +func Unpack(b []byte, elts ...interface{}) (int, error) { + buf := bytes.NewBuffer(b) + err := UnpackBuf(buf, elts...) + read := len(b) - buf.Len() + return read, err +} + +func unpackValue(buf io.Reader, v reflect.Value) error { + if v.Type() == handlesAreaType { + v = v.Convert(reflect.TypeOf((*handleList)(nil))) + } + if didUnmarshal, err := tryUnmarshal(buf, v); didUnmarshal { + return err + } + + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + return fmt.Errorf("cannot unpack nil %s", v.Type().String()) + } + return unpackValue(buf, v.Elem()) + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + if err := unpackValue(buf, f); err != nil { + return err + } + } + return nil + default: + // binary.Read can only set pointer values, so we need to take the address. + if !v.CanAddr() { + return fmt.Errorf("cannot unpack unaddressable leaf type %q", v.Type().String()) + } + return binary.Read(buf, binary.BigEndian, v.Addr().Interface()) + } +} + +// UnpackBuf recursively unpacks types from a reader just as encoding/binary +// does under binary.BigEndian, but with one difference: it unpacks a byte +// slice by first reading an integer with lengthPrefixSize bytes, then reading +// that many bytes. It assumes that incoming values are pointers to values so +// that, e.g., underlying slices can be resized as needed. +func UnpackBuf(buf io.Reader, elts ...interface{}) error { + for _, e := range elts { + v := reflect.ValueOf(e) + if v.Kind() != reflect.Ptr { + return fmt.Errorf("non-pointer value %q passed to UnpackBuf", v.Type().String()) + } + if v.IsNil() { + return errors.New("nil pointer passed to UnpackBuf") + } + + if err := unpackValue(buf, v); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/google/go-tpm/tpmutil/poll_other.go b/vendor/github.com/google/go-tpm/tpmutil/poll_other.go new file mode 100644 index 00000000000..ba7e062e32e --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpmutil/poll_other.go @@ -0,0 +1,10 @@ +//go:build !linux && !darwin + +package tpmutil + +import ( + "os" +) + +// Not implemented on Windows. +func poll(_ *os.File) error { return nil } diff --git a/vendor/github.com/google/go-tpm/tpmutil/poll_unix.go b/vendor/github.com/google/go-tpm/tpmutil/poll_unix.go new file mode 100644 index 00000000000..89d85d38148 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpmutil/poll_unix.go @@ -0,0 +1,32 @@ +//go:build linux || darwin + +package tpmutil + +import ( + "fmt" + "os" + + "golang.org/x/sys/unix" +) + +// poll blocks until the file descriptor is ready for reading or an error occurs. +func poll(f *os.File) error { + var ( + fds = []unix.PollFd{{ + Fd: int32(f.Fd()), + Events: 0x1, // POLLIN + }} + timeout = -1 // Indefinite timeout + ) + + if _, err := unix.Poll(fds, timeout); err != nil { + return err + } + + // Revents is filled in by the kernel. + // If the expected event happened, Revents should match Events. + if fds[0].Revents != fds[0].Events { + return fmt.Errorf("unexpected poll Revents 0x%x", fds[0].Revents) + } + return nil +} diff --git a/vendor/github.com/google/go-tpm/tpmutil/run.go b/vendor/github.com/google/go-tpm/tpmutil/run.go new file mode 100644 index 00000000000..c07e3abab4b --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpmutil/run.go @@ -0,0 +1,113 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tpmutil provides common utility functions for both TPM 1.2 and TPM +// 2.0 devices. +package tpmutil + +import ( + "errors" + "io" + "os" + "time" +) + +// maxTPMResponse is the largest possible response from the TPM. We need to know +// this because we don't always know the length of the TPM response, and +// /dev/tpm insists on giving it all back in a single value rather than +// returning a header and a body in separate responses. +const maxTPMResponse = 4096 + +// RunCommandRaw executes the given raw command and returns the raw response. +// Does not check the response code except to execute retry logic. +func RunCommandRaw(rw io.ReadWriter, inb []byte) ([]byte, error) { + if rw == nil { + return nil, errors.New("nil TPM handle") + } + + // f(t) = (2^t)ms, up to 2s + var backoffFac uint + var rh responseHeader + var outb []byte + + for { + if _, err := rw.Write(inb); err != nil { + return nil, err + } + + // If the TPM is a real device, it may not be ready for reading + // immediately after writing the command. Wait until the file + // descriptor is ready to be read from. + if f, ok := rw.(*os.File); ok { + if err := poll(f); err != nil { + return nil, err + } + } + + outb = make([]byte, maxTPMResponse) + outlen, err := rw.Read(outb) + if err != nil { + return nil, err + } + // Resize the buffer to match the amount read from the TPM. + outb = outb[:outlen] + + _, err = Unpack(outb, &rh) + if err != nil { + return nil, err + } + + // If TPM is busy, retry the command after waiting a few ms. + if rh.Res == RCRetry { + if backoffFac < 11 { + dur := (1 << backoffFac) * time.Millisecond + time.Sleep(dur) + backoffFac++ + } else { + return nil, err + } + } else { + break + } + } + + return outb, nil +} + +// RunCommand executes cmd with given tag and arguments. Returns TPM response +// body (without response header) and response code from the header. Returned +// error may be nil if response code is not RCSuccess; caller should check +// both. +func RunCommand(rw io.ReadWriter, tag Tag, cmd Command, in ...interface{}) ([]byte, ResponseCode, error) { + inb, err := packWithHeader(commandHeader{tag, 0, cmd}, in...) + if err != nil { + return nil, 0, err + } + + outb, err := RunCommandRaw(rw, inb) + if err != nil { + return nil, 0, err + } + + var rh responseHeader + read, err := Unpack(outb, &rh) + if err != nil { + return nil, 0, err + } + if rh.Res != RCSuccess { + return nil, rh.Res, nil + } + + return outb[read:], rh.Res, nil +} diff --git a/vendor/github.com/google/go-tpm/tpmutil/run_other.go b/vendor/github.com/google/go-tpm/tpmutil/run_other.go new file mode 100644 index 00000000000..2a142d39ec8 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpmutil/run_other.go @@ -0,0 +1,111 @@ +//go:build !windows + +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpmutil + +import ( + "fmt" + "io" + "net" + "os" +) + +// OpenTPM opens a channel to the TPM at the given path. If the file is a +// device, then it treats it like a normal TPM device, and if the file is a +// Unix domain socket, then it opens a connection to the socket. +func OpenTPM(path string) (io.ReadWriteCloser, error) { + // If it's a regular file, then open it + var rwc io.ReadWriteCloser + fi, err := os.Stat(path) + if err != nil { + return nil, err + } + + if fi.Mode()&os.ModeDevice != 0 { + var f *os.File + f, err = os.OpenFile(path, os.O_RDWR, 0600) + if err != nil { + return nil, err + } + rwc = io.ReadWriteCloser(f) + } else if fi.Mode()&os.ModeSocket != 0 { + rwc = NewEmulatorReadWriteCloser(path) + } else { + return nil, fmt.Errorf("unsupported TPM file mode %s", fi.Mode().String()) + } + + return rwc, nil +} + +// dialer abstracts the net.Dial call so test code can provide its own net.Conn +// implementation. +type dialer func(network, path string) (net.Conn, error) + +// EmulatorReadWriteCloser manages connections with a TPM emulator over a Unix +// domain socket. These emulators often operate in a write/read/disconnect +// sequence, so the Write method always connects, and the Read method always +// closes. EmulatorReadWriteCloser is not thread safe. +type EmulatorReadWriteCloser struct { + path string + conn net.Conn + dialer dialer +} + +// NewEmulatorReadWriteCloser stores information about a Unix domain socket to +// write to and read from. +func NewEmulatorReadWriteCloser(path string) *EmulatorReadWriteCloser { + return &EmulatorReadWriteCloser{ + path: path, + dialer: net.Dial, + } +} + +// Read implements io.Reader by reading from the Unix domain socket and closing +// it. +func (erw *EmulatorReadWriteCloser) Read(p []byte) (int, error) { + // Read is always the second operation in a Write/Read sequence. + if erw.conn == nil { + return 0, fmt.Errorf("must call Write then Read in an alternating sequence") + } + n, err := erw.conn.Read(p) + erw.conn.Close() + erw.conn = nil + return n, err +} + +// Write implements io.Writer by connecting to the Unix domain socket and +// writing. +func (erw *EmulatorReadWriteCloser) Write(p []byte) (int, error) { + if erw.conn != nil { + return 0, fmt.Errorf("must call Write then Read in an alternating sequence") + } + var err error + erw.conn, err = erw.dialer("unix", erw.path) + if err != nil { + return 0, err + } + return erw.conn.Write(p) +} + +// Close implements io.Closer by closing the Unix domain socket if one is open. +func (erw *EmulatorReadWriteCloser) Close() error { + if erw.conn == nil { + return fmt.Errorf("cannot call Close when no connection is open") + } + err := erw.conn.Close() + erw.conn = nil + return err +} diff --git a/vendor/github.com/google/go-tpm/tpmutil/run_windows.go b/vendor/github.com/google/go-tpm/tpmutil/run_windows.go new file mode 100644 index 00000000000..f355b810123 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpmutil/run_windows.go @@ -0,0 +1,84 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpmutil + +import ( + "io" + + "github.com/google/go-tpm/tpmutil/tbs" +) + +// winTPMBuffer is a ReadWriteCloser to access the TPM in Windows. +type winTPMBuffer struct { + context tbs.Context + outBuffer []byte +} + +// Executes the TPM command specified by commandBuffer (at Normal Priority), returning the number +// of bytes in the command and any error code returned by executing the TPM command. Command +// response can be read by calling Read(). +func (rwc *winTPMBuffer) Write(commandBuffer []byte) (int, error) { + // TPM spec defines longest possible response to be maxTPMResponse. + rwc.outBuffer = rwc.outBuffer[:maxTPMResponse] + + outBufferLen, err := rwc.context.SubmitCommand( + tbs.NormalPriority, + commandBuffer, + rwc.outBuffer, + ) + + if err != nil { + rwc.outBuffer = rwc.outBuffer[:0] + return 0, err + } + // Shrink outBuffer so it is length of response. + rwc.outBuffer = rwc.outBuffer[:outBufferLen] + return len(commandBuffer), nil +} + +// Provides TPM response from the command called in the last Write call. +func (rwc *winTPMBuffer) Read(responseBuffer []byte) (int, error) { + if len(rwc.outBuffer) == 0 { + return 0, io.EOF + } + lenCopied := copy(responseBuffer, rwc.outBuffer) + // Cut out the piece of slice which was just read out, maintaining original slice capacity. + rwc.outBuffer = append(rwc.outBuffer[:0], rwc.outBuffer[lenCopied:]...) + return lenCopied, nil +} + +func (rwc *winTPMBuffer) Close() error { + return rwc.context.Close() +} + +// OpenTPM creates a new instance of a ReadWriteCloser which can interact with a +// Windows TPM. +func OpenTPM() (io.ReadWriteCloser, error) { + tpmContext, err := tbs.CreateContext(tbs.TPMVersion20, tbs.IncludeTPM12|tbs.IncludeTPM20) + rwc := &winTPMBuffer{ + context: tpmContext, + outBuffer: make([]byte, 0, maxTPMResponse), + } + return rwc, err +} + +// FromContext creates a new instance of a ReadWriteCloser which can +// interact with a Windows TPM, using the specified TBS handle. +func FromContext(ctx tbs.Context) io.ReadWriteCloser { + return &winTPMBuffer{ + context: ctx, + outBuffer: make([]byte, 0, maxTPMResponse), + } +} diff --git a/vendor/github.com/google/go-tpm/tpmutil/structures.go b/vendor/github.com/google/go-tpm/tpmutil/structures.go new file mode 100644 index 00000000000..893b6b6df95 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpmutil/structures.go @@ -0,0 +1,195 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpmutil + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" +) + +// maxBytesBufferSize sets a sane upper bound on the size of a U32Bytes +// buffer. This limit exists to prevent a maliciously large size prefix +// from resulting in a massive memory allocation, potentially causing +// an OOM condition on the system. +// We expect no buffer from a TPM to approach 1Mb in size. +const maxBytesBufferSize uint32 = 1024 * 1024 // 1Mb. + +// RawBytes is for Pack and RunCommand arguments that are already encoded. +// Compared to []byte, RawBytes will not be prepended with slice length during +// encoding. +type RawBytes []byte + +// U16Bytes is a byte slice with a 16-bit header +type U16Bytes []byte + +// TPMMarshal packs U16Bytes +func (b *U16Bytes) TPMMarshal(out io.Writer) error { + size := len([]byte(*b)) + if err := binary.Write(out, binary.BigEndian, uint16(size)); err != nil { + return err + } + + n, err := out.Write(*b) + if err != nil { + return err + } + if n != size { + return fmt.Errorf("unable to write all contents of U16Bytes") + } + return nil +} + +// TPMUnmarshal unpacks a U16Bytes +func (b *U16Bytes) TPMUnmarshal(in io.Reader) error { + var tmpSize uint16 + if err := binary.Read(in, binary.BigEndian, &tmpSize); err != nil { + return err + } + size := int(tmpSize) + + if len(*b) >= size { + *b = (*b)[:size] + } else { + *b = append(*b, make([]byte, size-len(*b))...) + } + + n, err := in.Read(*b) + if err != nil { + return err + } + if n != size { + return io.ErrUnexpectedEOF + } + return nil +} + +// U32Bytes is a byte slice with a 32-bit header +type U32Bytes []byte + +// TPMMarshal packs U32Bytes +func (b *U32Bytes) TPMMarshal(out io.Writer) error { + size := len([]byte(*b)) + if err := binary.Write(out, binary.BigEndian, uint32(size)); err != nil { + return err + } + + n, err := out.Write(*b) + if err != nil { + return err + } + if n != size { + return fmt.Errorf("unable to write all contents of U32Bytes") + } + return nil +} + +// TPMUnmarshal unpacks a U32Bytes +func (b *U32Bytes) TPMUnmarshal(in io.Reader) error { + var tmpSize uint32 + if err := binary.Read(in, binary.BigEndian, &tmpSize); err != nil { + return err + } + + if tmpSize > maxBytesBufferSize { + return bytes.ErrTooLarge + } + // We can now safely cast to an int on 32-bit or 64-bit machines + size := int(tmpSize) + + if len(*b) >= size { + *b = (*b)[:size] + } else { + *b = append(*b, make([]byte, size-len(*b))...) + } + + n, err := in.Read(*b) + if err != nil { + return err + } + if n != size { + return fmt.Errorf("unable to read all contents in to U32Bytes") + } + return nil +} + +// Tag is a command tag. +type Tag uint16 + +// Command is an identifier of a TPM command. +type Command uint32 + +// A commandHeader is the header for a TPM command. +type commandHeader struct { + Tag Tag + Size uint32 + Cmd Command +} + +// ResponseCode is a response code returned by TPM. +type ResponseCode uint32 + +// RCSuccess is response code for successful command. Identical for TPM 1.2 and +// 2.0. +const RCSuccess ResponseCode = 0x000 + +// RCRetry is response code for TPM is busy. +const RCRetry ResponseCode = 0x922 + +// A responseHeader is a header for TPM responses. +type responseHeader struct { + Tag Tag + Size uint32 + Res ResponseCode +} + +// A Handle is a reference to a TPM object. +type Handle uint32 + +// HandleValue returns the handle value. This behavior is intended to satisfy +// an interface that can be implemented by other, more complex types as well. +func (h Handle) HandleValue() uint32 { + return uint32(h) +} + +type handleList []Handle + +func (l *handleList) TPMMarshal(_ io.Writer) error { + return fmt.Errorf("TPMMarhsal on []Handle is not supported yet") +} + +func (l *handleList) TPMUnmarshal(in io.Reader) error { + var numHandles uint16 + if err := binary.Read(in, binary.BigEndian, &numHandles); err != nil { + return err + } + + // Make len(e) match size exactly. + size := int(numHandles) + if len(*l) >= size { + *l = (*l)[:size] + } else { + *l = append(*l, make([]Handle, size-len(*l))...) + } + return binary.Read(in, binary.BigEndian, *l) +} + +// SelfMarshaler allows custom types to override default encoding/decoding +// behavior in Pack, Unpack and UnpackBuf. +type SelfMarshaler interface { + TPMMarshal(out io.Writer) error + TPMUnmarshal(in io.Reader) error +} diff --git a/vendor/github.com/google/go-tpm/tpmutil/tbs/tbs_windows.go b/vendor/github.com/google/go-tpm/tpmutil/tbs/tbs_windows.go new file mode 100644 index 00000000000..b23bf96a1ec --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpmutil/tbs/tbs_windows.go @@ -0,0 +1,267 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tbs provides an low-level interface directly mapping to Windows +// Tbs.dll system library commands: +// https://docs.microsoft.com/en-us/windows/desktop/TBS/tpm-base-services-portal +// Public field descriptions contain links to the high-level Windows documentation. +package tbs + +import ( + "fmt" + "syscall" + "unsafe" +) + +// Context references the current TPM context +type Context uintptr + +// Version of TPM being used by the application. +type Version uint32 + +// Flag indicates TPM versions that are supported by the application. +type Flag uint32 + +// CommandPriority is used to determine which pending command to submit whenever the TPM is free. +type CommandPriority uint32 + +// Command parameters: +// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/shared/tbs.h +const ( + // https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/ns-tbs-tdtbs_context_params2 + // OR flags to use multiple. + RequestRaw Flag = 1 << iota // Add flag to request raw context + IncludeTPM12 // Add flag to support TPM 1.2 + IncludeTPM20 // Add flag to support TPM 2 + + TPMVersion12 Version = 1 // For TPM 1.2 applications + TPMVersion20 Version = 2 // For TPM 2 applications or applications using multiple TPM versions + + // https://docs.microsoft.com/en-us/windows/desktop/tbs/command-scheduling + // https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsip_submit_command#parameters + LowPriority CommandPriority = 100 // For low priority application use + NormalPriority CommandPriority = 200 // For normal priority application use + HighPriority CommandPriority = 300 // For high priority application use + SystemPriority CommandPriority = 400 // For system tasks that access the TPM + + commandLocalityZero uint32 = 0 // Windows currently only supports TBS_COMMAND_LOCALITY_ZERO. +) + +// Error is the return type of all functions in this package. +type Error uint32 + +func (err Error) Error() string { + if description, ok := errorDescriptions[err]; ok { + return fmt.Sprintf("TBS Error 0x%X: %s", uint32(err), description) + } + return fmt.Sprintf("Unrecognized TBS Error 0x%X", uint32(err)) +} + +func getError(err uintptr) error { + // tbs.dll uses 0x0 as the return value for success. + if err == 0 { + return nil + } + return Error(err) +} + +// TBS Return Codes: +// https://docs.microsoft.com/en-us/windows/desktop/TBS/tbs-return-codes +const ( + ErrInternalError Error = 0x80284001 + ErrBadParameter Error = 0x80284002 + ErrInvalidOutputPointer Error = 0x80284003 + ErrInvalidContext Error = 0x80284004 + ErrInsufficientBuffer Error = 0x80284005 + ErrIOError Error = 0x80284006 + ErrInvalidContextParam Error = 0x80284007 + ErrServiceNotRunning Error = 0x80284008 + ErrTooManyTBSContexts Error = 0x80284009 + ErrTooManyResources Error = 0x8028400A + ErrServiceStartPending Error = 0x8028400B + ErrPPINotSupported Error = 0x8028400C + ErrCommandCanceled Error = 0x8028400D + ErrBufferTooLarge Error = 0x8028400E + ErrTPMNotFound Error = 0x8028400F + ErrServiceDisabled Error = 0x80284010 + ErrNoEventLog Error = 0x80284011 + ErrAccessDenied Error = 0x80284012 + ErrProvisioningNotAllowed Error = 0x80284013 + ErrPPIFunctionUnsupported Error = 0x80284014 + ErrOwnerauthNotFound Error = 0x80284015 +) + +var errorDescriptions = map[Error]string{ + ErrInternalError: "An internal software error occurred.", + ErrBadParameter: "One or more parameter values are not valid.", + ErrInvalidOutputPointer: "A specified output pointer is bad.", + ErrInvalidContext: "The specified context handle does not refer to a valid context.", + ErrInsufficientBuffer: "The specified output buffer is too small.", + ErrIOError: "An error occurred while communicating with the TPM.", + ErrInvalidContextParam: "A context parameter that is not valid was passed when attempting to create a TBS context.", + ErrServiceNotRunning: "The TBS service is not running and could not be started.", + ErrTooManyTBSContexts: "A new context could not be created because there are too many open contexts.", + ErrTooManyResources: "A new virtual resource could not be created because there are too many open virtual resources.", + ErrServiceStartPending: "The TBS service has been started but is not yet running.", + ErrPPINotSupported: "The physical presence interface is not supported.", + ErrCommandCanceled: "The command was canceled.", + ErrBufferTooLarge: "The input or output buffer is too large.", + ErrTPMNotFound: "A compatible Trusted Platform Module (TPM) Security Device cannot be found on this computer.", + ErrServiceDisabled: "The TBS service has been disabled.", + ErrNoEventLog: "The TBS event log is not available.", + ErrAccessDenied: "The caller does not have the appropriate rights to perform the requested operation.", + ErrProvisioningNotAllowed: "The TPM provisioning action is not allowed by the specified flags.", + ErrPPIFunctionUnsupported: "The Physical Presence Interface of this firmware does not support the requested method.", + ErrOwnerauthNotFound: "The requested TPM OwnerAuth value was not found.", +} + +// Tbs.dll provides an API for making calls to the TPM: +// https://docs.microsoft.com/en-us/windows/desktop/TBS/tpm-base-services-portal +var ( + tbsDLL = syscall.NewLazyDLL("Tbs.dll") + tbsGetDeviceInfo = tbsDLL.NewProc("Tbsi_GetDeviceInfo") + tbsCreateContext = tbsDLL.NewProc("Tbsi_Context_Create") + tbsContextClose = tbsDLL.NewProc("Tbsip_Context_Close") + tbsSubmitCommand = tbsDLL.NewProc("Tbsip_Submit_Command") + tbsGetTCGLog = tbsDLL.NewProc("Tbsi_Get_TCG_Log") +) + +// Returns the address of the beginning of a slice or 0 for a nil slice. +func sliceAddress(s []byte) uintptr { + if len(s) == 0 { + return 0 + } + return uintptr(unsafe.Pointer(&(s[0]))) +} + +// DeviceInfo is TPM_DEVICE_INFO from tbs.h +type DeviceInfo struct { + StructVersion uint32 + TPMVersion Version + TPMInterfaceType uint32 + TPMImpRevision uint32 +} + +// GetDeviceInfo gets the DeviceInfo of the current TPM: +// https://docs.microsoft.com/en-us/windows/win32/api/tbs/nf-tbs-tbsi_getdeviceinfo +func GetDeviceInfo() (*DeviceInfo, error) { + info := DeviceInfo{} + // TBS_RESULT Tbsi_GetDeviceInfo( + // UINT32 Size, + // PVOID Info + // ); + if err := tbsGetDeviceInfo.Find(); err != nil { + return nil, err + } + result, _, _ := tbsGetDeviceInfo.Call( + unsafe.Sizeof(info), + uintptr(unsafe.Pointer(&info)), + ) + return &info, getError(result) +} + +// CreateContext creates a new TPM context: +// https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsi_context_create +func CreateContext(version Version, flag Flag) (Context, error) { + var context Context + params := struct { + Version + Flag + }{version, flag} + // TBS_RESULT Tbsi_Context_Create( + // _In_ PCTBS_CONTEXT_PARAMS pContextParams, + // _Out_ PTBS_HCONTEXT *phContext + // ); + if err := tbsCreateContext.Find(); err != nil { + return context, err + } + result, _, _ := tbsCreateContext.Call( + uintptr(unsafe.Pointer(¶ms)), + uintptr(unsafe.Pointer(&context)), + ) + return context, getError(result) +} + +// Close closes an existing TPM context: +// https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsip_context_close +func (context Context) Close() error { + // TBS_RESULT Tbsip_Context_Close( + // _In_ TBS_HCONTEXT hContext + // ); + if err := tbsContextClose.Find(); err != nil { + return err + } + result, _, _ := tbsContextClose.Call(uintptr(context)) + return getError(result) +} + +// SubmitCommand sends commandBuffer to the TPM, returning the number of bytes +// written to responseBuffer. ErrInsufficientBuffer is returned if the +// responseBuffer is too short. ErrInvalidOutputPointer is returned if the +// responseBuffer is nil. On failure, the returned length is unspecified. +// https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsip_submit_command +func (context Context) SubmitCommand( + priority CommandPriority, + commandBuffer []byte, + responseBuffer []byte, +) (uint32, error) { + responseBufferLen := uint32(len(responseBuffer)) + + // TBS_RESULT Tbsip_Submit_Command( + // _In_ TBS_HCONTEXT hContext, + // _In_ TBS_COMMAND_LOCALITY Locality, + // _In_ TBS_COMMAND_PRIORITY Priority, + // _In_ const PCBYTE *pabCommand, + // _In_ UINT32 cbCommand, + // _Out_ PBYTE *pabResult, + // _Inout_ UINT32 *pcbOutput + // ); + if err := tbsSubmitCommand.Find(); err != nil { + return 0, err + } + result, _, _ := tbsSubmitCommand.Call( + uintptr(context), + uintptr(commandLocalityZero), + uintptr(priority), + sliceAddress(commandBuffer), + uintptr(len(commandBuffer)), + sliceAddress(responseBuffer), + uintptr(unsafe.Pointer(&responseBufferLen)), + ) + return responseBufferLen, getError(result) +} + +// GetTCGLog gets the system event log, returning the number of bytes written +// to logBuffer. If logBuffer is nil, the size of the TCG log is returned. +// ErrInsufficientBuffer is returned if the logBuffer is too short. On failure, +// the returned length is unspecified. +// https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsi_get_tcg_log +func (context Context) GetTCGLog(logBuffer []byte) (uint32, error) { + logBufferLen := uint32(len(logBuffer)) + + // TBS_RESULT Tbsi_Get_TCG_Log( + // TBS_HCONTEXT hContext, + // PBYTE pOutputBuf, + // PUINT32 pOutputBufLen + // ); + if err := tbsGetTCGLog.Find(); err != nil { + return 0, err + } + result, _, _ := tbsGetTCGLog.Call( + uintptr(context), + sliceAddress(logBuffer), + uintptr(unsafe.Pointer(&logBufferLen)), + ) + return logBufferLen, getError(result) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c82c992879a..be200b72cd0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -36,6 +36,12 @@ github.com/docker/go-units # github.com/godbus/dbus/v5 v5.1.0 ## explicit; go 1.12 github.com/godbus/dbus/v5 +# github.com/google/go-tpm v0.0.0-00010101000000-000000000000 => github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049 +## explicit; go 1.22 +github.com/google/go-tpm/legacy/tpm2 +github.com/google/go-tpm/tpm +github.com/google/go-tpm/tpmutil +github.com/google/go-tpm/tpmutil/tbs # github.com/moby/sys/capability v0.4.0 ## explicit; go 1.21 github.com/moby/sys/capability @@ -127,3 +133,4 @@ google.golang.org/protobuf/reflect/protoreflect google.golang.org/protobuf/reflect/protoregistry google.golang.org/protobuf/runtime/protoiface google.golang.org/protobuf/runtime/protoimpl +# github.com/google/go-tpm => github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049 From 3c4d44534f55b223b605b4cc371cb1caa977322e Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:47:23 +0000 Subject: [PATCH 19/29] cleancode: simplify functions Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 10 ++-- libcontainer/vtpm/vtpm.go | 55 +++++++++----------- utils_linux.go | 27 ++++++---- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index b9985c1c926..aaf9eaad0c6 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -25,11 +25,12 @@ import ( // - fd= // - file= func getEncryptionPassword(pwdString string) ([]byte, error) { - if strings.HasPrefix(pwdString, "file=") { + switch { + case strings.HasPrefix(pwdString, "file="): return ioutil.ReadFile(pwdString[5:]) - } else if strings.HasPrefix(pwdString, "pass=") { + case strings.HasPrefix(pwdString, "pass="): return []byte(pwdString[5:]), nil - } else if strings.HasPrefix(pwdString, "fd=") { + case strings.HasPrefix(pwdString, "fd="): fdStr := pwdString[3:] fd, err := strconv.Atoi(fdStr) if err != nil { @@ -46,8 +47,9 @@ func getEncryptionPassword(pwdString string) ([]byte, error) { return nil, fmt.Errorf("could not read from file descriptor: %v", err) } return pwd[:n], nil + default: + return []byte(pwdString), nil } - return []byte(pwdString), nil } // CreateVTPM create a VTPM proxy device and starts the TPM emulator with it diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index 5eee7188a2a..8051a57f77e 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -81,14 +81,16 @@ const ( ) func translateUser(username string) (*user.User, error) { - usr, err := user.Lookup(username) - if err != nil { - usr, err = user.LookupId(username) + translatedUser, err := user.Lookup(username) + if err == nil { + return translatedUser, nil } + + translatedUser, err = user.LookupId(username) if err != nil { return nil, fmt.Errorf("User '%s' not available: %v", username, err) } - return usr, nil + return translatedUser, nil } // getCapabilities gets the capabilities map of an executable by invoking it with @@ -97,24 +99,20 @@ func translateUser(username string) (*user.User, error) { // Expected output looks like this: // { "type": "swtpm_setup", "features": [ "cmdarg-keyfile-fd", "cmdarg-pwdfile-fd" ] } func getCapabilities(cmd *exec.Cmd) ([]string, error) { - caps := make(map[string]interface{}) - output, err := cmd.Output() if err != nil { - return nil, nil + return nil, err + } + + var caps struct { + Features []string `json:"features"` } err = json.Unmarshal([]byte(output), &caps) if err != nil { return nil, fmt.Errorf("Could not unmarshal output: %s: %v\n", output, err) } - - features, _ := caps["features"].([]interface{}) - res := make([]string, 0) - for _, f := range features { - res = append(res, f.(string)) - } - return res, nil + return caps.Features, nil } func getSwtpmSetupCapabilities() ([]string, error) { @@ -122,7 +120,7 @@ func getSwtpmSetupCapabilities() ([]string, error) { } func getSwtpmCapabilities() ([]string, error) { - return getCapabilities(exec.Command("swtpm", "chardev", "--print-capabilities")) + return getCapabilities(exec.Command("swtpm_cuse", "--print-capabilities")) } func hasCapability(capabilities []string, capability string) bool { @@ -475,21 +473,6 @@ func (vtpm *VTPM) waitForTPMDevice(loops int) error { return fmt.Errorf("TPM device %s did not appear", devpath) } -func (vtpm *VTPM) GetDeviceNum() error { - if fileInfo, err := os.Lstat(vtpm.GetTPMDevpath()); err == nil { - if stat_t, ok := fileInfo.Sys().(*syscall.Stat_t); ok { - devNumber := stat_t.Rdev - vtpm.major = unix.Major(devNumber) - vtpm.minor = unix.Minor(devNumber) - } - } else { - return err - } - logrus.Infof("device major num: %d", vtpm.major) - logrus.Infof("device minor num: %d", vtpm.minor) - return nil -} - // startSwtpm creates the VTPM proxy device and start the swtpm process func (vtpm *VTPM) startSwtpm() error { tpm_dev_name := fmt.Sprintf("tpm%s", vtpm.VtpmName) @@ -548,6 +531,12 @@ func (vtpm *VTPM) startSwtpm() error { return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError) } + // Swtpm_cuse uses cuse_lowlevel_setup function https://github.com/stefanberger/swtpm/blob/master/src/swtpm/cuse_tpm.c#L1585 + // to setup cuse device. + // This function calls fuse_daemonize https://github.com/libfuse/libfuse/blob/fuse-2.9.9/lib/helper.c#L180 + // to fork and parent process will be exited. We need wait until + // ptm_init_done https://github.com/stefanberger/swtpm/blob/master/src/swtpm/cuse_tpm.c#L1526 + // callback will be called. vtpm.Pid, err = vtpm.waitForPidFile(10) if err != nil { return err @@ -640,7 +629,11 @@ func (vtpm *VTPM) Stop(deleteStatePath bool) error { vtpm.DeleteStatePath() } - return err + if err != nil { + return fmt.Errorf("can not stop swtpm process: %w", err) + } + + return nil } // Get the TPM device name; this method can be called after successful Start() diff --git a/utils_linux.go b/utils_linux.go index 69258321a74..16175471d02 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -517,22 +517,27 @@ func createVTPMs(spec *specs.Spec) ([]*vtpm.VTPM, error) { v, err := vtpmhelper.CreateVTPM(spec, &vtpm) if err != nil { destroyVTPMs(vtpms) - return vtpms, err + return nil, err } vtpms = append(vtpms, v) } - if fileInfo, err = os.Lstat(hostdev); err == nil { - if stat_t, ok := fileInfo.Sys().(*syscall.Stat_t); ok { - devNumber := stat_t.Rdev - major = unix.Major(devNumber) - minor = unix.Minor(devNumber) - } - logrus.Infof("device major num: %d", major) - logrus.Infof("device minor num: %d", minor) - devpath := hostdev - addVTPMDevice(spec, hostdev, devpath, major, minor) + fileInfo, err = os.Lstat(hostdev) + if err != nil { + return nil, fmt.Errorf("failed to stat device %q: %w", hostdev, err) } + + stat_t, ok := fileInfo.Sys().(*syscall.Stat_t) + if !ok { + return nil, fmt.Errorf("unexpected file info type for device %q", hostdev) + } + + devNumber := stat_t.Rdev + major = unix.Major(devNumber) + minor = unix.Minor(devNumber) + + devpath := hostdev + addVTPMDevice(spec, hostdev, devpath, major, minor) } return vtpms, nil From df023928cd9d14e1810d527db12e6916c4202797 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:47:40 +0000 Subject: [PATCH 20/29] vtpm: use generated device host path Signed-off-by: Efim Verzakov --- Makefile | 2 + libcontainer/rootfs_linux.go | 9 +-- libcontainer/specconv/spec_linux.go | 1 - libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 11 +++ tests/integration/swtpm.bats | 80 ++++++++++++++----- utils_linux.go | 18 +++-- .../cgroups/devices/config/device.go | 4 - 7 files changed, 85 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index 41d3725c343..7b6f088ff44 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,7 @@ GO_BUILD_STATIC := $(GO) build $(TRIMPATH) $(GO_BUILDMODE_STATIC) \ GPG_KEYID ?= asarai@suse.de RUN_IN_CONTAINER_MAJOR := 100 +RUN_IN_CONTAINER_MAJOR_SECOND := 101 RUN_IN_CONTAINER_MINOR := 1 # Some targets need cgo, which is disabled by default when cross compiling. @@ -176,6 +177,7 @@ integration: runcimage -v /lib/modules:/lib/modules:ro \ -v $(CURDIR):/go/src/$(PROJECT) \ --device=/dev/cuse --device-cgroup-rule "c $(RUN_IN_CONTAINER_MAJOR):$(RUN_IN_CONTAINER_MINOR) rwm" \ + --device-cgroup-rule "c $(RUN_IN_CONTAINER_MAJOR_SECOND):$(RUN_IN_CONTAINER_MINOR) rwm" \ $(RUNC_IMAGE) make localintegration TESTPATH="$(TESTPATH)" .PHONY: localintegration diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go index 28527f191e1..53ab1d22e99 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -950,14 +950,7 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error { return nil } - var path string - if node.Devpath != "" { - path = node.Devpath - } else { - path = node.Path - } - - dest, err := securejoin.SecureJoin(rootfs, path) + dest, err := securejoin.SecureJoin(rootfs, node.Path) if err != nil { return err } diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index baadb7768ce..2df20ad03c7 100644 --- a/libcontainer/specconv/spec_linux.go +++ b/libcontainer/specconv/spec_linux.go @@ -1012,7 +1012,6 @@ next: Minor: d.Minor, }, Path: d.Path, - Devpath: d.Devpath, FileMode: filemode, Uid: uid, Gid: gid, diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index aaf9eaad0c6..f016465af24 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -3,6 +3,7 @@ package vtpmhelper import ( + "crypto/sha256" "fmt" "io/ioutil" "os" @@ -18,6 +19,10 @@ import ( // "github.com/sirupsen/logrus" ) +const ( + HashedRootOffset = 6 +) + // getEncryptionPassword gets the plain password from the caller // valid formats passed to this function are: // - @@ -52,6 +57,12 @@ func getEncryptionPassword(pwdString string) ([]byte, error) { } } +func GenerateDeviceHostPathName(root, containerName, deviceName string) string { + // In runc we do not have a namespace, so we will used hash from root as a namespace. + hashedValue := fmt.Sprintf("%x", sha256.Sum256(append([]byte(root), '\n'))) + return fmt.Sprintf("%s-%s-%s", hashedValue[:HashedRootOffset], containerName, deviceName) +} + // CreateVTPM create a VTPM proxy device and starts the TPM emulator with it func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM) (*vtpm.VTPM, error) { encryptionPassword, err := getEncryptionPassword(vtpmdev.EncryptionPassword) diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index 6854689060a..1979f23e764 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -3,13 +3,9 @@ load helpers function setup() { requires root - rm /dev/tpmtpmm2 || true - rm /dev/tpmtpmm12 || true - rm /dev/tpmtpmstop || true - rm /dev/tpmtpmkill || true - rm /dev/tpmtpmdelete || true - rm /dev/tpmtpmforcekill || true setup_debian + ROOT_HASH=$(sha256sum - <<<"$ROOT/state") + ROOT_HASH_OFFSET=${ROOT_HASH:0:6} } function teardown() { @@ -29,7 +25,7 @@ function teardown() { cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/'"$HELPER"'", "-devicePath=/dev/tpmtpmm2"] - | .linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmm2", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmm2", "vtpmMajor": 100, "vtpmMinor": 1}]' runc run tst [ "$status" -eq 0 ] } @@ -39,7 +35,7 @@ function teardown() { cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/'"$HELPER"'", "-devicePath=/dev/tpmtpmm12", "-deviceVersion=1.2"] - | .linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "1.2", "vtpmname" : "tpmm12", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "1.2", "vtpmname" : "tpmm12", "vtpmMajor": 100, "vtpmMinor": 1}]' runc run tst [ "$status" -eq 0 ] } @@ -48,19 +44,19 @@ function teardown() { HELPER="tpm-helper" vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmstop", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmstop", "vtpmMajor": 100, "vtpmMinor": 1}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] wait_for_container 10 1 tst runc pause tst - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmstop -deviceVersion=2 || ret=$? + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmstop -deviceVersion=2 || ret=$? if [ "$ret" -ne 1 ]; then fail "should not be able to read from swtpm paused container" fi runc resume tst - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmstop -deviceVersion=2 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmstop -deviceVersion=2 ret=$? if [ "$ret" -ne 0 ]; then fail "should be able to read from swtpm resumed container" @@ -71,7 +67,7 @@ function teardown() { HELPER="tpm-helper" vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmkill", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmkill", "vtpmMajor": 100, "vtpmMinor": 1}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] wait_for_container 10 1 tst @@ -79,7 +75,7 @@ function teardown() { runc kill tst KILL [ "$status" -eq 0 ] wait_for_container 10 1 tst stopped - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmkill -deviceVersion=2 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmkill -deviceVersion=2 ret=$? if [ "$ret" -ne 0 ]; then fail "should be able to read from swtpm killed container" @@ -87,7 +83,7 @@ function teardown() { runc delete tst [ "$status" -eq 0 ] - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmkill -deviceVersion=2 || ret=$? + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmkill -deviceVersion=2 || ret=$? if [ "$ret" -ne 1 ]; then fail "should not be able to read from swtpm deleted container" fi @@ -97,14 +93,14 @@ function teardown() { HELPER="tpm-helper" vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmdelete", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmdelete", "vtpmMajor": 100, "vtpmMinor": 1}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] wait_for_container 10 1 tst runc delete --force tst [ "$status" -eq 0 ] - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmdelete -deviceVersion=2 || ret=$? + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmdelete -deviceVersion=2 || ret=$? if [ "$ret" -ne 1 ]; then fail "should not be able to read from swtpm deleted container" fi @@ -115,15 +111,59 @@ function teardown() { HELPER="tpm-helper" vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms += [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmforcekill", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmforcekill", "vtpmMajor": 100, "vtpmMinor": 1}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] wait_for_container 10 1 tst - - swtpm_pid=$(cat $vtpm_path/tpmforcekill-swtpm.pid) + swtpm_pid=$(cat $vtpm_path/"$ROOT_HASH_OFFSET"-tst-tpmforcekill-swtpm.pid) kill -9 "$swtpm_pid" - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpmtpmforcekill -deviceVersion=2 || ret=$? + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmforcekill -deviceVersion=2 || ret=$? if [ "$ret" -ne 1 ]; then fail "should not be able to read from killed swtpm" fi + runc delete --force tst + [ "$status" -eq 0 ] +} + + +@test "runc with 2 container with the same devpath" { + HELPER="tpm-helper" + cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmsame", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst1 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst1 + + vtpm_pth1=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_pth1"'", "vtpmversion": "2", "vtpmname" : "tpmsame", "vtpmMajor": 101, "vtpmMinor": 1}]' + + runc run -d --console-socket "$CONSOLE_SOCKET" tst2 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst2 + + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst1-tpmsame -deviceVersion=2 + ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from first container" + fi + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst2-tpmsame -deviceVersion=2 + ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from second container" + fi + + runc exec tst1 "${HELPER}" -devicePath=/dev/tpmtpmsame -deviceVersion=2 + [ "$status" -eq 0 ] + + runc exec tst2 "${HELPER}" -devicePath=/dev/tpmtpmsame -deviceVersion=2 + [ "$status" -eq 0 ] + + runc delete --force ts1 + [ "$status" -eq 0 ] + + runc exec tst2 "${HELPER}" -devicePath=/dev/tpmtpmsame -deviceVersion=2 + [ "$status" -eq 0 ] } \ No newline at end of file diff --git a/utils_linux.go b/utils_linux.go index 16175471d02..e2f46c24607 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -387,7 +387,7 @@ func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.C notifySocket.setupSpec(spec) } - vtpms, err := createVTPMs(spec) + vtpms, err := createVTPMs(context.GlobalString("root"), id, spec) if err != nil { return -1, err } @@ -471,12 +471,11 @@ func maybeLogCgroupWarning(op string, err error) { } // addVTPMDevice adds a device and cgroup entry to the spec -func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint32) { +func addVTPMDevice(spec *specs.Spec, devpath string, major, minor uint32) { var filemode os.FileMode = 0600 device := specs.LinuxDevice{ - Path: hostpath, - Devpath: devpath, + Path: devpath, Type: "c", Major: int64(major), Minor: int64(minor), @@ -499,7 +498,7 @@ func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld) } -func createVTPMs(spec *specs.Spec) ([]*vtpm.VTPM, error) { +func createVTPMs(root, containerID string, spec *specs.Spec) ([]*vtpm.VTPM, error) { var vtpms []*vtpm.VTPM r := spec.Linux.Resources @@ -512,7 +511,12 @@ func createVTPMs(spec *specs.Spec) ([]*vtpm.VTPM, error) { var minor uint32 var fileInfo os.FileInfo var err error + containerVTPMName := vtpm.VTPMName + + // Several containers can have vtpms on the same devpath that's why we need to create unique host path. + vtpm.VTPMName = vtpmhelper.GenerateDeviceHostPathName(root, containerID, containerVTPMName) hostdev := "/dev/tpm" + vtpm.VTPMName + if fileInfo, err = os.Lstat(hostdev); err != nil { v, err := vtpmhelper.CreateVTPM(spec, &vtpm) if err != nil { @@ -536,8 +540,8 @@ func createVTPMs(spec *specs.Spec) ([]*vtpm.VTPM, error) { major = unix.Major(devNumber) minor = unix.Minor(devNumber) - devpath := hostdev - addVTPMDevice(spec, hostdev, devpath, major, minor) + devpath := "/dev/tpm" + containerVTPMName + addVTPMDevice(spec, devpath, major, minor) } return vtpms, nil diff --git a/vendor/github.com/opencontainers/cgroups/devices/config/device.go b/vendor/github.com/opencontainers/cgroups/devices/config/device.go index 464c4496a47..295575cbfa7 100644 --- a/vendor/github.com/opencontainers/cgroups/devices/config/device.go +++ b/vendor/github.com/opencontainers/cgroups/devices/config/device.go @@ -16,10 +16,6 @@ type Device struct { // Path to the device. Path string `json:"path"` - // the name of the device inside the container (optional) - // allows a host device to appear under different name inside container - Devpath string `json:"devpath"` - // FileMode permission bits for the device. FileMode os.FileMode `json:"file_mode"` From a7db154109ccb77b0ed49a16ff9d9c6d35a22295 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:48:08 +0000 Subject: [PATCH 21/29] vtpms: add vtpm names check Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 14 ++++++ tests/integration/swtpm.bats | 47 ++++++++++++++++++++ utils_linux.go | 10 +++++ 3 files changed, 71 insertions(+) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index f016465af24..05037ef0da0 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -126,3 +126,17 @@ func ApplyCGroupVTPMs(vtpms []*vtpm.VTPM, cgroupManager cgroups.Manager) error { } return nil } + +func CheckVTPMNames(vtpms []string) error { + namesMap := make(map[string]int, 0) + for ind, name := range vtpms { + if name == "" { + return fmt.Errorf("VTPM device %d has empty name", ind) + } + if mappedInd, ok := namesMap[name]; ok { + return fmt.Errorf("VTPM devices %d and %d has the same name %s", mappedInd, ind, name) + } + namesMap[name] = ind + } + return nil +} diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index 1979f23e764..3d5332ef6db 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -166,4 +166,51 @@ function teardown() { runc exec tst2 "${HELPER}" -devicePath=/dev/tpmtpmsame -deviceVersion=2 [ "$status" -eq 0 ] +} + +@test "runc run with wrong VTPM names" { + HELPER="tpm-helper" + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -ne 0 ] + + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, + {"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 101, "vtpmMinor": 1} + ]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -ne 0 ] +} + + +@test "runc run container with 2 containers" { + HELPER="tpm-helper" + cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + vtpm_path1=$(mktemp -d) + vtpm_path2=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, + {"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1} + ]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + + runc exec tst "${HELPER}" -devicePath=/dev/tpmtpmone -deviceVersion=2 + [ "$status" -eq 0 ] + + runc exec tst "${HELPER}" -devicePath=/dev/tpmtpmsecond -deviceVersion=2 + [ "$status" -eq 0 ] + + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmone -deviceVersion=2 + ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from first device" + fi + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsecond -deviceVersion=2 + ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from second device" + fi } \ No newline at end of file diff --git a/utils_linux.go b/utils_linux.go index e2f46c24607..00078c24e1d 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -506,6 +506,16 @@ func createVTPMs(root, containerID string, spec *specs.Spec) ([]*vtpm.VTPM, erro return vtpms, nil } + var vtpmNames []string + for _, vtpm := range r.VTPMs { + vtpmNames = append(vtpmNames, vtpm.VTPMName) + } + + err := vtpmhelper.CheckVTPMNames(vtpmNames) + if err != nil { + return nil, err + } + for _, vtpm := range r.VTPMs { var major uint32 var minor uint32 From 53f4653a2bcea3b6be2142ac62c273ea13335122 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:48:25 +0000 Subject: [PATCH 22/29] vtpm: add config to ignore vtpms errors Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 40 +++++++- tests/integration/swtpm.bats | 102 ++++++++++++++++--- utils_linux.go | 94 +++++++++++------ 3 files changed, 189 insertions(+), 47 deletions(-) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index 05037ef0da0..8343a2191aa 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -4,11 +4,14 @@ package vtpmhelper import ( "crypto/sha256" + "encoding/json" "fmt" + "io" "io/ioutil" "os" "strconv" "strings" + "sync" "github.com/opencontainers/cgroups" "github.com/opencontainers/runc/libcontainer/configs" @@ -16,7 +19,7 @@ import ( "github.com/opencontainers/runtime-spec/specs-go" // "golang.org/x/sys/unix" - // "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) const ( @@ -140,3 +143,38 @@ func CheckVTPMNames(vtpms []string) error { } return nil } + +const defaultSWTPMRuncConfig = "/etc/swtpm/runc.conf" + +var ( + ignoreVtpmErrors bool + configOnce sync.Once +) + +func CanIgnoreVTPMErrors() bool { + configOnce.Do(func() { + file, err := os.Open(defaultSWTPMRuncConfig) + if err != nil { + logrus.Errorf("can not open config %s: %s", defaultSWTPMRuncConfig, err) + return + } + + data, err := io.ReadAll(file) + if err != nil { + logrus.Errorf("can not read data from config %s: %s", defaultSWTPMRuncConfig, err) + return + } + + var config struct { + IgnoreVTPMErrors bool `json:"ignoreVTPMErrors,omitempty"` + } + + err = json.Unmarshal(data, &config) + if err != nil { + logrus.Errorf("can not unmarshal config %s: %s", defaultSWTPMRuncConfig, err) + return + } + ignoreVtpmErrors = config.IgnoreVTPMErrors + }) + return ignoreVtpmErrors +} diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index 3d5332ef6db..ee6c174ed7e 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -3,6 +3,7 @@ load helpers function setup() { requires root + rm /etc/swtpm/runc.conf || true setup_debian ROOT_HASH=$(sha256sum - <<<"$ROOT/state") ROOT_HASH_OFFSET=${ROOT_HASH:0:6} @@ -50,14 +51,17 @@ function teardown() { wait_for_container 10 1 tst runc pause tst + + ret=0 ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmstop -deviceVersion=2 || ret=$? if [ "$ret" -ne 1 ]; then fail "should not be able to read from swtpm paused container" fi runc resume tst - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmstop -deviceVersion=2 - ret=$? + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmstop -deviceVersion=2 || ret=$? if [ "$ret" -ne 0 ]; then fail "should be able to read from swtpm resumed container" fi @@ -75,14 +79,17 @@ function teardown() { runc kill tst KILL [ "$status" -eq 0 ] wait_for_container 10 1 tst stopped - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmkill -deviceVersion=2 - ret=$? + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmkill -deviceVersion=2 || ret=$? if [ "$ret" -ne 0 ]; then fail "should be able to read from swtpm killed container" fi runc delete tst [ "$status" -eq 0 ] + + ret=0 ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmkill -deviceVersion=2 || ret=$? if [ "$ret" -ne 1 ]; then fail "should not be able to read from swtpm deleted container" @@ -100,6 +107,8 @@ function teardown() { runc delete --force tst [ "$status" -eq 0 ] + + ret=0 ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmdelete -deviceVersion=2 || ret=$? if [ "$ret" -ne 1 ]; then fail "should not be able to read from swtpm deleted container" @@ -117,6 +126,8 @@ function teardown() { wait_for_container 10 1 tst swtpm_pid=$(cat $vtpm_path/"$ROOT_HASH_OFFSET"-tst-tpmforcekill-swtpm.pid) kill -9 "$swtpm_pid" + + ret=0 ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmforcekill -deviceVersion=2 || ret=$? if [ "$ret" -ne 1 ]; then fail "should not be able to read from killed swtpm" @@ -144,13 +155,14 @@ function teardown() { [ "$status" -eq 0 ] wait_for_container 10 1 tst2 - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst1-tpmsame -deviceVersion=2 - ret=$? + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst1-tpmsame -deviceVersion=2 || ret=$? if [ "$ret" -ne 0 ]; then fail "should be able to read from first container" fi - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst2-tpmsame -deviceVersion=2 - ret=$? + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst2-tpmsame -deviceVersion=2 || ret=$? if [ "$ret" -ne 0 ]; then fail "should be able to read from second container" fi @@ -170,15 +182,18 @@ function teardown() { @test "runc run with wrong VTPM names" { HELPER="tpm-helper" - vtpm_path=$(mktemp -d) + vtpm_path1=$(mktemp -d) + vtpm_path2=$(mktemp -d) + vtpm_path3=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": 100, "vtpmMinor": 1}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -ne 0 ] update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, - {"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 101, "vtpmMinor": 1} + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, + {"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 101, "vtpmMinor": 1} ]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -ne 0 ] @@ -203,14 +218,69 @@ function teardown() { runc exec tst "${HELPER}" -devicePath=/dev/tpmtpmsecond -deviceVersion=2 [ "$status" -eq 0 ] - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmone -deviceVersion=2 - ret=$? + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmone -deviceVersion=2 || ret=$? if [ "$ret" -ne 0 ]; then fail "should be able to read from first device" fi - ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsecond -deviceVersion=2 - ret=$? + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsecond -deviceVersion=2 || ret=$? if [ "$ret" -ne 0 ]; then fail "should be able to read from second device" fi +} + +@test "runc run with ignored errors" { + mkdir -p /etc/swtpm + echo '{"ignoreVTPMErrors": true}' > /etc/swtpm/runc.conf + HELPER="tpm-helper" + vtpm_path1=$(mktemp -d) + vtpm_path2=$(mktemp -d) + vtpm_path3=$(mktemp -d) + vtpm_path4=$(mktemp -d) + + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst1 + [ "$status" -eq 0 ] + + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, + {"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 101, "vtpmMinor": 1} + ]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst2 + [ "$status" -eq 0 ] + + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst1- -deviceVersion=2 || ret=$? + if [ "$ret" -eq 0 ]; then + fail "should not be able to read from empty name device" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst2-tpmon2 -deviceVersion=2 || ret=$? + if [ "$ret" -eq 0 ]; then + fail "should not be able to read from repeated name device" + fi + + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, + {"statepath": "'"$vtpm_path4"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1} + ]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst3 + [ "$status" -eq 0 ] + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst3-tpmone -deviceVersion=2 || ret=$? + if [ "$ret" -eq 0 ]; then + fail "should not be able to read from wrong major device" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst3-tpmsecond -deviceVersion=2 || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from right name device" + fi } \ No newline at end of file diff --git a/utils_linux.go b/utils_linux.go index 00078c24e1d..69b40bccd4b 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -500,6 +500,12 @@ func addVTPMDevice(spec *specs.Spec, devpath string, major, minor uint32) { func createVTPMs(root, containerID string, spec *specs.Spec) ([]*vtpm.VTPM, error) { var vtpms []*vtpm.VTPM + type vtpmLinuxDevice struct { + devPath string + major uint32 + minor uint32 + } + var devices []vtpmLinuxDevice r := spec.Linux.Resources if r == nil { @@ -513,45 +519,73 @@ func createVTPMs(root, containerID string, spec *specs.Spec) ([]*vtpm.VTPM, erro err := vtpmhelper.CheckVTPMNames(vtpmNames) if err != nil { + if vtpmhelper.CanIgnoreVTPMErrors() { + logrus.Errorf("createVTPMs has an error: %s", err) + return nil, nil + } return nil, err } - for _, vtpm := range r.VTPMs { - var major uint32 - var minor uint32 - var fileInfo os.FileInfo - var err error - containerVTPMName := vtpm.VTPMName - - // Several containers can have vtpms on the same devpath that's why we need to create unique host path. - vtpm.VTPMName = vtpmhelper.GenerateDeviceHostPathName(root, containerID, containerVTPMName) - hostdev := "/dev/tpm" + vtpm.VTPMName - - if fileInfo, err = os.Lstat(hostdev); err != nil { - v, err := vtpmhelper.CreateVTPM(spec, &vtpm) + for ind, vtpmSpec := range r.VTPMs { + vtpm, device, err := func(vtpmSpec specs.LinuxVTPM) (*vtpm.VTPM, vtpmLinuxDevice, error) { + var major uint32 + var minor uint32 + var fileInfo os.FileInfo + var err error + var vtpm *vtpm.VTPM + containerVTPMName := vtpmSpec.VTPMName + + // Several containers can have vtpms on the same devpath that's why we need to create unique host path. + vtpmSpec.VTPMName = vtpmhelper.GenerateDeviceHostPathName(root, containerID, containerVTPMName) + hostdev := "/dev/tpm" + vtpmSpec.VTPMName + + if fileInfo, err = os.Lstat(hostdev); err != nil && os.IsNotExist(err) { + vtpm, err = vtpmhelper.CreateVTPM(spec, &vtpmSpec) + if err != nil { + return nil, vtpmLinuxDevice{}, fmt.Errorf("createVTPMs has an error while create %d VTPM device: %w", ind, err) + } + } else if err != nil { + return nil, vtpmLinuxDevice{}, fmt.Errorf("createVTPMs has an error while checking dev path %s: %s", hostdev, err) + } + + fileInfo, err = os.Lstat(hostdev) if err != nil { - destroyVTPMs(vtpms) - return nil, err + return vtpm, vtpmLinuxDevice{}, fmt.Errorf("createVTPMs has an error while checking dev path %s: %s", hostdev, err) } - vtpms = append(vtpms, v) - } - fileInfo, err = os.Lstat(hostdev) - if err != nil { - return nil, fmt.Errorf("failed to stat device %q: %w", hostdev, err) - } + stat_t, ok := fileInfo.Sys().(*syscall.Stat_t) + if !ok { + return vtpm, vtpmLinuxDevice{}, fmt.Errorf("createVTPMs has an error while checking info type for device %s: %w", hostdev, err) + } - stat_t, ok := fileInfo.Sys().(*syscall.Stat_t) - if !ok { - return nil, fmt.Errorf("unexpected file info type for device %q", hostdev) - } + devNumber := stat_t.Rdev + major = unix.Major(devNumber) + minor = unix.Minor(devNumber) - devNumber := stat_t.Rdev - major = unix.Major(devNumber) - minor = unix.Minor(devNumber) + devpath := "/dev/tpm" + containerVTPMName + device := vtpmLinuxDevice{ + devPath: devpath, + major: major, + minor: minor, + } + return vtpm, device, nil + }(vtpmSpec) - devpath := "/dev/tpm" + containerVTPMName - addVTPMDevice(spec, devpath, major, minor) + if vtpm != nil { + vtpms = append(vtpms, vtpm) + } + if err != nil { + if vtpmhelper.CanIgnoreVTPMErrors() { + logrus.Error(err) + continue + } + destroyVTPMs(vtpms) + return nil, err + } + devices = append(devices, device) + } + for _, device := range devices { + addVTPMDevice(spec, device.devPath, device.major, device.minor) } return vtpms, nil From ed21eac920051eafb6d5a6ba44c4cdd72e19958f Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:48:49 +0000 Subject: [PATCH 23/29] vtpms: use generated name to pass in user-namespaced container Signed-off-by: Efim Verzakov --- tests/integration/swtpm.bats | 72 ++++++++++++++++++- utils_linux.go | 15 ++++ .../runtime-spec/specs-go/config.go | 2 - 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index ee6c174ed7e..69e6b9e7256 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -173,7 +173,7 @@ function teardown() { runc exec tst2 "${HELPER}" -devicePath=/dev/tpmtpmsame -deviceVersion=2 [ "$status" -eq 0 ] - runc delete --force ts1 + runc delete --force tst1 [ "$status" -eq 0 ] runc exec tst2 "${HELPER}" -devicePath=/dev/tpmtpmsame -deviceVersion=2 @@ -283,4 +283,72 @@ function teardown() { if [ "$ret" -ne 0 ]; then fail "should be able to read from right name device" fi -} \ No newline at end of file +} + +@test "runc swtpm with user namespace" { + rm /etc/swtpm/runc.conf || true + HELPER="tpm-helper" + cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.linux.namespaces += [{"type": "user"}] + |.linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}] + |.linux.gidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmuser", "vtpmMajor": 100, "vtpmMinor": 1}]' + remap_rootfs + + runc run -d --console-socket "$CONSOLE_SOCKET" tst1 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst1 + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst1-tpmuser -deviceVersion=2 || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from user namespaced container" + fi + + runc exec tst1 "${HELPER}" -devicePath=/dev/tpmtpmuser -deviceVersion=2 + [ "$status" -ne 0 ] + + runc exec tst1 "${HELPER}" -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst1-tpmuser -deviceVersion=2 + [ "$status" -eq 0 ] + + runc delete --force tst1 + [ "$status" -eq 0 ] +} + +@test "runc swtpm with joined user namespace" { + HELPER="tpm-helper" + cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["sleep", "infinity"] + |.linux.namespaces += [{"type": "user"}] + |.linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}] + |.linux.gidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}]' + + runc run -d --console-socket "$CONSOLE_SOCKET" tmp_sleep_container + [ "$status" -eq 0 ] + wait_for_container 10 1 tmp_sleep_container + + target_pid="$(__runc state tmp_sleep_container | jq .pid)" + update_config '.linux.namespaces |= map(if .type == "user" then (.path = "/proc/'"$target_pid"'/ns/" + .type) else . end) + | del(.linux.uidMappings) + | del(.linux.gidMappings) + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "joined_userns", "vtpmMajor": 100, "vtpmMinor": 1}]' + + runc run -d --console-socket "$CONSOLE_SOCKET" vtpm_in_joined_userns + [ "$status" -eq 0 ] + wait_for_container 10 1 vtpm_in_joined_userns + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-vtpm_in_joined_userns-joined_userns -deviceVersion=2 || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from user namespaced container" + fi + + runc exec vtpm_in_joined_userns "${HELPER}" -devicePath=/dev/tpmjoined_userns -deviceVersion=2 + [ "$status" -ne 0 ] + + runc exec vtpm_in_joined_userns "${HELPER}" -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-vtpm_in_joined_userns-joined_userns -deviceVersion=2 + [ "$status" -eq 0 ] +} diff --git a/utils_linux.go b/utils_linux.go index 69b40bccd4b..68bb614e44c 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -11,6 +11,7 @@ import ( "syscall" "github.com/coreos/go-systemd/v22/activation" + "github.com/moby/sys/userns" "github.com/opencontainers/runc/libcontainer/vtpm" vtpmhelper "github.com/opencontainers/runc/libcontainer/vtpm/vtpm-helper" "github.com/opencontainers/runtime-spec/specs-go" @@ -498,6 +499,15 @@ func addVTPMDevice(spec *specs.Spec, devpath string, major, minor uint32) { spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld) } +func ContainsUserNamespace(namespaces []specs.LinuxNamespace, userNs specs.LinuxNamespaceType) bool { + for _, ns := range namespaces { + if ns.Type == userNs { + return true + } + } + return false +} + func createVTPMs(root, containerID string, spec *specs.Spec) ([]*vtpm.VTPM, error) { var vtpms []*vtpm.VTPM type vtpmLinuxDevice struct { @@ -563,6 +573,11 @@ func createVTPMs(root, containerID string, spec *specs.Spec) ([]*vtpm.VTPM, erro minor = unix.Minor(devNumber) devpath := "/dev/tpm" + containerVTPMName + // We switch to the created device name because in runc's init we will use bind command instead mknod + if userns.RunningInUserNS() || spec.Linux != nil && ContainsUserNamespace(spec.Linux.Namespaces, specs.UserNamespace) { + devpath = hostdev + } + device := vtpmLinuxDevice{ devPath: devpath, major: major, diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index fe144c863d4..3d2d84dd082 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -505,8 +505,6 @@ type LinuxResources struct { type LinuxDevice struct { // Path to the device. Path string `json:"path"` - // Path of passed-through device on host - Devpath string `json:"devpath"` // Device type, block, char, etc. Type string `json:"type"` // Major is the device's major number. From a0fa80b4788c031d297d657a8bc50b8d3f32116f Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:49:10 +0000 Subject: [PATCH 24/29] vtpms: return the call of swtpm_setup and more info about errors. Signed-off-by: Efim Verzakov --- Dockerfile | 1 + go.sum | 2 + libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 6 +- libcontainer/vtpm/vtpm.go | 32 +- script/swtpm.sh | 3 +- tests/cmd/tpm-helper/main.go | 204 +- tests/integration/swtpm.bats | 105 + vendor/github.com/google/go-tpm/tpm2/audit.go | 87 + .../github.com/google/go-tpm/tpm2/bitfield.go | 82 + .../google/go-tpm/tpm2/child_object_crypto.go | 62 + .../google/go-tpm/tpm2/constants.go | 718 ++++ .../google/go-tpm/tpm2/constants_internal.go | 14 + .../google/go-tpm/tpm2/create_credential.go | 40 + .../google/go-tpm/tpm2/create_duplicate.go | 41 + .../google/go-tpm/tpm2/create_salt.go | 14 + .../github.com/google/go-tpm/tpm2/crypto.go | 193 + .../github.com/google/go-tpm/tpm2/errors.go | 561 +++ vendor/github.com/google/go-tpm/tpm2/kdf.go | 29 + .../google/go-tpm/tpm2/labeled_kem_convert.go | 33 + .../google/go-tpm/tpm2/labeled_kem_ecc.go | 107 + .../google/go-tpm/tpm2/labeled_kem_rsa.go | 112 + .../google/go-tpm/tpm2/marshalling.go | 128 + vendor/github.com/google/go-tpm/tpm2/names.go | 62 + vendor/github.com/google/go-tpm/tpm2/pcrs.go | 55 + .../github.com/google/go-tpm/tpm2/policy.go | 60 + .../github.com/google/go-tpm/tpm2/reflect.go | 1098 ++++++ .../github.com/google/go-tpm/tpm2/sessions.go | 996 +++++ .../google/go-tpm/tpm2/structures.go | 3331 +++++++++++++++++ .../google/go-tpm/tpm2/templates.go | 200 + vendor/github.com/google/go-tpm/tpm2/tpm2.go | 2232 +++++++++++ vendor/github.com/google/go-tpm/tpm2/tpm2b.go | 83 + .../go-tpm/tpm2/transport/open_other.go | 22 + .../go-tpm/tpm2/transport/open_windows.go | 21 + .../google/go-tpm/tpm2/transport/tpm.go | 90 + vendor/modules.txt | 2 + 35 files changed, 10798 insertions(+), 28 deletions(-) create mode 100644 vendor/github.com/google/go-tpm/tpm2/audit.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/bitfield.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/child_object_crypto.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/constants.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/constants_internal.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/create_credential.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/create_duplicate.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/create_salt.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/crypto.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/errors.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/kdf.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/labeled_kem_convert.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/labeled_kem_ecc.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/labeled_kem_rsa.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/marshalling.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/names.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/pcrs.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/policy.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/reflect.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/sessions.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/structures.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/templates.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/tpm2.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/tpm2b.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/transport/open_other.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/transport/open_windows.go create mode 100644 vendor/github.com/google/go-tpm/tpm2/transport/tpm.go diff --git a/Dockerfile b/Dockerfile index d5c91586ab3..350c1d40577 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,7 @@ RUN KEYFILE=/usr/share/keyrings/criu-repo-keyring.gpg; \ libseccomp-dev \ libfuse-dev \ libglib2.0-dev \ + gnutls-bin \ && apt-get clean \ && rm -rf /var/cache/apt /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list diff --git a/go.sum b/go.sum index a078a409713..b244c8a69be 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc= +github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index 8343a2191aa..d19eace40db 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -70,18 +70,18 @@ func GenerateDeviceHostPathName(root, containerName, deviceName string) string { func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM) (*vtpm.VTPM, error) { encryptionPassword, err := getEncryptionPassword(vtpmdev.EncryptionPassword) if err != nil { - return nil, err + return nil, fmt.Errorf("can not get encryption password: %w", err) } vtpm, err := vtpm.NewVTPM(vtpmdev, encryptionPassword) if err != nil { - return nil, err + return nil, fmt.Errorf("can not create new vtpm: %w", err) } // Start the vTPM process; once stopped, the device pair will also disappear vtpm.CreatedStatepath, err = vtpm.Start() if err != nil { - return nil, err + return nil, fmt.Errorf("can not start new vtpm: %w", err) } return vtpm, nil diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index 8051a57f77e..a4ac56aa272 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -76,8 +76,9 @@ type VTPM struct { } const ( - VTPM_VERSION_1_2 = "1.2" - VTPM_VERSION_2 = "2" + VTPM_VERSION_1_2 = "1.2" + VTPM_VERSION_2 = "2" + SWTPM_CUSE_PERM_ERROR = 252 ) func translateUser(username string) (*user.User, error) { @@ -120,7 +121,15 @@ func getSwtpmSetupCapabilities() ([]string, error) { } func getSwtpmCapabilities() ([]string, error) { - return getCapabilities(exec.Command("swtpm_cuse", "--print-capabilities")) + caps, err := getCapabilities(exec.Command("swtpm_cuse", "--print-capabilities")) + if err == nil { + return caps, nil + } + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == SWTPM_CUSE_PERM_ERROR { + // https://github.com/stefanberger/swtpm/blob/master/src/swtpm/cuse_tpm.c#L1806 + return nil, fmt.Errorf("rootless container can not have vtpm devices: %w", err) + } + return nil, err } func hasCapability(capabilities []string, capability string) bool { @@ -350,7 +359,7 @@ func (vtpm *VTPM) chownStatePath() error { return nil }) if err != nil { - return err + return fmt.Errorf("error on walk: %w", err) } if uid != 0 { @@ -452,7 +461,7 @@ func (vtpm *VTPM) waitForTPMDevice(loops int) error { for loops >= 0 { if _, err := os.Stat(pidfile); err != nil { logrus.Errorf("swtpm process has terminated") - return err + return fmt.Errorf("waitForTPMDevice swtpm process has terminated: %w", err) } if _, err := os.Stat(devpath); err == nil { @@ -539,12 +548,12 @@ func (vtpm *VTPM) startSwtpm() error { // callback will be called. vtpm.Pid, err = vtpm.waitForPidFile(10) if err != nil { - return err + return fmt.Errorf("wait for PidFile: %w", err) } err = vtpm.waitForTPMDevice(50) if err != nil { - return err + return fmt.Errorf("wait for waitForTPMDevice: %w", err) } vtpm.resetSELinux() @@ -603,6 +612,11 @@ func (vtpm *VTPM) Start() (bool, error) { return false, err } + err = vtpm.runSwtpmSetup() + if err != nil { + return false, err + } + err = vtpm.startSwtpm() if err != nil { return false, err @@ -772,6 +786,10 @@ func (vtpm *VTPM) setupSELinux() error { return selinux.SetFileLabel(path, fileLabel) }) + if err != nil { + return fmt.Errorf("error on walk: %w", err) + } + err = selinux.SetFSCreateLabel(fileLabel) if err != nil { return err diff --git a/script/swtpm.sh b/script/swtpm.sh index e8444d753a5..0e745fdbacb 100755 --- a/script/swtpm.sh +++ b/script/swtpm.sh @@ -37,7 +37,8 @@ function build_swtpm() { tar xf "v${swtpm_ver}.tar.gz" -C "$swtpm_srcdir" echo $(ls -la $swtpm_srcdir) pushd "$swtpm_srcdir/swtpm-${swtpm_ver}" || return - ./autogen.sh --with-openssl --with-cuse --prefix=/usr --enable-debug + + ./autogen.sh --with-openssl --with-gnutls --with-cuse --prefix=/usr --enable-debug make -j4 # make -j4 check make install diff --git a/tests/cmd/tpm-helper/main.go b/tests/cmd/tpm-helper/main.go index bcecb8654ed..d6ddd3fa75d 100644 --- a/tests/cmd/tpm-helper/main.go +++ b/tests/cmd/tpm-helper/main.go @@ -2,51 +2,221 @@ package main import ( "context" + "crypto/sha1" + "crypto/x509" "flag" "fmt" + "io" "log" + "os" + "strconv" + "strings" "time" - "github.com/google/go-tpm/legacy/tpm2" "github.com/google/go-tpm/tpm" + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpm2/transport" + "github.com/opencontainers/runc/libcontainer/vtpm" ) +const ( + OwnerAuth = "owner12345" + SRKAuth = "srk12345" +) + var ( deviceVersion = flag.String("deviceVersion", "2", "version of TPM to use") devicePath = flag.String("devicePath", "/dev/tpm0", "path to the device") + deviceCommand = flag.String("deviceCommand", "random", "command to execute using TPM device") + hashAlgo = flag.String("hashAlgo", "sha256", "hash algo to use in read pcr") + prcIndexes = flag.String("pcrs", "16", "array of pcr indexes") ) const ( RandomDataLen = 16 ) -func WorkWithTPM2(devicePath string, dataCh chan []byte, errCh chan error) { - rwc, err := tpm2.OpenTPM(devicePath) - if err != nil { - errCh <- fmt.Errorf("Can not open tpm2 device path %s: %+v", devicePath, err) - return +func convertAlgNameToHashAlgoIDTPM2(algo string) tpm2.TPMAlgID { + switch algo { + case "sha1": + return tpm2.TPMAlgSHA1 + case "sha256": + return tpm2.TPMAlgSHA256 + case "sha384": + return tpm2.TPMAlgSHA384 + case "sha512": + return tpm2.TPMAlgSHA512 + default: + return tpm2.TPMAlgNull } - data, err := tpm2.GetRandom(rwc, RandomDataLen) +} + +func WorkWithTPM2(devicePath string, command string, dataCh chan []byte, errCh chan error) { + + var rwc io.ReadWriteCloser + f, err := os.OpenFile(devicePath, os.O_RDWR, 0600) if err != nil { - errCh <- fmt.Errorf("Can not get random data from tpm2 device path %s: %+v", devicePath, err) + errCh <- fmt.Errorf("can not open tpm2 device path %s: %w", devicePath, err) return } - dataCh <- data + rwc = io.ReadWriteCloser(f) + + tpmInterface := transport.FromReadWriter(rwc) + switch command { + case "random": + cmd := tpm2.GetRandom{ + BytesRequested: RandomDataLen, + } + data, err := cmd.Execute(tpmInterface) + if err != nil { + errCh <- fmt.Errorf("Can not get random data from tpm2 device path %s: %+v", devicePath, err) + return + } + dataCh <- data.RandomBytes.Buffer + case "pcr": + var pcrs []uint + for _, indexStr := range strings.Split(*prcIndexes, ",") { + index, err := strconv.Atoi(indexStr) + if err != nil { + errCh <- fmt.Errorf("Can not parse PCR index %s: %w", indexStr, err) + return + } + pcrs = append(pcrs, uint(index)) + } + cmd := tpm2.PCRRead{ + PCRSelectionIn: tpm2.TPMLPCRSelection{ + PCRSelections: []tpm2.TPMSPCRSelection{ + { + Hash: convertAlgNameToHashAlgoIDTPM2(*hashAlgo), + PCRSelect: tpm2.PCClientCompatible.PCRs(pcrs...), + }, + }, + }, + } + mp, err := cmd.Execute(tpmInterface) + if err != nil { + errCh <- fmt.Errorf("Can not get PCR data from tpm2 device path %s: %+v", devicePath, err) + return + } + + var data []byte + + for _, dt := range mp.PCRValues.Digests { + data = append(data, dt.Buffer...) + } + + if len(data) == 0 { + errCh <- fmt.Errorf("selected pcrs are blanked") + return + } + dataCh <- data + + case "pubek": + var cmd tpm2.ReadPublic + cmd.ObjectHandle = tpm2.TPMHandle(0x81010001) + resp, err := cmd.Execute(tpmInterface) + if err != nil { + errCh <- fmt.Errorf("Can not get public ek data from tpm2 device path %s: %+v", devicePath, err) + return + } + dataCh <- resp.OutPublic.Bytes() + case "cert": + var cmd tpm2.NVReadPublic + cmd.NVIndex = tpm2.TPMHandle(0x01C00002) + resp, err := cmd.Execute(tpmInterface) + if err != nil { + errCh <- fmt.Errorf("Can not get public ek cert from tpm2 device path %s: %+v", devicePath, err) + return + } + dataCh <- resp.NVName.Buffer + default: + errCh <- fmt.Errorf("not defined command %s", command) + } } -func WorkWithTPM12(devicePath string, dataCh chan []byte, errCh chan error) { +func WorkWithTPM12(devicePath string, command string, dataCh chan []byte, errCh chan error) { rwc, err := tpm.OpenTPM(devicePath) if err != nil { errCh <- fmt.Errorf("Can not open tpm12 device path %s: %+v", devicePath, err) return } - data, err := tpm.GetRandom(rwc, RandomDataLen) - if err != nil { - errCh <- fmt.Errorf("Can not get random data from tpm12 device path %s: %+v", devicePath, err) - return + switch command { + case "random": + data, err := tpm.GetRandom(rwc, RandomDataLen) + if err != nil { + errCh <- fmt.Errorf("Can not get random data from tpm12 device path %s: %+v", devicePath, err) + return + } + dataCh <- data + case "pcr": + var indexes []int + for _, indexStr := range strings.Split(*prcIndexes, ",") { + index, err := strconv.Atoi(indexStr) + if err != nil { + errCh <- fmt.Errorf("Can not parse PCR index %s: %w", indexStr, err) + return + } + indexes = append(indexes, index) + } + data, err := tpm.FetchPCRValues(rwc, indexes) + if err != nil { + errCh <- fmt.Errorf("Can not get PCR data from tpm 1.2 device path %s: %+v", devicePath, err) + return + } + if len(data) == 0 { + errCh <- fmt.Errorf("selected pcrs are blanked") + return + } + dataCh <- data + case "pubek": + data, err := tpm.ReadPubEK(rwc) + if err != nil { + errCh <- fmt.Errorf("Can not get public ek data from tpm 1.2 device path %s: %+v", devicePath, err) + return + } + dataCh <- data + case "owner": + data, err := tpm.ReadPubEK(rwc) + if err != nil { + errCh <- fmt.Errorf("Can not get public ek data from tpm 1.2 device path %s: %+v", devicePath, err) + return + } + + var ownerAuth [20]byte + oa := sha1.Sum([]byte(OwnerAuth)) + copy(ownerAuth[:], oa[:]) + + var srkAuth [20]byte + sa := sha1.Sum([]byte(SRKAuth)) + copy(srkAuth[:], sa[:]) + + err = tpm.TakeOwnership(rwc, ownerAuth, srkAuth, data) + if err != nil { + errCh <- fmt.Errorf("Can not get set ownership from tpm 1.2 device path %s: %+v", devicePath, err) + return + } + + dataCh <- []byte{} + case "cert": + var ownerAuth [20]byte + oa := sha1.Sum([]byte(OwnerAuth)) + copy(ownerAuth[:], oa[:]) + + data, err := tpm.ReadEKCert(rwc, ownerAuth) + if err != nil { + errCh <- fmt.Errorf("Can not get ek cert data from tpm 1.2 device path %s: %+v", devicePath, err) + return + } + x509cert, err := x509.ParseCertificate(data) + if err != nil { + errCh <- fmt.Errorf("Can not get unmarshall x509 ek cert from tpm 1.2 device path %s: %+v", devicePath, err) + return + } + dataCh <- x509cert.Raw + default: + errCh <- fmt.Errorf("not defined command %s", command) } - dataCh <- data } func main() { @@ -56,7 +226,7 @@ func main() { errCh := make(chan error) switch *deviceVersion { case vtpm.VTPM_VERSION_2: - go WorkWithTPM2(*devicePath, dataCh, errCh) + go WorkWithTPM2(*devicePath, *deviceCommand, dataCh, errCh) select { case <-dataCh: break @@ -66,7 +236,7 @@ func main() { log.Fatalf("Time exceed to get random from TPM2 device") } case vtpm.VTPM_VERSION_1_2: - go WorkWithTPM12(*devicePath, dataCh, errCh) + go WorkWithTPM12(*devicePath, *deviceCommand, dataCh, errCh) select { case <-dataCh: break diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index 69e6b9e7256..372d56fb59b 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -2,6 +2,8 @@ load helpers function setup() { + # Root privileges are required by swtpm_cuse. + # https://github.com/stefanberger/swtpm/blob/master/src/swtpm/cuse_tpm.c#L1806 requires root rm /etc/swtpm/runc.conf || true setup_debian @@ -352,3 +354,106 @@ function teardown() { runc exec vtpm_in_joined_userns "${HELPER}" -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-vtpm_in_joined_userns-joined_userns -deviceVersion=2 [ "$status" -eq 0 ] } + +@test "runc check swtpm_setup" { + HELPER="tpm-helper" + cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + vtpm_path=$(mktemp -d) + + update_config ' .process.args = ["/bin/sh"] + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "12345", "vtpmname" : "tpmsetup", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsetup -deviceVersion=2 || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsetup -deviceVersion=2 -deviceCommand=pcr -pcrs=10,16 -hashAlgo=sha256 || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read activated pcrs from container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsetup -deviceVersion=2 -deviceCommand=pcr -pcrs=10,16 -hashAlgo=sha1 || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read activated pcrs from container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsetup -deviceVersion=2 -deviceCommand=pcr -pcrs=10,16 -hashAlgo=sha512 || ret=$? + if [ "$ret" -eq 0 ]; then + fail "should not be able to read deactivated pcrs from container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsetup -deviceVersion=2 -deviceCommand=pubek || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read pubek from container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst-tpmsetup -deviceVersion=2 -deviceCommand=cert || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read ek cert from container created by swtpm_setup" + fi + + runc delete --force tst + [ "$status" -eq 0 ] + + # wrong encryption password + update_config ' .process.args = ["/bin/sh"] + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "54321", "vtpmname" : "tpmsetup", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst1 + [ "$status" -ne 0 ] + + # password with encryption + update_config ' .process.args = ["/bin/sh"] + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "pass=12345", "vtpmname" : "tpmsetup", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst2 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst2 + + runc delete --force tst2 + [ "$status" -eq 0 ] + + vtpm_path1=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "statePathIsManaged": true, "vtpmversion": "1.2", "createCerts": true, "encryptionPassword": "12345", "vtpmname" : "tpmsetup", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst3 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst3 + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst3-tpmsetup -deviceVersion=1.2 || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read from container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst3-tpmsetup -deviceVersion=1.2 -deviceCommand=pcr -pcrs=10,16 || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read activated pcrs from container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst3-tpmsetup -deviceVersion=1.2 -deviceCommand=pubek || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read pubek from container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst3-tpmsetup -deviceVersion=1.2 -deviceCommand=owner || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to set owner to container created by swtpm_setup" + fi + + ret=0 + ${TESTBINDIR}/${HELPER} -devicePath=/dev/tpm"$ROOT_HASH_OFFSET"-tst3-tpmsetup -deviceVersion=1.2 -deviceCommand=cert || ret=$? + if [ "$ret" -ne 0 ]; then + fail "should be able to read ek certs from container created by swtpm_setup" + fi +} diff --git a/vendor/github.com/google/go-tpm/tpm2/audit.go b/vendor/github.com/google/go-tpm/tpm2/audit.go new file mode 100644 index 00000000000..95ad2cb0281 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/audit.go @@ -0,0 +1,87 @@ +package tpm2 + +import ( + "bytes" + "fmt" + "reflect" +) + +// CommandAudit represents an audit session for attesting the execution of a +// series of commands in the TPM. It is useful for both command and session +// auditing. +type CommandAudit struct { + hash TPMIAlgHash + digest []byte +} + +// NewAudit initializes a new CommandAudit with the specified hash algorithm. +func NewAudit(hash TPMIAlgHash) (*CommandAudit, error) { + h, err := hash.Hash() + if err != nil { + return nil, err + } + return &CommandAudit{ + hash: hash, + digest: make([]byte, h.Size()), + }, nil +} + +// AuditCommand extends the audit digest with the given command and response. +// Go Generics do not allow type parameters on methods, otherwise this would be +// a method on CommandAudit. +// See https://github.com/golang/go/issues/49085 for more information. +func AuditCommand[C Command[R, *R], R any](a *CommandAudit, cmd C, rsp *R) error { + cc := cmd.Command() + cpHash, err := auditCPHash[R](cc, a.hash, cmd) + if err != nil { + return err + } + rpHash, err := auditRPHash(cc, a.hash, rsp) + if err != nil { + return err + } + ha, err := a.hash.Hash() + if err != nil { + return err + } + h := ha.New() + h.Write(a.digest) + h.Write(cpHash) + h.Write(rpHash) + a.digest = h.Sum(nil) + return nil +} + +// Digest returns the current digest of the audit. +func (a *CommandAudit) Digest() []byte { + return a.digest +} + +// auditCPHash calculates the command parameter hash for a given command with +// the given hash algorithm. The command is assumed to not have any decrypt +// sessions. +func auditCPHash[R any](cc TPMCC, h TPMIAlgHash, c Command[R, *R]) ([]byte, error) { + names, err := cmdNames(c) + if err != nil { + return nil, err + } + parms, err := cmdParameters(c, nil) + if err != nil { + return nil, err + } + return cpHash(h, cc, names, parms) +} + +// auditRPHash calculates the response parameter hash for a given response with +// the given hash algorithm. The command is assumed to be successful and to not +// have any encrypt sessions. +func auditRPHash(cc TPMCC, h TPMIAlgHash, r any) ([]byte, error) { + var parms bytes.Buffer + parameters := taggedMembers(reflect.ValueOf(r).Elem(), "handle", true) + for i, parameter := range parameters { + if err := marshal(&parms, parameter); err != nil { + return nil, fmt.Errorf("marshalling parameter %v: %w", i+1, err) + } + } + return rpHash(h, TPMRCSuccess, cc, parms.Bytes()) +} diff --git a/vendor/github.com/google/go-tpm/tpm2/bitfield.go b/vendor/github.com/google/go-tpm/tpm2/bitfield.go new file mode 100644 index 00000000000..c8f52b6f2e5 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/bitfield.go @@ -0,0 +1,82 @@ +package tpm2 + +import ( + "fmt" +) + +// Bitfield represents a TPM bitfield (i.e., TPMA_*) type. +type Bitfield interface { + // Length returns the length of the bitfield. + Length() int +} + +// BitGetter represents a TPM bitfield (i.e., TPMA_*) type that can be read. +type BitGetter interface { + Bitfield + // GetReservedBit returns the value of the given reserved bit. + // If the bit is not reserved, returns false. + GetReservedBit(pos int) bool +} + +// BitSetter represents a TPM bitfield (i.e., TPMA_*) type that can be written. +type BitSetter interface { + Bitfield + // GetReservedBit sets the value of the given reserved bit. + SetReservedBit(pos int, val bool) +} + +func checkPos(pos int, len int) { + if pos >= len || pos < 0 { + panic(fmt.Errorf("bit %d out of range for %d-bit field", pos, len)) + } +} + +// bitfield8 represents an 8-bit bitfield which may have reserved bits. +// 8-bit TPMA_* types embed this one, and the reserved bits are stored in it. +type bitfield8 uint8 + +// Length implements the Bitfield interface. +func (bitfield8) Length() int { + return 8 +} + +// GetReservedBit implements the BitGetter interface. +func (r bitfield8) GetReservedBit(pos int) bool { + checkPos(pos, 8) + return r&(1<> 8) + r &= 0xFFFFF0FF + return true, TPMFmt1Error{ + canonical: r, + subject: subj, + index: idx, + } +} + +// IsWarning returns true if the error is a warning code. +// This usually indicates a problem with the TPM state, and not the command. +// Retrying the command later may succeed. +func (r TPMRC) IsWarning() bool { + if isFmt1, _ := r.isFmt1Error(); isFmt1 { + // There aren't any format-1 warnings. + return false + } + return (r&rcVer1) == rcVer1 && (r&rcWarn) == rcWarn +} + +// Error produces a nice human-readable representation of the error, parsing TPM +// FMT1 errors as needed. +func (r TPMRC) Error() string { + if isFmt1, fmt1 := r.isFmt1Error(); isFmt1 { + return fmt1.Error() + } + if r.isFmt0Error() { + desc, ok := fmt0Descs[r] + if !ok { + return fmt.Sprintf("unknown format-0 error code (0x%x)", uint32(r)) + } + return fmt.Sprintf("%s: %s", desc.name, desc.description) + } + if r.IsWarning() { + desc, ok := warnDescs[r] + if !ok { + return fmt.Sprintf("unknown warning (0x%x)", uint32(r)) + } + return fmt.Sprintf("%s: %s", desc.name, desc.description) + } + return fmt.Sprintf("unrecognized error code (0x%x)", uint32(r)) +} + +// Is returns whether the TPMRC (which may be a FMT1 error) is equal to the +// given canonical error. +func (r TPMRC) Is(target error) bool { + targetTPMRC, ok := target.(TPMRC) + if !ok { + return false + } + if isFmt1, fmt1 := r.isFmt1Error(); isFmt1 { + return fmt1.canonical == targetTPMRC + } + return r == targetTPMRC +} + +// As returns whether the error can be assigned to the given interface type. +// If supported, it updates the value pointed at by target. +// Supports the Fmt1Error type. +func (r TPMRC) As(target interface{}) bool { + pFmt1, ok := target.(*TPMFmt1Error) + if !ok { + return false + } + isFmt1, fmt1 := r.isFmt1Error() + if !isFmt1 { + return false + } + *pFmt1 = fmt1 + return true +} diff --git a/vendor/github.com/google/go-tpm/tpm2/kdf.go b/vendor/github.com/google/go-tpm/tpm2/kdf.go new file mode 100644 index 00000000000..569a4daf8a5 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/kdf.go @@ -0,0 +1,29 @@ +package tpm2 + +import ( + "crypto" + + legacy "github.com/google/go-tpm/legacy/tpm2" +) + +// KDFa implements TPM 2.0's default key derivation function, as defined in +// section 11.4.9.2 of the TPM revision 2 specification part 1. +// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ +// The key & label parameters must not be zero length. +// The label parameter is a non-null-terminated string. +// The contextU & contextV parameters are optional. +func KDFa(h crypto.Hash, key []byte, label string, contextU, contextV []byte, bits int) []byte { + return legacy.KDFaHash(h, key, label, contextU, contextV, bits) +} + +// KDFe implements TPM 2.0's ECDH key derivation function, as defined in +// section 11.4.9.3 of the TPM revision 2 specification part 1. +// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ +// The z parameter is the x coordinate of one party's private ECC key multiplied +// by the other party's public ECC point. +// The use parameter is a non-null-terminated string. +// The partyUInfo and partyVInfo are the x coordinates of the initiator's and +// the responder's ECC points, respectively. +func KDFe(h crypto.Hash, z []byte, use string, partyUInfo, partyVInfo []byte, bits int) []byte { + return legacy.KDFeHash(h, z, use, partyUInfo, partyVInfo, bits) +} diff --git a/vendor/github.com/google/go-tpm/tpm2/labeled_kem_convert.go b/vendor/github.com/google/go-tpm/tpm2/labeled_kem_convert.go new file mode 100644 index 00000000000..047d278f157 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/labeled_kem_convert.go @@ -0,0 +1,33 @@ +package tpm2 + +import ( + "errors" + "fmt" + "io" +) + +var ( + ErrUnsupportedType = errors.New("unsupported key type") +) + +// An LabeledEncapsulationKey represents a public key used in a TPM labeled-encapsulation scheme. +type LabeledEncapsulationKey interface { + // Encapsulate performs the labeled key encapsulation. + Encapsulate(random io.Reader, label string) (secret []byte, ciphertext []byte, err error) + // NameAlg fetches the Name hash algorithm of the encapsulation key. + NameAlg() TPMAlgID + // SymmetricParameters fetches the symmetric parameters for protection. + SymmetricParameters() *TPMTSymDefObject +} + +// ImportEncapsulationKey imports the TPM-form public key as a LabeledEncapsulationkey. +func ImportEncapsulationKey(pub *TPMTPublic) (LabeledEncapsulationKey, error) { + switch pub.Type { + case TPMAlgRSA: + return importRSAEncapsulationKey(pub) + case TPMAlgECC: + return importECCEncapsulationKey(pub) + default: + return nil, fmt.Errorf("%w %v", ErrUnsupportedType, pub.Type) + } +} diff --git a/vendor/github.com/google/go-tpm/tpm2/labeled_kem_ecc.go b/vendor/github.com/google/go-tpm/tpm2/labeled_kem_ecc.go new file mode 100644 index 00000000000..0c36df4d392 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/labeled_kem_ecc.go @@ -0,0 +1,107 @@ +package tpm2 + +import ( + "crypto/ecdh" + "errors" + "fmt" + "io" +) + +var ( + // The curve is not supported. + ErrUnsupportedCurve = errors.New("unsupported curve") + // There was an internal error parsing the ephemeral public key during encapsulation. + ErrBadEphemeralKey = errors.New("bad ephemeral ECC key") +) + +// An eccKey is an One-Pass-Diffie-Hellman-based Labeled Encapsulation key. +type eccKey struct { + // The actual public key. + eccPub *ecdh.PublicKey + // The name algorithm of the key. + nameAlg TPMIAlgHash + // The symmetric parameters of the key. + symParms *TPMTSymDefObject +} + +// importECCEncapsulationKey imports an ECC key for use in labeled encapsulation. +func importECCEncapsulationKey(pub *TPMTPublic) (*eccKey, error) { + eccParms, err := pub.Parameters.ECCDetail() + if err != nil { + return nil, err + } + eccPub, err := pub.Unique.ECC() + if err != nil { + return nil, err + } + ecdhPub, err := ECDHPub(eccParms, eccPub) + if err != nil { + return nil, err + } + + return &eccKey{ + eccPub: ecdhPub, + nameAlg: pub.NameAlg, + symParms: &eccParms.Symmetric, + }, nil +} + +// getXY gets the big-endian X/Y coordinates as full-length buffers. +func getXY(pub *ecdh.PublicKey) ([]byte, []byte, error) { + // Check and strip the leading 0x04 byte, which indicates an uncompressed ECC point. + rawPub := pub.Bytes() + if len(rawPub) == 0 || rawPub[0] != 0x04 { + return nil, nil, fmt.Errorf("%w: could not decode %x as an uncompressed point", ErrBadEphemeralKey, rawPub) + } + rawPub = rawPub[1:] + return rawPub[:len(rawPub)/2], rawPub[len(rawPub)/2:], nil +} + +// Encapsulate implements LabeledEncapsulationKey. +func (pub *eccKey) Encapsulate(random io.Reader, label string) (secret []byte, ciphertext []byte, err error) { + ephemeralPriv, err := pub.eccPub.Curve().GenerateKey(random) + if err != nil { + return nil, nil, err + } + return pub.encapsulateDerandomized(ephemeralPriv, label) +} + +// encapsulateDerandomized is a derandomized internal version of Encapsulate for testing. +func (pub *eccKey) encapsulateDerandomized(ephPrivate *ecdh.PrivateKey, label string) (secret []byte, ciphertext []byte, err error) { + nameHash, err := pub.nameAlg.Hash() + if err != nil { + return nil, nil, err + } + pubX, _, err := getXY(pub.eccPub) + if err != nil { + return nil, nil, err + } + ephX, ephY, err := getXY(ephPrivate.PublicKey()) + if err != nil { + return nil, nil, err + } + z, err := ephPrivate.ECDH(pub.eccPub) + if err != nil { + return nil, nil, err + } + secret = KDFe(nameHash, z, label, ephX, pubX, nameHash.Size()*8) + ciphertext = Marshal(TPMSECCPoint{ + X: TPM2BECCParameter{ + Buffer: ephX, + }, + Y: TPM2BECCParameter{ + Buffer: ephY, + }, + }) + return secret, ciphertext, nil +} + +// NameAlg implements LabeledEncapsulationKey. +func (pub *eccKey) NameAlg() TPMAlgID { + return pub.nameAlg +} + +// SymmetricParameters implements LabeledEncapsulationkey. +func (pub *eccKey) SymmetricParameters() *TPMTSymDefObject { + return pub.symParms +} diff --git a/vendor/github.com/google/go-tpm/tpm2/labeled_kem_rsa.go b/vendor/github.com/google/go-tpm/tpm2/labeled_kem_rsa.go new file mode 100644 index 00000000000..863f7c99437 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/labeled_kem_rsa.go @@ -0,0 +1,112 @@ +package tpm2 + +import ( + "crypto" + "crypto/rsa" + "errors" + "fmt" + "io" + "strings" +) + +var ( + // The source of randomness used for encapsulation ran out of data. + ErrInsufficientRandom = errors.New("random source did not provide enough data") +) + +// An rsaKey is an RSA-OAEP-based Labeled Encapsulation key. +type rsaKey struct { + // The actual public key. + rsaPub rsa.PublicKey + // The scheme hash algorithm to use for the OAEP-based encapsulation. + hash crypto.Hash + // The name algorithm of the key. + nameAlg TPMIAlgHash + // The symmetric parameters of the key. + symParms *TPMTSymDefObject +} + +func importRSAEncapsulationKey(pub *TPMTPublic) (*rsaKey, error) { + rsaParms, err := pub.Parameters.RSADetail() + if err != nil { + return nil, err + } + rsaPub, err := pub.Unique.RSA() + if err != nil { + return nil, err + } + rsa, err := RSAPub(rsaParms, rsaPub) + if err != nil { + return nil, err + } + + // Decide what hash algorithm to use for OAEP. + // It's the scheme hash algorithm if not null, otherwise it's the name algorithm. + hashAlgID := pub.NameAlg + if rsaParms.Scheme.Scheme == TPMAlgOAEP { + oaep, err := rsaParms.Scheme.Details.OAEP() + if err != nil { + return nil, err + } + if oaep.HashAlg != TPMAlgNull { + hashAlgID = oaep.HashAlg + } + } + hashAlg, err := hashAlgID.Hash() + if err != nil { + return nil, err + } + + return &rsaKey{ + rsaPub: *rsa, + hash: hashAlg, + nameAlg: pub.NameAlg, + symParms: &rsaParms.Symmetric, + }, nil +} + +// Encapsulate performs the OAEP-based RSA Labeled Encapsulation. +func (pub *rsaKey) Encapsulate(random io.Reader, label string) (secret []byte, ciphertext []byte, err error) { + secret = make([]byte, pub.hash.Size()) + n, err := random.Read(secret) + if err != nil { + return nil, nil, err + } + if n != len(secret) { + return nil, nil, fmt.Errorf("%w: only read %d bytes but %d were needed", ErrInsufficientRandom, n, len(secret)) + } + + ciphertext, err = pub.encapsulateDerandomized(random, secret, label) + if err != nil { + return nil, nil, err + } + return secret, ciphertext, err +} + +// encapsulateDerandomized is a derandomized internal version of Encapsulate for testing. +func (pub *rsaKey) encapsulateDerandomized(oaepSaltReader io.Reader, secret []byte, label string) (ciphertext []byte, err error) { + // Ensure label is null-terminated. + if !strings.HasSuffix(label, "\x00") { + label = label + "\x00" + } + + if len(secret) != pub.hash.Size() { + return nil, fmt.Errorf("%w: secret was only %d bytes but %d were needed", ErrInsufficientRandom, len(secret), pub.hash.Size()) + } + + ciphertext, err = rsa.EncryptOAEP(pub.hash.New(), oaepSaltReader, &pub.rsaPub, secret, []byte(label)) + if err != nil { + return nil, err + } + return ciphertext, err +} + +// NameAlg implements LabeledEncapsulationKey. +func (pub *rsaKey) NameAlg() TPMAlgID { + return pub.nameAlg +} + +// SymmetricParameters implements LabeledEncapsulationkey. +func (pub *rsaKey) SymmetricParameters() *TPMTSymDefObject { + return pub.symParms +} diff --git a/vendor/github.com/google/go-tpm/tpm2/marshalling.go b/vendor/github.com/google/go-tpm/tpm2/marshalling.go new file mode 100644 index 00000000000..04d16074611 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/marshalling.go @@ -0,0 +1,128 @@ +package tpm2 + +import ( + "bytes" + "fmt" + "reflect" +) + +// Marshallable represents any TPM type that can be marshalled. +type Marshallable interface { + // marshal will serialize the given value, appending onto the given buffer. + // Returns an error if the value is not marshallable. + marshal(buf *bytes.Buffer) +} + +// marshallableWithHint represents any TPM type that can be marshalled, +// but that requires a selector ("hint") value when marshalling. Most TPMU_ are +// an example of this. +type marshallableWithHint interface { + // get will return the corresponding union member by copy. If the union is + // uninitialized, it will initialize a new zero-valued one. + get(hint int64) (reflect.Value, error) +} + +// Unmarshallable represents any TPM type that can be marshalled or unmarshalled. +type Unmarshallable interface { + Marshallable + // marshal will deserialize the given value from the given buffer. + // Returns an error if there was an unmarshalling error or if there was not + // enough data in the buffer. + unmarshal(buf *bytes.Buffer) error +} + +// unmarshallableWithHint represents any TPM type that can be marshalled or unmarshalled, +// but that requires a selector ("hint") value when unmarshalling. Most TPMU_ are +// an example of this. +type unmarshallableWithHint interface { + marshallableWithHint + // create will instantiate and return the corresponding union member. + create(hint int64) (reflect.Value, error) +} + +// Marshal will serialize the given values, returning them as a byte slice. +func Marshal(v Marshallable) []byte { + var buf bytes.Buffer + if err := marshal(&buf, reflect.ValueOf(v)); err != nil { + panic(fmt.Sprintf("unexpected error marshalling %v: %v", reflect.TypeOf(v).Name(), err)) + } + return buf.Bytes() +} + +// Unmarshal unmarshals the given type from the byte array. +// Returns an error if the buffer does not contain enough data to satisfy the +// types, or if the types are not unmarshallable. +func Unmarshal[T Marshallable, P interface { + *T + Unmarshallable +}](data []byte) (*T, error) { + buf := bytes.NewBuffer(data) + var t T + value := reflect.New(reflect.TypeOf(t)) + if err := unmarshal(buf, value.Elem()); err != nil { + return nil, err + } + return value.Interface().(*T), nil +} + +// marshallableByReflection is a placeholder interface, to hint to the unmarshalling +// library that it is supposed to use reflection. +type marshallableByReflection interface { + reflectionSafe() +} + +// marshalByReflection is embedded into any type that can be marshalled by reflection, +// needing no custom logic. +type marshalByReflection struct{} + +func (marshalByReflection) reflectionSafe() {} + +// These placeholders are required because a type constraint cannot union another interface +// that contains methods. +// Otherwise, marshalByReflection would not implement Unmarshallable, and the Marshal/Unmarshal +// functions would accept interface{ Marshallable | marshallableByReflection } instead. + +// Placeholder: because this type implements the defaultMarshallable interface, +// the reflection library knows not to call this. +func (marshalByReflection) marshal(_ *bytes.Buffer) { + panic("not implemented") +} + +// Placeholder: because this type implements the defaultMarshallable interface, +// the reflection library knows not to call this. +func (*marshalByReflection) unmarshal(_ *bytes.Buffer) error { + panic("not implemented") +} + +// boxed is a helper type for corner cases such as unions, where all members must be structs. +type boxed[T any] struct { + Contents *T +} + +// box will put a value into a box. +func box[T any](contents *T) boxed[T] { + return boxed[T]{ + Contents: contents, + } +} + +// unbox will take a value out of a box. +func (b *boxed[T]) unbox() *T { + return b.Contents +} + +// marshal implements the Marshallable interface. +func (b *boxed[T]) marshal(buf *bytes.Buffer) { + if b.Contents == nil { + var contents T + marshal(buf, reflect.ValueOf(&contents)) + } else { + marshal(buf, reflect.ValueOf(b.Contents)) + } +} + +// unmarshal implements the Unmarshallable interface. +func (b *boxed[T]) unmarshal(buf *bytes.Buffer) error { + b.Contents = new(T) + return unmarshal(buf, reflect.ValueOf(b.Contents)) +} diff --git a/vendor/github.com/google/go-tpm/tpm2/names.go b/vendor/github.com/google/go-tpm/tpm2/names.go new file mode 100644 index 00000000000..4b5741d0dfa --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/names.go @@ -0,0 +1,62 @@ +package tpm2 + +import ( + "bytes" + "encoding/binary" + "reflect" +) + +// HandleName returns the TPM Name of a PCR, session, or permanent value +// (e.g., hierarchy) handle. +func HandleName(h TPMHandle) TPM2BName { + result := make([]byte, 4) + binary.BigEndian.PutUint32(result, uint32(h)) + return TPM2BName{ + Buffer: result, + } +} + +// objectOrNVName calculates the Name of an NV index or object. +// pub is a pointer to either a TPMTPublic or TPMSNVPublic. +func objectOrNVName(alg TPMAlgID, pub interface{}) (*TPM2BName, error) { + h, err := alg.Hash() + if err != nil { + return nil, err + } + + // Create a byte slice with the correct reserved size and marshal the + // NameAlg to it. + result := make([]byte, 2, 2+h.Size()) + binary.BigEndian.PutUint16(result, uint16(alg)) + + // Calculate the hash of the entire Public contents and append it to the + // result. + ha := h.New() + var buf bytes.Buffer + if err := marshal(&buf, reflect.ValueOf(pub)); err != nil { + return nil, err + } + ha.Write(buf.Bytes()) + result = ha.Sum(result) + + return &TPM2BName{ + Buffer: result, + }, nil +} + +// ObjectName returns the TPM Name of an object. +func ObjectName(p *TPMTPublic) (*TPM2BName, error) { + return objectOrNVName(p.NameAlg, p) +} + +// NVName returns the TPM Name of an NV index. +func NVName(p *TPMSNVPublic) (*TPM2BName, error) { + return objectOrNVName(p.NameAlg, p) +} + +// PrimaryHandleName returns the TPM Name of a primary handle. +func PrimaryHandleName(h TPMHandle) []byte { + result := make([]byte, 4) + binary.BigEndian.PutUint32(result, uint32(h)) + return result +} diff --git a/vendor/github.com/google/go-tpm/tpm2/pcrs.go b/vendor/github.com/google/go-tpm/tpm2/pcrs.go new file mode 100644 index 00000000000..3a250c52e53 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/pcrs.go @@ -0,0 +1,55 @@ +package tpm2 + +// pcrSelectionFormatter is a Platform TPM Profile-specific interface for +// formatting TPM PCR selections. +// This interface isn't (yet) part of the go-tpm public interface. After we +// add a second implementation, we should consider making it public. +type pcrSelectionFormatter interface { + // PCRs returns the TPM PCR selection bitmask associated with the given PCR indices. + PCRs(pcrs ...uint) []byte +} + +// PCClientCompatible is a pcrSelectionFormatter that formats PCR selections +// suitable for use in PC Client PTP-compatible TPMs (the vast majority): +// https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ +// PC Client mandates at least 24 PCRs but does not provide an upper limit. +var PCClientCompatible pcrSelectionFormatter = pcClient{} + +type pcClient struct{} + +// The TPM requires all PCR selections to be at least big enough to select all +// the PCRs in the minimum PCR allocation. +const pcClientMinimumPCRCount = 24 + +func (pcClient) PCRs(pcrs ...uint) []byte { + // Find the biggest PCR we selected. + maxPCR := uint(0) + for _, pcr := range pcrs { + if pcr > maxPCR { + maxPCR = pcr + } + } + selectionSize := maxPCR/8 + 1 + + // Enforce the minimum PCR selection size. + if selectionSize < (pcClientMinimumPCRCount / 8) { + selectionSize = (pcClientMinimumPCRCount / 8) + } + + // Allocate a byte array to store the bitfield, that has at least + // enough bits to store our selections. + selection := make([]byte, selectionSize) + for _, pcr := range pcrs { + // The PCR selection mask is byte-wise little-endian: + // select[0] contains bits representing the selection of PCRs 0 through 7 + // select[1] contains PCRs 8 through 15, and so on. + byteIdx := pcr / 8 + // Within the byte, the PCR selection is bit-wise big-endian: + // bit 0 of select[0] contains the selection of PCR 0 + // bit 1 of select[0] contains the selection of PCR 1, and so on. + bitIdx := pcr % 8 + + selection[byteIdx] |= (1 << bitIdx) + } + return selection +} diff --git a/vendor/github.com/google/go-tpm/tpm2/policy.go b/vendor/github.com/google/go-tpm/tpm2/policy.go new file mode 100644 index 00000000000..c64f17e4e1c --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/policy.go @@ -0,0 +1,60 @@ +package tpm2 + +import ( + "bytes" + "crypto" + "reflect" +) + +// PolicyCalculator represents a TPM 2.0 policy that needs to be calculated +// synthetically (i.e., without a TPM). +type PolicyCalculator struct { + alg TPMIAlgHash + hash crypto.Hash + state []byte +} + +// NewPolicyCalculator creates a fresh policy using the given hash algorithm. +func NewPolicyCalculator(alg TPMIAlgHash) (*PolicyCalculator, error) { + hash, err := alg.Hash() + if err != nil { + return nil, err + } + return &PolicyCalculator{ + alg: alg, + hash: hash, + state: make([]byte, hash.Size()), + }, nil +} + +// Reset resets the internal state of the policy hash to all 0x00. +func (p *PolicyCalculator) Reset() { + p.state = make([]byte, p.hash.Size()) +} + +// Update updates the internal state of the policy hash by appending the +// current state with the given contents, and updating the new state to the +// hash of that. +func (p *PolicyCalculator) Update(data ...interface{}) error { + hash := p.hash.New() + hash.Write(p.state) + var buf bytes.Buffer + for _, d := range data { + if err := marshal(&buf, reflect.ValueOf(d)); err != nil { + return err + } + } + hash.Write(buf.Bytes()) + p.state = hash.Sum(nil) + return nil +} + +// Hash returns the current state of the policy hash. +func (p *PolicyCalculator) Hash() *TPMTHA { + result := TPMTHA{ + HashAlg: p.alg, + Digest: make([]byte, len(p.state)), + } + copy(result.Digest, p.state) + return &result +} diff --git a/vendor/github.com/google/go-tpm/tpm2/reflect.go b/vendor/github.com/google/go-tpm/tpm2/reflect.go new file mode 100644 index 00000000000..d32473eba8f --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/reflect.go @@ -0,0 +1,1098 @@ +// Package tpm2 provides 1:1 mapping to TPM 2.0 APIs. +package tpm2 + +import ( + "bytes" + "encoding/binary" + "fmt" + "math" + "reflect" + "strconv" + "strings" + + "github.com/google/go-tpm/tpm2/transport" +) + +const ( + // Chosen based on MAX_DIGEST_BUFFER, the length of the longest + // reasonable list returned by the reference implementation. + // The maxListLength must be greater than MAX_CONTEXT_SIZE = 1344, + // in order to allow for the unmarshalling of Context. + maxListLength uint32 = 4096 +) + +// execute sends the provided command and returns the TPM's response. +func execute[R any](t transport.TPM, cmd Command[R, *R], rsp *R, extraSess ...Session) error { + cc := cmd.Command() + sess, err := cmdAuths(cmd) + if err != nil { + return err + } + sess = append(sess, extraSess...) + if len(sess) > 3 { + return fmt.Errorf("too many sessions: %v", len(sess)) + } + hasSessions := len(sess) > 0 + // Initialize the sessions, if needed + for i, s := range sess { + if err := s.Init(t); err != nil { + return fmt.Errorf("initializing session %d: %w", i, err) + } + if err := s.NewNonceCaller(); err != nil { + return err + } + } + handles, err := cmdHandles(cmd) + if err != nil { + return err + } + parms, err := cmdParameters(cmd, sess) + if err != nil { + return err + } + var names []TPM2BName + var sessions []byte + if hasSessions { + var err error + names, err = cmdNames(cmd) + if err != nil { + return err + } + sessions, err = cmdSessions(sess, cc, names, parms) + if err != nil { + return err + } + } + hdr := cmdHeader(hasSessions, 10 /* size of command header */ +len(handles)+len(sessions)+len(parms), cc) + command := append(hdr, handles...) + command = append(command, sessions...) + command = append(command, parms...) + + // Send the command via the transport. + response, err := t.Send(command) + if err != nil { + return err + } + + // Parse the command tpm2ly into the response structure. + rspBuf := bytes.NewBuffer(response) + err = rspHeader(rspBuf) + if err != nil { + var bonusErrs []string + // Emergency cleanup, then return. + for _, s := range sess { + if err := s.CleanupFailure(t); err != nil { + bonusErrs = append(bonusErrs, err.Error()) + } + } + if len(bonusErrs) != 0 { + return fmt.Errorf("%w - additional errors encountered during cleanup: %v", err, strings.Join(bonusErrs, ", ")) + } + return err + } + err = rspHandles(rspBuf, rsp) + if err != nil { + return err + } + rspParms, err := rspParametersArea(hasSessions, rspBuf) + if err != nil { + return err + } + if hasSessions { + // We don't need the TPM RC here because we would have errored + // out from rspHeader + // TODO: Authenticate the error code with sessions, if desired. + err = rspSessions(rspBuf, TPMRCSuccess, cc, names, rspParms, sess) + if err != nil { + return err + } + } + err = rspParameters(rspParms, sess, rsp) + if err != nil { + return err + } + + return nil +} + +func isMarshalledByReflection(v reflect.Value) bool { + var mbr marshallableByReflection + if v.Type().AssignableTo(reflect.TypeOf(&mbr).Elem()) { + return true + } + // basic types are also marshalled by reflection, as are empty structs + switch v.Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Array, reflect.Slice, reflect.Ptr: + return true + case reflect.Struct: + if v.NumField() == 0 { + return true + } + } + return false +} + +// marshal will serialize the given value, appending onto the given buffer. +// Returns an error if the value is not marshallable. +func marshal(buf *bytes.Buffer, v reflect.Value) error { + // If the type is not marshalled by reflection, try to call the custom marshal method. + if !isMarshalledByReflection(v) { + u, ok := v.Interface().(Marshallable) + if ok { + u.marshal(buf) + return nil + } + if v.CanAddr() { + // Maybe we got an addressable value whose pointer implements Marshallable + pu, ok := v.Addr().Interface().(Marshallable) + if ok { + pu.marshal(buf) + return nil + } + } + return fmt.Errorf("can't marshal: type %v does not implement Marshallable or marshallableByReflection", v.Type().Name()) + } + + // Otherwise, use reflection. + switch v.Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return marshalNumeric(buf, v) + case reflect.Array, reflect.Slice: + return marshalArray(buf, v) + case reflect.Struct: + return marshalStruct(buf, v) + case reflect.Ptr: + return marshal(buf, v.Elem()) + case reflect.Interface: + // Special case: there are very few TPM types which, for TPM spec + // backwards-compatibility reasons, are implemented as Go interfaces + // so that callers can ergonomically satisfy cases where the TPM spec + // allows a parameter to literally be one of a couple of types. + // In a few of these cases, we want the caller to be able to sensibly + // omit the data, and fill in reasonable defaults. + // These cases are enumerated here. + if v.IsNil() { + switch v.Type().Name() { + case "TPMUSensitiveCreate": + return marshal(buf, reflect.ValueOf(TPM2BSensitiveData{})) + default: + return fmt.Errorf("missing required value for %v interface", v.Type().Name()) + } + } + return marshal(buf, v.Elem()) + default: + return fmt.Errorf("not marshallable: %#v", v) + } +} + +// marshalOptional will serialize the given value, appending onto the given +// buffer. +// Special case: Part 3 specifies some input/output +// parameters as "optional", which means that they are +// sized fields that can be zero-length, even if the +// enclosed type has no legal empty serialization. +// When nil, marshal the zero size. +// Returns an error if the value is not marshallable. +func marshalOptional(buf *bytes.Buffer, v reflect.Value) error { + if v.Kind() == reflect.Ptr && v.IsNil() { + return marshalArray(buf, reflect.ValueOf([2]byte{})) + } + return marshal(buf, v) +} + +func marshalNumeric(buf *bytes.Buffer, v reflect.Value) error { + return binary.Write(buf, binary.BigEndian, v.Interface()) +} + +func marshalArray(buf *bytes.Buffer, v reflect.Value) error { + for i := 0; i < v.Len(); i++ { + if err := marshal(buf, v.Index(i)); err != nil { + return fmt.Errorf("marshalling element %d of %v: %v", i, v.Type(), err) + } + } + return nil +} + +// Marshals the members of the struct, handling sized and bitwise fields. +func marshalStruct(buf *bytes.Buffer, v reflect.Value) error { + // Check if this is a bitwise-defined structure. This requires all the + // members to be bitwise-defined. + numBitwise := 0 + numChecked := 0 + for i := 0; i < v.NumField(); i++ { + // Ignore embedded Bitfield hints. + if !v.Type().Field(i).IsExported() { + //if _, isBitfield := v.Field(i).Interface().(TPMABitfield); isBitfield { + continue + } + thisBitwise := hasTag(v.Type().Field(i), "bit") + if thisBitwise { + numBitwise++ + if hasTag(v.Type().Field(i), "sized") || hasTag(v.Type().Field(i), "sized8") { + return fmt.Errorf("struct '%v' field '%v' is both bitwise and sized", + v.Type().Name(), v.Type().Field(i).Name) + } + if hasTag(v.Type().Field(i), "tag") { + return fmt.Errorf("struct '%v' field '%v' is both bitwise and a tagged union", + v.Type().Name(), v.Type().Field(i).Name) + } + } + numChecked++ + } + if numBitwise != numChecked && numBitwise != 0 { + return fmt.Errorf("struct '%v' has mixture of bitwise and non-bitwise members", v.Type().Name()) + } + if numBitwise > 0 { + return marshalBitwise(buf, v) + } + // Make a pass to create a map of tag values + // UInt64-valued fields with values greater than MaxInt64 cannot be + // selectors. + possibleSelectors := make(map[string]int64) + for i := 0; i < v.NumField(); i++ { + // Special case: Treat a zero-valued nullable field as + // TPMAlgNull for union selection. + // This allows callers to omit uninteresting scheme structures. + if v.Field(i).IsZero() && hasTag(v.Type().Field(i), "nullable") { + possibleSelectors[v.Type().Field(i).Name] = int64(TPMAlgNull) + continue + } + switch v.Field(i).Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + possibleSelectors[v.Type().Field(i).Name] = v.Field(i).Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + val := v.Field(i).Uint() + if val <= math.MaxInt64 { + possibleSelectors[v.Type().Field(i).Name] = int64(val) + } + } + } + for i := 0; i < v.NumField(); i++ { + if hasTag(v.Type().Field(i), "skip") { + continue + } + list := hasTag(v.Type().Field(i), "list") + sized := hasTag(v.Type().Field(i), "sized") + sized8 := hasTag(v.Type().Field(i), "sized8") + tag, _ := tag(v.Type().Field(i), "tag") + // Serialize to a temporary buffer, in case we need to size it + // (Better to simplify this complex reflection-based marshalling + // code than to save some unnecessary copying before talking to + // a low-speed device like a TPM) + var res bytes.Buffer + if list { + binary.Write(&res, binary.BigEndian, uint32(v.Field(i).Len())) + } + if tag != "" { + // Check that the tagged value was present (and numeric + // and smaller than MaxInt64) + tagValue, ok := possibleSelectors[tag] + // Don't marshal anything if the tag value was TPM_ALG_NULL + if tagValue == int64(TPMAlgNull) { + continue + } + if !ok { + return fmt.Errorf("union tag '%v' for member '%v' of struct '%v' did not reference "+ + "a numeric field of int64-compatible value", + tag, v.Type().Field(i).Name, v.Type().Name()) + } + if u, ok := v.Field(i).Interface().(marshallableWithHint); ok { + v, err := u.get(tagValue) + if err != nil { + return err + } + if err := marshal(buf, v); err != nil { + return err + } + } + } else if v.Field(i).IsZero() && v.Field(i).Kind() == reflect.Uint32 && hasTag(v.Type().Field(i), "nullable") { + // Special case: Anything with the same underlying type + // as TPMHandle's zero value is TPM_RH_NULL. + // This allows callers to omit uninteresting handles + // instead of specifying them as TPM_RH_NULL. + if err := binary.Write(&res, binary.BigEndian, uint32(TPMRHNull)); err != nil { + return err + } + } else if v.Field(i).IsZero() && v.Field(i).Kind() == reflect.Uint16 && hasTag(v.Type().Field(i), "nullable") { + // Special case: Anything with the same underlying type + // as TPMAlg's zero value is TPM_ALG_NULL. + // This allows callers to omit uninteresting + // algorithms/schemes instead of specifying them as + // TPM_ALG_NULL. + if err := binary.Write(&res, binary.BigEndian, uint16(TPMAlgNull)); err != nil { + return err + } + } else if hasTag(v.Type().Field(i), "optional") { + if err := marshalOptional(&res, v.Field(i)); err != nil { + return err + } + } else { + if err := marshal(&res, v.Field(i)); err != nil { + return err + } + } + if sized { + if err := binary.Write(buf, binary.BigEndian, uint16(res.Len())); err != nil { + return err + } + } + if sized8 { + if err := binary.Write(buf, binary.BigEndian, uint8(res.Len())); err != nil { + return err + } + } + buf.Write(res.Bytes()) + } + return nil +} + +// Marshals a bitwise-defined struct. +func marshalBitwise(buf *bytes.Buffer, v reflect.Value) error { + bg, ok := v.Interface().(BitGetter) + if !ok { + return fmt.Errorf("'%v' was not a BitGetter", v.Type().Name()) + } + bitArray := make([]bool, bg.Length()) + // Marshal the defined fields + for i := 0; i < v.NumField(); i++ { + if !v.Type().Field(i).IsExported() { + continue + } + high, low, _ := rangeTag(v.Type().Field(i), "bit") + var buf bytes.Buffer + if err := marshal(&buf, v.Field(i)); err != nil { + return err + } + b := buf.Bytes() + for i := 0; i <= (high - low); i++ { + bitArray[low+i] = ((b[len(b)-i/8-1] >> (i % 8)) & 1) == 1 + } + } + // Also marshal the reserved values + for i := 0; i < len(bitArray); i++ { + if bg.GetReservedBit(i) { + bitArray[i] = true + } + } + result := make([]byte, len(bitArray)/8) + for i, bit := range bitArray { + if bit { + result[len(result)-(i/8)-1] |= (1 << (i % 8)) + } + } + buf.Write(result) + return nil +} + +// unmarshal will deserialize the given value from the given buffer. +// Returns an error if the buffer does not contain enough data to satisfy the +// type. +func unmarshal(buf *bytes.Buffer, v reflect.Value) error { + // If the type is not marshalled by reflection, try to call the custom unmarshal method. + if !isMarshalledByReflection(v) { + if u, ok := v.Addr().Interface().(Unmarshallable); ok { + return u.unmarshal(buf) + } + return fmt.Errorf("can't unmarshal: type %v does not implement Unmarshallable or marshallableByReflection", v.Type().Name()) + } + + switch v.Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if err := unmarshalNumeric(buf, v); err != nil { + return err + } + case reflect.Slice: + var length uint32 + // special case for byte slices: just read the entire + // rest of the buffer + if v.Type().Elem().Kind() == reflect.Uint8 { + length = uint32(buf.Len()) + } else { + err := unmarshalNumeric(buf, reflect.ValueOf(&length).Elem()) + if err != nil { + return fmt.Errorf("deserializing size for field of type '%v': %w", v.Type(), err) + } + } + if length > uint32(math.MaxInt32) || length > maxListLength { + return fmt.Errorf("could not deserialize slice of length %v", length) + } + // Go's reflect library doesn't allow increasing the + // capacity of an existing slice. + // Since we can't be sure that the capacity of the + // passed-in value was enough, allocate + // a new temporary one of the correct length, unmarshal + // to it, and swap it in. + tmp := reflect.MakeSlice(v.Type(), int(length), int(length)) + if err := unmarshalArray(buf, tmp); err != nil { + return err + } + v.Set(tmp) + return nil + case reflect.Array: + return unmarshalArray(buf, v) + case reflect.Struct: + return unmarshalStruct(buf, v) + case reflect.Ptr: + return unmarshal(buf, v.Elem()) + default: + return fmt.Errorf("not unmarshallable: %v", v.Type()) + } + return nil +} + +func unmarshalNumeric(buf *bytes.Buffer, v reflect.Value) error { + return binary.Read(buf, binary.BigEndian, v.Addr().Interface()) +} + +// For slices, the slice's length must already be set to the expected amount of +// data. +func unmarshalArray(buf *bytes.Buffer, v reflect.Value) error { + for i := 0; i < v.Len(); i++ { + if err := unmarshal(buf, v.Index(i)); err != nil { + return fmt.Errorf("deserializing slice/array index %v: %w", i, err) + } + } + return nil +} + +func unmarshalStruct(buf *bytes.Buffer, v reflect.Value) error { + // Check if this is a bitwise-defined structure. This requires all the + // exported members to be bitwise-defined. + numBitwise := 0 + numChecked := 0 + for i := 0; i < v.NumField(); i++ { + // Ignore embedded Bitfield hints. + // Ignore embedded Bitfield hints. + if !v.Type().Field(i).IsExported() { + //if _, isBitfield := v.Field(i).Interface().(TPMABitfield); isBitfield { + continue + } + thisBitwise := hasTag(v.Type().Field(i), "bit") + if thisBitwise { + numBitwise++ + if hasTag(v.Type().Field(i), "sized") { + return fmt.Errorf("struct '%v' field '%v' is both bitwise and sized", + v.Type().Name(), v.Type().Field(i).Name) + } + if hasTag(v.Type().Field(i), "tag") { + return fmt.Errorf("struct '%v' field '%v' is both bitwise and a tagged union", + v.Type().Name(), v.Type().Field(i).Name) + } + } + numChecked++ + } + if numBitwise != numChecked && numBitwise != 0 { + return fmt.Errorf("struct '%v' has mixture of bitwise and non-bitwise members", v.Type().Name()) + } + if numBitwise > 0 { + return unmarshalBitwise(buf, v) + } + for i := 0; i < v.NumField(); i++ { + if hasTag(v.Type().Field(i), "skip") { + continue + } + list := hasTag(v.Type().Field(i), "list") + if list && (v.Field(i).Kind() != reflect.Slice) { + return fmt.Errorf("field '%v' of struct '%v' had the 'list' tag but was not a slice", + v.Type().Field(i).Name, v.Type().Name()) + } + // Slices of anything but byte/uint8 must have the 'list' tag. + if !list && (v.Field(i).Kind() == reflect.Slice) && (v.Type().Field(i).Type.Elem().Kind() != reflect.Uint8) { + return fmt.Errorf("field '%v' of struct '%v' was a slice of non-byte but did not have the 'list' tag", + v.Type().Field(i).Name, v.Type().Name()) + } + if hasTag(v.Type().Field(i), "optional") { + // Special case: Part 3 specifies some input/output + // parameters as "optional", which means that they are + // (2B-) sized fields that can be zero-length, even if the + // enclosed type has no legal empty serialization. + // When unmarshalling an optional field, test for zero size + // and skip if empty. + if buf.Len() < 2 { + if binary.BigEndian.Uint16(buf.Bytes()) == 0 { + // Advance the buffer past the zero size and skip to the + // next field of the struct. + buf.Next(2) + continue + } + // If non-zero size, proceed to unmarshal the contents below. + } + } + sized := hasTag(v.Type().Field(i), "sized") + sized8 := hasTag(v.Type().Field(i), "sized8") + // If sized, unmarshal a size field first, then restrict + // unmarshalling to the given size + bufToReadFrom := buf + if sized { + var expectedSize uint16 + binary.Read(buf, binary.BigEndian, &expectedSize) + sizedBufArray := make([]byte, int(expectedSize)) + n, err := buf.Read(sizedBufArray) + if n != int(expectedSize) { + return fmt.Errorf("ran out of data reading sized parameter '%v' inside struct of type '%v'", + v.Type().Field(i).Name, v.Type().Name()) + } + if err != nil { + return fmt.Errorf("error reading data for parameter '%v' inside struct of type '%v'", + v.Type().Field(i).Name, v.Type().Name()) + } + bufToReadFrom = bytes.NewBuffer(sizedBufArray) + } + if sized8 { + var expectedSize uint8 + binary.Read(buf, binary.BigEndian, &expectedSize) + sizedBufArray := make([]byte, int(expectedSize)) + n, err := buf.Read(sizedBufArray) + if n != int(expectedSize) { + return fmt.Errorf("ran out of data reading sized parameter '%v' inside struct of type '%v'", + v.Type().Field(i).Name, v.Type().Name()) + } + if err != nil { + return fmt.Errorf("error reading data for parameter '%v' inside struct of type '%v'", + v.Type().Field(i).Name, v.Type().Name()) + } + bufToReadFrom = bytes.NewBuffer(sizedBufArray) + } + tag, _ := tag(v.Type().Field(i), "tag") + if tag != "" { + // Make a pass to create a map of tag values + // UInt64-valued fields with values greater than + // MaxInt64 cannot be selectors. + possibleSelectors := make(map[string]int64) + for j := 0; j < v.NumField(); j++ { + switch v.Field(j).Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + possibleSelectors[v.Type().Field(j).Name] = v.Field(j).Int() + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + val := v.Field(j).Uint() + if val <= math.MaxInt64 { + possibleSelectors[v.Type().Field(j).Name] = int64(val) + } + } + } + // Check that the tagged value was present (and numeric + // and smaller than MaxInt64) + tagValue, ok := possibleSelectors[tag] + // Don't marshal anything if the tag value was TPM_ALG_NULL + if tagValue == int64(TPMAlgNull) { + continue + } + if !ok { + return fmt.Errorf("union tag '%v' for member '%v' of struct '%v' did not reference "+ + "a numeric field of in64-compatible value", + tag, v.Type().Field(i).Name, v.Type().Name()) + } + var uwh unmarshallableWithHint + if v.Field(i).CanAddr() && v.Field(i).Addr().Type().AssignableTo(reflect.TypeOf(&uwh).Elem()) { + u := v.Field(i).Addr().Interface().(unmarshallableWithHint) + contents, err := u.create(tagValue) + if err != nil { + return fmt.Errorf("unmarshalling field %v of struct of type '%v', %w", i, v.Type(), err) + } + err = unmarshal(buf, contents) + if err != nil { + return fmt.Errorf("unmarshalling field %v of struct of type '%v', %w", i, v.Type(), err) + } + } else if v.Field(i).Type().AssignableTo(reflect.TypeOf(&uwh).Elem()) { + u := v.Field(i).Interface().(unmarshallableWithHint) + contents, err := u.create(tagValue) + if err != nil { + return fmt.Errorf("unmarshalling field %v of struct of type '%v', %w", i, v.Type(), err) + } + err = unmarshal(buf, contents) + if err != nil { + return fmt.Errorf("unmarshalling field %v of struct of type '%v', %w", i, v.Type(), err) + } + } + } else { + if err := unmarshal(bufToReadFrom, v.Field(i)); err != nil { + return fmt.Errorf("unmarshalling field %v of struct of type '%v', %w", i, v.Type(), err) + } + } + if sized || sized8 { + if bufToReadFrom.Len() != 0 { + return fmt.Errorf("extra data at the end of sized parameter '%v' inside struct of type '%v'", + v.Type().Field(i).Name, v.Type().Name()) + } + } + } + return nil +} + +// Unmarshals a bitwise-defined struct. +func unmarshalBitwise(buf *bytes.Buffer, v reflect.Value) error { + bs, ok := v.Addr().Interface().(BitSetter) + if !ok { + return fmt.Errorf("'%v' was not a BitSetter", v.Addr().Type()) + } + bitArray := make([]bool, bs.Length()) + // We will read big-endian, starting from the last byte and working our + // way down. + for i := len(bitArray)/8 - 1; i >= 0; i-- { + b, err := buf.ReadByte() + if err != nil { + return fmt.Errorf("error %d bits into field '%v' of struct '%v': %w", + i, v.Type().Field(i).Name, v.Type().Name(), err) + } + for j := 0; j < 8; j++ { + bitArray[8*i+j] = (((b >> j) & 1) == 1) + } + } + // Unmarshal the defined fields and clear the bits from the array as we + // read them. + for i := 0; i < v.NumField(); i++ { + if !v.Type().Field(i).IsExported() { + continue + } + high, low, _ := rangeTag(v.Type().Field(i), "bit") + var val uint64 + for j := 0; j <= high-low; j++ { + if bitArray[low+j] { + val |= (1 << j) + } + bitArray[low+j] = false + } + if v.Field(i).Kind() == reflect.Bool { + v.Field(i).SetBool((val & 1) == 1) + } else { + v.Field(i).SetUint(val) + } + } + // Unmarshal the remaining uncleared bits as reserved bits. + for i := 0; i < len(bitArray); i++ { + bs.SetReservedBit(i, bitArray[i]) + } + return nil +} + +// Looks up the given gotpm tag on a field. +// Some tags are settable (with "="). For these, the value is the RHS. +// For all others, the value is the empty string. +func tag(t reflect.StructField, query string) (string, bool) { + allTags, ok := t.Tag.Lookup("gotpm") + if !ok { + return "", false + } + tags := strings.Split(allTags, ",") + for _, tag := range tags { + // Split on the equals sign for settable tags. + // If the split returns a slice of length 1, this is an + // un-settable tag or an empty tag (which we'll ignore). + // If the split returns a slice of length 2, this is a settable + // tag. + if tag == query { + return "", true + } + if strings.HasPrefix(tag, query+"=") { + assignment := strings.SplitN(tag, "=", 2) + return assignment[1], true + } + } + return "", false +} + +// hasTag looks up to see if the type's gotpm-namespaced tag contains the +// given value. +// Returns false if there is no gotpm-namespaced tag on the type. +func hasTag(t reflect.StructField, query string) bool { + _, ok := tag(t, query) + return ok +} + +// Returns the range on a tag like 4:3 or 4. +// If there is no colon, the low and high part of the range are equal. +func rangeTag(t reflect.StructField, query string) (int, int, bool) { + val, ok := tag(t, query) + if !ok { + return 0, 0, false + } + vals := strings.Split(val, ":") + high, err := strconv.Atoi(vals[0]) + if err != nil { + return 0, 0, false + } + low := high + if len(vals) > 1 { + low, err = strconv.Atoi(vals[1]) + if err != nil { + return 0, 0, false + } + } + if low > high { + low, high = high, low + } + return high, low, true +} + +// taggedMembers will return a slice of all the members of the given +// structure that contain (or don't contain) the given tag in the "gotpm" +// namespace. +// Panics if v's Kind is not Struct. +func taggedMembers(v reflect.Value, tag string, invert bool) []reflect.Value { + var result []reflect.Value + t := v.Type() + + for i := 0; i < t.NumField(); i++ { + // Add this one to the list if it has the tag and we're not + // inverting, or if it doesn't have the tag and we are + // inverting. + if hasTag(t.Field(i), tag) != invert { + result = append(result, v.Field(i)) + } + } + + return result +} + +// cmdAuths returns the authorization sessions of the command. +func cmdAuths[R any](cmd Command[R, *R]) ([]Session, error) { + authHandles := taggedMembers(reflect.ValueOf(cmd), "auth", false) + var result []Session + for i, authHandle := range authHandles { + // TODO: A cleaner way to do this would be to have an interface method that + // returns a Session. + if h, ok := authHandle.Interface().(AuthHandle); ok { + if h.Auth == nil { + return nil, fmt.Errorf("missing auth for '%v' parameter", + reflect.ValueOf(cmd).Type().Field(i).Name) + } + result = append(result, h.Auth) + } else { + result = append(result, PasswordAuth(nil)) + } + } + + return result, nil +} + +func asHandle(value reflect.Value) (handle, error) { + // Special case: `handle`-typed members. + // Since `handle` is an interface, the zero-value is nil. + // https://go.dev/ref/spec#Type_assertions in this case will return false. + // Similarly, reflect.AssignableTo() will panic. + // Workaround: treat any nil interface value annotated as a `handle` as TPMRHNull. + var h handle + if value.Kind() == reflect.Interface && value.IsNil() { + h = TPMRHNull + } else { + var ok bool + h, ok = value.Interface().(handle) + if !ok { + return nil, fmt.Errorf("value of type %q does not satisfy handle", value.Type()) + } + } + return h, nil +} + +// cmdHandles returns the handles area of the command. +func cmdHandles[R any](cmd Command[R, *R]) ([]byte, error) { + handles := taggedMembers(reflect.ValueOf(cmd), "handle", false) + + // Initial capacity is enough to hold 3 handles + result := bytes.NewBuffer(make([]byte, 0, 12)) + + for _, maybeHandle := range handles { + h, err := asHandle(maybeHandle) + if err != nil { + return nil, fmt.Errorf("invalid 'handle'-tagged member of %q: %v", + reflect.TypeOf(cmd), err) + } + binary.Write(result, binary.BigEndian, h.HandleValue()) + } + + return result.Bytes(), nil +} + +// cmdNames returns the names of the entities referenced by the handles of the command. +func cmdNames[R any](cmd Command[R, *R]) ([]TPM2BName, error) { + handles := taggedMembers(reflect.ValueOf(cmd), "handle", false) + var result []TPM2BName + for i, maybeHandle := range handles { + h, err := asHandle(maybeHandle) + if err != nil { + return nil, fmt.Errorf("invalid 'handle'-tagged member of %q: %v", + reflect.TypeOf(cmd), err) + } + + // Special case: handles with an empty name buffer (anonymous:anon) + // See part 1: Architecture, section 32.4.5: + // The Name of a sequence object is an Empty Buffer (sized array with no + // data; indicated by a size field of zero followed by an array + // containing no elements) + if hasTag(reflect.ValueOf(cmd).Type().Field(i), "anon") { + continue + } + + name := h.KnownName() + if name == nil { + return nil, fmt.Errorf("missing Name for '%v' parameter", + reflect.ValueOf(cmd).Type().Field(i).Name) + } + result = append(result, *name) + } + + return result, nil +} + +// TODO: Extract the logic of "marshal the Nth field of some struct after the handles" +// For now, we duplicate some logic from marshalStruct here. +func marshalParameter[R any](buf *bytes.Buffer, cmd Command[R, *R], i int) error { + numHandles := len(taggedMembers(reflect.ValueOf(cmd), "handle", false)) + if numHandles+i >= reflect.TypeOf(cmd).NumField() { + return fmt.Errorf("invalid parameter index %v", i) + } + parm := reflect.ValueOf(cmd).Field(numHandles + i) + field := reflect.TypeOf(cmd).Field(numHandles + i) + if hasTag(field, "optional") { + return marshalOptional(buf, parm) + } else if parm.IsZero() && parm.Kind() == reflect.Uint32 && hasTag(field, "nullable") { + return marshal(buf, reflect.ValueOf(TPMRHNull)) + } else if parm.IsZero() && parm.Kind() == reflect.Uint16 && hasTag(field, "nullable") { + return marshal(buf, reflect.ValueOf(TPMAlgNull)) + } + + return marshal(buf, parm) +} + +// cmdParameters returns the parameters area of the command. +// The first parameter may be encrypted by one of the sessions. +func cmdParameters[R any](cmd Command[R, *R], sess []Session) ([]byte, error) { + parms := taggedMembers(reflect.ValueOf(cmd), "handle", true) + if len(parms) == 0 { + return nil, nil + } + + var firstParm bytes.Buffer + if err := marshalParameter(&firstParm, cmd, 0); err != nil { + return nil, err + } + firstParmBytes := firstParm.Bytes() + + // Encrypt the first parameter if there are any decryption sessions. + encrypted := false + for i, s := range sess { + if s.IsDecryption() { + if encrypted { + // Only one session may be used for decryption. + return nil, fmt.Errorf("too many decrypt sessions") + } + if len(firstParmBytes) < 2 { + return nil, fmt.Errorf("this command's first parameter is not a tpm2b") + } + err := s.Encrypt(firstParmBytes[2:]) + if err != nil { + return nil, fmt.Errorf("encrypting with session %d: %w", i, err) + } + encrypted = true + } + } + + var result bytes.Buffer + result.Write(firstParmBytes) + // Write the rest of the parameters normally. + for i := 1; i < len(parms); i++ { + if err := marshalParameter(&result, cmd, i); err != nil { + return nil, err + } + } + return result.Bytes(), nil +} + +// cmdSessions returns the authorization area of the command. +func cmdSessions(sess []Session, cc TPMCC, names []TPM2BName, parms []byte) ([]byte, error) { + // There is no authorization area if there are no sessions. + if len(sess) == 0 { + return nil, nil + } + // Find the non-first-session encryption and decryption session + // nonceTPMs, if any. + var encNonceTPM, decNonceTPM []byte + if len(sess) > 0 { + for i := 1; i < len(sess); i++ { + s := sess[i] + if s.IsEncryption() { + if encNonceTPM != nil { + // Only one encrypt session is permitted. + return nil, fmt.Errorf("too many encrypt sessions") + } + encNonceTPM = s.NonceTPM().Buffer + // A session used for both encryption and + // decryption only needs its nonce counted once. + continue + } + if s.IsDecryption() { + if decNonceTPM != nil { + // Only one decrypt session is permitted. + return nil, fmt.Errorf("too many decrypt sessions") + } + decNonceTPM = s.NonceTPM().Buffer + } + } + } + + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + // Skip space to write the size later + buf.Write(make([]byte, 4)) + // Calculate the authorization HMAC for each session + for i, s := range sess { + var addNonces []byte + // Special case: the HMAC on the first authorization session of + // a command also includes any decryption and encryption + // nonceTPMs, too. + if i == 0 { + addNonces = append(addNonces, decNonceTPM...) + addNonces = append(addNonces, encNonceTPM...) + } + auth, err := s.Authorize(cc, parms, addNonces, names, i) + if err != nil { + return nil, fmt.Errorf("session %d: %w", i, err) + } + marshal(buf, reflect.ValueOf(auth).Elem()) + } + + result := buf.Bytes() + // Write the size + binary.BigEndian.PutUint32(result[0:], uint32(buf.Len()-4)) + + return result, nil +} + +// cmdHeader returns the structured TPM command header. +func cmdHeader(hasSessions bool, length int, cc TPMCC) []byte { + tag := TPMSTNoSessions + if hasSessions { + tag = TPMSTSessions + } + hdr := TPMCmdHeader{ + Tag: tag, + Length: uint32(length), + CommandCode: cc, + } + buf := bytes.NewBuffer(make([]byte, 0, 8)) + marshal(buf, reflect.ValueOf(hdr)) + return buf.Bytes() +} + +// rspHeader parses the response header. If the TPM returned an error, +// returns an error here. +// rsp is updated to point to the rest of the response after the header. +func rspHeader(rsp *bytes.Buffer) error { + var hdr TPMRspHeader + if err := unmarshal(rsp, reflect.ValueOf(&hdr).Elem()); err != nil { + return fmt.Errorf("unmarshalling TPM response: %w", err) + } + if hdr.ResponseCode != TPMRCSuccess { + return hdr.ResponseCode + } + return nil +} + +// rspHandles parses the response handles area into the response structure. +// If there is a mismatch between the expected and actual amount of handles, +// returns an error here. +// rsp is updated to point to the rest of the response after the handles. +func rspHandles(rsp *bytes.Buffer, rspStruct any) error { + handles := taggedMembers(reflect.ValueOf(rspStruct).Elem(), "handle", false) + for i, handle := range handles { + if err := unmarshal(rsp, handle); err != nil { + return fmt.Errorf("unmarshalling handle %v: %w", i, err) + } + } + return nil +} + +// rspParametersArea fetches, but does not manipulate, the parameters area +// from the response. If there is a mismatch between the response's +// indicated parameters area size and the actual size, returns an error here. +// rsp is updated to point to the rest of the response after the handles. +func rspParametersArea(hasSessions bool, rsp *bytes.Buffer) ([]byte, error) { + var length uint32 + if hasSessions { + if err := binary.Read(rsp, binary.BigEndian, &length); err != nil { + return nil, fmt.Errorf("reading length of parameter area: %w", err) + } + } else { + // If there are no sessions, there is no length-of-parameters + // field, because the whole rest of the response is the + // parameters area. + length = uint32(rsp.Len()) + } + if length > uint32(rsp.Len()) { + return nil, fmt.Errorf("response indicated %d bytes of parameters but there "+ + "were only %d more bytes of response", length, rsp.Len()) + } + if length > math.MaxInt32 { + return nil, fmt.Errorf("invalid length of parameter area: %d", length) + } + parms := make([]byte, int(length)) + if n, err := rsp.Read(parms); err != nil { + return nil, fmt.Errorf("reading parameter area: %w", err) + } else if n != len(parms) { + return nil, fmt.Errorf("only read %d bytes of parameters, expected %d", n, len(parms)) + } + return parms, nil +} + +// rspSessions fetches the sessions area of the response and updates all +// the sessions with it. If there is a response validation error, returns +// an error here. +// rsp is updated to point to the rest of the response after the sessions. +func rspSessions(rsp *bytes.Buffer, rc TPMRC, cc TPMCC, names []TPM2BName, parms []byte, sess []Session) error { + for i, s := range sess { + var auth TPMSAuthResponse + if err := unmarshal(rsp, reflect.ValueOf(&auth).Elem()); err != nil { + return fmt.Errorf("reading auth session %d: %w", i, err) + } + if err := s.Validate(rc, cc, parms, names, i, &auth); err != nil { + return fmt.Errorf("validating auth session %d: %w", i, err) + } + } + if rsp.Len() != 0 { + return fmt.Errorf("%d unaccounted-for bytes at the end of the TPM response", rsp.Len()) + } + return nil +} + +// rspParameters decrypts (if needed) the parameters area of the response +// into the response structure. If there is a mismatch between the expected +// and actual response structure, returns an error here. +func rspParameters(parms []byte, sess []Session, rspStruct any) error { + numHandles := len(taggedMembers(reflect.ValueOf(rspStruct).Elem(), "handle", false)) + + // Use the heuristic of "does interpreting the first 2 bytes of response + // as a length make any sense" to attempt encrypted parameter + // decryption. + // If the command supports parameter encryption, the first parameter is + // a 2B. + if len(parms) < 2 { + return nil + } + length := binary.BigEndian.Uint16(parms[0:]) + // TODO: Make this nice using structure tagging. + if int(length)+2 <= len(parms) { + for i, s := range sess { + if !s.IsEncryption() { + continue + } + if err := s.Decrypt(parms[2 : 2+length]); err != nil { + return fmt.Errorf("decrypting first parameter with session %d: %w", i, err) + } + } + } + buf := bytes.NewBuffer(parms) + for i := numHandles; i < reflect.TypeOf(rspStruct).Elem().NumField(); i++ { + parmsField := reflect.ValueOf(rspStruct).Elem().Field(i) + if parmsField.Kind() == reflect.Ptr && hasTag(reflect.TypeOf(rspStruct).Elem().Field(i), "optional") { + if binary.BigEndian.Uint16(buf.Bytes()) == 0 { + // Advance the buffer past the zero size and skip to the + // next field of the struct. + buf.Next(2) + continue + } + } + if err := unmarshal(buf, parmsField); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/google/go-tpm/tpm2/sessions.go b/vendor/github.com/google/go-tpm/tpm2/sessions.go new file mode 100644 index 00000000000..9de6bd73730 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/sessions.go @@ -0,0 +1,996 @@ +package tpm2 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "encoding/binary" + "fmt" + + "github.com/google/go-tpm/tpm2/transport" +) + +// Session represents a session in the TPM. +type Session interface { + // Initializes the session, if needed. Has no effect if not needed or + // already done. Some types of sessions may need to be initialized + // just-in-time, e.g., to support calling patterns that help the user + // securely authorize their actions without writing a lot of code. + Init(tpm transport.TPM) error + // Cleans up the session, if needed. + // Some types of session need to be cleaned up if the command failed, + // again to support calling patterns that help the user securely + // authorize their actions without writing a lot of code. + CleanupFailure(tpm transport.TPM) error + // The last nonceTPM for this session. + NonceTPM() TPM2BNonce + // Updates nonceCaller to a new random value. + NewNonceCaller() error + // Computes the authorization HMAC for the session. + // If this is the first authorization session for a command, and + // there is another session (or sessions) for parameter + // decryption and/or encryption, then addNonces contains the + // nonceTPMs from each of them, respectively (see Part 1, 19.6.5) + Authorize(cc TPMCC, parms, addNonces []byte, names []TPM2BName, authIndex int) (*TPMSAuthCommand, error) + // Validates the response for the session. + // Updates NonceTPM for the session. + Validate(rc TPMRC, cc TPMCC, parms []byte, names []TPM2BName, authIndex int, auth *TPMSAuthResponse) error + // Returns true if this is an encryption session. + IsEncryption() bool + // Returns true if this is a decryption session. + IsDecryption() bool + // If this session is used for parameter decryption, encrypts the + // parameter. Otherwise, does not modify the parameter. + Encrypt(parameter []byte) error + // If this session is used for parameter encryption, encrypts the + // parameter. Otherwise, does not modify the parameter. + Decrypt(parameter []byte) error + // Returns the handle value of this session. + Handle() TPMHandle +} + +// CPHash calculates the TPM command parameter hash for a given Command. +// N.B. Authorization sessions on handles are ignored, but names aren't. +func CPHash[R any](alg TPMIAlgHash, cmd Command[R, *R]) (*TPM2BDigest, error) { + cc := cmd.Command() + names, err := cmdNames(cmd) + if err != nil { + return nil, err + } + parms, err := cmdParameters(cmd, nil) + if err != nil { + return nil, err + } + digest, err := cpHash(alg, cc, names, parms) + if err != nil { + return nil, err + } + return &TPM2BDigest{ + Buffer: digest, + }, nil +} + +// pwSession represents a password-pseudo-session. +type pwSession struct { + auth []byte +} + +// PasswordAuth assembles a password pseudo-session with the given auth value. +func PasswordAuth(auth []byte) Session { + return &pwSession{ + auth: auth, + } +} + +// Init is not required and has no effect for a password session. +func (s *pwSession) Init(_ transport.TPM) error { return nil } + +// Cleanup is not required and has no effect for a password session. +func (s *pwSession) CleanupFailure(_ transport.TPM) error { return nil } + +// NonceTPM normally returns the last nonceTPM value from the session. +// Since a password session is a pseudo-session with the auth value stuffed +// in where the HMAC should go, this is not used. +func (s *pwSession) NonceTPM() TPM2BNonce { return TPM2BNonce{} } + +// NewNonceCaller updates the nonceCaller for this session. +// Password sessions don't have nonces. +func (s *pwSession) NewNonceCaller() error { return nil } + +// Computes the authorization structure for the session. +func (s *pwSession) Authorize(_ TPMCC, _, _ []byte, _ []TPM2BName, _ int) (*TPMSAuthCommand, error) { + return &TPMSAuthCommand{ + Handle: TPMRSPW, + Nonce: TPM2BNonce{}, + Attributes: TPMASession{}, + Authorization: TPM2BData{ + Buffer: s.auth, + }, + }, nil +} + +// Validates the response session structure for the session. +func (s *pwSession) Validate(_ TPMRC, _ TPMCC, _ []byte, _ []TPM2BName, _ int, auth *TPMSAuthResponse) error { + if len(auth.Nonce.Buffer) != 0 { + return fmt.Errorf("expected empty nonce in response auth to PW session, got %x", auth.Nonce) + } + expectedAttrs := TPMASession{ + ContinueSession: true, + } + if auth.Attributes != expectedAttrs { + return fmt.Errorf("expected only ContinueSession in response auth to PW session, got %v", auth.Attributes) + } + if len(auth.Authorization.Buffer) != 0 { + return fmt.Errorf("expected empty HMAC in response auth to PW session, got %x", auth.Authorization) + } + return nil +} + +// IsEncryption returns true if this is an encryption session. +// Password sessions can't be used for encryption. +func (s *pwSession) IsEncryption() bool { return false } + +// IsDecryption returns true if this is a decryption session. +// Password sessions can't be used for decryption. +func (s *pwSession) IsDecryption() bool { return false } + +// If this session is used for parameter decryption, encrypts the +// parameter. Otherwise, does not modify the parameter. +// Password sessions can't be used for decryption. +func (s *pwSession) Encrypt(_ []byte) error { return nil } + +// If this session is used for parameter encryption, encrypts the +// parameter. Otherwise, does not modify the parameter. +// Password sessions can't be used for encryption. +func (s *pwSession) Decrypt(_ []byte) error { return nil } + +// Handle returns the handle value associated with this session. +// In the case of a password session, this is always TPM_RS_PW. +func (s *pwSession) Handle() TPMHandle { return TPMRSPW } + +// cpHash calculates the TPM command parameter hash. +// cpHash = hash(CC || names || parms) +func cpHash(alg TPMIAlgHash, cc TPMCC, names []TPM2BName, parms []byte) ([]byte, error) { + ha, err := alg.Hash() + if err != nil { + return nil, err + } + h := ha.New() + binary.Write(h, binary.BigEndian, cc) + for _, name := range names { + h.Write(name.Buffer) + } + h.Write(parms) + return h.Sum(nil), nil +} + +// rpHash calculates the TPM response parameter hash. +// rpHash = hash(RC || CC || parms) +func rpHash(alg TPMIAlgHash, rc TPMRC, cc TPMCC, parms []byte) ([]byte, error) { + ha, err := alg.Hash() + if err != nil { + return nil, err + } + h := ha.New() + binary.Write(h, binary.BigEndian, rc) + binary.Write(h, binary.BigEndian, cc) + h.Write(parms) + return h.Sum(nil), nil +} + +// sessionOptions represents extra options used when setting up an HMAC or policy session. +type sessionOptions struct { + auth []byte + password bool + bindHandle TPMIDHEntity + bindName TPM2BName + bindAuth []byte + saltHandle TPMIDHObject + saltPub TPMTPublic + attrs TPMASession + symmetric TPMTSymDef + trialPolicy bool +} + +// defaultOptions represents the default options used when none are provided. +func defaultOptions() sessionOptions { + return sessionOptions{ + symmetric: TPMTSymDef{ + Algorithm: TPMAlgNull, + }, + bindHandle: TPMRHNull, + saltHandle: TPMRHNull, + } +} + +// AuthOption is an option for setting up an auth session variadically. +type AuthOption func(*sessionOptions) + +// Auth uses the session to prove knowledge of the object's auth value. +func Auth(auth []byte) AuthOption { + return func(o *sessionOptions) { + o.auth = auth + } +} + +// Password is a policy-session-only option that specifies to provide the +// object's auth value in place of the authorization HMAC when authorizing. +// For HMAC sessions, has the same effect as using Auth. +// Deprecated: This is not recommended and is only provided for completeness; +// use Auth instead. +func Password(auth []byte) AuthOption { + return func(o *sessionOptions) { + o.auth = auth + o.password = true + } +} + +// Bound specifies that this session's session key should depend on the auth +// value of the given object. +func Bound(handle TPMIDHEntity, name TPM2BName, auth []byte) AuthOption { + return func(o *sessionOptions) { + o.bindHandle = handle + o.bindName = name + o.bindAuth = auth + } +} + +// Salted specifies that this session's session key should depend on an +// encrypted seed value using the given public key. +// 'handle' must refer to a loaded RSA or ECC key. +func Salted(handle TPMIDHObject, pub TPMTPublic) AuthOption { + return func(o *sessionOptions) { + o.saltHandle = handle + o.saltPub = pub + } +} + +// parameterEncryptiontpm2ion specifies whether the session-encrypted +// parameters are encrypted on the way into the TPM, out of the TPM, or both. +type parameterEncryptiontpm2ion int + +const ( + // EncryptIn specifies a decrypt session. + EncryptIn parameterEncryptiontpm2ion = 1 + iota + // EncryptOut specifies an encrypt session. + EncryptOut + // EncryptInOut specifies a decrypt+encrypt session. + EncryptInOut +) + +// AESEncryption uses the session to encrypt the first parameter sent to/from +// the TPM. +// Note that only commands whose first command/response parameter is a 2B can +// support session encryption. +func AESEncryption(keySize TPMKeyBits, dir parameterEncryptiontpm2ion) AuthOption { + return func(o *sessionOptions) { + o.attrs.Decrypt = (dir == EncryptIn || dir == EncryptInOut) + o.attrs.Encrypt = (dir == EncryptOut || dir == EncryptInOut) + o.symmetric = TPMTSymDef{ + Algorithm: TPMAlgAES, + KeyBits: NewTPMUSymKeyBits( + TPMAlgAES, + TPMKeyBits(keySize), + ), + Mode: NewTPMUSymMode( + TPMAlgAES, + TPMAlgCFB, + ), + } + } +} + +// Audit uses the session to compute extra HMACs. +// An Audit session can be used with GetSessionAuditDigest to obtain attestation +// over a sequence of commands. +func Audit() AuthOption { + return func(o *sessionOptions) { + o.attrs.Audit = true + } +} + +// AuditExclusive is like an audit session, but even more powerful. +// This allows an audit session to additionally indicate that no other auditable +// commands were executed other than the ones described by the audit hash. +func AuditExclusive() AuthOption { + return func(o *sessionOptions) { + o.attrs.Audit = true + o.attrs.AuditExclusive = true + } +} + +// Trial indicates that the policy session should be in trial-mode. +// This allows using the TPM to calculate policy hashes. +// This option has no effect on non-Policy sessions. +func Trial() AuthOption { + return func(o *sessionOptions) { + o.trialPolicy = true + } +} + +// hmacSession generally implements the HMAC session. +type hmacSession struct { + sessionOptions + hash TPMIAlgHash + nonceSize int + handle TPMHandle + sessionKey []byte + // last nonceCaller + nonceCaller TPM2BNonce + // last nonceTPM + nonceTPM TPM2BNonce +} + +// HMAC sets up a just-in-time HMAC session that is used only once. +// A real session is created, but just in time and it is flushed when used. +func HMAC(hash TPMIAlgHash, nonceSize int, opts ...AuthOption) Session { + // Set up a one-off session that knows the auth value. + sess := hmacSession{ + sessionOptions: defaultOptions(), + hash: hash, + nonceSize: nonceSize, + handle: TPMRHNull, + } + for _, opt := range opts { + opt(&sess.sessionOptions) + } + return &sess +} + +// HMACSession sets up a reusable HMAC session that needs to be closed. +func HMACSession(t transport.TPM, hash TPMIAlgHash, nonceSize int, opts ...AuthOption) (s Session, close func() error, err error) { + // Set up a not-one-off session that knows the auth value. + sess := hmacSession{ + sessionOptions: defaultOptions(), + hash: hash, + nonceSize: nonceSize, + handle: TPMRHNull, + } + for _, opt := range opts { + opt(&sess.sessionOptions) + } + // This session is reusable and is closed with the function we'll + // return. + sess.sessionOptions.attrs.ContinueSession = true + + // Initialize the session. + if err := sess.Init(t); err != nil { + return nil, nil, err + } + + closer := func() error { + _, err := (&FlushContext{FlushHandle: sess.handle}).Execute(t) + return err + } + + return &sess, closer, nil +} + +// getEncryptedSalt creates a salt value for salted sessions. +// Returns the encrypted salt and plaintext salt, or an error value. +func getEncryptedSalt(pub TPMTPublic) (*TPM2BEncryptedSecret, []byte, error) { + key, err := ImportEncapsulationKey(&pub) + if err != nil { + return nil, nil, err + } + salt, encSalt, err := CreateEncryptedSalt(rand.Reader, key) + if err != nil { + return nil, nil, err + } + return &TPM2BEncryptedSecret{ + Buffer: encSalt, + }, salt, nil +} + +// Init initializes the session, just in time, if needed. +func (s *hmacSession) Init(t transport.TPM) error { + if s.handle != TPMRHNull { + // Session is already initialized. + return nil + } + + // Get a high-quality nonceCaller for our use. + // Store it with the session object for later reference. + s.nonceCaller = TPM2BNonce{ + Buffer: make([]byte, s.nonceSize), + } + if _, err := rand.Read(s.nonceCaller.Buffer); err != nil { + return err + } + + // Start up the actual auth session. + sasCmd := StartAuthSession{ + TPMKey: s.saltHandle, + Bind: s.bindHandle, + NonceCaller: s.nonceCaller, + SessionType: TPMSEHMAC, + Symmetric: s.symmetric, + AuthHash: s.hash, + } + var salt []byte + if s.saltHandle != TPMRHNull { + var err error + var encSalt *TPM2BEncryptedSecret + encSalt, salt, err = getEncryptedSalt(s.saltPub) + if err != nil { + return err + } + sasCmd.EncryptedSalt = *encSalt + } + sasRsp, err := sasCmd.Execute(t) + if err != nil { + return err + } + s.handle = TPMHandle(sasRsp.SessionHandle.HandleValue()) + s.nonceTPM = sasRsp.NonceTPM + // Part 1, 19.6 + ha, err := s.hash.Hash() + if err != nil { + return err + } + if s.bindHandle != TPMRHNull || len(salt) != 0 { + var authSalt []byte + authSalt = append(authSalt, s.bindAuth...) + authSalt = append(authSalt, salt...) + s.sessionKey = KDFa(ha, authSalt, "ATH", s.nonceTPM.Buffer, s.nonceCaller.Buffer, ha.Size()*8) + } + return nil +} + +// Cleanup cleans up the session, if needed. +func (s *hmacSession) CleanupFailure(t transport.TPM) error { + // The user is already responsible to clean up this session. + if s.attrs.ContinueSession { + return nil + } + fc := FlushContext{FlushHandle: s.handle} + if _, err := fc.Execute(t); err != nil { + return err + } + s.handle = TPMRHNull + return nil +} + +// NonceTPM returns the last nonceTPM value from the session. +// May be nil, if the session hasn't been initialized yet. +func (s *hmacSession) NonceTPM() TPM2BNonce { return s.nonceTPM } + +// To avoid a circular dependency on gotpm by tpm2, implement a +// tiny serialization by hand for TPMASession here +func attrsToBytes(attrs TPMASession) []byte { + var res byte + if attrs.ContinueSession { + res |= (1 << 0) + } + if attrs.AuditExclusive { + res |= (1 << 1) + } + if attrs.AuditReset { + res |= (1 << 2) + } + if attrs.Decrypt { + res |= (1 << 5) + } + if attrs.Encrypt { + res |= (1 << 6) + } + if attrs.Audit { + res |= (1 << 7) + } + return []byte{res} +} + +// computeHMAC computes an authorization HMAC according to various equations in +// Part 1. +// This applies to both commands and responses. +// The value of key depends on whether the session is bound and/or salted. +// pHash cpHash for a command, or an rpHash for a response. +// nonceNewer in a command is the new nonceCaller sent in the command session packet. +// nonceNewer in a response is the new nonceTPM sent in the response session packet. +// nonceOlder in a command is the last nonceTPM sent by the TPM for this session. +// This may be when the session was created, or the last time it was used. +// nonceOlder in a response is the corresponding nonceCaller sent in the command. +func computeHMAC(alg TPMIAlgHash, key, pHash, nonceNewer, nonceOlder, addNonces []byte, attrs TPMASession) ([]byte, error) { + ha, err := alg.Hash() + if err != nil { + return nil, err + } + mac := hmac.New(ha.New, key) + mac.Write(pHash) + mac.Write(nonceNewer) + mac.Write(nonceOlder) + mac.Write(addNonces) + mac.Write(attrsToBytes(attrs)) + return mac.Sum(nil), nil +} + +// Trim trailing zeros from the auth value. Part 1, 19.6.5, Note 2 +// Does not allocate a new underlying byte array. +func hmacKeyFromAuthValue(auth []byte) []byte { + key := auth + for i := len(key) - 1; i >= 0; i-- { + if key[i] == 0 { + key = key[:i] + } + } + return key +} + +// NewNonceCaller updates the nonceCaller for this session. +func (s *hmacSession) NewNonceCaller() error { + _, err := rand.Read(s.nonceCaller.Buffer) + return err +} + +// Authorize computes the authorization structure for the session. +// Unlike the TPM spec, authIndex is zero-based. +func (s *hmacSession) Authorize(cc TPMCC, parms, addNonces []byte, names []TPM2BName, authIndex int) (*TPMSAuthCommand, error) { + if s.handle == TPMRHNull { + // Session is not initialized. + return nil, fmt.Errorf("session not initialized") + } + + // Part 1, 19.6 + // HMAC key is (sessionKey || auth) unless this session is authorizing + // its bind target + var hmacKey []byte + hmacKey = append(hmacKey, s.sessionKey...) + if len(s.bindName.Buffer) == 0 || authIndex >= len(names) || !bytes.Equal(names[authIndex].Buffer, s.bindName.Buffer) { + hmacKey = append(hmacKey, hmacKeyFromAuthValue(s.auth)...) + } + + // Compute the authorization HMAC. + cph, err := cpHash(s.hash, cc, names, parms) + if err != nil { + return nil, err + } + hmac, err := computeHMAC(s.hash, hmacKey, cph, s.nonceCaller.Buffer, s.nonceTPM.Buffer, addNonces, s.attrs) + if err != nil { + return nil, err + } + result := TPMSAuthCommand{ + Handle: s.handle, + Nonce: s.nonceCaller, + Attributes: s.attrs, + Authorization: TPM2BData{ + Buffer: hmac, + }, + } + return &result, nil +} + +// Validate validates the response session structure for the session. +// It updates nonceTPM from the TPM's response. +func (s *hmacSession) Validate(rc TPMRC, cc TPMCC, parms []byte, names []TPM2BName, authIndex int, auth *TPMSAuthResponse) error { + // Track the new nonceTPM for the session. + s.nonceTPM = auth.Nonce + // Track the session being automatically flushed. + if !auth.Attributes.ContinueSession { + s.handle = TPMRHNull + } + + // Part 1, 19.6 + // HMAC key is (sessionKey || auth) unless this session is authorizing + // its bind target + var hmacKey []byte + hmacKey = append(hmacKey, s.sessionKey...) + if len(s.bindName.Buffer) == 0 || authIndex >= len(names) || !bytes.Equal(names[authIndex].Buffer, s.bindName.Buffer) { + hmacKey = append(hmacKey, hmacKeyFromAuthValue(s.auth)...) + } + + // Compute the authorization HMAC. + rph, err := rpHash(s.hash, rc, cc, parms) + if err != nil { + return err + } + mac, err := computeHMAC(s.hash, hmacKey, rph, s.nonceTPM.Buffer, s.nonceCaller.Buffer, nil, auth.Attributes) + if err != nil { + return err + } + // Compare the HMAC (constant time) + if !hmac.Equal(mac, auth.Authorization.Buffer) { + return fmt.Errorf("incorrect authorization HMAC") + } + return nil +} + +// IsEncryption returns true if this is an encryption session. +func (s *hmacSession) IsEncryption() bool { + return s.attrs.Encrypt +} + +// IsDecryption returns true if this is a decryption session. +func (s *hmacSession) IsDecryption() bool { + return s.attrs.Decrypt +} + +// Encrypt decrypts the parameter in place, if this session is used for +// parameter decryption. Otherwise, it does not modify the parameter. +func (s *hmacSession) Encrypt(parameter []byte) error { + if !s.IsDecryption() { + return nil + } + // Only AES-CFB is supported. + bits, err := s.symmetric.KeyBits.AES() + if err != nil { + return err + } + keyBytes := *bits / 8 + keyIVBytes := int(keyBytes) + 16 + var sessionValue []byte + sessionValue = append(sessionValue, s.sessionKey...) + sessionValue = append(sessionValue, s.auth...) + ha, err := s.hash.Hash() + if err != nil { + return err + } + keyIV := KDFa(ha, sessionValue, "CFB", s.nonceCaller.Buffer, s.nonceTPM.Buffer, keyIVBytes*8) + key, err := aes.NewCipher(keyIV[:keyBytes]) + if err != nil { + return err + } + stream := cipher.NewCFBEncrypter(key, keyIV[keyBytes:]) + stream.XORKeyStream(parameter, parameter) + return nil +} + +// Decrypt encrypts the parameter in place, if this session is used for +// parameter encryption. Otherwise, it does not modify the parameter. +func (s *hmacSession) Decrypt(parameter []byte) error { + if !s.IsEncryption() { + return nil + } + // Only AES-CFB is supported. + bits, err := s.symmetric.KeyBits.AES() + if err != nil { + return err + } + keyBytes := *bits / 8 + keyIVBytes := int(keyBytes) + 16 + // Part 1, 21.1 + var sessionValue []byte + sessionValue = append(sessionValue, s.sessionKey...) + sessionValue = append(sessionValue, s.auth...) + ha, err := s.hash.Hash() + if err != nil { + return err + } + keyIV := KDFa(ha, sessionValue, "CFB", s.nonceTPM.Buffer, s.nonceCaller.Buffer, keyIVBytes*8) + key, err := aes.NewCipher(keyIV[:keyBytes]) + if err != nil { + return err + } + stream := cipher.NewCFBDecrypter(key, keyIV[keyBytes:]) + stream.XORKeyStream(parameter, parameter) + return nil +} + +// Handle returns the handle value of the session. +// If the session is created with HMAC (instead of HMACSession) this will be +// TPM_RH_NULL. +func (s *hmacSession) Handle() TPMHandle { + return s.handle +} + +// PolicyCallback represents an object's policy in the form of a function. +// This function makes zero or more TPM policy commands and returns error. +type PolicyCallback = func(tpm transport.TPM, handle TPMISHPolicy, nonceTPM TPM2BNonce) error + +// policySession generally implements the policy session. +type policySession struct { + sessionOptions + hash TPMIAlgHash + nonceSize int + handle TPMHandle + sessionKey []byte + // last nonceCaller + nonceCaller TPM2BNonce + // last nonceTPM + nonceTPM TPM2BNonce + callback *PolicyCallback +} + +// Policy sets up a just-in-time policy session that created each time it's +// needed. +// Each time the policy is created, the callback is invoked to authorize the +// session. +// A real session is created, but just in time, and it is flushed when used. +func Policy(hash TPMIAlgHash, nonceSize int, callback PolicyCallback, opts ...AuthOption) Session { + // Set up a one-off session that knows the auth value. + sess := policySession{ + sessionOptions: defaultOptions(), + hash: hash, + nonceSize: nonceSize, + handle: TPMRHNull, + callback: &callback, + } + for _, opt := range opts { + opt(&sess.sessionOptions) + } + return &sess +} + +// PolicySession opens a policy session that needs to be closed. +// The caller is responsible to call whichever policy commands they want in the +// session. +// Note that the TPM resets a policy session after it is successfully used. +func PolicySession(t transport.TPM, hash TPMIAlgHash, nonceSize int, opts ...AuthOption) (s Session, close func() error, err error) { + // Set up a not-one-off session that knows the auth value. + sess := policySession{ + sessionOptions: defaultOptions(), + hash: hash, + nonceSize: nonceSize, + handle: TPMRHNull, + } + for _, opt := range opts { + opt(&sess.sessionOptions) + } + + // This session is reusable and is closed with the function we'll + // return. + sess.sessionOptions.attrs.ContinueSession = true + + // Initialize the session. + if err := sess.Init(t); err != nil { + return nil, nil, err + } + + closer := func() error { + _, err := (&FlushContext{sess.handle}).Execute(t) + return err + } + + return &sess, closer, nil +} + +// Init initializes the session, just in time, if needed. +func (s *policySession) Init(t transport.TPM) error { + if s.handle != TPMRHNull { + // Session is already initialized. + return nil + } + + // Get a high-quality nonceCaller for our use. + // Store it with the session object for later reference. + s.nonceCaller = TPM2BNonce{ + Buffer: make([]byte, s.nonceSize), + } + if _, err := rand.Read(s.nonceCaller.Buffer); err != nil { + return err + } + + sessType := TPMSEPolicy + if s.sessionOptions.trialPolicy { + sessType = TPMSETrial + } + + // Start up the actual auth session. + sasCmd := StartAuthSession{ + TPMKey: s.saltHandle, + Bind: s.bindHandle, + NonceCaller: s.nonceCaller, + SessionType: sessType, + Symmetric: s.symmetric, + AuthHash: s.hash, + } + var salt []byte + if s.saltHandle != TPMRHNull { + var err error + var encSalt *TPM2BEncryptedSecret + encSalt, salt, err = getEncryptedSalt(s.saltPub) + if err != nil { + return err + } + sasCmd.EncryptedSalt = *encSalt + } + sasRsp, err := sasCmd.Execute(t) + if err != nil { + return err + } + s.handle = TPMHandle(sasRsp.SessionHandle.HandleValue()) + s.nonceTPM = sasRsp.NonceTPM + // Part 1, 19.6 + if s.bindHandle != TPMRHNull || len(salt) != 0 { + var authSalt []byte + authSalt = append(authSalt, s.bindAuth...) + authSalt = append(authSalt, salt...) + ha, err := s.hash.Hash() + if err != nil { + return err + } + s.sessionKey = KDFa(ha, authSalt, "ATH", s.nonceTPM.Buffer, s.nonceCaller.Buffer, ha.Size()*8) + } + + // Call the callback to execute the policy, if needed + if s.callback != nil { + if err := (*s.callback)(t, s.handle, s.nonceTPM); err != nil { + return fmt.Errorf("executing policy: %w", err) + } + } + + return nil +} + +// CleanupFailure cleans up the session, if needed. +func (s *policySession) CleanupFailure(t transport.TPM) error { + // The user is already responsible to clean up this session. + if s.attrs.ContinueSession { + return nil + } + fc := FlushContext{FlushHandle: s.handle} + if _, err := fc.Execute(t); err != nil { + return err + } + s.handle = TPMRHNull + return nil +} + +// NonceTPM returns the last nonceTPM value from the session. +// May be nil, if the session hasn't been initialized yet. +func (s *policySession) NonceTPM() TPM2BNonce { return s.nonceTPM } + +// NewNonceCaller updates the nonceCaller for this session. +func (s *policySession) NewNonceCaller() error { + _, err := rand.Read(s.nonceCaller.Buffer) + return err +} + +// Authorize computes the authorization structure for the session. +func (s *policySession) Authorize(cc TPMCC, parms, addNonces []byte, names []TPM2BName, _ int) (*TPMSAuthCommand, error) { + if s.handle == TPMRHNull { + // Session is not initialized. + return nil, fmt.Errorf("session not initialized") + } + + var hmac []byte + if s.password { + hmac = s.auth + } else { + // Part 1, 19.6 + // HMAC key is (sessionKey || auth). + var hmacKey []byte + hmacKey = append(hmacKey, s.sessionKey...) + hmacKey = append(hmacKey, hmacKeyFromAuthValue(s.auth)...) + + // Compute the authorization HMAC. + cph, err := cpHash(s.hash, cc, names, parms) + if err != nil { + return nil, err + } + hmac, err = computeHMAC(s.hash, hmacKey, cph, s.nonceCaller.Buffer, s.nonceTPM.Buffer, addNonces, s.attrs) + if err != nil { + return nil, err + } + } + + result := TPMSAuthCommand{ + Handle: s.handle, + Nonce: s.nonceCaller, + Attributes: s.attrs, + Authorization: TPM2BData{ + Buffer: hmac, + }, + } + return &result, nil +} + +// Validate valitades the response session structure for the session. +// Updates nonceTPM from the TPM's response. +func (s *policySession) Validate(rc TPMRC, cc TPMCC, parms []byte, _ []TPM2BName, _ int, auth *TPMSAuthResponse) error { + // Track the new nonceTPM for the session. + s.nonceTPM = auth.Nonce + // Track the session being automatically flushed. + if !auth.Attributes.ContinueSession { + s.handle = TPMRHNull + } + + if s.password { + // If we used a password, expect no nonce and no response HMAC. + if len(auth.Nonce.Buffer) != 0 { + return fmt.Errorf("expected empty nonce in response auth to PW policy, got %x", auth.Nonce) + } + if len(auth.Authorization.Buffer) != 0 { + return fmt.Errorf("expected empty HMAC in response auth to PW policy, got %x", auth.Authorization) + } + } else { + // Part 1, 19.6 + // HMAC key is (sessionKey || auth). + var hmacKey []byte + hmacKey = append(hmacKey, s.sessionKey...) + hmacKey = append(hmacKey, hmacKeyFromAuthValue(s.auth)...) + // Compute the authorization HMAC. + rph, err := rpHash(s.hash, rc, cc, parms) + if err != nil { + return err + } + mac, err := computeHMAC(s.hash, hmacKey, rph, s.nonceTPM.Buffer, s.nonceCaller.Buffer, nil, auth.Attributes) + if err != nil { + return err + } + // Compare the HMAC (constant time) + if !hmac.Equal(mac, auth.Authorization.Buffer) { + return fmt.Errorf("incorrect authorization HMAC") + } + } + return nil +} + +// IsEncryption returns true if this is an encryption session. +func (s *policySession) IsEncryption() bool { + return s.attrs.Encrypt +} + +// IsDecryption returns true if this is a decryption session. +func (s *policySession) IsDecryption() bool { + return s.attrs.Decrypt +} + +// Encrypt encrypts the parameter in place, if this session is used for +// parameter decryption. Otherwise, it does not modify the parameter. +func (s *policySession) Encrypt(parameter []byte) error { + if !s.IsDecryption() { + return nil + } + // Only AES-CFB is supported. + bits, err := s.symmetric.KeyBits.AES() + if err != nil { + return err + } + keyBytes := *bits / 8 + keyIVBytes := int(keyBytes) + 16 + var sessionValue []byte + sessionValue = append(sessionValue, s.sessionKey...) + sessionValue = append(sessionValue, s.auth...) + ha, err := s.hash.Hash() + if err != nil { + return err + } + keyIV := KDFa(ha, sessionValue, "CFB", s.nonceCaller.Buffer, s.nonceTPM.Buffer, keyIVBytes*8) + key, err := aes.NewCipher(keyIV[:keyBytes]) + if err != nil { + return err + } + stream := cipher.NewCFBEncrypter(key, keyIV[keyBytes:]) + stream.XORKeyStream(parameter, parameter) + return nil +} + +// Decrypt decrypts the parameter in place, if this session is used for +// parameter encryption. Otherwise, it does not modify the parameter. +func (s *policySession) Decrypt(parameter []byte) error { + if !s.IsEncryption() { + return nil + } + // Only AES-CFB is supported. + bits, err := s.symmetric.KeyBits.AES() + if err != nil { + return err + } + keyBytes := *bits / 8 + keyIVBytes := int(keyBytes) + 16 + // Part 1, 21.1 + var sessionValue []byte + sessionValue = append(sessionValue, s.sessionKey...) + sessionValue = append(sessionValue, s.auth...) + ha, err := s.hash.Hash() + if err != nil { + return err + } + keyIV := KDFa(ha, sessionValue, "CFB", s.nonceTPM.Buffer, s.nonceCaller.Buffer, keyIVBytes*8) + key, err := aes.NewCipher(keyIV[:keyBytes]) + if err != nil { + return err + } + stream := cipher.NewCFBDecrypter(key, keyIV[keyBytes:]) + stream.XORKeyStream(parameter, parameter) + return nil +} + +// Handle returns the handle value of the session. +// If the session is created with Policy (instead of PolicySession) this will be +// TPM_RH_NULL. +func (s *policySession) Handle() TPMHandle { + return s.handle +} diff --git a/vendor/github.com/google/go-tpm/tpm2/structures.go b/vendor/github.com/google/go-tpm/tpm2/structures.go new file mode 100644 index 00000000000..b173ed29b11 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/structures.go @@ -0,0 +1,3331 @@ +// Package tpm2 defines all the TPM 2.0 structures together to avoid import cycles +package tpm2 + +import ( + "bytes" + "crypto" + "crypto/ecdh" + "crypto/elliptic" + "encoding/binary" + "reflect" + + // Register the relevant hash implementations. + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "fmt" +) + +// TPMCmdHeader is the header structure in front of any TPM command. +// It is described in Part 1, Architecture. +type TPMCmdHeader struct { + marshalByReflection + Tag TPMISTCommandTag + Length uint32 + CommandCode TPMCC +} + +// TPMRspHeader is the header structure in front of any TPM response. +// It is described in Part 1, Architecture. +type TPMRspHeader struct { + marshalByReflection + Tag TPMISTCommandTag + Length uint32 + ResponseCode TPMRC +} + +// TPMAlgorithmID represents a TPM_ALGORITHM_ID +// this is the 1.2 compatible form of the TPM_ALG_ID +// See definition in Part 2, Structures, section 5.3. +type TPMAlgorithmID uint32 + +// TPMModifierIndicator represents a TPM_MODIFIER_INDICATOR. +// See definition in Part 2, Structures, section 5.3. +type TPMModifierIndicator uint32 + +// TPMAuthorizationSize represents a TPM_AUTHORIZATION_SIZE. +// the authorizationSize parameter in a command +// See definition in Part 2, Structures, section 5.3. +type TPMAuthorizationSize uint32 + +// TPMParameterSize represents a TPM_PARAMETER_SIZE. +// the parameterSize parameter in a command +// See definition in Part 2, Structures, section 5.3. +type TPMParameterSize uint32 + +// TPMKeySize represents a TPM_KEY_SIZE. +// a key size in octets +// See definition in Part 2, Structures, section 5.3. +type TPMKeySize uint16 + +// TPMKeyBits represents a TPM_KEY_BITS. +// a key size in bits +// See definition in Part 2, Structures, section 5.3. +type TPMKeyBits uint16 + +// TPMGenerated represents a TPM_GENERATED. +// See definition in Part 2: Structures, section 6.2. +type TPMGenerated uint32 + +// Generated values come from Part 2: Structures, section 6.2. +const ( + TPMGeneratedValue TPMGenerated = 0xff544347 +) + +// Check verifies that a TPMGenerated value is correct, and returns an error +// otherwise. +func (g TPMGenerated) Check() error { + if g != TPMGeneratedValue { + return fmt.Errorf("TPM_GENERATED value should be 0x%x, was 0x%x", TPMGeneratedValue, g) + } + return nil +} + +// Curve returns the elliptic.Curve associated with a TPMECCCurve. +func (c TPMECCCurve) Curve() (elliptic.Curve, error) { + switch c { + case TPMECCNistP224: + return elliptic.P224(), nil + case TPMECCNistP256: + return elliptic.P256(), nil + case TPMECCNistP384: + return elliptic.P384(), nil + case TPMECCNistP521: + return elliptic.P521(), nil + default: + return nil, fmt.Errorf("unsupported ECC curve: %v", c) + } +} + +// ECDHCurve returns the ecdh.Curve associated with a TPMECCCurve. +func (c TPMECCCurve) ECDHCurve() (ecdh.Curve, error) { + switch c { + case TPMECCNistP256: + return ecdh.P256(), nil + case TPMECCNistP384: + return ecdh.P384(), nil + case TPMECCNistP521: + return ecdh.P521(), nil + default: + return nil, fmt.Errorf("unsupported ECC curve: %v", c) + } +} + +// HandleValue returns the handle value. This behavior is intended to satisfy +// an interface that can be implemented by other, more complex types as well. +func (h TPMHandle) HandleValue() uint32 { + return uint32(h) +} + +// KnownName returns the TPM Name associated with the handle, if it can be known +// based only on the handle. This depends upon the value of the handle: +// only PCR, session, and permanent values have known constant Names. +// See definition in part 1: Architecture, section 16. +func (h TPMHandle) KnownName() *TPM2BName { + switch (TPMHT)(h >> 24) { + case TPMHTPCR, TPMHTHMACSession, TPMHTPolicySession, TPMHTPermanent: + result := make([]byte, 4) + binary.BigEndian.PutUint32(result, h.HandleValue()) + return &TPM2BName{Buffer: result} + } + return nil +} + +// TPMAAlgorithm represents a TPMA_ALGORITHM. +// See definition in Part 2: Structures, section 8.2. +type TPMAAlgorithm struct { + bitfield32 + marshalByReflection + // SET (1): an asymmetric algorithm with public and private portions + // CLEAR (0): not an asymmetric algorithm + Asymmetric bool `gotpm:"bit=0"` + // SET (1): a symmetric block cipher + // CLEAR (0): not a symmetric block cipher + Symmetric bool `gotpm:"bit=1"` + // SET (1): a hash algorithm + // CLEAR (0): not a hash algorithm + Hash bool `gotpm:"bit=2"` + // SET (1): an algorithm that may be used as an object type + // CLEAR (0): an algorithm that is not used as an object type + Object bool `gotpm:"bit=3"` + // SET (1): a signing algorithm. The setting of asymmetric, + // symmetric, and hash will indicate the type of signing algorithm. + // CLEAR (0): not a signing algorithm + Signing bool `gotpm:"bit=8"` + // SET (1): an encryption/decryption algorithm. The setting of + // asymmetric, symmetric, and hash will indicate the type of + // encryption/decryption algorithm. + // CLEAR (0): not an encryption/decryption algorithm + Encrypting bool `gotpm:"bit=9"` + // SET (1): a method such as a key derivative function (KDF) + // CLEAR (0): not a method + Method bool `gotpm:"bit=10"` +} + +// TPMAObject represents a TPMA_OBJECT. +// See definition in Part 2: Structures, section 8.3.2. +type TPMAObject struct { + bitfield32 + marshalByReflection + // SET (1): The hierarchy of the object, as indicated by its + // Qualified Name, may not change. + // CLEAR (0): The hierarchy of the object may change as a result + // of this object or an ancestor key being duplicated for use in + // another hierarchy. + FixedTPM bool `gotpm:"bit=1"` + // SET (1): Previously saved contexts of this object may not be + // loaded after Startup(CLEAR). + // CLEAR (0): Saved contexts of this object may be used after a + // Shutdown(STATE) and subsequent Startup(). + STClear bool `gotpm:"bit=2"` + // SET (1): The parent of the object may not change. + // CLEAR (0): The parent of the object may change as the result of + // a TPM2_Duplicate() of the object. + FixedParent bool `gotpm:"bit=4"` + // SET (1): Indicates that, when the object was created with + // TPM2_Create() or TPM2_CreatePrimary(), the TPM generated all of + // the sensitive data other than the authValue. + // CLEAR (0): A portion of the sensitive data, other than the + // authValue, was provided by the caller. + SensitiveDataOrigin bool `gotpm:"bit=5"` + // SET (1): Approval of USER role actions with this object may be + // with an HMAC session or with a password using the authValue of + // the object or a policy session. + // CLEAR (0): Approval of USER role actions with this object may + // only be done with a policy session. + UserWithAuth bool `gotpm:"bit=6"` + // SET (1): Approval of ADMIN role actions with this object may + // only be done with a policy session. + // CLEAR (0): Approval of ADMIN role actions with this object may + // be with an HMAC session or with a password using the authValue + // of the object or a policy session. + AdminWithPolicy bool `gotpm:"bit=7"` + // SET (1): The object exists only within a firmware-limited hierarchy. + // CLEAR (0): The object can exist outside a firmware-limited hierarchy. + FirmwareLimited bool `gotpm:"bit=8"` + // SET (1): The object is not subject to dictionary attack + // protections. + // CLEAR (0): The object is subject to dictionary attack + // protections. + NoDA bool `gotpm:"bit=10"` + // SET (1): If the object is duplicated, then symmetricAlg shall + // not be TPM_ALG_NULL and newParentHandle shall not be + // TPM_RH_NULL. + // CLEAR (0): The object may be duplicated without an inner + // wrapper on the private portion of the object and the new parent + // may be TPM_RH_NULL. + EncryptedDuplication bool `gotpm:"bit=11"` + // SET (1): Key usage is restricted to manipulate structures of + // known format; the parent of this key shall have restricted SET. + // CLEAR (0): Key usage is not restricted to use on special + // formats. + Restricted bool `gotpm:"bit=16"` + // SET (1): The private portion of the key may be used to decrypt. + // CLEAR (0): The private portion of the key may not be used to + // decrypt. + Decrypt bool `gotpm:"bit=17"` + // SET (1): For a symmetric cipher object, the private portion of + // the key may be used to encrypt. For other objects, the private + // portion of the key may be used to sign. + // CLEAR (0): The private portion of the key may not be used to + // sign or encrypt. + SignEncrypt bool `gotpm:"bit=18"` + // SET (1): An asymmetric key that may not be used to sign with + // TPM2_Sign() CLEAR (0): A key that may be used with TPM2_Sign() + // if sign is SET + // NOTE: This attribute only has significance if sign is SET. + X509Sign bool `gotpm:"bit=19"` +} + +// TPMASession represents a TPMA_SESSION. +// See definition in Part 2: Structures, section 8.4. +type TPMASession struct { + bitfield8 + marshalByReflection + // SET (1): In a command, this setting indicates that the session + // is to remain active after successful completion of the command. + // In a response, it indicates that the session is still active. + // If SET in the command, this attribute shall be SET in the response. + // CLEAR (0): In a command, this setting indicates that the TPM should + // close the session and flush any related context when the command + // completes successfully. In a response, it indicates that the + // session is closed and the context is no longer active. + // This attribute has no meaning for a password authorization and the + // TPM will allow any setting of the attribute in the command and SET + // the attribute in the response. + ContinueSession bool `gotpm:"bit=0"` + // SET (1): In a command, this setting indicates that the command + // should only be executed if the session is exclusive at the start of + // the command. In a response, it indicates that the session is + // exclusive. This setting is only allowed if the audit attribute is + // SET (TPM_RC_ATTRIBUTES). + // CLEAR (0): In a command, indicates that the session need not be + // exclusive at the start of the command. In a response, indicates that + // the session is not exclusive. + AuditExclusive bool `gotpm:"bit=1"` + // SET (1): In a command, this setting indicates that the audit digest + // of the session should be initialized and the exclusive status of the + // session SET. This setting is only allowed if the audit attribute is + // SET (TPM_RC_ATTRIBUTES). + // CLEAR (0): In a command, indicates that the audit digest should not + // be initialized. This bit is always CLEAR in a response. + AuditReset bool `gotpm:"bit=2"` + // SET (1): In a command, this setting indicates that the first + // parameter in the command is symmetrically encrypted using the + // parameter encryption scheme described in TPM 2.0 Part 1. The TPM will + // decrypt the parameter after performing any HMAC computations and + // before unmarshaling the parameter. In a response, the attribute is + // copied from the request but has no effect on the response. + // CLEAR (0): Session not used for encryption. + // For a password authorization, this attribute will be CLEAR in both the + // command and response. + Decrypt bool `gotpm:"bit=5"` + // SET (1): In a command, this setting indicates that the TPM should use + // this session to encrypt the first parameter in the response. In a + // response, it indicates that the attribute was set in the command and + // that the TPM used the session to encrypt the first parameter in the + // response using the parameter encryption scheme described in TPM 2.0 + // Part 1. + // CLEAR (0): Session not used for encryption. + // For a password authorization, this attribute will be CLEAR in both the + // command and response. + Encrypt bool `gotpm:"bit=6"` + // SET (1): In a command or response, this setting indicates that the + // session is for audit and that auditExclusive and auditReset have + // meaning. This session may also be used for authorization, encryption, + // or decryption. The encrypted and encrypt fields may be SET or CLEAR. + // CLEAR (0): Session is not used for audit. + // If SET in the command, then this attribute will be SET in the response. + Audit bool `gotpm:"bit=7"` +} + +// TPMALocality represents a TPMA_LOCALITY. +// See definition in Part 2: Structures, section 8.5. +type TPMALocality struct { + bitfield8 + marshalByReflection + TPMLocZero bool `gotpm:"bit=0"` + TPMLocOne bool `gotpm:"bit=1"` + TPMLocTwo bool `gotpm:"bit=2"` + TPMLocThree bool `gotpm:"bit=3"` + TPMLocFour bool `gotpm:"bit=4"` + // If any of these bits is set, an extended locality is indicated + Extended uint8 `gotpm:"bit=7:5"` +} + +// TPMACC represents a TPMA_CC. +// See definition in Part 2: Structures, section 8.9. +type TPMACC struct { + bitfield32 + marshalByReflection + // indicates the command being selected + CommandIndex uint16 `gotpm:"bit=15:0"` + // SET (1): indicates that the command may write to NV + // CLEAR (0): indicates that the command does not write to NV + NV bool `gotpm:"bit=22"` + // SET (1): This command could flush any number of loaded contexts. + // CLEAR (0): no additional changes other than indicated by the flushed attribute + Extensive bool `gotpm:"bit=23"` + // SET (1): The context associated with any transient handle in the command will be flushed when this command completes. + // CLEAR (0): No context is flushed as a side effect of this command. + Flushed bool `gotpm:"bit=24"` + // indicates the number of the handles in the handle area for this command + CHandles uint8 `gotpm:"bit=27:25"` + // SET (1): indicates the presence of the handle area in the response + RHandle bool `gotpm:"bit=28"` + // SET (1): indicates that the command is vendor-specific + // CLEAR (0): indicates that the command is defined in a version of this specification + V bool `gotpm:"bit=29"` +} + +// TPMAACT represents a TPMA_ACT. +// See definition in Part 2: Structures, section 8.12. +type TPMAACT struct { + bitfield32 + marshalByReflection + // SET (1): The ACT has signaled + // CLEAR (0): The ACT has not signaled + Signaled bool `gotpm:"bit=0"` + // SET (1): The ACT signaled bit is preserved over a power cycle + // CLEAR (0): The ACT signaled bit is not preserved over a power cycle + PreserveSignaled bool `gotpm:"bit=1"` +} + +// TPMIYesNo represents a TPMI_YES_NO. +// See definition in Part 2: Structures, section 9.2. +// Use native bool for TPMI_YES_NO; encoding/binary already treats this as 8 bits wide. +type TPMIYesNo = bool + +// TPMIDHObject represents a TPMI_DH_OBJECT. +// See definition in Part 2: Structures, section 9.3. +type TPMIDHObject = TPMHandle + +// TPMIDHPersistent represents a TPMI_DH_PERSISTENT. +// See definition in Part 2: Structures, section 9.5. +type TPMIDHPersistent = TPMHandle + +// TPMIDHEntity represents a TPMI_DH_ENTITY. +// See definition in Part 2: Structures, section 9.6. +type TPMIDHEntity = TPMHandle + +// TPMISHAuthSession represents a TPMI_SH_AUTH_SESSION. +// See definition in Part 2: Structures, section 9.8. +type TPMISHAuthSession = TPMHandle + +// TPMISHHMAC represents a TPMI_SH_HMAC. +// See definition in Part 2: Structures, section 9.9. +type TPMISHHMAC = TPMHandle + +// TPMISHPolicy represents a TPMI_SH_POLICY. +// See definition in Part 2: Structures, section 9.10. +type TPMISHPolicy = TPMHandle + +// TPMIDHContext represents a TPMI_DH_CONTEXT. +// See definition in Part 2: Structures, section 9.11. +type TPMIDHContext = TPMHandle + +// TPMIDHSaved represents a TPMI_DH_SAVED. +// See definition in Part 2: Structures, section 9.12. +type TPMIDHSaved = TPMHandle + +// TPMIRHHierarchy represents a TPMI_RH_HIERARCHY. +// See definition in Part 2: Structures, section 9.13. +type TPMIRHHierarchy = TPMHandle + +// TPMIRHEnables represents a TPMI_RH_ENABLES. +// See definition in Part 2: Structures, section 9.14. +type TPMIRHEnables = TPMHandle + +// TPMIRHHierarchyAuth represents a TPMI_RH_HIERARCHY_AUTH. +// See definition in Part 2: Structures, section 9.15. +type TPMIRHHierarchyAuth = TPMHandle + +// TPMIRHHierarchyPolicy represents a TPMI_RH_HIERARCHY_POLICY. +// See definition in Part 2: Structures, section 9.16. +type TPMIRHHierarchyPolicy = TPMHandle + +// TPMIRHPlatform represents a TPMI_RH_PLATFORM. +// See definition in Part 2: Structures, section 9.17. +type TPMIRHPlatform = TPMHandle + +// TPMIRHOwner represents a TPMI_RH_OWNER. +// See definition in Part 2: Structures, section 9.18. +type TPMIRHOwner = TPMHandle + +// TPMIRHEndorsement represents a TPMI_RH_ENDORSEMENT. +// See definition in Part 2: Structures, section 9.19. +type TPMIRHEndorsement = TPMHandle + +// TPMIRHProvision represents a TPMI_RH_PROVISION. +// See definition in Part 2: Structures, section 9.20. +type TPMIRHProvision = TPMHandle + +// TPMIRHClear represents a TPMI_RH_CLEAR. +// See definition in Part 2: Structures, section 9.21. +type TPMIRHClear = TPMHandle + +// TPMIRHNVAuth represents a TPMI_RH_NV_AUTH. +// See definition in Part 2: Structures, section 9.22. +type TPMIRHNVAuth = TPMHandle + +// TPMIRHLockout represents a TPMI_RH_LOCKOUT. +// See definition in Part 2: Structures, section 9.23. +type TPMIRHLockout = TPMHandle + +// TPMIRHNVIndex represents a TPMI_RH_NV_INDEX. +// See definition in Part 2: Structures, section 9.24. +type TPMIRHNVIndex = TPMHandle + +// TPMIRHAC represents a TPMI_RH_AC. +// See definition in Part 2: Structures, section 9.25. +type TPMIRHAC = TPMHandle + +// TPMIRHACT represents a TPMI_RH_ACT. +// See definition in Part 2: Structures, section 9.26. +type TPMIRHACT = TPMHandle + +// TPMIAlgHash represents a TPMI_ALG_HASH. +// See definition in Part 2: Structures, section 9.27. +type TPMIAlgHash = TPMAlgID + +// Hash returns the crypto.Hash associated with a TPMIAlgHash. +func (a TPMIAlgHash) Hash() (crypto.Hash, error) { + switch TPMAlgID(a) { + case TPMAlgSHA1: + return crypto.SHA1, nil + case TPMAlgSHA256: + return crypto.SHA256, nil + case TPMAlgSHA384: + return crypto.SHA384, nil + case TPMAlgSHA512: + return crypto.SHA512, nil + } + return crypto.SHA256, fmt.Errorf("unsupported hash algorithm: %v", a) +} + +// TPMIAlgSym represents a TPMI_ALG_SYM. +// See definition in Part 2: Structures, section 9.29. +type TPMIAlgSym = TPMAlgID + +// TPMIAlgSymObject represents a TPMI_ALG_SYM_OBJECT. +// See definition in Part 2: Structures, section 9.30. +type TPMIAlgSymObject = TPMAlgID + +// TPMIAlgSymMode represents a TPMI_ALG_SYM_MODE. +// See definition in Part 2: Structures, section 9.31. +type TPMIAlgSymMode = TPMAlgID + +// TPMIAlgKDF represents a TPMI_ALG_KDF. +// See definition in Part 2: Structures, section 9.32. +type TPMIAlgKDF = TPMAlgID + +// TPMIAlgSigScheme represents a TPMI_ALG_SIG_SCHEME. +// See definition in Part 2: Structures, section 9.33. +type TPMIAlgSigScheme = TPMAlgID + +// TPMISTCommandTag represents a TPMI_ST_COMMAND_TAG. +// See definition in Part 2: Structures, section 9.35. +type TPMISTCommandTag = TPMST + +// TPMSEmpty represents a TPMS_EMPTY. +// See definition in Part 2: Structures, section 10.1. +type TPMSEmpty struct { + marshalByReflection +} + +// TPMTHA represents a TPMT_HA. +// See definition in Part 2: Structures, section 10.3.2. +type TPMTHA struct { + marshalByReflection + // selector of the hash contained in the digest that implies the size of the digest + HashAlg TPMIAlgHash `gotpm:"nullable"` + // the digest data + // NOTE: For convenience, this is not implemented as a union. + Digest []byte +} + +// TPM2BDigest represents a TPM2B_DIGEST. +// See definition in Part 2: Structures, section 10.4.2. +type TPM2BDigest TPM2BData + +// TPM2BData represents a TPM2B_DATA. +// See definition in Part 2: Structures, section 10.4.3. +type TPM2BData struct { + marshalByReflection + // size in octets of the buffer field; may be 0 + Buffer []byte `gotpm:"sized"` +} + +// TPM2BNonce represents a TPM2B_NONCE. +// See definition in Part 2: Structures, section 10.4.4. +type TPM2BNonce TPM2BDigest + +// TPM2BEvent represents a TPM2B_EVENT. +// See definition in Part 2: Structures, section 10.4.7. +type TPM2BEvent TPM2BData + +// TPM2BTimeout represents a TPM2B_TIMEOUT. +// See definition in Part 2: Structures, section 10.4.10. +type TPM2BTimeout TPM2BData + +// TPM2BAuth represents a TPM2B_AUTH. +// See definition in Part 2: Structures, section 10.4.5. +type TPM2BAuth TPM2BDigest + +// TPM2BOperand represents a TPM2B_Operand. +// See definition in Part 2: Structures, section 10.4.6. +type TPM2BOperand TPM2BDigest + +// TPM2BMaxBuffer represents a TPM2B_MAX_BUFFER. +// See definition in Part 2: Structures, section 10.4.8. +type TPM2BMaxBuffer TPM2BData + +// TPM2BMaxNVBuffer represents a TPM2B_MAX_NV_BUFFER. +// See definition in Part 2: Structures, section 10.4.9. +type TPM2BMaxNVBuffer TPM2BData + +// TPM2BIV represents a TPM2B_IV. +// See definition in Part 2: Structures, section 10.4.11. +type TPM2BIV TPM2BData + +// TPM2BName represents a TPM2B_NAME. +// See definition in Part 2: Structures, section 10.5.3. +// NOTE: This structure does not contain a TPMUName, because that union +// is not tagged with a selector. Instead, TPM2B_Name is flattened and +// all TPMDirect helpers that deal with names will deal with them as so. +type TPM2BName TPM2BData + +// TPMSPCRSelection represents a TPMS_PCR_SELECTION. +// See definition in Part 2: Structures, section 10.6.2. +type TPMSPCRSelection struct { + marshalByReflection + Hash TPMIAlgHash + PCRSelect []byte `gotpm:"sized8"` +} + +// TPMTTKCreation represents a TPMT_TK_CREATION. +// See definition in Part 2: Structures, section 10.7.3. +type TPMTTKCreation struct { + marshalByReflection + // ticket structure tag + Tag TPMST + // the hierarchy containing name + Hierarchy TPMIRHHierarchy + // This shall be the HMAC produced using a proof value of hierarchy. + Digest TPM2BDigest +} + +// TPMTTKVerified represents a TPMT_TK_Verified. +// See definition in Part 2: Structures, section 10.7.4. +type TPMTTKVerified struct { + marshalByReflection + // ticket structure tag + Tag TPMST + // the hierarchy containing keyName + Hierarchy TPMIRHHierarchy + // This shall be the HMAC produced using a proof value of hierarchy. + Digest TPM2BDigest +} + +// TPMTTKAuth represents a TPMT_TK_AUTH. +// See definition in Part 2: Structures, section 10.7.5. +type TPMTTKAuth struct { + marshalByReflection + // ticket structure tag + Tag TPMST + // the hierarchy of the object used to produce the ticket + Hierarchy TPMIRHHierarchy `gotpm:"nullable"` + // This shall be the HMAC produced using a proof value of hierarchy. + Digest TPM2BDigest +} + +// TPMTTKHashCheck represents a TPMT_TK_HASHCHECK. +// See definition in Part 2: Structures, section 10.7.6. +type TPMTTKHashCheck struct { + marshalByReflection + // ticket structure tag + Tag TPMST + // the hierarchy + Hierarchy TPMIRHHierarchy `gotpm:"nullable"` + // This shall be the HMAC produced using a proof value of hierarchy. + Digest TPM2BDigest +} + +// TPMSAlgProperty represents a TPMS_ALG_PROPERTY. +// See definition in Part 2: Structures, section 10.8.1. +type TPMSAlgProperty struct { + marshalByReflection + // an algorithm identifier + Alg TPMAlgID + // the attributes of the algorithm + AlgProperties TPMAAlgorithm +} + +// TPMSTaggedProperty represents a TPMS_TAGGED_PROPERTY. +// See definition in Part 2: Structures, section 10.8.2. +type TPMSTaggedProperty struct { + marshalByReflection + // a property identifier + Property TPMPT + // the value of the property + Value uint32 +} + +// TPMSTaggedPCRSelect represents a TPMS_TAGGED_PCR_SELECT. +// See definition in Part 2: Structures, section 10.8.3. +type TPMSTaggedPCRSelect struct { + marshalByReflection + // the property identifier + Tag TPMPTPCR + // the bit map of PCR with the identified property + PCRSelect []byte `gotpm:"sized8"` +} + +// TPMSTaggedPolicy represents a TPMS_TAGGED_POLICY. +// See definition in Part 2: Structures, section 10.8.4. +type TPMSTaggedPolicy struct { + marshalByReflection + // a permanent handle + Handle TPMHandle + // the policy algorithm and hash + PolicyHash TPMTHA +} + +// TPMSACTData represents a TPMS_ACT_DATA. +// See definition in Part 2: Structures, section 10.8.5. +type TPMSACTData struct { + marshalByReflection + // a permanent handle + Handle TPMHandle + // the current timeout of the ACT + Timeout uint32 + // the state of the ACT + Attributes TPMAACT +} + +// TPMLCC represents a TPML_CC. +// See definition in Part 2: Structures, section 10.9.1. +type TPMLCC struct { + marshalByReflection + CommandCodes []TPMCC `gotpm:"list"` +} + +// TPMLCCA represents a TPML_CCA. +// See definition in Part 2: Structures, section 10.9.2. +type TPMLCCA struct { + marshalByReflection + CommandAttributes []TPMACC `gotpm:"list"` +} + +// TPMLAlg represents a TPML_ALG. +// See definition in Part 2: Structures, section 10.9.3. +type TPMLAlg struct { + marshalByReflection + Algorithms []TPMAlgID `gotpm:"list"` +} + +// TPMLHandle represents a TPML_HANDLE. +// See definition in Part 2: Structures, section 10.9.4. +type TPMLHandle struct { + marshalByReflection + Handle []TPMHandle `gotpm:"list"` +} + +// TPMLDigest represents a TPML_DIGEST. +// See definition in Part 2: Structures, section 10.9.5. +type TPMLDigest struct { + marshalByReflection + // a list of digests + Digests []TPM2BDigest `gotpm:"list"` +} + +// TPMLDigestValues represents a TPML_DIGEST_VALUES. +// See definition in Part 2: Structures, section 10.9.6. +type TPMLDigestValues struct { + marshalByReflection + // a list of tagged digests + Digests []TPMTHA `gotpm:"list"` +} + +// TPMLPCRSelection represents a TPML_PCR_SELECTION. +// See definition in Part 2: Structures, section 10.9.7. +type TPMLPCRSelection struct { + marshalByReflection + PCRSelections []TPMSPCRSelection `gotpm:"list"` +} + +// TPMLAlgProperty represents a TPML_ALG_PROPERTY. +// See definition in Part 2: Structures, section 10.9.8. +type TPMLAlgProperty struct { + marshalByReflection + AlgProperties []TPMSAlgProperty `gotpm:"list"` +} + +// TPMLTaggedTPMProperty represents a TPML_TAGGED_TPM_PROPERTY. +// See definition in Part 2: Structures, section 10.9.9. +type TPMLTaggedTPMProperty struct { + marshalByReflection + TPMProperty []TPMSTaggedProperty `gotpm:"list"` +} + +// TPMLTaggedPCRProperty represents a TPML_TAGGED_PCR_PROPERTY. +// See definition in Part 2: Structures, section 10.9.10. +type TPMLTaggedPCRProperty struct { + marshalByReflection + PCRProperty []TPMSTaggedPCRSelect `gotpm:"list"` +} + +// TPMLECCCurve represents a TPML_ECC_CURVE. +// See definition in Part 2: Structures, section 10.9.11. +type TPMLECCCurve struct { + marshalByReflection + ECCCurves []TPMECCCurve `gotpm:"list"` +} + +// TPMLTaggedPolicy represents a TPML_TAGGED_POLICY. +// See definition in Part 2: Structures, section 10.9.12. +type TPMLTaggedPolicy struct { + marshalByReflection + Policies []TPMSTaggedPolicy `gotpm:"list"` +} + +// TPMLACTData represents a TPML_ACT_DATA. +// See definition in Part 2: Structures, section 10.9.13. +type TPMLACTData struct { + marshalByReflection + ACTData []TPMSACTData `gotpm:"list"` +} + +// TPMUCapabilities represents a TPMU_CAPABILITIES. +// See definition in Part 2: Structures, section 10.10.1. +type TPMUCapabilities struct { + selector TPMCap + contents Marshallable +} + +// CapabilitiesContents is a type constraint representing the possible contents of TPMUCapabilities. +type CapabilitiesContents interface { + Marshallable + *TPMLAlgProperty | *TPMLHandle | *TPMLCCA | *TPMLCC | *TPMLPCRSelection | *TPMLTaggedTPMProperty | + *TPMLTaggedPCRProperty | *TPMLECCCurve | *TPMLTaggedPolicy | *TPMLACTData +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUCapabilities) create(hint int64) (reflect.Value, error) { + switch TPMCap(hint) { + case TPMCapAlgs: + contents := TPMLAlgProperty{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapHandles: + contents := TPMLHandle{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapCommands: + contents := TPMLCCA{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapPPCommands, TPMCapAuditCommands: + contents := TPMLCC{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapPCRs: + contents := TPMLPCRSelection{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapTPMProperties: + contents := TPMLTaggedTPMProperty{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapPCRProperties: + contents := TPMLTaggedPCRProperty{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapECCCurves: + contents := TPMLECCCurve{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapAuthPolicies: + contents := TPMLTaggedPolicy{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + case TPMCapACT: + contents := TPMLACTData{} + u.contents = &contents + u.selector = TPMCap(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUCapabilities) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMCap(hint) { + case TPMCapAlgs: + contents := TPMLAlgProperty{} + if u.contents != nil { + contents = *u.contents.(*TPMLAlgProperty) + } + return reflect.ValueOf(&contents), nil + case TPMCapHandles: + contents := TPMLHandle{} + if u.contents != nil { + contents = *u.contents.(*TPMLHandle) + } + return reflect.ValueOf(&contents), nil + case TPMCapCommands: + contents := TPMLCCA{} + if u.contents != nil { + contents = *u.contents.(*TPMLCCA) + } + return reflect.ValueOf(&contents), nil + case TPMCapPPCommands, TPMCapAuditCommands: + contents := TPMLCC{} + if u.contents != nil { + contents = *u.contents.(*TPMLCC) + } + return reflect.ValueOf(&contents), nil + case TPMCapPCRs: + contents := TPMLPCRSelection{} + if u.contents != nil { + contents = *u.contents.(*TPMLPCRSelection) + } + return reflect.ValueOf(&contents), nil + case TPMCapTPMProperties: + contents := TPMLTaggedTPMProperty{} + if u.contents != nil { + contents = *u.contents.(*TPMLTaggedTPMProperty) + } + return reflect.ValueOf(&contents), nil + case TPMCapPCRProperties: + contents := TPMLTaggedPCRProperty{} + if u.contents != nil { + contents = *u.contents.(*TPMLTaggedPCRProperty) + } + return reflect.ValueOf(&contents), nil + case TPMCapECCCurves: + contents := TPMLECCCurve{} + if u.contents != nil { + contents = *u.contents.(*TPMLECCCurve) + } + return reflect.ValueOf(&contents), nil + case TPMCapAuthPolicies: + contents := TPMLTaggedPolicy{} + if u.contents != nil { + contents = *u.contents.(*TPMLTaggedPolicy) + } + return reflect.ValueOf(&contents), nil + case TPMCapACT: + contents := TPMLACTData{} + if u.contents != nil { + contents = *u.contents.(*TPMLACTData) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUCapabilities instantiates a TPMUCapabilities with the given contents. +func NewTPMUCapabilities[C CapabilitiesContents](selector TPMCap, contents C) TPMUCapabilities { + return TPMUCapabilities{ + selector: selector, + contents: contents, + } +} + +// Algorithms returns the 'algorithms' member of the union. +func (u *TPMUCapabilities) Algorithms() (*TPMLAlgProperty, error) { + if u.selector == TPMCapAlgs { + return u.contents.(*TPMLAlgProperty), nil + } + return nil, fmt.Errorf("did not contain algorithms (selector value was %v)", u.selector) +} + +// Handles returns the 'handles' member of the union. +func (u *TPMUCapabilities) Handles() (*TPMLHandle, error) { + if u.selector == TPMCapHandles { + return u.contents.(*TPMLHandle), nil + } + return nil, fmt.Errorf("did not contain handles (selector value was %v)", u.selector) +} + +// Command returns the 'command' member of the union. +func (u *TPMUCapabilities) Command() (*TPMLCCA, error) { + if u.selector == TPMCapCommands { + return u.contents.(*TPMLCCA), nil + } + return nil, fmt.Errorf("did not contain command (selector value was %v)", u.selector) +} + +// PPCommands returns the 'ppCommands' member of the union. +func (u *TPMUCapabilities) PPCommands() (*TPMLCC, error) { + if u.selector == TPMCapPPCommands { + return u.contents.(*TPMLCC), nil + } + return nil, fmt.Errorf("did not contain ppCommands (selector value was %v)", u.selector) +} + +// AuditCommands returns the 'auditCommands' member of the union. +func (u *TPMUCapabilities) AuditCommands() (*TPMLCC, error) { + if u.selector == TPMCapAuditCommands { + return u.contents.(*TPMLCC), nil + } + return nil, fmt.Errorf("did not contain auditCommands (selector value was %v)", u.selector) +} + +// AssignedPCR returns the 'assignedPCR' member of the union. +func (u *TPMUCapabilities) AssignedPCR() (*TPMLPCRSelection, error) { + if u.selector == TPMCapPCRs { + return u.contents.(*TPMLPCRSelection), nil + } + return nil, fmt.Errorf("did not contain assignedPCR (selector value was %v)", u.selector) +} + +// TPMProperties returns the 'tpmProperties' member of the union. +func (u *TPMUCapabilities) TPMProperties() (*TPMLTaggedTPMProperty, error) { + if u.selector == TPMCapTPMProperties { + return u.contents.(*TPMLTaggedTPMProperty), nil + } + return nil, fmt.Errorf("did not contain tpmProperties (selector value was %v)", u.selector) +} + +// PCRProperties returns the 'pcrProperties' member of the union. +func (u *TPMUCapabilities) PCRProperties() (*TPMLTaggedPCRProperty, error) { + if u.selector == TPMCapPCRProperties { + return u.contents.(*TPMLTaggedPCRProperty), nil + } + return nil, fmt.Errorf("did not contain pcrProperties (selector value was %v)", u.selector) +} + +// ECCCurves returns the 'eccCurves' member of the union. +func (u *TPMUCapabilities) ECCCurves() (*TPMLECCCurve, error) { + if u.selector == TPMCapECCCurves { + return u.contents.(*TPMLECCCurve), nil + } + return nil, fmt.Errorf("did not contain eccCurves (selector value was %v)", u.selector) +} + +// AuthPolicies returns the 'authPolicies' member of the union. +func (u *TPMUCapabilities) AuthPolicies() (*TPMLTaggedPolicy, error) { + if u.selector == TPMCapAuthPolicies { + return u.contents.(*TPMLTaggedPolicy), nil + } + return nil, fmt.Errorf("did not contain authPolicies (selector value was %v)", u.selector) +} + +// ACTData returns the 'actData' member of the union. +func (u *TPMUCapabilities) ACTData() (*TPMLACTData, error) { + if u.selector == TPMCapAuthPolicies { + return u.contents.(*TPMLACTData), nil + } + return nil, fmt.Errorf("did not contain actData (selector value was %v)", u.selector) +} + +// TPMSCapabilityData represents a TPMS_CAPABILITY_DATA. +// See definition in Part 2: Structures, section 10.10.2. +type TPMSCapabilityData struct { + marshalByReflection + // the capability + Capability TPMCap + // the capability data + Data TPMUCapabilities `gotpm:"tag=Capability"` +} + +// TPMSClockInfo represents a TPMS_CLOCK_INFO. +// See definition in Part 2: Structures, section 10.11.1. +type TPMSClockInfo struct { + marshalByReflection + // time value in milliseconds that advances while the TPM is powered + Clock uint64 + // number of occurrences of TPM Reset since the last TPM2_Clear() + ResetCount uint32 + // number of times that TPM2_Shutdown() or _TPM_Hash_Start have + // occurred since the last TPM Reset or TPM2_Clear(). + RestartCount uint32 + // no value of Clock greater than the current value of Clock has been + // previously reported by the TPM. Set to YES on TPM2_Clear(). + Safe TPMIYesNo +} + +// TPMSTimeInfo represents a TPMS_TIMEzINFO. +// See definition in Part 2: Structures, section 10.11.6. +type TPMSTimeInfo struct { + marshalByReflection + // time in milliseconds since the TIme circuit was last reset + Time uint64 + // a structure containing the clock information + ClockInfo TPMSClockInfo +} + +// TPMSTimeAttestInfo represents a TPMS_TIME_ATTEST_INFO. +// See definition in Part 2: Structures, section 10.12.2. +type TPMSTimeAttestInfo struct { + marshalByReflection + // the Time, Clock, resetCount, restartCount, and Safe indicator + Time TPMSTimeInfo + // a TPM vendor-specific value indicating the version number of the firmware + FirmwareVersion uint64 +} + +// TPMSCertifyInfo represents a TPMS_CERTIFY_INFO. +// See definition in Part 2: Structures, section 10.12.3. +type TPMSCertifyInfo struct { + marshalByReflection + // Name of the certified object + Name TPM2BName + // Qualified Name of the certified object + QualifiedName TPM2BName +} + +// TPMSQuoteInfo represents a TPMS_QUOTE_INFO. +// See definition in Part 2: Structures, section 10.12.4. +type TPMSQuoteInfo struct { + marshalByReflection + // information on algID, PCR selected and digest + PCRSelect TPMLPCRSelection + // digest of the selected PCR using the hash of the signing key + PCRDigest TPM2BDigest +} + +// TPMSCommandAuditInfo represents a TPMS_COMMAND_AUDIT_INFO. +// See definition in Part 2: Structures, section 10.12.5. +type TPMSCommandAuditInfo struct { + marshalByReflection + // the monotonic audit counter + AuditCounter uint64 + // hash algorithm used for the command audit + DigestAlg TPMAlgID + // the current value of the audit digest + AuditDigest TPM2BDigest + // digest of the command codes being audited using digestAlg + CommandDigest TPM2BDigest +} + +// TPMSSessionAuditInfo represents a TPMS_SESSION_AUDIT_INFO. +// See definition in Part 2: Structures, section 10.12.6. +type TPMSSessionAuditInfo struct { + marshalByReflection + // current exclusive status of the session + ExclusiveSession TPMIYesNo + // the current value of the session audit digest + SessionDigest TPM2BDigest +} + +// TPMSCreationInfo represents a TPMS_CREATION_INFO. +// See definition in Part 2: Structures, section 10.12.7. +type TPMSCreationInfo struct { + marshalByReflection + // Name of the object + ObjectName TPM2BName + // creationHash + CreationHash TPM2BDigest +} + +// TPMSNVCertifyInfo represents a TPMS_NV_CERTIFY_INFO. +// See definition in Part 2: Structures, section 10.12.8. +type TPMSNVCertifyInfo struct { + marshalByReflection + // Name of the NV Index + IndexName TPM2BName + // the offset parameter of TPM2_NV_Certify() + Offset uint16 + // contents of the NV Index + NVContents TPM2BData +} + +// TPMSNVDigestCertifyInfo represents a TPMS_NV_DIGEST_CERTIFY_INFO. +// See definition in Part 2: Structures, section 10.12.9. +type TPMSNVDigestCertifyInfo struct { + marshalByReflection + // Name of the NV Index + IndexName TPM2BName + // hash of the contents of the index + NVDigest TPM2BDigest +} + +// TPMISTAttest represents a TPMI_ST_ATTEST. +// See definition in Part 2: Structures, section 10.12.10. +type TPMISTAttest = TPMST + +// TPMUAttest represents a TPMU_ATTEST. +// See definition in Part 2: Structures, section 10.12.11. +type TPMUAttest struct { + selector TPMST + contents Marshallable +} + +// AttestContents is a type constraint representing the possible contents of TPMUAttest. +type AttestContents interface { + Marshallable + *TPMSNVCertifyInfo | *TPMSCommandAuditInfo | *TPMSSessionAuditInfo | *TPMSCertifyInfo | + *TPMSQuoteInfo | *TPMSTimeAttestInfo | *TPMSCreationInfo | *TPMSNVDigestCertifyInfo +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUAttest) create(hint int64) (reflect.Value, error) { + switch TPMST(hint) { + case TPMSTAttestNV: + contents := TPMSNVCertifyInfo{} + u.contents = &contents + u.selector = TPMST(hint) + return reflect.ValueOf(&contents), nil + case TPMSTAttestCommandAudit: + contents := TPMSCommandAuditInfo{} + u.contents = &contents + u.selector = TPMST(hint) + return reflect.ValueOf(&contents), nil + case TPMSTAttestSessionAudit: + contents := TPMSSessionAuditInfo{} + u.contents = &contents + u.selector = TPMST(hint) + return reflect.ValueOf(&contents), nil + case TPMSTAttestCertify: + contents := TPMSCertifyInfo{} + u.contents = &contents + u.selector = TPMST(hint) + return reflect.ValueOf(&contents), nil + case TPMSTAttestQuote: + contents := TPMSQuoteInfo{} + u.contents = &contents + u.selector = TPMST(hint) + return reflect.ValueOf(&contents), nil + case TPMSTAttestTime: + contents := TPMSTimeAttestInfo{} + u.contents = &contents + u.selector = TPMST(hint) + return reflect.ValueOf(&contents), nil + case TPMSTAttestCreation: + contents := TPMSCreationInfo{} + u.contents = &contents + u.selector = TPMST(hint) + return reflect.ValueOf(&contents), nil + case TPMSTAttestNVDigest: + contents := TPMSNVDigestCertifyInfo{} + u.contents = &contents + u.selector = TPMST(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUAttest) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMST(hint) { + case TPMSTAttestNV: + contents := TPMSNVCertifyInfo{} + if u.contents != nil { + contents = *u.contents.(*TPMSNVCertifyInfo) + } + return reflect.ValueOf(&contents), nil + case TPMSTAttestCommandAudit: + contents := TPMSCommandAuditInfo{} + if u.contents != nil { + contents = *u.contents.(*TPMSCommandAuditInfo) + } + return reflect.ValueOf(&contents), nil + case TPMSTAttestSessionAudit: + contents := TPMSSessionAuditInfo{} + if u.contents != nil { + contents = *u.contents.(*TPMSSessionAuditInfo) + } + return reflect.ValueOf(&contents), nil + case TPMSTAttestCertify: + contents := TPMSCertifyInfo{} + if u.contents != nil { + contents = *u.contents.(*TPMSCertifyInfo) + } + return reflect.ValueOf(&contents), nil + case TPMSTAttestQuote: + contents := TPMSQuoteInfo{} + if u.contents != nil { + contents = *u.contents.(*TPMSQuoteInfo) + } + return reflect.ValueOf(&contents), nil + case TPMSTAttestTime: + contents := TPMSTimeAttestInfo{} + if u.contents != nil { + contents = *u.contents.(*TPMSTimeAttestInfo) + } + return reflect.ValueOf(&contents), nil + case TPMSTAttestCreation: + contents := TPMSCreationInfo{} + if u.contents != nil { + contents = *u.contents.(*TPMSCreationInfo) + } + return reflect.ValueOf(&contents), nil + case TPMSTAttestNVDigest: + contents := TPMSNVDigestCertifyInfo{} + if u.contents != nil { + contents = *u.contents.(*TPMSNVDigestCertifyInfo) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUAttest instantiates a TPMUAttest with the given contents. +func NewTPMUAttest[C AttestContents](selector TPMST, contents C) TPMUAttest { + return TPMUAttest{ + selector: selector, + contents: contents, + } +} + +// Certify returns the 'certify' member of the union. +func (u *TPMUAttest) Certify() (*TPMSCertifyInfo, error) { + if u.selector == TPMSTAttestCertify { + return u.contents.(*TPMSCertifyInfo), nil + } + return nil, fmt.Errorf("did not contain certify (selector value was %v)", u.selector) +} + +// Creation returns the 'creation' member of the union. +func (u *TPMUAttest) Creation() (*TPMSCreationInfo, error) { + if u.selector == TPMSTAttestCreation { + return u.contents.(*TPMSCreationInfo), nil + } + return nil, fmt.Errorf("did not contain creation (selector value was %v)", u.selector) +} + +// Quote returns the 'quote' member of the union. +func (u *TPMUAttest) Quote() (*TPMSQuoteInfo, error) { + if u.selector == TPMSTAttestQuote { + return u.contents.(*TPMSQuoteInfo), nil + } + return nil, fmt.Errorf("did not contain quote (selector value was %v)", u.selector) +} + +// CommandAudit returns the 'commandAudit' member of the union. +func (u *TPMUAttest) CommandAudit() (*TPMSCommandAuditInfo, error) { + if u.selector == TPMSTAttestCommandAudit { + return u.contents.(*TPMSCommandAuditInfo), nil + } + return nil, fmt.Errorf("did not contain commandAudit (selector value was %v)", u.selector) +} + +// SessionAudit returns the 'sessionAudit' member of the union. +func (u *TPMUAttest) SessionAudit() (*TPMSSessionAuditInfo, error) { + if u.selector == TPMSTAttestSessionAudit { + return u.contents.(*TPMSSessionAuditInfo), nil + } + return nil, fmt.Errorf("did not contain sessionAudit (selector value was %v)", u.selector) +} + +// Time returns the 'time' member of the union. +func (u *TPMUAttest) Time() (*TPMSTimeAttestInfo, error) { + if u.selector == TPMSTAttestTime { + return u.contents.(*TPMSTimeAttestInfo), nil + } + return nil, fmt.Errorf("did not contain time (selector value was %v)", u.selector) +} + +// NV returns the 'nv' member of the union. +func (u *TPMUAttest) NV() (*TPMSNVCertifyInfo, error) { + if u.selector == TPMSTAttestNV { + return u.contents.(*TPMSNVCertifyInfo), nil + } + return nil, fmt.Errorf("did not contain nv (selector value was %v)", u.selector) +} + +// NVDigest returns the 'nvDigest' member of the union. +func (u *TPMUAttest) NVDigest() (*TPMSNVDigestCertifyInfo, error) { + if u.selector == TPMSTAttestNVDigest { + return u.contents.(*TPMSNVDigestCertifyInfo), nil + } + return nil, fmt.Errorf("did not contain nvDigest (selector value was %v)", u.selector) +} + +// TPMSAttest represents a TPMS_ATTEST. +// See definition in Part 2: Structures, section 10.12.12. +type TPMSAttest struct { + marshalByReflection + // the indication that this structure was created by a TPM (always TPM_GENERATED_VALUE) + Magic TPMGenerated `gotpm:"check"` + // type of the attestation structure + Type TPMISTAttest + // Qualified Name of the signing key + QualifiedSigner TPM2BName + // external information supplied by caller + ExtraData TPM2BData + // Clock, resetCount, restartCount, and Safe + ClockInfo TPMSClockInfo + // TPM-vendor-specific value identifying the version number of the firmware + FirmwareVersion uint64 + // the type-specific attestation information + Attested TPMUAttest `gotpm:"tag=Type"` +} + +// TPM2BAttest represents a TPM2B_ATTEST. +// See definition in Part 2: Structures, section 10.12.13. +type TPM2BAttest = TPM2B[TPMSAttest, *TPMSAttest] + +// TPMSAuthCommand represents a TPMS_AUTH_COMMAND. +// See definition in Part 2: Structures, section 10.13.2. +type TPMSAuthCommand struct { + marshalByReflection + Handle TPMISHAuthSession + Nonce TPM2BNonce + Attributes TPMASession + Authorization TPM2BData +} + +// TPMSAuthResponse represents a TPMS_AUTH_RESPONSE. +// See definition in Part 2: Structures, section 10.13.3. +type TPMSAuthResponse struct { + marshalByReflection + Nonce TPM2BNonce + Attributes TPMASession + Authorization TPM2BData +} + +// TPMUSymKeyBits represents a TPMU_SYM_KEY_BITS. +// See definition in Part 2: Structures, section 11.1.3. +type TPMUSymKeyBits struct { + selector TPMAlgID + contents Marshallable +} + +// SymKeyBitsContents is a type constraint representing the possible contents of TPMUSymKeyBits. +type SymKeyBitsContents interface { + TPMKeyBits | TPMAlgID +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUSymKeyBits) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia: + var contents boxed[TPMKeyBits] + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgXOR: + var contents boxed[TPMAlgID] + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUSymKeyBits) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia: + var contents boxed[TPMKeyBits] + if u.contents != nil { + contents = *u.contents.(*boxed[TPMKeyBits]) + } + return reflect.ValueOf(&contents), nil + case TPMAlgXOR: + var contents boxed[TPMAlgID] + if u.contents != nil { + contents = *u.contents.(*boxed[TPMAlgID]) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUSymKeyBits instantiates a TPMUSymKeyBits with the given contents. +func NewTPMUSymKeyBits[C SymKeyBitsContents](selector TPMAlgID, contents C) TPMUSymKeyBits { + boxed := box(&contents) + return TPMUSymKeyBits{ + selector: selector, + contents: &boxed, + } +} + +// Sym returns the 'sym' member of the union. +func (u *TPMUSymKeyBits) Sym() (*TPMKeyBits, error) { + + switch u.selector { + case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia: + value := u.contents.(*boxed[TPMKeyBits]).unbox() + return value, nil + default: + return nil, fmt.Errorf("did not contain sym (selector value was %v)", u.selector) + } +} + +// AES returns the 'aes' member of the union. +func (u *TPMUSymKeyBits) AES() (*TPMKeyBits, error) { + if u.selector == TPMAlgAES { + value := u.contents.(*boxed[TPMKeyBits]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain aes (selector value was %v)", u.selector) +} + +// TDES returns the 'tdes' member of the union. +// +// Deprecated: TDES exists for historical compatibility +// and is not recommended anymore. +// https://csrc.nist.gov/news/2023/nist-to-withdraw-sp-800-67-rev-2 +func (u *TPMUSymKeyBits) TDES() (*TPMKeyBits, error) { + if u.selector == TPMAlgTDES { + value := u.contents.(*boxed[TPMKeyBits]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain tdes (selector value was %v)", u.selector) +} + +// SM4 returns the 'sm4' member of the union. +func (u *TPMUSymKeyBits) SM4() (*TPMKeyBits, error) { + if u.selector == TPMAlgSM4 { + value := u.contents.(*boxed[TPMKeyBits]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain sm4 (selector value was %v)", u.selector) +} + +// Camellia returns the 'camellia' member of the union. +func (u *TPMUSymKeyBits) Camellia() (*TPMKeyBits, error) { + if u.selector == TPMAlgCamellia { + value := u.contents.(*boxed[TPMKeyBits]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain camellia (selector value was %v)", u.selector) +} + +// XOR returns the 'xor' member of the union. +func (u *TPMUSymKeyBits) XOR() (*TPMAlgID, error) { + if u.selector == TPMAlgXOR { + value := u.contents.(*boxed[TPMAlgID]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain xor (selector value was %v)", u.selector) +} + +// TPMUSymMode represents a TPMU_SYM_MODE. +// See definition in Part 2: Structures, section 11.1.4. +type TPMUSymMode struct { + selector TPMAlgID + contents Marshallable +} + +// SymModeContents is a type constraint representing the possible contents of TPMUSymMode. +type SymModeContents interface { + TPMIAlgSymMode | TPMSEmpty +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUSymMode) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia: + var contents boxed[TPMAlgID] + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgXOR: + var contents boxed[TPMSEmpty] + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUSymMode) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia: + var contents boxed[TPMAlgID] + if u.contents != nil { + contents = *u.contents.(*boxed[TPMAlgID]) + } + return reflect.ValueOf(&contents), nil + case TPMAlgXOR: + var contents boxed[TPMSEmpty] + if u.contents != nil { + contents = *u.contents.(*boxed[TPMSEmpty]) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUSymMode instantiates a TPMUSymMode with the given contents. +func NewTPMUSymMode[C SymModeContents](selector TPMAlgID, contents C) TPMUSymMode { + boxed := box(&contents) + return TPMUSymMode{ + selector: selector, + contents: &boxed, + } +} + +// Sym returns the 'sym' member of the union. +func (u *TPMUSymMode) Sym() (*TPMIAlgSymMode, error) { + switch u.selector { + case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia: + value := u.contents.(*boxed[TPMIAlgSymMode]).unbox() + return value, nil + default: + return nil, fmt.Errorf("did not contain sym (selector value was %v)", u.selector) + } +} + +// AES returns the 'aes' member of the union. +func (u *TPMUSymMode) AES() (*TPMIAlgSymMode, error) { + if u.selector == TPMAlgAES { + value := u.contents.(*boxed[TPMIAlgSymMode]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain aes (selector value was %v)", u.selector) +} + +// TDES returns the 'tdes' member of the union. +// +// Deprecated: TDES exists for historical compatibility +// and is not recommended anymore. +// https://csrc.nist.gov/news/2023/nist-to-withdraw-sp-800-67-rev-2 +func (u *TPMUSymMode) TDES() (*TPMIAlgSymMode, error) { + if u.selector == TPMAlgTDES { + value := u.contents.(*boxed[TPMIAlgSymMode]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain tdes (selector value was %v)", u.selector) +} + +// SM4 returns the 'sm4' member of the union. +func (u *TPMUSymMode) SM4() (*TPMIAlgSymMode, error) { + if u.selector == TPMAlgSM4 { + value := u.contents.(*boxed[TPMIAlgSymMode]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain sm4 (selector value was %v)", u.selector) +} + +// Camellia returns the 'camellia' member of the union. +func (u *TPMUSymMode) Camellia() (*TPMIAlgSymMode, error) { + if u.selector == TPMAlgCamellia { + value := u.contents.(*boxed[TPMIAlgSymMode]).unbox() + return value, nil + } + return nil, fmt.Errorf("did not contain camellia (selector value was %v)", u.selector) +} + +// TPMUSymDetails represents a TPMU_SYM_DETAILS. +// See definition in Part 2: Structures, section 11.1.5. +type TPMUSymDetails struct { + selector TPMAlgID + contents Marshallable +} + +// SymDetailsContents is a type constraint representing the possible contents of TPMUSymDetails. +type SymDetailsContents interface { + TPMSEmpty +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUSymDetails) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgAES: + var contents boxed[TPMSEmpty] + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgXOR: + var contents boxed[TPMSEmpty] + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUSymDetails) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgAES, TPMAlgXOR: + var contents boxed[TPMSEmpty] + if u.contents != nil { + contents = *u.contents.(*boxed[TPMSEmpty]) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUSymDetails instantiates a TPMUSymDetails with the given contents. +func NewTPMUSymDetails[C SymDetailsContents](selector TPMAlgID, contents C) TPMUSymMode { + boxed := box(&contents) + return TPMUSymMode{ + selector: selector, + contents: &boxed, + } +} + +// TPMTSymDef represents a TPMT_SYM_DEF. +// See definition in Part 2: Structures, section 11.1.6. +type TPMTSymDef struct { + marshalByReflection + // indicates a symmetric algorithm + Algorithm TPMIAlgSym `gotpm:"nullable"` + // the key size + KeyBits TPMUSymKeyBits `gotpm:"tag=Algorithm"` + // the mode for the key + Mode TPMUSymMode `gotpm:"tag=Algorithm"` + // contains the additional algorithm details + Details TPMUSymDetails `gotpm:"tag=Algorithm"` +} + +// TPMTSymDefObject represents a TPMT_SYM_DEF_OBJECT. +// See definition in Part 2: Structures, section 11.1.7. +type TPMTSymDefObject struct { + marshalByReflection + // selects a symmetric block cipher + // When used in the parameter area of a parent object, this shall + // be a supported block cipher and not TPM_ALG_NULL + Algorithm TPMIAlgSymObject `gotpm:"nullable"` + // the key size + KeyBits TPMUSymKeyBits `gotpm:"tag=Algorithm"` + // default mode + // When used in the parameter area of a parent object, this shall + // be TPM_ALG_CFB. + Mode TPMUSymMode `gotpm:"tag=Algorithm"` + // contains the additional algorithm details, if any + Details TPMUSymDetails `gotpm:"tag=Algorithm"` +} + +// TPM2BSymKey represents a TPM2B_SYM_KEY. +// See definition in Part 2: Structures, section 11.1.8. +type TPM2BSymKey TPM2BData + +// TPMSSymCipherParms represents a TPMS_SYMCIPHER_PARMS. +// See definition in Part 2: Structures, section 11.1.9. +type TPMSSymCipherParms struct { + marshalByReflection + // a symmetric block cipher + Sym TPMTSymDefObject +} + +// TPM2BLabel represents a TPM2B_LABEL. +// See definition in Part 2: Structures, section 11.1.10. +type TPM2BLabel TPM2BData + +// TPMSDerive represents a TPMS_DERIVE. +// See definition in Part 2: Structures, section 11.1.11. +type TPMSDerive struct { + marshalByReflection + Label TPM2BLabel + Context TPM2BLabel +} + +// TPM2BDerive represents a TPM2B_DERIVE. +// See definition in Part 2: Structures, section 11.1.12. +type TPM2BDerive = TPM2B[TPMSDerive, *TPMSDerive] + +// TPMUSensitiveCreate represents a TPMU_SENSITIVE_CREATE. +// See definition in Part 2: Structures, section 11.1.13. +type TPMUSensitiveCreate struct { + contents Marshallable +} + +// SensitiveCreateContents is a type constraint representing the possible contents of TPMUSensitiveCreate. +type SensitiveCreateContents interface { + Marshallable + *TPM2BDerive | *TPM2BSensitiveData +} + +// marshal implements the Marshallable interface. +func (u TPMUSensitiveCreate) marshal(buf *bytes.Buffer) { + if u.contents != nil { + buf.Write(Marshal(u.contents)) + } else { + // If this is a zero-valued structure, marshal a default TPM2BSensitiveData. + var defaultValue TPM2BSensitiveData + buf.Write(Marshal(&defaultValue)) + } +} + +// NewTPMUSensitiveCreate instantiates a TPMUSensitiveCreate with the given contents. +func NewTPMUSensitiveCreate[C SensitiveCreateContents](contents C) TPMUSensitiveCreate { + return TPMUSensitiveCreate{contents: contents} +} + +// TPM2BSensitiveData represents a TPM2B_SENSITIVE_DATA. +// See definition in Part 2: Structures, section 11.1.14. +type TPM2BSensitiveData TPM2BData + +// TPMSSensitiveCreate represents a TPMS_SENSITIVE_CREATE. +// See definition in Part 2: Structures, section 11.1.15. +type TPMSSensitiveCreate struct { + marshalByReflection + // the USER auth secret value. + UserAuth TPM2BAuth + // data to be sealed, a key, or derivation values. + Data TPMUSensitiveCreate +} + +// TPM2BSensitiveCreate represents a TPM2B_SENSITIVE_CREATE. +// See definition in Part 2: Structures, section 11.1.16. +// This is a structure instead of an alias to TPM2B[TPMSSensitiveCreate], +// because it has custom marshalling logic for zero-valued parameters. +type TPM2BSensitiveCreate struct { + Sensitive *TPMSSensitiveCreate +} + +// Quirk: When this structure is zero-valued, we need to marshal +// a 2B-wrapped zero-valued TPMS_SENSITIVE_CREATE instead of +// [0x00, 0x00] (a zero-valued 2B). +func (c TPM2BSensitiveCreate) marshal(buf *bytes.Buffer) { + var marshalled TPM2B[TPMSSensitiveCreate, *TPMSSensitiveCreate] + if c.Sensitive != nil { + marshalled = New2B(*c.Sensitive) + } else { + // If no value was provided (i.e., this is a zero-valued structure), + // provide an 2B containing a zero-valued TPMS_SensitiveCreate. + marshalled = New2B(TPMSSensitiveCreate{ + Data: NewTPMUSensitiveCreate(&TPM2BSensitiveData{}), + }) + } + marshalled.marshal(buf) +} + +// TPMSSchemeHash represents a TPMS_SCHEME_HASH. +// See definition in Part 2: Structures, section 11.1.17. +type TPMSSchemeHash struct { + marshalByReflection + // the hash algorithm used to digest the message + HashAlg TPMIAlgHash +} + +// TPMSSchemeECDAA represents a TPMS_SCHEME_ECDAA. +// See definition in Part 2: Structures, section 11.1.18. +type TPMSSchemeECDAA struct { + marshalByReflection + // the hash algorithm used to digest the message + HashAlg TPMIAlgHash + // the counter value that is used between TPM2_Commit() + // and the sign operation + Count uint16 +} + +// TPMIAlgKeyedHashScheme represents a TPMI_ALG_KEYEDHASH_SCHEME. +// See definition in Part 2: Structures, section 11.1.19. +type TPMIAlgKeyedHashScheme = TPMAlgID + +// TPMSSchemeHMAC represents a TPMS_SCHEME_HMAC. +// See definition in Part 2: Structures, section 11.1.20. +type TPMSSchemeHMAC TPMSSchemeHash + +// TPMSSchemeXOR represents a TPMS_SCHEME_XOR. +// See definition in Part 2: Structures, section 11.1.21. +type TPMSSchemeXOR struct { + marshalByReflection + // the hash algorithm used to digest the message + HashAlg TPMIAlgHash + // the key derivation function + KDF TPMIAlgKDF +} + +// TPMUSchemeKeyedHash represents a TPMU_SCHEME_KEYEDHASH. +// See definition in Part 2: Structures, section 11.1.22. +type TPMUSchemeKeyedHash struct { + selector TPMAlgID + contents Marshallable +} + +// SchemeKeyedHashContents is a type constraint representing the possible contents of TPMUSchemeKeyedHash. +type SchemeKeyedHashContents interface { + Marshallable + *TPMSSchemeHMAC | *TPMSSchemeXOR +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUSchemeKeyedHash) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgHMAC: + var contents TPMSSchemeHMAC + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgXOR: + var contents TPMSSchemeXOR + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUSchemeKeyedHash) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgHMAC: + var contents TPMSSchemeHMAC + if u.contents != nil { + contents = *u.contents.(*TPMSSchemeHMAC) + } + return reflect.ValueOf(&contents), nil + case TPMAlgXOR: + var contents TPMSSchemeXOR + if u.contents != nil { + contents = *u.contents.(*TPMSSchemeXOR) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUSchemeKeyedHash instantiates a TPMUSchemeKeyedHash with the given contents. +func NewTPMUSchemeKeyedHash[C SchemeKeyedHashContents](selector TPMAlgID, contents C) TPMUSchemeKeyedHash { + return TPMUSchemeKeyedHash{ + selector: selector, + contents: contents, + } +} + +// HMAC returns the 'hmac' member of the union. +func (u *TPMUSchemeKeyedHash) HMAC() (*TPMSSchemeHMAC, error) { + if u.selector == TPMAlgHMAC { + value := u.contents.(*TPMSSchemeHMAC) + return value, nil + } + return nil, fmt.Errorf("did not contain hmac (selector value was %v)", u.selector) +} + +// XOR returns the 'xor' member of the union. +func (u *TPMUSchemeKeyedHash) XOR() (*TPMSSchemeXOR, error) { + if u.selector == TPMAlgXOR { + value := u.contents.(*TPMSSchemeXOR) + return value, nil + } + return nil, fmt.Errorf("did not contain xor (selector value was %v)", u.selector) +} + +// TPMTKeyedHashScheme represents a TPMT_KEYEDHASH_SCHEME. +// See definition in Part 2: Structures, section 11.1.23. +type TPMTKeyedHashScheme struct { + marshalByReflection + Scheme TPMIAlgKeyedHashScheme `gotpm:"nullable"` + Details TPMUSchemeKeyedHash `gotpm:"tag=Scheme"` +} + +// TPMSSigSchemeRSASSA represents a TPMS_SIG_SCHEME_RSASSA. +// See definition in Part 2: Structures, section 11.2.1.2. +type TPMSSigSchemeRSASSA TPMSSchemeHash + +// TPMSSigSchemeRSAPSS represents a TPMS_SIG_SCHEME_RSAPSS. +// See definition in Part 2: Structures, section 11.2.1.2. +type TPMSSigSchemeRSAPSS TPMSSchemeHash + +// TPMSSigSchemeECDSA represents a TPMS_SIG_SCHEME_ECDSA. +// See definition in Part 2: Structures, section 11.2.1.3. +type TPMSSigSchemeECDSA TPMSSchemeHash + +// TPMUSigScheme represents a TPMU_SIG_SCHEME. +// See definition in Part 2: Structures, section 11.2.1.4. +type TPMUSigScheme struct { + selector TPMAlgID + contents Marshallable +} + +// SigSchemeContents is a type constraint representing the possible contents of TPMUSigScheme. +type SigSchemeContents interface { + Marshallable + *TPMSSchemeHMAC | *TPMSSchemeHash | *TPMSSchemeECDAA +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUSigScheme) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgHMAC: + var contents TPMSSchemeHMAC + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgRSASSA, TPMAlgRSAPSS, TPMAlgECDSA: + var contents TPMSSchemeHash + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECDAA: + var contents TPMSSchemeECDAA + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUSigScheme) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgHMAC: + var contents TPMSSchemeHMAC + if u.contents != nil { + contents = *u.contents.(*TPMSSchemeHMAC) + } + return reflect.ValueOf(&contents), nil + case TPMAlgRSASSA, TPMAlgRSAPSS, TPMAlgECDSA: + var contents TPMSSchemeHash + if u.contents != nil { + contents = *u.contents.(*TPMSSchemeHash) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECDAA: + var contents TPMSSchemeECDAA + if u.contents != nil { + contents = *u.contents.(*TPMSSchemeECDAA) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUSigScheme instantiates a TPMUSigScheme with the given contents. +func NewTPMUSigScheme[C SigSchemeContents](selector TPMAlgID, contents C) TPMUSigScheme { + return TPMUSigScheme{ + selector: selector, + contents: contents, + } +} + +// HMAC returns the 'hmac' member of the union. +func (u *TPMUSigScheme) HMAC() (*TPMSSchemeHMAC, error) { + if u.selector == TPMAlgHMAC { + return u.contents.(*TPMSSchemeHMAC), nil + } + return nil, fmt.Errorf("did not contain hmac (selector value was %v)", u.selector) +} + +// RSASSA returns the 'rsassa' member of the union. +func (u *TPMUSigScheme) RSASSA() (*TPMSSchemeHash, error) { + if u.selector == TPMAlgRSASSA { + return u.contents.(*TPMSSchemeHash), nil + } + return nil, fmt.Errorf("did not contain rsassa (selector value was %v)", u.selector) +} + +// RSAPSS returns the 'rsapss' member of the union. +func (u *TPMUSigScheme) RSAPSS() (*TPMSSchemeHash, error) { + if u.selector == TPMAlgRSAPSS { + return u.contents.(*TPMSSchemeHash), nil + } + return nil, fmt.Errorf("did not contain rsapss (selector value was %v)", u.selector) +} + +// ECDSA returns the 'ecdsa' member of the union. +func (u *TPMUSigScheme) ECDSA() (*TPMSSchemeHash, error) { + if u.selector == TPMAlgECDSA { + return u.contents.(*TPMSSchemeHash), nil + } + return nil, fmt.Errorf("did not contain ecdsa (selector value was %v)", u.selector) +} + +// ECDAA returns the 'ecdaa' member of the union. +func (u *TPMUSigScheme) ECDAA() (*TPMSSchemeECDAA, error) { + if u.selector == TPMAlgECDAA { + return u.contents.(*TPMSSchemeECDAA), nil + } + return nil, fmt.Errorf("did not contain ecdaa (selector value was %v)", u.selector) +} + +// TPMTSigScheme represents a TPMT_SIG_SCHEME. +// See definition in Part 2: Structures, section 11.2.1.5. +type TPMTSigScheme struct { + marshalByReflection + Scheme TPMIAlgSigScheme `gotpm:"nullable"` + Details TPMUSigScheme `gotpm:"tag=Scheme"` +} + +// TPMSEncSchemeRSAES represents a TPMS_ENC_SCHEME_RSAES. +// See definition in Part 2: Structures, section 11.2.2.2. +type TPMSEncSchemeRSAES TPMSEmpty + +// TPMSEncSchemeOAEP represents a TPMS_ENC_SCHEME_OAEP. +// See definition in Part 2: Structures, section 11.2.2.2. +type TPMSEncSchemeOAEP TPMSSchemeHash + +// TPMSKeySchemeECDH represents a TPMS_KEY_SCHEME_ECDH. +// See definition in Part 2: Structures, section 11.2.2.3. +type TPMSKeySchemeECDH TPMSSchemeHash + +// TPMSKeySchemeECMQV represents a TPMS_KEY_SCHEME_ECMQV. +// See definition in Part 2: Structures, section 11.2.2.3. +type TPMSKeySchemeECMQV TPMSSchemeHash + +// TPMSKDFSchemeMGF1 represents a TPMS_KDF_SCHEME_MGF1. +// See definition in Part 2: Structures, section 11.2.3.1. +type TPMSKDFSchemeMGF1 TPMSSchemeHash + +// TPMSKDFSchemeECDH represents a TPMS_KDF_SCHEME_ECDH. +// See definition in Part 2: Structures, section 11.2.3.1. +type TPMSKDFSchemeECDH TPMSSchemeHash + +// TPMSKDFSchemeKDF1SP80056A represents a TPMS_KDF_SCHEME_KDF1SP80056A. +// See definition in Part 2: Structures, section 11.2.3.1. +type TPMSKDFSchemeKDF1SP80056A TPMSSchemeHash + +// TPMSKDFSchemeKDF2 represents a TPMS_KDF_SCHEME_KDF2. +// See definition in Part 2: Structures, section 11.2.3.1. +type TPMSKDFSchemeKDF2 TPMSSchemeHash + +// TPMSKDFSchemeKDF1SP800108 represents a TPMS_KDF_SCHEME_KDF1SP800108. +// See definition in Part 2: Structures, section 11.2.3.1. +type TPMSKDFSchemeKDF1SP800108 TPMSSchemeHash + +// TPMUKDFScheme represents a TPMU_KDF_SCHEME. +// See definition in Part 2: Structures, section 11.2.3.2. +type TPMUKDFScheme struct { + selector TPMAlgID + contents Marshallable +} + +// KDFSchemeContents is a type constraint representing the possible contents of TPMUKDFScheme. +type KDFSchemeContents interface { + Marshallable + *TPMSKDFSchemeMGF1 | *TPMSKDFSchemeECDH | *TPMSKDFSchemeKDF1SP80056A | + *TPMSKDFSchemeKDF2 | *TPMSKDFSchemeKDF1SP800108 +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUKDFScheme) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgMGF1: + var contents TPMSKDFSchemeMGF1 + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECDH: + var contents TPMSKDFSchemeECDH + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgKDF1SP80056A: + var contents TPMSKDFSchemeKDF1SP80056A + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgKDF2: + var contents TPMSKDFSchemeKDF2 + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgKDF1SP800108: + var contents TPMSKDFSchemeKDF1SP800108 + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUKDFScheme) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgMGF1: + var contents TPMSKDFSchemeMGF1 + if u.contents != nil { + contents = *u.contents.(*TPMSKDFSchemeMGF1) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECDH: + var contents TPMSKDFSchemeECDH + if u.contents != nil { + contents = *u.contents.(*TPMSKDFSchemeECDH) + } + return reflect.ValueOf(&contents), nil + case TPMAlgKDF1SP80056A: + var contents TPMSKDFSchemeKDF1SP80056A + if u.contents != nil { + contents = *u.contents.(*TPMSKDFSchemeKDF1SP80056A) + } + return reflect.ValueOf(&contents), nil + case TPMAlgKDF2: + var contents TPMSKDFSchemeKDF2 + if u.contents != nil { + contents = *u.contents.(*TPMSKDFSchemeKDF2) + } + return reflect.ValueOf(&contents), nil + + case TPMAlgKDF1SP800108: + var contents TPMSKDFSchemeKDF1SP800108 + if u.contents != nil { + contents = *u.contents.(*TPMSKDFSchemeKDF1SP800108) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUKDFScheme instantiates a TPMUKDFScheme with the given contents. +func NewTPMUKDFScheme[C KDFSchemeContents](selector TPMAlgID, contents C) TPMUKDFScheme { + return TPMUKDFScheme{ + selector: selector, + contents: contents, + } +} + +// MGF1 returns the 'mgf1' member of the union. +func (u *TPMUKDFScheme) MGF1() (*TPMSKDFSchemeMGF1, error) { + if u.selector == TPMAlgMGF1 { + return u.contents.(*TPMSKDFSchemeMGF1), nil + } + return nil, fmt.Errorf("did not contain mgf1 (selector value was %v)", u.selector) +} + +// ECDH returns the 'ecdh' member of the union. +func (u *TPMUKDFScheme) ECDH() (*TPMSKDFSchemeECDH, error) { + if u.selector == TPMAlgECDH { + return u.contents.(*TPMSKDFSchemeECDH), nil + } + return nil, fmt.Errorf("did not contain ecdh (selector value was %v)", u.selector) +} + +// KDF1SP80056A returns the 'kdf1sp80056a' member of the union. +func (u *TPMUKDFScheme) KDF1SP80056A() (*TPMSKDFSchemeKDF1SP80056A, error) { + if u.selector == TPMAlgMGF1 { + return u.contents.(*TPMSKDFSchemeKDF1SP80056A), nil + } + return nil, fmt.Errorf("did not contain kdf1sp80056a (selector value was %v)", u.selector) +} + +// KDF2 returns the 'kdf2' member of the union. +func (u *TPMUKDFScheme) KDF2() (*TPMSKDFSchemeKDF2, error) { + if u.selector == TPMAlgMGF1 { + return u.contents.(*TPMSKDFSchemeKDF2), nil + } + return nil, fmt.Errorf("did not contain mgf1 (selector value was %v)", u.selector) +} + +// KDF1SP800108 returns the 'kdf1sp800108' member of the union. +func (u *TPMUKDFScheme) KDF1SP800108() (*TPMSKDFSchemeKDF1SP800108, error) { + if u.selector == TPMAlgMGF1 { + return u.contents.(*TPMSKDFSchemeKDF1SP800108), nil + } + return nil, fmt.Errorf("did not contain kdf1sp800108 (selector value was %v)", u.selector) +} + +// TPMTKDFScheme represents a TPMT_KDF_SCHEME. +// See definition in Part 2: Structures, section 11.2.3.3. +type TPMTKDFScheme struct { + marshalByReflection + // scheme selector + Scheme TPMIAlgKDF `gotpm:"nullable"` + // scheme parameters + Details TPMUKDFScheme `gotpm:"tag=Scheme"` +} + +// TPMUAsymScheme represents a TPMU_ASYM_SCHEME. +// See definition in Part 2: Structures, section 11.2.3.5. +type TPMUAsymScheme struct { + selector TPMAlgID + contents Marshallable +} + +// AsymSchemeContents is a type constraint representing the possible contents of TPMUAsymScheme. +type AsymSchemeContents interface { + Marshallable + *TPMSSigSchemeRSASSA | *TPMSEncSchemeRSAES | *TPMSSigSchemeRSAPSS | *TPMSEncSchemeOAEP | + *TPMSSigSchemeECDSA | *TPMSKeySchemeECDH | *TPMSKeySchemeECMQV | *TPMSSchemeECDAA +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUAsymScheme) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgRSASSA: + var contents TPMSSigSchemeRSASSA + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgRSAES: + var contents TPMSEncSchemeRSAES + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgRSAPSS: + var contents TPMSSigSchemeRSAPSS + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgOAEP: + var contents TPMSEncSchemeOAEP + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECDSA: + var contents TPMSSigSchemeECDSA + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECDH: + var contents TPMSKeySchemeECDH + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECMQV: + var contents TPMSKeySchemeECMQV + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECDAA: + var contents TPMSSchemeECDAA + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUAsymScheme) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgRSASSA: + var contents TPMSSigSchemeRSASSA + if u.contents != nil { + contents = *u.contents.(*TPMSSigSchemeRSASSA) + } + return reflect.ValueOf(&contents), nil + case TPMAlgRSAES: + var contents TPMSEncSchemeRSAES + if u.contents != nil { + contents = *u.contents.(*TPMSEncSchemeRSAES) + } + return reflect.ValueOf(&contents), nil + case TPMAlgRSAPSS: + var contents TPMSSigSchemeRSAPSS + if u.contents != nil { + contents = *u.contents.(*TPMSSigSchemeRSAPSS) + } + return reflect.ValueOf(&contents), nil + case TPMAlgOAEP: + var contents TPMSEncSchemeOAEP + if u.contents != nil { + contents = *u.contents.(*TPMSEncSchemeOAEP) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECDSA: + var contents TPMSSigSchemeECDSA + if u.contents != nil { + contents = *u.contents.(*TPMSSigSchemeECDSA) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECDH: + var contents TPMSKeySchemeECDH + if u.contents != nil { + contents = *u.contents.(*TPMSKeySchemeECDH) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECMQV: + var contents TPMSKeySchemeECMQV + if u.contents != nil { + contents = *u.contents.(*TPMSKeySchemeECMQV) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECDAA: + var contents TPMSSchemeECDAA + if u.contents != nil { + contents = *u.contents.(*TPMSSchemeECDAA) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUAsymScheme instantiates a TPMUAsymScheme with the given contents. +func NewTPMUAsymScheme[C AsymSchemeContents](selector TPMAlgID, contents C) TPMUAsymScheme { + return TPMUAsymScheme{ + selector: selector, + contents: contents, + } +} + +// RSASSA returns the 'rsassa' member of the union. +func (u *TPMUAsymScheme) RSASSA() (*TPMSSigSchemeRSASSA, error) { + if u.selector == TPMAlgRSASSA { + return u.contents.(*TPMSSigSchemeRSASSA), nil + } + return nil, fmt.Errorf("did not contain rsassa (selector value was %v)", u.selector) +} + +// RSAES returns the 'rsaes' member of the union. +func (u *TPMUAsymScheme) RSAES() (*TPMSEncSchemeRSAES, error) { + if u.selector == TPMAlgRSAES { + return u.contents.(*TPMSEncSchemeRSAES), nil + } + return nil, fmt.Errorf("did not contain rsaes (selector value was %v)", u.selector) +} + +// RSAPSS returns the 'rsapss' member of the union. +func (u *TPMUAsymScheme) RSAPSS() (*TPMSSigSchemeRSAPSS, error) { + if u.selector == TPMAlgRSAPSS { + return u.contents.(*TPMSSigSchemeRSAPSS), nil + } + return nil, fmt.Errorf("did not contain rsapss (selector value was %v)", u.selector) +} + +// OAEP returns the 'oaep' member of the union. +func (u *TPMUAsymScheme) OAEP() (*TPMSEncSchemeOAEP, error) { + if u.selector == TPMAlgOAEP { + return u.contents.(*TPMSEncSchemeOAEP), nil + } + return nil, fmt.Errorf("did not contain oaep (selector value was %v)", u.selector) +} + +// ECDSA returns the 'ecdsa' member of the union. +func (u *TPMUAsymScheme) ECDSA() (*TPMSSigSchemeECDSA, error) { + if u.selector == TPMAlgECDSA { + return u.contents.(*TPMSSigSchemeECDSA), nil + } + return nil, fmt.Errorf("did not contain rsassa (selector value was %v)", u.selector) +} + +// ECDH returns the 'ecdh' member of the union. +func (u *TPMUAsymScheme) ECDH() (*TPMSKeySchemeECDH, error) { + if u.selector == TPMAlgRSASSA { + return u.contents.(*TPMSKeySchemeECDH), nil + } + return nil, fmt.Errorf("did not contain ecdh (selector value was %v)", u.selector) +} + +// ECDAA returns the 'ecdaa' member of the union. +func (u *TPMUAsymScheme) ECDAA() (*TPMSSchemeECDAA, error) { + if u.selector == TPMAlgECDAA { + return u.contents.(*TPMSSchemeECDAA), nil + } + return nil, fmt.Errorf("did not contain rsassa (selector value was %v)", u.selector) +} + +// TPMIAlgRSAScheme represents a TPMI_ALG_RSA_SCHEME. +// See definition in Part 2: Structures, section 11.2.4.1. +type TPMIAlgRSAScheme = TPMAlgID + +// TPMTRSAScheme represents a TPMT_RSA_SCHEME. +// See definition in Part 2: Structures, section 11.2.4.2. +type TPMTRSAScheme struct { + marshalByReflection + // scheme selector + Scheme TPMIAlgRSAScheme `gotpm:"nullable"` + // scheme parameters + Details TPMUAsymScheme `gotpm:"tag=Scheme"` +} + +// TPMIAlgRSADecrypt represents a TPMI_ALG_RSA_DECRYPT. +// See definition in Part 2: Structures, section 11.2.4.3. +type TPMIAlgRSADecrypt = TPMAlgID + +// TPMTRSADecrypt represents a TPMT_RSA_DECRYPT. +// See definition in Part 2: Structures, section 11.2.4.4. +type TPMTRSADecrypt struct { + marshalByReflection + // scheme selector + Scheme TPMIAlgRSADecrypt `gotpm:"nullable"` + // scheme parameters + Details TPMUAsymScheme `gotpm:"tag=Scheme"` +} + +// TPM2BPublicKeyRSA represents a TPM2B_PUBLIC_KEY_RSA. +// See definition in Part 2: Structures, section 11.2.4.5. +type TPM2BPublicKeyRSA TPM2BData + +// TPMIRSAKeyBits represents a TPMI_RSA_KEY_BITS. +// See definition in Part 2: Structures, section 11.2.4.6. +type TPMIRSAKeyBits = TPMKeyBits + +// TPM2BPrivateKeyRSA representsa a TPM2B_PRIVATE_KEY_RSA. +// See definition in Part 2: Structures, section 11.2.4.7. +type TPM2BPrivateKeyRSA TPM2BData + +// TPM2BECCParameter represents a TPM2B_ECC_PARAMETER. +// See definition in Part 2: Structures, section 11.2.5.1. +type TPM2BECCParameter TPM2BData + +// TPMSECCPoint represents a TPMS_ECC_POINT. +// See definition in Part 2: Structures, section 11.2.5.2. +type TPMSECCPoint struct { + marshalByReflection + // X coordinate + X TPM2BECCParameter + // Y coordinate + Y TPM2BECCParameter +} + +// TPM2BECCPoint represents a TPM2B_ECC_POINT. +// See definition in Part 2: Structures, section 11.2.5.3. +type TPM2BECCPoint = TPM2B[TPMSECCPoint, *TPMSECCPoint] + +// TPMIAlgECCScheme represents a TPMI_ALG_ECC_SCHEME. +// See definition in Part 2: Structures, section 11.2.5.4. +type TPMIAlgECCScheme = TPMAlgID + +// TPMIECCCurve represents a TPMI_ECC_CURVE. +// See definition in Part 2: Structures, section 11.2.5.5. +type TPMIECCCurve = TPMECCCurve + +// TPMTECCScheme represents a TPMT_ECC_SCHEME. +// See definition in Part 2: Structures, section 11.2.5.6. +type TPMTECCScheme struct { + marshalByReflection + // scheme selector + Scheme TPMIAlgECCScheme `gotpm:"nullable"` + // scheme parameters + Details TPMUAsymScheme `gotpm:"tag=Scheme"` +} + +// TPMSSignatureRSA represents a TPMS_SIGNATURE_RSA. +// See definition in Part 2: Structures, section 11.3.1. +type TPMSSignatureRSA struct { + marshalByReflection + // the hash algorithm used to digest the message + Hash TPMIAlgHash + // The signature is the size of a public key. + Sig TPM2BPublicKeyRSA +} + +// TPMSSignatureECC represents a TPMS_SIGNATURE_ECC. +// See definition in Part 2: Structures, section 11.3.2. +type TPMSSignatureECC struct { + marshalByReflection + // the hash algorithm used in the signature process + Hash TPMIAlgHash + SignatureR TPM2BECCParameter + SignatureS TPM2BECCParameter +} + +// TPMUSignature represents a TPMU_SIGNATURE. +// See definition in Part 2: Structures, section 11.3.3. +type TPMUSignature struct { + selector TPMAlgID + contents Marshallable +} + +// SignatureContents is a type constraint representing the possible contents of TPMUSignature. +type SignatureContents interface { + Marshallable + *TPMTHA | *TPMSSignatureRSA | *TPMSSignatureECC +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUSignature) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgHMAC: + var contents TPMTHA + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgRSASSA, TPMAlgRSAPSS: + var contents TPMSSignatureRSA + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECDSA, TPMAlgECDAA: + var contents TPMSSignatureECC + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUSignature) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgHMAC: + var contents TPMTHA + if u.contents != nil { + contents = *u.contents.(*TPMTHA) + } + return reflect.ValueOf(&contents), nil + case TPMAlgRSASSA, TPMAlgRSAPSS: + var contents TPMSSignatureRSA + if u.contents != nil { + contents = *u.contents.(*TPMSSignatureRSA) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECDSA, TPMAlgECDAA: + var contents TPMSSignatureECC + if u.contents != nil { + contents = *u.contents.(*TPMSSignatureECC) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUSignature instantiates a TPMUSignature with the given contents. +func NewTPMUSignature[C SignatureContents](selector TPMAlgID, contents C) TPMUSignature { + return TPMUSignature{ + selector: selector, + contents: contents, + } +} + +// HMAC returns the 'hmac' member of the union. +func (u *TPMUSignature) HMAC() (*TPMTHA, error) { + if u.selector == TPMAlgHMAC { + return u.contents.(*TPMTHA), nil + } + return nil, fmt.Errorf("did not contain hmac (selector value was %v)", u.selector) +} + +// RSASSA returns the 'rsassa' member of the union. +func (u *TPMUSignature) RSASSA() (*TPMSSignatureRSA, error) { + if u.selector == TPMAlgRSASSA { + return u.contents.(*TPMSSignatureRSA), nil + } + return nil, fmt.Errorf("did not contain rsassa (selector value was %v)", u.selector) +} + +// RSAPSS returns the 'rsapss' member of the union. +func (u *TPMUSignature) RSAPSS() (*TPMSSignatureRSA, error) { + if u.selector == TPMAlgRSAPSS { + return u.contents.(*TPMSSignatureRSA), nil + } + return nil, fmt.Errorf("did not contain rsapss (selector value was %v)", u.selector) +} + +// ECDSA returns the 'ecdsa' member of the union. +func (u *TPMUSignature) ECDSA() (*TPMSSignatureECC, error) { + if u.selector == TPMAlgECDSA { + return u.contents.(*TPMSSignatureECC), nil + } + return nil, fmt.Errorf("did not contain ecdsa (selector value was %v)", u.selector) +} + +// ECDAA returns the 'ecdaa' member of the union. +func (u *TPMUSignature) ECDAA() (*TPMSSignatureECC, error) { + if u.selector == TPMAlgECDAA { + return u.contents.(*TPMSSignatureECC), nil + } + return nil, fmt.Errorf("did not contain ecdaa (selector value was %v)", u.selector) +} + +// TPMTSignature represents a TPMT_SIGNATURE. +// See definition in Part 2: Structures, section 11.3.4. +type TPMTSignature struct { + marshalByReflection + // selector of the algorithm used to construct the signature + SigAlg TPMIAlgSigScheme `gotpm:"nullable"` + // This shall be the actual signature information. + Signature TPMUSignature `gotpm:"tag=SigAlg"` +} + +// TPM2BEncryptedSecret represents a TPM2B_ENCRYPTED_SECRET. +// See definition in Part 2: Structures, section 11.4.33. +type TPM2BEncryptedSecret TPM2BData + +// TPMIAlgPublic represents a TPMI_ALG_PUBLIC. +// See definition in Part 2: Structures, section 12.2.2. +type TPMIAlgPublic = TPMAlgID + +// TPMUPublicID represents a TPMU_PUBLIC_ID. +// See definition in Part 2: Structures, section 12.2.3.2. +type TPMUPublicID struct { + selector TPMAlgID + contents Marshallable +} + +// PublicIDContents is a type constraint representing the possible contents of TPMUPublicID. +type PublicIDContents interface { + Marshallable + *TPM2BDigest | *TPM2BPublicKeyRSA | *TPMSECCPoint +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUPublicID) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgKeyedHash: + var contents TPM2BDigest + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgSymCipher: + var contents TPM2BDigest + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgRSA: + var contents TPM2BPublicKeyRSA + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECC: + var contents TPMSECCPoint + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUPublicID) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgKeyedHash: + var contents TPM2BDigest + if u.contents != nil { + contents = *u.contents.(*TPM2BDigest) + } + return reflect.ValueOf(&contents), nil + case TPMAlgSymCipher: + var contents TPM2BDigest + if u.contents != nil { + contents = *u.contents.(*TPM2BDigest) + } + return reflect.ValueOf(&contents), nil + case TPMAlgRSA: + var contents TPM2BPublicKeyRSA + if u.contents != nil { + contents = *u.contents.(*TPM2BPublicKeyRSA) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECC: + var contents TPMSECCPoint + if u.contents != nil { + contents = *u.contents.(*TPMSECCPoint) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUPublicID instantiates a TPMUPublicID with the given contents. +func NewTPMUPublicID[C PublicIDContents](selector TPMAlgID, contents C) TPMUPublicID { + return TPMUPublicID{ + selector: selector, + contents: contents, + } +} + +// KeyedHash returns the 'keyedHash' member of the union. +func (u *TPMUPublicID) KeyedHash() (*TPM2BDigest, error) { + if u.selector == TPMAlgKeyedHash { + return u.contents.(*TPM2BDigest), nil + } + return nil, fmt.Errorf("did not contain keyedHash (selector value was %v)", u.selector) +} + +// SymCipher returns the 'symCipher' member of the union. +func (u *TPMUPublicID) SymCipher() (*TPM2BDigest, error) { + if u.selector == TPMAlgSymCipher { + return u.contents.(*TPM2BDigest), nil + } + return nil, fmt.Errorf("did not contain symCipher (selector value was %v)", u.selector) +} + +// RSA returns the 'rsa' member of the union. +func (u *TPMUPublicID) RSA() (*TPM2BPublicKeyRSA, error) { + if u.selector == TPMAlgRSA { + return u.contents.(*TPM2BPublicKeyRSA), nil + } + return nil, fmt.Errorf("did not contain rsa (selector value was %v)", u.selector) +} + +// ECC returns the 'ecc' member of the union. +func (u *TPMUPublicID) ECC() (*TPMSECCPoint, error) { + if u.selector == TPMAlgECC { + return u.contents.(*TPMSECCPoint), nil + } + return nil, fmt.Errorf("did not contain ecc (selector value was %v)", u.selector) +} + +// TPMSKeyedHashParms represents a TPMS_KEYEDHASH_PARMS. +// See definition in Part 2: Structures, section 12.2.3.3. +type TPMSKeyedHashParms struct { + marshalByReflection + // Indicates the signing method used for a keyedHash signing + // object. This field also determines the size of the data field + // for a data object created with TPM2_Create() or + // TPM2_CreatePrimary(). + Scheme TPMTKeyedHashScheme +} + +// TPMSRSAParms represents a TPMS_RSA_PARMS. +// See definition in Part 2: Structures, section 12.2.3.5. +type TPMSRSAParms struct { + marshalByReflection + // for a restricted decryption key, shall be set to a supported + // symmetric algorithm, key size, and mode. + // if the key is not a restricted decryption key, this field shall + // be set to TPM_ALG_NULL. + Symmetric TPMTSymDefObject + // scheme.scheme shall be: + // for an unrestricted signing key, either TPM_ALG_RSAPSS + // TPM_ALG_RSASSA or TPM_ALG_NULL + // for a restricted signing key, either TPM_ALG_RSAPSS or + // TPM_ALG_RSASSA + // for an unrestricted decryption key, TPM_ALG_RSAES, TPM_ALG_OAEP, + // or TPM_ALG_NULL unless the object also has the sign attribute + // for a restricted decryption key, TPM_ALG_NULL + Scheme TPMTRSAScheme + // number of bits in the public modulus + KeyBits TPMIRSAKeyBits + // the public exponent + // A prime number greater than 2. + Exponent uint32 +} + +// TPMSECCParms represents a TPMS_ECC_PARMS. +// See definition in Part 2: Structures, section 12.2.3.6. +type TPMSECCParms struct { + marshalByReflection + // for a restricted decryption key, shall be set to a supported + // symmetric algorithm, key size. and mode. + // if the key is not a restricted decryption key, this field shall + // be set to TPM_ALG_NULL. + Symmetric TPMTSymDefObject + // If the sign attribute of the key is SET, then this shall be a + // valid signing scheme. + Scheme TPMTECCScheme + // ECC curve ID + CurveID TPMIECCCurve + // an optional key derivation scheme for generating a symmetric key + // from a Z value + // If the kdf parameter associated with curveID is not TPM_ALG_NULL + // then this is required to be NULL. + KDF TPMTKDFScheme +} + +// TPMUPublicParms represents a TPMU_PUBLIC_PARMS. +// See definition in Part 2: Structures, section 12.2.3.7. +type TPMUPublicParms struct { + selector TPMAlgID + contents Marshallable +} + +// PublicParmsContents is a type constraint representing the possible contents of TPMUPublicParms. +type PublicParmsContents interface { + Marshallable + *TPMSKeyedHashParms | *TPMSSymCipherParms | *TPMSRSAParms | + *TPMSECCParms +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUPublicParms) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgKeyedHash: + var contents TPMSKeyedHashParms + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgSymCipher: + var contents TPMSSymCipherParms + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgRSA: + var contents TPMSRSAParms + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECC: + var contents TPMSECCParms + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUPublicParms) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgKeyedHash: + var contents TPMSKeyedHashParms + if u.contents != nil { + contents = *u.contents.(*TPMSKeyedHashParms) + } + return reflect.ValueOf(&contents), nil + case TPMAlgSymCipher: + var contents TPMSSymCipherParms + if u.contents != nil { + contents = *u.contents.(*TPMSSymCipherParms) + } + return reflect.ValueOf(&contents), nil + case TPMAlgRSA: + var contents TPMSRSAParms + if u.contents != nil { + contents = *u.contents.(*TPMSRSAParms) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECC: + var contents TPMSECCParms + if u.contents != nil { + contents = *u.contents.(*TPMSECCParms) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUPublicParms instantiates a TPMUPublicParms with the given contents. +func NewTPMUPublicParms[C PublicParmsContents](selector TPMAlgID, contents C) TPMUPublicParms { + return TPMUPublicParms{ + selector: selector, + contents: contents, + } +} + +// KeyedHashDetail returns the 'keyedHashDetail' member of the union. +func (u *TPMUPublicParms) KeyedHashDetail() (*TPMSKeyedHashParms, error) { + if u.selector == TPMAlgKeyedHash { + return u.contents.(*TPMSKeyedHashParms), nil + } + return nil, fmt.Errorf("did not contain keyedHashDetail (selector value was %v)", u.selector) +} + +// SymDetail returns the 'symDetail' member of the union. +func (u *TPMUPublicParms) SymDetail() (*TPMSSymCipherParms, error) { + if u.selector == TPMAlgSymCipher { + return u.contents.(*TPMSSymCipherParms), nil + } + return nil, fmt.Errorf("did not contain symDetail (selector value was %v)", u.selector) +} + +// RSADetail returns the 'rsaDetail' member of the union. +func (u *TPMUPublicParms) RSADetail() (*TPMSRSAParms, error) { + if u.selector == TPMAlgRSA { + return u.contents.(*TPMSRSAParms), nil + } + return nil, fmt.Errorf("did not contain rsaDetail (selector value was %v)", u.selector) +} + +// ECCDetail returns the 'eccDetail' member of the union. +func (u *TPMUPublicParms) ECCDetail() (*TPMSECCParms, error) { + if u.selector == TPMAlgECC { + return u.contents.(*TPMSECCParms), nil + } + return nil, fmt.Errorf("did not contain eccDetail (selector value was %v)", u.selector) +} + +// TPMTPublicParms represents a TPMT_PUBLIC_PARMS. +// See definition in Part 2: Structures, section 12.2.3.8. +type TPMTPublicParms struct { + marshalByReflection + // algorithm to be tested + Type TPMIAlgPublic + // algorithm details + Parameters TPMUPublicParms `gotpm:"tag=Type"` +} + +// TPMTPublic represents a TPMT_PUBLIC. +// See definition in Part 2: Structures, section 12.2.4. +type TPMTPublic struct { + marshalByReflection + // “algorithm” associated with this object + Type TPMIAlgPublic + // algorithm used for computing the Name of the object + NameAlg TPMIAlgHash + // attributes that, along with type, determine the manipulations + // of this object + ObjectAttributes TPMAObject + // optional policy for using this key + // The policy is computed using the nameAlg of the object. + AuthPolicy TPM2BDigest + // the algorithm or structure details + Parameters TPMUPublicParms `gotpm:"tag=Type"` + // the unique identifier of the structure + // For an asymmetric key, this would be the public key. + Unique TPMUPublicID `gotpm:"tag=Type"` +} + +// TPM2BPublic represents a TPM2B_PUBLIC. +// See definition in Part 2: Structures, section 12.2.5. +type TPM2BPublic = TPM2B[TPMTPublic, *TPMTPublic] + +// TPM2BTemplate represents a TPM2B_TEMPLATE. +// See definition in Part 2: Structures, section 12.2.6. +type TPM2BTemplate TPM2BData + +// TemplateContents is a type constraint representing the possible contents of TPMUTemplate. +type TemplateContents interface { + Marshallable + *TPMTPublic | *TPMTTemplate +} + +// TPMTTemplate represents a TPMT_TEMPLATE. It is not defined in the spec. +// It represents the alternate form of TPMT_PUBLIC for TPM2B_TEMPLATE as +// described in Part 2: Structures, 12.2.6. +type TPMTTemplate struct { + marshalByReflection + // “algorithm” associated with this object + Type TPMIAlgPublic + // algorithm used for computing the Name of the object + NameAlg TPMIAlgHash + // attributes that, along with type, determine the manipulations + // of this object + ObjectAttributes TPMAObject + // optional policy for using this key + // The policy is computed using the nameAlg of the object. + AuthPolicy TPM2BDigest + // the algorithm or structure details + Parameters TPMUPublicParms `gotpm:"tag=Type"` + // the derivation parameters + Unique TPMSDerive +} + +// New2BTemplate creates a TPM2BTemplate with the given data. +func New2BTemplate[C TemplateContents](data C) TPM2BTemplate { + return TPM2BTemplate{ + Buffer: Marshal(data), + } +} + +// Sym returns the 'sym' member of the union. +func (u *TPMUSensitiveComposite) Sym() (*TPM2BSymKey, error) { + if u.selector == TPMAlgSymCipher { + return u.contents.(*TPM2BSymKey), nil + } + return nil, fmt.Errorf("did not contain sym (selector value was %v)", u.selector) +} + +// Bits returns the 'bits' member of the union. +func (u *TPMUSensitiveComposite) Bits() (*TPM2BSensitiveData, error) { + if u.selector == TPMAlgKeyedHash { + return u.contents.(*TPM2BSensitiveData), nil + } + return nil, fmt.Errorf("did not contain bits (selector value was %v)", u.selector) +} + +// RSA returns the 'rsa' member of the union. +func (u *TPMUSensitiveComposite) RSA() (*TPM2BPrivateKeyRSA, error) { + if u.selector == TPMAlgRSA { + return u.contents.(*TPM2BPrivateKeyRSA), nil + } + return nil, fmt.Errorf("did not contain rsa (selector value was %v)", u.selector) +} + +// ECC returns the 'ecc' member of the union. +func (u *TPMUSensitiveComposite) ECC() (*TPM2BECCParameter, error) { + if u.selector == TPMAlgECC { + return u.contents.(*TPM2BECCParameter), nil + } + return nil, fmt.Errorf("did not contain ecc (selector value was %v)", u.selector) +} + +// TPMUSensitiveComposite represents a TPMU_SENSITIVE_COMPOSITE. +// See definition in Part 2: Structures, section 12.3.2.3. +type TPMUSensitiveComposite struct { + selector TPMAlgID + contents Marshallable +} + +// SensitiveCompositeContents is a type constraint representing the possible contents of TPMUSensitiveComposite. +type SensitiveCompositeContents interface { + Marshallable + *TPM2BPrivateKeyRSA | *TPM2BECCParameter | *TPM2BSensitiveData | *TPM2BSymKey +} + +// create implements the unmarshallableWithHint interface. +func (u *TPMUSensitiveComposite) create(hint int64) (reflect.Value, error) { + switch TPMAlgID(hint) { + case TPMAlgRSA: + var contents TPM2BPrivateKeyRSA + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgECC: + var contents TPM2BECCParameter + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgKeyedHash: + var contents TPM2BSensitiveData + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + case TPMAlgSymCipher: + var contents TPM2BSymKey + u.contents = &contents + u.selector = TPMAlgID(hint) + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// get implements the marshallableWithHint interface. +func (u TPMUSensitiveComposite) get(hint int64) (reflect.Value, error) { + if u.selector != 0 && hint != int64(u.selector) { + return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector) + } + switch TPMAlgID(hint) { + case TPMAlgRSA: + var contents TPM2BPrivateKeyRSA + if u.contents != nil { + contents = *u.contents.(*TPM2BPrivateKeyRSA) + } + return reflect.ValueOf(&contents), nil + case TPMAlgECC: + var contents TPM2BECCParameter + if u.contents != nil { + contents = *u.contents.(*TPM2BECCParameter) + } + return reflect.ValueOf(&contents), nil + case TPMAlgKeyedHash: + var contents TPM2BSensitiveData + if u.contents != nil { + contents = *u.contents.(*TPM2BSensitiveData) + } + return reflect.ValueOf(&contents), nil + case TPMAlgSymCipher: + var contents TPM2BSymKey + if u.contents != nil { + contents = *u.contents.(*TPM2BSymKey) + } + return reflect.ValueOf(&contents), nil + } + return reflect.ValueOf(nil), fmt.Errorf("no union member for tag %v", hint) +} + +// NewTPMUSensitiveComposite instantiates a TPMUSensitiveComposite with the given contents. +func NewTPMUSensitiveComposite[C SensitiveCompositeContents](selector TPMAlgID, contents C) TPMUSensitiveComposite { + return TPMUSensitiveComposite{ + selector: selector, + contents: contents, + } +} + +// RSA returns the 'rsa' member of the union. +func (u *TPMUKDFScheme) RSA() (*TPM2BPrivateKeyRSA, error) { + if u.selector == TPMAlgRSA { + return u.contents.(*TPM2BPrivateKeyRSA), nil + } + return nil, fmt.Errorf("did not contain rsa (selector value was %v)", u.selector) +} + +// ECC returns the 'ecc' member of the union. +func (u *TPMUKDFScheme) ECC() (*TPM2BECCParameter, error) { + if u.selector == TPMAlgECC { + return u.contents.(*TPM2BECCParameter), nil + } + return nil, fmt.Errorf("did not contain ecc (selector value was %v)", u.selector) +} + +// Bits returns the 'bits' member of the union. +func (u *TPMUKDFScheme) Bits() (*TPM2BSensitiveData, error) { + if u.selector == TPMAlgKeyedHash { + return u.contents.(*TPM2BSensitiveData), nil + } + return nil, fmt.Errorf("did not contain bits (selector value was %v)", u.selector) +} + +// Sym returns the 'sym' member of the union. +func (u *TPMUKDFScheme) Sym() (*TPM2BSymKey, error) { + if u.selector == TPMAlgSymCipher { + return u.contents.(*TPM2BSymKey), nil + } + return nil, fmt.Errorf("did not contain sym (selector value was %v)", u.selector) +} + +// TPMTSensitive represents a TPMT_SENSITIVE. +// See definition in Part 2: Structures, section 12.3.2.4. +type TPMTSensitive struct { + marshalByReflection + // identifier for the sensitive area + SensitiveType TPMIAlgPublic + // user authorization data + AuthValue TPM2BAuth + // for a parent object, the optional protection seed; for other objects, + // the obfuscation value + SeedValue TPM2BDigest + // the type-specific private data + Sensitive TPMUSensitiveComposite `gotpm:"tag=SensitiveType"` +} + +// TPM2BSensitive represents a TPM2B_SENSITIVE. +// See definition in Part 2: Structures, section 12.3.3. +type TPM2BSensitive = TPM2B[TPMTSensitive, *TPMTSensitive] + +// TPM2BPrivate represents a TPM2B_PRIVATE. +// See definition in Part 2: Structures, section 12.3.7. +type TPM2BPrivate TPM2BData + +// TPMSCreationData represents a TPMS_CREATION_DATA. +// See definition in Part 2: Structures, section 15.1. +type TPMSCreationData struct { + marshalByReflection + // list indicating the PCR included in pcrDigest + PCRSelect TPMLPCRSelection + // digest of the selected PCR using nameAlg of the object for which + // this structure is being created + PCRDigest TPM2BDigest + // the locality at which the object was created + Locality TPMALocality + // nameAlg of the parent + ParentNameAlg TPMAlgID + // Name of the parent at time of creation + ParentName TPM2BName + // Qualified Name of the parent at the time of creation + ParentQualifiedName TPM2BName + // association with additional information added by the key + OutsideInfo TPM2BData +} + +// TPM2BIDObject represents a TPM2B_ID_OBJECT. +// See definition in Part 2: Structures, section 12.4.3. +type TPM2BIDObject TPM2BData + +// TPMANV represents a TPMA_NV. +// See definition in Part 2: Structures, section 13.4. +type TPMANV struct { + bitfield32 + marshalByReflection + // SET (1): The Index data can be written if Platform Authorization is + // provided. + // CLEAR (0): Writing of the Index data cannot be authorized with + // Platform Authorization. + PPWrite bool `gotpm:"bit=0"` + // SET (1): The Index data can be written if Owner Authorization is + // provided. + // CLEAR (0): Writing of the Index data cannot be authorized with Owner + // Authorization. + OwnerWrite bool `gotpm:"bit=1"` + // SET (1): Authorizations to change the Index contents that require + // USER role may be provided with an HMAC session or password. + // CLEAR (0): Authorizations to change the Index contents that require + // USER role may not be provided with an HMAC session or password. + AuthWrite bool `gotpm:"bit=2"` + // SET (1): Authorizations to change the Index contents that require + // USER role may be provided with a policy session. + // CLEAR (0): Authorizations to change the Index contents that require + // USER role may not be provided with a policy session. + PolicyWrite bool `gotpm:"bit=3"` + // The type of the index. + NT TPMNT `gotpm:"bit=7:4"` + // SET (1): Index may not be deleted unless the authPolicy is satisfied + // using TPM2_NV_UndefineSpaceSpecial(). + // CLEAR (0): Index may be deleted with proper platform or owner + // authorization using TPM2_NV_UndefineSpace(). + PolicyDelete bool `gotpm:"bit=10"` + // SET (1): Index cannot be written. + // CLEAR (0): Index can be written. + WriteLocked bool `gotpm:"bit=11"` + // SET (1): A partial write of the Index data is not allowed. The write + // size shall match the defined space size. + // CLEAR (0): Partial writes are allowed. This setting is required if + // the .dataSize of the Index is larger than NV_MAX_BUFFER_SIZE for the + // implementation. + WriteAll bool `gotpm:"bit=12"` + // SET (1): TPM2_NV_WriteLock() may be used to prevent further writes + // to this location. + // CLEAR (0): TPM2_NV_WriteLock() does not block subsequent writes if + // TPMA_NV_WRITE_STCLEAR is also CLEAR. + WriteDefine bool `gotpm:"bit=13"` + // SET (1): TPM2_NV_WriteLock() may be used to prevent further writes + // to this location until the next TPM Reset or TPM Restart. + // CLEAR (0): TPM2_NV_WriteLock() does not block subsequent writes if + // TPMA_NV_WRITEDEFINE is also CLEAR. + WriteSTClear bool `gotpm:"bit=14"` + // SET (1): If TPM2_NV_GlobalWriteLock() is successful, + // TPMA_NV_WRITELOCKED is set. + // CLEAR (0): TPM2_NV_GlobalWriteLock() has no effect on the writing of + // the data at this Index. + GlobalLock bool `gotpm:"bit=15"` + // SET (1): The Index data can be read if Platform Authorization is + // provided. + // CLEAR (0): Reading of the Index data cannot be authorized with + // Platform Authorization. + PPRead bool `gotpm:"bit=16"` + // SET (1): The Index data can be read if Owner Authorization is + // provided. + // CLEAR (0): Reading of the Index data cannot be authorized with Owner + // Authorization. + OwnerRead bool `gotpm:"bit=17"` + // SET (1): The Index data may be read if the authValue is provided. + // CLEAR (0): Reading of the Index data cannot be authorized with the + // Index authValue. + AuthRead bool `gotpm:"bit=18"` + // SET (1): The Index data may be read if the authPolicy is satisfied. + // CLEAR (0): Reading of the Index data cannot be authorized with the + // Index authPolicy. + PolicyRead bool `gotpm:"bit=19"` + // SET (1): Authorization failures of the Index do not affect the DA + // logic and authorization of the Index is not blocked when the TPM is + // in Lockout mode. + // CLEAR (0): Authorization failures of the Index will increment the + // authorization failure counter and authorizations of this Index are + // not allowed when the TPM is in Lockout mode. + NoDA bool `gotpm:"bit=25"` + // SET (1): NV Index state is only required to be saved when the TPM + // performs an orderly shutdown (TPM2_Shutdown()). + // CLEAR (0): NV Index state is required to be persistent after the + // command to update the Index completes successfully (that is, the NV + // update is synchronous with the update command). + Orderly bool `gotpm:"bit=26"` + // SET (1): TPMA_NV_WRITTEN for the Index is CLEAR by TPM Reset or TPM + // Restart. + // CLEAR (0): TPMA_NV_WRITTEN is not changed by TPM Restart. + ClearSTClear bool `gotpm:"bit=27"` + // SET (1): Reads of the Index are blocked until the next TPM Reset or + // TPM Restart. + // CLEAR (0): Reads of the Index are allowed if proper authorization is + // provided. + ReadLocked bool `gotpm:"bit=28"` + // SET (1): Index has been written. + // CLEAR (0): Index has not been written. + Written bool `gotpm:"bit=29"` + // SET (1): This Index may be undefined with Platform Authorization + // but not with Owner Authorization. + // CLEAR (0): This Index may be undefined using Owner Authorization but + // not with Platform Authorization. + PlatformCreate bool `gotpm:"bit=30"` + // SET (1): TPM2_NV_ReadLock() may be used to SET TPMA_NV_READLOCKED + // for this Index. + // CLEAR (0): TPM2_NV_ReadLock() has no effect on this Index. + ReadSTClear bool `gotpm:"bit=31"` +} + +// TPMSNVPublic represents a TPMS_NV_PUBLIC. +// See definition in Part 2: Structures, section 13.5. +type TPMSNVPublic struct { + marshalByReflection + // the handle of the data area + NVIndex TPMIRHNVIndex + // hash algorithm used to compute the name of the Index and used for + // the authPolicy. For an extend index, the hash algorithm used for the + // extend. + NameAlg TPMIAlgHash + // the Index attributes + Attributes TPMANV + // optional access policy for the Index + AuthPolicy TPM2BDigest + // the size of the data area + DataSize uint16 +} + +// TPM2BNVPublic represents a TPM2B_NV_PUBLIC. +// See definition in Part 2: Structures, section 13.6. +type TPM2BNVPublic = TPM2B[TPMSNVPublic, *TPMSNVPublic] + +// TPM2BContextSensitive represents a TPM2B_CONTEXT_SENSITIVE +// See definition in Part 2: Structures, section 14.2. +type TPM2BContextSensitive TPM2BData + +// TPMSContextData represents a TPMS_CONTEXT_DATA +// See definition in Part 2: Structures, section 14.3. +type TPMSContextData struct { + marshalByReflection + // the integrity value + Integrity TPM2BDigest + // the sensitive area + Encrypted TPM2BContextSensitive +} + +// TPM2BContextData represents a TPM2B_CONTEXT_DATA +// See definition in Part 2: Structures, section 14.4. +// Represented here as a flat buffer because how a TPM chooses +// to represent its context data is implementation-dependent. +type TPM2BContextData TPM2BData + +// TPMSContext represents a TPMS_CONTEXT +// See definition in Part 2: Structures, section 14.5. +type TPMSContext struct { + marshalByReflection + // the sequence number of the context + Sequence uint64 + // a handle indicating if the context is a session, object, or sequence object + SavedHandle TPMIDHSaved + // the hierarchy of the context + Hierarchy TPMIRHHierarchy + // the context data and integrity HMAC + ContextBlob TPM2BContextData +} + +type tpm2bCreationData = TPM2B[TPMSCreationData, *TPMSCreationData] diff --git a/vendor/github.com/google/go-tpm/tpm2/templates.go b/vendor/github.com/google/go-tpm/tpm2/templates.go new file mode 100644 index 00000000000..a0cfa811c49 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/templates.go @@ -0,0 +1,200 @@ +package tpm2 + +var ( + // RSASRKTemplate contains the TCG reference RSA-2048 SRK template. + // https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf + RSASRKTemplate = TPMTPublic{ + Type: TPMAlgRSA, + NameAlg: TPMAlgSHA256, + ObjectAttributes: TPMAObject{ + FixedTPM: true, + STClear: false, + FixedParent: true, + SensitiveDataOrigin: true, + UserWithAuth: true, + AdminWithPolicy: false, + NoDA: true, + EncryptedDuplication: false, + Restricted: true, + Decrypt: true, + SignEncrypt: false, + }, + Parameters: NewTPMUPublicParms( + TPMAlgRSA, + &TPMSRSAParms{ + Symmetric: TPMTSymDefObject{ + Algorithm: TPMAlgAES, + KeyBits: NewTPMUSymKeyBits( + TPMAlgAES, + TPMKeyBits(128), + ), + Mode: NewTPMUSymMode( + TPMAlgAES, + TPMAlgCFB, + ), + }, + KeyBits: 2048, + }, + ), + Unique: NewTPMUPublicID( + TPMAlgRSA, + &TPM2BPublicKeyRSA{ + Buffer: make([]byte, 256), + }, + ), + } + // RSAEKTemplate contains the TCG reference RSA-2048 EK template. + RSAEKTemplate = TPMTPublic{ + Type: TPMAlgRSA, + NameAlg: TPMAlgSHA256, + ObjectAttributes: TPMAObject{ + FixedTPM: true, + STClear: false, + FixedParent: true, + SensitiveDataOrigin: true, + UserWithAuth: false, + AdminWithPolicy: true, + NoDA: false, + EncryptedDuplication: false, + Restricted: true, + Decrypt: true, + SignEncrypt: false, + }, + AuthPolicy: TPM2BDigest{ + Buffer: []byte{ + // TPM2_PolicySecret(RH_ENDORSEMENT) + 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xB3, 0xF8, + 0x1A, 0x90, 0xCC, 0x8D, 0x46, 0xA5, 0xD7, 0x24, + 0xFD, 0x52, 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64, + 0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, 0x69, 0xAA, + }, + }, + Parameters: NewTPMUPublicParms( + TPMAlgRSA, + &TPMSRSAParms{ + Symmetric: TPMTSymDefObject{ + Algorithm: TPMAlgAES, + KeyBits: NewTPMUSymKeyBits( + TPMAlgAES, + TPMKeyBits(128), + ), + Mode: NewTPMUSymMode( + TPMAlgAES, + TPMAlgCFB, + ), + }, + KeyBits: 2048, + }, + ), + Unique: NewTPMUPublicID( + TPMAlgRSA, + &TPM2BPublicKeyRSA{ + Buffer: make([]byte, 256), + }, + ), + } + + // ECCSRKTemplate contains the TCG reference ECC-P256 SRK template. + // https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf + ECCSRKTemplate = TPMTPublic{ + Type: TPMAlgECC, + NameAlg: TPMAlgSHA256, + ObjectAttributes: TPMAObject{ + FixedTPM: true, + STClear: false, + FixedParent: true, + SensitiveDataOrigin: true, + UserWithAuth: true, + AdminWithPolicy: false, + NoDA: true, + EncryptedDuplication: false, + Restricted: true, + Decrypt: true, + SignEncrypt: false, + }, + Parameters: NewTPMUPublicParms( + TPMAlgECC, + &TPMSECCParms{ + Symmetric: TPMTSymDefObject{ + Algorithm: TPMAlgAES, + KeyBits: NewTPMUSymKeyBits( + TPMAlgAES, + TPMKeyBits(128), + ), + Mode: NewTPMUSymMode( + TPMAlgAES, + TPMAlgCFB, + ), + }, + CurveID: TPMECCNistP256, + }, + ), + Unique: NewTPMUPublicID( + TPMAlgECC, + &TPMSECCPoint{ + X: TPM2BECCParameter{ + Buffer: make([]byte, 32), + }, + Y: TPM2BECCParameter{ + Buffer: make([]byte, 32), + }, + }, + ), + } + + // ECCEKTemplate contains the TCG reference ECC-P256 EK template. + ECCEKTemplate = TPMTPublic{ + Type: TPMAlgECC, + NameAlg: TPMAlgSHA256, + ObjectAttributes: TPMAObject{ + FixedTPM: true, + STClear: false, + FixedParent: true, + SensitiveDataOrigin: true, + UserWithAuth: false, + AdminWithPolicy: true, + NoDA: false, + EncryptedDuplication: false, + Restricted: true, + Decrypt: true, + SignEncrypt: false, + }, + AuthPolicy: TPM2BDigest{ + Buffer: []byte{ + // TPM2_PolicySecret(RH_ENDORSEMENT) + 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xB3, 0xF8, + 0x1A, 0x90, 0xCC, 0x8D, 0x46, 0xA5, 0xD7, 0x24, + 0xFD, 0x52, 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64, + 0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, 0x69, 0xAA, + }, + }, + Parameters: NewTPMUPublicParms( + TPMAlgECC, + &TPMSECCParms{ + Symmetric: TPMTSymDefObject{ + Algorithm: TPMAlgAES, + KeyBits: NewTPMUSymKeyBits( + TPMAlgAES, + TPMKeyBits(128), + ), + Mode: NewTPMUSymMode( + TPMAlgAES, + TPMAlgCFB, + ), + }, + CurveID: TPMECCNistP256, + }, + ), + Unique: NewTPMUPublicID( + TPMAlgECC, + &TPMSECCPoint{ + X: TPM2BECCParameter{ + Buffer: make([]byte, 32), + }, + Y: TPM2BECCParameter{ + Buffer: make([]byte, 32), + }, + }, + ), + } +) diff --git a/vendor/github.com/google/go-tpm/tpm2/tpm2.go b/vendor/github.com/google/go-tpm/tpm2/tpm2.go new file mode 100644 index 00000000000..4a2ebae0239 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/tpm2.go @@ -0,0 +1,2232 @@ +// Package tpm2 contains TPM 2.0 commands and structures. +package tpm2 + +import ( + "bytes" + "encoding/binary" + + "github.com/google/go-tpm/tpm2/transport" +) + +// handle represents a TPM handle as comprehended in Part 3: Commands. +// In the context of TPM commands, handles are special parameters for which +// there is a known associated name. +// This is not an exported interface, because the reflection logic has special +// behavior for AuthHandle, due to the fact that referencing Session from this +// interface would break the ability to make TPMHandle implement it. +type handle interface { + // HandleValue is the numeric concrete handle value in the TPM. + HandleValue() uint32 + // KnownName is the TPM Name of the associated entity. See Part 1, section 16. + KnownName() *TPM2BName +} + +// NamedHandle represents an associated pairing of TPM handle and known Name. +type NamedHandle struct { + Handle TPMHandle + Name TPM2BName +} + +// HandleValue implements the handle interface. +func (h NamedHandle) HandleValue() uint32 { + return h.Handle.HandleValue() +} + +// KnownName implements the handle interface. +func (h NamedHandle) KnownName() *TPM2BName { + return &h.Name +} + +// AuthHandle allows the caller to add an authorization session onto a handle. +type AuthHandle struct { + Handle TPMHandle + Name TPM2BName + Auth Session +} + +// HandleValue implements the handle interface. +func (h AuthHandle) HandleValue() uint32 { + return h.Handle.HandleValue() +} + +// KnownName implements the handle interface. +// If Name is not provided (i.e., only Auth), then rely on the underlying +// TPMHandle. +func (h AuthHandle) KnownName() *TPM2BName { + if len(h.Name.Buffer) != 0 { + return &h.Name + } + return h.Handle.KnownName() +} + +// Command is an interface for any TPM command, parameterized by its response +// type. +type Command[R any, PR *R] interface { + // The TPM command code associated with this command. + Command() TPMCC + // Executes the command and returns the response. + Execute(t transport.TPM, s ...Session) (PR, error) +} + +// PolicyCommand is a TPM command that can be part of a TPM policy. +type PolicyCommand interface { + // Update updates the given policy hash according to the command + // parameters. + Update(policy *PolicyCalculator) error +} + +// Shutdown is the input to TPM2_Shutdown. +// See definition in Part 3, Commands, section 9.4. +type Shutdown struct { + // TPM_SU_CLEAR or TPM_SU_STATE + ShutdownType TPMSU +} + +// Command implements the Command interface. +func (Shutdown) Command() TPMCC { return TPMCCShutdown } + +// Execute executes the command and returns the response. +func (cmd Shutdown) Execute(t transport.TPM, s ...Session) (*ShutdownResponse, error) { + var rsp ShutdownResponse + err := execute[ShutdownResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// ShutdownResponse is the response from TPM2_Shutdown. +type ShutdownResponse struct{} + +// Startup is the input to TPM2_Startup. +// See definition in Part 3, Commands, section 9.3. +type Startup struct { + // TPM_SU_CLEAR or TPM_SU_STATE + StartupType TPMSU +} + +// Command implements the Command interface. +func (Startup) Command() TPMCC { return TPMCCStartup } + +// Execute executes the command and returns the response. +func (cmd Startup) Execute(t transport.TPM, s ...Session) (*StartupResponse, error) { + var rsp StartupResponse + err := execute[StartupResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// StartupResponse is the response from TPM2_Startup. +type StartupResponse struct{} + +// StartAuthSession is the input to TPM2_StartAuthSession. +// See definition in Part 3, Commands, section 11.1 +type StartAuthSession struct { + // handle of a loaded decrypt key used to encrypt salt + // may be TPM_RH_NULL + TPMKey handle `gotpm:"handle"` + // entity providing the authValue + // may be TPM_RH_NULL + Bind handle `gotpm:"handle"` + // initial nonceCaller, sets nonceTPM size for the session + // shall be at least 16 octets + NonceCaller TPM2BNonce + // value encrypted according to the type of tpmKey + // If tpmKey is TPM_RH_NULL, this shall be the Empty Buffer. + EncryptedSalt TPM2BEncryptedSecret + // indicates the type of the session; simple HMAC or policy (including + // a trial policy) + SessionType TPMSE + // the algorithm and key size for parameter encryption + // may select transport.TPM_ALG_NULL + Symmetric TPMTSymDef + // hash algorithm to use for the session + // Shall be a hash algorithm supported by the TPM and not transport.TPM_ALG_NULL + AuthHash TPMIAlgHash +} + +// Command implements the Command interface. +func (StartAuthSession) Command() TPMCC { return TPMCCStartAuthSession } + +// Execute executes the command and returns the response. +func (cmd StartAuthSession) Execute(t transport.TPM, s ...Session) (*StartAuthSessionResponse, error) { + var rsp StartAuthSessionResponse + if err := execute[StartAuthSessionResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// StartAuthSessionResponse is the response from TPM2_StartAuthSession. +type StartAuthSessionResponse struct { + // handle for the newly created session + SessionHandle TPMISHAuthSession `gotpm:"handle"` + // the initial nonce from the TPM, used in the computation of the sessionKey + NonceTPM TPM2BNonce +} + +// Create is the input to TPM2_Create. +// See definition in Part 3, Commands, section 12.1 +type Create struct { + // handle of parent for new object + ParentHandle handle `gotpm:"handle,auth"` + // the sensitive data + InSensitive TPM2BSensitiveCreate + // the public template + InPublic TPM2BPublic + // data that will be included in the creation data for this + // object to provide permanent, verifiable linkage between this + // object and some object owner data + OutsideInfo TPM2BData + // PCR that will be used in creation data + CreationPCR TPMLPCRSelection +} + +// Command implements the Command interface. +func (Create) Command() TPMCC { return TPMCCCreate } + +// Execute executes the command and returns the response. +func (cmd Create) Execute(t transport.TPM, s ...Session) (*CreateResponse, error) { + var rsp CreateResponse + if err := execute[CreateResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// CreateResponse is the response from TPM2_Create. +type CreateResponse struct { + // the private portion of the object + OutPrivate TPM2BPrivate + // the public portion of the created object + OutPublic TPM2BPublic + // contains a TPMS_CREATION_DATA + CreationData tpm2bCreationData + // digest of creationData using nameAlg of outPublic + CreationHash TPM2BDigest + // ticket used by TPM2_CertifyCreation() to validate that the + // creation data was produced by the TPM. + CreationTicket TPMTTKCreation +} + +// Load is the input to TPM2_Load. +// See definition in Part 3, Commands, section 12.2 +type Load struct { + // handle of parent for new object + ParentHandle handle `gotpm:"handle,auth"` + // the private portion of the object + InPrivate TPM2BPrivate + // the public portion of the object + InPublic TPM2BPublic +} + +// Command implements the Command interface. +func (Load) Command() TPMCC { return TPMCCLoad } + +// Execute executes the command and returns the response. +func (cmd Load) Execute(t transport.TPM, s ...Session) (*LoadResponse, error) { + var rsp LoadResponse + if err := execute[LoadResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// LoadResponse is the response from TPM2_Load. +type LoadResponse struct { + // handle of type TPM_HT_TRANSIENT for loaded object + ObjectHandle TPMHandle `gotpm:"handle"` + // Name of the loaded object + Name TPM2BName +} + +// LoadExternal is the input to TPM2_LoadExternal. +// See definition in Part 3, Commands, section 12.3 +type LoadExternal struct { + // the sensitive portion of the object (optional) + InPrivate TPM2BSensitive `gotpm:"optional"` + // the public portion of the object + InPublic TPM2BPublic + // hierarchy with which the object area is associated + Hierarchy TPMIRHHierarchy `gotpm:"nullable"` +} + +// Command implements the Command interface. +func (LoadExternal) Command() TPMCC { return TPMCCLoadExternal } + +// Execute executes the command and returns the response. +func (cmd LoadExternal) Execute(t transport.TPM, s ...Session) (*LoadExternalResponse, error) { + var rsp LoadExternalResponse + if err := execute[LoadExternalResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// LoadExternalResponse is the response from TPM2_LoadExternal. +type LoadExternalResponse struct { + // handle of type TPM_HT_TRANSIENT for loaded object + ObjectHandle TPMHandle `gotpm:"handle"` + // Name of the loaded object + Name TPM2BName +} + +// ReadPublic is the input to TPM2_ReadPublic. +// See definition in Part 3, Commands, section 12.4 +type ReadPublic struct { + // TPM handle of an object + ObjectHandle TPMIDHObject `gotpm:"handle"` +} + +// Command implements the Command interface. +func (ReadPublic) Command() TPMCC { return TPMCCReadPublic } + +// Execute executes the command and returns the response. +func (cmd ReadPublic) Execute(t transport.TPM, s ...Session) (*ReadPublicResponse, error) { + var rsp ReadPublicResponse + if err := execute[ReadPublicResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// ReadPublicResponse is the response from TPM2_ReadPublic. +type ReadPublicResponse struct { + // structure containing the public area of an object + OutPublic TPM2BPublic + // name of object + Name TPM2BName + // the Qualified Name of the object + QualifiedName TPM2BName +} + +// ActivateCredential is the input to TPM2_ActivateCredential. +// See definition in Part 3, Commands, section 12.5. +type ActivateCredential struct { + // handle of the object associated with certificate in credentialBlob + ActivateHandle handle `gotpm:"handle,auth"` + // loaded key used to decrypt the TPMS_SENSITIVE in credentialBlob + KeyHandle handle `gotpm:"handle,auth"` + // the credential + CredentialBlob TPM2BIDObject + // keyHandle algorithm-dependent encrypted seed that protects credentialBlob + Secret TPM2BEncryptedSecret +} + +// Command implements the Command interface. +func (ActivateCredential) Command() TPMCC { return TPMCCActivateCredential } + +// Execute executes the command and returns the response. +func (cmd ActivateCredential) Execute(t transport.TPM, s ...Session) (*ActivateCredentialResponse, error) { + var rsp ActivateCredentialResponse + if err := execute[ActivateCredentialResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// ActivateCredentialResponse is the response from TPM2_ActivateCredential. +type ActivateCredentialResponse struct { + // the decrypted certificate information + CertInfo TPM2BDigest +} + +// MakeCredential is the input to TPM2_MakeCredential. +// See definition in Part 3, Commands, section 12.6. +type MakeCredential struct { + // loaded public area, used to encrypt the sensitive area containing the credential key + Handle TPMIDHObject `gotpm:"handle"` + // the credential information + Credential TPM2BDigest + // Name of the object to which the credential applies + ObjectName TPM2BName +} + +// Command implements the Command interface. +func (MakeCredential) Command() TPMCC { return TPMCCMakeCredential } + +// Execute executes the command and returns the response. +func (cmd MakeCredential) Execute(t transport.TPM, s ...Session) (*MakeCredentialResponse, error) { + var rsp MakeCredentialResponse + if err := execute[MakeCredentialResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// MakeCredentialResponse is the response from TPM2_MakeCredential. +type MakeCredentialResponse struct { + // the credential + CredentialBlob TPM2BIDObject + // handle algorithm-dependent data that wraps the key that encrypts credentialBlob + Secret TPM2BEncryptedSecret +} + +// Unseal is the input to TPM2_Unseal. +// See definition in Part 3, Commands, section 12.7 +type Unseal struct { + ItemHandle handle `gotpm:"handle,auth"` +} + +// Command implements the Command interface. +func (Unseal) Command() TPMCC { return TPMCCUnseal } + +// Execute executes the command and returns the response. +func (cmd Unseal) Execute(t transport.TPM, s ...Session) (*UnsealResponse, error) { + var rsp UnsealResponse + if err := execute[UnsealResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// UnsealResponse is the response from TPM2_Unseal. +type UnsealResponse struct { + OutData TPM2BSensitiveData +} + +// ObjectChangeAuth is the input to TPM2_ObjectChangeAuth. +// See definition in Part 3, Commands, section 12.8 +type ObjectChangeAuth struct { + // TPM handle of an object + ObjectHandle handle `gotpm:"handle,auth"` + // handle of the parent + ParentHandle handle `gotpm:"handle"` + // new authorization value + NewAuth TPM2BAuth +} + +// Command implements the Command interface. +func (ObjectChangeAuth) Command() TPMCC { return TPMCCObjectChangeAuth } + +// Execute executes the command and returns the response. +func (cmd ObjectChangeAuth) Execute(t transport.TPM, s ...Session) (*ObjectChangeAuthResponse, error) { + var rsp ObjectChangeAuthResponse + if err := execute[ObjectChangeAuthResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// ObjectChangeAuthResponse the response from TPM2_ObjectChangeAuth. +type ObjectChangeAuthResponse struct { + // private area containing the new authorization value + OutPrivate TPM2BPrivate +} + +// CreateLoaded is the input to TPM2_CreateLoaded. +// See definition in Part 3, Commands, section 12.9 +type CreateLoaded struct { + // Handle of a transient storage key, a persistent storage key, + // TPM_RH_ENDORSEMENT, TPM_RH_OWNER, TPM_RH_PLATFORM+{PP}, or TPM_RH_NULL + ParentHandle handle `gotpm:"handle,auth"` + // the sensitive data, see TPM 2.0 Part 1 Sensitive Values + InSensitive TPM2BSensitiveCreate + // the public template + InPublic TPM2BTemplate +} + +// Command implements the Command interface. +func (CreateLoaded) Command() TPMCC { return TPMCCCreateLoaded } + +// Execute executes the command and returns the response. +func (cmd CreateLoaded) Execute(t transport.TPM, s ...Session) (*CreateLoadedResponse, error) { + var rsp CreateLoadedResponse + if err := execute[CreateLoadedResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// CreateLoadedResponse is the response from TPM2_CreateLoaded. +type CreateLoadedResponse struct { + // handle of type TPM_HT_TRANSIENT for loaded object + ObjectHandle TPMHandle `gotpm:"handle"` + // the sensitive area of the object (optional) + OutPrivate TPM2BPrivate `gotpm:"optional"` + // the public portion of the created object + OutPublic TPM2BPublic + // the name of the created object + Name TPM2BName +} + +// EncryptDecrypt2 is the input to TPM2_EncryptDecrypt2 +type EncryptDecrypt2 struct { + // reference to public portion of symmetric key to use for encryption + KeyHandle handle `gotpm:"handle,auth"` + Message TPM2BMaxBuffer + Decrypt TPMIYesNo + Mode TPMIAlgSymMode `gotpm:"nullable"` + IV TPM2BIV +} + +// Command implements the Command interface. +func (EncryptDecrypt2) Command() TPMCC { return TPMCCEncryptDecrypt2 } + +// Execute executes the command and returns the response. +func (cmd EncryptDecrypt2) Execute(t transport.TPM, s ...Session) (*EncryptDecrypt2Response, error) { + var rsp EncryptDecrypt2Response + err := execute[EncryptDecrypt2Response](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// EncryptDecrypt2Response is the response from TPM2_EncryptDecrypt2. +type EncryptDecrypt2Response struct { + OutData TPM2BMaxBuffer + IV TPM2BIV +} + +// RSAEncrypt is the input to TPM2_RSA_Encrypt +// See definition in Part 3, Commands, section 14.2. +type RSAEncrypt struct { + // reference to public portion of RSA key to use for encryption + KeyHandle handle `gotpm:"handle"` + // message to be encrypted + Message TPM2BPublicKeyRSA + // the padding scheme to use if scheme associated with keyHandle is TPM_ALG_NULL + InScheme TPMTRSADecrypt `gotpm:"nullable"` + // optional label L to be associated with the message + Label TPM2BData `gotpm:"optional"` +} + +// Command implements the Command interface. +func (RSAEncrypt) Command() TPMCC { return TPMCCRSAEncrypt } + +// Execute executes the command and returns the response. +func (cmd RSAEncrypt) Execute(t transport.TPM, s ...Session) (*RSAEncryptResponse, error) { + var rsp RSAEncryptResponse + if err := execute[RSAEncryptResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// RSAEncryptResponse is the response from TPM2_RSA_Encrypt +type RSAEncryptResponse struct { + // encrypted output + OutData TPM2BPublicKeyRSA +} + +// RSADecrypt is the input to TPM2_RSA_Decrypt +// See definition in Part 3, Commands, section 14.3. +type RSADecrypt struct { + // RSA key to use for decryption + KeyHandle handle `gotpm:"handle,auth"` + // cipher text to be decrypted + CipherText TPM2BPublicKeyRSA + // the padding scheme to use if scheme associated with keyHandle is TPM_ALG_NULL + InScheme TPMTRSADecrypt `gotpm:"nullable"` + // label whose association with the message is to be verified + Label TPM2BData `gotpm:"optional"` +} + +// Command implements the Command interface. +func (RSADecrypt) Command() TPMCC { return TPMCCRSADecrypt } + +// Execute executes the command and returns the response. +func (cmd RSADecrypt) Execute(t transport.TPM, s ...Session) (*RSADecryptResponse, error) { + var rsp RSADecryptResponse + if err := execute[RSADecryptResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// RSADecryptResponse is the response from TPM2_RSA_Decrypt +type RSADecryptResponse struct { + // decrypted output + Message TPM2BPublicKeyRSA +} + +// ECDHZGen is the input to TPM2_ECDHZGen. +// See definition in Part 3, Commands, section 14.5 +type ECDHZGen struct { + // handle of a loaded ECC key + KeyHandle handle `gotpm:"handle,auth"` + // a public key + InPoint TPM2BECCPoint +} + +// Command implements the Command interface. +func (ECDHZGen) Command() TPMCC { return TPMCCECDHZGen } + +// Execute executes the command and returns the response. +func (cmd ECDHZGen) Execute(t transport.TPM, s ...Session) (*ECDHZGenResponse, error) { + var rsp ECDHZGenResponse + if err := execute[ECDHZGenResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// ECDHZGenResponse is the response from TPM2_ECDHZGen. +type ECDHZGenResponse struct { + // X and Y coordinates of the product of the multiplication + OutPoint TPM2BECCPoint +} + +// Hash is the input to TPM2_Hash. +// See definition in Part 3, Commands, section 15.4 +type Hash struct { + //data to be hashed + Data TPM2BMaxBuffer + // algorithm for the hash being computed - shall not be TPM_ALH_NULL + HashAlg TPMIAlgHash + // hierarchy to use for the ticket (TPM_RH_NULL_allowed) + Hierarchy TPMIRHHierarchy `gotpm:"nullable"` +} + +// Command implements the Command interface. +func (Hash) Command() TPMCC { return TPMCCHash } + +// Execute executes the command and returns the response. +func (cmd Hash) Execute(t transport.TPM, s ...Session) (*HashResponse, error) { + var rsp HashResponse + if err := execute[HashResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// HashResponse is the response from TPM2_Hash. +type HashResponse struct { + // results + OutHash TPM2BDigest + // ticket indicating that the sequence of octets used to + // compute outDigest did not start with TPM_GENERATED_VALUE + Validation TPMTTKHashCheck +} + +// Hmac is the input to TPM2_HMAC. +// See definition in Part 3, Commands, section 15.5. +type Hmac struct { + // HMAC key handle requiring an authorization session for the USER role + Handle AuthHandle `gotpm:"handle,auth"` + // HMAC data + Buffer TPM2BMaxBuffer + // Algorithm to use for HMAC + HashAlg TPMIAlgHash +} + +// Command implements the Command interface. +func (Hmac) Command() TPMCC { return TPMCCHMAC } + +// Execute executes the command and returns the response. +func (cmd Hmac) Execute(t transport.TPM, s ...Session) (*HmacResponse, error) { + var rsp HmacResponse + if err := execute[HmacResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// HmacResponse is the response from TPM2_HMAC. +type HmacResponse struct { + // the returned HMAC in a sized buffer + OutHMAC TPM2BDigest +} + +// GetRandom is the input to TPM2_GetRandom. +// See definition in Part 3, Commands, section 16.1 +type GetRandom struct { + // number of octets to return + BytesRequested uint16 +} + +// Command implements the Command interface. +func (GetRandom) Command() TPMCC { return TPMCCGetRandom } + +// Execute executes the command and returns the response. +func (cmd GetRandom) Execute(t transport.TPM, s ...Session) (*GetRandomResponse, error) { + var rsp GetRandomResponse + if err := execute[GetRandomResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// GetRandomResponse is the response from TPM2_GetRandom. +type GetRandomResponse struct { + // the random octets + RandomBytes TPM2BDigest +} + +// HashSequenceStart is the input to TPM2_HashSequenceStart. +// See definition in Part 3, Commands, section 17.3 +type HashSequenceStart struct { + // authorization value for subsequent use of the sequence + Auth TPM2BAuth + // the hash algorithm to use for the hash sequence + // An Event Sequence starts if this is TPM_ALG_NULL. + HashAlg TPMIAlgHash +} + +// Command implements the Command interface. +func (HashSequenceStart) Command() TPMCC { return TPMCCHashSequenceStart } + +// Execute executes the command and returns the response. +func (cmd HashSequenceStart) Execute(t transport.TPM, s ...Session) (*HashSequenceStartResponse, error) { + var rsp HashSequenceStartResponse + if err := execute[HashSequenceStartResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// HashSequenceStartResponse is the response from TPM2_StartHashSequence. +type HashSequenceStartResponse struct { + // a handle to reference the sequence + SequenceHandle TPMIDHObject +} + +// HmacStart is the input to TPM2_HMAC_Start. +// See definition in Part 3, Commands, section 17.2.2 +type HmacStart struct { + // HMAC key handle + Handle handle `gotpm:"handle,auth"` + // authorization value for subsequent use of the sequence + Auth TPM2BAuth + // the hash algorithm to use for the hmac sequence + HashAlg TPMIAlgHash `gotpm:"nullable"` +} + +// Command implements the Command interface. +func (HmacStart) Command() TPMCC { return TPMCCHMACStart } + +// Execute executes the command and returns the response. +func (cmd HmacStart) Execute(t transport.TPM, s ...Session) (*HmacStartResponse, error) { + var rsp HmacStartResponse + if err := execute[HmacStartResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// HmacStartResponse is the response from TPM2_HMAC_Start. +// See definition in Part 3, Commands, section 17.2.2 +type HmacStartResponse struct { + // a handle to reference the sequence + SequenceHandle TPMIDHObject `gotpm:"handle"` +} + +// SequenceUpdate is the input to TPM2_SequenceUpdate. +// See definition in Part 3, Commands, section 17.4 +type SequenceUpdate struct { + // handle for the sequence object + SequenceHandle handle `gotpm:"handle,auth,anon"` + // data to be added to hash + Buffer TPM2BMaxBuffer +} + +// Command implements the Command interface. +func (SequenceUpdate) Command() TPMCC { return TPMCCSequenceUpdate } + +// Execute executes the command and returns the response. +func (cmd SequenceUpdate) Execute(t transport.TPM, s ...Session) (*SequenceUpdateResponse, error) { + var rsp SequenceUpdateResponse + if err := execute[SequenceUpdateResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// SequenceUpdateResponse is the response from TPM2_SequenceUpdate. +type SequenceUpdateResponse struct{} + +// SequenceComplete is the input to TPM2_SequenceComplete. +// See definition in Part 3, Commands, section 17.5 +type SequenceComplete struct { + // authorization for the sequence + SequenceHandle handle `gotpm:"handle,auth,anon"` + // data to be added to the hash/HMAC + Buffer TPM2BMaxBuffer + // hierarchy of the ticket for a hash + Hierarchy TPMIRHHierarchy `gotpm:"nullable"` +} + +// Command implements the Command interface. +func (SequenceComplete) Command() TPMCC { return TPMCCSequenceComplete } + +// Execute executes the command and returns the response. +func (cmd SequenceComplete) Execute(t transport.TPM, s ...Session) (*SequenceCompleteResponse, error) { + var rsp SequenceCompleteResponse + if err := execute[SequenceCompleteResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// SequenceCompleteResponse is the response from TPM2_SequenceComplete. +type SequenceCompleteResponse struct { + // the returned HMAC or digest in a sized buffer + Result TPM2BDigest + // ticket indicating that the sequence of octets used to + // compute outDigest did not start with TPM_GENERATED_VALUE + Validation TPMTTKHashCheck +} + +// Certify is the input to TPM2_Certify. +// See definition in Part 3, Commands, section 18.2. +type Certify struct { + // handle of the object to be certified + ObjectHandle handle `gotpm:"handle,auth"` + // handle of the key used to sign the attestation structure + SignHandle handle `gotpm:"handle,auth"` + // user provided qualifying data + QualifyingData TPM2BData + // signing scheme to use if the scheme for signHandle is TPM_ALG_NULL + InScheme TPMTSigScheme +} + +// Command implements the Command interface. +func (Certify) Command() TPMCC { return TPMCCCertify } + +// Execute executes the command and returns the response. +func (cmd Certify) Execute(t transport.TPM, s ...Session) (*CertifyResponse, error) { + var rsp CertifyResponse + if err := execute[CertifyResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// CertifyResponse is the response from TPM2_Certify. +type CertifyResponse struct { + // the structure that was signed + CertifyInfo TPM2BAttest + // the asymmetric signature over certifyInfo using the key referenced by signHandle + Signature TPMTSignature +} + +// CertifyCreation is the input to TPM2_CertifyCreation. +// See definition in Part 3, Commands, section 18.3. +type CertifyCreation struct { + // handle of the key that will sign the attestation block + SignHandle handle `gotpm:"handle,auth"` + // the object associated with the creation data + ObjectHandle handle `gotpm:"handle"` + // user-provided qualifying data + QualifyingData TPM2BData + // hash of the creation data produced by TPM2_Create() or TPM2_CreatePrimary() + CreationHash TPM2BDigest + // signing scheme to use if the scheme for signHandle is TPM_ALG_NULL + InScheme TPMTSigScheme + // ticket produced by TPM2_Create() or TPM2_CreatePrimary() + CreationTicket TPMTTKCreation +} + +// Command implements the Command interface. +func (CertifyCreation) Command() TPMCC { return TPMCCCertifyCreation } + +// Execute executes the command and returns the response. +func (cmd CertifyCreation) Execute(t transport.TPM, s ...Session) (*CertifyCreationResponse, error) { + var rsp CertifyCreationResponse + if err := execute[CertifyCreationResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// CertifyCreationResponse is the response from TPM2_CertifyCreation. +type CertifyCreationResponse struct { + // the structure that was signed + CertifyInfo TPM2BAttest + // the signature over certifyInfo + Signature TPMTSignature +} + +// Quote is the input to TPM2_Quote. +// See definition in Part 3, Commands, section 18.4 +type Quote struct { + // handle of key that will perform signature + SignHandle handle `gotpm:"handle,auth"` + // data supplied by the caller + QualifyingData TPM2BData + // signing scheme to use if the scheme for signHandle is TPM_ALG_NULL + InScheme TPMTSigScheme + // PCR set to quote + PCRSelect TPMLPCRSelection +} + +// Command implements the Command interface. +func (Quote) Command() TPMCC { return TPMCCQuote } + +// Execute executes the command and returns the response. +func (cmd Quote) Execute(t transport.TPM, s ...Session) (*QuoteResponse, error) { + var rsp QuoteResponse + if err := execute[QuoteResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// QuoteResponse is the response from TPM2_Quote. +type QuoteResponse struct { + // the quoted information + Quoted TPM2BAttest + // the signature over quoted + Signature TPMTSignature +} + +// GetSessionAuditDigest is the input to TPM2_GetSessionAuditDigest. +// See definition in Part 3, Commands, section 18.5 +type GetSessionAuditDigest struct { + // handle of the privacy administrator (TPM_RH_ENDORSEMENT) + PrivacyAdminHandle handle `gotpm:"handle,auth"` + // handle of the signing key + SignHandle handle `gotpm:"handle,auth"` + // handle of the audit session + SessionHandle handle `gotpm:"handle"` + // user-provided qualifying data – may be zero-length + QualifyingData TPM2BData + // signing scheme to use if the scheme for signHandle is TPM_ALG_NULL + InScheme TPMTSigScheme +} + +// Command implements the Command interface. +func (GetSessionAuditDigest) Command() TPMCC { return TPMCCGetSessionAuditDigest } + +// Execute executes the command and returns the response. +func (cmd GetSessionAuditDigest) Execute(t transport.TPM, s ...Session) (*GetSessionAuditDigestResponse, error) { + var rsp GetSessionAuditDigestResponse + if err := execute[GetSessionAuditDigestResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// GetSessionAuditDigestResponse is the response from +// TPM2_GetSessionAuditDigest. +type GetSessionAuditDigestResponse struct { + // the audit information that was signed + AuditInfo TPM2BAttest + // the signature over auditInfo + Signature TPMTSignature +} + +// Commit is the input to TPM2_Commit. +// See definition in Part 3, Commands, section 19.2. +type Commit struct { + // handle of the key that will be used in the signing operation + SignHandle handle `gotpm:"handle,auth"` + // a point (M) on the curve used by signHandle + P1 TPM2BECCPoint + // octet array used to derive x-coordinate of a base point + S2 TPM2BSensitiveData + // y coordinate of the point associated with s2 + Y2 TPM2BECCParameter +} + +// Command implements the Command interface. +func (Commit) Command() TPMCC { return TPMCCCommit } + +// Execute executes the command and returns the response. +func (cmd Commit) Execute(t transport.TPM, s ...Session) (*CommitResponse, error) { + var rsp CommitResponse + if err := execute[CommitResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + + return &rsp, nil +} + +// CommitResponse is the response from TPM2_Commit. +type CommitResponse struct { + // ECC point K ≔ [ds](x2, y2) + K TPM2BECCPoint + // ECC point L ≔ [r](x2, y2) + L TPM2BECCPoint + // ECC point E ≔ [r]P1 + E TPM2BECCPoint + // least-significant 16 bits of commitCount + Counter uint16 +} + +// VerifySignature is the input to TPM2_VerifySignature. +// See definition in Part 3, Commands, section 20.1 +type VerifySignature struct { + // handle of public key that will be used in the validation + KeyHandle handle `gotpm:"handle"` + // digest of the signed message + Digest TPM2BDigest + // signature to be tested + Signature TPMTSignature +} + +// Command implements the Command interface. +func (VerifySignature) Command() TPMCC { return TPMCCVerifySignature } + +// Execute executes the command and returns the response. +func (cmd VerifySignature) Execute(t transport.TPM, s ...Session) (*VerifySignatureResponse, error) { + var rsp VerifySignatureResponse + if err := execute[VerifySignatureResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// VerifySignatureResponse is the response from TPM2_VerifySignature. +type VerifySignatureResponse struct { + Validation TPMTTKVerified +} + +// Sign is the input to TPM2_Sign. +// See definition in Part 3, Commands, section 20.2. +type Sign struct { + // Handle of key that will perform signing + KeyHandle handle `gotpm:"handle,auth"` + // digest to be signed + Digest TPM2BDigest + // signing scheme to use if the scheme for keyHandle is TPM_ALG_NULL + InScheme TPMTSigScheme `gotpm:"nullable"` + // proof that digest was created by the TPM. + // If keyHandle is not a restricted signing key, then this + // may be a NULL Ticket with tag = TPM_ST_CHECKHASH. + Validation TPMTTKHashCheck +} + +// Command implements the Command interface. +func (Sign) Command() TPMCC { return TPMCCSign } + +// Execute executes the command and returns the response. +func (cmd Sign) Execute(t transport.TPM, s ...Session) (*SignResponse, error) { + var rsp SignResponse + if err := execute[SignResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// SignResponse is the response from TPM2_Sign. +type SignResponse struct { + // the signature + Signature TPMTSignature +} + +// PCRExtend is the input to TPM2_PCR_Extend. +// See definition in Part 3, Commands, section 22.2 +type PCRExtend struct { + // handle of the PCR + PCRHandle handle `gotpm:"handle,auth"` + // list of tagged digest values to be extended + Digests TPMLDigestValues +} + +// Command implements the Command interface. +func (PCRExtend) Command() TPMCC { return TPMCCPCRExtend } + +// Execute executes the command and returns the response. +func (cmd PCRExtend) Execute(t transport.TPM, s ...Session) (*PCRExtendResponse, error) { + var rsp PCRExtendResponse + err := execute[PCRExtendResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// PCRExtendResponse is the response from TPM2_PCR_Extend. +type PCRExtendResponse struct{} + +// PCREvent is the input to TPM2_PCR_Event. +// See definition in Part 3, Commands, section 22.3 +type PCREvent struct { + // Handle of the PCR + PCRHandle handle `gotpm:"handle,auth"` + // Event data in sized buffer + EventData TPM2BEvent +} + +// Command implements the Command interface. +func (PCREvent) Command() TPMCC { return TPMCCPCREvent } + +// Execute executes the command and returns the response. +func (cmd PCREvent) Execute(t transport.TPM, s ...Session) (*PCREventResponse, error) { + var rsp PCREventResponse + err := execute[PCREventResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// PCREventResponse is the response from TPM2_PCR_Event. +type PCREventResponse struct{} + +// PCRRead is the input to TPM2_PCR_Read. +// See definition in Part 3, Commands, section 22.4 +type PCRRead struct { + // The selection of PCR to read + PCRSelectionIn TPMLPCRSelection +} + +// Command implements the Command interface. +func (PCRRead) Command() TPMCC { return TPMCCPCRRead } + +// Execute executes the command and returns the response. +func (cmd PCRRead) Execute(t transport.TPM, s ...Session) (*PCRReadResponse, error) { + var rsp PCRReadResponse + if err := execute[PCRReadResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// PCRReadResponse is the response from TPM2_PCR_Read. +type PCRReadResponse struct { + // the current value of the PCR update counter + PCRUpdateCounter uint32 + // the PCR in the returned list + PCRSelectionOut TPMLPCRSelection + // the contents of the PCR indicated in pcrSelectOut-> pcrSelection[] as tagged digests + PCRValues TPMLDigest +} + +// PCRReset is the input to TPM2_PCRReset. +// See definition in Part 3, Commands, section 22.8. +type PCRReset struct { + // the PCR to reset + PCRHandle handle `gotpm:"handle,auth"` +} + +// Command implements the Command interface. +func (PCRReset) Command() TPMCC { return TPMCCPCRReset } + +// Execute executes the command and returns the response. +func (cmd PCRReset) Execute(t transport.TPM, s ...Session) (*PCRResetResponse, error) { + var rsp PCRResetResponse + if err := execute[PCRResetResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// PCRResetResponse is the response from TPM2_PCRReset. +type PCRResetResponse struct{} + +// PolicySigned is the input to TPM2_PolicySigned. +// See definition in Part 3, Commands, section 23.3. +type PolicySigned struct { + // handle for an entity providing the authorization + AuthObject handle `gotpm:"handle"` + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // the policy nonce for the session + NonceTPM TPM2BNonce + // digest of the command parameters to which this authorization is limited + CPHashA TPM2BDigest + // a reference to a policy relating to the authorization – may be the Empty Buffer + PolicyRef TPM2BNonce + // time when authorization will expire, measured in seconds from the time + // that nonceTPM was generated + Expiration int32 + // signed authorization (not optional) + Auth TPMTSignature +} + +// Command implements the Command interface. +func (PolicySigned) Command() TPMCC { return TPMCCPolicySigned } + +// Execute executes the command and returns the response. +func (cmd PolicySigned) Execute(t transport.TPM, s ...Session) (*PolicySignedResponse, error) { + var rsp PolicySignedResponse + if err := execute[PolicySignedResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// policyUpdate implements the PolicyUpdate helper for the several TPM policy +// commands as described in Part 3, 23.2.3. +func policyUpdate(policy *PolicyCalculator, cc TPMCC, arg2, arg3 []byte) error { + if err := policy.Update(cc, arg2); err != nil { + return err + } + return policy.Update(arg3) +} + +// Update implements the PolicyCommand interface. +func (cmd PolicySigned) Update(policy *PolicyCalculator) error { + return policyUpdate(policy, TPMCCPolicySigned, cmd.AuthObject.KnownName().Buffer, cmd.PolicyRef.Buffer) +} + +// PolicySignedResponse is the response from TPM2_PolicySigned. +type PolicySignedResponse struct { + // implementation-specific time value used to indicate to the TPM when the ticket expires + Timeout TPM2BTimeout + // produced if the command succeeds and expiration in the command was non-zero + PolicyTicket TPMTTKAuth +} + +// PolicySecret is the input to TPM2_PolicySecret. +// See definition in Part 3, Commands, section 23.4. +type PolicySecret struct { + // handle for an entity providing the authorization + AuthHandle handle `gotpm:"handle,auth"` + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // the policy nonce for the session + NonceTPM TPM2BNonce + // digest of the command parameters to which this authorization is limited + CPHashA TPM2BDigest + // a reference to a policy relating to the authorization – may be the Empty Buffer + PolicyRef TPM2BNonce + // time when authorization will expire, measured in seconds from the time + // that nonceTPM was generated + Expiration int32 +} + +// Command implements the Command interface. +func (PolicySecret) Command() TPMCC { return TPMCCPolicySecret } + +// Execute executes the command and returns the response. +func (cmd PolicySecret) Execute(t transport.TPM, s ...Session) (*PolicySecretResponse, error) { + var rsp PolicySecretResponse + if err := execute[PolicySecretResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicySecret) Update(policy *PolicyCalculator) error { + return policyUpdate(policy, TPMCCPolicySecret, cmd.AuthHandle.KnownName().Buffer, cmd.PolicyRef.Buffer) +} + +// PolicySecretResponse is the response from TPM2_PolicySecret. +type PolicySecretResponse struct { + // implementation-specific time value used to indicate to the TPM when the ticket expires + Timeout TPM2BTimeout + // produced if the command succeeds and expiration in the command was non-zero + PolicyTicket TPMTTKAuth +} + +// PolicyOr is the input to TPM2_PolicyOR. +// See definition in Part 3, Commands, section 23.6. +type PolicyOr struct { + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // the list of hashes to check for a match + PHashList TPMLDigest +} + +// Command implements the Command interface. +func (PolicyOr) Command() TPMCC { return TPMCCPolicyOR } + +// Execute executes the command and returns the response. +func (cmd PolicyOr) Execute(t transport.TPM, s ...Session) (*PolicyOrResponse, error) { + var rsp PolicyOrResponse + err := execute[PolicyOrResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicyOr) Update(policy *PolicyCalculator) error { + policy.Reset() + var digests bytes.Buffer + for _, digest := range cmd.PHashList.Digests { + digests.Write(digest.Buffer) + } + return policy.Update(TPMCCPolicyOR, digests.Bytes()) +} + +// PolicyOrResponse is the response from TPM2_PolicyOr. +type PolicyOrResponse struct{} + +// PolicyPCR is the input to TPM2_PolicyPCR. +// See definition in Part 3, Commands, section 23.7. +type PolicyPCR struct { + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // expected digest value of the selected PCR using the + // hash algorithm of the session; may be zero length + PcrDigest TPM2BDigest + // the PCR to include in the check digest + Pcrs TPMLPCRSelection +} + +// Command implements the Command interface. +func (PolicyPCR) Command() TPMCC { return TPMCCPolicyPCR } + +// Execute executes the command and returns the response. +func (cmd PolicyPCR) Execute(t transport.TPM, s ...Session) (*PolicyPCRResponse, error) { + var rsp PolicyPCRResponse + err := execute[PolicyPCRResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicyPCR) Update(policy *PolicyCalculator) error { + return policy.Update(TPMCCPolicyPCR, cmd.Pcrs, cmd.PcrDigest.Buffer) +} + +// PolicyPCRResponse is the response from TPM2_PolicyPCR. +type PolicyPCRResponse struct{} + +// PolicyAuthValue is the input to TPM2_PolicyAuthValue. +// See definition in Part 3, Commands, section 23.17. +type PolicyAuthValue struct { + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (PolicyAuthValue) Command() TPMCC { return TPMCCPolicyAuthValue } + +// Execute executes the command and returns the response. +func (cmd PolicyAuthValue) Execute(t transport.TPM, s ...Session) (*PolicyAuthValueResponse, error) { + var rsp PolicyAuthValueResponse + err := execute[PolicyAuthValueResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyAuthValue interface. +func (cmd PolicyAuthValue) Update(policy *PolicyCalculator) error { + return policy.Update(TPMCCPolicyAuthValue) +} + +// PolicyAuthValueResponse is the response from TPM2_PolicyAuthValue. +type PolicyAuthValueResponse struct{} + +// PolicyDuplicationSelect is the input to TPM2_PolicyDuplicationSelect. +// See definition in Part 3, Commands, section 23.15. +type PolicyDuplicationSelect struct { + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + ObjectName TPM2BName + NewParentName TPM2BName + IncludeObject TPMIYesNo +} + +// Command implements the Command interface. +func (PolicyDuplicationSelect) Command() TPMCC { return TPMCCPolicyDuplicationSelect } + +// Execute executes the command and returns the response. +func (cmd PolicyDuplicationSelect) Execute(t transport.TPM, s ...Session) (*PolicyDuplicationSelectResponse, error) { + var rsp PolicyDuplicationSelectResponse + err := execute[PolicyDuplicationSelectResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyDuplicationSelect interface. +func (cmd PolicyDuplicationSelect) Update(policy *PolicyCalculator) error { + if cmd.IncludeObject { + return policy.Update(TPMCCPolicyDuplicationSelect, cmd.ObjectName.Buffer, cmd.NewParentName.Buffer, cmd.IncludeObject) + } + return policy.Update(TPMCCPolicyDuplicationSelect, cmd.NewParentName.Buffer, cmd.IncludeObject) +} + +// PolicyDuplicationSelectResponse is the response from TPM2_PolicyDuplicationSelect. +type PolicyDuplicationSelectResponse struct{} + +// PolicyNV is the input to TPM2_PolicyNV. +// See definition in Part 3, Commands, section 23.9. +type PolicyNV struct { + // handle indicating the source of the authorization value + AuthHandle handle `gotpm:"handle,auth"` + // the NV Index of the area to read + NVIndex handle `gotpm:"handle"` + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // the second operand + OperandB TPM2BOperand + // the octet offset in the NV Index for the start of operand A + Offset uint16 + // the comparison to make + Operation TPMEO +} + +// Command implements the Command interface. +func (PolicyNV) Command() TPMCC { return TPMCCPolicyNV } + +// Execute executes the command and returns the response. +func (cmd PolicyNV) Execute(t transport.TPM, s ...Session) (*PolicyNVResponse, error) { + var rsp PolicyNVResponse + err := execute[PolicyNVResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicyNV) Update(policy *PolicyCalculator) error { + alg, err := policy.alg.Hash() + if err != nil { + return err + } + h := alg.New() + h.Write(cmd.OperandB.Buffer) + binary.Write(h, binary.BigEndian, cmd.Offset) + binary.Write(h, binary.BigEndian, cmd.Operation) + args := h.Sum(nil) + return policy.Update(TPMCCPolicyNV, args, cmd.NVIndex.KnownName().Buffer) +} + +// PolicyNVResponse is the response from TPM2_PolicyPCR. +type PolicyNVResponse struct{} + +// PolicyCommandCode is the input to TPM2_PolicyCommandCode. +// See definition in Part 3, Commands, section 23.11. +type PolicyCommandCode struct { + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // the allowed commandCode + Code TPMCC +} + +// Command implements the Command interface. +func (PolicyCommandCode) Command() TPMCC { return TPMCCPolicyCommandCode } + +// Execute executes the command and returns the response. +func (cmd PolicyCommandCode) Execute(t transport.TPM, s ...Session) (*PolicyCommandCodeResponse, error) { + var rsp PolicyCommandCodeResponse + err := execute[PolicyCommandCodeResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicyCommandCode) Update(policy *PolicyCalculator) error { + return policy.Update(TPMCCPolicyCommandCode, cmd.Code) +} + +// PolicyCommandCodeResponse is the response from TPM2_PolicyCommandCode. +type PolicyCommandCodeResponse struct{} + +// PolicyCPHash is the input to TPM2_PolicyCpHash. +// See definition in Part 3, Commands, section 23.13. +type PolicyCPHash struct { + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // the cpHash added to the policy + CPHashA TPM2BDigest +} + +// Command implements the Command interface. +func (PolicyCPHash) Command() TPMCC { return TPMCCPolicyCpHash } + +// Execute executes the command and returns the response. +func (cmd PolicyCPHash) Execute(t transport.TPM, s ...Session) (*PolicyCPHashResponse, error) { + var rsp PolicyCPHashResponse + err := execute[PolicyCPHashResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicyCPHash) Update(policy *PolicyCalculator) error { + return policy.Update(TPMCCPolicyCpHash, cmd.CPHashA.Buffer) +} + +// PolicyCPHashResponse is the response from TPM2_PolicyCpHash. +type PolicyCPHashResponse struct{} + +// PolicyAuthorize is the input to TPM2_PolicySigned. +// See definition in Part 3, Commands, section 23.16. +type PolicyAuthorize struct { + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // digest of the policy being approved + ApprovedPolicy TPM2BDigest + // a policy qualifier + PolicyRef TPM2BDigest + // Name of a key that can sign a policy addition + KeySign TPM2BName + // ticket validating that approvedPolicy and policyRef were signed by keySign + CheckTicket TPMTTKVerified +} + +// Command implements the Command interface. +func (PolicyAuthorize) Command() TPMCC { return TPMCCPolicyAuthorize } + +// Execute executes the command and returns the response. +func (cmd PolicyAuthorize) Execute(t transport.TPM, s ...Session) (*PolicyAuthorizeResponse, error) { + var rsp PolicyAuthorizeResponse + err := execute[PolicyAuthorizeResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicyAuthorize) Update(policy *PolicyCalculator) error { + return policyUpdate(policy, TPMCCPolicyAuthorize, cmd.KeySign.Buffer, cmd.PolicyRef.Buffer) +} + +// PolicyAuthorizeResponse is the response from TPM2_PolicyAuthorize. +type PolicyAuthorizeResponse struct{} + +// PolicyGetDigest is the input to TPM2_PolicyGetDigest. +// See definition in Part 3, Commands, section 23.19. +type PolicyGetDigest struct { + // handle for the policy session + PolicySession handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (PolicyGetDigest) Command() TPMCC { return TPMCCPolicyGetDigest } + +// Execute executes the command and returns the response. +func (cmd PolicyGetDigest) Execute(t transport.TPM, s ...Session) (*PolicyGetDigestResponse, error) { + var rsp PolicyGetDigestResponse + if err := execute[PolicyGetDigestResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// PolicyGetDigestResponse is the response from TPM2_PolicyGetDigest. +type PolicyGetDigestResponse struct { + // the current value of the policySession→policyDigest + PolicyDigest TPM2BDigest +} + +// PolicyNVWritten is the input to TPM2_PolicyNvWritten. +// See definition in Part 3, Commands, section 23.20. +type PolicyNVWritten struct { + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` + // YES if NV Index is required to have been written + // NO if NV Index is required not to have been written + WrittenSet TPMIYesNo +} + +// Command implements the Command interface. +func (PolicyNVWritten) Command() TPMCC { return TPMCCPolicyNvWritten } + +// Execute executes the command and returns the response. +func (cmd PolicyNVWritten) Execute(t transport.TPM, s ...Session) (*PolicyNVWrittenResponse, error) { + var rsp PolicyNVWrittenResponse + if err := execute[PolicyNVWrittenResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicyNVWritten) Update(policy *PolicyCalculator) error { + return policy.Update(TPMCCPolicyNvWritten, cmd.WrittenSet) +} + +// PolicyNVWrittenResponse is the response from TPM2_PolicyNvWritten. +type PolicyNVWrittenResponse struct { +} + +// PolicyAuthorizeNV is the input to TPM2_PolicyAuthorizeNV. +// See definition in Part 3, Commands, section 23.22. +type PolicyAuthorizeNV struct { + // handle indicating the source of the authorization value + AuthHandle handle `gotpm:"handle,auth"` + // the NV Index of the area to read + NVIndex handle `gotpm:"handle"` + // handle for the policy session being extended + PolicySession handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (PolicyAuthorizeNV) Command() TPMCC { return TPMCCPolicyAuthorizeNV } + +// Execute executes the command and returns the response. +func (cmd PolicyAuthorizeNV) Execute(t transport.TPM, s ...Session) (*PolicyAuthorizeNVResponse, error) { + var rsp PolicyAuthorizeNVResponse + err := execute[PolicyAuthorizeNVResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// Update implements the PolicyCommand interface. +func (cmd PolicyAuthorizeNV) Update(policy *PolicyCalculator) error { + policy.Reset() + return policy.Update(TPMCCPolicyAuthorizeNV, cmd.NVIndex.KnownName().Buffer) +} + +// PolicyAuthorizeNVResponse is the response from TPM2_PolicyAuthorizeNV. +type PolicyAuthorizeNVResponse struct{} + +// CreatePrimary is the input to TPM2_CreatePrimary. +// See definition in Part 3, Commands, section 24.1 +type CreatePrimary struct { + // TPM_RH_ENDORSEMENT, TPM_RH_OWNER, TPM_RH_PLATFORM+{PP}, + // TPM_RH_NULL, TPM_RH_FW_ENDORSEMENT, TPM_RH_FW_OWNER + // TPM_RH_FW_PLATFORM+{PP} or TPM_RH_FW_NULL + PrimaryHandle handle `gotpm:"handle,auth"` + // the sensitive data + InSensitive TPM2BSensitiveCreate + // the public template + InPublic TPM2BPublic + // data that will be included in the creation data for this + // object to provide permanent, verifiable linkage between this + // object and some object owner data + OutsideInfo TPM2BData + // PCR that will be used in creation data + CreationPCR TPMLPCRSelection +} + +// Command implements the Command interface. +func (CreatePrimary) Command() TPMCC { return TPMCCCreatePrimary } + +// Execute executes the command and returns the response. +func (cmd CreatePrimary) Execute(t transport.TPM, s ...Session) (*CreatePrimaryResponse, error) { + var rsp CreatePrimaryResponse + if err := execute[CreatePrimaryResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// CreatePrimaryResponse is the response from TPM2_CreatePrimary. +type CreatePrimaryResponse struct { + // handle of type TPM_HT_TRANSIENT for created Primary Object + ObjectHandle TPMHandle `gotpm:"handle"` + // the public portion of the created object + OutPublic TPM2BPublic + // contains a TPMS_CREATION_DATA + CreationData tpm2bCreationData + // digest of creationData using nameAlg of outPublic + CreationHash TPM2BDigest + // ticket used by TPM2_CertifyCreation() to validate that the + // creation data was produced by the TPM. + CreationTicket TPMTTKCreation + // the name of the created object + Name TPM2BName +} + +// Clear is the input to TPM2_Clear. +// See definition in Part 3, Commands, section 24.6 +type Clear struct { + // TPM_RH_LOCKOUT or TPM_RH_PLATFORM+{PP} + AuthHandle handle `gotpm:"handle,auth"` +} + +// Command implements the Command interface. +func (Clear) Command() TPMCC { return TPMCCClear } + +// Execute executes the command and returns the response. +func (cmd Clear) Execute(t transport.TPM, s ...Session) (*ClearResponse, error) { + var rsp ClearResponse + err := execute[ClearResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// ClearResponse is the response from TPM2_Clear. +type ClearResponse struct{} + +// HierarchyChangeAuth is the input to TPM2_HierarchyChangeAuth. +// See definition in Part 3, Commands, section 24.8 +type HierarchyChangeAuth struct { + // TPM_RH_ENDORSEMENT, TPM_RH_LOCKOUT, TPM_RH_OWNER or TPM_RH_PLATFORM+{PP} + AuthHandle handle `gotpm:"handle,auth"` + // new authorization value + NewAuth TPM2BAuth +} + +// Command implements the Command interface. +func (HierarchyChangeAuth) Command() TPMCC { return TPMCCHierarchyChanegAuth } + +// Execute executes the command and returns the response. +func (cmd HierarchyChangeAuth) Execute(t transport.TPM, s ...Session) (*HierarchyChangeAuthResponse, error) { + var rsp HierarchyChangeAuthResponse + if err := execute[HierarchyChangeAuthResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// HierarchyChangeAuthResponse is the response from TPM2_HierarchyChangeAuth. +type HierarchyChangeAuthResponse struct{} + +// ContextSave is the input to TPM2_ContextSave. +// See definition in Part 3, Commands, section 28.2 +type ContextSave struct { + // handle of the resource to save + SaveHandle TPMIDHContext +} + +// Command implements the Command interface. +func (ContextSave) Command() TPMCC { return TPMCCContextSave } + +// Execute executes the command and returns the response. +func (cmd ContextSave) Execute(t transport.TPM, s ...Session) (*ContextSaveResponse, error) { + var rsp ContextSaveResponse + if err := execute[ContextSaveResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// ContextSaveResponse is the response from TPM2_ContextSave. +type ContextSaveResponse struct { + Context TPMSContext +} + +// ContextLoad is the input to TPM2_ContextLoad. +// See definition in Part 3, Commands, section 28.3 +type ContextLoad struct { + // the context blob + Context TPMSContext +} + +// Command implements the Command interface. +func (ContextLoad) Command() TPMCC { return TPMCCContextLoad } + +// Execute executes the command and returns the response. +func (cmd ContextLoad) Execute(t transport.TPM, s ...Session) (*ContextLoadResponse, error) { + var rsp ContextLoadResponse + if err := execute[ContextLoadResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// ContextLoadResponse is the response from TPM2_ContextLoad. +type ContextLoadResponse struct { + // the handle assigned to the resource after it has been successfully loaded + LoadedHandle TPMIDHContext +} + +// FlushContext is the input to TPM2_FlushContext. +// See definition in Part 3, Commands, section 28.4 +type FlushContext struct { + // the handle of the item to flush + FlushHandle handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (FlushContext) Command() TPMCC { return TPMCCFlushContext } + +// Execute executes the command and returns the response. +func (cmd FlushContext) Execute(t transport.TPM, s ...Session) (*FlushContextResponse, error) { + var rsp FlushContextResponse + err := execute[FlushContextResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// FlushContextResponse is the response from TPM2_FlushContext. +type FlushContextResponse struct{} + +// EvictControl is the input to TPM2_EvictControl. +// See definition in Part 3, Commands, section 28.5 +type EvictControl struct { + // TPM_RH_OWNER or TPM_RH_PLATFORM+{PP} + Auth handle `gotpm:"handle,auth"` + ObjectHandle handle `gotpm:"handle"` + PersistentHandle TPMIDHPersistent +} + +// EvictControlResponse is the response from TPM2_EvictControl. +type EvictControlResponse struct{} + +// Command implements the Command interface. +func (EvictControl) Command() TPMCC { return TPMCCEvictControl } + +// Execute executes the command and returns the response. +func (cmd EvictControl) Execute(t transport.TPM, s ...Session) (*EvictControlResponse, error) { + var rsp EvictControlResponse + if err := execute[EvictControlResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// Duplicate is the input to TPM2_Duplicate. +// See definition in Part 3, Commands, section 13.1 +type Duplicate struct { + // ObjectHandle is the handle of the object to dupliate. + ObjectHandle handle `gotpm:"handle,auth"` + + // NewParentHandle is the handle of the new parent. + NewParentHandle handle `gotpm:"handle"` + + // EncryptionKeyIn is the optional symmetric encryption key used as the + // inner wrapper. If SymmetricAlg is TPM_ALG_NULL, then this parameter + // shall be the Empty Buffer. + EncryptionKeyIn TPM2BData + + // Definition of the symmetric algorithm to use for the inner wrapper. + // It may be TPM_ALG_NULL if no inner wrapper is applied. + Symmetric TPMTSymDef +} + +// DuplicateResponse is the response from TPM2_Duplicate. +type DuplicateResponse struct { + // EncryptionKeyOut is the symmetric encryption key used as the + // inner wrapper. If SymmetricAlg is TPM_ALG_NULL, this value + // shall be the Empty Buffer. + EncryptionKeyOut TPM2BData + + // Duplicate is the private area of the object. It may be encrypted by + // EncryptionKeyIn and may be doubly encrypted. + Duplicate TPM2BPrivate + + // OutSymSeed is the seed protected by the asymmetric algorithms of new + // parent. + OutSymSeed TPM2BEncryptedSecret +} + +// Command implements the Command interface. +func (Duplicate) Command() TPMCC { return TPMCCDuplicate } + +// Execute executes the command and returns the response. +func (cmd Duplicate) Execute(t transport.TPM, s ...Session) (*DuplicateResponse, error) { + var rsp DuplicateResponse + if err := execute[DuplicateResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// Import is the input to TPM2_Import. +// See definition in Part 3, Commands, section 13.3 +type Import struct { + // handle of parent for new object + ParentHandle handle `gotpm:"handle,auth"` + + // The optional symmetric encryption key used as the inner wrapper for duplicate + // If SymmetricAlg is TPM_ALG_NULL, then this parametert shall be the Empty Buffer + EncryptionKey TPM2BData + + // The public area of the object to be imported + ObjectPublic TPM2BPublic + + // The symmetrically encrypted duplicate object that may contain an inner + // symmetric wrapper + Duplicate TPM2BPrivate + + // The seed for the symmetric key and HMAC key + InSymSeed TPM2BEncryptedSecret + + // Definition of the symmetric algorithm to use for the inner wrapper + Symmetric TPMTSymDef +} + +// ImportResponse is the response from TPM2_Import. +type ImportResponse struct { + // the private portion of the object + OutPrivate TPM2BPrivate +} + +// Command implements the Command interface. +func (Import) Command() TPMCC { return TPMCCImport } + +// Execute executes the command and returns the response. +func (cmd Import) Execute(t transport.TPM, s ...Session) (*ImportResponse, error) { + var rsp ImportResponse + if err := execute[ImportResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// ReadClock is the input to TPM2_ReadClock. +// See definition in Part 3, Commands, section 29.1 +type ReadClock struct{} + +// Command implements the Command interface. +func (ReadClock) Command() TPMCC { return TPMCCReadClock } + +// Execute executes the command and returns the response. +func (cmd ReadClock) Execute(t transport.TPM, s ...Session) (*ReadClockResponse, error) { + var rsp ReadClockResponse + if err := execute[ReadClockResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// ReadClockResponse is the response from TPM2_ReadClock. +type ReadClockResponse struct { + CurrentTime TPMSTimeInfo +} + +// GetCapability is the input to TPM2_GetCapability. +// See definition in Part 3, Commands, section 30.2 +type GetCapability struct { + // group selection; determines the format of the response + Capability TPMCap + // further definition of information + Property uint32 + // number of properties of the indicated type to return + PropertyCount uint32 +} + +// Command implements the Command interface. +func (GetCapability) Command() TPMCC { return TPMCCGetCapability } + +// Execute executes the command and returns the response. +func (cmd GetCapability) Execute(t transport.TPM, s ...Session) (*GetCapabilityResponse, error) { + var rsp GetCapabilityResponse + if err := execute[GetCapabilityResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// GetCapabilityResponse is the response from TPM2_GetCapability. +type GetCapabilityResponse struct { + // flag to indicate if there are more values of this type + MoreData TPMIYesNo + // the capability data + CapabilityData TPMSCapabilityData +} + +// TestParms is the input to TPM2_TestParms. +// See definition in Part 3, Commands, section 30.3 +type TestParms struct { + // Algorithms parameters to be validates + Parameters TPMTPublicParms +} + +// Command implements the Command interface. +func (TestParms) Command() TPMCC { return TPMCCTestParms } + +// Execute executes the command and returns the response. +func (cmd TestParms) Execute(t transport.TPM, s ...Session) (*TestParmsResponse, error) { + var rsp TestParmsResponse + if err := execute[TestParmsResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// TestParmsResponse is the response from TPM2_TestParms. +type TestParmsResponse struct{} + +// NVDefineSpace is the input to TPM2_NV_DefineSpace. +// See definition in Part 3, Commands, section 31.3. +type NVDefineSpace struct { + // TPM_RH_OWNER or TPM_RH_PLATFORM+{PP} + AuthHandle handle `gotpm:"handle,auth"` + // the authorization value + Auth TPM2BAuth + // the public parameters of the NV area + PublicInfo TPM2BNVPublic +} + +// Command implements the Command interface. +func (NVDefineSpace) Command() TPMCC { return TPMCCNVDefineSpace } + +// Execute executes the command and returns the response. +func (cmd NVDefineSpace) Execute(t transport.TPM, s ...Session) (*NVDefineSpaceResponse, error) { + var rsp NVDefineSpaceResponse + err := execute[NVDefineSpaceResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// NVDefineSpaceResponse is the response from TPM2_NV_DefineSpace. +type NVDefineSpaceResponse struct{} + +// NVUndefineSpace is the input to TPM2_NV_UndefineSpace. +// See definition in Part 3, Commands, section 31.4. +type NVUndefineSpace struct { + // TPM_RH_OWNER or TPM_RH_PLATFORM+{PP} + AuthHandle handle `gotpm:"handle,auth"` + // the NV Index to remove from NV space + NVIndex handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (NVUndefineSpace) Command() TPMCC { return TPMCCNVUndefineSpace } + +// Execute executes the command and returns the response. +func (cmd NVUndefineSpace) Execute(t transport.TPM, s ...Session) (*NVUndefineSpaceResponse, error) { + var rsp NVUndefineSpaceResponse + err := execute[NVUndefineSpaceResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// NVUndefineSpaceResponse is the response from TPM2_NV_UndefineSpace. +type NVUndefineSpaceResponse struct{} + +// NVUndefineSpaceSpecial is the input to TPM2_NV_UndefineSpaceSpecial. +// See definition in Part 3, Commands, section 31.5. +type NVUndefineSpaceSpecial struct { + // Index to be deleted + NVIndex handle `gotpm:"handle,auth"` + // TPM_RH_PLATFORM+{PP} + Platform handle `gotpm:"handle,auth"` +} + +// Command implements the Command interface. +func (NVUndefineSpaceSpecial) Command() TPMCC { return TPMCCNVUndefineSpaceSpecial } + +// Execute executes the command and returns the response. +func (cmd NVUndefineSpaceSpecial) Execute(t transport.TPM, s ...Session) (*NVUndefineSpaceSpecialResponse, error) { + var rsp NVUndefineSpaceSpecialResponse + err := execute[NVUndefineSpaceSpecialResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// NVUndefineSpaceSpecialResponse is the response from TPM2_NV_UndefineSpaceSpecial. +type NVUndefineSpaceSpecialResponse struct{} + +// NVReadPublic is the input to TPM2_NV_ReadPublic. +// See definition in Part 3, Commands, section 31.6. +type NVReadPublic struct { + // the NV index + NVIndex handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (NVReadPublic) Command() TPMCC { return TPMCCNVReadPublic } + +// Execute executes the command and returns the response. +func (cmd NVReadPublic) Execute(t transport.TPM, s ...Session) (*NVReadPublicResponse, error) { + var rsp NVReadPublicResponse + if err := execute[NVReadPublicResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// NVReadPublicResponse is the response from TPM2_NV_ReadPublic. +type NVReadPublicResponse struct { + NVPublic TPM2BNVPublic + NVName TPM2BName +} + +// NVWrite is the input to TPM2_NV_Write. +// See definition in Part 3, Commands, section 31.7. +type NVWrite struct { + // handle indicating the source of the authorization value + AuthHandle handle `gotpm:"handle,auth"` + // the NV index of the area to write + NVIndex handle `gotpm:"handle"` + // the data to write + Data TPM2BMaxNVBuffer + // the octet offset into the NV Area + Offset uint16 +} + +// Command implements the Command interface. +func (NVWrite) Command() TPMCC { return TPMCCNVWrite } + +// Execute executes the command and returns the response. +func (cmd NVWrite) Execute(t transport.TPM, s ...Session) (*NVWriteResponse, error) { + var rsp NVWriteResponse + err := execute[NVWriteResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// NVWriteResponse is the response from TPM2_NV_Write. +type NVWriteResponse struct{} + +// NVIncrement is the input to TPM2_NV_Increment. +// See definition in Part 3, Commands, section 31.8. +type NVIncrement struct { + // handle indicating the source of the authorization value + AuthHandle handle `gotpm:"handle,auth"` + // the NV index of the area to write + NVIndex handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (NVIncrement) Command() TPMCC { return TPMCCNVIncrement } + +// Execute executes the command and returns the response. +func (cmd NVIncrement) Execute(t transport.TPM, s ...Session) (*NVIncrementResponse, error) { + var rsp NVIncrementResponse + err := execute[NVIncrementResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// NVIncrementResponse is the response from TPM2_NV_Increment. +type NVIncrementResponse struct{} + +// NVWriteLock is the input to TPM2_NV_WriteLock. +// See definition in Part 3, Commands, section 31.11. +type NVWriteLock struct { + // handle indicating the source of the authorization value + AuthHandle handle `gotpm:"handle,auth"` + // the NV index of the area to lock + NVIndex handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (NVWriteLock) Command() TPMCC { return TPMCCNVWriteLock } + +// Execute executes the command and returns the response. +func (cmd NVWriteLock) Execute(t transport.TPM, s ...Session) (*NVWriteLockResponse, error) { + var rsp NVWriteLockResponse + err := execute[NVWriteLockResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// NVWriteLockResponse is the response from TPM2_NV_WriteLock. +type NVWriteLockResponse struct{} + +// NVRead is the input to TPM2_NV_Read. +// See definition in Part 3, Commands, section 31.13. +type NVRead struct { + // handle indicating the source of the authorization value + AuthHandle handle `gotpm:"handle,auth"` + // the NV index to read + NVIndex handle `gotpm:"handle"` + // number of octets to read + Size uint16 + // octet offset into the NV area + Offset uint16 +} + +// Command implements the Command interface. +func (NVRead) Command() TPMCC { return TPMCCNVRead } + +// Execute executes the command and returns the response. +func (cmd NVRead) Execute(t transport.TPM, s ...Session) (*NVReadResponse, error) { + var rsp NVReadResponse + if err := execute[NVReadResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// NVReadResponse is the response from TPM2_NV_Read. +type NVReadResponse struct { + // the data read + Data TPM2BMaxNVBuffer +} + +// NVReadLock is the input to TPM2_NV_NVReadLock. +// See definition in Part 3, Commands, section 31.14. +type NVReadLock struct { + // handle indicating the source of the authorization value + AuthHandle handle `gotpm:"handle,auth"` + // the NV index of the area to lock + NVIndex handle `gotpm:"handle"` +} + +// Command implements the Command interface. +func (NVReadLock) Command() TPMCC { return TPMCCNVReadLock } + +// Execute executes the command and returns the response. +func (cmd NVReadLock) Execute(t transport.TPM, s ...Session) (*NVReadLockResponse, error) { + var rsp NVReadLockResponse + err := execute[NVReadLockResponse](t, cmd, &rsp, s...) + if err != nil { + return nil, err + } + return &rsp, nil +} + +// NVReadLockResponse is the response from TPM2_NV_ReadLock. +type NVReadLockResponse struct{} + +// NVCertify is the input to TPM2_NV_Certify. +// See definition in Part 3, Commands, section 31.16. +type NVCertify struct { + // handle of the key used to sign the attestation structure + SignHandle handle `gotpm:"handle,auth"` + // handle indicating the source of the authorization value + AuthHandle handle `gotpm:"handle,auth"` + // Index for the area to be certified + NVIndex handle `gotpm:"handle"` + // user-provided qualifying data + QualifyingData TPM2BData + // signing scheme to use if the scheme for signHandle is TPM_ALG_NULL + InScheme TPMTSigScheme `gotpm:"nullable"` + // number of octets to certify + Size uint16 + // octet offset into the NV area + Offset uint16 +} + +// Command implements the Command interface. +func (NVCertify) Command() TPMCC { return TPMCCNVCertify } + +// Execute executes the command and returns the response. +func (cmd NVCertify) Execute(t transport.TPM, s ...Session) (*NVCertifyResponse, error) { + var rsp NVCertifyResponse + if err := execute[NVCertifyResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// NVCertifyResponse is the response from TPM2_NV_Read. +type NVCertifyResponse struct { + // the structure that was signed + CertifyInfo TPM2BAttest + // the asymmetric signature over certifyInfo using the key referenced by signHandle + Signature TPMTSignature +} + +// GetTime is the input to TPM2_GetTime. +// See definition in Part 3, Commands, section 18.7. +type GetTime struct { + // handle of the privacy administrator (Must be the value TPM_RH_ENDORSEMENT or command will fail) + PrivacyAdminHandle TPMIRHEndorsement `gotpm:"handle,auth"` + // the keyHandle identifier of a loaded key that can perform digital signatures + SignHandle handle `gotpm:"handle,auth"` + // data to "tick stamp" + QualifyingData TPM2BData + // signing scheme to use if the scheme for signHandle is TPM_ALG_NULL + InScheme TPMTSigScheme `gotpm:"nullable"` +} + +// Command implements the Command interface. +func (GetTime) Command() TPMCC { return TPMCCGetTime } + +// Execute executes the command and returns the response. +func (cmd GetTime) Execute(t transport.TPM, s ...Session) (*GetTimeResponse, error) { + var rsp GetTimeResponse + if err := execute[GetTimeResponse](t, cmd, &rsp, s...); err != nil { + return nil, err + } + return &rsp, nil +} + +// GetTimeResponse is the response from TPM2_GetTime. +type GetTimeResponse struct { + // standard TPM-generated attestation block + TimeInfo TPM2BAttest + // the signature over timeInfo + Signature TPMTSignature +} diff --git a/vendor/github.com/google/go-tpm/tpm2/tpm2b.go b/vendor/github.com/google/go-tpm/tpm2/tpm2b.go new file mode 100644 index 00000000000..f5af16ab5f8 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/tpm2b.go @@ -0,0 +1,83 @@ +package tpm2 + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" +) + +// TPM2B is a helper type for all sized TPM structures. It can be instantiated with either a raw byte buffer or the actual struct. +type TPM2B[T Marshallable, P interface { + *T + Unmarshallable +}] struct { + contents *T + buffer []byte +} + +// New2B creates a new TPM2B containing the given contents. +func New2B[T Marshallable, P interface { + *T + Unmarshallable +}](t T) TPM2B[T, P] { + return TPM2B[T, P]{contents: &t} +} + +// BytesAs2B creates a new TPM2B containing the given byte array. +func BytesAs2B[T Marshallable, P interface { + *T + Unmarshallable +}](b []byte) TPM2B[T, P] { + return TPM2B[T, P]{buffer: b} +} + +// Contents returns the structured contents of the TPM2B. +// It can fail if the TPM2B was instantiated with an invalid byte buffer. +func (value *TPM2B[T, P]) Contents() (*T, error) { + if value.contents != nil { + return value.contents, nil + } + if value.buffer == nil { + return nil, fmt.Errorf("TPMB had no contents or buffer") + } + contents, err := Unmarshal[T, P](value.buffer) + if err != nil { + return nil, err + } + // Cache the result + value.contents = (*T)(contents) + return value.contents, nil +} + +// Bytes returns the inner contents of the TPM2B as a byte array, not including the length field. +func (value *TPM2B[T, P]) Bytes() []byte { + if value.buffer != nil { + return value.buffer + } + if value.contents == nil { + return []byte{} + } + + // Cache the result + value.buffer = Marshal(*value.contents) + return value.buffer +} + +// marshal implements the tpm2.Marshallable interface. +func (value TPM2B[T, P]) marshal(buf *bytes.Buffer) { + b := value.Bytes() + binary.Write(buf, binary.BigEndian, uint16(len(b))) + buf.Write(b) +} + +// unmarshal implements the tpm2.Unmarshallable interface. +// Note: the structure contents are not validated during unmarshalling. +func (value *TPM2B[T, P]) unmarshal(buf *bytes.Buffer) error { + var size uint16 + binary.Read(buf, binary.BigEndian, &size) + value.contents = nil + value.buffer = make([]byte, size) + _, err := io.ReadAtLeast(buf, value.buffer, int(size)) + return err +} diff --git a/vendor/github.com/google/go-tpm/tpm2/transport/open_other.go b/vendor/github.com/google/go-tpm/tpm2/transport/open_other.go new file mode 100644 index 00000000000..4bd4f532b85 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/transport/open_other.go @@ -0,0 +1,22 @@ +//go:build !windows + +package transport + +import ( + legacy "github.com/google/go-tpm/legacy/tpm2" +) + +// OpenTPM opens the TPM at the given path. If no path is provided, it will +// attempt to use reasonable defaults. +// +// Deprecated: Please use the individual transport packages (e.g., +// go-tpm/tpm2/transport/linuxtpm). +func OpenTPM(path ...string) (TPMCloser, error) { + rwc, err := legacy.OpenTPM(path...) + if err != nil { + return nil, err + } + return &wrappedRWC{ + transport: rwc, + }, nil +} diff --git a/vendor/github.com/google/go-tpm/tpm2/transport/open_windows.go b/vendor/github.com/google/go-tpm/tpm2/transport/open_windows.go new file mode 100644 index 00000000000..97d76af3439 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/transport/open_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package transport + +import ( + legacy "github.com/google/go-tpm/legacy/tpm2" +) + +// OpenTPM opens the local system TPM. +// +// Deprecated: Please use the individual transport packages (e.g., +// go-tpm/tpm2/transport/windowstpm). +func OpenTPM() (TPMCloser, error) { + rwc, err := legacy.OpenTPM() + if err != nil { + return nil, err + } + return &wrappedRWC{ + transport: rwc, + }, nil +} diff --git a/vendor/github.com/google/go-tpm/tpm2/transport/tpm.go b/vendor/github.com/google/go-tpm/tpm2/transport/tpm.go new file mode 100644 index 00000000000..fef27d62d66 --- /dev/null +++ b/vendor/github.com/google/go-tpm/tpm2/transport/tpm.go @@ -0,0 +1,90 @@ +// Package transport implements types for physically talking to TPMs. +package transport + +import ( + "io" + + "github.com/google/go-tpm/tpmutil" +) + +// TPM represents a logical connection to a TPM. +type TPM interface { + Send(input []byte) ([]byte, error) +} + +// TPMCloser represents a logical connection to a TPM and you can close it. +type TPMCloser interface { + TPM + io.Closer +} + +// wrappedRW represents a struct that wraps an io.ReadWriter +// to a transport.TPM to be compatible with tpmdirect. +type wrappedRW struct { + transport io.ReadWriter +} + +// wrappedRWC represents a struct that wraps an io.ReadWriteCloser +// to a transport.TPM to be compatible with tpmdirect. +type wrappedRWC struct { + transport io.ReadWriteCloser +} + +// wrappedTPM represents a struct that wraps a transport.TPM's underlying +// transport to use with legacy code that expects an io.ReadWriter. +type wrappedTPM struct { + response []byte + tpm TPM +} + +// FromReadWriter takes in a io.ReadWriter and returns a +// transport.TPM wrapping the io.ReadWriter. +func FromReadWriter(rw io.ReadWriter) TPM { + return &wrappedRW{transport: rw} +} + +// FromReadWriteCloser takes in a io.ReadWriteCloser and returns a +// transport.TPMCloser wrapping the io.ReadWriteCloser. +func FromReadWriteCloser(rwc io.ReadWriteCloser) TPMCloser { + return &wrappedRWC{transport: rwc} +} + +// ToReadWriter takes in a transport TPM and returns an +// io.ReadWriter wrapping the transport TPM. +func ToReadWriter(tpm TPM) io.ReadWriter { + return &wrappedTPM{tpm: tpm} +} + +// Read copies t.response into the p buffer and return the appropriate length. +func (t *wrappedTPM) Read(p []byte) (int, error) { + n := copy(p, t.response) + t.response = t.response[n:] + if len(t.response) == 0 { + return n, io.EOF + } + return n, nil +} + +// Write implements the io.ReadWriter interface. +func (t *wrappedTPM) Write(p []byte) (n int, err error) { + t.response, err = t.tpm.Send(p) + if err != nil { + return 0, err + } + return len(p), nil +} + +// Send implements the TPM interface. +func (t *wrappedRW) Send(input []byte) ([]byte, error) { + return tpmutil.RunCommandRaw(t.transport, input) +} + +// Send implements the TPM interface. +func (t *wrappedRWC) Send(input []byte) ([]byte, error) { + return tpmutil.RunCommandRaw(t.transport, input) +} + +// Close implements the TPM interface. +func (t *wrappedRWC) Close() error { + return t.transport.Close() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index be200b72cd0..be6fd7bee73 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -40,6 +40,8 @@ github.com/godbus/dbus/v5 ## explicit; go 1.22 github.com/google/go-tpm/legacy/tpm2 github.com/google/go-tpm/tpm +github.com/google/go-tpm/tpm2 +github.com/google/go-tpm/tpm2/transport github.com/google/go-tpm/tpmutil github.com/google/go-tpm/tpmutil/tbs # github.com/moby/sys/capability v0.4.0 From f61691e2625522c3b55bb74737a2aa5bc764af67 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:49:51 +0000 Subject: [PATCH 25/29] vtpms: fix flaky tests Signed-off-by: Efim Verzakov --- .../vtpm/vtpm-helper/vtpm_helper_test.go | 4 ++ libcontainer/vtpm/vtpm.go | 62 +++++++++++++++++-- tests/integration/swtpm.bats | 35 ++++++++++- 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go index db51ce3e466..e627156f3ae 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go @@ -128,6 +128,10 @@ func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificate t.Fatalf("VTPM could not be stopped cleanly: %v", err) } + if err := os.Remove(myvtpm.GetTPMDevpath()); err != nil && !os.IsNotExist(err) { + t.Fatalf("While testing the docker container, we should remove device ourselves: %s", err) + } + createdStatePath, err := myvtpm.Start() if err != nil { t.Fatalf("VTPM could not be started: %v", err) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index a4ac56aa272..c9a377c8166 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -242,17 +242,47 @@ func (vtpm *VTPM) getPidFromFile() (int, error) { } // waitForPidFile: wait for the PID file to appear and read the PID from it -func (vtpm *VTPM) waitForPidFile(loops int) (int, error) { - for loops >= 0 { +// It is possible that swtpm_cuse will exit after ptm_init callback will be called. +// E.g. if we create two devices with the same major/minor +func (vtpm *VTPM) waitForPidFile(loops int, successLoops int) (int, error) { + var created bool + for !created && loops >= 0 || created && successLoops >= 0 { pid, err := vtpm.getPidFromFile() if pid > 0 && err == nil { - return pid, nil + created = true + if successLoops == 0 { + return pid, nil + } + } else if created { + return -1, fmt.Errorf("swtpm's pid file disappeared: log %s", vtpm.ReadLog()) } time.Sleep(time.Millisecond * 100) - loops -= 1 + if created { + successLoops -= 1 + } else { + loops -= 1 + } } logrus.Error("PID file did not appear") - return -1, fmt.Errorf("swtpm's pid file did not appear") + return -1, fmt.Errorf("swtpm's pid file did not appear: log %s", vtpm.ReadLog()) +} + +// waitForDisappearPidFile: Wait for /dev/tpm%d to appear and while waiting +// +// check whether the swtpm is still alive by checking its PID file +func (vtpm *VTPM) waitForDisappearPidFile(loops int) error { + pidfile := vtpm.getPidFile() + + for loops >= 0 { + if _, err := os.Stat(pidfile); err != nil && os.IsNotExist(err) { + return nil + } else if err != nil { + return err + } + time.Sleep(time.Millisecond * 100) + loops -= 1 + } + return fmt.Errorf("TPM pid file %s did not disappear", pidfile) } // stopByPidFile: Stop the vTPM by its PID file @@ -269,7 +299,13 @@ func (vtpm *VTPM) stopByPidFile() error { } err = p.Signal(syscall.SIGTERM) + if err != nil { + return err + } + // We can not use p.Wait because swtpm is forked and not our child. + // However, we need to be sure that swtpm_cuse process is stopped. + err = vtpm.waitForDisappearPidFile(10) return err } @@ -347,6 +383,13 @@ func (vtpm *VTPM) chownStatePath() error { } err = filepath.Walk(vtpm.StatePath, func(path string, info os.FileInfo, err error) error { + // In vtpm_helper unit tests the VTPM device is created, stopped and recreated. + // The "race condition" is possible because after SIGTERM signal swtpm_cuse will try to delete swtpm's pid file. + // However, Walk function reads the list of all names in the dir and calls os.Lstat for each file. + // If an error is occured, then it will be passed to this function. So, in this case we need to return nil. + if os.IsNotExist(err) && path != vtpm.StatePath { + return nil + } if err != nil { return err } @@ -546,7 +589,7 @@ func (vtpm *VTPM) startSwtpm() error { // to fork and parent process will be exited. We need wait until // ptm_init_done https://github.com/stefanberger/swtpm/blob/master/src/swtpm/cuse_tpm.c#L1526 // callback will be called. - vtpm.Pid, err = vtpm.waitForPidFile(10) + vtpm.Pid, err = vtpm.waitForPidFile(10, 5) if err != nil { return fmt.Errorf("wait for PidFile: %w", err) } @@ -777,6 +820,13 @@ func (vtpm *VTPM) setupSELinux() error { } err := filepath.Walk(vtpm.StatePath, func(path string, info os.FileInfo, err error) error { + // In vtpm_helper unit tests the VTPM device is created, stopped and recreated. + // The "race condition" is possible because after SIGTERM signal swtpm_cuse will try to delete swtpm's pid file. + // However, Walk function reads the list of all names in the dir and calls os.Lstat for each file. + // If an error is occured, then it will be passed to this function. So, in this case we need to return nil. + if os.IsNotExist(err) && path != vtpm.StatePath { + return nil + } if err != nil { return err } diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index 372d56fb59b..4b37868399c 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -142,6 +142,7 @@ function teardown() { @test "runc with 2 container with the same devpath" { HELPER="tpm-helper" cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ + # first container vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmsame", "vtpmMajor": 100, "vtpmMinor": 1}]' @@ -149,6 +150,7 @@ function teardown() { [ "$status" -eq 0 ] wait_for_container 10 1 tst1 + # second container vtpm_pth1=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] |.linux.resources.vtpms = [{"statepath": "'"$vtpm_pth1"'", "vtpmversion": "2", "vtpmname" : "tpmsame", "vtpmMajor": 101, "vtpmMinor": 1}]' @@ -182,31 +184,60 @@ function teardown() { [ "$status" -eq 0 ] } -@test "runc run with wrong VTPM names" { +@test "runc run with wrong VTPM params" { HELPER="tpm-helper" vtpm_path1=$(mktemp -d) vtpm_path2=$(mktemp -d) vtpm_path3=$(mktemp -d) + # empty name update_config ' .process.args = ["/bin/sh"] |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": 100, "vtpmMinor": 1}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -ne 0 ] + # the same name update_config ' .process.args = ["/bin/sh"] |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, {"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 101, "vtpmMinor": 1} ]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -ne 0 ] + + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst1 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst1 + + # with the same state path + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst2 + [ "$status" -ne 0 ] + + # with the same major/minor + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 100, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst3 + [ "$status" -ne 0 ] + + # with different params + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst4 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst4 } -@test "runc run container with 2 containers" { +@test "runc run container with 2 devices" { HELPER="tpm-helper" cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ vtpm_path1=$(mktemp -d) vtpm_path2=$(mktemp -d) + + # two devices update_config ' .process.args = ["/bin/sh"] |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, {"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1} From 507c3c7c3575d321bed0349aa1901e8b967b4ee8 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:50:07 +0000 Subject: [PATCH 26/29] vtpms: go.mod update runtime-spec Signed-off-by: Efim Verzakov --- go.mod | 2 + go.sum | 4 +- .../vtpm/vtpm-helper/vtpm_helper_test.go | 8 +-- libcontainer/vtpm/vtpm.go | 11 +++- .../runtime-spec/specs-go/config.go | 58 ++++++++++++++++--- .../specs-go/features/features.go | 9 +++ vendor/modules.txt | 3 +- 7 files changed, 77 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index dfe8fca2ad9..5969e392ee4 100644 --- a/go.mod +++ b/go.mod @@ -35,3 +35,5 @@ require ( ) replace github.com/google/go-tpm => github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049 + +replace github.com/opencontainers/runtime-spec => github.com/everzakov/runtime-spec v0.0.0-20250816064520-f0885e035161 diff --git a/go.sum b/go.sum index b244c8a69be..70378d0bb06 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049 h1:TOFYpbcgHw260lHT+hW7k97Ri7i/+UfEuQW7/3GBb8E= github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/everzakov/runtime-spec v0.0.0-20250816064520-f0885e035161 h1:B4CMvhQY1Sijg20viaZo4jy267Mo8/Z+huqx9x3rd7A= +github.com/everzakov/runtime-spec v0.0.0-20250816064520-f0885e035161/go.mod h1:Kbvgc27P6fS+ifaBC6lxYMD2hmLwpmWT4+fJCvyJvRg= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -51,8 +53,6 @@ github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/opencontainers/cgroups v0.0.4 h1:XVj8P/IHVms/j+7eh8ggdkTLAxjz84ZzuFyGoE28DR4= github.com/opencontainers/cgroups v0.0.4/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs= -github.com/opencontainers/runtime-spec v1.2.2-0.20250401095657-e935f995dd67 h1:Q+KewUGTMamIe6Q39xCD/T1NC1POmaTlWnhjikCrZHA= -github.com/opencontainers/runtime-spec v1.2.2-0.20250401095657-e935f995dd67/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go index e627156f3ae..acb4980e052 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go @@ -43,21 +43,21 @@ const ( minorEnvName = "RUN_IN_CONTAINER_MINOR" ) -func getDefaultMajorMinorDevices() (uint32, uint32, error) { - var major, minor uint32 +func getDefaultMajorMinorDevices() (int64, int64, error) { + var major, minor int64 if val := os.Getenv(majorEnvName); len(val) > 0 { converted, err := strconv.Atoi(val) if err != nil { return 0, 0, fmt.Errorf("can not use %s as a device major: %s", val, err) } - major = uint32(converted) + major = int64(converted) } if val := os.Getenv(minorEnvName); len(val) > 0 { converted, err := strconv.Atoi(val) if err != nil { return 0, 0, fmt.Errorf("can not use %s as a device minor: %s", val, err) } - minor = uint32(converted) + minor = int64(converted) } return major, minor, nil } diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index c9a377c8166..7c43ce99efb 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -507,14 +507,21 @@ func (vtpm *VTPM) waitForTPMDevice(loops int) error { return fmt.Errorf("waitForTPMDevice swtpm process has terminated: %w", err) } - if _, err := os.Stat(devpath); err == nil { + if fileInfo, err := os.Stat(devpath); err == nil { + // Read major/minor of the created device + stat_t := fileInfo.Sys().(*syscall.Stat_t) + devNumber := stat_t.Rdev + vtpm.major = unix.Major(devNumber) + vtpm.minor = unix.Minor(devNumber) return nil } time.Sleep(time.Millisecond * 100) loops -= 1 } // if we testing in the docker container, we should create devices ourselves - if vtpm.major != 0 && vtpm.minor != 0 { + // If major is provided then cuse will try to register with provided minor. The minor default value is 0. + // https://elixir.bootlin.com/linux/v6.15.5/source/fs/fuse/cuse.c#L356 + if vtpm.major != 0 { fileMode := 0o666 | unix.S_IFCHR dev := unix.Mkdev(vtpm.major, vtpm.minor) if err := unix.Mknod(devpath, uint32(fileMode), int(dev)); err != nil { diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index 3d2d84dd082..6d4b25b985e 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -251,6 +251,8 @@ type Linux struct { // IntelRdt contains Intel Resource Director Technology (RDT) information for // handling resource constraints and monitoring metrics (e.g., L3 cache, memory bandwidth) for the container IntelRdt *LinuxIntelRdt `json:"intelRdt,omitempty"` + // MemoryPolicy contains NUMA memory policy for the container. + MemoryPolicy *LinuxMemoryPolicy `json:"memoryPolicy,omitempty"` // Personality contains configuration for the Linux personality syscall Personality *LinuxPersonality `json:"personality,omitempty"` // TimeOffsets specifies the offset for supporting time namespaces. @@ -470,9 +472,9 @@ type LinuxVTPM struct { // Name of the vtpm VTPMName string `json:"vtpmName,omitempty"` // Device's major to be created - VTPMMajor uint32 `json:"vtpmMajor,omitempty"` + VTPMMajor int64 `json:"vtpmMajor,omitempty"` // Device's minor to be created - VTPMMinor uint32 `json:"vtpmMinor,omitempty"` + VTPMMinor int64 `json:"vtpmMinor,omitempty"` } // LinuxResources has container runtime resource constraints @@ -497,7 +499,7 @@ type LinuxResources struct { Rdma map[string]LinuxRdma `json:"rdma,omitempty"` // Unified resources. Unified map[string]string `json:"unified,omitempty"` - // Linux configuration + // Linux VTPM configuration VTPMs []LinuxVTPM `json:"vtpms,omitempty"` } @@ -862,23 +864,41 @@ type LinuxSyscall struct { type LinuxIntelRdt struct { // The identity for RDT Class of Service ClosID string `json:"closID,omitempty"` + + // Schemata specifies the complete schemata to be written as is to the + // schemata file in resctrl fs. Each element represents a single line in the schemata file. + // NOTE: This will overwrite schemas specified in the L3CacheSchema and/or + // MemBwSchema fields. + Schemata []string `json:"schemata,omitempty"` + // The schema for L3 cache id and capacity bitmask (CBM) // Format: "L3:=;=;..." + // NOTE: Should not be specified if Schemata is non-empty. L3CacheSchema string `json:"l3CacheSchema,omitempty"` // The schema of memory bandwidth per L3 cache id // Format: "MB:=bandwidth0;=bandwidth1;..." // The unit of memory bandwidth is specified in "percentages" by // default, and in "MBps" if MBA Software Controller is enabled. + // NOTE: Should not be specified if Schemata is non-empty. MemBwSchema string `json:"memBwSchema,omitempty"` - // EnableCMT is the flag to indicate if the Intel RDT CMT is enabled. CMT (Cache Monitoring Technology) supports monitoring of - // the last-level cache (LLC) occupancy for the container. - EnableCMT bool `json:"enableCMT,omitempty"` + // EnableMonitoring enables resctrl monitoring for the container. This will + // create a dedicated resctrl monitoring group for the container. + EnableMonitoring bool `json:"enableMonitoring,omitempty"` +} + +// LinuxMemoryPolicy represents input for the set_mempolicy syscall. +type LinuxMemoryPolicy struct { + // Mode for the set_mempolicy syscall. + Mode MemoryPolicyModeType `json:"mode"` - // EnableMBM is the flag to indicate if the Intel RDT MBM is enabled. MBM (Memory Bandwidth Monitoring) supports monitoring of - // total and local memory bandwidth for the container. - EnableMBM bool `json:"enableMBM,omitempty"` + // Nodes representing the nodemask for the set_mempolicy syscall in comma separated ranges format. + // Format: "-,,-,..." + Nodes string `json:"nodes"` + + // Flags for the set_mempolicy syscall. + Flags []MemoryPolicyFlagType `json:"flags,omitempty"` } // ZOS contains platform-specific configuration for z/OS based containers. @@ -910,6 +930,26 @@ const ( ZOSUTSNamespace ZOSNamespaceType = "uts" ) +type MemoryPolicyModeType string + +const ( + MpolDefault MemoryPolicyModeType = "MPOL_DEFAULT" + MpolBind MemoryPolicyModeType = "MPOL_BIND" + MpolInterleave MemoryPolicyModeType = "MPOL_INTERLEAVE" + MpolWeightedInterleave MemoryPolicyModeType = "MPOL_WEIGHTED_INTERLEAVE" + MpolPreferred MemoryPolicyModeType = "MPOL_PREFERRED" + MpolPreferredMany MemoryPolicyModeType = "MPOL_PREFERRED_MANY" + MpolLocal MemoryPolicyModeType = "MPOL_LOCAL" +) + +type MemoryPolicyFlagType string + +const ( + MpolFNumaBalancing MemoryPolicyFlagType = "MPOL_F_NUMA_BALANCING" + MpolFRelativeNodes MemoryPolicyFlagType = "MPOL_F_RELATIVE_NODES" + MpolFStaticNodes MemoryPolicyFlagType = "MPOL_F_STATIC_NODES" +) + // LinuxSchedulerPolicy represents different scheduling policies used with the Linux Scheduler type LinuxSchedulerPolicy string diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/features/features.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/features/features.go index d8eb169dc39..8271ded8afd 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/features/features.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/features/features.go @@ -47,6 +47,7 @@ type Linux struct { Apparmor *Apparmor `json:"apparmor,omitempty"` Selinux *Selinux `json:"selinux,omitempty"` IntelRdt *IntelRdt `json:"intelRdt,omitempty"` + MemoryPolicy *MemoryPolicy `json:"memoryPolicy,omitempty"` MountExtensions *MountExtensions `json:"mountExtensions,omitempty"` NetDevices *NetDevices `json:"netDevices,omitempty"` } @@ -132,6 +133,14 @@ type IntelRdt struct { Enabled *bool `json:"enabled,omitempty"` } +// MemoryPolicy represents the "memoryPolicy" field. +type MemoryPolicy struct { + // modes is the list of known memory policy modes, e.g., "MPOL_INTERLEAVE". + Modes []string `json:"modes,omitempty"` + // flags is the list of known memory policy mode flags, e.g., "MPOL_F_STATIC_NODES". + Flags []string `json:"flags,omitempty"` +} + // MountExtensions represents the "mountExtensions" field. type MountExtensions struct { // IDMap represents the status of idmap mounts support. diff --git a/vendor/modules.txt b/vendor/modules.txt index be6fd7bee73..d35c50bd1c3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -70,7 +70,7 @@ github.com/opencontainers/cgroups/fscommon github.com/opencontainers/cgroups/internal/path github.com/opencontainers/cgroups/manager github.com/opencontainers/cgroups/systemd -# github.com/opencontainers/runtime-spec v1.2.2-0.20250401095657-e935f995dd67 +# github.com/opencontainers/runtime-spec v1.2.2-0.20250401095657-e935f995dd67 => github.com/everzakov/runtime-spec v0.0.0-20250816064520-f0885e035161 ## explicit github.com/opencontainers/runtime-spec/specs-go github.com/opencontainers/runtime-spec/specs-go/features @@ -136,3 +136,4 @@ google.golang.org/protobuf/reflect/protoregistry google.golang.org/protobuf/runtime/protoiface google.golang.org/protobuf/runtime/protoimpl # github.com/google/go-tpm => github.com/everzakov/go-tpm v0.0.0-20250815102554-13e640365049 +# github.com/opencontainers/runtime-spec => github.com/everzakov/runtime-spec v0.0.0-20250816064520-f0885e035161 From 8e5d958309a89cc5212b17b175a30fd829127c66 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:50:23 +0000 Subject: [PATCH 27/29] tests: unbind test vtpms devices major minor Signed-off-by: Efim Verzakov --- Makefile | 9 +++-- tests/integration/swtpm.bats | 65 ++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 7b6f088ff44..b94289c9544 100644 --- a/Makefile +++ b/Makefile @@ -58,9 +58,9 @@ GO_BUILD_STATIC := $(GO) build $(TRIMPATH) $(GO_BUILDMODE_STATIC) \ GPG_KEYID ?= asarai@suse.de -RUN_IN_CONTAINER_MAJOR := 100 -RUN_IN_CONTAINER_MAJOR_SECOND := 101 -RUN_IN_CONTAINER_MINOR := 1 +RUN_IN_CONTAINER_MAJOR ?= 100 +RUN_IN_CONTAINER_MAJOR_SECOND ?= 101 +RUN_IN_CONTAINER_MINOR ?= 1 # Some targets need cgo, which is disabled by default when cross compiling. # Enable cgo explicitly for those. @@ -178,6 +178,9 @@ integration: runcimage -v $(CURDIR):/go/src/$(PROJECT) \ --device=/dev/cuse --device-cgroup-rule "c $(RUN_IN_CONTAINER_MAJOR):$(RUN_IN_CONTAINER_MINOR) rwm" \ --device-cgroup-rule "c $(RUN_IN_CONTAINER_MAJOR_SECOND):$(RUN_IN_CONTAINER_MINOR) rwm" \ + -e "RUN_IN_CONTAINER_MAJOR=$(RUN_IN_CONTAINER_MAJOR)" \ + -e "RUN_IN_CONTAINER_MAJOR_SECOND=$(RUN_IN_CONTAINER_MAJOR_SECOND)" \ + -e "RUN_IN_CONTAINER_MINOR=$(RUN_IN_CONTAINER_MINOR)" \ $(RUNC_IMAGE) make localintegration TESTPATH="$(TESTPATH)" .PHONY: localintegration diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index 4b37868399c..c535255dfa3 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -9,6 +9,9 @@ function setup() { setup_debian ROOT_HASH=$(sha256sum - <<<"$ROOT/state") ROOT_HASH_OFFSET=${ROOT_HASH:0:6} + test_major=${RUN_IN_CONTAINER_MAJOR:-0} + test_major_second=${RUN_IN_CONTAINER_MAJOR_SECOND:-0} + test_minor=${RUN_IN_CONTAINER_MINOR:-0} } function teardown() { @@ -28,7 +31,7 @@ function teardown() { cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/'"$HELPER"'", "-devicePath=/dev/tpmtpmm2"] - | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmm2", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmm2", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run tst [ "$status" -eq 0 ] } @@ -38,7 +41,7 @@ function teardown() { cp "${TESTBINDIR}/${HELPER}" rootfs/bin/ vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/'"$HELPER"'", "-devicePath=/dev/tpmtpmm12", "-deviceVersion=1.2"] - | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "1.2", "vtpmname" : "tpmm12", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "1.2", "vtpmname" : "tpmm12", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run tst [ "$status" -eq 0 ] } @@ -47,7 +50,7 @@ function teardown() { HELPER="tpm-helper" vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmstop", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmstop", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] wait_for_container 10 1 tst @@ -73,7 +76,7 @@ function teardown() { HELPER="tpm-helper" vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmkill", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmkill", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] wait_for_container 10 1 tst @@ -102,7 +105,7 @@ function teardown() { HELPER="tpm-helper" vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmdelete", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmdelete", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] wait_for_container 10 1 tst @@ -122,7 +125,7 @@ function teardown() { HELPER="tpm-helper" vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmforcekill", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmforcekill", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] wait_for_container 10 1 tst @@ -145,7 +148,7 @@ function teardown() { # first container vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmsame", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmsame", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst1 [ "$status" -eq 0 ] wait_for_container 10 1 tst1 @@ -153,7 +156,7 @@ function teardown() { # second container vtpm_pth1=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_pth1"'", "vtpmversion": "2", "vtpmname" : "tpmsame", "vtpmMajor": 101, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_pth1"'", "vtpmversion": "2", "vtpmname" : "tpmsame", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst2 [ "$status" -eq 0 ] @@ -192,39 +195,43 @@ function teardown() { # empty name update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -ne 0 ] # the same name update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, - {"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 101, "vtpmMinor": 1} + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}, + {"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'} ]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -ne 0 ] update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst1 [ "$status" -eq 0 ] wait_for_container 10 1 tst1 # with the same state path update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst2 [ "$status" -ne 0 ] - # with the same major/minor + # with the same major/minor. major and minor are not bind to some values (unless we are running in the container). + # we need to know the right major/minor of tst1 vtpm device. + the_same_minor=$(ls -la /dev/tpm"$ROOT_HASH_OFFSET"-tst1-tpmone | awk '{print $6}') + the_same_major_str=$(ls -la /dev/tpm"$ROOT_HASH_OFFSET"-tst1-tpmone | awk '{print $5}') + the_same_major=${the_same_major_str::-1} update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": '"$the_same_major"', "vtpmMinor": '"$the_same_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst3 [ "$status" -ne 0 ] # with different params update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst4 [ "$status" -eq 0 ] wait_for_container 10 1 tst4 @@ -239,8 +246,8 @@ function teardown() { # two devices update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, - {"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1} + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}, + {"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'} ]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] @@ -274,13 +281,13 @@ function teardown() { vtpm_path4=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst1 [ "$status" -eq 0 ] update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, - {"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 101, "vtpmMinor": 1} + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}, + {"statepath": "'"$vtpm_path3"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'} ]' runc run -d --console-socket "$CONSOLE_SOCKET" tst2 [ "$status" -eq 0 ] @@ -299,8 +306,8 @@ function teardown() { fi update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": 100, "vtpmMinor": 1}, - {"statepath": "'"$vtpm_path4"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": 101, "vtpmMinor": 1} + |.linux.resources.vtpms = [{"statepath": "", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}, + {"statepath": "'"$vtpm_path4"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'} ]' runc run -d --console-socket "$CONSOLE_SOCKET" tst3 [ "$status" -eq 0 ] @@ -327,7 +334,7 @@ function teardown() { |.linux.namespaces += [{"type": "user"}] |.linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}] |.linux.gidMappings += [{"containerID": 0, "hostID": 100000, "size": 65536}] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmuser", "vtpmMajor": 100, "vtpmMinor": 1}]' + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "tpmuser", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' remap_rootfs runc run -d --console-socket "$CONSOLE_SOCKET" tst1 @@ -367,7 +374,7 @@ function teardown() { update_config '.linux.namespaces |= map(if .type == "user" then (.path = "/proc/'"$target_pid"'/ns/" + .type) else . end) | del(.linux.uidMappings) | del(.linux.gidMappings) - | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "joined_userns", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "vtpmversion": "2", "vtpmname" : "joined_userns", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" vtpm_in_joined_userns [ "$status" -eq 0 ] @@ -392,7 +399,7 @@ function teardown() { vtpm_path=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "12345", "vtpmname" : "tpmsetup", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "12345", "vtpmname" : "tpmsetup", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -eq 0 ] @@ -437,13 +444,13 @@ function teardown() { # wrong encryption password update_config ' .process.args = ["/bin/sh"] - | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "54321", "vtpmname" : "tpmsetup", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "54321", "vtpmname" : "tpmsetup", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst1 [ "$status" -ne 0 ] # password with encryption update_config ' .process.args = ["/bin/sh"] - | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "pass=12345", "vtpmname" : "tpmsetup", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path"'", "statePathIsManaged": true, "vtpmversion": "2", "createCerts": true, "pcrBanks": "sha256,sha1", "encryptionPassword": "pass=12345", "vtpmname" : "tpmsetup", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst2 [ "$status" -eq 0 ] wait_for_container 10 1 tst2 @@ -453,7 +460,7 @@ function teardown() { vtpm_path1=$(mktemp -d) update_config ' .process.args = ["/bin/sh"] - | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "statePathIsManaged": true, "vtpmversion": "1.2", "createCerts": true, "encryptionPassword": "12345", "vtpmname" : "tpmsetup", "vtpmMajor": 100, "vtpmMinor": 1}]' + | .linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "statePathIsManaged": true, "vtpmversion": "1.2", "createCerts": true, "encryptionPassword": "12345", "vtpmname" : "tpmsetup", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst3 [ "$status" -eq 0 ] wait_for_container 10 1 tst3 From 272f00859eab702d889026be68ee3977fb3492ae Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:50:56 +0000 Subject: [PATCH 28/29] vtpms: pass run as to swtpm_cuse and fix chown race condition Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm-helper/vtpm_helper.go | 22 +++- libcontainer/vtpm/vtpm.go | 102 ++++++++++++++++++- tests/integration/swtpm.bats | 8 ++ utils_linux.go | 7 +- 4 files changed, 126 insertions(+), 13 deletions(-) diff --git a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go index d19eace40db..c2d5c323dbe 100644 --- a/libcontainer/vtpm/vtpm-helper/vtpm_helper.go +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -9,6 +9,7 @@ import ( "io" "io/ioutil" "os" + "path" "strconv" "strings" "sync" @@ -130,16 +131,33 @@ func ApplyCGroupVTPMs(vtpms []*vtpm.VTPM, cgroupManager cgroups.Manager) error { return nil } -func CheckVTPMNames(vtpms []string) error { +func CheckVTPMParams(vtpms []specs.LinuxVTPM) error { namesMap := make(map[string]int, 0) - for ind, name := range vtpms { + // We need to be sure that there are not several vtpm devices with the same state path. + // checkPossibleChown will help to deal with other processes. This method deals with the requested vTPM devices. + stateMap := make(map[string]int, 0) + for ind, vtpm := range vtpms { + name := vtpm.VTPMName + stateDir := path.Clean(vtpm.StatePath) + if name == "" { return fmt.Errorf("VTPM device %d has empty name", ind) } + + if stateDir == "" { + return fmt.Errorf("VTPM device %d has empty state dir", ind) + } + if mappedInd, ok := namesMap[name]; ok { return fmt.Errorf("VTPM devices %d and %d has the same name %s", mappedInd, ind, name) } + + if mappedInd, ok := stateMap[stateDir]; ok { + return fmt.Errorf("VTPM devices %d and %d has the same state dir %s", mappedInd, ind, stateDir) + } + namesMap[name] = ind + stateMap[stateDir] = ind } return nil } diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index 7c43ce99efb..9093a064339 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -5,6 +5,7 @@ package vtpm import ( "encoding/json" "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -224,6 +225,14 @@ func (vtpm *VTPM) getLogFile() string { return path.Join(vtpm.StatePath, "swtpm.log") } +func (vtpm *VTPM) getVTPMLockFile() string { + return path.Join(vtpm.StatePath, ".lock") +} + +func (vtpm *VTPM) getRuncLockFile() string { + return path.Join(vtpm.StatePath, ".runc-lock") +} + // getPidFromFile: Get the PID from the PID file func (vtpm *VTPM) getPidFromFile() (int, error) { d, err := ioutil.ReadFile(vtpm.getPidFile()) @@ -342,6 +351,71 @@ func (vtpm *VTPM) DeleteStatePath() error { return nil } +// There are 2 possible race conditions caused by chowning files in the state dir: +// 1) When vTPM device is already created and it will be moved to the failure mode by chowning +// 2) When another runc is trying to create vTPM device on the same state dir +// and vTPM creation pipeline will be failed +func (vtpm *VTPM) checkPossibleChown() error { + // open first lock + firstLock, err := os.OpenFile(vtpm.getRuncLockFile(), os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return fmt.Errorf("can not open first lock %s: %w", vtpm.getRuncLockFile(), err) + } + + // get first lock + err = unix.FcntlFlock(firstLock.Fd(), unix.F_SETLK, &unix.Flock_t{ + Type: unix.F_WRLCK, + Whence: io.SeekStart, + Start: 0, + Len: 0, + }) + if err != nil { + firstLock.Close() + return fmt.Errorf("can not get first lock %s: %w", vtpm.getRuncLockFile(), err) + } + + // open second lock + secondLock, err := os.OpenFile(vtpm.getVTPMLockFile(), os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + firstLock.Close() + return fmt.Errorf("can not open second lock %s: %w", vtpm.getVTPMLockFile(), err) + } + + // we need to close anyway + defer secondLock.Close() + + // get second lock + err = unix.FcntlFlock(secondLock.Fd(), unix.F_SETLK, &unix.Flock_t{ + Type: unix.F_WRLCK, + Whence: io.SeekStart, + Start: 0, + Len: 0, + }) + + if err != nil { + firstLock.Close() + return fmt.Errorf("can not get second lock %s: %w", vtpm.getVTPMLockFile(), err) + } + + // close second lock + err = unix.FcntlFlock(firstLock.Fd(), unix.F_SETLK, &unix.Flock_t{ + Type: unix.F_UNLCK, + Whence: io.SeekStart, + Start: 0, + Len: 0, + }) + + if err != nil { + firstLock.Close() + return fmt.Errorf("can not close second lock %s: %w", vtpm.getVTPMLockFile(), err) + } + + // we do not want to close first lock now + // because we want to stop another runc process from creating vTPM device on the same state dir. + // After runc is terminated, record lock will be removed. + return nil +} + // createStatePath creates the TPM directory where the TPM writes its state // into; it also makes the directory accessible to the 'runas' user // @@ -356,6 +430,10 @@ func (vtpm *VTPM) createStatePath() (bool, error) { created = true } + if err := vtpm.checkPossibleChown(); err != nil { + return false, fmt.Errorf("before chown check is not passed: %w", err) + } + err := vtpm.chownStatePath() if err != nil { if created { @@ -553,11 +631,20 @@ func (vtpm *VTPM) startSwtpm() error { if hasCapability(vtpm.swtpmCaps, "flags-opt-startup") { flags += ",startup-clear" } + + // swtpm_cuse can not parse user by uid, get username + userName, err := user.LookupId(vtpm.user) + if err != nil { + return fmt.Errorf("can not look up username by id %s: %w", vtpm.user, err) + } + args := []string{ "--tpmstate", tpmstate, "-n", tpm_dev_name, "--pid", pidfile, "--log", logfile, "--flags", flags, - "--locality", "reject-locality-4,allow-set-locality"} + "--locality", "reject-locality-4,allow-set-locality", + "--runas", userName.Username, + } if vtpm.major != 0 { args = append(args, fmt.Sprintf("--maj=%d", vtpm.major)) @@ -584,7 +671,7 @@ func (vtpm *VTPM) startSwtpm() error { } output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("swtpm failed on fd %s: %s\nlog: %s", tpm_dev_name, string(output), vtpm.ReadLog()) + return fmt.Errorf("swtpm failed on dev name %s: %s\nlog: %s", tpm_dev_name, string(output), vtpm.ReadLog()) } if vtpm.passwordPipeError != nil { return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError) @@ -747,27 +834,32 @@ func (vtpm *VTPM) setupAppArmor() error { tmpStateFilePattern = path.Join(vtpm.StatePath, "TMP2-00.*") } + // We need to add dac_read_search and dac_override capailities in cases when runAs is not a root. profile := fmt.Sprintf("\n#include \n"+ "profile %s {\n"+ " #include \n"+ " capability setgid,\n"+ " capability setuid,\n"+ " capability sys_nice,\n"+ + " capability dac_read_search,\n"+ + " capability dac_override,\n"+ " /dev/tpm[0-9]* rw,\n"+ " owner /etc/group r,\n"+ " owner /etc/nsswitch.conf r,\n"+ " owner /etc/passwd r,\n"+ " /dev/cuse rw,\n"+ " %s/ rw,\n"+ - " %s/.lock wk,\n"+ - " %s w,\n"+ + " %s wk,\n"+ + " %s wk,\n"+ + " %s rw,\n"+ " %s rw,\n"+ " %s rw,\n"+ " %s rw,\n"+ "}\n", profilename, vtpm.StatePath, - vtpm.StatePath, + vtpm.getRuncLockFile(), + vtpm.getVTPMLockFile(), vtpm.getLogFile(), vtpm.getPidFile(), statefilepattern, diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index c535255dfa3..d8f56f9bac8 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -207,6 +207,14 @@ function teardown() { runc run -d --console-socket "$CONSOLE_SOCKET" tst [ "$status" -ne 0 ] + # the same dir + update_config ' .process.args = ["/bin/sh"] + |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}, + {"statepath": "'"$vtpm_path2"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'} + ]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -ne 0 ] + update_config ' .process.args = ["/bin/sh"] |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmone", "vtpmMajor": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' runc run -d --console-socket "$CONSOLE_SOCKET" tst1 diff --git a/utils_linux.go b/utils_linux.go index 68bb614e44c..3d16af561e8 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -522,12 +522,7 @@ func createVTPMs(root, containerID string, spec *specs.Spec) ([]*vtpm.VTPM, erro return vtpms, nil } - var vtpmNames []string - for _, vtpm := range r.VTPMs { - vtpmNames = append(vtpmNames, vtpm.VTPMName) - } - - err := vtpmhelper.CheckVTPMNames(vtpmNames) + err := vtpmhelper.CheckVTPMParams(r.VTPMs) if err != nil { if vtpmhelper.CanIgnoreVTPMErrors() { logrus.Errorf("createVTPMs has an error: %s", err) From 05488b2e811730d8e6d6642042ecb02148f87595 Mon Sep 17 00:00:00 2001 From: Efim Verzakov Date: Sat, 16 Aug 2025 19:52:03 +0000 Subject: [PATCH 29/29] tests: reduce time to wait tpm device and disable integration test with swtpm_cuse issue Signed-off-by: Efim Verzakov --- libcontainer/vtpm/vtpm.go | 2 +- tests/integration/swtpm.bats | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libcontainer/vtpm/vtpm.go b/libcontainer/vtpm/vtpm.go index 9093a064339..942b848c3c3 100644 --- a/libcontainer/vtpm/vtpm.go +++ b/libcontainer/vtpm/vtpm.go @@ -688,7 +688,7 @@ func (vtpm *VTPM) startSwtpm() error { return fmt.Errorf("wait for PidFile: %w", err) } - err = vtpm.waitForTPMDevice(50) + err = vtpm.waitForTPMDevice(10) if err != nil { return fmt.Errorf("wait for waitForTPMDevice: %w", err) } diff --git a/tests/integration/swtpm.bats b/tests/integration/swtpm.bats index d8f56f9bac8..c987b0b201b 100644 --- a/tests/integration/swtpm.bats +++ b/tests/integration/swtpm.bats @@ -221,11 +221,11 @@ function teardown() { [ "$status" -eq 0 ] wait_for_container 10 1 tst1 - # with the same state path - update_config ' .process.args = ["/bin/sh"] - |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'}]' - runc run -d --console-socket "$CONSOLE_SOCKET" tst2 - [ "$status" -ne 0 ] + # with the same state path test is ignored waiting https://github.com/stefanberger/swtpm/issues/1050 to be closed + # update_config ' .process.args = ["/bin/sh"] + # |.linux.resources.vtpms = [{"statepath": "'"$vtpm_path1"'", "vtpmversion": "2", "vtpmname" : "tpmsecond", "vtpmMajor": '"$test_major_second"', "vtpmMinor": '"$test_minor"'}]' + # runc run -d --console-socket "$CONSOLE_SOCKET" tst2 + # [ "$status" -ne 0 ] # with the same major/minor. major and minor are not bind to some values (unless we are running in the container). # we need to know the right major/minor of tst1 vtpm device.