#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"
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(
c->fido2_device,
/* rp_id= */ "io.systemd.cryptsetup",
#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;
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,
};
}
* 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;
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)) {
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();
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);
}
/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
}
int wipe_slots(const EnrollContext *c,
- struct crypt_device *cd) {
+ 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 (c->n_wipe_slots == 0 && c->wipe_slots_mask == 0 && c->wipe_slots_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:
*
if (n_ordered_slots == 0 && n_ordered_tokens == 0) {
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]);
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--) {
}
}
+ 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;
}
#include "shared-forward.h"
/* 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). */
-int wipe_slots(const EnrollContext *c, struct crypt_device *cd);
+ * 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);
#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"
return r;
}
-static int prepare_luks(
+int prepare_luks(
const EnrollContext *c,
struct crypt_device **ret_cd,
struct iovec *ret_volume_key) {
return 0;
}
-static int run(int argc, char *argv[]) {
- _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
- _cleanup_(iovec_done_erase) struct iovec vk = {};
- _cleanup_(enroll_context_done) EnrollContext c = ENROLL_CONTEXT_NULL;
- int slot, slot_to_wipe, r;
-
- log_setup();
-
- r = parse_argv(argc, argv);
- if (r <= 0)
- return r;
-
- r = DLOPEN_CRYPTSETUP(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED);
- if (r < 0)
- return r;
-
- /* A delicious drop of snake oil */
- (void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT);
+int enroll_now(
+ const EnrollContext *c,
+ struct crypt_device *cd,
+ const struct iovec *volume_key,
+ char **ret_recovery_key) {
- r = enroll_context_from_args(&c);
- if (r < 0)
- return r;
+ int slot, slot_to_wipe = -1, r;
- if (c.enroll_type < 0)
- r = prepare_luks(&c, &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(&c, &cd, &vk);
- if (r < 0)
- return r;
+ assert(c);
+ assert(cd);
+ assert(iovec_is_set(volume_key));
- switch (c.enroll_type) {
+ switch (c->enroll_type) {
case ENROLL_PASSWORD:
- slot = enroll_password(&c, cd, &vk);
- break;
+ return enroll_password(c, cd, volume_key);
case ENROLL_RECOVERY:
- slot = enroll_recovery(&c, cd, &vk, /* ret_recovery_key= */ NULL);
- break;
+ return enroll_recovery(c, cd, volume_key, ret_recovery_key);
case ENROLL_PKCS11:
- slot = enroll_pkcs11(&c, cd, &vk);
- break;
+ return enroll_pkcs11(c, cd, volume_key);
case ENROLL_FIDO2:
- slot = enroll_fido2(&c, cd, &vk);
- break;
+ return enroll_fido2(c, cd, volume_key);
case ENROLL_TPM2:
- slot = enroll_tpm2(&c, cd, &vk, &slot_to_wipe);
+ slot = enroll_tpm2(c, cd, volume_key, &slot_to_wipe);
+ if (slot < 0)
+ return slot;
- if (slot >= 0 && slot_to_wipe >= 0) {
+ if (slot_to_wipe >= 0) {
assert(slot != slot_to_wipe);
- /* Updating PIN on an existing enrollment: wipe just that one slot. This is an
- * internal one-off wipe that is unrelated to the user's wipe selection, so use a
- * throwaway context referencing a single explicit slot. */
- EnrollContext wipe_ctx = ENROLL_CONTEXT_NULL;
- wipe_ctx.wipe_slots = &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);
+ r = wipe_slots(&wipe_ctx, cd, /* ret_wiped_slots= */ NULL, /* ret_n_wiped_slots= */ NULL);
if (r < 0)
return r;
}
- break;
- case _ENROLL_TYPE_INVALID:
+
+ 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 = {};
+ _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;
+
+ r = DLOPEN_CRYPTSETUP(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED);
+ if (r < 0)
+ return r;
+
+ r = enroll_context_from_args(&c);
+ if (r < 0)
+ return r;
+
+ /* If we are called without anything to enroll, we just need the LUKS device, not the volume key. */
+ if (c.enroll_type < 0) {
+ r = prepare_luks(&c, &cd, /* ret_volume_key= */ NULL);
+ if (r < 0)
+ return r;
+
/* 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(&c, cd);
-
- 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);
}
+
+ r = prepare_luks(&c, &cd, &vk);
+ if (r < 0)
+ return r;
+
+ slot = enroll_now(&c, cd, &vk, /* ret_recovery_key= */ NULL);
if (slot < 0)
return slot;
/* After we completed enrolling, remove user selected slots (keeping the one we just added) */
c.wipe_except_slot = slot;
- r = wipe_slots(&c, cd);
+ r = wipe_slots(&c, cd, /* ret_wiped_slots= */ NULL, /* ret_n_wiped_slots= */ NULL);
if (r < 0)
return r;
}
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);
--- /dev/null
+<?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>
'cryptenroll-pkcs11.c',
'cryptenroll-recovery.c',
'cryptenroll-tpm2.c',
+ 'cryptenroll-varlink.c',
'cryptenroll-wipe.c',
)
],
},
]
+
+install_data('io.systemd.cryptenroll.policy',
+ install_dir : polkitpolicydir)
#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"
&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,
'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',
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
'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-exit.service' },
{ 'file' : 'systemd-factory-reset@.service.in' },
{
--- /dev/null
+# 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
--- /dev/null
+# 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