1
1
//go:build linux
2
2
3
- package luks_runner
3
+ package luks
4
4
5
5
import (
6
6
"context"
7
7
"crypto/rand"
8
+ "encoding/json"
8
9
"errors"
9
10
"fmt"
10
11
"math/big"
12
+ "os/exec"
11
13
"regexp"
12
14
"time"
13
15
@@ -16,7 +18,7 @@ import (
16
18
"github.com/fleetdm/fleet/v4/server/fleet"
17
19
"github.com/rs/zerolog/log"
18
20
"github.com/siderolabs/go-blockdevice/v2/encryption"
19
- "github.com/siderolabs/go-blockdevice/v2/encryption/luks"
21
+ luksdevice "github.com/siderolabs/go-blockdevice/v2/encryption/luks"
20
22
)
21
23
22
24
const (
@@ -40,13 +42,27 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error {
40
42
return nil
41
43
}
42
44
45
+ devicePath , err := lvm .FindRootDisk ()
46
+ if err != nil {
47
+ return fmt .Errorf ("Failed to find LUKS Root Partition: %w" , err )
48
+ }
49
+
43
50
var response LuksResponse
44
- key , err := lr .getEscrowKey (ctx )
51
+ key , keyslot , err := lr .getEscrowKey (ctx , devicePath )
45
52
if err != nil {
46
53
response .Err = err .Error ()
47
54
}
48
55
49
- response .Key = string (key )
56
+ response .Passphrase = string (key )
57
+ response .KeySlot = keyslot
58
+
59
+ if keyslot != nil {
60
+ salt , err := getSaltforKeySlot (ctx , devicePath , * keyslot )
61
+ if err != nil {
62
+ return fmt .Errorf ("Failed to get salt for key slot: %w" , err )
63
+ }
64
+ response .Salt = salt
65
+ }
50
66
51
67
if err := lr .escrower .SendLinuxKeyEscrowResponse (response ); err != nil {
52
68
if err := lr .infoPrompt (ctx , infoFailedTitle , infoFailedText ); err != nil {
@@ -70,31 +86,26 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error {
70
86
return nil
71
87
}
72
88
73
- func (lr * LuksRunner ) getEscrowKey (ctx context.Context ) ([]byte , error ) {
74
- devicePath , err := lvm .FindRootDisk ()
75
- if err != nil {
76
- return nil , fmt .Errorf ("Failed to find LUKS Root Partition: %w" , err )
77
- }
78
-
79
- device := luks .New (luks .AESXTSPlain64Cipher )
89
+ func (lr * LuksRunner ) getEscrowKey (ctx context.Context , devicePath string ) ([]byte , * uint , error ) {
90
+ device := luksdevice .New (luksdevice .AESXTSPlain64Cipher )
80
91
81
92
// Prompt user for existing LUKS passphrase
82
93
passphrase , err := lr .entryPrompt (ctx , entryDialogTitle , entryDialogText )
83
94
if err != nil {
84
- return nil , fmt .Errorf ("Failed to show passphrase entry prompt: %w" , err )
95
+ return nil , nil , fmt .Errorf ("Failed to show passphrase entry prompt: %w" , err )
85
96
}
86
97
87
98
// Validate the passphrase
88
99
for {
89
100
valid , err := lr .passphraseIsValid (ctx , device , devicePath , passphrase )
90
101
if err != nil {
91
- return nil , fmt .Errorf ("Failed validating passphrase: %w" , err )
102
+ return nil , nil , fmt .Errorf ("Failed validating passphrase: %w" , err )
92
103
}
93
104
94
105
if ! valid {
95
106
passphrase , err = lr .entryPrompt (ctx , entryDialogTitle , retryEntryDialogText )
96
107
if err != nil {
97
- return nil , fmt .Errorf ("Failed re-prompting for passphrase: %w" , err )
108
+ return nil , nil , fmt .Errorf ("Failed re-prompting for passphrase: %w" , err )
98
109
}
99
110
continue
100
111
}
@@ -104,41 +115,41 @@ func (lr *LuksRunner) getEscrowKey(ctx context.Context) ([]byte, error) {
104
115
105
116
if len (passphrase ) == 0 {
106
117
log .Debug ().Msg ("Passphrase is empty, no password supplied, dialog was canceled, or timed out" )
107
- return nil , nil
118
+ return nil , nil , nil
108
119
}
109
120
110
121
escrowPassphrase , err := generateRandomPassphrase ()
111
122
if err != nil {
112
- return nil , fmt .Errorf ("Failed to generate random passphrase: %w" , err )
123
+ return nil , nil , fmt .Errorf ("Failed to generate random passphrase: %w" , err )
113
124
}
114
125
115
126
// Create a new key slot, error if all key slots are full
116
127
// keySlot 0 is assumed to be the user's passphrase
117
128
// so we start at 1
118
- keySlot : = 1
129
+ var keySlot uint = 1
119
130
for {
120
131
if keySlot == maxKeySlots {
121
- return nil , errors .New ("all LUKS key slots are full" )
132
+ return nil , nil , errors .New ("all LUKS key slots are full" )
122
133
}
123
134
124
135
userKey := encryption .NewKey (0 , passphrase )
125
- escrowKey := encryption .NewKey (keySlot , escrowPassphrase )
136
+ escrowKey := encryption .NewKey (int ( keySlot ) , escrowPassphrase )
126
137
127
138
if err := device .AddKey (ctx , devicePath , userKey , escrowKey ); err != nil {
128
139
if ErrKeySlotFull .MatchString (err .Error ()) {
129
140
keySlot ++
130
141
continue
131
142
}
132
- return nil , fmt .Errorf ("Failed to add key: %w" , err )
143
+ return nil , nil , fmt .Errorf ("Failed to add key: %w" , err )
133
144
}
134
145
135
146
break
136
147
}
137
148
138
- return escrowPassphrase , nil
149
+ return escrowPassphrase , & keySlot , nil
139
150
}
140
151
141
- func (lr * LuksRunner ) passphraseIsValid (ctx context.Context , device * luks .LUKS , devicePath string , passphrase []byte ) (bool , error ) {
152
+ func (lr * LuksRunner ) passphraseIsValid (ctx context.Context , device * luksdevice .LUKS , devicePath string , passphrase []byte ) (bool , error ) {
142
153
if len (passphrase ) == 0 {
143
154
return false , nil
144
155
}
@@ -219,3 +230,35 @@ func (lr *LuksRunner) infoPrompt(ctx context.Context, title, text string) error
219
230
220
231
return nil
221
232
}
233
+
234
+ type LuksDump struct {
235
+ Keyslots map [string ]Keyslot `json:"keyslots"`
236
+ }
237
+
238
+ type Keyslot struct {
239
+ KDF KDF `json:"kdf"`
240
+ }
241
+
242
+ type KDF struct {
243
+ Salt string `json:"salt"`
244
+ }
245
+
246
+ func getSaltforKeySlot (ctx context.Context , devicePath string , keySlot uint ) (string , error ) {
247
+ cmd := exec .CommandContext (ctx , "cryptsetup" , "luksDump" , "--dump-json-metadata" , devicePath )
248
+ output , err := cmd .Output ()
249
+ if err != nil {
250
+ return "" , fmt .Errorf ("Failed to run cryptsetup luksDump: %w" , err )
251
+ }
252
+
253
+ var dump LuksDump
254
+ if err := json .Unmarshal (output , & dump ); err != nil {
255
+ return "" , fmt .Errorf ("Failed to unmarshal luksDump output: %w" , err )
256
+ }
257
+
258
+ slot , ok := dump .Keyslots [fmt .Sprintf ("%d" , keySlot )]
259
+ if ! ok {
260
+ return "" , errors .New ("key slot not found" )
261
+ }
262
+
263
+ return slot .KDF .Salt , nil
264
+ }
0 commit comments