blob: 99a49eb5ee4461fa22ee915732a15d611e87f25c [file] [log] [blame]
Lorenz Brunf95909d2019-09-11 19:48:26 +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 storage
18
Lorenz Brundd8c80e2019-10-07 16:19:49 +020019import (
20 "fmt"
21 "io/ioutil"
22 "os"
23 "os/exec"
24 "path/filepath"
Lorenz Brundd8c80e2019-10-07 16:19:49 +020025 "sync"
26
27 "go.uber.org/zap"
28 "golang.org/x/sys/unix"
Hendrik Hofstadt8efe51e2020-02-28 12:53:41 +010029
30 "git.monogon.dev/source/nexantic.git/core/pkg/tpm"
Lorenz Brundd8c80e2019-10-07 16:19:49 +020031)
32
33const (
34 dataMountPath = "/data"
35 espMountPath = "/esp"
36 espDataPath = espMountPath + "/EFI/smalltown"
37 etcdSealedKeyLocation = espDataPath + "/data-key.bin"
38)
39
40type Manager struct {
41 logger *zap.Logger
42 dataReady bool
43 initializationError error
44 mutex sync.RWMutex
45}
46
47func Initialize(logger *zap.Logger) (*Manager, error) {
48 if err := FindPartitions(); err != nil {
49 return nil, err
50 }
51
52 if err := os.Mkdir("/esp", 0755); err != nil {
53 return nil, err
54 }
55
56 // We're mounting ESP sync for reliability, this lowers our chances of getting half-written files
57 if err := unix.Mount(ESPDevicePath, espMountPath, "vfat", unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_SYNC, ""); err != nil {
58 return nil, err
59 }
60
61 manager := &Manager{
62 logger: logger,
63 dataReady: false,
64 }
65
66 manager.mutex.Lock()
67 defer manager.mutex.Unlock()
68
Lorenz Brundd8c80e2019-10-07 16:19:49 +020069 return manager, nil
70}
71
Lorenz Brunaa6b7342019-12-12 02:55:02 +010072var keySize uint16 = 256 / 8
73
74// MountData mounts the Smalltown data partition with the given global unlock key. It automatically
75// unseals the local unlock key from the TPM.
76func (s *Manager) MountData(globalUnlockKey []byte) error {
77 localPath, err := s.GetPathInPlace(PlaceESP, "local_unlock.bin")
Lorenz Brundd8c80e2019-10-07 16:19:49 +020078 if err != nil {
Lorenz Brunaa6b7342019-12-12 02:55:02 +010079 return fmt.Errorf("failed to find ESP mount: %w", err)
Lorenz Brundd8c80e2019-10-07 16:19:49 +020080 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +010081 localUnlockBlob, err := ioutil.ReadFile(localPath)
Lorenz Brundd8c80e2019-10-07 16:19:49 +020082 if err != nil {
Lorenz Brunaa6b7342019-12-12 02:55:02 +010083 return fmt.Errorf("failed to read local unlock file from ESP: %w", err)
Lorenz Brundd8c80e2019-10-07 16:19:49 +020084 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +010085 localUnlockKey, err := tpm.Unseal(localUnlockBlob)
86 if err != nil {
87 return fmt.Errorf("failed to unseal local unlock key: %w", err)
88 }
89
90 key := make([]byte, keySize)
91 for i := uint16(0); i < keySize; i++ {
92 key[i] = localUnlockKey[i] ^ globalUnlockKey[i]
93 }
94
95 if err := MapEncryptedBlockDevice("data", SmalltownDataCryptPath, key); err != nil {
96 return err
97 }
98 if err := s.mountData(); err != nil {
99 return err
100 }
101 s.mutex.Lock()
102 s.dataReady = true
103 s.mutex.Unlock()
104 s.logger.Info("Mounted encrypted storage")
105 return nil
106}
107
108// InitializeData initializes the Smalltown data partition and returns the global unlock key. It seals
109// the local portion into the TPM and stores the blob on the ESP. This is a potentially slow
110// operation since it touches the whole partition.
111func (s *Manager) InitializeData() ([]byte, error) {
112 localUnlockKey, err := tpm.GenerateSafeKey(keySize)
113 if err != nil {
114 return []byte{}, fmt.Errorf("failed to generate safe key: %w", err)
115 }
116 globalUnlockKey, err := tpm.GenerateSafeKey(keySize)
117 if err != nil {
118 return []byte{}, fmt.Errorf("failed to generate safe key: %w", err)
119 }
120
121 localUnlockBlob, err := tpm.Seal(localUnlockKey, tpm.SecureBootPCRs)
122 if err != nil {
123 return []byte{}, fmt.Errorf("failed to seal local unlock key: %w", err)
124 }
125
126 // The actual key is generated by XORing together the localUnlockKey and the globalUnlockKey
127 // This provides us with a mathematical guarantee that the resulting key cannot be recovered
128 // whithout knowledge of both parts.
129 key := make([]byte, keySize)
130 for i := uint16(0); i < keySize; i++ {
131 key[i] = localUnlockKey[i] ^ globalUnlockKey[i]
132 }
133
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200134 if err := InitializeEncryptedBlockDevice("data", SmalltownDataCryptPath, key); err != nil {
135 s.logger.Error("Failed to initialize encrypted block device", zap.Error(err))
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100136 return []byte{}, fmt.Errorf("failed to initialize encrypted block device: %w", err)
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200137 }
138 mkfsCmd := exec.Command("/bin/mkfs.xfs", "-qf", "/dev/data")
139 if _, err := mkfsCmd.Output(); err != nil {
140 s.logger.Error("Failed to format encrypted block device", zap.Error(err))
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100141 return []byte{}, fmt.Errorf("failed to format encrypted block device: %w", err)
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200142 }
143
144 if err := s.mountData(); err != nil {
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100145 return []byte{}, err
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200146 }
147
148 s.mutex.Lock()
149 s.dataReady = true
150 s.mutex.Unlock()
151
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100152 localPath, err := s.GetPathInPlace(PlaceESP, "local_unlock.bin")
153 if err != nil {
154 return []byte{}, fmt.Errorf("failed to find ESP mount: %w", err)
155 }
156 if err := ioutil.WriteFile(localPath, localUnlockBlob, 0600); err != nil {
157 return []byte{}, fmt.Errorf("failed to write local unlock file to ESP: %w", err)
158 }
159
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200160 s.logger.Info("Initialized encrypted storage")
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100161 return globalUnlockKey, nil
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200162}
163
164func (s *Manager) mountData() error {
165 if err := os.Mkdir("/data", 0755); err != nil {
166 return err
167 }
168
169 if err := unix.Mount("/dev/data", "/data", "xfs", unix.MS_NOEXEC|unix.MS_NODEV, ""); err != nil {
170 return err
171 }
172 return nil
173}
174
175// GetPathInPlace returns a path in the given place
176// It may return ErrNotInitialized if the place you're trying to access
177// is not initialized or ErrUnknownPlace if the place is not known
Leopold Schabel68c58752019-11-14 21:00:59 +0100178func (s *Manager) GetPathInPlace(place DataPlace, path string) (string, error) {
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200179 s.mutex.RLock()
180 defer s.mutex.RUnlock()
181 switch place {
Leopold Schabel68c58752019-11-14 21:00:59 +0100182 case PlaceESP:
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200183 return filepath.Join(espDataPath, path), nil
Leopold Schabel68c58752019-11-14 21:00:59 +0100184 case PlaceData:
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200185 if s.dataReady {
186 return filepath.Join(dataMountPath, path), nil
187 }
Leopold Schabel68c58752019-11-14 21:00:59 +0100188 return "", ErrNotInitialized
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200189 default:
Leopold Schabel68c58752019-11-14 21:00:59 +0100190 return "", ErrUnknownPlace
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200191 }
192}