blob: 5691c86d7fac6b120f19bad34f3623a326d17bb4 [file] [log] [blame]
Tim Windelschmidt9f21f532024-05-07 15:14:20 +02001package launch
Serge Bazanski2b6dc312024-06-04 17:44:55 +02002
3import (
4 "context"
5 "fmt"
6 "log"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "sort"
11 "strings"
12
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020013 "source.monogon.dev/osbase/test/launch"
Serge Bazanski2b6dc312024-06-04 17:44:55 +020014)
15
16// A TPMFactory manufactures virtual TPMs using swtpm.
17//
18// A factory has an assigned state directory into which it will write per-factory
19// data (like CA certificates and keys). Each manufactured TPM also has a state
20// directory, which is first generated on manufacturing, and then passed to an
21// swtpm instance.
22type TPMFactory struct {
23 stateDir string
24}
25
26// NewTPMFactory creates a new TPM factory at a given state path. The state path
27// is a directory used to persist TPM factory data. It will be created if needed,
28// and can be reused across TPM factories (but not used in parallel).
29func NewTPMFactory(stateDir string) (*TPMFactory, error) {
30 if err := os.MkdirAll(stateDir, 0744); err != nil {
31 return nil, fmt.Errorf("could not create state directory: %w", err)
32 }
33
34 f := &TPMFactory{
35 stateDir: stateDir,
36 }
37
38 if err := os.MkdirAll(f.caDir(), 0700); err != nil {
39 return nil, fmt.Errorf("could not create CA state directory: %w", err)
40 }
41 err := writeSWTPMConfig(f.localCAConfPath(), map[string]string{
42 "statedir": f.caDir(),
43 "signingkey": filepath.Join(f.caDir(), "signkey.pem"),
44 "issuercert": filepath.Join(f.caDir(), "issuercert.pem"),
45 "certserial": filepath.Join(f.caDir(), "certserial"),
46 })
47 if err != nil {
48 return nil, err
49 }
50 return f, nil
51}
52
53func (f *TPMFactory) caDir() string {
54 return filepath.Join(f.stateDir, "ca")
55}
56
57func (f *TPMFactory) localCAConfPath() string {
58 return filepath.Join(f.caDir(), "swtpm-localca.conf")
59}
60
61func (f *TPMFactory) localCAOptionsPath() string {
62 return filepath.Join(f.caDir(), "swtpm-localca.options")
63}
64
65func (f *TPMFactory) swtpmConfPath() string {
66 return filepath.Join(f.stateDir, "swtpm.conf")
67}
68
69// writeSWTPMConfig serializes a key/value config file for swtpm tools into a
70// path.
71func writeSWTPMConfig(path string, data map[string]string) error {
72 var keys []string
73 for k := range data {
74 keys = append(keys, k)
75 }
76 sort.Strings(keys)
77
78 f, err := os.Create(path)
79 if err != nil {
80 return err
81 }
82 defer f.Close()
83 for _, k := range keys {
84 if _, err := fmt.Fprintf(f, "%s = %s\n", k, data[k]); err != nil {
85 return err
86 }
87 }
88 return nil
89}
90
91// A TPMPlatform defines a platform that a TPM is part of. This will usually be
92// some kind of device, in this case a virtual device.
93type TPMPlatform struct {
94 Manufacturer string
95 Version string
96 Model string
97}
98
99// Manufacture builds a new TPM for a given platform at a path. The path points
100// to a directory that will be created if it doens't exist yet, and can be passed
101// to swtpm to actually emulate the created TPM.
102func (f *TPMFactory) Manufacture(ctx context.Context, path string, platform *TPMPlatform) error {
103 launch.Log("Starting to manufacture TPM for %s... (%+v)", path, platform)
104
105 // Path to state file. Used to make sure Manufacture runs only once.
106 permall := filepath.Join(path, "tpm2-00.permall")
107
108 if _, err := os.Stat(permall); err == nil {
109 launch.Log("Skipping manufacturing TPM for %s, already exists", path)
110 return nil
111 }
Serge Bazanski2b6dc312024-06-04 17:44:55 +0200112 // Prepare swtpm-localca.options.
113 options := []string{
114 "--platform-manufacturer " + platform.Manufacturer,
115 "--platform-version " + platform.Version,
116 "--platform-model " + platform.Model,
117 "",
118 }
Tim Windelschmidt82e6af72024-07-23 00:05:42 +0000119 err := os.WriteFile(f.localCAOptionsPath(), []byte(strings.Join(options, "\n")), 0600)
Serge Bazanski2b6dc312024-06-04 17:44:55 +0200120 if err != nil {
121 return fmt.Errorf("could not write local options: %w", err)
122 }
123
124 // Prepare swptm.conf.
125 err = writeSWTPMConfig(f.swtpmConfPath(), map[string]string{
Tim Windelschmidt82e6af72024-07-23 00:05:42 +0000126 "create_certs_tool": xSwtpmLocalCAPath,
Serge Bazanski2b6dc312024-06-04 17:44:55 +0200127 "create_certs_tool_config": f.localCAConfPath(),
128 "create_certs_tool_options": f.localCAOptionsPath(),
129 })
130 if err != nil {
131 return fmt.Errorf("could not write swtpm.conf: %w", err)
132 }
133
134 if err := os.MkdirAll(path, 0700); err != nil {
135 return fmt.Errorf("could not make output path: %w", err)
136 }
Tim Windelschmidt82e6af72024-07-23 00:05:42 +0000137 cmd := exec.CommandContext(ctx, xSwtpmSetupPath,
138 "--tpm", fmt.Sprintf("%s socket", xSwtpmPath),
Serge Bazanski2b6dc312024-06-04 17:44:55 +0200139 "--tpmstate", path,
140 "--create-ek-cert",
141 "--create-platform-cert",
142 "--allow-signing",
143 "--tpm2",
144 "--display",
145 "--pcr-banks", "sha1,sha256,sha384,sha512",
146 "--config", f.swtpmConfPath())
Tim Windelschmidt82e6af72024-07-23 00:05:42 +0000147 cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s:%s", filepath.Dir(xSwtpmCertPath), filepath.Dir(xCerttoolPath)))
Serge Bazanski2b6dc312024-06-04 17:44:55 +0200148 cmd.Env = append(cmd.Env, "MONOGON_LIBTPMS_ACKNOWLEDGE_UNSAFE=yes")
149 if out, err := cmd.CombinedOutput(); err != nil {
150 log.Printf("Manufacturing TPM for %s failed: swtm_setup: %s", path, out)
151 return fmt.Errorf("swtpm_setup failed: %w", err)
152 }
153
154 if _, err := os.Stat(permall); os.IsNotExist(err) {
155 log.Printf("Manufacturing TPM for %s failed: state file did not get created", path)
156 return fmt.Errorf("%s did not get created during TPM manufacture", permall)
157 }
158
159 launch.Log("Successfully manufactured TPM for %s", path)
160 return nil
161}