Skip to content

Commit 7757a99

Browse files
jacobmyers-codeninjaanatol
authored andcommitted
Add support to allow unlocking a luks volume by keyfile
...reworked the luks mappings a little to allow more complex patterns of parsing parameters and building mappings. This slightly tweaks creating luksMapping lists so they can occur multiple times in the kernel parameter list and it just updates the relevant properties for each mapping (to allow extending even further if desired). updated luksMapping type to hold a keyfile add a luksMapping findOrCreate function in luks.go, either finds the existing mapping by UUID or adds one update the rd.luks.uuid and rd.luks.name in cmdline.go to make use of findOrCreate adds rd.luks.key with the format UUID=keyfile which should point to a file in initramfs containing the password adds recoverKeyfilePassword go routine which attempts to read the password from the keyfile and unlock the device, falling back to running the requestKeyboardPassword option if that fails update luksOpen to call recoverKeyfilePassword if a keyfile is defined on the mapping Putting this forward more as an idea for the approach, not sure how good or bad the specific way I implemented it is. Implements #37
1 parent fa21997 commit 7757a99

File tree

4 files changed

+109
-13
lines changed

4 files changed

+109
-13
lines changed

docs/manpage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Some parts of booster boot functionality can be modified with kernel boot parame
114114
* `rootflags=$OPTIONS` mount options for the root filesystem, e.g. rootflags=user_xattr,nobarrier. In partition autodiscovery mode GPT attribute 60 ("read-only") is taken into account.
115115
* `rd.luks.uuid=$UUID` UUID of the LUKS partition where the root partition is enclosed. booster will try to unlock this LUKS device.
116116
* `rd.luks.name=$UUID=$NAME` similar to rd.luks.uuid parameter but also specifies the name used for the LUKS device opening.
117+
* `rd.luks.key=$UUID=$PATH` absolute path to a keyfile in the initrd/initramfs which can be unsed to unlock the device identified by UUID, if this file does not exist or fails to unlock it will fall back to a password request.
117118
* `rd.luks.options=opt1,opt2` a comma-separated list of LUKS flags. Supported options are `discard`, `same-cpu-crypt`, `submit-from-crypt-cpus`, `no-read-workqueue`, `no-write-workqueue`.
118119
Note that booster also supports LUKS v2 persistent flags stored with the partition metadata. Any command-line options are added on top of the persistent flags.
119120
* `resume=$deviceref` device reference to suspend-to-disk device.

