blob: 0f5719bb34c207d13527404d3dc49a1222644ed1 [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"
22 "io"
23 "os"
24 "path/filepath"
25 "smalltown/pkg/sysfs"
26 "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 (
39 // SecureBootPCRs are all PCRs that measure the current Secure Boot configuration
40 SecureBootPCRs = []int{7}
41
42 // FirmwarePCRs are alle PCRs that contain the firmware measurements
43 // See https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf
44 FirmwarePCRs = []int{0, 2, 3}
45
46 // FullSystemPCRs are all PCRs that contain any measurements up to the currently running EFI
47 // payload.
48 FullSystemPCRs = []int{0, 1, 2, 3, 4}
49)
50
51var (
52 // ErrNotExists is returned when no TPMs are available in the system
53 ErrNotExists = errors.New("no TPMs found")
54 // ErrNotInitialized is returned when this package was not initialized successfully
55 ErrNotInitialized = errors.New("no TPM was initialized")
56)
57
58// Singleton since the TPM is too
59var tpm *TPM
60
61// We're serializing all TPM operations since it has a limited number of handles and recovering
62// if it runs out is difficult to implement correctly. Might also be marginally more secure.
63var lock sync.Mutex
64
65// TPM represents a high-level interface to a connected TPM 2.0
66type TPM struct {
67 logger *zap.Logger
68 device io.ReadWriteCloser
69}
70
71// Initialize finds and opens the TPM (if any). If there is no TPM available it returns
72// ErrNotExists
73func Initialize(logger *zap.Logger) error {
74 lock.Lock()
75 defer lock.Unlock()
76 tpmDir, err := os.Open("/sys/class/tpm")
77 if err != nil {
78 return errors.Wrap(err, "failed to open sysfs TPM class")
79 }
80 defer tpmDir.Close()
81
82 tpms, err := tpmDir.Readdirnames(2)
83 if err != nil {
84 return errors.Wrap(err, "failed to read TPM device class")
85 }
86
87 if len(tpms) == 0 {
88 return ErrNotExists
89 }
90 if len(tpms) > 1 {
91 logger.Warn("Found more than one TPM, using the first one")
92 }
93 tpmName := tpms[0]
94 ueventData, err := sysfs.ReadUevents(filepath.Join("/sys/class/tpm", tpmName, "uevent"))
95 majorDev, err := strconv.Atoi(ueventData["MAJOR"])
96 if err != nil {
97 return fmt.Errorf("failed to convert uevent: %w", err)
98 }
99 minorDev, err := strconv.Atoi(ueventData["MINOR"])
100 if err != nil {
101 return fmt.Errorf("failed to convert uevent: %w", err)
102 }
103 if err := unix.Mknod("/dev/tpm", 0600|unix.S_IFCHR, int(unix.Mkdev(uint32(majorDev), uint32(minorDev)))); err != nil {
104 return errors.Wrap(err, "failed to create TPM device node")
105 }
106 device, err := tpm2.OpenTPM("/dev/tpm")
107 if err != nil {
108 return errors.Wrap(err, "failed to open TPM")
109 }
110 tpm = &TPM{
111 device: device,
112 logger: logger,
113 }
114 return nil
115}
116
117// GenerateSafeKey uses two sources of randomness (Kernel & TPM) to generate the key
118func GenerateSafeKey(size uint16) ([]byte, error) {
119 lock.Lock()
120 defer lock.Unlock()
121 if tpm == nil {
122 return []byte{}, ErrNotInitialized
123 }
124 encryptionKeyHost := make([]byte, size)
125 if _, err := io.ReadFull(rand.Reader, encryptionKeyHost); err != nil {
126 return []byte{}, errors.Wrap(err, "failed to generate host portion of new key")
127 }
128 var encryptionKeyTPM []byte
129 for i := 48; i > 0; i-- {
130 tpmKeyPart, err := tpm2.GetRandom(tpm.device, size-uint16(len(encryptionKeyTPM)))
131 if err != nil {
132 return []byte{}, errors.Wrap(err, "failed to generate TPM portion of new key")
133 }
134 encryptionKeyTPM = append(encryptionKeyTPM, tpmKeyPart...)
135 if len(encryptionKeyTPM) >= int(size) {
136 break
137 }
138 }
139
140 if len(encryptionKeyTPM) != int(size) {
141 return []byte{}, fmt.Errorf("got incorrect amount of TPM randomess: %v, requested %v", len(encryptionKeyTPM), size)
142 }
143
144 encryptionKey := make([]byte, size)
145 for i := uint16(0); i < size; i++ {
146 encryptionKey[i] = encryptionKeyHost[i] ^ encryptionKeyTPM[i]
147 }
148 return encryptionKey, nil
149}
150
151// Seal seals sensitive data and only allows access if the current platform configuration in
152// matches the one the data was sealed on.
153func Seal(data []byte, pcrs []int) ([]byte, error) {
154 lock.Lock()
155 defer lock.Unlock()
156 if tpm == nil {
157 return []byte{}, ErrNotInitialized
158 }
159 srk, err := tpm2tools.StorageRootKeyRSA(tpm.device)
160 if err != nil {
161 return []byte{}, errors.Wrap(err, "failed to load TPM SRK")
162 }
163 defer srk.Close()
164 sealedKey, err := srk.Seal(pcrs, data)
165 sealedKeyRaw, err := proto.Marshal(sealedKey)
166 if err != nil {
167 return []byte{}, errors.Wrapf(err, "failed to marshal sealed data")
168 }
169 return sealedKeyRaw, nil
170}
171
172// Unseal unseals sensitive data if the current platform configuration allows and sealing constraints
173// allow it.
174func Unseal(data []byte) ([]byte, error) {
175 lock.Lock()
176 defer lock.Unlock()
177 if tpm == nil {
178 return []byte{}, ErrNotInitialized
179 }
180 srk, err := tpm2tools.StorageRootKeyRSA(tpm.device)
181 if err != nil {
182 return []byte{}, errors.Wrap(err, "failed to load TPM SRK")
183 }
184 defer srk.Close()
185
186 var sealedKey tpmpb.SealedBytes
187 if err := proto.Unmarshal(data, &sealedKey); err != nil {
188 return []byte{}, errors.Wrap(err, "failed to decode sealed data")
189 }
190 // Logging this for auditing purposes
191 tpm.logger.Info("Attempting to unseal data protected with PCRs", zap.Int32s("pcrs", sealedKey.Pcrs))
192 unsealedData, err := srk.Unseal(&sealedKey)
193 if err != nil {
194 return []byte{}, errors.Wrap(err, "failed to unseal data")
195 }
196 return unsealedData, nil
197}