Skip to content

Commit 0a352ff

Browse files
committed
vtpm: Add support for encrypted vTPM state
This patch adds support for encrypting the vTPM state by allowing a user to pass a password to swtpm_setup and swtpm. Signed-off-by: Stefan Berger <[email protected]>
1 parent 5b7bb1f commit 0a352ff

File tree

3 files changed

+157
-9
lines changed

3 files changed

+157
-9
lines changed

libcontainer/vtpm/vtpm-helper/vtpm_helper.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ package vtpmhelper
44

55
import (
66
"fmt"
7+
"io/ioutil"
78
"os"
9+
"strconv"
10+
"strings"
811
"syscall"
912

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

49+
// getEncryptionPassword gets the plain password from the caller
50+
// valid formats passed to this function are:
51+
// - <password>
52+
// - pass=<password>
53+
// - fd=<filedescriptor>
54+
// - file=<filename>
55+
func getEncryptionPassword(pwdString string) ([]byte, error) {
56+
if strings.HasPrefix(pwdString, "file=") {
57+
return ioutil.ReadFile(pwdString[5:])
58+
} else if strings.HasPrefix(pwdString, "pass=") {
59+
return []byte(pwdString[5:]), nil
60+
} else if strings.HasPrefix(pwdString, "fd=") {
61+
fdStr := pwdString[3:]
62+
fd, err := strconv.Atoi(fdStr)
63+
if err != nil {
64+
return nil, fmt.Errorf("could not parse file descriptor %s", fdStr)
65+
}
66+
f := os.NewFile(uintptr(fd), "pwdfile")
67+
if f == nil {
68+
return nil, fmt.Errorf("%s is not a valid file descriptor", fdStr)
69+
}
70+
defer f.Close()
71+
pwd := make([]byte, 1024)
72+
n, err := f.Read(pwd)
73+
if err != nil {
74+
return nil, fmt.Errorf("could not read from file descriptor: %v", err)
75+
}
76+
return pwd[:n], nil
77+
}
78+
return []byte(pwdString), nil
79+
}
80+
4681
// CreateVTPM create a VTPM proxy device and starts the TPM emulator with it
4782
func CreateVTPM(spec *specs.Spec, vtpmdev *specs.LinuxVTPM, devnum int) (*vtpm.VTPM, error) {
83+
encryptionPassword, err := getEncryptionPassword(vtpmdev.EncryptionPassword)
84+
if err != nil {
85+
return nil, err
86+
}
4887

49-
vtpm, err := vtpm.NewVTPM(vtpmdev.StatePath, vtpmdev.StatePathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.RunAs, vtpmdev.PcrBanks)
88+
vtpm, err := vtpm.NewVTPM(vtpmdev.StatePath, vtpmdev.StatePathIsManaged, vtpmdev.TPMVersion, vtpmdev.CreateCertificates, vtpmdev.RunAs, vtpmdev.PcrBanks, encryptionPassword)
5089
if err != nil {
5190
return nil, err
5291
}

libcontainer/vtpm/vtpm-helper/vtpm_helper_test.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package vtpmhelper
44

55
import (
6+
"fmt"
67
"io/ioutil"
78
"os"
89
"os/exec"
@@ -36,7 +37,7 @@ func checkPrerequisites(t *testing.T) {
3637
}
3738
}
3839

39-
func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas string) *vtpm.VTPM {
40+
func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword string) *vtpm.VTPM {
4041

4142
checkPrerequisites(t)
4243

@@ -59,6 +60,7 @@ func createVTPM(t *testing.T, tpmversion string, createCertificates bool, runas
5960
TPMVersion: tpmversion,
6061
CreateCertificates: createCertificates,
6162
RunAs: runas,
63+
EncryptionPassword: encryptionPassword,
6264
}
6365

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

85-
func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificates bool, runas string) {
86-
myvtpm := createVTPM(t, tpmversion, createCertificates, runas)
87+
func createRestartDestroyVTPM(t *testing.T, tpmversion string, createCertificates bool, runas, encryptionPassword string) {
88+
myvtpm := createVTPM(t, tpmversion, createCertificates, runas, encryptionPassword)
8789

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

104106
func TestCreateVTPM2(t *testing.T) {
105-
createRestartDestroyVTPM(t, "", true, "root")
106-
createRestartDestroyVTPM(t, "", false, "0")
107-
createRestartDestroyVTPM(t, "2", true, "0")
107+
createRestartDestroyVTPM(t, "", true, "root", "")
108+
createRestartDestroyVTPM(t, "", false, "0", "")
109+
createRestartDestroyVTPM(t, "2", true, "0", "")
108110
}
109111

110112
func TestCreateVTPM12(t *testing.T) {
111-
createRestartDestroyVTPM(t, "1.2", true, "root")
113+
createRestartDestroyVTPM(t, "1.2", true, "root", "")
114+
}
115+
116+
func TestCreateEncryptedVTPM_Pipe(t *testing.T) {
117+
checkPrerequisites(t)
118+
119+
piper, pipew, err := os.Pipe()
120+
if err != nil {
121+
t.Fatalf("Could not create pipe")
122+
}
123+
defer piper.Close()
124+
125+
password := "123456"
126+
127+
// pass password via write to pipe
128+
go func() {
129+
n, err := pipew.Write([]byte(password))
130+
if err != nil {
131+
t.Fatalf("Could not write to pipe: %v", err)
132+
}
133+
if n != len(password) {
134+
t.Fatalf("Could not write all data to pipe")
135+
}
136+
pipew.Close()
137+
}()
138+
createRestartDestroyVTPM(t, "", true, "root", fmt.Sprintf("fd=%d", piper.Fd()))
139+
}
140+
141+
func TestCreateEncryptedVTPM_File(t *testing.T) {
142+
fil, err := ioutil.TempFile("", "passwordfile")
143+
if err != nil {
144+
t.Fatalf("Could not create temporary file: %v", err)
145+
}
146+
defer os.Remove(fil.Name())
147+
148+
_, err = fil.WriteString("123456")
149+
if err != nil {
150+
t.Fatalf("Could not write to temporary file: %v", err)
151+
}
152+
createRestartDestroyVTPM(t, "", true, "root", fmt.Sprintf("file=%s", fil.Name()))
153+
}
154+
155+
func TestCreateEncryptedVTPM_Direct(t *testing.T) {
156+
createRestartDestroyVTPM(t, "", true, "root", "pass=123456")
157+
createRestartDestroyVTPM(t, "", true, "root", "123456")
112158
}

libcontainer/vtpm/vtpm.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ type VTPM struct {
4343
// Set of active PCR banks
4444
PcrBanks string `json:"pcrbanks"`
4545

46+
// plain text encryption password used by vTPM
47+
encryptionPassword []byte
48+
49+
// whether an error occurred writing the password to the pipe
50+
passwordPipeError error
51+
4652
// The user under which to run the TPM emulator
4753
user string
4854

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

463+
// setup the password pipe so that we can transfer the TPM state encryption via
464+
// a pipe where the read-end is passed to swtpm / swtpm_setup as a file descriptor
465+
func (vtpm *VTPM) setupPasswordPipe(password []byte) (*os.File, error) {
466+
if !hasCapability(vtpm.swtpmSetupCaps, "cmdarg-pwdfile-fd") {
467+
return nil, fmt.Errorf("Requiring newer version of swtpm for state encryption; needs cmdarg-pwd-fd feature")
468+
}
469+
470+
piper, pipew, err := os.Pipe()
471+
if err != nil {
472+
return nil, fmt.Errorf("Could not create pipe")
473+
}
474+
vtpm.passwordPipeError = nil
475+
476+
go func() {
477+
tot := 0
478+
for tot < len(password) {
479+
var n int
480+
n, vtpm.passwordPipeError = pipew.Write(password)
481+
if vtpm.passwordPipeError != nil {
482+
break
483+
}
484+
tot = tot + n
485+
}
486+
pipew.Close()
487+
}()
488+
return piper, nil
489+
}
490+
456491
// runSwtpmSetup runs swtpm_setup to simulate TPM manufacturing by creating
457492
// EK and platform certificates and enabling TPM 2 PCR banks
458493
func (vtpm *VTPM) runSwtpmSetup() error {
@@ -470,6 +505,16 @@ func (vtpm *VTPM) runSwtpmSetup() error {
470505
if vtpm.CreateCerts {
471506
cmd.Args = append(cmd.Args, "--create-ek-cert", "--create-platform-cert", "--lock-nvram")
472507
}
508+
if len(vtpm.encryptionPassword) > 0 {
509+
piper, err := vtpm.setupPasswordPipe(vtpm.encryptionPassword)
510+
if err != nil {
511+
return err
512+
}
513+
cmd.ExtraFiles = append(cmd.ExtraFiles, piper)
514+
pwdfile_fd := fmt.Sprintf("%d", 3+len(cmd.ExtraFiles)-1)
515+
cmd.Args = append(cmd.Args, "--cipher", "aes-256-cbc", "--pwdfile-fd", pwdfile_fd)
516+
defer piper.Close()
517+
}
473518

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

536+
if vtpm.passwordPipeError != nil {
537+
return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError)
538+
}
539+
491540
return nil
492541
}
493542

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

600+
if len(vtpm.encryptionPassword) > 0 {
601+
piper, err := vtpm.setupPasswordPipe(vtpm.encryptionPassword)
602+
if err != nil {
603+
return err
604+
}
605+
cmd.ExtraFiles = append(cmd.ExtraFiles, piper)
606+
cmd.Args = append(cmd.Args, "--key",
607+
fmt.Sprintf("pwdfd=%d,mode=aes-256-cbc,kdf=pbkdf2", 3+len(cmd.ExtraFiles)-1))
608+
defer piper.Close()
609+
}
610+
551611
output, err := cmd.CombinedOutput()
552612
if err != nil {
553613
return fmt.Errorf("swtpm failed on fd %d: %s\nlog: %s", vtpm.fd, string(output), vtpm.ReadLog())
554614
}
615+
if vtpm.passwordPipeError != nil {
616+
return fmt.Errorf("Error transferring password using pipe: %v", vtpm.passwordPipeError)
617+
}
555618

556619
vtpm.Pid, err = vtpm.waitForPidFile(10)
557620
if err != nil {

0 commit comments

Comments
 (0)