This page describes how to setup a special root disk loaded via a USB stick to unlock a CGD root via a fido2 key and a secret bound to that key via fidocrypt(1) from pkgsrc.

Why boot from USB?

The method here can be easily modified to load the ramdisk image from some partition on the harddisk of the notebook instead (or the EFI partition). However, for a "road warrior" notebook setup the external booting is a nice additional feature.

If you chose so, you can leave a Windows installation (that the notebook probably came with) intact and just shrink its main partition as far as you like, then add the extra NetBSD partitions. By default the notebook w/o the special key then would boot into that Windows setup.

Moving the ramdisk image onto the EFI System Partition (or into another NetBSD partition) is easy and allows for setting up full automatic booting instead of the multi-step ("three factor" unlocking authentication) booting described here.

The boot process

The main parts needed to boot and unlock the root CGD partition look like this:

image of a fido key ringed to a usb stick

Before powering up (or rebooting) the notbook insert the USB memory stick, then wait until the unlock system has booted:

encoding -> de.nodead
NAME=nbsd_root_cgd's passphrase:

At this point remove the USB memory stick and insert the Fido Key instead. (Side note: if they would not be bound together by a key ring and assuming the Notebook has enough USB ports you could, instead, plug in both during the whole boot process.) Now enter the CGD passphrase. The System makes the Fido key blink and asks you to prove you are there:

encoding -> de.nodead
NAME=nbsd_root_cgd's passphrase:

[kernel messages about removing sd0 and attaching the fido device]

tap key; waiting...

If the passphrase was correct as soon as you touch the blinking Fido key the system unlocks the CGD root and boots to multiuser from it. Now you may remove (and safely store) the two unlock devices.

How does it work?

We use the wip/fidocrypt-git package to unlock the CGD. The setup/configuration is documented in the fidocrypt man page. This page is meant to document how to put it all together and create the root ramdisk image and script updates to it.

There are several parts to the secret used to unlock the CGD in the end:

