Skip to content

Commit

Permalink
vtpm: Add support for encrypted vTPM state
Browse files Browse the repository at this point in the history
This patch adds support for encrypting the vTPM state by allowing
a user to pass a password to swtpm_setup and swtpm.

Signed-off-by: Stefan Berger <[email protected]>
  • Loading branch information
stefanberger committed Jul 6, 2020
1 parent e2e3c97 commit 7279c3e
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 9 deletions.
41 changes: 40 additions & 1 deletion libcontainer/vtpm/vtpm-helper/vtpm_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ package vtpmhelper

import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"syscall"

"github.com/opencontainers/runc/libcontainer/configs"
Expand Down Expand Up @@ -43,10 +46,46 @@ func addVTPMDevice(spec *specs.Spec, hostpath, devpath string, major, minor uint
spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, *ld)
}

// getEncryptionPassword gets the plain password from the caller
// valid formats passed to this function are:
// - <password>
// - pass=<password>
// - fd=<filedescriptor>
// - file=<filename>
func getEncryptionPassword(pwdString string) ([]byte, error) {
if strings.HasPrefix(pwdString, "file=") {
return ioutil.ReadFile(pwdString[5:])
} else if strings.HasPrefix(pwdString, "pass=") {
return []byte(pwdString[5:]), nil
} else if strings.HasPrefix(pwdString, "fd=") {
fdStr := pwdString[3:]
fd, err := strconv.Atoi(fdStr)
if err != nil {
return nil, fmt.Errorf("could not parse file descriptor %s", fdStr)
}
f := os.NewFile(uintptr(fd), "pwdfile")
if f == nil {
return nil, fmt.Errorf("%s is not a valid file descriptor", fdStr)
}
defer f.Close()
pwd := make([]byte, 1024)
n, err := f.Read(pwd)
if err != nil {
return nil, fmt.Errorf("could not read from file descriptor: %v", err)
}
return pwd[:n], nil
}
return []byte(pwdString), nil
}

// CreateVTPM create a VTPM proxy device and starts the TPM emulator with it
func CreateVTPM(spec *specs.Spec, vtpmdev *specs.VTPM, devnum int) (*vtpm.VTPM, error) {
encryptionPassword, err := getEncryptionPassword(vtpmdev.EncryptionPassword)
if err != nil {
return nil, err
}

vtpm, err := vtpm.NewVTPM(vtpmdev.Statepath, vtpmdev.StatepathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.Runas, vtpmdev.PcrBanks)
vtpm, err := vtpm.NewVTPM(vtpmdev.Statepath, vtpmdev.StatepathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.Runas, vtpmdev.PcrBanks, encryptionPassword)
if err != nil {
return nil, err
}
Expand Down
60 changes: 53 additions & 7 deletions libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package vtpmhelper

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
Expand Down Expand Up @@ -36,7 +37,7 @@ func checkPrerequisites(t *testing.T) {
}
}

func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas string) *vtpm.VTPM {
func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword string) *vtpm.VTPM {

checkPrerequisites(t)

Expand All @@ -59,6 +60,7 @@ func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas
TPMVersion: tpmversion,
CreateCertificates: createCertificates,
Runas: runas,
EncryptionPassword: encryptionPassword,
}

myvtpm, err := CreateVTPM(spec, vtpmdev, 0)
Expand All @@ -82,8 +84,8 @@ func destroyVTPM(t *testing.T, myvtpm *vtpm.VTPM) {
}
}

