Skip to content

Commit

Permalink
govc: Cmd to enc data for VMs w TPM2 devs
Browse files Browse the repository at this point in the history
This patch introduces support for encrypting plain-text
information for VMs with TPM2 devices without the system
on which the command is run needing a TPM.

Please refer to google/go-tpm#343
for more information.
  • Loading branch information
akutz committed Sep 4, 2023
1 parent 5574c08 commit 4b64181
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 0 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d
github.com/dougm/pretty v0.0.0-20171025230240-2ee9d7453c02
github.com/google/go-cmp v0.5.9
github.com/google/go-tpm v0.0.0-00010101000000-000000000000
github.com/google/uuid v1.3.1
github.com/rasky/go-xdr v0.0.0-20170217172119-4930550ba2e2
github.com/stretchr/testify v1.8.4
Expand All @@ -17,6 +18,9 @@ require (
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.8.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/google/go-tpm => github.com/akutz/go-tpm v0.0.0-20230904203701-1d12d24e581e
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d h1:4E8RufAN3UQ/weB6AnQ4y5miZCO0Yco8ZdGId41WuQs=
github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg=
github.com/akutz/go-tpm v0.0.0-20230904203701-1d12d24e581e h1:3+q0gcMmpQWxi4UI+KEmyC1OUBD4m2/O7Bcb2J/5AQ4=
github.com/akutz/go-tpm v0.0.0-20230904203701-1d12d24e581e/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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=
Expand All @@ -26,6 +28,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728 h1:sH9mEk+flyDxiUa5BuPiuhDETMbzrt9A20I2wktMvRQ=
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
1 change: 1 addition & 0 deletions govc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import (
_ "github.com/vmware/govmomi/govc/vm/option"
_ "github.com/vmware/govmomi/govc/vm/rdm"
_ "github.com/vmware/govmomi/govc/vm/snapshot"
_ "github.com/vmware/govmomi/govc/vm/tpm2"
_ "github.com/vmware/govmomi/govc/volume"
_ "github.com/vmware/govmomi/govc/volume/snapshot"
_ "github.com/vmware/govmomi/govc/vsan"
Expand Down
178 changes: 178 additions & 0 deletions govc/vm/tpm2/seal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
Copyright (c) 2023 VMware, Inc. 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 (
"context"
"crypto/x509"
"encoding/base64"
"flag"
"fmt"
"io"
"os"

"github.com/vmware/govmomi/govc/cli"
"github.com/vmware/govmomi/govc/flags"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"

"github.com/google/go-tpm/tpm2"
)

type seal struct {
*flags.VirtualMachineFlag
*flags.OutputFlag

input string
format string
}

func init() {
cli.Register("vm.tpm2.seal", &seal{})
}

func (cmd *seal) Register(ctx context.Context, f *flag.FlagSet) {
cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx)
cmd.VirtualMachineFlag.Register(ctx, f)
cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
cmd.OutputFlag.Register(ctx, f)

f.StringVar(&cmd.input, "in", "-", `Input data. Defaults to STDIN via "-"`)
f.StringVar(&cmd.format, "of", "json",
"Output format. Options are \"json\", \"cmds\", and \"0\". "+
"The format \"json\" emits the encrypted data as a JSON object. "+
"The format \"cmds\" emits three shell commands that can be copied "+
"and pasted into another system to easily create the encrypted "+
"data's three parts as files. "+
"The format \"0\" emits the encrypted data as base64-encoded"+
"data structures delimited by the string \"@@NULL@@\".")
}

func (cmd *seal) Description() string {
return `Seal plain-text data to the VM's TPM2 endorsement key.
Examples:
govc vm.tpm2.seal -vm VM -in plain.txt
echo "Hello, world" | govc vm.tpm2.seal -vm VM`
}

func (cmd *seal) Process(ctx context.Context) error {
if err := cmd.VirtualMachineFlag.Process(ctx); err != nil {
return err
}
if err := cmd.OutputFlag.Process(ctx); err != nil {
return err
}
return nil
}

func (cmd *seal) Run(ctx context.Context, f *flag.FlagSet) error {
vm, err := cmd.VirtualMachine()
if err != nil {
return err
}

if vm == nil {
return flag.ErrHelp
}

// Read the plain-text data.
var plainTextFile *os.File
if cmd.input == "-" {
plainTextFile = os.Stdin
} else {
f, err := os.Open(cmd.input)
if err != nil {
return err
}
plainTextFile = f
defer f.Close()
}
plainTextData, err := io.ReadAll(plainTextFile)
if err != nil {
return err
}

// Get the VM's EK.
var moVM mo.VirtualMachine
if err := vm.Properties(
ctx,
vm.Reference(),
[]string{"config.hardware.device"},
&moVM); err != nil {
return err
}

devices := object.VirtualDeviceList(moVM.Config.Hardware.Device)
selectedDevices := devices.SelectByType(&types.VirtualTPM{})
if len(selectedDevices) == 0 {
return fmt.Errorf("no VirtualTPM devices found")
}
if len(selectedDevices) > 1 {
return fmt.Errorf("multiple VirtualTPM devices found")
}

vtpmDev := selectedDevices[0].(*types.VirtualTPM)

// There will be two EK certs. The first is RSA and the second is ECC.
// We want the RSA cert. Also, using DecodeString since Decode leaves
// trailing data.
certDer, err := base64.StdEncoding.DecodeString(
string(vtpmDev.EndorsementKeyCertificate[0]))
if err != nil {
return err
}
base64.StdEncoding.Decode(certDer, vtpmDev.EndorsementKeyCertificate[0])
cert, err := x509.ParseCertificate(certDer)
if err != nil {
return err
}

ek, err := tpm2.EKCertToTPMTPublic(*cert)
if err != nil {
return err
}

pub, priv, seed, err := tpm2.EKSeal(ek, plainTextData)
if err != nil {
return err
}

return cmd.WriteResult(sealResult{
Public: base64.StdEncoding.EncodeToString(tpm2.Marshal(pub)),
Private: base64.StdEncoding.EncodeToString(tpm2.Marshal(priv)),
Seed: base64.StdEncoding.EncodeToString(tpm2.Marshal(seed)),
})
}

type sealResult struct {
Public string `json:"public"`
Private string `json:"private"`
Seed string `json:"seed"`
}

func (r sealResult) Write(w io.Writer) error {
_, err := fmt.Fprintf(
w,
"%s@@NULL@@%s@@NULL@@%s",
r.Public,
r.Private,
r.Seed,
)
return err
}

0 comments on commit 4b64181

Please sign in to comment.