Here is an example of a GPT when leaving the (preinstalled) Windows on the device (with its main partition resized to make space for the additional NetBSD partitions:

# gpt show -a wd0
      start       size  index  contents
          0          1         PMBR
          1          1         Pri GPT header
                                 GUID: ed834012-78ff-45c9-8d3b-5d9167ce0d8a
          2         32         Pri GPT table
         34       2014         Unused
       2048     204800      1  GPT part - EFI System
                                 Type: efi
                                 TypeID: c12a7328-f81f-11d2-ba4b-00a0c93ec93b
                                 GUID: a762ee8f-ed9e-46b1-9422-8aa52f5a1889
                                 Size: 100 M
                                 Label: EFI system partition
                                 Attributes: [0x8000000000000000]
     206848      32768      2  GPT part - Windows reserved
                                 Type: windows-reserved
                                 TypeID: e3c9e316-0b5c-4db8-817d-f92df00215ae
                                 GUID: d70d8d86-2bd8-4acf-9c09-e477c9717a3a
                                 Size: 16384 K
                                 Label: Microsoft reserved partition
                                 Attributes: [0x8000000000000000]
     239616  252518400      3  GPT part - Windows basic data
                                 Type: windows
                                 TypeID: ebd0a0a2-b9e5-4433-87c0-68b6b72699c7
                                 GUID: 719856bf-6581-4b5a-96ca-5749137f0c66
                                 Size: 120 G
                                 Label: Basic data partition
                                 Attributes: None
  252758016       2048         Unused
  252760064   16777216      5  GPT part - NetBSD swap
                                 Type: swap
                                 TypeID: 49f48d32-b10e-11dc-b99b-0019d1879648
                                 GUID: 65b3c248-d71a-456e-89f9-7a5c8ea7a75f
                                 Size: 8192 M
                                 Label: nbsd swap
                                 Attributes: None
  269537280  229556224      6  GPT part - NetBSD Cryptographic Disk
                                 Type: cgd
                                 TypeID: 2db519ec-b10f-11dc-b99b-0019d1879648
                                 GUID: 1c1591ed-1fbc-416f-8318-e6304b98aaee
                                 Size: 109 G
                                 Label: nbsd_root_cgd
                                 Attributes: None
  499093504    1024000      4  GPT part - Windows recovery
                                 Type: windows-recovery
                                 TypeID: de94bba4-06d1-4d40-a16a-bfd50179d6ac
                                 GUID: 2d02bc12-8433-4e41-b9f6-f8f967b1a93e
                                 Size: 500 M
                                 Label: Basic data partition
                                 Attributes: required, [0x8000000000000000]
  500117504        655         Unused
  500118159         32         Sec GPT table
  500118191          1         Sec GPT header
                                 GUID: ed834012-78ff-45c9-8d3b-5d9167ce0d8a

For initial setup you can create a bootable USB memory stick by just using sysinst(8) and adding the fidocrypt package from a binary pkg repository. Then follow the example in the fidocrypt man page to create the secret file.

The final USB memory stick used is a lot simpler. It has the Efi System Partition that sysinst created, and the ffs root partition, but the latter is mostly empty, it just contains:

total 71074
-rw-r--r--  1 root  wheel        71 Dec  9 12:20 boot.cfg
-rw-r--r--  1 root  wheel  13541376 Nov 12 19:59 cgdrd.img
-rwxr-xr-x  1 root  wheel  29576728 Dec 10 20:56 netbsd
-rwxr-xr-x  1 root  wheel  29574568 Nov 12 21:32 netbsd.old

The kernel (you can use plain GENERIC) and its .old backup are obvious, cgdrd.img is the ramdisk image used as root filesystem and boot.cfg is the magic to plug it all together.

/boot.cfg

clear=1
timeout=0
fs=cgdrd.img
banner=loading...
menu=:boot netbsd -qz

Populating the ramdisk image

To be able to update the ramdisk contents easily (and because you will need a secure backup as all content on my CGD root disk will be lost if you loose it) we create a staging directory of its content on a secure machine. Below are two simple scripts, one to update the ramdisk contents and one to rebuild the filesystem image (cgdrd.img).

Let's start with the content update script, which needs to run first:

#! /bin/sh

SETS=/tmp/downloads/sets
PKGS=/tmp/x86_64/All

cd root || exit 1
tar xpzf "${SETS}/base.tar.xz" ./dev/\* ./bin/sh ./sbin/init        \
    ./sbin/fsck_ffs ./sbin/cgdconfig ./sbin/mount_ffs       \
    ./sbin/sysctl ./sbin/wsconsctl                  \
    ./lib/libc.\* ./lib/libpthread\* ./lib/libm.so.\*       \
    ./lib/libedit.\* ./lib/libterminfo.\* ./lib/libcrypt.so.\*  \
    ./lib/libz.so.\* ./lib/libpthread.so.\* ./lib/libcrypto.so.\*   \
    ./lib/libutil.so.\* ./lib/libprop.so.\*             \
    ./usr/lib/libcrypto.so.\* ./usr/lib/libc.\*         \
    ./usr/lib/libusbhid.so.\* ./usr/lib/libz.so.\*          \
    ./usr/lib/libsqlite3.so.\* ./usr/lib/libm.so.\*         \
    ./usr/lib/libpthread.so.\*                  \
    ./libexec/ld.elf_so ./usr/libexec/ld.elf_so

tar xpzf "${SETS}/etc.tar.xz" ./dev/\*
( cd dev && sh MAKEDEV all )

cd ..
rm -rf root/usr/pkg/*
mkdir -p root/usr/pkg/pkgdb root/usr/pkg/pkgdb.refcount

pkg_add -P root ${PKGS}/fidocrypt-*
rm -rf root/usr/pkg/include root/usr/pkg/pkgdb root/usr/pkg/pkgdb.refcount \
    root/usr/pkg/lib/cmake root/usr/pkg/man root/usr/pkg/share

After this script has run and updated/populated your root/ staging directory you can pack the contents as a filesystem image, so the bootloader can directly load it:

#! /bin/sh

makefs cgdrd.img root

Copy this file to the root directory of the ffs partition of the USB stick (as shown in the directory listing above).

The update-contents.sh script extracts just enough from the distribution sets to allow booting multi-user after unlocking the CGD root.

The other part copied here is a stripped down version of the binary fidocrypt pkg (and its dependencies). The script above removes documentation and other unneeded parts to make the ramdisk size minimal.

CAVEAT: this script as-is does not work with any unpatched NetBSD version so far!

It requires a pkg_add that has the bug from PR #58809 fixed.

Static ramdisk content

Besides the files that are updated by above script, there are a few static files in the ramdisk root staging directory.

The main part is a simple replacement script for /etc/rc:

root/etc/rc

#! /bin/sh

set -e 1

/sbin/wsconsctl -w encoding=de.nodead
/sbin/cgdconfig -C
/sbin/fsck_ffs -pq /dev/rcgd0d
/sbin/mount_ffs -o log /dev/cgd0d /targetroot
/sbin/sysctl -w init.root=/targetroot
exit 0

The wsconsctl line should match your keyboard (makes entering the passphrase a lot easier).

The main work is done by cgdconfig -C. Then the filesystem on the (now unlocked) CGD device is checked and mounted as /targetroot. Finaly the sysctl init.root is set to point at the chroot directory for init.

For the cgdconfig part to work the root/etc/cgd directory needs to be populated:

 # ls -la root/etc/cgd/
total 40
drwx------  2 root  wheel    512 Nov  2 16:16 .
drwxr-xr-x  3 root  wheel    512 Nov  5 20:24 ..
-rw-------  1 root  wheel     71 Nov  2 18:12 cgd.conf
-rw-------  1 root  wheel    261 Nov  2 16:17 root.cgd
-r--------  1 root  wheel  24576 Nov  2 16:54 root_unlock.secret

root_unlock.secret is the crypted file created by fidocrypt containing your Fido key(s) enrollement and the secret used to unlock the CGD.

The other two tell cgdconfig what to find where:

root/etc/cgd/cgd.conf

# cgd   target              params file
cgd0    NAME=nbsd_root_cgd      /etc/cgd/root.cgd

Note: the NAME used her has to match the label in the GPT

root/etc/cgd/root.cgd

algorithm adiantum;
iv-method encblkno1;
keylength 256;
verify_method ffs;
keygen pkcs5_pbkdf2/sha1 {
    iterations NNNN;
    salt XxXxXxXxXxXxXxXxX=;
};

keygen shell_cmd {
    cmd "/usr/pkg/bin/fidocrypt -V u2f get -F raw /etc/cgd/root_unlock.secret";
};

This file is created by cgdconfig when creating the CGD following the fidocrypt man page (with potential minor edits, like verify_method). The -V u2f may not be needed with your Fido key. If your key supports both u2f and fido2, and some idiotic other application or system forced you to set a PIN for the device, forcing u2f format (via -V u2f) allows you to use the key without the superflous (in this context) PIN.

You need to use the same -V flag (or none) both when creating the secret file (or enrolling your key), and when retrieving the secret.