func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificates bool, runas string) {
myvtpm := createVTPM(t, tpmversion, createCertificates, runas)
func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword string) {
myvtpm := createVTPM(t, tpmversion, createCertificates, runas, encryptionPassword)

err := myvtpm.Stop(false)
if err != nil {
Expand All @@ -102,11 +104,55 @@ func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificate
}

func TestCreateVTPM2(t *testing.T) {
createRestartDestroyVTPM(t, "", true, "root")
createRestartDestroyVTPM(t, "", false, "0")
createRestartDestroyVTPM(t, "2", true, "0")
createRestartDestroyVTPM(t, "", true, "root", "")
createRestartDestroyVTPM(t, "", false, "0", "")
createRestartDestroyVTPM(t, "2", true, "0", "")
}

func TestCreateVTPM12(t *testing.T) {
createRestartDestroyVTPM(t, "1.2", true, "root")
createRestartDestroyVTPM(t, "1.2", true, "root", "")
}

func TestCreateEncryptedVTPM_Pipe(t *testing.T) {
checkPrerequisites(t)

piper, pipew, err := os.Pipe()
if err != nil {
t.Fatalf("Could not create pipe")
}
defer piper.Close()

password := "123456"

// pass password via write to pipe
go func() {
n, err := pipew.Write([]byte(password))
if err != nil {
t.Fatalf("Could not write to pipe: %v", err)
}
if n != len(password) {
t.Fatalf("Could not write all data to pipe")
}
pipew.Close()
}()
createRestartDestroyVTPM(t, "", true, "root", fmt.Sprintf("fd=%d", piper.Fd()))
}

func TestCreateEncryptedVTPM_File(t *testing.T) {
fil, err := ioutil.TempFile("", "passwordfile")
if err != nil {
t.Fatalf("Could not create temporary file: %v", err)
}
defer os.Remove(fil.Name())

_, err = fil.WriteString("123456")
if err != nil {
t.Fatalf("Could not write to temporary file: %v", err)
}
createRestartDestroyVTPM(t, "", true, "root", fmt.Sprintf("file=%s", fil.Name()))
}

func TestCreateEncryptedVTPM_Direct(t *testing.T) {
createRestartDestroyVTPM(t, "", true, "root", "pass=123456")
createRestartDestroyVTPM(t, "", true, "root", "123456")
}
65 changes: 64 additions & 1 deletion libcontainer/vtpm/vtpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ type VTPM struct {
// Set of active PCR banks
PcrBanks string `json:"pcrbanks"`

// plain text encryption password used by vTPM
encryptionPassword []byte

// whether an error occurred writing the password to the pipe
passwordPipeError error

// The user under which to run the TPM emulator
user string

Expand Down Expand Up @@ -182,7 +188,7 @@ func hasCapability(capabilities []string, capability string) bool {
// with account tss; TPM 2 has more flexibility
//
// After successful creation of the object the Start() method can be called
func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, createcerts bool, runas string, pcrbanks string) (*VTPM, error) {
func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, createcerts bool, runas string, pcrbanks string, encryptionpassword []byte) (*VTPM, error) {
if len(statepath) == 0 {
return nil, fmt.Errorf("Missing required statpath for vTPM.")
}
Expand Down Expand Up @@ -231,6 +237,7 @@ func NewVTPM(statepath string, statepathismanaged bool, vtpmversion string, crea
Vtpmversion: vtpmversion,
CreateCerts: createcerts,
PcrBanks: pcrbanks,
encryptionPassword: encryptionpassword,
Tpm_dev_num: VTPM_DEV_NUM_INVALID,
fd: ILLEGAL_FD,
swtpmSetupCaps: swtpmSetupCaps,
Expand Down Expand Up @@ -453,6 +460,34 @@ func (vtpm *VTPM) chownStatePath() error {
return nil
}

// setup the password pipe so that we can transfer the TPM state encryption via
// a pipe where the read-end is passed to swtpm / swtpm_setup as a file descriptor
func (vtpm *VTPM) setupPasswordPipe(password []byte) (*os.File, error) {
if !hasCapability(vtpm.swtpmSetupCaps, "cmdarg-pwdfile-fd") {
return nil, fmt.Errorf("Requiring newer version of swtpm for state encryption; needs cmdarg-pwd-fd feature")
}

piper, pipew, err := os.Pipe()
if err != nil {
return nil, fmt.Errorf("Could not create pipe")
}
vtpm.passwordPipeError = nil

go func() {
tot := 0
for tot < len(password) {
var n int
n, vtpm.passwordPipeError = pipew.Write(password)
if vtpm.passwordPipeError != nil {
break
}
tot = tot + n
}
pipew.Close()
}()
return piper, nil
}

// runSwtpmSetup runs swtpm_setup to simulate TPM manufacturing by creating
// EK and platform certificates and enabling TPM 2 PCR banks
func (vtpm *VTPM) runSwtpmSetup() error {
Expand All @@ -470,6 +505,16 @@ func (vtpm *VTPM) runSwtpmSetup() error {
if vtpm.CreateCerts {
cmd.Args = append(cmd.Args, "--create-ek-cert", "--create-platform-cert", "--lock-nvram")
}
if len(vtpm.encryptionPassword) > 0 {
piper, err := vtpm.setupPasswordPipe(vtpm.encryptionPassword)
if err != nil {
return err
}
cmd.ExtraFiles = append(cmd.ExtraFiles, piper)
pwdfile_fd := fmt.Sprintf("%d", 3+len(cmd.ExtraFiles)-1)
cmd.Args = append(cmd.Args, "--cipher", "aes-256-cbc", "--pwdfile-fd", pwdfile_fd)
defer piper.Close()
}

if vtpm.Vtpmversion == VTPM_VERSION_2 {
cmd.Args = append(cmd.Args, "--tpm2")
Expand All @@ -488,6 +533,10 @@ func (vtpm *VTPM) runSwtpmSetup() error {
return fmt.Errorf("swtpm_setup failed: %s\nlog: %s", string(output), vtpm.ReadLog())
}

if vtpm.passwordPipeError != nil {
return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError)
}

return nil
}

Expand Down Expand Up @@ -548,10 +597,24 @@ func (vtpm *VTPM) startSwtpm() error {
file := os.NewFile(uintptr(vtpm.fd), "[vtpm]")
cmd.ExtraFiles = append(cmd.ExtraFiles, file)

if len(vtpm.encryptionPassword) > 0 {
piper, err := vtpm.setupPasswordPipe(vtpm.encryptionPassword)
if err != nil {
return err
}
cmd.ExtraFiles = append(cmd.ExtraFiles, piper)
cmd.Args = append(cmd.Args, "--key",
fmt.Sprintf("pwdfd=%d,mode=aes-256-cbc,kdf=pbkdf2", 3+len(cmd.ExtraFiles)-1))
defer piper.Close()
}

output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("swtpm failed on fd %d: %s\nlog: %s", vtpm.fd, string(output), vtpm.ReadLog())
}
if vtpm.passwordPipeError != nil {
return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError)
}

vtpm.Pid, err = vtpm.waitForPidFile(10)
if err != nil {
Expand Down

0 comments on commit 7279c3e

Please sign in to comment.