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