-
Notifications
You must be signed in to change notification settings - Fork 566
/
keymgr_luks2.go
277 lines (240 loc) · 9.39 KB
/
keymgr_luks2.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package keymgr
import (
"fmt"
"regexp"
"time"
sb "github.com/snapcore/secboot"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/secboot/keys"
"github.com/snapcore/snapd/secboot/luks2"
)
const (
// key slot used by the encryption key
encryptionKeySlot = 0
// key slot used by the recovery key
recoveryKeySlot = 1
// temporary key slot used when changing the encryption key
tempKeySlot = recoveryKeySlot + 1
)
var (
sbGetDiskUnlockKeyFromKernel = sb.GetDiskUnlockKeyFromKernel
)
func getEncryptionKeyFromUserKeyring(dev string) ([]byte, error) {
const remove = false
const defaultPrefix = "ubuntu-fde"
// note this is the unlock key, which can be either the main key which
// was unsealed, or the recovery key, in which case some operations may
// not make sense
currKey, err := sbGetDiskUnlockKeyFromKernel(defaultPrefix, dev, remove)
if err != nil {
return nil, fmt.Errorf("cannot obtain current unlock key for %v: %v", dev, err)
}
return currKey, err
}
// TODO rather than inspecting the error messages, parse the LUKS2 headers
var keyslotFull = regexp.MustCompile(`^.*cryptsetup failed with: Key slot [0-9]+ is full, please select another one\.$`)
// IsKeyslotAlreadyUsed returns true if the error indicates that the keyslot
// attempted for a given key is already used
func IsKeyslotAlreadyUsed(err error) bool {
if err == nil {
return false
}
return keyslotFull.MatchString(err.Error())
}
func isKeyslotNotActive(err error) bool {
match, _ := regexp.MatchString(`.*: Keyslot [0-9]+ is not active`, err.Error())
return match
}
func recoveryKDF() (*luks2.KDFOptions, error) {
usableMem, err := osutil.TotalUsableMemory()
if err != nil {
return nil, fmt.Errorf("cannot get usable memory for KDF parameters when adding the recovery key: %v", err)
}
// The KDF memory is heuristically calculated by taking the
// usable memory and subtracting hardcoded 384MB that is
// needed to keep the system working. Half of that is the mem
// we want to use for the KDF. Doing it this way avoids the expensive
// benchmark from cryptsetup. The recovery key is already 128bit
// strong so we don't need to be super precise here.
kdfMem := (int(usableMem) - 384*1024*1024) / 2
// at most 1 GB, but at least 32 kB
if kdfMem > 1024*1024*1024 {
kdfMem = (1024 * 1024 * 1024)
} else if kdfMem < 32*1024 {
kdfMem = 32 * 1024
}
return &luks2.KDFOptions{
MemoryKiB: kdfMem / 1024,
ForceIterations: 4,
}, nil
}
// AddRecoveryKeyToLUKSDevice adds a recovery key to a LUKS2 device. It the
// devuce unlock key from the user keyring to authorize the change. The
// recoveyry key is added to keyslot 1.
func AddRecoveryKeyToLUKSDevice(recoveryKey keys.RecoveryKey, dev string) error {
currKey, err := getEncryptionKeyFromUserKeyring(dev)
if err != nil {
return err
}
return AddRecoveryKeyToLUKSDeviceUsingKey(recoveryKey, currKey, dev)
}
// AddRecoveryKeyToLUKSDeviceUsingKey adds a recovery key rkey to the existing
// LUKS encrypted volume on the block device given by node. The existing key to
// the encrypted volume is provided in the key argument and used to authorize
// the operation.
//
// A heuristic memory cost is used.
func AddRecoveryKeyToLUKSDeviceUsingKey(recoveryKey keys.RecoveryKey, currKey keys.EncryptionKey, dev string) error {
opts, err := recoveryKDF()
if err != nil {
return err
}
options := luks2.AddKeyOptions{
KDFOptions: *opts,
Slot: recoveryKeySlot,
}
if err := luks2.AddKey(dev, currKey, recoveryKey[:], &options); err != nil {
return fmt.Errorf("cannot add key: %v", err)
}
if err := luks2.SetSlotPriority(dev, encryptionKeySlot, luks2.SlotPriorityHigh); err != nil {
return fmt.Errorf("cannot change keyslot priority: %v", err)
}
return nil
}
// RemoveRecoveryKeyFromLUKSDevice removes an existing recovery key a LUKS2
// device.
func RemoveRecoveryKeyFromLUKSDevice(dev string) error {
currKey, err := getEncryptionKeyFromUserKeyring(dev)
if err != nil {
return err
}
return RemoveRecoveryKeyFromLUKSDeviceUsingKey(currKey, dev)
}
// RemoveRecoveryKeyFromLUKSDeviceUsingKey removes an existing recovery key a
// LUKS2 using the provided key to authorize the operation.
func RemoveRecoveryKeyFromLUKSDeviceUsingKey(currKey keys.EncryptionKey, dev string) error {
// just remove the key we think is a recovery key (luks keyslot 1)
if err := luks2.KillSlot(dev, recoveryKeySlot, currKey); err != nil {
if !isKeyslotNotActive(err) {
return fmt.Errorf("cannot kill recovery key slot: %v", err)
}
}
return nil
}
// StageLUKSDeviceEncryptionKeyChange stages a new encryption key with the goal
// of changing the main encryption key referenced in keyslot 0. The operation is
// authorized using the key that unlocked the device and is stored in the
// keyring (as it happens during factory reset).
func StageLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev string) error {
if len(newKey) != keys.EncryptionKeySize {
return fmt.Errorf("cannot use a key of size different than %v", keys.EncryptionKeySize)
}
// the key to authorize the device is in the keyring
currKey, err := getEncryptionKeyFromUserKeyring(dev)
if err != nil {
return err
}
// TODO rather than inspecting the errors, parse the LUKS2 headers
// free up the temp slot
if err := luks2.KillSlot(dev, tempKeySlot, currKey); err != nil {
if !isKeyslotNotActive(err) {
return fmt.Errorf("cannot kill the temporary keyslot: %v", err)
}
}
options := luks2.AddKeyOptions{
KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond},
Slot: tempKeySlot,
}
if err := luks2.AddKey(dev, currKey[:], newKey, &options); err != nil {
return fmt.Errorf("cannot add temporary key: %v", err)
}
return nil
}
// TransitionLUKSDeviceEncryptionKeyChange completes the main encryption key
// change to the new key provided in the parameters. The new key must have been
// staged before, thus it can authorize LUKS operations. Lastly, the unlock key
// in the keyring is updated to the new key.
func TransitionLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev string) error {
if len(newKey) != keys.EncryptionKeySize {
return fmt.Errorf("cannot use a key of size different than %v", keys.EncryptionKeySize)
}
// the expected state is as follows:
// key slot 0 - the old encryption key
// key slot 2 - the new encryption key (added during --stage)
// the desired state is:
// key slot 0 - the new encryption key
// key slot 2 - empty
// it is possible that the system was rebooted right after key slot 0 was
// populated with the new key and key slot 2 was emptied
// there is no state information on disk which would tell if the
// scenario 1 above occurred and to which stage it was executed, but we
// need to find out if key slot 2 is in use (as the caller believes that
// a key was staged earlier); do this indirectly by trying to add a key
// to key slot 2
// TODO rather than inspecting the errors, parse the LUKS2 headers
tempKeyslotAlreadyUsed := true
options := luks2.AddKeyOptions{
KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond},
Slot: tempKeySlot,
}
err := luks2.AddKey(dev, newKey, newKey, &options)
if err == nil {
// key slot is not in use, so we are dealing with unexpected reboot scenario
tempKeyslotAlreadyUsed = false
} else if err != nil && !IsKeyslotAlreadyUsed(err) {
return fmt.Errorf("cannot add new encryption key: %v", err)
}
if !tempKeyslotAlreadyUsed {
// since the key slot was not used, it means that the transition
// was already carried out (since it got authorized by the new
// key), so now all is needed is to remove the added key
if err := luks2.KillSlot(dev, tempKeySlot, newKey); err != nil {
return fmt.Errorf("cannot kill temporary key slot: %v", err)
}
return nil
}
// first kill the main encryption key slot, authorize the operation
// using the new key which must have been added to the temp keyslot in
// the stage operation
if err := luks2.KillSlot(dev, encryptionKeySlot, newKey); err != nil {
if !isKeyslotNotActive(err) {
return fmt.Errorf("cannot kill the encryption key slot: %v", err)
}
}
options = luks2.AddKeyOptions{
KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond},
Slot: encryptionKeySlot,
}
if err := luks2.AddKey(dev, newKey, newKey, &options); err != nil {
return fmt.Errorf("cannot add new encryption key: %v", err)
}
// now it should be possible to kill the temporary keyslot by using the
// new key for authorization
if err := luks2.KillSlot(dev, tempKeySlot, newKey); err != nil {
if !isKeyslotNotActive(err) {
return fmt.Errorf("cannot kill temporary key slot: %v", err)
}
}
// TODO needed?
if err := luks2.SetSlotPriority(dev, encryptionKeySlot, luks2.SlotPriorityHigh); err != nil {
return fmt.Errorf("cannot change key slot priority: %v", err)
}
return nil
}