diff --git a/Dockerfile b/Dockerfile index 5c3b933b38e..350c1d40577 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,24 @@ 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 \ + gnutls-bin \ && apt-get clean \ && rm -rf /var/cache/apt /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list @@ -63,6 +83,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..b94289c9544 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,10 @@ 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. # Enable cgo explicitly for those. # Both runc and libcontainer/integration need libcontainer/nsenter. @@ -67,6 +71,8 @@ ifneq (,$(filter $(BUILDTAGS),seccomp)) seccompagent: export CGO_ENABLED=1 endif +tpm-helper: export CGO_ENABLED=0 + .DEFAULT: runc .PHONY: runc @@ -87,7 +93,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) @@ -155,6 +161,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 @@ -167,6 +176,11 @@ 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" \ + --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/go.mod b/go.mod index bf28e63b986..5969e392ee4 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,7 @@ 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 + +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 00a6f65b121..70378d0bb06 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,10 @@ 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/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= @@ -23,6 +27,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= @@ -47,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/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/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/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..53ab1d22e99 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -949,6 +949,7 @@ 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) if err != nil { return err 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", diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index ec417ed37e2..2df20ad03c7 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 { 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..c2d5c323dbe --- /dev/null +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper.go @@ -0,0 +1,198 @@ +// + build linux + +package vtpmhelper + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "strconv" + "strings" + "sync" + + "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" + "github.com/sirupsen/logrus" +) + +const ( + HashedRootOffset = 6 +) + +// getEncryptionPassword gets the plain password from the caller +// valid formats passed to this function are: +// - +// - pass= +// - fd= +// - file= +func getEncryptionPassword(pwdString string) ([]byte, error) { + switch { + case strings.HasPrefix(pwdString, "file="): + return ioutil.ReadFile(pwdString[5:]) + case strings.HasPrefix(pwdString, "pass="): + return []byte(pwdString[5:]), nil + case 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 + default: + return []byte(pwdString), nil + } +} + +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) + if err != nil { + return nil, fmt.Errorf("can not get encryption password: %w", err) + } + + vtpm, err := vtpm.NewVTPM(vtpmdev, encryptionPassword) + if err != nil { + 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, fmt.Errorf("can not start new vtpm: %w", err) + } + + return vtpm, nil +} + +func setVTPMHostDevOwner(vtpm *vtpm.VTPM, uid, gid int) error { + 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 + } + 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) + } +} + +// ApplyCGroupVTPMs puts all VTPMs into the given Cgroup manager's cgroup +func ApplyCGroupVTPMs(vtpms []*vtpm.VTPM, cgroupManager cgroups.Manager) error { + for _, vtpm := range vtpms { + 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 +} + +func CheckVTPMParams(vtpms []specs.LinuxVTPM) error { + namesMap := make(map[string]int, 0) + // 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 +} + +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/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go new file mode 100644 index 00000000000..acb4980e052 --- /dev/null +++ b/libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go @@ -0,0 +1,198 @@ +// + build linux + +package vtpmhelper + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "strconv" + "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) + 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) + } + } +} + +const ( + majorEnvName = "RUN_IN_CONTAINER_MAJOR" + minorEnvName = "RUN_IN_CONTAINER_MINOR" +) + +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 = 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 = int64(converted) + } + return major, minor, nil +} + +func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword 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{}, + }, + } + + major, minor, err := getDefaultMajorMinorDevices() + if err != nil { + t.Fatalf("Could not get default device major, minor: %s", err) + } + + vtpmdev := &specs.LinuxVTPM{ + StatePath: tpmdirname, + VTPMVersion: tpmversion, + CreateCertificates: createCertificates, + RunAs: runas, + EncryptionPassword: encryptionPassword, + VTPMMajor: major, + VTPMMinor: minor, + } + + myvtpm, err := CreateVTPM(spec, vtpmdev) + 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") + } + 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) { + myvtpm := createVTPM(t, tpmversion, createCertificates, runas, encryptionPassword) + + err := myvtpm.Stop(false) + if err != nil { + 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) + } + 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", "") +} + +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 new file mode 100644 index 00000000000..942b848c3c3 --- /dev/null +++ b/libcontainer/vtpm/vtpm.go @@ -0,0 +1,968 @@ +// + build linux + +package vtpm + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "os/user" + "path" + "path/filepath" + "strconv" + "syscall" + "time" + + "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"` + + // 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"` + + // 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 + + // 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 + + // The AppArmor profile's full path + aaprofile string + + // swtpm_setup capabilities + swtpmSetupCaps []string + + // swtpm capabilities + swtpmCaps []string +} + +const ( + VTPM_VERSION_1_2 = "1.2" + VTPM_VERSION_2 = "2" + SWTPM_CUSE_PERM_ERROR = 252 +) + +func translateUser(username string) (*user.User, error) { + 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 translatedUser, 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) { + output, err := cmd.Output() + if err != 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) + } + return caps.Features, nil +} + +func getSwtpmSetupCapabilities() ([]string, error) { + return getCapabilities(exec.Command("swtpm_setup", "--print-capabilities")) +} + +func getSwtpmCapabilities() ([]string, error) { + 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 { + 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 +// @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(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.") + } + + 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 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 + + swtpmSetupCaps, err := getSwtpmSetupCapabilities() + if err != nil { + return nil, err + } + swtpmCaps, err := getSwtpmCapabilities() + if err != nil { + return nil, err + } + + return &VTPM{ + user: runas, + StatePath: statepath, + StatePathIsManaged: statepathismanaged, + Vtpmversion: vtpmversion, + CreateCerts: createcerts, + PcrBanks: pcrbanks, + encryptionPassword: encryptionpassword, + swtpmSetupCaps: swtpmSetupCaps, + swtpmCaps: swtpmCaps, + VtpmName: vtpmname, + major: uint32(vtpmdev.VTPMMajor), + minor: uint32(vtpmdev.VTPMMinor), + }, 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") +} + +// getLogFile creates the full path of the TPM emulator log file +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()) + 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 +// 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 { + 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) + 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: 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 +func (vtpm *VTPM) stopByPidFile() error { + + pid, err := vtpm.getPidFromFile() + if err != nil { + return err + } + + p, err := os.FindProcess(pid) + if err != nil { + return err + } + + 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 +} + +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 +} + +// 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 +// +// 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 + } + + 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 { + 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 { + // 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 + } + 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 fmt.Errorf("error on walk: %w", 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 +} + +// 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 { + // 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 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") + 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()) + } + + if vtpm.passwordPipeError != nil { + return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError) + } + + 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 { + devpath := vtpm.GetTPMDevpath() + pidfile := vtpm.getPidFile() + + for loops >= 0 { + if _, err := os.Stat(pidfile); err != nil { + logrus.Errorf("swtpm process has terminated") + return fmt.Errorf("waitForTPMDevice swtpm process has terminated: %w", err) + } + + 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 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 { + return &os.PathError{Op: "mknod", Path: devpath, Err: err} + } + return nil + } + 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 { + tpm_dev_name := fmt.Sprintf("tpm%s", vtpm.VtpmName) + + err := vtpm.setupAppArmor() + 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()) + logfile := fmt.Sprintf("file=%s", vtpm.getLogFile()) + + flags := "not-need-init" + 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", + "--runas", userName.Username, + } + + 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") + } + + 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 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) + } + + // 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, 5) + if err != nil { + return fmt.Errorf("wait for PidFile: %w", err) + } + + err = vtpm.waitForTPMDevice(10) + if err != nil { + return fmt.Errorf("wait for waitForTPMDevice: %w", err) + } + + vtpm.resetSELinux() + vtpm.resetAppArmor() + + return nil +} + +// runSwtpmBios runs swtpm_bios to initialize the TPM +func (vtpm *VTPM) runSwtpmBios() error { + tpmname := vtpm.GetTPMDevpath() + + 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.chownStatePath() + if err != nil { + return false, err + } + + err = vtpm.runSwtpmSetup() + 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.teardownSELinux() + vtpm.teardownAppArmor() + + if deleteStatePath { + vtpm.DeleteStatePath() + } + + 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() +func (vtpm *VTPM) GetTPMDevpath() string { + return fmt.Sprintf("/dev/tpm%s", vtpm.VtpmName) +} + +// 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) +} + +// 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 + var tmpStateFilePattern string + + if !apparmor.IsEnabled() { + return nil + } + + 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 { + 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.*") + } + + // 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 wk,\n"+ + " %s wk,\n"+ + " %s rw,\n"+ + " %s rw,\n"+ + " %s rw,\n"+ + " %s rw,\n"+ + "}\n", + profilename, + vtpm.StatePath, + vtpm.getRuncLockFile(), + vtpm.getVTPMLockFile(), + vtpm.getLogFile(), + vtpm.getPidFile(), + statefilepattern, + tmpStateFilePattern) + + 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.ApplyProfile(profilename) + if err != nil { + return err + } + + return nil +} + +func (vtpm *VTPM) resetAppArmor() { + apparmor.ApplyProfile("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 = "" + } +} + +// 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 { + // 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 + } + if info.IsDir() && path != vtpm.StatePath { + return filepath.SkipDir + } + 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 + } + 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() +} diff --git a/script/swtpm.sh b/script/swtpm.sh new file mode 100755 index 00000000000..0e745fdbacb --- /dev/null +++ b/script/swtpm.sh @@ -0,0 +1,53 @@ +#!/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-gnutls --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/tests/cmd/tpm-helper/main.go b/tests/cmd/tpm-helper/main.go new file mode 100644 index 00000000000..d6ddd3fa75d --- /dev/null +++ b/tests/cmd/tpm-helper/main.go @@ -0,0 +1,252 @@ +package main + +import ( + "context" + "crypto/sha1" + "crypto/x509" + "flag" + "fmt" + "io" + "log" + "os" + "strconv" + "strings" + "time" + + "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 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 + } +} + +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 open tpm2 device path %s: %w", devicePath, err) + return + } + 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, 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 + } + 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) + } +} + +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, *deviceCommand, 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, *deviceCommand, 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..c987b0b201b --- /dev/null +++ b/tests/integration/swtpm.bats @@ -0,0 +1,505 @@ +#!/usr/bin/env bats +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 + 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() { + 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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + 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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + 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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + 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 + + 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 +} + +@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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + 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 + + 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" + 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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + wait_for_container 10 1 tst + + 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" + 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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst + [ "$status" -eq 0 ] + 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" + 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/ + # first container + vtpm_path=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.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 + + # second container + vtpm_pth1=$(mktemp -d) + update_config ' .process.args = ["/bin/sh"] + |.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 ] + wait_for_container 10 1 tst2 + + 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 + + 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 + + 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 tst1 + [ "$status" -eq 0 ] + + runc exec tst2 "${HELPER}" -devicePath=/dev/tpmtpmsame -deviceVersion=2 + [ "$status" -eq 0 ] +} + +@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": '"$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": '"$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 ] + + # 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 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst1 + + # 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. + 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": '"$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": '"$test_major_second"', "vtpmMinor": '"$test_minor"'}]' + runc run -d --console-socket "$CONSOLE_SOCKET" tst4 + [ "$status" -eq 0 ] + wait_for_container 10 1 tst4 +} + + +@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": '"$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 ] + + runc exec tst "${HELPER}" -devicePath=/dev/tpmtpmone -deviceVersion=2 + [ "$status" -eq 0 ] + + runc exec tst "${HELPER}" -devicePath=/dev/tpmtpmsecond -deviceVersion=2 + [ "$status" -eq 0 ] + + 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 + + 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": '"$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": '"$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 ] + + + 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": '"$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 ] + + 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 +} + +@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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + 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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + + 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 ] +} + +@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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + 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": '"$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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + 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": '"$test_major"', "vtpmMinor": '"$test_minor"'}]' + 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/utils_linux.go b/utils_linux.go index bd45bf05937..3d16af561e8 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -8,8 +8,12 @@ import ( "os" "path/filepath" "strconv" + "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" selinux "github.com/opencontainers/selinux/go-selinux" "github.com/sirupsen/logrus" @@ -179,7 +183,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 +196,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 +388,17 @@ func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.C notifySocket.setupSpec(spec) } - container, err := createContainer(context, id, spec) + vtpms, err := createVTPMs(context.GlobalString("root"), id, 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 +470,155 @@ func maybeLogCgroupWarning(op string, err error) { logrus.Warn("runc " + op + " failure might be caused by lack of full access to cgroups") } } + +// addVTPMDevice adds a device and cgroup entry to the spec +func addVTPMDevice(spec *specs.Spec, devpath string, major, minor uint32) { + var filemode os.FileMode = 0600 + + device := specs.LinuxDevice{ + Path: 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 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 { + devPath string + major uint32 + minor uint32 + } + var devices []vtpmLinuxDevice + + r := spec.Linux.Resources + if r == nil { + return vtpms, nil + } + + err := vtpmhelper.CheckVTPMParams(r.VTPMs) + if err != nil { + if vtpmhelper.CanIgnoreVTPMErrors() { + logrus.Errorf("createVTPMs has an error: %s", err) + return nil, nil + } + return nil, err + } + + 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 { + return vtpm, vtpmLinuxDevice{}, fmt.Errorf("createVTPMs has an error while checking dev path %s: %s", 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) + } + + devNumber := stat_t.Rdev + major = unix.Major(devNumber) + 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, + minor: minor, + } + return vtpm, device, nil + }(vtpmSpec) + + 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 +} + +func destroyVTPMs(vtpms []*vtpm.VTPM) { + if len(vtpms) > 0 { + 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/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/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/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/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index 854290da205..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. @@ -451,6 +453,30 @@ 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 + VTPMVersion 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"` + // Name of the vtpm + VTPMName string `json:"vtpmName,omitempty"` + // Device's major to be created + VTPMMajor int64 `json:"vtpmMajor,omitempty"` + // Device's minor to be created + VTPMMinor int64 `json:"vtpmMinor,omitempty"` +} + // LinuxResources has container runtime resource constraints type LinuxResources struct { // Devices configures the device allowlist. @@ -473,6 +499,8 @@ type LinuxResources struct { Rdma map[string]LinuxRdma `json:"rdma,omitempty"` // Unified resources. Unified map[string]string `json:"unified,omitempty"` + // Linux VTPM configuration + VTPMs []LinuxVTPM `json:"vtpms,omitempty"` } // LinuxDevice represents the mknod information for a Linux special device file @@ -836,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. @@ -884,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 c82c992879a..d35c50bd1c3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -36,6 +36,14 @@ 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/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 ## explicit; go 1.21 github.com/moby/sys/capability @@ -62,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 @@ -127,3 +135,5 @@ 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 +# github.com/opencontainers/runtime-spec => github.com/everzakov/runtime-spec v0.0.0-20250816064520-f0885e035161