init/cmdline.go

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -222,27 +222,52 @@ func parseParams(params string) error {
222222
if len(parts) != 2 {
223223
return fmt.Errorf("invalid rd.luks.name kernel parameter %s, expected format rd.luks.name=<UUID>=<name>", value)
224224
}
225+
225226
uuid, err := parseUUID(parts[0])
226227
if err != nil {
227228
return fmt.Errorf("invalid UUID %s %v", parts[0], err)
228229
}
229230

230-
dev := luksMapping{
231-
ref: &deviceRef{refFsUUID, uuid},
232-
name: parts[1],
233-
}
234-
luksMappings = append(luksMappings, dev)
231+
m := findOrCreateLuksMapping(uuid)
232+
m.name = parts[1]
235233
case "rd.luks.uuid":
236-
u, err := parseUUID(value)
234+
uuid, err := parseUUID(value)
237235
if err != nil {
238236
return fmt.Errorf("invalid UUID %s in rd.luks.uuid boot param: %v", value, err)
239237
}
240238

241-
dev := luksMapping{
242-
ref: &deviceRef{refFsUUID, u},
243-
name: "luks-" + value,
239+
findOrCreateLuksMapping(uuid)
240+
case "rd.luks.key":
241+
var uuid UUID
242+
var keyfile string
243+
244+
parts := strings.SplitN(value, "=", 2)
245+
246+
if len(parts) == 1 {
247+
// do we only have 1 luks device?
248+
if len(luksMappings) == 1 {
249+
// we attach to it and hope for the best
250+
uuid = luksMappings[0].ref.data.(UUID)
251+
} else {
252+
// don't know what to do here
253+
return fmt.Errorf("invalid rd.luks.key kernel parameter %s, more than 1 luks device", value)
254+
}
255+
256+
keyfile = parts[0]
257+
} else if len(parts) == 2 {
258+
var err error
259+
uuid, err = parseUUID(parts[0])
260+
if err != nil {
261+
return fmt.Errorf("invalid UUID %s in rd.luks.key boot param: %v", value, err)
262+
}
263+
264+
keyfile = parts[1]
265+
} else {
266+
return fmt.Errorf("invalid rd.luks.key kernel parameter %s, expected format rd.luks.key=<UUID>=<keyfile>", value)
244267
}
245-
luksMappings = append(luksMappings, dev)
268+
269+
m := findOrCreateLuksMapping(uuid)
270+
m.keyfile = keyfile
246271
case "zfs":
247272
zfsDataset = value
248273
default:

init/luks.go

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net"
1313
"os"
1414
"os/exec"
15+
"regexp"
1516
"strings"
1617
"time"
1718

@@ -24,6 +25,7 @@ import (
2425
type luksMapping struct {
2526
ref *deviceRef
2627
name string
28+
keyfile string
2729
options []string
2830
}
2931

@@ -298,6 +300,47 @@ func recoverTokenPassword(volumes chan *luks.Volume, d luks.Device, t luks.Token
298300
info("password from %s token #%d does not match", t.Type, t.ID)
299301
}
300302

303+
func recoverKeyfilePassword(volumes chan *luks.Volume, d luks.Device, checkSlots []int, mappingName string, keyfile string) {
304+
var err error
305+
var password []byte
306+
307+
// keyfile might be in the format /path:UUID=<DEV UUID> to indicate the keyfile lives on another device
308+
parts := regexp.MustCompile("(?i):UUID=").Split(keyfile, 2)
309+
310+
if len(parts) == 1 {
311+
password, err = os.ReadFile(parts[0])
312+
313+
if err != nil {
314+
warning("reading password: %v", err)
315+
}
316+
} else {
317+
// read password from device matching uuid
318+
uuid, err := parseUUID(parts[1])
319+
if err != nil {
320+
warning("invalid UUID %s in rd.luks.key boot param: %s", uuid, keyfile)
321+
} else {
322+
// TODO: access path on uuid device, read password
323+
warning("user wants keyfile from device %s, but I don't know how to do that", uuid)
324+
}
325+
}
326+
327+
if len(password) > 0 {
328+
for _, s := range checkSlots {
329+
v, err := d.UnsealVolume(s, password)
330+
if err == luks.ErrPassphraseDoesNotMatch {
331+
continue
332+
}
333+
volumes <- v
334+
return
335+
}
336+
}
337+
338+
warning("password in keyfile #{keyfile} was unable to unseal #{mappingName}\n")
339+
340+
// have to use keyboard password
341+
requestKeyboardPassword(volumes, d, checkSlots, mappingName)
342+
}
343+
301344
func requestKeyboardPassword(volumes chan *luks.Volume, d luks.Device, checkSlots []int, mappingName string) {
302345
for {
303346
prompt := fmt.Sprintf("Enter passphrase for %s:", mappingName)
@@ -369,7 +412,13 @@ func luksOpen(dev string, mapping *luksMapping) error {
369412
}
370413
}
371414
if len(checkSlotsWithPassword) > 0 {
372-
go requestKeyboardPassword(volumes, d, checkSlotsWithPassword, mapping.name)
415+
// is there a keyfile defined for the password for this volume?
416+
if len(mapping.keyfile) > 0 {
417+
// if the keyfile doesn't work we will fallback to password
418+
go recoverKeyfilePassword(volumes, d, checkSlotsWithPassword, mapping.name, mapping.keyfile)
419+
} else {
420+
go requestKeyboardPassword(volumes, d, checkSlotsWithPassword, mapping.name)
421+
}
373422
}
374423

375424
v := <-volumes
@@ -381,7 +430,7 @@ func luksOpen(dev string, mapping *luksMapping) error {
381430
func matchLuksMapping(blk *blkInfo) *luksMapping {
382431
for _, m := range luksMappings {
383432
if blk.matchesRef(m.ref) {
384-
return &m
433+
return m
385434
}
386435
}
387436

@@ -410,3 +459,24 @@ func handleLuksBlockDevice(blk *blkInfo) error {
410459

411460
return luksOpen(blk.path, m)
412461
}
462+
463+
func findOrCreateLuksMapping(uuid UUID) *luksMapping {
464+
blk := blkInfo{
465+
uuid: uuid,
466+
}
467+
468+
for _, o := range luksMappings {
469+
if blk.matchesRef(o.ref) {
470+
return o
471+
}
472+
}
473+
474+
// didn't locate the device make a new one
475+
m := &luksMapping{
476+
ref: &deviceRef{refFsUUID, uuid},
477+
name: "luks-" + uuid.toString(),
478+
}
479+
luksMappings = append(luksMappings, m)
480+
481+
return m
482+
}

init/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ var (
3636

3737
initBinary = "/sbin/init" // path to init binary inside the user's chroot
3838

39-
luksMappings []luksMapping // list of LUKS devices that booster unlocked during boot process
39+
luksMappings []*luksMapping // list of LUKS devices that booster unlocked during boot process
4040

4141
rootAutodiscoveryMode bool
4242
rootAutodiscoveryMountFlags uintptr // autodiscovery mode uses GPT attribute to configure mount flags

0 commit comments

Comments
 (0)