cryptenroll: expose enrollment as an io.systemd.CryptEnroll Varlink service

Add a Varlink interface for systemd-cryptenroll, building on the EnrollContext
introduced previously. A single Enroll method covers password, recovery-key and
FIDO2 enrollment; PKCS#11 and TPM2 are not exposed for now (they are not
part of the EnrollMechanism allowlist, so the generic InvalidParameter error
applies). A ListSlots method enumerates the currently enrolled keyslots.

The dispatcher populates the same EnrollContext the command line uses and then
runs the shared enroll_now()/prepare_luks()/wipe_slots() paths, so both
front-ends behave identically. FIDO2 enrollment that requires user presence
reports an imminent touch via a non-terminating "state":"touch" reply when the
caller passes 'more'. Credential material (password, FIDO2 PIN, recovery key)
is handled as sensitive, and key files may be passed either by path or as an
fd index.

The server is allocated root-only plus caller's-own-UID, with the listening
socket created in 0644 mode.

Replaces: #31096
This commit is contained in:
Lennart Poettering
2026-06-27 17:28:39 +02:00
parent fd3fb3a85c
commit 0df41e9329
18 changed files with 895 additions and 89 deletions
+1
View File
@@ -222,6 +222,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',
+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;