blob: 2b2251a603b30157eea25099c18b5fd0060a126d [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
Lorenz Brunaa6b7342019-12-12 02:55:02 +010027 "git.monogon.dev/source/nexantic.git/core/pkg/tpm"
Lorenz Brundd8c80e2019-10-07 16:19:49 +020028 "go.uber.org/zap"
29 "golang.org/x/sys/unix"
30)
31
32const (
33 dataMountPath = "/data"
34 espMountPath = "/esp"
35 espDataPath = espMountPath + "/EFI/smalltown"
36 etcdSealedKeyLocation = espDataPath + "/data-key.bin"
37)
38
39type Manager struct {
40 logger *zap.Logger
41 dataReady bool
42 initializationError error
43 mutex sync.RWMutex
44}
45
46func Initialize(logger *zap.Logger) (*Manager, error) {
47 if err := FindPartitions(); err != nil {
48 return nil, err
49 }
50
51 if err := os.Mkdir("/esp", 0755); err != nil {
52 return nil, err
53 }
54
55 // We're mounting ESP sync for reliability, this lowers our chances of getting half-written files
56 if err := unix.Mount(ESPDevicePath, espMountPath, "vfat", unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_SYNC, ""); err != nil {
57 return nil, err
58 }
59
60 manager := &Manager{
61 logger: logger,
62 dataReady: false,
63 }
64
65 manager.mutex.Lock()
66 defer manager.mutex.Unlock()
67
Lorenz Brundd8c80e2019-10-07 16:19:49 +020068 return manager, nil
69}
70
Lorenz Brunaa6b7342019-12-12 02:55:02 +010071var keySize uint16 = 256 / 8
72
73// MountData mounts the Smalltown data partition with the given global unlock key. It automatically
74// unseals the local unlock key from the TPM.
75func (s *Manager) MountData(globalUnlockKey []byte) error {
76 localPath, err := s.GetPathInPlace(PlaceESP, "local_unlock.bin")
Lorenz Brundd8c80e2019-10-07 16:19:49 +020077 if err != nil {
Lorenz Brunaa6b7342019-12-12 02:55:02 +010078 return fmt.Errorf("failed to find ESP mount: %w", err)
Lorenz Brundd8c80e2019-10-07 16:19:49 +020079 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +010080 localUnlockBlob, err := ioutil.ReadFile(localPath)
Lorenz Brundd8c80e2019-10-07 16:19:49 +020081 if err != nil {
Lorenz Brunaa6b7342019-12-12 02:55:02 +010082 return fmt.Errorf("failed to read local unlock file from ESP: %w", err)
Lorenz Brundd8c80e2019-10-07 16:19:49 +020083 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +010084 localUnlockKey, err := tpm.Unseal(localUnlockBlob)
85 if err != nil {
86 return fmt.Errorf("failed to unseal local unlock key: %w", err)
87 }
88
89 key := make([]byte, keySize)
90 for i := uint16(0); i < keySize; i++ {
91 key[i] = localUnlockKey[i] ^ globalUnlockKey[i]
92 }
93
94 if err := MapEncryptedBlockDevice("data", SmalltownDataCryptPath, key); err != nil {
95 return err
96 }
97 if err := s.mountData(); err != nil {
98 return err
99 }
100 s.mutex.Lock()
101 s.dataReady = true
102 s.mutex.Unlock()
103 s.logger.Info("Mounted encrypted storage")
104 return nil
105}
106
107// InitializeData initializes the Smalltown data partition and returns the global unlock key. It seals
108// the local portion into the TPM and stores the blob on the ESP. This is a potentially slow
109// operation since it touches the whole partition.
110func (s *Manager) InitializeData() ([]byte, error) {
111 localUnlockKey, err := tpm.GenerateSafeKey(keySize)
112 if err != nil {
113 return []byte{}, fmt.Errorf("failed to generate safe key: %w", err)
114 }
115 globalUnlockKey, err := tpm.GenerateSafeKey(keySize)
116 if err != nil {
117 return []byte{}, fmt.Errorf("failed to generate safe key: %w", err)
118 }
119
120 localUnlockBlob, err := tpm.Seal(localUnlockKey, tpm.SecureBootPCRs)
121 if err != nil {
122 return []byte{}, fmt.Errorf("failed to seal local unlock key: %w", err)
123 }
124
125 // The actual key is generated by XORing together the localUnlockKey and the globalUnlockKey
126 // This provides us with a mathematical guarantee that the resulting key cannot be recovered
127 // whithout knowledge of both parts.
128 key := make([]byte, keySize)
129 for i := uint16(0); i < keySize; i++ {
130 key[i] = localUnlockKey[i] ^ globalUnlockKey[i]
131 }
132
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200133 if err := InitializeEncryptedBlockDevice("data", SmalltownDataCryptPath, key); err != nil {
134 s.logger.Error("Failed to initialize encrypted block device", zap.Error(err))
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100135 return []byte{}, fmt.Errorf("failed to initialize encrypted block device: %w", err)
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200136 }
137 mkfsCmd := exec.Command("/bin/mkfs.xfs", "-qf", "/dev/data")
138 if _, err := mkfsCmd.Output(); err != nil {
139 s.logger.Error("Failed to format encrypted block device", zap.Error(err))
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100140 return []byte{}, fmt.Errorf("failed to format encrypted block device: %w", err)
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200141 }
142
143 if err := s.mountData(); err != nil {
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100144 return []byte{}, err
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200145 }
146
147 s.mutex.Lock()
148 s.dataReady = true
149 s.mutex.Unlock()
150
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100151 localPath, err := s.GetPathInPlace(PlaceESP, "local_unlock.bin")
152 if err != nil {
153 return []byte{}, fmt.Errorf("failed to find ESP mount: %w", err)
154 }
155 if err := ioutil.WriteFile(localPath, localUnlockBlob, 0600); err != nil {
156 return []byte{}, fmt.Errorf("failed to write local unlock file to ESP: %w", err)
157 }
158
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200159 s.logger.Info("Initialized encrypted storage")
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100160 return globalUnlockKey, nil
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200161}
162
163func (s *Manager) mountData() error {
164 if err := os.Mkdir("/data", 0755); err != nil {
165 return err
166 }
167
168 if err := unix.Mount("/dev/data", "/data", "xfs", unix.MS_NOEXEC|unix.MS_NODEV, ""); err != nil {
169 return err
170 }
171 return nil
172}
173
174// GetPathInPlace returns a path in the given place
175// It may return ErrNotInitialized if the place you're trying to access
176// is not initialized or ErrUnknownPlace if the place is not known
Leopold Schabel68c58752019-11-14 21:00:59 +0100177func (s *Manager) GetPathInPlace(place DataPlace, path string) (string, error) {
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200178 s.mutex.RLock()
179 defer s.mutex.RUnlock()
180 switch place {
Leopold Schabel68c58752019-11-14 21:00:59 +0100181 case PlaceESP:
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200182 return filepath.Join(espDataPath, path), nil
Leopold Schabel68c58752019-11-14 21:00:59 +0100183 case PlaceData:
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200184 if s.dataReady {
185 return filepath.Join(dataMountPath, path), nil
186 }
Leopold Schabel68c58752019-11-14 21:00:59 +0100187 return "", ErrNotInitialized
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200188 default:
Leopold Schabel68c58752019-11-14 21:00:59 +0100189 return "", ErrUnknownPlace
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200190 }
191}