cryptenroll: add interactive mode and Varlink IPC API (#42380)

Replaces: #31096
Fixes: #36298
This commit is contained in:
Lennart Poettering
2026-06-28 08:47:27 +02:00
committed by GitHub
42 changed files with 2284 additions and 418 deletions
-4
View File
@@ -2527,9 +2527,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
the dropped in certs and encrypted with machine pubkey, and pass to machine.
Machine is then able to authenticate you, and confidentiality is guaranteed.
- systemd-cryptenroll: add --firstboot or so, that will interactively ask user
whether recovery key shall be enrolled and do so
- systemd-dissect: add --cat switch for dumping files such as /etc/os-release
- systemd-dissect: show available versions inside of a disk image, i.e. if
@@ -2893,7 +2890,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
- coredumpctl
- systemd-bless-boot
- systemd-measure
- systemd-cryptenroll (to allow UIs to enroll FIDO2 keys and such)
- systemd-dissect
- systemd-sysupdate
- systemd-analyze
+81
View File
@@ -270,6 +270,16 @@
<para>The following options are understood that may be used to unlock the device in preparation of the enrollment operations:</para>
<variablelist>
<varlistentry>
<term><option>--unlock-empty</option></term>
<listitem><para>Use an empty password/passphrase to unlock the volume, instead of reading one from
stdin. This is useful to unlock volumes that are currently protected by an empty password, in order to
enroll a different, stronger unlock mechanism.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--unlock-key-file=<replaceable>PATH</replaceable></option></term>
@@ -307,6 +317,18 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--unlock-headless</option></term>
<listitem><para>Try the unlock mechanisms suitable for headless operation in turn, without requiring
any interactive input. If a TPM2 device is available the volume is unlocked via TPM2, and otherwise
(or if TPM2 unlocking fails) an empty password is attempted. This is primarily useful in automated
provisioning scenarios where a volume is first set up with a TPM2 enrollment or an empty password and
a stronger unlock mechanism shall be enrolled subsequently.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
@@ -340,6 +362,65 @@
</variablelist>
</refsect1>
<refsect1>
<title>Interactive Enrollment</title>
<para>The following options are understood that drive an interactive enrollment wizard, intended to be
run on first boot (similar in spirit to
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>):</para>
<variablelist>
<varlistentry>
<term><option>--firstboot</option></term>
<listitem><para>Run an interactive wizard that lets the user enroll a passphrase, a recovery key, or a
FIDO2 security token (one menu entry per suitable token currently plugged in). If credentials of any
of these types are already enrolled, the wizard additionally offers to wipe them. Pressing enter at
the top-level menu leaves the volume unchanged. This option may not be combined with an explicit
enrollment type or with <option>--wipe-slot=</option>. As with the other commands, the volume to
operate on is the one backing <filename>/var/</filename> if none is specified. Honours the
<varname>systemd.firstboot=</varname> kernel command line option.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--prompt-suppress=<replaceable>TYPE</replaceable>…</option></term>
<listitem><para>Takes a comma-separated list of slot types, out of <literal>password</literal>,
<literal>recovery</literal>, <literal>pkcs11</literal>, <literal>fido2</literal> and
<literal>tpm2</literal>. Only useful together with <option>--firstboot</option>: if a slot of any of
the listed types is already enrolled on the volume, the wizard does nothing and exits successfully.
This is useful to hook the wizard into the boot process while ensuring it stays quiet once suitable
credentials have been set up.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--chrome=<replaceable>BOOL</replaceable></option></term>
<listitem><para>Takes a boolean argument, defaults to on. Only useful together with
<option>--firstboot</option>: if enabled the interactive wizard draws a coloured bar across the top and
bottom of the terminal to visually frame the prompt. Pass <option>--chrome=no</option> to disable this
decoration, for example when running on a terminal that does not render it well.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--mute-console=<replaceable>BOOL</replaceable></option></term>
<listitem><para>Takes a boolean argument, defaults to off. Only useful together with
<option>--firstboot</option>: if enabled the kernel and PID 1 are asked to refrain from writing log
messages to the console while the interactive wizard is shown, so that such output does not interfere
with the prompt. The console is unmuted again once the wizard exits.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>PKCS#11 Enrollment</title>
+11 -1
View File
@@ -43,10 +43,14 @@ _systemd_cryptenroll() {
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
local -A OPTS=(
[STANDALONE]='-h --help --version
--password --recovery-key --list-devices'
--password --recovery-key --list-devices
--unlock-empty --unlock-headless --firstboot'
[ARG]='--unlock-key-file
--unlock-fido2-device
--unlock-tpm2-device
--prompt-suppress
--chrome
--mute-console
--pkcs11-token-uri
--fido2-credential-algorithm
--fido2-device
@@ -99,6 +103,12 @@ _systemd_cryptenroll() {
--wipe-slot)
comps='all empty password recovery pkcs11 fido2 tpm2'
;;
--prompt-suppress)
comps='password recovery pkcs11 fido2 tpm2'
;;
--chrome|--mute-console)
comps='yes no'
;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
+74
View File
@@ -0,0 +1,74 @@
#compdef systemd-cryptenroll
# SPDX-License-Identifier: LGPL-2.1-or-later
(( $+functions[_systemd-cryptenroll_devices] )) ||
_systemd-cryptenroll_devices() {
local -a devices
devices=( ${(f)"$(_call_program devices systemd-cryptenroll --list-devices 2>/dev/null)"} )
_describe -t devices 'block device' devices
}
(( $+functions[_systemd-cryptenroll_fido2_device] )) ||
_systemd-cryptenroll_fido2_device() {
_alternative \
"special:special:($*)" \
'devices:FIDO2 device:_files -g "/dev/hidraw*(-c)"'
}
(( $+functions[_systemd-cryptenroll_tpm2_device] )) ||
_systemd-cryptenroll_tpm2_device() {
_alternative \
"special:special:($*)" \
'devices:TPM2 device:_files -g "/dev/tpmrm*(-c)"'
}
(( $+functions[_systemd-cryptenroll_wipe_slot] )) ||
_systemd-cryptenroll_wipe_slot() {
_values -s , 'slot' all empty password recovery pkcs11 fido2 tpm2
}
(( $+functions[_systemd-cryptenroll_prompt_suppress] )) ||
_systemd-cryptenroll_prompt_suppress() {
_values -s , 'type' password recovery pkcs11 fido2 tpm2
}
# Unlock methods are mutually exclusive with each other
local unlock='--unlock-empty --unlock-key-file --unlock-fido2-device --unlock-tpm2-device --unlock-headless'
# Enrollment operations are mutually exclusive with each other
local enroll='--password --recovery-key --pkcs11-token-uri --fido2-device --tpm2-device --tpm2-device-key --firstboot'
_arguments -s \
'(- *)'{-h,--help}'[Show this help]' \
'(- *)--version[Show package version]' \
'--no-pager[Do not pipe output into a pager]' \
'(- *)--list-devices[List candidate block devices to operate on]' \
'--wipe-slot=[Wipe specified slots]:slot:_systemd-cryptenroll_wipe_slot' \
"($enroll)--firstboot[Interactively enroll a credential (first-boot wizard)]" \
'--prompt-suppress=[Skip the --firstboot wizard if a slot of any listed type exists]:type:_systemd-cryptenroll_prompt_suppress' \
'--chrome=[In first-boot mode, do not show colour bar at top and bottom of terminal]:boolean:(yes no)' \
'--mute-console=[In first-boot mode, tell kernel/PID 1 to not write to the console while running]:boolean:(yes no)' \
"($unlock)--unlock-empty[Use an empty password to unlock the volume]" \
"($unlock)--unlock-key-file=[Use a file to unlock the volume]:key file:_files" \
"($unlock)--unlock-fido2-device=[Use a FIDO2 device to unlock the volume]:FIDO2 device:_systemd-cryptenroll_fido2_device auto" \
"($unlock)--unlock-tpm2-device=[Use a TPM2 device to unlock the volume]:TPM2 device:_systemd-cryptenroll_tpm2_device auto" \
"($unlock)--unlock-headless[Try the 'headless' unlock mechanisms in turn]" \
"($enroll)--password[Enroll a user-supplied password]" \
"($enroll)--recovery-key[Enroll a recovery key]" \
"($enroll)--pkcs11-token-uri=[Enroll a PKCS#11 security token or list them]:PKCS#11 token URI:(auto list pkcs11:)" \
"($enroll)--fido2-device=[Enroll a FIDO2-HMAC security token or list them]:FIDO2 device:_systemd-cryptenroll_fido2_device auto list" \
'--fido2-salt-file=[Use salt from a file instead of generating one]:salt file:_files' \
'--fido2-parameters-in-header=[Whether to store FIDO2 parameters in the LUKS2 header]:boolean:(yes no)' \
'--fido2-credential-algorithm=[Specify COSE algorithm for FIDO2 credential]:algorithm:(es256 rs256 eddsa)' \
'--fido2-with-client-pin=[Whether to require entering a PIN to unlock the volume]:boolean:(yes no)' \
'--fido2-with-user-presence=[Whether to require user presence to unlock the volume]:boolean:(yes no)' \
'--fido2-with-user-verification=[Whether to require user verification to unlock the volume]:boolean:(yes no)' \
"($enroll)--tpm2-device=[Enroll a TPM2 device or list them]:TPM2 device:_systemd-cryptenroll_tpm2_device auto list" \
"($enroll)--tpm2-device-key=[Enroll a TPM2 device using its public key]:public key file:_files" \
'--tpm2-seal-key-handle=[Specify handle of key to use for sealing]:handle:' \
'--tpm2-pcrs=[Specify TPM2 PCRs to seal against]:PCRs:' \
'--tpm2-public-key=[Enroll signed TPM2 PCR policy against PEM public key]:public key file:_files' \
'--tpm2-public-key-pcrs=[Enroll signed TPM2 PCR policy for specified TPM2 PCRs]:PCRs:' \
'--tpm2-signature=[Validate public key enrollment works with JSON signature file]:signature file:_files' \
'--tpm2-pcrlock=[Specify pcrlock policy to lock against]:pcrlock file:_files' \
'--tpm2-with-pin=[Whether to require entering a PIN to unlock the volume]:boolean:(yes no)' \
'*::block device:_systemd-cryptenroll_devices'
+1
View File
@@ -36,6 +36,7 @@ foreach item : [
['_storagectl', ''],
['_systemd', ''],
['_systemd-analyze', ''],
['_systemd-cryptenroll', 'HAVE_LIBCRYPTSETUP'],
['_systemd-delta', ''],
['_systemd-hwdb', 'ENABLE_HWDB'],
['_systemd-id128', ''],
+30 -25
View File
@@ -3,6 +3,7 @@
#include "alloc-util.h"
#include "ask-password-api.h"
#include "cryptenroll-fido2.h"
#include "cryptenroll-varlink.h"
#include "cryptsetup-fido2.h"
#include "cryptsetup-util.h"
#include "fido2-util.h"
@@ -15,11 +16,9 @@
#include "string-util.h"
int load_volume_key_fido2(
const EnrollContext *c,
struct crypt_device *cd,
const char *cd_node,
const char *device,
void *ret_vk,
size_t *ret_vks) {
struct iovec *ret_vk) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
@@ -28,19 +27,19 @@ int load_volume_key_fido2(
ssize_t passphrase_size;
int r;
assert_se(c);
assert_se(c->node);
assert_se(cd);
assert_se(cd_node);
assert_se(ret_vk);
assert_se(ret_vks);
r = acquire_fido2_key_auto(
cd,
cd_node,
cd_node,
device,
c->node,
c->node,
c->unlock_fido2_device,
/* until= */ 0,
"cryptenroll.fido2-pin",
ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED,
ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED|(c->interactive ? 0 : ASK_PASSWORD_HEADLESS),
&decrypted_key,
&decrypted_key_size);
if (r == -EAGAIN)
@@ -57,8 +56,8 @@ int load_volume_key_fido2(
r = sym_crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk,
ret_vks,
ret_vk->iov_base,
&ret_vk->iov_len,
passphrase,
passphrase_size);
if (r < 0)
@@ -71,13 +70,9 @@ int load_volume_key_fido2(
}
int enroll_fido2(
const EnrollContext *c,
struct crypt_device *cd,
const struct iovec *volume_key,
const char *device,
Fido2EnrollFlags lock_with,
int cred_alg,
const char *salt_file,
bool parameters_in_header) {
const struct iovec *volume_key) {
#if HAVE_LIBFIDO2
_cleanup_(iovec_done_erase) struct iovec salt = {};
@@ -88,20 +83,22 @@ int enroll_fido2(
size_t cid_size, secret_size;
_cleanup_free_ void *cid = NULL;
ssize_t base64_encoded_size;
Fido2EnrollFlags lock_with; /* receives the flags actually locked with, see below */
const char *node, *un;
int r, keyslot;
assert_se(c);
assert_se(cd);
assert_se(iovec_is_set(volume_key));
assert_se(device);
assert_se(c->fido2_device);
assert_se(node = sym_crypt_get_device_name(cd));
un = strempty(sym_crypt_get_uuid(cd));
if (salt_file)
if (c->fido2_salt_file)
r = fido2_read_salt_file(
salt_file,
c->fido2_salt_file,
/* offset= */ UINT64_MAX,
/* client= */ "cryptenroll",
/* node= */ un,
@@ -111,8 +108,14 @@ int enroll_fido2(
if (r < 0)
return r;
/* If we are operating over a Varlink connection that requested progress reports, let the client
* know a token touch is imminent before we enter the (blocking) FIDO2 operations below. */
r = enroll_context_notify_state(c, "touch");
if (r < 0)
return r;
r = fido2_generate_hmac_hash(
device,
c->fido2_device,
/* rp_id= */ "io.systemd.cryptsetup",
/* rp_name= */ "Encrypted Volume",
/* user_id= */ un, strlen(un), /* We pass the user ID and name as the same: the disk's UUID if we have it */
@@ -121,8 +124,10 @@ int enroll_fido2(
/* user_icon= */ NULL,
/* askpw_icon= */ "drive-harddisk",
/* askpw_credential= */ "cryptenroll.fido2-pin",
lock_with,
cred_alg,
c->interactive ? 0 : ASK_PASSWORD_HEADLESS,
c->fido2_pin,
c->fido2_lock_with,
c->fido2_cred_alg != 0 ? c->fido2_cred_alg : COSE_ES256,
&salt,
&cid, &cid_size,
&secret, &secret_size,
@@ -150,7 +155,7 @@ int enroll_fido2(
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new FIDO2 key to %s: %m", node);
if (parameters_in_header) {
if (c->fido2_parameters_in_header) {
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return log_oom();
+3 -2
View File
@@ -1,7 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
#include "shared-forward.h"
int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks);
int enroll_fido2(struct crypt_device *cd, const struct iovec *volume_key, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header);
int load_volume_key_fido2(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk);
int enroll_fido2(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key);
+243
View File
@@ -0,0 +1,243 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#include "alloc-util.h"
#include "ansi-color.h"
#include "cryptenroll.h"
#include "cryptenroll-interactive.h"
#include "cryptenroll-list.h"
#include "cryptsetup-util.h"
#include "glyph-util.h"
#include "libfido2-util.h"
#include "log.h"
#include "proc-cmdline.h"
#include "prompt-util.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
static int collect_existing_types(struct crypt_device *cd, unsigned *ret_mask) {
_cleanup_free_ EnrolledSlot *slots = NULL;
size_t n_slots;
unsigned mask = 0;
int r;
assert(cd);
assert(ret_mask);
r = collect_enrolled_slots(cd, &slots, &n_slots);
if (r < 0)
return r;
FOREACH_ARRAY(s, slots, n_slots)
if (!s->conflict && s->type >= 0)
mask |= 1U << s->type;
*ret_mask = mask;
return 0;
}
static int choose_mechanism(EnrollContext *c) {
int r;
assert(c);
/* Top-level menu. Each iteration re-enumerates the FIDO2 tokens, so a token plugged in after the
* menu was first shown appears once the user picks "Rescan". An empty answer leaves the volume
* untouched. */
for (;;) {
Fido2DeviceInfo *devices = NULL;
size_t n_devices = 0;
CLEANUP_ARRAY(devices, n_devices, fido2_device_info_free_many);
/* Best effort: if FIDO2 isn't available we simply offer no token rows. */
(void) fido2_enumerate_devices(&devices, &n_devices);
_cleanup_strv_free_ char **menu = strv_new("Enroll a recovery key", "Enroll a passphrase");
if (!menu)
return log_oom();
FOREACH_ARRAY(d, devices, n_devices) {
_cleanup_free_ char *label = NULL;
label = strjoin("Enroll FIDO2 security token: ",
d->manufacturer ?: "Security Token",
d->product ? " " : "", strempty(d->product),
" (", d->path, ")");
if (!label)
return log_oom();
if (strv_consume(&menu, TAKE_PTR(label)) < 0)
return log_oom();
}
if (strv_extend(&menu, "Rescan for FIDO2 security tokens") < 0)
return log_oom();
_cleanup_free_ char *choice = NULL;
r = prompt_loop(
"Select enrollment option",
GLYPH_LOCK_AND_KEY,
/* prefill= */ NULL,
menu,
/* accepted= */ menu, /* only accept exact menu entries (or their numbers) */
/* ellipsize_percentage= */ 60,
/* n_columns= */ 1,
/* column_width= */ SIZE_MAX, /* auto-size to the widest entry */
/* is_valid= */ NULL,
/* refresh= */ NULL,
/* userdata= */ NULL,
PROMPT_MAY_SKIP|PROMPT_SHOW_MENU|PROMPT_SHOW_MENU_NOW|PROMPT_HIDE_MENU_HINT,
&choice);
if (r < 0)
return r;
if (!choice) {
/* Empty answer: do nothing. */
log_info("No selection made, leaving volume unchanged.");
return 0;
}
/* Map the chosen label back to its index. */
size_t idx = SIZE_MAX;
STRV_FOREACH(m, menu)
if (streq(*m, choice)) {
idx = m - menu;
break;
}
assert(idx != SIZE_MAX);
if (idx == 0)
c->enroll_type = ENROLL_RECOVERY;
else if (idx == 1)
c->enroll_type = ENROLL_PASSWORD;
else if (idx + 1 == strv_length(menu)) /* "Rescan" */
continue;
else {
c->enroll_type = ENROLL_FIDO2;
assert(idx > 1);
assert(idx + 1 < strv_length(menu));
if (strdup_to(&c->fido2_device, devices[idx - 2].path) < 0)
return log_oom();
}
return 1;
}
}
static int ask_wipe(EnrollContext *c, unsigned existing_mask) {
static const EnrollType candidates[] = {
ENROLL_PASSWORD,
ENROLL_RECOVERY,
ENROLL_FIDO2,
};
int r;
assert(c);
/* For each already-enrolled type the wizard understands, offer to wipe it alongside the new
* enrollment. */
FOREACH_ELEMENT(t, candidates) {
if (!FLAGS_SET(existing_mask, 1U << *t))
continue;
_cleanup_free_ char *question = NULL;
question = strjoin("A ", enroll_type_to_string(*t), " slot is already enrolled. Wipe it as part of this enrollment?");
if (!question)
return log_oom();
bool wipe;
r = prompt_loop_yes_no(question, /* def= */ false, &wipe);
if (r < 0)
return r;
if (wipe)
c->wipe_slots_mask |= 1U << *t;
}
return 0;
}
int cryptenroll_run_interactive(
EnrollContext *c,
unsigned prompt_suppress_mask,
bool chrome,
sd_varlink **mute_console_link) {
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
unsigned existing_mask = 0;
int r;
assert(c);
assert(c->node);
/* Honour the systemd.firstboot= kernel command line option, just like systemd-firstboot. */
bool enabled;
r = proc_cmdline_get_bool("systemd.firstboot", PROC_CMDLINE_TRUE_WHEN_MISSING, &enabled);
if (r < 0)
log_warning_errno(r, "Failed to parse systemd.firstboot= kernel command line option, ignoring: %m");
else if (!enabled) {
log_debug("systemd.firstboot=no set, skipping interactive enrollment.");
return 0;
}
/* Open the volume just to inspect its header (no unlocking needed yet). */
r = prepare_luks(c, &cd, /* ret_volume_key= */ NULL);
if (r < 0)
return r;
r = collect_existing_types(cd, &existing_mask);
if (r < 0)
return r;
/* If a credential of a suppressed type is already enrolled, do nothing. This lets the wizard be
* wired into first boot but stay quiet once the system has been set up. */
if ((existing_mask & prompt_suppress_mask) != 0) {
log_debug("A credential of a suppressed type is already enrolled, skipping interactive setup.");
return 0;
}
if (existing_mask == 0) {
log_debug("No recognized LUKS slots, we're unlikely able to unlock, skipping interactive setup.");
return 0;
}
(void) mute_console(mute_console_link);
/* Draw the installer-style chrome (blue bars at the top and bottom) around the wizard, matching
* systemd-sysinstall. The caller hides it again via a deferred chrome_hide() once enrollment is
* complete. */
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
if (chrome)
(void) chrome_show("Additional Disk Encryption Key Enrollment", /* bottom= */ NULL);
printf("%s%s%sLet's enroll additional disk encryption mechanisms for recovering access to the system.%s\n\n",
emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "",
ansi_highlight(), ansi_normal());
_cleanup_free_ char *s = NULL;
for (EnrollType t = 0; t < _ENROLL_TYPE_MAX; t++) {
if (!FLAGS_SET(existing_mask, 1 << t))
continue;
if (!strextend_with_separator(&s, ", ", enroll_type_to_string(t)))
return log_oom();
}
printf("Currently enrolled mechanisms: %s\n\n", s);
r = choose_mechanism(c);
if (r <= 0)
return r;
r = ask_wipe(c, existing_mask);
if (r < 0)
return r;
return 1;
}
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
/* Runs the interactive first-boot enrollment wizard, populating *c (c->enroll_type, c->fido2_device,
* c->wipe_slots_mask). On return c->enroll_type is set to the chosen mechanism, or left
* _ENROLL_TYPE_INVALID if the user skipped or the wizard was suppressed.
*
* prompt_suppress_mask is a bitmask of (1U << EnrollType): if a slot of any such type already exists on the
* volume, the wizard does nothing (so it can be hooked into first boot but stay quiet on later boots). */
int cryptenroll_run_interactive(EnrollContext *c, unsigned prompt_suppress_mask, bool chrome, sd_varlink **mute_console_link);
+50 -29
View File
@@ -11,21 +11,17 @@
#include "log.h"
#include "parse-util.h"
struct keyslot_metadata {
int slot;
const char *type;
};
int list_enrolled(struct crypt_device *cd) {
_cleanup_free_ struct keyslot_metadata *keyslot_metadata = NULL;
_cleanup_(table_unrefp) Table *t = NULL;
size_t n_keyslot_metadata = 0;
int collect_enrolled_slots(struct crypt_device *cd, EnrolledSlot **ret, size_t *ret_n) {
_cleanup_free_ EnrolledSlot *slots = NULL;
size_t n_slots = 0;
int slot_max, r;
TableCell *cell;
assert(cd);
assert(ret);
assert(ret_n);
/* First step, find out all currently used slots */
/* First step, find out all currently used slots. A slot without an associated token is a bare
* passphrase slot, hence default to ENROLL_PASSWORD. */
assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0);
for (int slot = 0; slot < slot_max; slot++) {
crypt_keyslot_info status;
@@ -34,11 +30,12 @@ int list_enrolled(struct crypt_device *cd) {
if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST))
continue;
if (!GREEDY_REALLOC(keyslot_metadata, n_keyslot_metadata+1))
if (!GREEDY_REALLOC(slots, n_slots + 1))
return log_oom();
keyslot_metadata[n_keyslot_metadata++] = (struct keyslot_metadata) {
slots[n_slots++] = (EnrolledSlot) {
.slot = slot,
.type = ENROLL_PASSWORD,
};
}
@@ -46,7 +43,6 @@ int list_enrolled(struct crypt_device *cd) {
* token they are assigned to */
for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
const char *type;
sd_json_variant *w, *z;
EnrollType et;
@@ -64,11 +60,7 @@ int list_enrolled(struct crypt_device *cd) {
continue;
}
et = luks2_token_type_from_string(sd_json_variant_string(w));
if (et < 0)
type = "other";
else
type = enroll_type_to_string(et);
et = luks2_token_type_from_string(sd_json_variant_string(w)); /* _ENROLL_TYPE_INVALID for unrecognized type */
w = sd_json_variant_by_key(v, "keyslots");
if (!w || !sd_json_variant_is_array(w)) {
@@ -90,19 +82,40 @@ int list_enrolled(struct crypt_device *cd) {
continue;
}
for (size_t i = 0; i < n_keyslot_metadata; i++) {
if ((unsigned) keyslot_metadata[i].slot != u)
FOREACH_ARRAY(s, slots, n_slots) {
if ((unsigned) s->slot != u)
continue;
if (keyslot_metadata[i].type) /* Slot claimed multiple times? */
keyslot_metadata[i].type = POINTER_MAX;
if (s->conflict) /* Already marked as claimed multiple times. */
break;
if (s->type != ENROLL_PASSWORD) /* Slot already claimed by another token? */
s->conflict = true;
else
keyslot_metadata[i].type = type;
s->type = et;
}
}
}
/* Finally, create a table out of it all */
*ret = TAKE_PTR(slots);
*ret_n = n_slots;
return 0;
}
int list_enrolled(struct crypt_device *cd) {
_cleanup_free_ EnrolledSlot *slots = NULL;
_cleanup_(table_unrefp) Table *t = NULL;
size_t n_slots;
int r;
TableCell *cell;
assert(cd);
r = collect_enrolled_slots(cd, &slots, &n_slots);
if (r < 0)
return r;
/* Create a table out of it all */
t = table_new("slot", "type");
if (!t)
return log_oom();
@@ -110,12 +123,20 @@ int list_enrolled(struct crypt_device *cd) {
assert_se(cell = table_get_cell(t, 0, 0));
(void) table_set_align_percent(t, cell, 100);
for (size_t i = 0; i < n_keyslot_metadata; i++) {
FOREACH_ARRAY(s, slots, n_slots) {
const char *type;
if (s->conflict)
type = "conflict";
else if (s->type < 0)
type = "other"; /* token of unrecognized type */
else
type = enroll_type_to_string(s->type);
r = table_add_many(
t,
TABLE_INT, keyslot_metadata[i].slot,
TABLE_STRING, keyslot_metadata[i].type == POINTER_MAX ? "conflict" :
keyslot_metadata[i].type ?: "password");
TABLE_INT, s->slot,
TABLE_STRING, type);
if (r < 0)
return table_log_add_error(r);
}
+13
View File
@@ -1,6 +1,19 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
#include "shared-forward.h"
typedef struct EnrolledSlot {
int slot;
/* The token type associated with the slot. ENROLL_PASSWORD means a bare passphrase slot without
* any token. _ENROLL_TYPE_INVALID means either a token of an unrecognized type, or a slot claimed
* by more than one token (see 'conflict'). */
EnrollType type;
bool conflict;
} EnrolledSlot;
/* Enumerates the active keyslots and classifies each by token type. Returns a newly allocated array. */
int collect_enrolled_slots(struct crypt_device *cd, EnrolledSlot **ret, size_t *ret_n);
int list_enrolled(struct crypt_device *cd);
+110 -16
View File
@@ -7,25 +7,101 @@
#include "env-util.h"
#include "errno-util.h"
#include "escape.h"
#include "fileio.h"
#include "iovec-util.h"
#include "log.h"
#include "password-quality-util.h"
#include "string-util.h"
#include "strv.h"
int load_volume_key_password(
int load_volume_key_empty(
const EnrollContext *c,
struct crypt_device *cd,
const char *cd_node,
void *ret_vk,
size_t *ret_vks) {
struct iovec *ret_vk) {
int r;
assert_se(c);
assert_se(cd);
assert_se(ret_vk);
r = sym_crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk->iov_base,
&ret_vk->iov_len,
"",
0);
if (r < 0)
return log_error_errno(r, "Provided empty password did not work: %m");
return r;
}
int load_volume_key_keyfile(
const EnrollContext *c,
struct crypt_device *cd,
struct iovec *ret_vk) {
_cleanup_(erase_and_freep) char *password = NULL;
size_t password_len;
int r;
assert_se(c);
assert_se(cd);
assert_se(ret_vk);
r = read_full_file_full(
AT_FDCWD,
c->unlock_keyfile,
UINT64_MAX,
4U * U64_MB, /* safety net */
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET|READ_FULL_FILE_FAIL_WHEN_LARGER,
NULL,
&password,
&password_len);
if (r < 0)
return log_error_errno(r, "Reading keyfile %s failed: %m", c->unlock_keyfile);
r = sym_crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk->iov_base,
&ret_vk->iov_len,
password,
password_len);
if (r < 0)
return log_error_errno(r, "Unlocking via keyfile failed: %m");
return r;
}
int load_volume_key_password(
const EnrollContext *c,
struct crypt_device *cd,
struct iovec *ret_vk) {
_cleanup_(erase_and_freep) char *envpw = NULL;
int r;
assert_se(c);
assert_se(c->node);
assert_se(cd);
assert_se(cd_node);
assert_se(ret_vk);
assert_se(ret_vks);
if (c->unlock_password) {
r = sym_crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk->iov_base,
&ret_vk->iov_len,
c->unlock_password,
strlen(c->unlock_password));
if (r < 0)
return log_error_errno(r, "Provided unlock password did not work: %m");
return r;
}
r = getenv_steal_erase("PASSWORD", &envpw);
if (r < 0)
@@ -34,22 +110,27 @@ int load_volume_key_password(
r = sym_crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk,
ret_vks,
ret_vk->iov_base,
&ret_vk->iov_len,
envpw,
strlen(envpw));
if (r < 0)
return log_error_errno(r, "Password from environment variable $PASSWORD did not work: %m");
} else {
AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED;
if (!c->interactive)
return log_error_errno(SYNTHETIC_ERRNO(ENOPKG),
"Password querying disabled via 'headless' option, but no password provided for disk %s.",
c->node);
_cleanup_free_ char *question = NULL, *id = NULL, *disk_path = NULL;
unsigned i = 5;
question = strjoin("Please enter current passphrase for disk ", cd_node, ":");
question = strjoin("Please enter current passphrase for disk ", c->node, ":");
if (!question)
return log_oom();
disk_path = cescape(cd_node);
disk_path = cescape(c->node);
if (!disk_path)
return log_oom();
@@ -84,8 +165,8 @@ int load_volume_key_password(
r = sym_crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk,
ret_vks,
ret_vk->iov_base,
&ret_vk->iov_len,
*p,
strlen(*p));
if (r >= 0)
@@ -103,6 +184,7 @@ int load_volume_key_password(
}
int enroll_password(
const EnrollContext *c,
struct crypt_device *cd,
const struct iovec *volume_key) {
@@ -111,18 +193,30 @@ int enroll_password(
const char *node;
int r, keyslot;
assert(c);
assert(cd);
assert(iovec_is_set(volume_key));
assert_se(node = sym_crypt_get_device_name(cd));
r = getenv_steal_erase("NEWPASSWORD", &new_password);
if (r < 0)
return log_error_errno(r, "Failed to acquire password from environment: %m");
if (r == 0) {
if (c->passphrase) {
new_password = memdup_suffix0(c->passphrase, c->passphrase_size);
if (!new_password)
return log_oom();
} else {
r = getenv_steal_erase("NEWPASSWORD", &new_password);
if (r < 0)
return log_error_errno(r, "Failed to acquire password from environment: %m");
}
if (!new_password) {
_cleanup_free_ char *disk_path = NULL, *id = NULL;
unsigned i = 5;
if (!c->interactive)
return log_error_errno(SYNTHETIC_ERRNO(ENOPKG),
"Password querying disabled via 'headless' option, but no new password provided.");
assert_se(node = sym_crypt_get_device_name(cd));
(void) suggest_passwords();
+6 -2
View File
@@ -1,7 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
#include "shared-forward.h"
int load_volume_key_password(struct crypt_device *cd, const char* cd_node, void *ret_vk, size_t *ret_vks);
int enroll_password(struct crypt_device *cd, const struct iovec *volume_key);
int load_volume_key_empty(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk);
int load_volume_key_keyfile(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk);
int load_volume_key_password(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk);
int enroll_password(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key);
+7 -6
View File
@@ -36,7 +36,7 @@ static int uri_set_private_class(const char *uri, char **ret_uri) {
}
#endif
int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const char *uri) {
int enroll_pkcs11(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key) {
#if HAVE_P11KIT && HAVE_OPENSSL
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
@@ -50,18 +50,19 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const
const char *node;
int r;
assert_se(c);
assert_se(cd);
assert_se(iovec_is_set(volume_key));
assert_se(uri);
assert_se(c->pkcs11_token_uri);
assert_se(node = sym_crypt_get_device_name(cd));
r = pkcs11_acquire_public_key(
uri,
c->pkcs11_token_uri,
"volume enrollment operation",
"drive-harddisk",
"cryptenroll.pkcs11-pin",
/* askpw_flags= */ 0,
c->interactive ? 0 : ASK_PASSWORD_HEADLESS,
&pkey,
&rsa_padding,
/* ret_pin_used= */ NULL);
@@ -100,14 +101,14 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const
/* Change 'type=cert' or 'type=public' in the provided URI to 'type=private' before storing in
a LUKS2 header. This allows users to use output of some PKCS#11 tools directly without
modifications. */
r = uri_set_private_class(uri, &private_uri);
r = uri_set_private_class(c->pkcs11_token_uri, &private_uri);
if (r < 0)
return r;
r = sd_json_buildo(&v,
SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")),
SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
SD_JSON_BUILD_PAIR_STRING("pkcs11-uri", private_uri ?: uri),
SD_JSON_BUILD_PAIR_STRING("pkcs11-uri", private_uri ?: c->pkcs11_token_uri),
SD_JSON_BUILD_PAIR_BASE64("pkcs11-key", saved_key, saved_key_size),
SD_JSON_BUILD_PAIR_CONDITION(rsa_padding > PKCS11_RSA_PADDING_PKCS1V15,
"pkcs11-padding", SD_JSON_BUILD_STRING(pkcs11_rsa_padding_to_string(rsa_padding))));
+2 -1
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
#include "shared-forward.h"
int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key, const char *uri);
int enroll_pkcs11(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key);
+32 -22
View File
@@ -12,8 +12,10 @@
#include "recovery-key.h"
int enroll_recovery(
const EnrollContext *c,
struct crypt_device *cd,
const struct iovec *volume_key) {
const struct iovec *volume_key,
char **ret_recovery_key) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_(erase_and_freep) char *password = NULL;
@@ -21,6 +23,7 @@ int enroll_recovery(
int keyslot, r, q;
const char *node;
assert_se(c);
assert_se(cd);
assert_se(iovec_is_set(volume_key));
@@ -44,31 +47,33 @@ int enroll_recovery(
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new recovery key to %s: %m", node);
fflush(stdout);
fprintf(stderr,
"A secret recovery key has been generated for this volume:\n\n"
" %s%s%s",
emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "",
emoji_enabled() ? " " : "",
ansi_highlight());
fflush(stderr);
if (c->interactive) {
fflush(stdout);
fprintf(stderr,
"A secret recovery key has been generated for this volume:\n\n"
" %s%s%s",
emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "",
emoji_enabled() ? " " : "",
ansi_highlight());
fflush(stderr);
fputs(password, stdout);
fflush(stdout);
fputs(password, stdout);
fflush(stdout);
fputs(ansi_normal(), stderr);
fflush(stderr);
fputs(ansi_normal(), stderr);
fflush(stderr);
fputc('\n', stdout);
fflush(stdout);
fputc('\n', stdout);
fflush(stdout);
fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n"
"regain access to the volume if the other configured access credentials have\n"
"been lost or forgotten. The recovery key may be entered in place of a password\n"
"whenever authentication is requested.\n", stderr);
fflush(stderr);
fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n"
"regain access to the volume if the other configured access credentials have\n"
"been lost or forgotten. The recovery key may be entered in place of a password\n"
"whenever authentication is requested.\n", stderr);
fflush(stderr);
(void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password);
(void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password);
}
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) {
r = log_oom();
@@ -89,7 +94,12 @@ int enroll_recovery(
goto rollback;
}
log_info("New recovery key enrolled as key slot %i.", keyslot);
if (c->interactive)
log_info("New recovery key enrolled as key slot %i.", keyslot);
if (ret_recovery_key)
*ret_recovery_key = TAKE_PTR(password);
return keyslot;
rollback:
+4 -1
View File
@@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
#include "shared-forward.h"
int enroll_recovery(struct crypt_device *cd, const struct iovec *volume_key);
/* When c->interactive is unset, the generated recovery key is returned via ret_recovery_key
* instead of being printed to stdout/stderr + rendered as a QR code. */
int enroll_recovery(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key, char **ret_recovery_key);
+53 -62
View File
@@ -169,11 +169,9 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) {
#endif
int load_volume_key_tpm2(
const EnrollContext *c,
struct crypt_device *cd,
const char *cd_node,
const char *device,
void *ret_vk,
size_t *ret_vks) {
struct iovec *ret_vk) {
#if HAVE_TPM2
_cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
@@ -181,10 +179,10 @@ int load_volume_key_tpm2(
ssize_t passphrase_size;
int r;
assert_se(c);
assert_se(c->node);
assert_se(cd);
assert_se(cd_node);
assert_se(ret_vk);
assert_se(ret_vks);
bool found_some = false;
int token = 0; /* first token to look at */
@@ -235,8 +233,8 @@ int load_volume_key_tpm2(
found_some = true;
r = acquire_tpm2_key(
cd_node,
device,
c->node,
c->unlock_tpm2_device,
hash_pcr_mask,
pcr_bank,
&pubkey,
@@ -255,7 +253,7 @@ int load_volume_key_tpm2(
tpm2_flags,
/* until= */ 0,
"cryptenroll.tpm2-pin",
/* askpw_flags= */ 0,
c->interactive ? 0 : ASK_PASSWORD_HEADLESS,
&decrypted_key);
if (IN_SET(r, -EACCES, -ENOLCK))
return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed");
@@ -275,8 +273,8 @@ int load_volume_key_tpm2(
r = sym_crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk,
ret_vks,
ret_vk->iov_base,
&ret_vk->iov_len,
passphrase,
passphrase_size);
if (r < 0)
@@ -288,19 +286,9 @@ int load_volume_key_tpm2(
#endif
}
int enroll_tpm2(struct crypt_device *cd,
int enroll_tpm2(const EnrollContext *c,
struct crypt_device *cd,
const struct iovec *volume_key,
const char *device,
uint32_t seal_key_handle,
const char *device_key,
Tpm2PCRValue *hash_pcr_values,
size_t n_hash_pcr_values,
const char *pcr_pubkey_path,
bool load_pcr_pubkey,
uint32_t pubkey_pcr_mask,
const char *signature_path,
bool use_pin,
const char *pcrlock_path,
int *ret_slot_to_wipe) {
#if HAVE_TPM2
@@ -314,6 +302,8 @@ int enroll_tpm2(struct crypt_device *cd,
int r, keyslot, slot_to_wipe = -1;
TPM2Flags flags = 0;
uint16_t primary_alg = 0;
/* Mutable copy: cleared on the no-public-key fallback paths below. */
uint32_t pubkey_pcr_mask = c->tpm2_public_key_pcr_mask;
uint8_t binary_salt[SHA256_DIGEST_SIZE] = {};
/*
* erase the salt, we'd rather attempt to not have this in a coredump
@@ -323,15 +313,16 @@ int enroll_tpm2(struct crypt_device *cd,
*/
CLEANUP_ERASE(binary_salt);
assert(c);
assert(cd);
assert(iovec_is_set(volume_key));
assert(tpm2_pcr_values_valid(hash_pcr_values, n_hash_pcr_values));
assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
assert(tpm2_pcr_values_valid(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values));
assert(TPM2_PCR_MASK_VALID(c->tpm2_public_key_pcr_mask));
assert(ret_slot_to_wipe);
assert_se(node = sym_crypt_get_device_name(cd));
if (use_pin) {
if (c->tpm2_pin) {
r = get_pin(&pin_str, &flags);
if (r < 0)
return r;
@@ -354,10 +345,10 @@ int enroll_tpm2(struct crypt_device *cd,
}
TPM2B_PUBLIC public = {};
if (pcr_pubkey_path || load_pcr_pubkey) {
r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (c->tpm2_public_key || c->tpm2_load_public_key) {
r = tpm2_load_pcr_public_key(c->tpm2_public_key, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (pcr_pubkey_path || signature_path || r != -ENOENT)
if (c->tpm2_public_key || c->tpm2_signature || r != -ENOENT)
return log_error_errno(r, "Failed to read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
@@ -367,11 +358,11 @@ int enroll_tpm2(struct crypt_device *cd,
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
if (signature_path) {
if (c->tpm2_signature) {
/* Also try to load the signature JSON object, to verify that our enrollment will work.
* This is optional however, skip it if it's not explicitly provided. */
r = tpm2_load_pcr_signature(signature_path, &signature_json);
r = tpm2_load_pcr_signature(c->tpm2_signature, &signature_json);
if (r < 0)
return log_error_errno(r, "Failed to read TPM PCR signature: %m");
}
@@ -379,15 +370,15 @@ int enroll_tpm2(struct crypt_device *cd,
} else
pubkey_pcr_mask = 0;
bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values);
bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values);
_cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {};
if (pcrlock_path) {
r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy);
if (c->tpm2_pcrlock) {
r = tpm2_pcrlock_policy_load(c->tpm2_pcrlock, &pcrlock_policy);
if (r < 0)
return r;
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find pcrlock policy %s.", pcrlock_path);
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find pcrlock policy %s.", c->tpm2_pcrlock);
any_pcr_value_specified = true;
flags |= TPM2_FLAGS_USE_PCRLOCK;
@@ -395,23 +386,23 @@ int enroll_tpm2(struct crypt_device *cd,
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
TPM2B_PUBLIC device_key_public = {};
if (device_key) {
r = tpm2_load_public_key_file(device_key, &device_key_public);
if (c->tpm2_device_key) {
r = tpm2_load_public_key_file(c->tpm2_device_key, &device_key_public);
if (r < 0)
return r;
if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values))
if (!tpm2_pcr_values_has_all_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Must provide all PCR values when using TPM2 device key.");
primary_alg = device_key_public.publicArea.type;
} else {
r = tpm2_context_new_or_warn(device, &tpm2_context);
r = tpm2_context_new_or_warn(c->tpm2_device, &tpm2_context);
if (r < 0)
return r;
if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) {
r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values);
if (!tpm2_pcr_values_has_all_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values)) {
r = tpm2_pcr_read_missing_values(tpm2_context, c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values);
if (r < 0)
return log_error_errno(r, "Could not read pcr values: %m");
}
@@ -420,10 +411,10 @@ int enroll_tpm2(struct crypt_device *cd,
uint16_t hash_pcr_bank = 0;
uint32_t hash_pcr_mask = 0;
if (n_hash_pcr_values > 0) {
if (c->tpm2_n_hash_pcr_values > 0) {
size_t hash_count;
r = tpm2_pcr_values_hash_count(hash_pcr_values, n_hash_pcr_values, &hash_count);
r = tpm2_pcr_values_hash_count(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values, &hash_count);
if (r < 0)
return log_error_errno(r, "Could not get hash count: %m");
@@ -431,12 +422,12 @@ int enroll_tpm2(struct crypt_device *cd,
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected.");
/* If we use a literal PCR value policy, derive the bank to use from the algorithm specified on the hash values */
hash_pcr_bank = hash_pcr_values[0].hash;
r = tpm2_pcr_values_to_mask(hash_pcr_values, n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask);
hash_pcr_bank = c->tpm2_hash_pcr_values[0].hash;
r = tpm2_pcr_values_to_mask(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask);
if (r < 0)
return log_error_errno(r, "Could not get hash mask: %m");
} else if (pubkey_pcr_mask != 0 && !device_key) {
} else if (pubkey_pcr_mask != 0 && !c->tpm2_device_key) {
/* If no literal PCR value policy is used, then let's determine the mask to use automatically
* from the measurements of the TPM. */
@@ -461,21 +452,21 @@ int enroll_tpm2(struct crypt_device *cd,
/* If both PCR public key unlock and pcrlock unlock is selected, then we create the one for PCR public key unlock first. */
r = tpm2_calculate_sealing_policy(
hash_pcr_values,
n_hash_pcr_values,
c->tpm2_hash_pcr_values,
c->tpm2_n_hash_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL,
use_pin,
pcrlock_path && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
c->tpm2_pin,
c->tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
policy_hash + 0);
if (r < 0)
return r;
if (pcrlock_path && iovec_is_set(&pubkey)) {
if (c->tpm2_pcrlock && iovec_is_set(&pubkey)) {
r = tpm2_calculate_sealing_policy(
hash_pcr_values,
n_hash_pcr_values,
c->tpm2_hash_pcr_values,
c->tpm2_n_hash_pcr_values,
/* public= */ NULL, /* This one is off now */
use_pin,
c->tpm2_pin,
&pcrlock_policy, /* And this one on instead. */
policy_hash + 1);
if (r < 0)
@@ -488,7 +479,7 @@ int enroll_tpm2(struct crypt_device *cd,
size_t n_blobs = 0;
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
if (device_key) {
if (c->tpm2_device_key) {
if (n_policy_hash > 1)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Combined signed PCR policies and pcrlock policies cannot be calculated offline, currently.");
@@ -500,7 +491,7 @@ int enroll_tpm2(struct crypt_device *cd,
n_blobs = 1;
r = tpm2_calculate_seal(
seal_key_handle,
c->tpm2_seal_key_handle,
&device_key_public,
/* attributes= */ NULL,
/* secret= */ NULL,
@@ -511,7 +502,7 @@ int enroll_tpm2(struct crypt_device *cd,
&srk);
} else
r = tpm2_seal(tpm2_context,
seal_key_handle,
c->tpm2_seal_key_handle,
policy_hash,
n_policy_hash,
pin_str,
@@ -534,7 +525,7 @@ int enroll_tpm2(struct crypt_device *cd,
log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now.");
else if (r < 0)
return r;
else if (use_pin) {
else if (c->tpm2_pin) {
log_debug("This PCR set is already enrolled, re-enrolling anyway to update PIN.");
slot_to_wipe = r;
} else {
@@ -544,7 +535,7 @@ int enroll_tpm2(struct crypt_device *cd,
}
/* If possible, verify the sealed data object. */
if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !device_key) {
if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !c->tpm2_device_key) {
_cleanup_(iovec_done_erase) struct iovec secret2 = {};
log_debug("Unsealing for verification...");
@@ -555,7 +546,7 @@ int enroll_tpm2(struct crypt_device *cd,
pubkey_pcr_mask,
signature_json,
pin_str,
pcrlock_path ? &pcrlock_policy : NULL,
c->tpm2_pcrlock ? &pcrlock_policy : NULL,
primary_alg,
blobs,
n_blobs,
@@ -600,9 +591,9 @@ int enroll_tpm2(struct crypt_device *cd,
n_blobs,
policy_hash_as_iovec,
n_policy_hash,
use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
c->tpm2_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
&srk,
pcrlock_path ? &pcrlock_policy.nv_handle : NULL,
c->tpm2_pcrlock ? &pcrlock_policy.nv_handle : NULL,
flags,
&v);
if (r < 0)
+3 -2
View File
@@ -1,7 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
#include "shared-forward.h"
int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks);
int enroll_tpm2(struct crypt_device *cd, const struct iovec *volume_key, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcr_values, size_t n_hash_pcr_values, const char *pubkey_path, bool load_pcr_pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *ret_slot_to_wipe);
int load_volume_key_tpm2(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk);
int enroll_tpm2(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key, int *ret_slot_to_wipe);
+481
View File
@@ -0,0 +1,481 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-varlink.h"
#include "alloc-util.h"
#include "bus-polkit.h"
#include "cryptenroll.h"
#include "cryptenroll-list.h"
#include "cryptenroll-varlink.h"
#include "cryptenroll-wipe.h"
#include "cryptsetup-util.h"
#include "fd-util.h"
#include "hashmap.h"
#include "iovec-util.h"
#include "json-util.h"
#include "libfido2-util.h"
#include "path-util.h"
#include "string-util.h"
#include "varlink-io.systemd.CryptEnroll.h"
#include "varlink-util.h"
int enroll_context_notify_state(const EnrollContext *c, const char *state) {
int r;
assert(c);
assert(state);
/* Only relevant when invoked over a Varlink connection that asked for progress ('more'). */
if (!c->link)
return 0;
r = sd_varlink_notifybo(c->link, SD_JSON_BUILD_PAIR_STRING("state", state));
if (r < 0)
return r;
return sd_varlink_flush(c->link);
}
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_enroll_type, EnrollType, enroll_type_from_string);
typedef struct MethodEnrollParameters {
char *node;
EnrollType mechanism;
char *unlock_password;
char *unlock_keyfile;
int64_t unlock_keyfile_fd_idx;
char *unlock_fido2_device;
char *unlock_tpm2_device;
char *password;
char *fido2_device;
char *fido2_pin;
int fido2_with_client_pin;
int fido2_with_user_presence;
int fido2_with_user_verification;
sd_json_variant *wipe_slots;
sd_json_variant *wipe_types;
} MethodEnrollParameters;
static void method_enroll_parameters_done(MethodEnrollParameters *p) {
assert(p);
free(p->node);
erase_and_free(p->unlock_password);
free(p->unlock_keyfile);
free(p->unlock_fido2_device);
free(p->unlock_tpm2_device);
erase_and_free(p->password);
free(p->fido2_device);
erase_and_free(p->fido2_pin);
sd_json_variant_unref(p->wipe_slots);
sd_json_variant_unref(p->wipe_types);
}
static int parse_wipe_slots(sd_json_variant *v, EnrollContext *c) {
sd_json_variant *e;
assert(c);
JSON_VARIANT_ARRAY_FOREACH(e, v) {
if (!sd_json_variant_is_unsigned(e))
return -EINVAL;
uint64_t u = sd_json_variant_unsigned(e);
if (u > INT_MAX)
return -ERANGE;
if (!GREEDY_REALLOC(c->wipe_slots, c->n_wipe_slots + 1))
return -ENOMEM;
c->wipe_slots[c->n_wipe_slots++] = (int) u;
}
return 0;
}
static int parse_wipe_types(sd_json_variant *v, EnrollContext *c) {
sd_json_variant *e;
assert(c);
JSON_VARIANT_ARRAY_FOREACH(e, v) {
if (!sd_json_variant_is_string(e))
return -EINVAL;
/* Translate the Varlink (underscore) spelling to the internal (dash) one before lookup. */
_cleanup_free_ char *s = strdup(sd_json_variant_string(e));
if (!s)
return -ENOMEM;
EnrollType t = enroll_type_from_string(json_dashify(s));
if (t < 0)
return -EINVAL;
c->wipe_slots_mask |= 1U << t;
}
return 0;
}
static int varlink_error_for_enroll(sd_varlink *link, int error) {
assert(link);
assert(error < 0);
/* Translates the errnos the enrollment/unlock helpers return into the interface's named errors,
* falling back to a plain errno error. */
switch (error) {
case -EHOSTDOWN: /* check_for_homed() */
return sd_varlink_error(link, "io.systemd.CryptEnroll.VolumeUnderForeignManagement", NULL);
case -ENOPKG: /* credential querying disabled in headless mode but none provided */
return sd_varlink_error(link, "io.systemd.CryptEnroll.PasswordRequired", NULL);
case -EPERM:
case -ENOKEY: /* provided password/key did not unlock the volume */
return sd_varlink_error(link, "io.systemd.CryptEnroll.PasswordIncorrect", NULL);
case -ENODEV:
case -ENOTUNIQ: /* no (or no unique) FIDO2 device found */
return sd_varlink_error(link, "io.systemd.CryptEnroll.FidoDeviceNotFound", NULL);
case -ENOSTR: /* FIDO_ERR_ACTION_TIMEOUT */
return sd_varlink_error(link, "io.systemd.CryptEnroll.FidoActionTimeout", NULL);
default:
return sd_varlink_error_errno(link, error);
}
}
static int vl_method_enroll(
sd_varlink *link,
sd_json_variant *parameters,
sd_varlink_method_flags_t flags,
void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "node", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(MethodEnrollParameters, node), SD_JSON_MANDATORY|SD_JSON_STRICT },
{ "mechanism", SD_JSON_VARIANT_STRING, json_dispatch_enroll_type, offsetof(MethodEnrollParameters, mechanism), SD_JSON_MANDATORY },
{ "unlockPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, unlock_password), SD_JSON_NULLABLE },
{ "unlockKeyFile", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(MethodEnrollParameters, unlock_keyfile), SD_JSON_NULLABLE|SD_JSON_STRICT },
{ "unlockKeyFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int64, offsetof(MethodEnrollParameters, unlock_keyfile_fd_idx), SD_JSON_NULLABLE },
{ "unlockFido2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, unlock_fido2_device), SD_JSON_NULLABLE },
{ "unlockTpm2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, unlock_tpm2_device), SD_JSON_NULLABLE },
{ "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, password), SD_JSON_NULLABLE },
{ "fido2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, fido2_device), SD_JSON_NULLABLE },
{ "fido2Pin", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, fido2_pin), SD_JSON_NULLABLE },
{ "fido2WithClientPin", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodEnrollParameters, fido2_with_client_pin), SD_JSON_NULLABLE },
{ "fido2WithUserPresence", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodEnrollParameters, fido2_with_user_presence), SD_JSON_NULLABLE },
{ "fido2WithUserVerification", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodEnrollParameters, fido2_with_user_verification), SD_JSON_NULLABLE },
{ "wipeSlots", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MethodEnrollParameters, wipe_slots), SD_JSON_NULLABLE },
{ "wipeTypes", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MethodEnrollParameters, wipe_types), SD_JSON_NULLABLE },
VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
_cleanup_(method_enroll_parameters_done) MethodEnrollParameters p = {
.mechanism = _ENROLL_TYPE_INVALID,
.unlock_keyfile_fd_idx = -1,
.fido2_with_client_pin = -1,
.fido2_with_user_presence = -1,
.fido2_with_user_verification = -1,
};
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(iovec_done_erase) struct iovec vk = {};
_cleanup_close_ int keyfile_fd = -EBADF;
Hashmap **polkit_registry = ASSERT_PTR(userdata);
int slot, r;
assert(link);
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
if (p.mechanism < 0)
return sd_varlink_error_invalid_parameter_name(link, "mechanism");
r = varlink_verify_polkit_async(
link,
/* bus= */ NULL,
"io.systemd.cryptenroll.enroll",
/* details= */ NULL,
polkit_registry);
if (r <= 0)
return r;
/* Populate the context. This is the Varlink equivalent of enroll_context_from_args(). */
_cleanup_(enroll_context_done) EnrollContext c = ENROLL_CONTEXT_NULL;
c.interactive = false;
c.enroll_type = p.mechanism;
c.unlock_type = _UNLOCK_TYPE_INVALID;
if (strdup_to(&c.node, p.node) < 0)
return -ENOMEM;
if (p.unlock_password) {
if (c.unlock_type >= 0)
return sd_varlink_error_invalid_parameter_name(link, "unlockPassword");
c.unlock_password = TAKE_PTR(p.unlock_password);
c.unlock_type = UNLOCK_PASSWORD;
}
if (p.unlock_keyfile || p.unlock_keyfile_fd_idx >= 0) {
if (c.unlock_type >= 0)
return sd_varlink_error_invalid_parameter_name(link, p.unlock_keyfile ? "unlockKeyFile" : "unlockKeyFileDescriptor");
if (p.unlock_keyfile && p.unlock_keyfile_fd_idx >= 0)
return sd_varlink_error_invalid_parameter_name(link, "unlockKeyFileDescriptor");
if (p.unlock_keyfile_fd_idx >= 0) {
keyfile_fd = sd_varlink_peek_dup_fd(link, p.unlock_keyfile_fd_idx);
if (keyfile_fd < 0)
return sd_varlink_error_invalid_parameter_name(link, "unlockKeyFileDescriptor");
if (asprintf(&c.unlock_keyfile, "/proc/self/fd/%i", keyfile_fd) < 0)
return -ENOMEM;
} else
c.unlock_keyfile = TAKE_PTR(p.unlock_keyfile);
c.unlock_type = UNLOCK_KEYFILE;
}
if (p.unlock_fido2_device) {
if (c.unlock_type >= 0)
return sd_varlink_error_invalid_parameter_name(link, "unlockFido2Device");
if (!streq(p.unlock_fido2_device, "auto")) {
if (!path_is_normalized(p.unlock_fido2_device) || !path_is_absolute(p.unlock_fido2_device))
return sd_varlink_error_invalid_parameter_name(link, "unlockFido2Device");
if (strdup_to(&c.unlock_fido2_device, p.unlock_fido2_device) < 0)
return -ENOMEM;
}
c.unlock_type = UNLOCK_FIDO2;
}
if (p.unlock_tpm2_device) {
if (c.unlock_type >= 0)
return sd_varlink_error_invalid_parameter_name(link, "unlockTpm2Device");
if (!streq(p.unlock_tpm2_device, "auto")) {
if (!path_is_normalized(p.unlock_tpm2_device) || !path_is_absolute(p.unlock_tpm2_device))
return sd_varlink_error_invalid_parameter_name(link, "unlockTpm2Device");
if (strdup_to(&c.unlock_tpm2_device, p.unlock_tpm2_device) < 0)
return -ENOMEM;
}
c.unlock_type = UNLOCK_TPM2;
}
/* If no unlock method is specified, return a recognizable error. We generate invalid parameter name
* for "unlockPassword", simply because it is the best-known unlock method */
if (c.unlock_type < 0)
return sd_varlink_error_invalid_parameter_name(link, "unlockPassword");
/* Mechanism-specific parameters */
switch (c.enroll_type) {
case ENROLL_PASSWORD:
if (p.password) {
c.passphrase = TAKE_PTR(p.password);
c.passphrase_size = strlen(c.passphrase);
}
break;
case ENROLL_RECOVERY:
break;
case ENROLL_FIDO2:
/* enroll_fido2() requires a concrete device, so if none was given (NULL), discover one. */
if (streq_ptr(p.fido2_device, "auto"))
p.fido2_device = mfree(p.fido2_device);
if (p.fido2_device) {
if (!path_is_normalized(p.fido2_device) || !path_is_absolute(p.fido2_device))
return sd_varlink_error_invalid_parameter_name(link, "fido2Device");
r = strdup_to(&c.fido2_device, p.fido2_device);
if (r < 0)
return r;
}
if (!c.fido2_device) {
r = fido2_find_device_auto(&c.fido2_device);
if (r < 0)
return varlink_error_for_enroll(link, r);
}
if (p.fido2_with_client_pin >= 0)
SET_FLAG(c.fido2_lock_with, FIDO2ENROLL_PIN, p.fido2_with_client_pin);
if (p.fido2_with_user_presence >= 0)
SET_FLAG(c.fido2_lock_with, FIDO2ENROLL_UP, p.fido2_with_user_presence);
if (p.fido2_with_user_verification >= 0)
SET_FLAG(c.fido2_lock_with, FIDO2ENROLL_UV, p.fido2_with_user_verification);
c.fido2_pin = TAKE_PTR(p.fido2_pin);
break;
default:
return sd_varlink_error_invalid_parameter_name(link, "mechanism");
}
if (p.wipe_slots) {
r = parse_wipe_slots(p.wipe_slots, &c);
if (r == -ENOMEM)
return r;
if (r < 0)
return sd_varlink_error_invalid_parameter_name(link, "wipeSlots");
}
if (p.wipe_types) {
r = parse_wipe_types(p.wipe_types, &c);
if (r == -ENOMEM)
return r;
if (r < 0)
return sd_varlink_error_invalid_parameter_name(link, "wipeTypes");
}
/* If the caller asked for 'more', remember the link so we can send progress (e.g. FIDO2 touch). */
if (FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
c.link = sd_varlink_ref(link);
r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED);
if (r < 0)
return r;
r = prepare_luks(&c, &cd, &vk);
if (r < 0)
return varlink_error_for_enroll(link, r);
_cleanup_(erase_and_freep) char *recovery_key = NULL;
slot = enroll_now(&c, cd, &vk, &recovery_key);
if (slot < 0)
return varlink_error_for_enroll(link, slot);
/* Wipe any slots the caller selected, keeping the one we just enrolled. */
c.wipe_except_slot = slot;
_cleanup_free_ int *wiped_slots = NULL;
size_t n_wiped_slots = 0;
r = wipe_slots(&c, cd, &wiped_slots, &n_wiped_slots);
if (r < 0)
return varlink_error_for_enroll(link, r);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *wiped_slots_json = NULL;
FOREACH_ARRAY(s, wiped_slots, n_wiped_slots) {
r = sd_json_variant_append_arrayb(&wiped_slots_json, SD_JSON_BUILD_INTEGER(*s));
if (r < 0)
return r;
}
_cleanup_(sd_json_variant_unrefp) sd_json_variant *rk = NULL;
if (recovery_key) {
r = sd_json_variant_new_string(&rk, recovery_key);
if (r < 0)
return r;
sd_json_variant_sensitive(rk);
}
return sd_varlink_replybo(
link,
SD_JSON_BUILD_PAIR_INTEGER("slot", slot),
JSON_BUILD_PAIR_VARIANT_NON_NULL("wipedSlots", wiped_slots_json),
JSON_BUILD_PAIR_VARIANT_NON_NULL("recoveryKey", rk));
}
static int vl_method_list_slots(
sd_varlink *link,
sd_json_variant *parameters,
sd_varlink_method_flags_t flags,
void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "node", SD_JSON_VARIANT_STRING, json_dispatch_const_path, 0, SD_JSON_MANDATORY|SD_JSON_STRICT },
{}
};
_cleanup_(enroll_context_done) EnrollContext c = ENROLL_CONTEXT_NULL;
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_free_ EnrolledSlot *slots = NULL;
const char *node = NULL;
size_t n_slots;
int r;
assert(link);
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL);
r = sd_varlink_dispatch(link, parameters, dispatch_table, &node);
if (r != 0)
return r;
/* NB: No polkit authentication here for now, given this is read access only. */
c.interactive = false;
if (strdup_to(&c.node, node) < 0)
return -ENOMEM;
r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED);
if (r < 0)
return r;
/* We only need to read the LUKS2 header, no volume key required. */
r = prepare_luks(&c, &cd, /* ret_volume_key= */ NULL);
if (r < 0)
return varlink_error_for_enroll(link, r);
r = collect_enrolled_slots(cd, &slots, &n_slots);
if (r < 0)
return r;
FOREACH_ARRAY(s, slots, n_slots) {
/* enroll_type_to_string() returns NULL for unrecognized types; conflicts are reported
* with no type too. The dash spelling is underscorified for the wire by the build macro. */
const char *type = s->conflict ? NULL : enroll_type_to_string(s->type);
r = sd_varlink_notifybo(
link,
SD_JSON_BUILD_PAIR_INTEGER("slot", s->slot),
JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("type", type));
if (r < 0)
return r;
}
return sd_varlink_reply(link, NULL);
}
int cryptenroll_varlink_server(void) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
_cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
int r;
r = varlink_server_new(
&varlink_server,
SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_MYSELF_ONLY|SD_VARLINK_SERVER_INPUT_SENSITIVE|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT|SD_VARLINK_SERVER_HANDLE_SIGINT|SD_VARLINK_SERVER_HANDLE_SIGTERM,
&polkit_registry);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_CryptEnroll);
if (r < 0)
return log_error_errno(r, "Failed to add Varlink interface: %m");
r = sd_varlink_server_bind_method_many(
varlink_server,
"io.systemd.CryptEnroll.Enroll", vl_method_enroll,
"io.systemd.CryptEnroll.ListSlots", vl_method_list_slots);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
r = sd_varlink_server_loop_auto(varlink_server);
if (r < 0)
return log_error_errno(r, "Failed to run Varlink event loop: %m");
return 0;
}
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
/* Sends a progress 'state' notification over c->link, if (and only if) the enrollment was triggered via a
* Varlink call with the 'more' flag set. A no-op (returning 0) otherwise. */
int enroll_context_notify_state(const EnrollContext *c, const char *state);
int cryptenroll_varlink_server(void);
+46 -19
View File
@@ -296,24 +296,29 @@ static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slo
return false;
}
int wipe_slots(struct crypt_device *cd,
const int explicit_slots[],
size_t n_explicit_slots,
WipeScope by_scope,
unsigned by_mask,
int except_slot) {
int wipe_slots(const EnrollContext *c,
struct crypt_device *cd,
int **ret_wiped_slots,
size_t *ret_n_wiped_slots) {
_cleanup_set_free_ Set *wipe_slots = NULL, *wipe_tokens = NULL, *keep_slots = NULL;
_cleanup_free_ int *ordered_slots = NULL, *ordered_tokens = NULL;
size_t n_ordered_slots = 0, n_ordered_tokens = 0;
_cleanup_free_ int *ordered_slots = NULL, *ordered_tokens = NULL, *wiped_slots = NULL;
size_t n_ordered_slots = 0, n_ordered_tokens = 0, n_wiped_slots = 0;
int r, slot_max, ret;
void *e;
assert_se(c);
assert_se(cd);
assert_se(!!ret_wiped_slots == !!ret_n_wiped_slots);
/* Shortcut if nothing to wipe. */
if (n_explicit_slots == 0 && by_mask == 0 && by_scope == WIPE_EXPLICIT)
if (c->n_wipe_slots == 0 && c->wipe_slots_mask == 0 && c->wipe_slots_scope == WIPE_EXPLICIT) {
if (ret_wiped_slots)
*ret_wiped_slots = NULL;
if (ret_n_wiped_slots)
*ret_n_wiped_slots = 0;
return 0;
}
/* So this is a bit more complicated than I'd wish, but we want support three different axis for wiping slots:
*
@@ -334,23 +339,23 @@ int wipe_slots(struct crypt_device *cd,
return log_oom();
/* Let's maintain one set of slots for the slots we definitely want to keep */
if (except_slot >= 0)
if (set_put(keep_slots, INT_TO_PTR(except_slot)) < 0)
if (c->wipe_except_slot >= 0)
if (set_put(keep_slots, INT_TO_PTR(c->wipe_except_slot)) < 0)
return log_oom();
assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0);
/* Maintain another set of the slots we intend to wipe */
for (size_t i = 0; i < n_explicit_slots; i++) {
if (explicit_slots[i] >= slot_max)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Slot index %i out of range.", explicit_slots[i]);
for (size_t i = 0; i < c->n_wipe_slots; i++) {
if (c->wipe_slots[i] >= slot_max)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Slot index %i out of range.", c->wipe_slots[i]);
if (set_put(wipe_slots, INT_TO_PTR(explicit_slots[i])) < 0)
if (set_put(wipe_slots, INT_TO_PTR(c->wipe_slots[i])) < 0)
return log_oom();
}
/* Now, handle the "all" and "empty passphrase" cases. */
switch (by_scope) {
switch (c->wipe_slots_scope) {
case WIPE_EXPLICIT:
break; /* Nothing to do here */
@@ -373,7 +378,7 @@ int wipe_slots(struct crypt_device *cd,
}
/* Then add all slots that match a token type */
r = find_slots_by_mask(cd, wipe_slots, keep_slots, by_mask);
r = find_slots_by_mask(cd, wipe_slots, keep_slots, c->wipe_slots_mask);
if (r < 0)
return r;
@@ -409,11 +414,22 @@ int wipe_slots(struct crypt_device *cd,
typesafe_qsort(ordered_tokens, n_ordered_tokens, cmp_int);
if (n_ordered_slots == 0 && n_ordered_tokens == 0) {
log_full(except_slot < 0 ? LOG_NOTICE : LOG_DEBUG,
log_full(c->wipe_except_slot < 0 ? LOG_NOTICE : LOG_DEBUG,
"No slots to remove selected.");
if (ret_wiped_slots)
*ret_wiped_slots = NULL;
if (ret_n_wiped_slots)
*ret_n_wiped_slots = 0;
return 0;
}
/* Remember which slots we actually managed to wipe, so we can report them back to the caller. */
if (ret_wiped_slots) {
wiped_slots = new(int, n_ordered_slots);
if (!wiped_slots)
return log_oom();
}
if (DEBUG_LOGGING) {
for (size_t i = 0; i < n_ordered_slots; i++)
log_debug("Going to wipe slot %i.", ordered_slots[i]);
@@ -433,8 +449,11 @@ int wipe_slots(struct crypt_device *cd,
log_warning_errno(r, "Failed to wipe slot %i, continuing: %m", ordered_slots[i - 1]);
if (ret == 0)
ret = r;
} else
} else {
log_info("Wiped slot %i.", ordered_slots[i - 1]);
if (wiped_slots)
wiped_slots[n_wiped_slots++] = ordered_slots[i - 1];
}
}
for (size_t i = n_ordered_tokens; i > 0; i--) {
@@ -446,5 +465,13 @@ int wipe_slots(struct crypt_device *cd,
}
}
if (ret_wiped_slots) {
/* We wiped from back to front, restore ascending order before handing the list out. */
typesafe_qsort(wiped_slots, n_wiped_slots, cmp_int);
*ret_wiped_slots = TAKE_PTR(wiped_slots);
}
if (ret_n_wiped_slots)
*ret_n_wiped_slots = n_wiped_slots;
return ret;
}
+6 -8
View File
@@ -1,13 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
#include "shared-forward.h"
typedef enum WipeScope WipeScope;
int wipe_slots(struct crypt_device *cd,
const int explicit_slots[],
size_t n_explicit_slots,
WipeScope by_scope,
unsigned by_mask,
int except_slot);
/* Wipes the slots selected by c->wipe_slots / c->n_wipe_slots / c->wipe_slots_scope /
* c->wipe_slots_mask, except for c->wipe_except_slot (set to -1 for none). If ret_wiped_slots/
* ret_n_wiped_slots are non-NULL they receive the (ascendingly sorted) list of slots that were actually
* wiped. */
int wipe_slots(const EnrollContext *c, struct crypt_device *cd, int **ret_wiped_slots, size_t *ret_n_wiped_slots);
+399 -159
View File
@@ -3,34 +3,42 @@
#include <sys/mman.h>
#include "sd-device.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "blockdev-list.h"
#include "blockdev-util.h"
#include "build.h"
#include "cleanup-util.h"
#include "cryptenroll.h"
#include "cryptenroll-fido2.h"
#include "cryptenroll-interactive.h"
#include "cryptenroll-list.h"
#include "cryptenroll-password.h"
#include "cryptenroll-pkcs11.h"
#include "cryptenroll-recovery.h"
#include "cryptenroll-tpm2.h"
#include "cryptenroll-varlink.h"
#include "cryptenroll-wipe.h"
#include "cryptsetup-util.h"
#include "extract-word.h"
#include "fileio.h"
#include "format-table.h"
#include "help-util.h"
#include "initrd-util.h"
#include "libfido2-util.h"
#include "log.h"
#include "main-func.h"
#include "memory-util.h"
#include "options.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "pkcs11-util.h"
#include "pretty-print.h"
#include "process-util.h"
#include "prompt-util.h"
#include "string-table.h"
#include "string-util.h"
#include "terminal-util.h"
#include "tpm2-pcr.h"
#include "tpm2-util.h"
@@ -60,6 +68,10 @@ static int *arg_wipe_slots = NULL;
static size_t arg_n_wipe_slots = 0;
static WipeScope arg_wipe_slots_scope = WIPE_EXPLICIT;
static unsigned arg_wipe_slots_mask = 0; /* Bitmask of (1U << EnrollType), for wiping all slots of specific types */
static bool arg_firstboot = false;
static bool arg_chrome = true;
static bool arg_mute_console = false;
static unsigned arg_prompt_suppress_mask = 0; /* Bitmask of (1U << EnrollType): if any such slot exists, --firstboot does nothing */
static Fido2EnrollFlags arg_fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP;
#if HAVE_LIBFIDO2
static int arg_fido2_cred_alg = COSE_ES256;
@@ -110,6 +122,91 @@ static const char *const luks2_token_type_table[_ENROLL_TYPE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType);
static int enroll_type_mask_from_string(const char *name) {
assert(name);
/* Maps an enroll type name (command line spelling) to its (1U << EnrollType) bitmask, or returns a
* negative errno if the name is not a known type. Callers merge the returned mask into their own
* accumulator. Shared by the various places that parse type lists into a bitmask. */
EnrollType t = enroll_type_from_string(name);
if (t < 0)
return -EINVAL;
return 1 << t;
}
void enroll_context_done(EnrollContext *c) {
if (!c)
return;
c->node = mfree(c->node);
c->unlock_keyfile = mfree(c->unlock_keyfile);
c->unlock_fido2_device = mfree(c->unlock_fido2_device);
c->unlock_tpm2_device = mfree(c->unlock_tpm2_device);
c->unlock_password = erase_and_free(c->unlock_password);
c->passphrase = erase_and_free(c->passphrase);
c->fido2_device = mfree(c->fido2_device);
c->fido2_salt_file = mfree(c->fido2_salt_file);
c->fido2_pin = erase_and_free(c->fido2_pin);
c->pkcs11_token_uri = mfree(c->pkcs11_token_uri);
c->tpm2_device = mfree(c->tpm2_device);
c->tpm2_device_key = mfree(c->tpm2_device_key);
c->tpm2_hash_pcr_values = mfree(c->tpm2_hash_pcr_values);
c->tpm2_public_key = mfree(c->tpm2_public_key);
c->tpm2_signature = mfree(c->tpm2_signature);
c->tpm2_pcrlock = mfree(c->tpm2_pcrlock);
c->wipe_slots = mfree(c->wipe_slots);
c->link = sd_varlink_unref(c->link);
}
static int resolve_default_node(const char *path, char **ret) {
int r;
/* Resolves the path of the underlying LUKS2 block device of the file system at the given mount
* point, i.e. the raw partition behind the dm-crypt mapping that file system sits on. Returns
* -ENXIO if the file system is not backed by a (single) LUKS2 device. Logs only at debug level, so
* the caller can try the next candidate path. */
assert(path);
assert(ret);
dev_t devno;
r = get_block_device(path, &devno);
if (r < 0)
return log_debug_errno(r, "Failed to determine block device backing %s: %m", path);
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENXIO),
"File system %s is not backed by a (single) whole block device.", path);
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
r = sd_device_new_from_devnum(&dev, 'b', devno);
if (r < 0)
return log_debug_errno(r, "Unable to access backing block device for %s: %m", path);
const char *dm_uuid;
r = sd_device_get_property_value(dev, "DM_UUID", &dm_uuid);
if (r == -ENOENT)
return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Backing block device of %s is not a DM device.", path);
if (r < 0)
return log_debug_errno(r, "Unable to query DM_UUID udev property of backing block device for %s: %m", path);
if (!startswith(dm_uuid, "CRYPT-LUKS2-"))
return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Block device backing %s is not a LUKS2 device.", path);
_cleanup_(sd_device_unrefp) sd_device *origin = NULL;
r = block_device_get_originating(dev, &origin, /* recursive= */ false);
if (r < 0)
return log_debug_errno(r, "Failed to get originating device of LUKS2 device backing %s: %m", path);
const char *dp;
r = sd_device_get_devname(origin, &dp);
if (r < 0)
return log_debug_errno(r, "Failed to get device path for LUKS2 device backing %s: %m", path);
return strdup_to(ret, dp);
}
static int determine_default_node(void) {
int r;
@@ -127,47 +224,30 @@ static int determine_default_node(void) {
* Or to say this differently: it makes sense to support well systems with /var/ being on /. It also
* makes sense to support well systems with them being separate, and /var/ being variable and
* persistent. But any other kind of system appears much less interesting to support, and in that
* case people should just specify the device name explicitly. */
* case people should just specify the device name explicitly.
*
* When invoked from the initrd the host's file systems are not mounted at their final location yet,
* but below /sysroot/, hence look there instead. */
dev_t devno;
r = get_block_device("/var", &devno);
if (r < 0)
return log_error_errno(r, "Failed to determine block device backing /var/: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"File system /var/ is on not backed by a (single) whole block device.");
const char *candidates[2];
size_t n_candidates = 0;
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
r = sd_device_new_from_devnum(&dev, 'b', devno);
if (r < 0)
return log_error_errno(r, "Unable to access backing block device for /var/: %m");
if (in_initrd()) {
candidates[n_candidates++] = "/sysroot/var";
candidates[n_candidates++] = "/sysroot";
} else
candidates[n_candidates++] = "/var";
const char *dm_uuid;
r = sd_device_get_property_value(dev, "DM_UUID", &dm_uuid);
if (r == -ENOENT)
return log_error_errno(r, "Backing block device of /var/ is not a DM device: %m");
if (r < 0)
return log_error_errno(r, "Unable to query DM_UUID udev property of backing block device for /var/: %m");
FOREACH_ARRAY(path, candidates, n_candidates) {
r = resolve_default_node(*path, &arg_node);
if (r >= 0) {
log_info("No device specified, defaulting to '%s' (backing %s).", arg_node, *path);
return 0;
}
}
if (!startswith(dm_uuid, "CRYPT-LUKS2-"))
return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Block device backing /var/ is not a LUKS2 device.");
_cleanup_(sd_device_unrefp) sd_device *origin = NULL;
r = block_device_get_originating(dev, &origin, /* recursive= */ false);
if (r < 0)
return log_error_errno(r, "Failed to get originating device of LUKS2 device backing /var/: %m");
const char *dp;
r = sd_device_get_devname(origin, &dp);
if (r < 0)
return log_error_errno(r, "Failed to get device path for LUKS2 device backing /var/: %m");
r = free_and_strdup_warn(&arg_node, dp);
if (r < 0)
return r;
log_info("No device specified, defaulting to '%s'.", arg_node);
return 0;
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"Failed to automatically determine a LUKS2 block device to operate on, please specify one explicitly.");
}
static int parse_wipe_slot(const char *arg) {
@@ -190,21 +270,15 @@ static int parse_wipe_slot(const char *arg) {
if (r < 0)
return log_error_errno(r, "Failed to parse slot list: %s", arg);
int mask;
if (streq(slot, "all"))
arg_wipe_slots_scope = WIPE_ALL;
else if (streq(slot, "empty")) {
if (arg_wipe_slots_scope != WIPE_ALL) /* if "all" was specified before, that wins */
arg_wipe_slots_scope = WIPE_EMPTY_PASSPHRASE;
} else if (streq(slot, "password"))
arg_wipe_slots_mask |= 1U << ENROLL_PASSWORD;
else if (streq(slot, "recovery"))
arg_wipe_slots_mask |= 1U << ENROLL_RECOVERY;
else if (streq(slot, "pkcs11"))
arg_wipe_slots_mask |= 1U << ENROLL_PKCS11;
else if (streq(slot, "fido2"))
arg_wipe_slots_mask |= 1U << ENROLL_FIDO2;
else if (streq(slot, "tpm2"))
arg_wipe_slots_mask |= 1U << ENROLL_TPM2;
} else if ((mask = enroll_type_mask_from_string(slot)) >= 0)
arg_wipe_slots_mask |= mask;
else {
unsigned n;
@@ -222,15 +296,34 @@ static int parse_wipe_slot(const char *arg) {
}
}
static int help(void) {
_cleanup_free_ char *link = NULL;
static int parse_prompt_suppress(const char *arg) {
int r;
pager_open(arg_pager_flags);
assert(arg);
r = terminal_urlify_man("systemd-cryptenroll", "1", &link);
if (r < 0)
return log_oom();
/* Parses a comma-separated list of the slot types the --firstboot wizard knows how to enroll. If a
* slot of any listed type already exists on the volume, the wizard does nothing. */
for (const char *p = arg;;) {
_cleanup_free_ char *type = NULL;
int mask;
r = extract_first_word(&p, &type, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r == 0)
return 0;
if (r < 0)
return log_error_errno(r, "Failed to parse type list: %s", arg);
mask = enroll_type_mask_from_string(type);
if (mask < 0)
return log_error_errno(mask, "Unknown slot type: %s", type);
arg_prompt_suppress_mask |= mask;
}
}
static int help(void) {
int r;
static const char* const groups[] = {
NULL,
@@ -252,21 +345,20 @@ static int help(void) {
(void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4], tables[5]);
printf("%s [OPTIONS...] [BLOCK-DEVICE]\n\n"
"%sEnroll a security token or authentication credential to a LUKS volume.%s\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal());
pager_open(arg_pager_flags);
help_cmdline("[OPTIONS...] [BLOCK-DEVICE]");
help_abstract("Enroll a security token or authentication credential to a LUKS volume.");
for (size_t i = 0; i < ELEMENTSOF(groups); i++) {
printf("\n%s%s:%s\n", ansi_underline(), groups[i] ?: "Options", ansi_normal());
help_section(groups[i] ?: "Options");
r = table_print_or_warn(tables[i]);
if (r < 0)
return r;
}
printf("\nSee the %s for details.\n", link);
help_man_page_reference("systemd-cryptenroll", "1");
return 0;
}
@@ -305,8 +397,42 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
OPTION_LONG("firstboot", NULL,
"Interactively enroll a credential (first-boot wizard)"):
arg_firstboot = true;
break;
OPTION_LONG("prompt-suppress", "TYPE1,TYPE2,…",
"Skip the --firstboot wizard if a slot of any listed type exists"):
r = parse_prompt_suppress(opts.arg);
if (r < 0)
return r;
break;
OPTION_LONG("chrome", "BOOL",
"In first-boot mode: if false don't show colour bar at top and bottom of terminal"):
r = parse_boolean_argument("--chrome=", opts.arg, &arg_chrome);
if (r < 0)
return r;
break;
OPTION_LONG("mute-console", "BOOL",
"In first-boot mode, tell kernel/PID 1 to not write to the console while running"):
r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console);
if (r < 0)
return r;
break;
OPTION_GROUP("Unlocking"): {}
OPTION_LONG("unlock-empty", NULL, "Use an empty password to unlock the volume"):
if (arg_unlock_type != UNLOCK_PASSWORD)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Multiple unlock methods specified at once, refusing.");
arg_unlock_type = UNLOCK_EMPTY;
break;
OPTION_LONG("unlock-key-file", "PATH",
"Use a file to unlock the volume"):
if (arg_unlock_type != UNLOCK_PASSWORD)
@@ -362,6 +488,14 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
OPTION_LONG("unlock-headless", NULL, "Try the 'headless' unlock mechanisms in turn"):
if (arg_unlock_type != UNLOCK_PASSWORD)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Multiple unlock methods specified at once, refusing.");
arg_unlock_type = UNLOCK_HEADLESS;
break;
OPTION_GROUP("Simple Enrollment"): {}
OPTION_LONG("password", NULL,
@@ -582,6 +716,17 @@ static int parse_argv(int argc, char *argv[]) {
if (option_parser_get_n_args(&opts) > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments, refusing.");
if (arg_firstboot) {
if (arg_enroll_type >= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--firstboot may not be combined with an explicit enrollment type, refusing.");
if (wipe_requested())
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--firstboot may not be combined with --wipe-slot=, refusing.");
} else if (arg_prompt_suppress_mask != 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--prompt-suppress= is only useful together with --firstboot, refusing.");
const char *arg = option_parser_get_arg(&opts, 0);
if (arg)
r = parse_path_argument(arg, false, &arg_node);
@@ -660,54 +805,18 @@ static int check_for_homed(struct crypt_device *cd) {
return 0;
}
static int load_volume_key_keyfile(
struct crypt_device *cd,
void *ret_vk,
size_t *ret_vks) {
_cleanup_(erase_and_freep) char *password = NULL;
size_t password_len;
int r;
assert_se(cd);
assert_se(ret_vk);
assert_se(ret_vks);
r = read_full_file_full(
AT_FDCWD,
arg_unlock_keyfile,
UINT64_MAX,
SIZE_MAX,
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
NULL,
&password,
&password_len);
if (r < 0)
return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile);
r = sym_crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk,
ret_vks,
password,
password_len);
if (r < 0)
return log_error_errno(r, "Unlocking via keyfile failed: %m");
return r;
}
static int prepare_luks(
int prepare_luks(
const EnrollContext *c,
struct crypt_device **ret_cd,
struct iovec *ret_volume_key) {
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
int r;
assert(c);
assert(ret_cd);
r = sym_crypt_init(&cd, arg_node);
r = sym_crypt_init(&cd, c->node);
if (r < 0)
return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
@@ -715,7 +824,7 @@ static int prepare_luks(
r = sym_crypt_load(cd, CRYPT_LUKS2, NULL);
if (r < 0)
return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", arg_node);
return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", c->node);
r = check_for_homed(cd);
if (r < 0)
@@ -731,35 +840,52 @@ static int prepare_luks(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
_cleanup_(iovec_done_erase) struct iovec vk = {};
vk.iov_base = malloc(r);
if (!vk.iov_base)
return log_oom();
vk.iov_len = (size_t) r;
switch (arg_unlock_type) {
switch (c->unlock_type) {
case UNLOCK_EMPTY:
r = load_volume_key_empty(c, cd, &vk);
break;
case UNLOCK_PASSWORD:
r = load_volume_key_password(cd, arg_node, vk.iov_base, &vk.iov_len);
r = load_volume_key_password(c, cd, &vk);
break;
case UNLOCK_KEYFILE:
r = load_volume_key_keyfile(cd, vk.iov_base, &vk.iov_len);
r = load_volume_key_keyfile(c, cd, &vk);
break;
case UNLOCK_FIDO2:
r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk.iov_base, &vk.iov_len);
r = load_volume_key_fido2(c, cd, &vk);
break;
case UNLOCK_TPM2:
r = load_volume_key_tpm2(cd, arg_node, arg_unlock_tpm2_device, vk.iov_base, &vk.iov_len);
r = load_volume_key_tpm2(c, cd, &vk);
break;
case UNLOCK_HEADLESS:
if (tpm2_is_mostly_supported()) {
log_info("TPM2 support available, trying unlocking via TPM2…");
r = load_volume_key_tpm2(c, cd, &vk);
if (r >= 0)
break;
log_info("TPM2 unlocking didn't work, trying unlocking via empty password…");
} else
log_info("TPM2 support not available, trying unlocking via empty password…");
r = load_volume_key_empty(c, cd, &vk);
break;
default:
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown LUKS unlock method");
}
if (r < 0)
return r;
@@ -769,13 +895,133 @@ static int prepare_luks(
return 0;
}
static int enroll_context_from_args(EnrollContext *c) {
assert(c);
/* Copies the parsed command line parameters from the static arg_* globals into a self-contained
* EnrollContext. The context owns its own copies of all strings/arrays, so it can be torn down
* independently of the arg_* destructors. */
*c = ENROLL_CONTEXT_NULL;
c->enroll_type = arg_enroll_type;
c->unlock_type = arg_unlock_type;
c->fido2_parameters_in_header = arg_fido2_parameters_in_header;
c->fido2_lock_with = arg_fido2_lock_with;
c->fido2_cred_alg = arg_fido2_cred_alg;
c->tpm2_seal_key_handle = arg_tpm2_seal_key_handle;
c->tpm2_pin = arg_tpm2_pin;
c->tpm2_load_public_key = arg_tpm2_load_public_key;
c->tpm2_public_key_pcr_mask = arg_tpm2_public_key_pcr_mask;
c->wipe_slots_scope = arg_wipe_slots_scope;
c->wipe_slots_mask = arg_wipe_slots_mask;
if (strdup_to(&c->node, arg_node) < 0 ||
strdup_to(&c->unlock_keyfile, arg_unlock_keyfile) < 0 ||
strdup_to(&c->unlock_fido2_device, arg_unlock_fido2_device) < 0 ||
strdup_to(&c->unlock_tpm2_device, arg_unlock_tpm2_device) < 0 ||
strdup_to(&c->fido2_device, arg_fido2_device) < 0 ||
strdup_to(&c->fido2_salt_file, arg_fido2_salt_file) < 0 ||
strdup_to(&c->pkcs11_token_uri, arg_pkcs11_token_uri) < 0 ||
strdup_to(&c->tpm2_device, arg_tpm2_device) < 0 ||
strdup_to(&c->tpm2_device_key, arg_tpm2_device_key) < 0 ||
strdup_to(&c->tpm2_public_key, arg_tpm2_public_key) < 0 ||
strdup_to(&c->tpm2_signature, arg_tpm2_signature) < 0 ||
strdup_to(&c->tpm2_pcrlock, arg_tpm2_pcrlock) < 0)
return log_oom();
if (arg_n_wipe_slots > 0) {
c->wipe_slots = newdup(int, arg_wipe_slots, arg_n_wipe_slots);
if (!c->wipe_slots)
return log_oom();
c->n_wipe_slots = arg_n_wipe_slots;
}
if (arg_tpm2_n_hash_pcr_values > 0) {
c->tpm2_hash_pcr_values = newdup(Tpm2PCRValue, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values);
if (!c->tpm2_hash_pcr_values)
return log_oom();
c->tpm2_n_hash_pcr_values = arg_tpm2_n_hash_pcr_values;
}
return 0;
}
int enroll_now(
const EnrollContext *c,
struct crypt_device *cd,
const struct iovec *volume_key,
char **ret_recovery_key) {
int slot, slot_to_wipe = -1, r;
assert(c);
assert(cd);
assert(iovec_is_set(volume_key));
switch (c->enroll_type) {
case ENROLL_PASSWORD:
return enroll_password(c, cd, volume_key);
case ENROLL_RECOVERY:
return enroll_recovery(c, cd, volume_key, ret_recovery_key);
case ENROLL_PKCS11:
return enroll_pkcs11(c, cd, volume_key);
case ENROLL_FIDO2:
return enroll_fido2(c, cd, volume_key);
case ENROLL_TPM2:
slot = enroll_tpm2(c, cd, volume_key, &slot_to_wipe);
if (slot < 0)
return slot;
if (slot_to_wipe >= 0) {
assert(slot != slot_to_wipe);
/* Updating the PIN on an existing enrollment: wipe just that one slot. This is an
* internal one-off wipe, unrelated to the user's wipe selection, so use a throwaway
* context referencing a single explicit slot. */
_cleanup_(enroll_context_done) EnrollContext wipe_ctx = ENROLL_CONTEXT_NULL;
wipe_ctx.wipe_slots = newdup(int, &slot_to_wipe, 1);
if (!wipe_ctx.wipe_slots)
return log_oom();
wipe_ctx.n_wipe_slots = 1;
r = wipe_slots(&wipe_ctx, cd, /* ret_wiped_slots= */ NULL, /* ret_n_wiped_slots= */ NULL);
if (r < 0)
return r;
}
return slot;
default:
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet.");
}
}
static int run(int argc, char *argv[]) {
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(iovec_done_erase) struct iovec vk = {};
int slot, slot_to_wipe, r;
_cleanup_(enroll_context_done) EnrollContext c = ENROLL_CONTEXT_NULL;
int slot, r;
log_setup();
/* A delicious drop of snake oil */
(void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT);
/* If invoked as a Varlink service, hand off to the Varlink server and don't process the command
* line any further. */
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
if (r < 0)
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
if (r > 0)
return cryptenroll_varlink_server();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
@@ -784,72 +1030,66 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
/* A delicious drop of snake oil */
(void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT);
if (arg_enroll_type < 0)
r = prepare_luks(&cd, /* ret_volume_key= */ NULL); /* No need to unlock device if we don't need the volume key because we don't need to enroll anything */
else
r = prepare_luks(&cd, &vk);
r = enroll_context_from_args(&c);
if (r < 0)
return r;
switch (arg_enroll_type) {
_cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL;
case ENROLL_PASSWORD:
slot = enroll_password(cd, &vk);
break;
/* Ensure the interactive chrome (drawn by cryptenroll_run_interactive() in --firstboot mode) is
* always torn down on exit; chrome_hide() is a no-op if no chrome was shown. */
DEFER_VOID_CALL(chrome_hide);
case ENROLL_RECOVERY:
slot = enroll_recovery(cd, &vk);
break;
if (arg_firstboot) {
assert(c.enroll_type < 0);
case ENROLL_PKCS11:
slot = enroll_pkcs11(cd, &vk, arg_pkcs11_token_uri);
break;
r = cryptenroll_run_interactive(
&c,
arg_prompt_suppress_mask,
arg_chrome,
arg_mute_console ? &mute_console_link : NULL);
if (r <= 0)
return r;
case ENROLL_FIDO2:
slot = enroll_fido2(cd, &vk, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg, arg_fido2_salt_file, arg_fido2_parameters_in_header);
break;
assert(c.enroll_type >= 0);
case ENROLL_TPM2:
slot = enroll_tpm2(cd, &vk, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_load_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin, arg_tpm2_pcrlock, &slot_to_wipe);
} else if (c.enroll_type < 0) {
/* If we are called without anything to enroll, we just need the LUKS device, not the volume key. */
r = prepare_luks(&c, &cd, /* ret_volume_key= */ NULL);
if (r < 0)
return r;
if (slot >= 0 && slot_to_wipe >= 0) {
assert(slot != slot_to_wipe);
/* Updating PIN on an existing enrollment */
r = wipe_slots(
cd,
&slot_to_wipe,
/* n_explicit_slots= */ 1,
WIPE_EXPLICIT,
/* by_mask= */ 0,
/* except_slot= */ -1);
if (r < 0)
return r;
}
break;
case _ENROLL_TYPE_INVALID:
/* List enrolled slots if we are called without anything to enroll or wipe */
if (!wipe_requested())
return list_enrolled(cd);
/* Only slot wiping selected */
return wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, -1);
default:
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet.");
return wipe_slots(&c, cd, /* ret_wiped_slots= */ NULL, /* ret_n_wiped_slots= */ NULL);
}
if (slot < 0)
return slot;
/* After we completed enrolling, remove user selected slots */
r = wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, slot);
r = prepare_luks(&c, &cd, &vk);
if (r < 0)
return r;
goto finish;
return 0;
slot = enroll_now(&c, cd, &vk, /* ret_recovery_key= */ NULL);
if (slot < 0) {
r = slot;
goto finish;
}
/* After we completed enrolling, remove user selected slots (keeping the one we just added) */
c.wipe_except_slot = slot;
r = wipe_slots(&c, cd, /* ret_wiped_slots= */ NULL, /* ret_n_wiped_slots= */ NULL);
if (r < 0)
goto finish;
r = 0;
finish:
if (arg_firstboot)
(void) any_key_to_proceed();
return r;
}
DEFINE_MAIN_FUNCTION(run);
+89
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "libfido2-util.h"
#include "shared-forward.h"
typedef enum EnrollType {
@@ -18,6 +19,8 @@ typedef enum UnlockType {
UNLOCK_KEYFILE,
UNLOCK_FIDO2,
UNLOCK_TPM2,
UNLOCK_EMPTY,
UNLOCK_HEADLESS,
_UNLOCK_TYPE_MAX,
_UNLOCK_TYPE_INVALID = -EINVAL,
} UnlockType;
@@ -32,3 +35,89 @@ typedef enum WipeScope {
DECLARE_STRING_TABLE_LOOKUP(enroll_type, EnrollType);
DECLARE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType);
/* A single bag of parameters consumed by the enrollment helpers. Populated either from the command line
* (see enroll_context_from_args() in cryptenroll.c), from a Varlink request (see cryptenroll-varlink.c),
* or by the interactive wizard (see cryptenroll-interactive.c). Owns all strings/arrays it points to. */
typedef struct EnrollContext {
EnrollType enroll_type;
UnlockType unlock_type;
/* Target device */
char *node;
/* Unlock side */
char *unlock_keyfile;
char *unlock_fido2_device;
char *unlock_tpm2_device;
char *unlock_password; /* used by Varlink; NULL on CLI path */
/* New password to enroll (mechanism == password). When NULL the helpers fall back to
* $NEWPASSWORD / askpw. */
char *passphrase;
size_t passphrase_size;
/* FIDO2 */
char *fido2_device;
char *fido2_salt_file;
bool fido2_parameters_in_header;
Fido2EnrollFlags fido2_lock_with;
int fido2_cred_alg;
char *fido2_pin; /* optional pre-supplied token PIN; NULL means prompt (if interactive) */
/* PKCS#11 */
char *pkcs11_token_uri;
/* TPM2 */
char *tpm2_device;
uint32_t tpm2_seal_key_handle;
char *tpm2_device_key;
Tpm2PCRValue *tpm2_hash_pcr_values;
size_t tpm2_n_hash_pcr_values;
bool tpm2_pin;
char *tpm2_public_key;
bool tpm2_load_public_key;
uint32_t tpm2_public_key_pcr_mask;
char *tpm2_signature;
char *tpm2_pcrlock;
/* Wipe selection */
int *wipe_slots;
size_t n_wipe_slots;
WipeScope wipe_slots_scope;
unsigned wipe_slots_mask;
int wipe_except_slot; /* slot to never wipe (e.g. the one we just enrolled); -1 for none */
/* If false, the enrollment helpers must never prompt the user (no askpw, no terminal I/O,
* no log printing of credential material). They use the fields in this context as the
* sole input, and fail with ENOPKG if a required piece of input is missing. CLI and
* interactive callers set this to true; the Varlink dispatcher sets it to false. */
bool interactive;
/* Varlink link the request came in on, if the caller asked for 'more'. NULL otherwise.
* Owned (sd_varlink_ref'd) by the context. */
sd_varlink *link;
} EnrollContext;
#define ENROLL_CONTEXT_NULL \
(EnrollContext) { \
.enroll_type = _ENROLL_TYPE_INVALID, \
.unlock_type = UNLOCK_PASSWORD, \
.fido2_parameters_in_header = true, \
.fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP, \
.tpm2_load_public_key = true, \
.wipe_slots_scope = WIPE_EXPLICIT, \
.wipe_except_slot = -1, \
.interactive = true, \
}
void enroll_context_done(EnrollContext *c);
/* Opens & loads the LUKS2 superblock of c->node, refuses homed-managed volumes, and (if ret_volume_key is
* non-NULL) unlocks it according to c->unlock_type, returning the volume key. */
int prepare_luks(const EnrollContext *c, struct crypt_device **ret_cd, struct iovec *ret_volume_key);
/* Dispatches to the enroll_*() helper matching c->enroll_type and returns the keyslot the new credential
* was added to. For ENROLL_RECOVERY the generated key is returned via ret_recovery_key (if non-NULL).
* Defined in cryptenroll.c, shared by the command line and Varlink code paths. */
int enroll_now(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key, char **ret_recovery_key);
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<!--
SPDX-License-Identifier: LGPL-2.1-or-later
This file is part of systemd.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
-->
<policyconfig>
<vendor>The systemd Project</vendor>
<vendor_url>https://systemd.io</vendor_url>
<action id="io.systemd.cryptenroll.enroll">
<description gettext-domain="systemd">Enroll an unlock mechanism into an encrypted volume</description>
<message gettext-domain="systemd">Authentication is required to enroll a new unlock mechanism into an encrypted volume.</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
+5
View File
@@ -7,11 +7,13 @@ endif
systemd_cryptenroll_sources = files(
'cryptenroll.c',
'cryptenroll-fido2.c',
'cryptenroll-interactive.c',
'cryptenroll-list.c',
'cryptenroll-password.c',
'cryptenroll-pkcs11.c',
'cryptenroll-recovery.c',
'cryptenroll-tpm2.c',
'cryptenroll-varlink.c',
'cryptenroll-wipe.c',
)
@@ -28,3 +30,6 @@ executables += [
],
},
]
install_data('io.systemd.cryptenroll.policy',
install_dir : polkitpolicydir)
+3
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "ask-password-api.h" /* IWYU pragma: keep */
#include "errno-util.h"
#include "fido2-util.h"
#include "hexdecoct.h"
@@ -174,6 +175,8 @@ int identity_add_fido2_parameters(
/* user_icon= */ NULL,
/* askpw_icon= */ "user-home",
/* askpw_credential= */ "home.token-pin",
/* askpw_flags= */ 0,
/* pin= */ NULL,
lock_with,
cred_alg,
&salt,
@@ -21,6 +21,7 @@
#include "varlink-io.systemd.AskPassword.h"
#include "varlink-io.systemd.BootControl.h"
#include "varlink-io.systemd.Credentials.h"
#include "varlink-io.systemd.CryptEnroll.h"
#include "varlink-io.systemd.FactoryReset.h"
#include "varlink-io.systemd.Hostname.h"
#include "varlink-io.systemd.Import.h"
@@ -199,6 +200,7 @@ TEST(parse_format) {
&vl_interface_io_systemd_AskPassword,
&vl_interface_io_systemd_BootControl,
&vl_interface_io_systemd_Credentials,
&vl_interface_io_systemd_CryptEnroll,
&vl_interface_io_systemd_FactoryReset,
&vl_interface_io_systemd_Hostname,
&vl_interface_io_systemd_Import,
+157 -35
View File
@@ -746,6 +746,8 @@ int fido2_generate_hmac_hash(
const char *user_icon,
const char *askpw_icon,
const char *askpw_credential,
AskPasswordFlags askpw_flags,
const char *pin,
Fido2EnrollFlags lock_with,
int cred_alg,
const struct iovec *salt,
@@ -920,48 +922,57 @@ int fido2_generate_hmac_hash(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Token asks for PIN but doesn't advertise 'clientPin' feature.");
AskPasswordFlags askpw_flags = ASK_PASSWORD_ACCEPT_CACHED;
AskPasswordFlags pin_askpw_flags = askpw_flags | ASK_PASSWORD_ACCEPT_CACHED;
for (;;) {
_cleanup_strv_free_erase_ char **pin = NULL;
_cleanup_free_ char *ask_pin_msg = NULL;
int pin_retries = -1;
_cleanup_strv_free_erase_ char **pins = NULL;
r = sym_fido_dev_get_retry_count(d, &pin_retries);
if (r != FIDO_OK) {
log_warning("Failed to obtain number of retries before lock-out for PIN "
"authentication, ignoring: %s", sym_fido_strerr(r));
pin_retries = -1;
}
if (pin_retries >= 0) {
ask_pin_msg = asprintf_safe(_("Please enter security token PIN "
"(remaining attempts before lock-out: %d):"),
pin_retries);
if (!ask_pin_msg)
if (pin) {
/* A PIN was supplied by the caller: use it directly, don't prompt. */
pins = strv_new(pin);
if (!pins)
return log_oom();
} else if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS))
return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option.");
else {
int pin_retries = -1;
r = sym_fido_dev_get_retry_count(d, &pin_retries);
if (r != FIDO_OK) {
log_warning("Failed to obtain number of retries before lock-out for PIN "
"authentication, ignoring: %s", sym_fido_strerr(r));
pin_retries = -1;
}
_cleanup_free_ char *ask_pin_msg = NULL;
if (pin_retries >= 0) {
ask_pin_msg = asprintf_safe(_("Please enter security token PIN "
"(remaining attempts before lock-out: %d):"),
pin_retries);
if (!ask_pin_msg)
return log_oom();
}
AskPasswordRequest req = {
.tty_fd = -EBADF,
.message = pin_retries >= 0
? ask_pin_msg
: _("Please enter security token PIN:"),
.icon = askpw_icon,
.keyring = "fido2-pin",
.credential = askpw_credential,
.until = USEC_INFINITY,
.hup_fd = -EBADF,
};
r = ask_password_auto(&req, pin_askpw_flags, &pins);
if (r < 0)
return log_error_errno(r, "Failed to acquire user PIN: %m");
pin_askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
}
AskPasswordRequest req = {
.tty_fd = -EBADF,
.message = pin_retries >= 0
? ask_pin_msg
: _("Please enter security token PIN:"),
.icon = askpw_icon,
.keyring = "fido2-pin",
.credential = askpw_credential,
.until = USEC_INFINITY,
.hup_fd = -EBADF,
};
r = ask_password_auto(&req, askpw_flags, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire user PIN: %m");
askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
r = FIDO_ERR_PIN_INVALID;
STRV_FOREACH(i, pin) {
STRV_FOREACH(i, pins) {
if (isempty(*i)) {
log_notice("PIN may not be empty.");
continue;
@@ -981,6 +992,11 @@ int fido2_generate_hmac_hash(
if (r != FIDO_ERR_PIN_INVALID)
break;
/* A caller-supplied PIN that's wrong won't get better by retrying: fail instead of
* looping forever (we'd never prompt). */
if (pin)
break;
log_notice("PIN incorrect, please try again.");
}
}
@@ -996,6 +1012,9 @@ int fido2_generate_hmac_hash(
if (r == FIDO_ERR_UNSUPPORTED_ALGORITHM)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Token doesn't support credential algorithm %s.", fido2_algorithm_to_string(cred_alg));
if (r == FIDO_ERR_PIN_INVALID)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PIN incorrect.");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to generate FIDO2 credential: %s", sym_fido_strerr(r));
@@ -1300,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;
+15
View File
@@ -117,6 +117,8 @@ int fido2_generate_hmac_hash(
const char *user_icon,
const char *askpw_icon,
const char *askpw_credential,
AskPasswordFlags askpw_flags,
const char *pin,
Fido2EnrollFlags lock_with,
int cred_alg,
const struct iovec *salt,
@@ -135,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);
+1
View File
@@ -224,6 +224,7 @@ shared_sources = files(
'varlink-io.systemd.AskPassword.c',
'varlink-io.systemd.BootControl.c',
'varlink-io.systemd.Credentials.c',
'varlink-io.systemd.CryptEnroll.c',
'varlink-io.systemd.FactoryReset.c',
'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Import.c',
+44 -2
View File
@@ -213,6 +213,47 @@ int prompt_loop(
}
}
static int boolean_is_valid(const char *name, void *userdata) {
return parse_boolean(name) >= 0;
}
int prompt_loop_yes_no(const char *question, bool def, bool *ret) {
int r;
assert(question);
assert(ret);
char **l = STRV_MAKE("yes", "no");
_cleanup_free_ char *reply = NULL;
r = prompt_loop(question,
GLYPH_WARNING_SIGN,
/* prefill= */ def ? "yes" : "no",
/* menu= */ l,
/* accepted= */ NULL,
/* ellipsize_percentage= */ 20,
/* n_columns= */ 2,
/* column_width= */ 40,
/* is_valid= */ boolean_is_valid,
/* refresh= */ NULL,
/* userdata= */ NULL,
PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT,
&reply);
if (r < 0)
return r;
if (r == 0) { /* Skipped (empty input): fall back to the default. */
*ret = def;
return 0;
}
r = parse_boolean(reply);
if (r < 0)
return log_error_errno(r, "Failed to parse reply: %s", reply);
*ret = r;
return 1;
}
/* Default: bright white on blue background */
#define ANSI_COLOR_CHROME "\x1B[0;44;1;37m"
@@ -371,11 +412,12 @@ static int vl_on_reply(sd_varlink *link, sd_json_variant *parameters, const char
int mute_console(sd_varlink **ret_link) {
int r;
assert(ret_link);
/* Talks to the MuteConsole service, and asks for output to the console to be muted, as long as the
* connection is retained */
if (!ret_link)
return 0;
_cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *link = NULL;
r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.MuteConsole");
if (r < 0)
+2
View File
@@ -26,6 +26,8 @@ int prompt_loop(const char *text,
PromptFlags flags,
char **ret);
int prompt_loop_yes_no(const char *question, bool def, bool *ret);
int chrome_show(const char *top, const char *bottom);
void chrome_hide(void);
+108
View File
@@ -0,0 +1,108 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "bus-polkit.h"
#include "varlink-io.systemd.CryptEnroll.h"
/* The credential types this interface knows about. The same enum is used for the 'mechanism' to enroll, for
* the slot types to wipe, and for the slot types reported by ListSlots. Note that only password, recovery key
* and fido2 may actually be *enrolled* via the Enroll() method; pkcs11 and tpm2 slots can be listed and wiped,
* but enrolling them requires the systemd-cryptenroll command line. Enroll() rejects them with an
* InvalidParameter error rather than via interface validation, so they are part of this enum. */
static SD_VARLINK_DEFINE_ENUM_TYPE(
EnrollMechanism,
SD_VARLINK_FIELD_COMMENT("A regular passphrase"),
SD_VARLINK_DEFINE_ENUM_VALUE(password),
SD_VARLINK_FIELD_COMMENT("A randomly generated recovery key"),
SD_VARLINK_DEFINE_ENUM_VALUE(recovery),
SD_VARLINK_FIELD_COMMENT("A PKCS#11 security token (not enrollable via this interface)"),
SD_VARLINK_DEFINE_ENUM_VALUE(pkcs11),
SD_VARLINK_FIELD_COMMENT("A FIDO2 security token"),
SD_VARLINK_DEFINE_ENUM_VALUE(fido2),
SD_VARLINK_FIELD_COMMENT("A TPM2 device (not enrollable via this interface)"),
SD_VARLINK_DEFINE_ENUM_VALUE(tpm2));
static SD_VARLINK_DEFINE_METHOD_FULL(
Enroll,
SD_VARLINK_SUPPORTS_MORE,
SD_VARLINK_FIELD_COMMENT("Path to the LUKS2 block device or image to operate on."),
SD_VARLINK_DEFINE_INPUT(node, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("Which kind of credential to enroll. Only 'password', 'recovery' and 'fido2' may be enrolled via this interface for now; 'pkcs11' and 'tpm2' are rejected with an InvalidParameter error, currently."),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(mechanism, EnrollMechanism, 0),
SD_VARLINK_FIELD_COMMENT("How to unlock the volume for the enrollment operation is inferred from which of the following fields are set: setting unlockPassword unlocks via that password, unlockKeyFile/unlockKeyFileDescriptor via a key file, unlockFido2Device via FIDO2, unlockTpm2Device via TPM2. Exactly one must be set."),
SD_VARLINK_DEFINE_INPUT(unlockPassword, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to a key file to unlock the volume with."),
SD_VARLINK_DEFINE_INPUT(unlockKeyFile, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Index into the file descriptors passed along with this call, identifying a key file to unlock the volume with."),
SD_VARLINK_DEFINE_INPUT(unlockKeyFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to a FIDO2 device to unlock the volume with. Leave the path empty to automatically discover a suitable device."),
SD_VARLINK_DEFINE_INPUT(unlockFido2Device, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to a TPM2 device to unlock the volume with. Leave the path empty to automatically discover a suitable device."),
SD_VARLINK_DEFINE_INPUT(unlockTpm2Device, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The passphrase to enroll, when mechanism is 'password'. Contains key material."),
SD_VARLINK_DEFINE_INPUT(password, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to the FIDO2 device to enroll, when mechanism is 'fido2'. Leave empty to automatically discover a suitable device."),
SD_VARLINK_DEFINE_INPUT(fido2Device, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The FIDO2 token PIN to use for enrollment. Contains key material."),
SD_VARLINK_DEFINE_INPUT(fido2Pin, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether unlocking the FIDO2 enrollment shall require a client PIN. Defaults to true."),
SD_VARLINK_DEFINE_INPUT(fido2WithClientPin, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether unlocking the FIDO2 enrollment shall require user presence (touch). Defaults to true."),
SD_VARLINK_DEFINE_INPUT(fido2WithUserPresence, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether unlocking the FIDO2 enrollment shall require user verification. Defaults to false."),
SD_VARLINK_DEFINE_INPUT(fido2WithUserVerification, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Explicit list of keyslot indexes to wipe after enrolling."),
SD_VARLINK_DEFINE_INPUT(wipeSlots, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Wipe all already-enrolled slots of these types after enrolling."),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(wipeTypes, EnrollMechanism, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("The keyslot the new credential was enrolled into. Set on the terminating reply only."),
SD_VARLINK_DEFINE_OUTPUT(slot, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The keyslots wiped as part of this call, if any."),
SD_VARLINK_DEFINE_OUTPUT(wipedSlots, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The generated recovery key, when mechanism is 'recoveryKey'. Contains key material."),
SD_VARLINK_DEFINE_OUTPUT(recoveryKey, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Progress indicator, only sent on intermediate replies when 'more' was set. Currently the only value is 'touch', signalling that the FIDO2 token is waiting for a touch. Unset on the terminating reply."),
SD_VARLINK_DEFINE_OUTPUT(state, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD_FULL(
ListSlots,
SD_VARLINK_REQUIRES_MORE,
SD_VARLINK_FIELD_COMMENT("Path to the LUKS2 block device or image to operate on."),
SD_VARLINK_DEFINE_INPUT(node, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("A currently enrolled keyslot index."),
SD_VARLINK_DEFINE_OUTPUT(slot, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The type of the keyslot."),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, EnrollMechanism, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_ERROR(VolumeUnderForeignManagement);
static SD_VARLINK_DEFINE_ERROR(PasswordRequired);
static SD_VARLINK_DEFINE_ERROR(PasswordIncorrect);
static SD_VARLINK_DEFINE_ERROR(FidoDeviceNotFound);
static SD_VARLINK_DEFINE_ERROR(FidoActionTimeout);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_CryptEnroll,
"io.systemd.CryptEnroll",
SD_VARLINK_INTERFACE_COMMENT("API for enrolling credentials into LUKS2 volumes via systemd-cryptenroll."),
SD_VARLINK_SYMBOL_COMMENT("The credential types this interface knows about, used both as the enrollment mechanism and as the slot type reported by ListSlots."),
&vl_type_EnrollMechanism,
SD_VARLINK_SYMBOL_COMMENT("Enroll a credential into a LUKS2 volume. When enrolling a FIDO2 token that requires user presence, the call must be made with the 'more' flag, so that the server can report when a touch is required via a 'touch' state notification."),
&vl_method_Enroll,
SD_VARLINK_SYMBOL_COMMENT("Enumerate the keyslots currently enrolled in a LUKS2 volume, one per reply. Must be called with the 'more' flag."),
&vl_method_ListSlots,
SD_VARLINK_SYMBOL_COMMENT("The volume is managed by another subsystem (e.g. systemd-homed) and may not be enrolled into directly."),
&vl_error_VolumeUnderForeignManagement,
SD_VARLINK_SYMBOL_COMMENT("A password is required to unlock the volume, but none was provided."),
&vl_error_PasswordRequired,
SD_VARLINK_SYMBOL_COMMENT("The provided password did not unlock the volume."),
&vl_error_PasswordIncorrect,
SD_VARLINK_SYMBOL_COMMENT("No matching FIDO2 device was found."),
&vl_error_FidoDeviceNotFound,
SD_VARLINK_SYMBOL_COMMENT("The FIDO2 token was not interacted with in time."),
&vl_error_FidoActionTimeout);
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-varlink-idl.h"
extern const sd_varlink_interface vl_interface_io_systemd_CryptEnroll;
+6 -21
View File
@@ -613,29 +613,14 @@ static int prompt_confirm(void) {
putchar('\n');
char **l = STRV_MAKE("yes", "no");
_cleanup_free_ char *reply = NULL;
r = prompt_loop(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" :
"Please type 'yes' to begin the installation",
GLYPH_WARNING_SIGN,
/* prefill= */ NULL,
/* menu= */ l,
/* accepted= */ l,
/* ellipsize_percentage= */ 20,
/* n_columns= */ 2,
/* column_width= */ 40,
/* is_valid= */ NULL,
/* refresh= */ NULL,
/* userdata= */ NULL,
PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT,
&reply);
bool yes;
r = prompt_loop_yes_no(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" :
"Please type 'yes' to begin the installation",
/* def= */ false,
&yes);
if (r < 0)
return r;
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
if (!streq(reply, "yes"))
if (!yes)
return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation not confirmed, cancelling.");
return 0;
+41 -1
View File
@@ -12,7 +12,7 @@ cryptenroll_wipe_and_check() {(
)}
at_exit() {
rm -f "${IMAGE:-}" /tmp/cryptenroll.out /tmp/password
rm -f "${IMAGE:-}" "${VL_IMAGE:-}" /tmp/cryptenroll.out /tmp/password
}
trap at_exit EXIT
@@ -101,3 +101,43 @@ PIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE"
(! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto "$IMAGE")
rm -f "$IMAGE"
# Exercise the io.systemd.CryptEnroll Varlink interface with calls equivalent to the command line ones above.
CRYPTENROLL="$(command -v systemd-cryptenroll)"
VL_ADDRESS="exec:$CRYPTENROLL"
VL_IMAGE="$(mktemp /tmp/systemd-cryptenroll-varlink-XXX.image)"
truncate -s 20M "$VL_IMAGE"
cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$VL_IMAGE" /tmp/password
# Enroll a recovery key, unlocking via key file (cf. systemd-cryptenroll --unlock-key-file= --recovery-key)
varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"recovery\",\"unlockKeyFile\":\"/tmp/password\"}" | grep recoveryKey >/dev/null
# Enroll a password, unlocking via key file (cf. NEWPASSWORD=… systemd-cryptenroll --unlock-key-file= --password)
varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"password\",\"unlockKeyFile\":\"/tmp/password\",\"password\":\"varlinkpassword\"}"
# Enroll a password, unlocking via the key file passed as a file descriptor instead of a path
varlinkctl --push-fd=3 call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"password\",\"unlockKeyFileDescriptor\":0,\"password\":\"fdpassword\"}" 3</tmp/password
# List enrolled slots (must be called with 'more'); we should see the password and recovery slots
varlinkctl call --more "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}" | grep '"type":"recovery"' >/dev/null
varlinkctl call --more "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}" | grep '"type":"password"' >/dev/null
# Enroll combined with a wipe of the recovery key slot (cf. systemd-cryptenroll --wipe-slot=recovery --password).
# The recovery key slot just got wiped, so it should be reported back in the (non-empty) wipedSlots output.
varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"password\",\"unlockKeyFile\":\"/tmp/password\",\"password\":\"wipepassword\",\"wipeTypes\":[\"recovery\"]}" | grep -E '"wipedSlots":\[[0-9]+(,[0-9]+)*\]' >/dev/null
(! varlinkctl call --more "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}" | grep '"type":"recovery"' >/dev/null)
# ListSlots without 'more' is refused
(! varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}")
# PKCS#11 and TPM2 cannot be enrolled via this interface, so the Enroll() handler rejects them as invalid parameters
(! varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"tpm2\",\"unlockKeyFile\":\"/tmp/password\"}")
(! varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"pkcs11\",\"unlockKeyFile\":\"/tmp/password\"}")
rm -f "$VL_IMAGE"
+14
View File
@@ -338,6 +338,20 @@ units = [
'symlinks' : ['sockets.target.wants/'],
},
{ 'file' : 'systemd-creds@.service' },
{
'file' : 'systemd-cryptenroll.socket',
'conditions' : ['HAVE_LIBCRYPTSETUP'],
'symlinks' : ['sockets.target.wants/'],
},
{
'file' : 'systemd-cryptenroll@.service',
'conditions' : ['HAVE_LIBCRYPTSETUP'],
},
{
'file' : 'systemd-cryptenroll-firstboot.service',
'conditions' : ['HAVE_LIBCRYPTSETUP', 'ENABLE_INITRD'],
'symlinks' : ['initrd.target.wants/'],
},
{ 'file' : 'systemd-exit.service' },
{ 'file' : 'systemd-factory-reset@.service.in' },
{
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
# Runs from the initrd: once systemd-repart has created/grown the partitions
# (including any encrypted ones) on first boot, but before we transition into
# the host, give the user a chance to interactively enroll a disk encryption
# credential.
[Unit]
Description=Interactive Disk Encryption Setup
Documentation=man:systemd-cryptenroll(1)
ConditionPathExists=/etc/initrd-release
# We want a first boot check, but in the initrd that's not a defined
# state. Hence let's look into the mounted image directly for the primary
# indicator for the first boot state
ConditionFileNotEmpty=!/sysroot/etc/machine-id
ConditionPathIsEncrypted=/sysroot/var
DefaultDependencies=no
After=systemd-repart.service systemd-vconsole-setup.service systemd-mute-console.socket initrd-fs.target
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target initrd.target
[Service]
Type=oneshot
RemainAfterExit=yes
# Ignore failures: there might not be an encrypted volume to enroll into, or the
# user might decline, neither of which should hold up the boot.
ExecStart=-systemd-cryptenroll --firstboot --prompt-suppress=password,recovery,fido2 --unlock-headless --mute-console=yes
StandardOutput=tty
StandardInput=tty
StandardError=tty
TTYReset=yes
+25
View File
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Disk Encryption Enrollment Service Socket
Documentation=man:systemd-cryptenroll(1)
DefaultDependencies=no
Before=sockets.target
[Socket]
ListenStream=/run/systemd/io.systemd.CryptEnroll
Symlinks=/run/varlink/registry/io.systemd.CryptEnroll
FileDescriptorName=varlink
SocketMode=0644
Accept=yes
MaxConnectionsPerSource=16
XAttrEntryPoint=user.varlink=entrypoint
XAttrListen=user.varlink=listen
XAttrAccept=user.varlink=server
+18
View File
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Disk Encryption Enrollment Service
Documentation=man:systemd-cryptenroll(1)
DefaultDependencies=no
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target
[Service]
ExecStart=-systemd-cryptenroll