blob: e3973d1c284ad3f7932ef244720595e31b4a89a7 [file] [log] [blame]
Lorenz Brunae0d90d2019-09-05 17:53:56 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package tpm
18
19import (
20 "crypto/rand"
21 "fmt"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020022 "git.monogon.dev/source/nexantic.git/core/pkg/sysfs"
Lorenz Brunae0d90d2019-09-05 17:53:56 +020023 "io"
24 "os"
25 "path/filepath"
Lorenz Brunae0d90d2019-09-05 17:53:56 +020026 "strconv"
27 "sync"
28
29 "github.com/gogo/protobuf/proto"
30 tpmpb "github.com/google/go-tpm-tools/proto"
31 "github.com/google/go-tpm-tools/tpm2tools"
32 "github.com/google/go-tpm/tpm2"
33 "github.com/pkg/errors"
34 "go.uber.org/zap"
35 "golang.org/x/sys/unix"
36)
37
38var (
Leopold Schabel68c58752019-11-14 21:00:59 +010039 // SecureBootPCRs are all PCRs that measure the current Secure Boot configuration.
40 // This is what we want if we rely on secure boot to verify boot integrity. The firmware
41 // hashes the secure boot policy and custom keys into the PCR.
42 //
43 // This requires an extra step that provisions the custom keys.
44 //
45 // Some background: https://mjg59.dreamwidth.org/48897.html?thread=1847297
46 // (the initramfs issue mentioned in the article has been solved by integrating
47 // it into the kernel binary, and we don't have a shim bootloader)
48 //
49 // PCR7 alone is not sufficient - it needs to be combined with firmware measurements.
Lorenz Brunae0d90d2019-09-05 17:53:56 +020050 SecureBootPCRs = []int{7}
51
52 // FirmwarePCRs are alle PCRs that contain the firmware measurements
53 // See https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf
Leopold Schabel68c58752019-11-14 21:00:59 +010054 FirmwarePCRs = []int{
55 0, // platform firmware
56 2, // option ROM code
57 3, // option ROM configuration and data
58 }
Lorenz Brunae0d90d2019-09-05 17:53:56 +020059
Leopold Schabel68c58752019-11-14 21:00:59 +010060 // FullSystemPCRs are all PCRs that contain any measurements up to the currently running EFI payload.
61 FullSystemPCRs = []int{
62 0, // platform firmware
63 1, // host platform configuration
64 2, // option ROM code
65 3, // option ROM configuration and data
66 4, // EFI payload
67 }
68
69 // Using FullSystemPCRs is the most secure, but also the most brittle option since updating the EFI
70 // binary, updating the platform firmware, changing platform settings or updating the binary
71 // would invalidate the sealed data. It's annoying (but possible) to predict values for PCR4,
72 // and even more annoying for the firmware PCR (comparison to known values on similar hardware
73 // is the only thing that comes to mind).
74 //
75 // See also: https://github.com/mxre/sealkey (generates PCR4 from EFI image, BSD license)
76 //
77 // Using only SecureBootPCRs is the easiest and still reasonably secure, if we assume that the
78 // platform knows how to take care of itself (i.e. Intel Boot Guard), and that secure boot
79 // is implemented properly. It is, however, a much larger amount of code we need to trust.
80 //
81 // We do not care about PCR 5 (GPT partition table) since modifying it is harmless. All of
82 // the boot options and cmdline are hardcoded in the kernel image, and we use no bootloader,
83 // so there's no PCR for bootloader configuration or kernel cmdline.
Lorenz Brunae0d90d2019-09-05 17:53:56 +020084)
85
86var (
87 // ErrNotExists is returned when no TPMs are available in the system
88 ErrNotExists = errors.New("no TPMs found")
89 // ErrNotInitialized is returned when this package was not initialized successfully
90 ErrNotInitialized = errors.New("no TPM was initialized")
91)
92
93// Singleton since the TPM is too
94var tpm *TPM
95
96// We're serializing all TPM operations since it has a limited number of handles and recovering
97// if it runs out is difficult to implement correctly. Might also be marginally more secure.
98var lock sync.Mutex
99
100// TPM represents a high-level interface to a connected TPM 2.0
101type TPM struct {
102 logger *zap.Logger
103 device io.ReadWriteCloser
104}
105
106// Initialize finds and opens the TPM (if any). If there is no TPM available it returns
107// ErrNotExists
108func Initialize(logger *zap.Logger) error {
109 lock.Lock()
110 defer lock.Unlock()
111 tpmDir, err := os.Open("/sys/class/tpm")
112 if err != nil {
113 return errors.Wrap(err, "failed to open sysfs TPM class")
114 }
115 defer tpmDir.Close()
116
117 tpms, err := tpmDir.Readdirnames(2)
118 if err != nil {
119 return errors.Wrap(err, "failed to read TPM device class")
120 }
121
122 if len(tpms) == 0 {
123 return ErrNotExists
124 }
125 if len(tpms) > 1 {
126 logger.Warn("Found more than one TPM, using the first one")
127 }
128 tpmName := tpms[0]
129 ueventData, err := sysfs.ReadUevents(filepath.Join("/sys/class/tpm", tpmName, "uevent"))
130 majorDev, err := strconv.Atoi(ueventData["MAJOR"])
131 if err != nil {
132 return fmt.Errorf("failed to convert uevent: %w", err)
133 }
134 minorDev, err := strconv.Atoi(ueventData["MINOR"])
135 if err != nil {
136 return fmt.Errorf("failed to convert uevent: %w", err)
137 }
138 if err := unix.Mknod("/dev/tpm", 0600|unix.S_IFCHR, int(unix.Mkdev(uint32(majorDev), uint32(minorDev)))); err != nil {
139 return errors.Wrap(err, "failed to create TPM device node")
140 }
141 device, err := tpm2.OpenTPM("/dev/tpm")
142 if err != nil {
143 return errors.Wrap(err, "failed to open TPM")
144 }
145 tpm = &TPM{
146 device: device,
147 logger: logger,
148 }
149 return nil
150}
151
152// GenerateSafeKey uses two sources of randomness (Kernel & TPM) to generate the key
153func GenerateSafeKey(size uint16) ([]byte, error) {
154 lock.Lock()
155 defer lock.Unlock()
156 if tpm == nil {
157 return []byte{}, ErrNotInitialized
158 }
159 encryptionKeyHost := make([]byte, size)
160 if _, err := io.ReadFull(rand.Reader, encryptionKeyHost); err != nil {
161 return []byte{}, errors.Wrap(err, "failed to generate host portion of new key")
162 }
163 var encryptionKeyTPM []byte
164 for i := 48; i > 0; i-- {
165 tpmKeyPart, err := tpm2.GetRandom(tpm.device, size-uint16(len(encryptionKeyTPM)))
166 if err != nil {
167 return []byte{}, errors.Wrap(err, "failed to generate TPM portion of new key")
168 }
169 encryptionKeyTPM = append(encryptionKeyTPM, tpmKeyPart...)
170 if len(encryptionKeyTPM) >= int(size) {
171 break
172 }
173 }
174
175 if len(encryptionKeyTPM) != int(size) {
176 return []byte{}, fmt.Errorf("got incorrect amount of TPM randomess: %v, requested %v", len(encryptionKeyTPM), size)
177 }
178
179 encryptionKey := make([]byte, size)
180 for i := uint16(0); i < size; i++ {
181 encryptionKey[i] = encryptionKeyHost[i] ^ encryptionKeyTPM[i]
182 }
183 return encryptionKey, nil
184}
185
186// Seal seals sensitive data and only allows access if the current platform configuration in
187// matches the one the data was sealed on.
188func Seal(data []byte, pcrs []int) ([]byte, error) {
189 lock.Lock()
190 defer lock.Unlock()
191 if tpm == nil {
192 return []byte{}, ErrNotInitialized
193 }
194 srk, err := tpm2tools.StorageRootKeyRSA(tpm.device)
195 if err != nil {
196 return []byte{}, errors.Wrap(err, "failed to load TPM SRK")
197 }
198 defer srk.Close()
199 sealedKey, err := srk.Seal(pcrs, data)
200 sealedKeyRaw, err := proto.Marshal(sealedKey)
201 if err != nil {
202 return []byte{}, errors.Wrapf(err, "failed to marshal sealed data")
203 }
204 return sealedKeyRaw, nil
205}
206
207// Unseal unseals sensitive data if the current platform configuration allows and sealing constraints
208// allow it.
209func Unseal(data []byte) ([]byte, error) {
210 lock.Lock()
211 defer lock.Unlock()
212 if tpm == nil {
213 return []byte{}, ErrNotInitialized
214 }
215 srk, err := tpm2tools.StorageRootKeyRSA(tpm.device)
216 if err != nil {
217 return []byte{}, errors.Wrap(err, "failed to load TPM SRK")
218 }
219 defer srk.Close()
220
221 var sealedKey tpmpb.SealedBytes
222 if err := proto.Unmarshal(data, &sealedKey); err != nil {
223 return []byte{}, errors.Wrap(err, "failed to decode sealed data")
224 }
225 // Logging this for auditing purposes
226 tpm.logger.Info("Attempting to unseal data protected with PCRs", zap.Int32s("pcrs", sealedKey.Pcrs))
227 unsealedData, err := srk.Unseal(&sealedKey)
228 if err != nil {
229 return []byte{}, errors.Wrap(err, "failed to unseal data")
230 }
231 return unsealedData, nil
232}