cryptenroll: add interactive --firstboot enrollment wizard
Add a --firstboot mode that interactively walks the user through enrolling a passphrase, a recovery key, or a FIDO2 token, with one menu entry per suitable token currently plugged in (driven by a new fido2_enumerate_devices() helper). Pressing enter at the top-level menu leaves the volume unchanged; for each already-enrolled credential type the wizard offers to wipe it as part of the operation. It populates the same EnrollContext the command line and Varlink paths use, so the actual enrollment goes through the shared enroll_now() path. A companion --prompt-suppress= option takes a list of slot types: if a slot of any listed type already exists, the wizard does nothing and exits successfully. This lets it be hooked into the boot process while staying quiet once the system has been set up. The accompanying systemd-cryptenroll-firstboot.service runs this from the initrd, after systemd-repart has created the encrypted volume but before we transition to the host, suppressing itself once a password, recovery key or FIDO2 token is enrolled. To make that work, determine_default_node() now looks below /sysroot/ when running in the initrd, since the host file systems aren't at their final location yet. While the wizard is active it draws the same installer-style chrome (blue bars at the top and bottom of the terminal) as systemd-sysinstall, using the shared prompt_loop_yes_no() helper for its wipe confirmations. Honours the systemd.firstboot= kernel command line option. Fixes: #36298
This commit is contained in:
@@ -1319,6 +1319,109 @@ finish:
|
||||
#endif
|
||||
}
|
||||
|
||||
void fido2_device_info_done(Fido2DeviceInfo *d) {
|
||||
assert(d);
|
||||
|
||||
d->path = mfree(d->path);
|
||||
d->manufacturer = mfree(d->manufacturer);
|
||||
d->product = mfree(d->product);
|
||||
}
|
||||
|
||||
void fido2_device_info_free_many(Fido2DeviceInfo *a, size_t n) {
|
||||
FOREACH_ARRAY(i, a, n)
|
||||
fido2_device_info_done(i);
|
||||
|
||||
free(a);
|
||||
}
|
||||
|
||||
int fido2_enumerate_devices(Fido2DeviceInfo **ret, size_t *ret_n) {
|
||||
#if HAVE_LIBFIDO2
|
||||
Fido2DeviceInfo *devices = NULL;
|
||||
size_t n_devices = 0, allocated = 64, found = 0;
|
||||
fido_dev_info_t *di = NULL;
|
||||
int r;
|
||||
|
||||
CLEANUP_ARRAY(devices, n_devices, fido2_device_info_free_many);
|
||||
|
||||
assert(ret);
|
||||
assert(ret_n);
|
||||
|
||||
r = dlopen_libfido2(LOG_ERR);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
di = sym_fido_dev_info_new(allocated);
|
||||
if (!di)
|
||||
return log_oom();
|
||||
|
||||
r = sym_fido_dev_info_manifest(di, allocated, &found);
|
||||
if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
|
||||
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
|
||||
r = 0;
|
||||
goto finish;
|
||||
}
|
||||
if (r != FIDO_OK) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < found; i++) {
|
||||
bool has_rk, has_client_pin, has_up, has_uv, has_always_uv;
|
||||
const fido_dev_info_t *entry;
|
||||
const char *path;
|
||||
|
||||
entry = sym_fido_dev_info_ptr(di, i);
|
||||
if (!entry) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to get device information for FIDO device %zu.", i);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
path = sym_fido_dev_info_path(entry);
|
||||
|
||||
r = check_device_is_fido2_with_hmac_secret(path, &has_rk, &has_client_pin, &has_up, &has_uv, &has_always_uv);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
if (r == 0) /* Not a FIDO2 device suitable for enrollment, skip it */
|
||||
continue;
|
||||
|
||||
if (!GREEDY_REALLOC(devices, n_devices + 1)) {
|
||||
r = log_oom();
|
||||
goto finish;
|
||||
}
|
||||
|
||||
Fido2DeviceInfo *d = devices + n_devices;
|
||||
*d = (Fido2DeviceInfo) {};
|
||||
|
||||
/* The manufacturer / product strings are optional (NULL when the device doesn't report
|
||||
* them), but a failure to duplicate any string is a genuine OOM and thus fatal. */
|
||||
if (strdup_to(&d->path, path) < 0 ||
|
||||
strdup_to(&d->manufacturer, empty_to_null(sym_fido_dev_info_manufacturer_string(entry))) < 0 ||
|
||||
strdup_to(&d->product, empty_to_null(sym_fido_dev_info_product_string(entry))) < 0) {
|
||||
fido2_device_info_done(d);
|
||||
r = log_oom();
|
||||
goto finish;
|
||||
}
|
||||
|
||||
n_devices++;
|
||||
}
|
||||
|
||||
r = 0;
|
||||
|
||||
finish:
|
||||
sym_fido_dev_info_free(&di, allocated);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(devices);
|
||||
*ret_n = n_devices;
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"FIDO2 tokens not supported on this build.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int fido2_find_device_auto(char **ret) {
|
||||
#if HAVE_LIBFIDO2
|
||||
_cleanup_free_ char *copy = NULL;
|
||||
|
||||
@@ -137,4 +137,17 @@ static inline int parse_fido2_algorithm(const char *s, int *ret) {
|
||||
int fido2_list_devices(void);
|
||||
int fido2_find_device_auto(char **ret);
|
||||
|
||||
typedef struct Fido2DeviceInfo {
|
||||
char *path;
|
||||
char *manufacturer; /* may be NULL if the device doesn't report it */
|
||||
char *product; /* may be NULL if the device doesn't report it */
|
||||
} Fido2DeviceInfo;
|
||||
|
||||
void fido2_device_info_done(Fido2DeviceInfo *d);
|
||||
void fido2_device_info_free_many(Fido2DeviceInfo *a, size_t n);
|
||||
|
||||
/* Enumerates the FIDO2 tokens that implement the 'hmac-secret' extension (i.e. are suitable for
|
||||
* enrollment), returning a newly allocated array. */
|
||||
int fido2_enumerate_devices(Fido2DeviceInfo **ret, size_t *ret_n);
|
||||
|
||||
int fido2_have_device(const char *device);
|
||||
|
||||
Reference in New Issue
Block a user