summaryrefslogtreecommitdiffstats
path: root/src/cryptsetup
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:35:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:35:18 +0000
commitb750101eb236130cf056c675997decbac904cc49 (patch)
treea5df1a06754bdd014cb975c051c83b01c9a97532 /src/cryptsetup
parentInitial commit. (diff)
downloadsystemd-b750101eb236130cf056c675997decbac904cc49.tar.xz
systemd-b750101eb236130cf056c675997decbac904cc49.zip
Adding upstream version 252.22.upstream/252.22
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/cryptsetup/cryptsetup-fido2.c277
-rw-r--r--src/cryptsetup/cryptsetup-fido2.h88
-rw-r--r--src/cryptsetup/cryptsetup-generator.c929
-rw-r--r--src/cryptsetup/cryptsetup-keyfile.c62
-rw-r--r--src/cryptsetup/cryptsetup-keyfile.h12
-rw-r--r--src/cryptsetup/cryptsetup-pkcs11.c173
-rw-r--r--src/cryptsetup/cryptsetup-pkcs11.h64
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c218
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c144
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c335
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c84
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h40
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h19
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym19
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-fido2.c157
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-fido2.h24
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c271
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h21
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c71
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h25
-rw-r--r--src/cryptsetup/cryptsetup-tokens/meson.build28
-rw-r--r--src/cryptsetup/cryptsetup-tpm2.c256
-rw-r--r--src/cryptsetup/cryptsetup-tpm2.h108
-rw-r--r--src/cryptsetup/cryptsetup.c2134
-rw-r--r--src/cryptsetup/meson.build21
25 files changed, 5580 insertions, 0 deletions
diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c
new file mode 100644
index 0000000..a3bdedb
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-fido2.c
@@ -0,0 +1,277 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "ask-password-api.h"
+#include "cryptsetup-fido2.h"
+#include "env-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "libfido2-util.h"
+#include "parse-util.h"
+#include "random-util.h"
+#include "strv.h"
+
+int acquire_fido2_key(
+ const char *volume_name,
+ const char *friendly_name,
+ const char *device,
+ const char *rp_id,
+ const void *cid,
+ size_t cid_size,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data,
+ size_t key_data_size,
+ usec_t until,
+ bool headless,
+ Fido2EnrollFlags required,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ AskPasswordFlags ask_password_flags) {
+
+ _cleanup_(erase_and_freep) char *envpw = NULL;
+ _cleanup_strv_free_erase_ char **pins = NULL;
+ _cleanup_free_ void *loaded_salt = NULL;
+ bool device_exists = false;
+ const char *salt;
+ size_t salt_size;
+ int r;
+
+ if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && headless)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOPKG),
+ "Local verification is required to unlock this volume, but the 'headless' parameter was set.");
+
+ ask_password_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED;
+
+ assert(cid);
+ assert(key_file || key_data);
+
+ if (key_data) {
+ salt = key_data;
+ salt_size = key_data_size;
+ } else {
+ _cleanup_free_ char *bindname = NULL;
+
+ /* If we read the salt via AF_UNIX, make this client recognizable */
+ if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-fido2/%s", random_u64(), volume_name) < 0)
+ return log_oom();
+
+ r = read_full_file_full(
+ AT_FDCWD, key_file,
+ key_file_offset == 0 ? UINT64_MAX : key_file_offset,
+ key_file_size == 0 ? SIZE_MAX : key_file_size,
+ READ_FULL_FILE_CONNECT_SOCKET,
+ bindname,
+ (char**) &loaded_salt, &salt_size);
+ if (r < 0)
+ return r;
+
+ salt = loaded_salt;
+ }
+
+ r = getenv_steal_erase("PIN", &envpw);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire password from environment: %m");
+ if (r > 0) {
+ pins = strv_new(envpw);
+ if (!pins)
+ return log_oom();
+ }
+
+ for (;;) {
+ if (!device_exists) {
+ /* Before we inquire for the PIN we'll need, if we never talked to the device, check
+ * if the device actually is plugged in. Otherwise we'll ask for the PIN already when
+ * the device is not plugged in, which is confusing. */
+
+ r = fido2_have_device(device);
+ if (r < 0)
+ return r;
+ if (r == 0) /* no device found, return EAGAIN so that caller will wait/watch udev */
+ return -EAGAIN;
+
+ device_exists = true; /* now we know for sure, a device exists, no need to ask again */
+ }
+
+ /* Always make an attempt before asking for PIN.
+ * fido2_use_hmac_hash() will perform a pre-flight check for whether the credential for
+ * can be found on one of the connected devices. This way, we can avoid prompting the user
+ * for a PIN when we are sure that no device can be used. */
+ r = fido2_use_hmac_hash(
+ device,
+ rp_id ?: "io.systemd.cryptsetup",
+ salt, salt_size,
+ cid, cid_size,
+ pins,
+ required,
+ ret_decrypted_key,
+ ret_decrypted_key_size);
+ if (!IN_SET(r,
+ -ENOANO, /* needs pin */
+ -ENOLCK)) /* pin incorrect */
+ return r;
+
+ device_exists = true; /* that a PIN is needed/wasn't correct means that we managed to
+ * talk to a device */
+
+ if (headless)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable.");
+
+ pins = strv_free_erase(pins);
+ r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, ask_password_flags, &pins);
+ if (r < 0)
+ return log_error_errno(r, "Failed to ask for user password: %m");
+
+ ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
+ }
+}
+
+int acquire_fido2_key_auto(
+ struct crypt_device *cd,
+ const char *name,
+ const char *friendly_name,
+ const char *fido2_device,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ usec_t until,
+ bool headless,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ AskPasswordFlags ask_password_flags) {
+
+ _cleanup_free_ void *cid = NULL;
+ size_t cid_size = 0;
+ int r, ret = -ENOENT;
+ Fido2EnrollFlags required = 0;
+
+ assert(cd);
+ assert(name);
+ assert(ret_decrypted_key);
+ assert(ret_decrypted_key_size);
+
+ /* Loads FIDO2 metadata from LUKS2 JSON token headers. */
+
+ for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ JsonVariant *w;
+ _cleanup_free_ void *salt = NULL;
+ _cleanup_free_ char *rp = NULL;
+ size_t salt_size = 0;
+ int ks;
+
+ r = cryptsetup_get_token_as_json(cd, token, "systemd-fido2", &v);
+ if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
+ continue;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read JSON token data off disk: %m");
+
+ ks = cryptsetup_get_keyslot_from_token(v);
+ if (ks < 0) {
+ /* Handle parsing errors of the keyslots field gracefully, since it's not 'owned' by
+ * us, but by the LUKS2 spec */
+ log_warning_errno(ks, "Failed to extract keyslot index from FIDO2 JSON data token %i, skipping: %m", token);
+ continue;
+ }
+
+ w = json_variant_by_key(v, "fido2-credential");
+ if (!w || !json_variant_is_string(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "FIDO2 token data lacks 'fido2-credential' field.");
+
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size);
+ if (r < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid base64 data in 'fido2-credential' field.");
+
+ w = json_variant_by_key(v, "fido2-salt");
+ if (!w || !json_variant_is_string(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "FIDO2 token data lacks 'fido2-salt' field.");
+
+ assert(!salt);
+ assert(salt_size == 0);
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decode base64 encoded salt.");
+
+ w = json_variant_by_key(v, "fido2-rp");
+ if (w) {
+ /* The "rp" field is optional. */
+
+ if (!json_variant_is_string(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "FIDO2 token data's 'fido2-rp' field is not a string.");
+
+ assert(!rp);
+ rp = strdup(json_variant_string(w));
+ if (!rp)
+ return log_oom();
+ }
+
+ w = json_variant_by_key(v, "fido2-clientPin-required");
+ if (w) {
+ /* The "fido2-clientPin-required" field is optional. */
+
+ if (!json_variant_is_boolean(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean.");
+
+ SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w));
+ } else
+ required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */
+
+ w = json_variant_by_key(v, "fido2-up-required");
+ if (w) {
+ /* The "fido2-up-required" field is optional. */
+
+ if (!json_variant_is_boolean(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "FIDO2 token data's 'fido2-up-required' field is not a boolean.");
+
+ SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w));
+ } else
+ required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */
+
+ w = json_variant_by_key(v, "fido2-uv-required");
+ if (w) {
+ /* The "fido2-uv-required" field is optional. */
+
+ if (!json_variant_is_boolean(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "FIDO2 token data's 'fido2-uv-required' field is not a boolean.");
+
+ SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w));
+ } else
+ required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */
+
+ ret = acquire_fido2_key(
+ name,
+ friendly_name,
+ fido2_device,
+ rp,
+ cid, cid_size,
+ key_file, key_file_size, key_file_offset,
+ salt, salt_size,
+ until,
+ headless,
+ required,
+ ret_decrypted_key, ret_decrypted_key_size,
+ ask_password_flags);
+ if (ret == 0)
+ break;
+ }
+
+ if (!cid)
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
+ "No valid FIDO2 token data found.");
+
+ if (ret == -EAGAIN) /* fido2 device does not exist, or UV is blocked; caller will prompt for retry */
+ return log_debug_errno(ret, "FIDO2 token does not exist, or UV is blocked.");
+ if (ret < 0)
+ return log_error_errno(ret, "Failed to unlock LUKS volume with FIDO2 token: %m");
+
+ log_info("Unlocked volume via automatically discovered security FIDO2 token.");
+ return ret;
+}
diff --git a/src/cryptsetup/cryptsetup-fido2.h b/src/cryptsetup/cryptsetup-fido2.h
new file mode 100644
index 0000000..371bf21
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-fido2.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#include "cryptsetup-util.h"
+#include "libfido2-util.h"
+#include "log.h"
+#include "time-util.h"
+
+#if HAVE_LIBFIDO2
+
+int acquire_fido2_key(
+ const char *volume_name,
+ const char *friendly_name,
+ const char *device,
+ const char *rp_id,
+ const void *cid,
+ size_t cid_size,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data,
+ size_t key_data_size,
+ usec_t until,
+ bool headless,
+ Fido2EnrollFlags required,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ AskPasswordFlags ask_password_flags);
+
+int acquire_fido2_key_auto(
+ struct crypt_device *cd,
+ const char *name,
+ const char *friendly_name,
+ const char *fido2_device,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ usec_t until,
+ bool headless,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ AskPasswordFlags ask_password_flags);
+
+#else
+
+static inline int acquire_fido2_key(
+ const char *volume_name,
+ const char *friendly_name,
+ const char *device,
+ const char *rp_id,
+ const void *cid,
+ size_t cid_size,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data,
+ size_t key_data_size,
+ usec_t until,
+ bool headless,
+ Fido2EnrollFlags required,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ AskPasswordFlags ask_password_flags) {
+
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "FIDO2 token support not available.");
+}
+
+static inline int acquire_fido2_key_auto(
+ struct crypt_device *cd,
+ const char *name,
+ const char *friendly_name,
+ const char *fido2_device,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ usec_t until,
+ bool headless,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ AskPasswordFlags ask_password_flags) {
+
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "FIDO2 token support not available.");
+}
+#endif
diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c
new file mode 100644
index 0000000..07903f1
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-generator.c
@@ -0,0 +1,929 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "alloc-util.h"
+#include "dropin.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fstab-util.h"
+#include "generator.h"
+#include "hashmap.h"
+#include "id128-util.h"
+#include "log.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "proc-cmdline.h"
+#include "specifier.h"
+#include "string-util.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "util.h"
+
+typedef struct crypto_device {
+ char *uuid;
+ char *keyfile;
+ char *keydev;
+ char *headerdev;
+ char *datadev;
+ char *name;
+ char *options;
+ bool create;
+} crypto_device;
+
+static const char *arg_dest = NULL;
+static bool arg_enabled = true;
+static bool arg_read_crypttab = true;
+static const char *arg_crypttab = NULL;
+static const char *arg_runtime_directory = NULL;
+static bool arg_allow_list = false;
+static Hashmap *arg_disks = NULL;
+static char *arg_default_options = NULL;
+static char *arg_default_keyfile = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_disks, hashmap_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_default_options, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_default_keyfile, freep);
+
+static int split_locationspec(const char *locationspec, char **ret_file, char **ret_device) {
+ _cleanup_free_ char *file = NULL, *device = NULL;
+ const char *c;
+
+ assert(ret_file);
+ assert(ret_device);
+
+ if (!locationspec) {
+ *ret_file = *ret_device = NULL;
+ return 0;
+ }
+
+ c = strrchr(locationspec, ':');
+ if (c) {
+ /* The device part has to be either an absolute path to device node (/dev/something,
+ * /dev/foo/something, or even possibly /dev/foo/something:part), or a fstab device
+ * specification starting with LABEL= or similar. The file part has the same syntax.
+ *
+ * Let's try to guess if the second part looks like a device specification, or just part of a
+ * filename with a colon. fstab_node_to_udev_node() will convert the fstab device syntax to
+ * an absolute path. If we didn't get an absolute path, assume that it is just part of the
+ * first file argument. */
+
+ device = fstab_node_to_udev_node(c + 1);
+ if (!device)
+ return log_oom();
+
+ if (path_is_absolute(device))
+ file = strndup(locationspec, c-locationspec);
+ else {
+ log_debug("Location specification argument contains a colon, but \"%s\" doesn't look like a device specification.\n"
+ "Assuming that \"%s\" is a single device specification.",
+ c + 1, locationspec);
+ device = mfree(device);
+ c = NULL;
+ }
+ }
+
+ if (!c)
+ /* No device specified */
+ file = strdup(locationspec);
+
+ if (!file)
+ return log_oom();
+
+ *ret_file = TAKE_PTR(file);
+ *ret_device = TAKE_PTR(device);
+
+ return 0;
+}
+
+static int generate_device_mount(
+ const char *name,
+ const char *device,
+ const char *type_prefix, /* "keydev" or "headerdev" */
+ const char *device_timeout,
+ bool canfail,
+ bool readonly,
+ char **unit,
+ char **mount) {
+
+ _cleanup_free_ char *u = NULL, *where = NULL, *name_escaped = NULL, *device_unit = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+ usec_t timeout_us;
+
+ assert(name);
+ assert(device);
+ assert(unit);
+ assert(mount);
+
+ r = mkdir_parents(arg_runtime_directory, 0755);
+ if (r < 0)
+ return r;
+
+ r = mkdir(arg_runtime_directory, 0700);
+ if (r < 0 && errno != EEXIST)
+ return -errno;
+
+ name_escaped = cescape(name);
+ if (!name_escaped)
+ return -ENOMEM;
+
+ where = strjoin(arg_runtime_directory, "/", type_prefix, "-", name_escaped);
+ if (!where)
+ return -ENOMEM;
+
+ r = mkdir(where, 0700);
+ if (r < 0 && errno != EEXIST)
+ return -errno;
+
+ r = unit_name_from_path(where, ".mount", &u);
+ if (r < 0)
+ return r;
+
+ r = generator_open_unit_file(arg_dest, NULL, u, &f);
+ if (r < 0)
+ return r;
+
+ fprintf(f,
+ "[Unit]\n"
+ "DefaultDependencies=no\n\n"
+ "[Mount]\n"
+ "What=%s\n"
+ "Where=%s\n"
+ "Options=%s%s\n", device, where, readonly ? "ro" : "rw", canfail ? ",nofail" : "");
+
+ if (device_timeout) {
+ r = parse_sec_fix_0(device_timeout, &timeout_us);
+ if (r >= 0) {
+ r = unit_name_from_path(device, ".device", &device_unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate unit name: %m");
+
+ r = write_drop_in_format(arg_dest, device_unit, 90, "device-timeout",
+ "# Automatically generated by systemd-cryptsetup-generator \n\n"
+ "[Unit]\nJobRunningTimeoutSec=%s", device_timeout);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write device drop-in: %m");
+
+ } else
+ log_warning_errno(r, "Failed to parse %s, ignoring: %m", device_timeout);
+
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return r;
+
+ *unit = TAKE_PTR(u);
+ *mount = TAKE_PTR(where);
+
+ return 0;
+}
+
+static int generate_device_umount(const char *name,
+ const char *device_mount,
+ const char *type_prefix, /* "keydev" or "headerdev" */
+ char **ret_umount_unit) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *u = NULL, *name_escaped = NULL, *mount = NULL;
+ int r;
+
+ assert(name);
+ assert(ret_umount_unit);
+
+ name_escaped = cescape(name);
+ if (!name_escaped)
+ return -ENOMEM;
+
+ u = strjoin(type_prefix, "-", name_escaped, "-umount.service");
+ if (!u)
+ return -ENOMEM;
+
+ r = unit_name_from_path(device_mount, ".mount", &mount);
+ if (r < 0)
+ return r;
+
+ r = generator_open_unit_file(arg_dest, NULL, u, &f);
+ if (r < 0)
+ return r;
+
+ fprintf(f,
+ "[Unit]\n"
+ "DefaultDependencies=no\n"
+ "After=%s\n\n"
+ "[Service]\n"
+ "ExecStart=-" UMOUNT_PATH " %s\n\n", mount, device_mount);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return r;
+
+ *ret_umount_unit = TAKE_PTR(u);
+ return 0;
+}
+
+static int print_dependencies(FILE *f, const char* device_path, const char* timeout_value, bool canfail) {
+ int r;
+
+ assert(!canfail || timeout_value);
+
+ if (STR_IN_SET(device_path, "-", "none"))
+ /* None, nothing to do */
+ return 0;
+
+ if (PATH_IN_SET(device_path,
+ "/dev/urandom",
+ "/dev/random",
+ "/dev/hw_random",
+ "/dev/hwrng")) {
+ /* RNG device, add random dep */
+ fputs("After=systemd-random-seed.service\n", f);
+ return 0;
+ }
+
+ _cleanup_free_ char *udev_node = fstab_node_to_udev_node(device_path);
+ if (!udev_node)
+ return log_oom();
+
+ if (path_equal(udev_node, "/dev/null"))
+ return 0;
+
+ if (path_startswith(udev_node, "/dev/")) {
+ /* We are dealing with a block device, add dependency for corresponding unit */
+ _cleanup_free_ char *unit = NULL;
+
+ r = unit_name_from_path(udev_node, ".device", &unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate unit name: %m");
+
+ fprintf(f, "After=%1$s\n", unit);
+ if (canfail) {
+ fprintf(f, "Wants=%1$s\n", unit);
+ r = write_drop_in_format(arg_dest, unit, 90, "device-timeout",
+ "# Automatically generated by systemd-cryptsetup-generator \n\n"
+ "[Unit]\nJobRunningTimeoutSec=%s", timeout_value);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write device drop-in: %m");
+ } else
+ fprintf(f, "Requires=%1$s\n", unit);
+ } else {
+ /* Regular file, add mount dependency */
+ _cleanup_free_ char *escaped_path = specifier_escape(device_path);
+ if (!escaped_path)
+ return log_oom();
+
+ fprintf(f, "RequiresMountsFor=%s\n", escaped_path);
+ }
+
+ return 0;
+}
+
+static int create_disk(
+ const char *name,
+ const char *device,
+ const char *key_file,
+ const char *keydev,
+ const char *headerdev,
+ const char *options,
+ const char *source) {
+
+ _cleanup_free_ char *n = NULL, *d = NULL, *u = NULL, *e = NULL,
+ *keydev_mount = NULL, *keyfile_timeout_value = NULL,
+ *filtered = NULL, *u_escaped = NULL, *name_escaped = NULL, *header_path = NULL, *key_file_buffer = NULL,
+ *tmp_fstype = NULL, *filtered_header = NULL, *headerdev_mount = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *dmname;
+ bool noauto, nofail, swap, netdev, attach_in_initrd;
+ int r, detached_header, keyfile_can_timeout, tmp;
+
+ assert(name);
+ assert(device);
+
+ noauto = fstab_test_yes_no_option(options, "noauto\0" "auto\0");
+ nofail = fstab_test_yes_no_option(options, "nofail\0" "fail\0");
+ swap = fstab_test_option(options, "swap\0");
+ netdev = fstab_test_option(options, "_netdev\0");
+ attach_in_initrd = fstab_test_option(options, "x-initrd.attach\0");
+
+ keyfile_can_timeout = fstab_filter_options(options,
+ "keyfile-timeout\0",
+ NULL, &keyfile_timeout_value, NULL, NULL);
+ if (keyfile_can_timeout < 0)
+ return log_error_errno(keyfile_can_timeout, "Failed to parse keyfile-timeout= option value: %m");
+
+ detached_header = fstab_filter_options(
+ options,
+ "header\0",
+ NULL,
+ &header_path,
+ NULL,
+ headerdev ? &filtered_header : NULL);
+ if (detached_header < 0)
+ return log_error_errno(detached_header, "Failed to parse header= option value: %m");
+
+ tmp = fstab_filter_options(options, "tmp\0", NULL, &tmp_fstype, NULL, NULL);
+ if (tmp < 0)
+ return log_error_errno(tmp, "Failed to parse tmp= option value: %m");
+
+ if (tmp && swap)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.",
+ name);
+
+ name_escaped = specifier_escape(name);
+ if (!name_escaped)
+ return log_oom();
+
+ e = unit_name_escape(name);
+ if (!e)
+ return log_oom();
+
+ u = fstab_node_to_udev_node(device);
+ if (!u)
+ return log_oom();
+
+ r = unit_name_build("systemd-cryptsetup", e, ".service", &n);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate unit name: %m");
+
+ u_escaped = specifier_escape(u);
+ if (!u_escaped)
+ return log_oom();
+
+ r = unit_name_from_path(u, ".device", &d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate unit name: %m");
+
+ if (keydev && !key_file)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Key device is specified, but path to the key file is missing.");
+
+ r = generator_open_unit_file(arg_dest, NULL, n, &f);
+ if (r < 0)
+ return r;
+
+ r = generator_write_cryptsetup_unit_section(f, source);
+ if (r < 0)
+ return r;
+
+ if (netdev)
+ fprintf(f, "After=remote-fs-pre.target\n");
+
+ /* If initrd takes care of attaching the disk then it should also detach it during shutdown. */
+ if (!attach_in_initrd)
+ fprintf(f, "Conflicts=umount.target\n");
+
+ if (keydev) {
+ _cleanup_free_ char *unit = NULL, *umount_unit = NULL;
+
+ r = generate_device_mount(
+ name,
+ keydev,
+ "keydev",
+ keyfile_timeout_value,
+ /* canfail = */ keyfile_can_timeout > 0,
+ /* readonly= */ true,
+ &unit,
+ &keydev_mount);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate keydev mount unit: %m");
+
+ r = generate_device_umount(name, keydev_mount, "keydev", &umount_unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate keydev umount unit: %m");
+
+ key_file_buffer = path_join(keydev_mount, key_file);
+ if (!key_file_buffer)
+ return log_oom();
+
+ key_file = key_file_buffer;
+
+ fprintf(f, "After=%s\n", unit);
+ if (keyfile_can_timeout > 0)
+ fprintf(f, "Wants=%s\n", unit);
+ else
+ fprintf(f, "Requires=%s\n", unit);
+
+ if (umount_unit)
+ fprintf(f,
+ "Wants=%s\n"
+ "Before=%s\n",
+ umount_unit,
+ umount_unit
+ );
+ }
+
+ if (headerdev) {
+ _cleanup_free_ char *unit = NULL, *umount_unit = NULL, *p = NULL;
+
+ r = generate_device_mount(
+ name,
+ headerdev,
+ "headerdev",
+ NULL,
+ /* canfail= */ false, /* header is always necessary */
+ /* readonly= */ false, /* LUKS2 recovery requires rw header access */
+ &unit,
+ &headerdev_mount);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate header device mount unit: %m");
+
+ r = generate_device_umount(name, headerdev_mount, "headerdev", &umount_unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate header device umount unit: %m");
+
+ p = path_join(headerdev_mount, header_path);
+ if (!p)
+ return log_oom();
+
+ free_and_replace(header_path, p);
+
+ if (isempty(filtered_header))
+ p = strjoin("header=", header_path);
+ else
+ p = strjoin(filtered_header, ",header=", header_path);
+
+ if (!p)
+ return log_oom();
+
+ free_and_replace(filtered_header, p);
+ options = filtered_header;
+
+ fprintf(f, "After=%s\n"
+ "Requires=%s\n", unit, unit);
+
+ if (umount_unit)
+ fprintf(f,
+ "Wants=%s\n"
+ "Before=%s\n",
+ umount_unit,
+ umount_unit
+ );
+ }
+
+ if (!nofail)
+ fprintf(f,
+ "Before=%s\n",
+ netdev ? "remote-cryptsetup.target" : "cryptsetup.target");
+
+ if (key_file && !keydev) {
+ r = print_dependencies(f, key_file,
+ keyfile_timeout_value,
+ /* canfail= */ keyfile_can_timeout > 0);
+ if (r < 0)
+ return r;
+ }
+
+ /* Check if a header option was specified */
+ if (detached_header > 0 && !headerdev) {
+ r = print_dependencies(f, header_path,
+ NULL,
+ /* canfail= */ false); /* header is always necessary */
+ if (r < 0)
+ return r;
+ }
+
+ if (path_startswith(u, "/dev/"))
+ fprintf(f,
+ "BindsTo=%s\n"
+ "After=%s\n"
+ "Before=umount.target\n",
+ d, d);
+ else
+ /* For loopback devices, add systemd-tmpfiles-setup-dev.service
+ dependency to ensure that loopback support is available in
+ the kernel (/dev/loop-control needs to exist) */
+ fprintf(f,
+ "RequiresMountsFor=%s\n"
+ "Requires=systemd-tmpfiles-setup-dev.service\n"
+ "After=systemd-tmpfiles-setup-dev.service\n",
+ u_escaped);
+
+ r = generator_write_timeouts(arg_dest, device, name, options, &filtered);
+ if (r < 0)
+ log_warning_errno(r, "Failed to write device timeout drop-in: %m");
+
+ r = generator_write_cryptsetup_service_section(f, name, u, key_file, filtered);
+ if (r < 0)
+ return r;
+
+ if (tmp) {
+ _cleanup_free_ char *tmp_fstype_escaped = NULL;
+
+ if (tmp_fstype) {
+ tmp_fstype_escaped = specifier_escape(tmp_fstype);
+ if (!tmp_fstype_escaped)
+ return log_oom();
+ }
+
+ fprintf(f,
+ "ExecStartPost=" ROOTLIBEXECDIR "/systemd-makefs '%s' '/dev/mapper/%s'\n",
+ tmp_fstype_escaped ?: "ext4", name_escaped);
+ }
+
+ if (swap)
+ fprintf(f,
+ "ExecStartPost=" ROOTLIBEXECDIR "/systemd-makefs swap '/dev/mapper/%s'\n",
+ name_escaped);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write unit file %s: %m", n);
+
+ if (!noauto) {
+ r = generator_add_symlink(arg_dest,
+ netdev ? "remote-cryptsetup.target" : "cryptsetup.target",
+ nofail ? "wants" : "requires", n);
+ if (r < 0)
+ return r;
+ }
+
+ dmname = strjoina("dev-mapper-", e, ".device");
+ r = generator_add_symlink(arg_dest, dmname, "requires", n);
+ if (r < 0)
+ return r;
+
+ if (!noauto && !nofail) {
+ r = write_drop_in(arg_dest, dmname, 40, "device-timeout",
+ "# Automatically generated by systemd-cryptsetup-generator\n\n"
+ "[Unit]\nJobTimeoutSec=0");
+ if (r < 0)
+ log_warning_errno(r, "Failed to write device timeout drop-in: %m");
+ }
+
+ return 0;
+}
+
+static crypto_device* crypt_device_free(crypto_device *d) {
+ if (!d)
+ return NULL;
+
+ free(d->uuid);
+ free(d->keyfile);
+ free(d->keydev);
+ free(d->name);
+ free(d->options);
+ return mfree(d);
+}
+
+static crypto_device *get_crypto_device(const char *uuid) {
+ int r;
+ crypto_device *d;
+
+ assert(uuid);
+
+ d = hashmap_get(arg_disks, uuid);
+ if (!d) {
+ d = new0(struct crypto_device, 1);
+ if (!d)
+ return NULL;
+
+ d->uuid = strdup(uuid);
+ if (!d->uuid)
+ return mfree(d);
+
+ r = hashmap_put(arg_disks, d->uuid, d);
+ if (r < 0) {
+ free(d->uuid);
+ return mfree(d);
+ }
+ }
+
+ return d;
+}
+
+static bool warn_uuid_invalid(const char *uuid, const char *key) {
+ assert(key);
+
+ if (!id128_is_valid(uuid)) {
+ log_warning("Failed to parse %s= kernel command line switch. UUID is invalid, ignoring.", key);
+ return true;
+ }
+
+ return false;
+}
+
+static int filter_header_device(const char *options,
+ char **ret_headerdev,
+ char **ret_filtered_headerdev_options) {
+ int r;
+ _cleanup_free_ char *headerfile = NULL, *headerdev = NULL, *headerspec = NULL,
+ *filtered_headerdev = NULL, *filtered_headerspec = NULL;
+
+ assert(ret_headerdev);
+ assert(ret_filtered_headerdev_options);
+
+ r = fstab_filter_options(options, "header\0", NULL, &headerspec, NULL, &filtered_headerspec);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse header= option value: %m");
+
+ if (r > 0) {
+ r = split_locationspec(headerspec, &headerfile, &headerdev);
+ if (r < 0)
+ return r;
+
+ if (isempty(filtered_headerspec))
+ filtered_headerdev = strjoin("header=", headerfile);
+ else
+ filtered_headerdev = strjoin(filtered_headerspec, ",header=", headerfile);
+
+ if (!filtered_headerdev)
+ return log_oom();
+ } else
+ filtered_headerdev = TAKE_PTR(filtered_headerspec);
+
+ *ret_filtered_headerdev_options = TAKE_PTR(filtered_headerdev);
+ *ret_headerdev = TAKE_PTR(headerdev);
+
+ return 0;
+}
+
+static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
+ _cleanup_free_ char *uuid = NULL, *uuid_value = NULL;
+ crypto_device *d;
+ int r;
+
+ if (streq(key, "luks")) {
+
+ r = value ? parse_boolean(value) : 1;
+ if (r < 0)
+ log_warning("Failed to parse luks= kernel command line switch %s. Ignoring.", value);
+ else
+ arg_enabled = r;
+
+ } else if (streq(key, "luks.crypttab")) {
+
+ r = value ? parse_boolean(value) : 1;
+ if (r < 0)
+ log_warning("Failed to parse luks.crypttab= kernel command line switch %s. Ignoring.", value);
+ else
+ arg_read_crypttab = r;
+
+ } else if (streq(key, "luks.uuid")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ d = get_crypto_device(startswith(value, "luks-") ?: value);
+ if (!d)
+ return log_oom();
+
+ d->create = arg_allow_list = true;
+
+ } else if (streq(key, "luks.options")) {
+ _cleanup_free_ char *headerdev = NULL, *filtered_headerdev_options = NULL;
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
+ if (r != 2)
+ return free_and_strdup_warn(&arg_default_options, value);
+
+ if (warn_uuid_invalid(uuid, key))
+ return 0;
+
+ d = get_crypto_device(uuid);
+ if (!d)
+ return log_oom();
+
+ r = filter_header_device(uuid_value, &headerdev, &filtered_headerdev_options);
+ if (r < 0)
+ return r;
+
+ free_and_replace(d->options, filtered_headerdev_options);
+ free_and_replace(d->headerdev, headerdev);
+ } else if (streq(key, "luks.key")) {
+ size_t n;
+ _cleanup_free_ char *keyfile = NULL, *keydev = NULL;
+ const char *keyspec;
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ n = strspn(value, ALPHANUMERICAL "-");
+ if (value[n] != '=')
+ return free_and_strdup_warn(&arg_default_keyfile, value);
+
+ uuid = strndup(value, n);
+ if (!uuid)
+ return log_oom();
+
+ if (warn_uuid_invalid(uuid, key))
+ return 0;
+
+ d = get_crypto_device(uuid);
+ if (!d)
+ return log_oom();
+
+ keyspec = value + n + 1;
+ r = split_locationspec(keyspec, &keyfile, &keydev);
+ if (r < 0)
+ return r;
+
+ free_and_replace(d->keyfile, keyfile);
+ free_and_replace(d->keydev, keydev);
+ } else if (streq(key, "luks.data")) {
+ size_t n;
+ _cleanup_free_ char *datadev = NULL;
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ n = strspn(value, ALPHANUMERICAL "-");
+ if (value[n] != '=') {
+ log_warning("Failed to parse luks.data= kernel command line switch. UUID is invalid, ignoring.");
+ return 0;
+ }
+
+ uuid = strndup(value, n);
+ if (!uuid)
+ return log_oom();
+
+ if (warn_uuid_invalid(uuid, key))
+ return 0;
+
+ d = get_crypto_device(uuid);
+ if (!d)
+ return log_oom();
+
+ datadev = fstab_node_to_udev_node(value + n + 1);
+ if (!datadev)
+ return log_oom();
+
+ free_and_replace(d->datadev, datadev);
+ } else if (streq(key, "luks.name")) {
+
+ if (proc_cmdline_value_missing(key, value))
+ return 0;
+
+ r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
+ if (r == 2) {
+ d = get_crypto_device(uuid);
+ if (!d)
+ return log_oom();
+
+ d->create = arg_allow_list = true;
+
+ free_and_replace(d->name, uuid_value);
+ } else
+ log_warning("Failed to parse luks name switch %s. Ignoring.", value);
+ }
+
+ return 0;
+}
+
+static int add_crypttab_devices(void) {
+ _cleanup_fclose_ FILE *f = NULL;
+ unsigned crypttab_line = 0;
+ int r;
+
+ if (!arg_read_crypttab)
+ return 0;
+
+ r = fopen_unlocked(arg_crypttab, "re", &f);
+ if (r < 0) {
+ if (errno != ENOENT)
+ log_error_errno(errno, "Failed to open %s: %m", arg_crypttab);
+ return 0;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *line = NULL, *name = NULL, *device = NULL, *keyspec = NULL, *options = NULL,
+ *keyfile = NULL, *keydev = NULL, *headerdev = NULL, *filtered_header = NULL;
+ crypto_device *d = NULL;
+ char *l, *uuid;
+ int k;
+
+ r = read_line(f, LONG_LINE_MAX, &line);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read %s: %m", arg_crypttab);
+ if (r == 0)
+ break;
+
+ crypttab_line++;
+
+ l = strstrip(line);
+ if (IN_SET(l[0], 0, '#'))
+ continue;
+
+ k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &keyspec, &options);
+ if (k < 2 || k > 4) {
+ log_error("Failed to parse %s:%u, ignoring.", arg_crypttab, crypttab_line);
+ continue;
+ }
+
+ uuid = startswith(device, "UUID=");
+ if (!uuid)
+ uuid = path_startswith(device, "/dev/disk/by-uuid/");
+ if (!uuid)
+ uuid = startswith(name, "luks-");
+ if (uuid)
+ d = hashmap_get(arg_disks, uuid);
+
+ if (arg_allow_list && !d) {
+ log_info("Not creating device '%s' because it was not specified on the kernel command line.", name);
+ continue;
+ }
+
+ r = split_locationspec(keyspec, &keyfile, &keydev);
+ if (r < 0)
+ return r;
+
+ if (options && (!d || !d->options)) {
+ r = filter_header_device(options, &headerdev, &filtered_header);
+ if (r < 0)
+ return r;
+ free_and_replace(options, filtered_header);
+ }
+
+ r = create_disk(name,
+ device,
+ keyfile,
+ keydev,
+ (d && d->options) ? d->headerdev : headerdev,
+ (d && d->options) ? d->options : options,
+ arg_crypttab);
+ if (r < 0)
+ return r;
+
+ if (d)
+ d->create = false;
+ }
+
+ return 0;
+}
+
+static int add_proc_cmdline_devices(void) {
+ int r;
+ crypto_device *d;
+
+ HASHMAP_FOREACH(d, arg_disks) {
+ _cleanup_free_ char *device = NULL;
+
+ if (!d->create)
+ continue;
+
+ if (!d->name) {
+ d->name = strjoin("luks-", d->uuid);
+ if (!d->name)
+ return log_oom();
+ }
+
+ device = strjoin("UUID=", d->uuid);
+ if (!device)
+ return log_oom();
+
+ r = create_disk(d->name,
+ d->datadev ?: device,
+ d->keyfile ?: arg_default_keyfile,
+ d->keydev,
+ d->headerdev,
+ d->options ?: arg_default_options,
+ "/proc/cmdline");
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(crypt_device_hash_ops, char, string_hash_func, string_compare_func,
+ crypto_device, crypt_device_free);
+
+static int run(const char *dest, const char *dest_early, const char *dest_late) {
+ int r;
+
+ assert_se(arg_dest = dest);
+
+ arg_crypttab = getenv("SYSTEMD_CRYPTTAB") ?: "/etc/crypttab";
+ arg_runtime_directory = getenv("RUNTIME_DIRECTORY") ?: "/run/systemd/cryptsetup";
+
+ arg_disks = hashmap_new(&crypt_device_hash_ops);
+ if (!arg_disks)
+ return log_oom();
+
+ r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to parse kernel command line: %m");
+
+ if (!arg_enabled)
+ return 0;
+
+ r = add_crypttab_devices();
+ if (r < 0)
+ return r;
+
+ r = add_proc_cmdline_devices();
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+DEFINE_MAIN_GENERATOR_FUNCTION(run);
diff --git a/src/cryptsetup/cryptsetup-keyfile.c b/src/cryptsetup/cryptsetup-keyfile.c
new file mode 100644
index 0000000..1867e90
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-keyfile.c
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "cryptsetup-keyfile.h"
+#include "fileio.h"
+#include "path-util.h"
+#include "strv.h"
+
+int find_key_file(
+ const char *key_file,
+ char **search_path,
+ const char *bindname,
+ void **ret_key,
+ size_t *ret_key_size) {
+
+ int r;
+
+ assert(key_file);
+ assert(ret_key);
+ assert(ret_key_size);
+
+ if (strv_isempty(search_path) || path_is_absolute(key_file)) {
+
+ r = read_full_file_full(
+ AT_FDCWD, key_file, UINT64_MAX, SIZE_MAX,
+ READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
+ bindname,
+ (char**) ret_key, ret_key_size);
+ if (r == -E2BIG)
+ return log_error_errno(r, "Key file '%s' too large.", key_file);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load key file '%s': %m", key_file);
+
+ return 1;
+ }
+
+ STRV_FOREACH(i, search_path) {
+ _cleanup_free_ char *joined = NULL;
+
+ joined = path_join(*i, key_file);
+ if (!joined)
+ return log_oom();
+
+ r = read_full_file_full(
+ AT_FDCWD, joined, UINT64_MAX, SIZE_MAX,
+ READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
+ bindname,
+ (char**) ret_key, ret_key_size);
+ if (r >= 0)
+ return 1;
+ if (r == -E2BIG) {
+ log_warning_errno(r, "Key file '%s' too large, ignoring.", key_file);
+ continue;
+ }
+ if (r != -ENOENT)
+ return log_error_errno(r, "Failed to load key file '%s': %m", key_file);
+ }
+
+ /* Search path supplied, but file not found, report by returning NULL, but not failing */
+ *ret_key = NULL;
+ *ret_key_size = 0;
+ return 0;
+}
diff --git a/src/cryptsetup/cryptsetup-keyfile.h b/src/cryptsetup/cryptsetup-keyfile.h
new file mode 100644
index 0000000..83bd1fb
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-keyfile.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+int find_key_file(
+ const char *key_file,
+ char **search_path,
+ const char *bindname,
+ void **ret_key,
+ size_t *ret_key_size);
diff --git a/src/cryptsetup/cryptsetup-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c
new file mode 100644
index 0000000..f991389
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-pkcs11.c
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <p11-kit/p11-kit.h>
+#include <p11-kit/uri.h>
+
+#include "alloc-util.h"
+#include "ask-password-api.h"
+#include "cryptsetup-pkcs11.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "parse-util.h"
+#include "pkcs11-util.h"
+#include "random-util.h"
+#include "stat-util.h"
+#include "strv.h"
+
+int decrypt_pkcs11_key(
+ const char *volume_name,
+ const char *friendly_name,
+ const char *pkcs11_uri,
+ const char *key_file, /* We either expect key_file and associated parameters to be set (for file keys) … */
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data, /* … or key_data and key_data_size (for literal keys) */
+ size_t key_data_size,
+ usec_t until,
+ bool headless,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size) {
+
+ _cleanup_(pkcs11_crypt_device_callback_data_release) pkcs11_crypt_device_callback_data data = {
+ .friendly_name = friendly_name,
+ .until = until,
+ .headless = headless,
+ };
+ int r;
+
+ assert(friendly_name);
+ assert(pkcs11_uri);
+ assert(key_file || key_data);
+ assert(ret_decrypted_key);
+ assert(ret_decrypted_key_size);
+
+ /* The functions called here log about all errors, except for EAGAIN which means "token not found right now" */
+
+ if (key_data) {
+ data.encrypted_key = (void*) key_data;
+ data.encrypted_key_size = key_data_size;
+
+ data.free_encrypted_key = false;
+ } else {
+ _cleanup_free_ char *bindname = NULL;
+
+ /* If we read the key via AF_UNIX, make this client recognizable */
+ if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-pkcs11/%s", random_u64(), volume_name) < 0)
+ return log_oom();
+
+ r = read_full_file_full(
+ AT_FDCWD, key_file,
+ key_file_offset == 0 ? UINT64_MAX : key_file_offset,
+ key_file_size == 0 ? SIZE_MAX : key_file_size,
+ READ_FULL_FILE_CONNECT_SOCKET,
+ bindname,
+ (char**) &data.encrypted_key, &data.encrypted_key_size);
+ if (r < 0)
+ return r;
+
+ data.free_encrypted_key = true;
+ }
+
+ r = pkcs11_find_token(pkcs11_uri, pkcs11_crypt_device_callback, &data);
+ if (r < 0)
+ return r;
+
+ *ret_decrypted_key = TAKE_PTR(data.decrypted_key);
+ *ret_decrypted_key_size = data.decrypted_key_size;
+
+ return 0;
+}
+
+int find_pkcs11_auto_data(
+ struct crypt_device *cd,
+ char **ret_uri,
+ void **ret_encrypted_key,
+ size_t *ret_encrypted_key_size,
+ int *ret_keyslot) {
+
+ _cleanup_free_ char *uri = NULL;
+ _cleanup_free_ void *key = NULL;
+ int r, keyslot = -1;
+ size_t key_size = 0;
+
+ assert(cd);
+ assert(ret_uri);
+ assert(ret_encrypted_key);
+ assert(ret_encrypted_key_size);
+ assert(ret_keyslot);
+
+ /* Loads PKCS#11 metadata from LUKS2 JSON token headers. */
+
+ for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ JsonVariant *w;
+ int ks;
+
+ r = cryptsetup_get_token_as_json(cd, token, "systemd-pkcs11", &v);
+ if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
+ continue;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read JSON token data off disk: %m");
+
+ ks = cryptsetup_get_keyslot_from_token(v);
+ if (ks < 0) {
+ /* Handle parsing errors of the keyslots field gracefully, since it's not 'owned' by
+ * us, but by the LUKS2 spec */
+ log_warning_errno(ks, "Failed to extract keyslot index from PKCS#11 JSON data token %i, skipping: %m", token);
+ continue;
+ }
+
+ if (uri)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+ "Multiple PKCS#11 tokens enrolled, cannot automatically determine token.");
+
+ assert(keyslot < 0);
+ keyslot = ks;
+
+ w = json_variant_by_key(v, "pkcs11-uri");
+ if (!w || !json_variant_is_string(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "PKCS#11 token data lacks 'pkcs11-uri' field.");
+
+ uri = strdup(json_variant_string(w));
+ if (!uri)
+ return log_oom();
+
+ if (!pkcs11_uri_valid(uri))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "PKCS#11 token data contains invalid PKCS#11 URI.");
+
+ w = json_variant_by_key(v, "pkcs11-key");
+ if (!w || !json_variant_is_string(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "PKCS#11 token data lacks 'pkcs11-key' field.");
+
+ assert(!key);
+ assert(key_size == 0);
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decode base64 encoded key.");
+ }
+
+ if (!uri)
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
+ "No valid PKCS#11 token data found.");
+
+ log_info("Automatically discovered security PKCS#11 token '%s' unlocks volume.", uri);
+
+ *ret_uri = TAKE_PTR(uri);
+ *ret_encrypted_key = TAKE_PTR(key);
+ *ret_encrypted_key_size = key_size;
+ *ret_keyslot = keyslot;
+ return 0;
+}
diff --git a/src/cryptsetup/cryptsetup-pkcs11.h b/src/cryptsetup/cryptsetup-pkcs11.h
new file mode 100644
index 0000000..256c09a
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-pkcs11.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#include "cryptsetup-util.h"
+#include "log.h"
+#include "time-util.h"
+
+#if HAVE_P11KIT
+
+int decrypt_pkcs11_key(
+ const char *volume_name,
+ const char *friendly_name,
+ const char *pkcs11_uri,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data,
+ size_t key_data_size,
+ usec_t until,
+ bool headless,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size);
+
+int find_pkcs11_auto_data(
+ struct crypt_device *cd,
+ char **ret_uri,
+ void **ret_encrypted_key,
+ size_t *ret_encrypted_key_size,
+ int *ret_keyslot);
+
+#else
+
+static inline int decrypt_pkcs11_key(
+ const char *volume_name,
+ const char *friendly_name,
+ const char *pkcs11_uri,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data,
+ size_t key_data_size,
+ usec_t until,
+ bool headless,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size) {
+
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "PKCS#11 Token support not available.");
+}
+
+static inline int find_pkcs11_auto_data(
+ struct crypt_device *cd,
+ char **ret_uri,
+ void **ret_encrypted_key,
+ size_t *ret_encrypted_key_size,
+ int *ret_keyslot) {
+
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "PKCS#11 Token support not available.");
+}
+
+#endif
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c
new file mode 100644
index 0000000..3027804
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c
@@ -0,0 +1,218 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <libcryptsetup.h>
+#include <string.h>
+
+#include "cryptsetup-token.h"
+#include "cryptsetup-token-util.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "luks2-fido2.h"
+#include "memory-util.h"
+#include "version.h"
+
+#define TOKEN_NAME "systemd-fido2"
+#define TOKEN_VERSION_MAJOR "1"
+#define TOKEN_VERSION_MINOR "0"
+
+/* for libcryptsetup debug purpose */
+_public_ const char *cryptsetup_token_version(void) {
+ return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")";
+}
+
+_public_ int cryptsetup_token_open_pin(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ int token /* is always >= 0 */,
+ const char *pin,
+ size_t pin_size,
+ char **password, /* freed by cryptsetup_token_buffer_free */
+ size_t *password_len,
+ void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
+
+ int r;
+ const char *json;
+ _cleanup_(erase_and_freep) char *pin_string = NULL;
+
+ assert(!pin || pin_size);
+ assert(token >= 0);
+
+ /* This must not fail at this moment (internal error) */
+ r = crypt_token_json_get(cd, token, &json);
+ /* Use assert_se() here to avoid emitting warning with -DNDEBUG */
+ assert_se(token == r);
+ assert(json);
+
+ r = crypt_normalize_pin(pin, pin_size, &pin_string);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Can not normalize PIN: %m");
+
+ return acquire_luks2_key(cd, json, (const char *)usrptr, pin_string, password, password_len);
+}
+
+/*
+ * This function is called from within following libcryptsetup calls
+ * provided conditions further below are met:
+ *
+ * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-fido2'):
+ *
+ * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device
+ * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY
+ * and token is assigned to at least single keyslot).
+ *
+ * - if plugin defines validate function (see cryptsetup_token_validate below) it must have
+ * passed the check (aka return 0)
+ */
+_public_ int cryptsetup_token_open(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ int token /* is always >= 0 */,
+ char **password, /* freed by cryptsetup_token_buffer_free */
+ size_t *password_len,
+ void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
+
+ return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr);
+}
+
+/*
+ * libcryptsetup callback for memory deallocation of 'password' parameter passed in
+ * any crypt_token_open_* plugin function
+ */
+_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) {
+ erase_and_free(buffer);
+}
+
+/*
+ * prints systemd-fido2 token content in crypt_dump().
+ * 'type' and 'keyslots' fields are printed by libcryptsetup
+ */
+_public_ void cryptsetup_token_dump(
+ struct crypt_device *cd /* is always LUKS2 context */,
+ const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
+
+ int r;
+ Fido2EnrollFlags required;
+ size_t cid_size, salt_size;
+ const char *client_pin_req_str, *up_req_str, *uv_req_str;
+ _cleanup_free_ void *cid = NULL, *salt = NULL;
+ _cleanup_free_ char *rp_id = NULL, *cid_str = NULL, *salt_str = NULL;
+
+ assert(json);
+
+ r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m.");
+
+ r = crypt_dump_buffer_to_hex_string(cid, cid_size, &cid_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
+
+ r = crypt_dump_buffer_to_hex_string(salt, salt_size, &salt_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
+
+ if (required & FIDO2ENROLL_PIN)
+ client_pin_req_str = "true";
+ else if (required & FIDO2ENROLL_PIN_IF_NEEDED)
+ client_pin_req_str = NULL;
+ else
+ client_pin_req_str = "false";
+
+ if (required & FIDO2ENROLL_UP)
+ up_req_str = "true";
+ else if (required & FIDO2ENROLL_UP_IF_NEEDED)
+ up_req_str = NULL;
+ else
+ up_req_str = "false";
+
+ if (required & FIDO2ENROLL_UV)
+ uv_req_str = "true";
+ else if (required & FIDO2ENROLL_UV_OMIT)
+ uv_req_str = NULL;
+ else
+ uv_req_str = "false";
+
+ crypt_log(cd, "\tfido2-credential:" CRYPT_DUMP_LINE_SEP "%s\n", cid_str);
+ crypt_log(cd, "\tfido2-salt: %s\n", salt_str);
+
+ /* optional fields */
+ if (rp_id)
+ crypt_log(cd, "\tfido2-rp: %s\n", rp_id);
+ if (client_pin_req_str)
+ crypt_log(cd, "\tfido2-clientPin-required:" CRYPT_DUMP_LINE_SEP "%s\n",
+ client_pin_req_str);
+ if (up_req_str)
+ crypt_log(cd, "\tfido2-up-required:" CRYPT_DUMP_LINE_SEP "%s\n", up_req_str);
+ if (uv_req_str)
+ crypt_log(cd, "\tfido2-uv-required:" CRYPT_DUMP_LINE_SEP "%s\n", uv_req_str);
+}
+
+/*
+ * Note:
+ * If plugin is available in library path, it's called in before following libcryptsetup calls:
+ *
+ * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour
+ */
+_public_ int cryptsetup_token_validate(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) {
+
+ int r;
+ JsonVariant *w;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+ assert(json);
+
+ r = json_parse(json, 0, &v, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m.");
+
+ w = json_variant_by_key(v, "fido2-credential");
+ if (!w || !json_variant_is_string(w)) {
+ crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-credential' field.");
+ return 1;
+ }
+
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'fido2-credential' field: %m");
+
+ w = json_variant_by_key(v, "fido2-salt");
+ if (!w || !json_variant_is_string(w)) {
+ crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-salt' field.");
+ return 1;
+ }
+
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded salt: %m.");
+
+ /* The "rp" field is optional. */
+ w = json_variant_by_key(v, "fido2-rp");
+ if (w && !json_variant_is_string(w)) {
+ crypt_log_debug(cd, "FIDO2 token data's 'fido2-rp' field is not a string.");
+ return 1;
+ }
+
+ /* The "fido2-clientPin-required" field is optional. */
+ w = json_variant_by_key(v, "fido2-clientPin-required");
+ if (w && !json_variant_is_boolean(w)) {
+ crypt_log_debug(cd, "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean.");
+ return 1;
+ }
+
+ /* The "fido2-up-required" field is optional. */
+ w = json_variant_by_key(v, "fido2-up-required");
+ if (w && !json_variant_is_boolean(w)) {
+ crypt_log_debug(cd, "FIDO2 token data's 'fido2-up-required' field is not a boolean.");
+ return 1;
+ }
+
+ /* The "fido2-uv-required" field is optional. */
+ w = json_variant_by_key(v, "fido2-uv-required");
+ if (w && !json_variant_is_boolean(w)) {
+ crypt_log_debug(cd, "FIDO2 token data's 'fido2-uv-required' field is not a boolean.");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c
new file mode 100644
index 0000000..c3e7fbd
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <libcryptsetup.h>
+
+#include "cryptsetup-token.h"
+#include "cryptsetup-token-util.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "luks2-pkcs11.h"
+#include "memory-util.h"
+#include "pkcs11-util.h"
+#include "version.h"
+
+#define TOKEN_NAME "systemd-pkcs11"
+#define TOKEN_VERSION_MAJOR "1"
+#define TOKEN_VERSION_MINOR "0"
+
+/* for libcryptsetup debug purpose */
+_public_ const char *cryptsetup_token_version(void) {
+ return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")";
+}
+
+_public_ int cryptsetup_token_open_pin(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ int token /* is always >= 0 */,
+ const char *pin,
+ size_t pin_size,
+ char **password, /* freed by cryptsetup_token_buffer_free */
+ size_t *password_len,
+ void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
+
+ const char *json;
+ int r;
+
+ assert(!pin || pin_size);
+ assert(token >= 0);
+
+ /* This must not fail at this moment (internal error) */
+ r = crypt_token_json_get(cd, token, &json);
+ /* Use assert_se() here to avoid emitting warning with -DNDEBUG */
+ assert_se(token == r);
+ assert(json);
+
+ return acquire_luks2_key(cd, json, usrptr, pin, pin_size, password, password_len);
+}
+
+/*
+ * This function is called from within following libcryptsetup calls
+ * provided conditions further below are met:
+ *
+ * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-pkcs11'):
+ *
+ * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device
+ * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY
+ * and token is assigned to at least single keyslot).
+ *
+ * - if plugin defines validate function (see cryptsetup_token_validate below) it must have
+ * passed the check (aka return 0)
+ */
+_public_ int cryptsetup_token_open(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ int token /* is always >= 0 */,
+ char **password, /* freed by cryptsetup_token_buffer_free */
+ size_t *password_len,
+ void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
+
+ return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr);
+}
+
+/*
+ * libcryptsetup callback for memory deallocation of 'password' parameter passed in
+ * any crypt_token_open_* plugin function
+ */
+_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) {
+ erase_and_free(buffer);
+}
+
+/*
+ * prints systemd-pkcs11 token content in crypt_dump().
+ * 'type' and 'keyslots' fields are printed by libcryptsetup
+ */
+_public_ void cryptsetup_token_dump(
+ struct crypt_device *cd /* is always LUKS2 context */,
+ const char *json /* validated 'systemd-pkcs11' token if cryptsetup_token_validate is defined */) {
+
+ int r;
+ size_t pkcs11_key_size;
+ _cleanup_free_ char *pkcs11_uri = NULL, *key_str = NULL;
+ _cleanup_free_ void *pkcs11_key = NULL;
+
+ r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m.");
+
+ r = crypt_dump_buffer_to_hex_string(pkcs11_key, pkcs11_key_size, &key_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
+
+ crypt_log(cd, "\tpkcs11-uri: %s\n", pkcs11_uri);
+ crypt_log(cd, "\tpkcs11-key: %s\n", key_str);
+}
+
+/*
+ * Note:
+ * If plugin is available in library path, it's called in before following libcryptsetup calls:
+ *
+ * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour
+ */
+_public_ int cryptsetup_token_validate(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-pkcs11' */) {
+
+ int r;
+ JsonVariant *w;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+ r = json_parse(json, 0, &v, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m.");
+
+ w = json_variant_by_key(v, "pkcs11-uri");
+ if (!w || !json_variant_is_string(w)) {
+ crypt_log_debug(cd, "PKCS#11 token data lacks 'pkcs11-uri' field.");
+ return 1;
+ }
+
+ if (!pkcs11_uri_valid(json_variant_string(w))) {
+ crypt_log_debug(cd, "PKCS#11 token data contains invalid PKCS#11 URI.");
+ return 1;
+ }
+
+ w = json_variant_by_key(v, "pkcs11-key");
+ if (!w || !json_variant_is_string(w)) {
+ crypt_log_debug(cd, "PKCS#11 token data lacks 'pkcs11-key' field.");
+ return 1;
+ }
+
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m.");
+
+ return 0;
+}
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
new file mode 100644
index 0000000..abe8072
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
@@ -0,0 +1,335 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <libcryptsetup.h>
+
+#include "cryptsetup-token.h"
+#include "cryptsetup-token-util.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "luks2-tpm2.h"
+#include "memory-util.h"
+#include "strv.h"
+#include "tpm2-util.h"
+#include "version.h"
+
+#define TOKEN_NAME "systemd-tpm2"
+#define TOKEN_VERSION_MAJOR "1"
+#define TOKEN_VERSION_MINOR "0"
+
+/* for libcryptsetup debug purpose */
+_public_ const char *cryptsetup_token_version(void) {
+
+ return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")";
+}
+
+static int log_debug_open_error(struct crypt_device *cd, int r) {
+ if (r == -EAGAIN)
+ return crypt_log_debug_errno(cd, r, "TPM2 device not found.");
+ if (r == -ENXIO)
+ return crypt_log_debug_errno(cd, r, "No matching TPM2 token data found.");
+
+ return crypt_log_debug_errno(cd, r, TOKEN_NAME " open failed: %m.");
+}
+
+_public_ int cryptsetup_token_open_pin(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ int token /* is always >= 0 */,
+ const char *pin,
+ size_t pin_size,
+ char **ret_password, /* freed by cryptsetup_token_buffer_free */
+ size_t *ret_password_len,
+ void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
+
+ _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL;
+ size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
+ systemd_tpm2_plugin_params params = {
+ .search_pcr_mask = UINT32_MAX
+ };
+ uint16_t pcr_bank, primary_alg;
+ TPM2Flags flags = 0;
+ const char *json;
+ int r;
+
+ assert(token >= 0);
+ assert(!pin || pin_size > 0);
+ assert(ret_password);
+ assert(ret_password_len);
+
+ /* This must not fail at this moment (internal error) */
+ r = crypt_token_json_get(cd, token, &json);
+ assert(token == r);
+ assert(json);
+
+ r = crypt_normalize_pin(pin, pin_size, &pin_string);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Can not normalize PIN: %m");
+
+ if (usrptr)
+ params = *(systemd_tpm2_plugin_params *)usrptr;
+
+ r = json_parse(json, 0, &v, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m");
+
+ r = tpm2_parse_luks2_json(
+ v,
+ NULL,
+ &hash_pcr_mask,
+ &pcr_bank,
+ &pubkey,
+ &pubkey_size,
+ &pubkey_pcr_mask,
+ &primary_alg,
+ &blob,
+ &blob_size,
+ &policy_hash,
+ &policy_hash_size,
+ &flags);
+ if (r < 0)
+ return log_debug_open_error(cd, r);
+
+ if (params.search_pcr_mask != UINT32_MAX && hash_pcr_mask != params.search_pcr_mask)
+ return crypt_log_debug_errno(cd, ENXIO, "PCR mask doesn't match expectation (%" PRIu32 " vs. %" PRIu32 ")", hash_pcr_mask, params.search_pcr_mask);
+
+ r = acquire_luks2_key(
+ params.device,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ params.signature_path,
+ pin_string,
+ primary_alg,
+ blob,
+ blob_size,
+ policy_hash,
+ policy_hash_size,
+ flags,
+ &decrypted_key,
+ &decrypted_key_size);
+ if (r < 0)
+ return log_debug_open_error(cd, r);
+
+ /* Before using this key as passphrase we base64 encode it, for compat with homed */
+ r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (r < 0)
+ return log_debug_open_error(cd, r);
+
+ /* free'd automatically by libcryptsetup */
+ *ret_password_len = strlen(base64_encoded);
+ *ret_password = TAKE_PTR(base64_encoded);
+
+ return 0;
+}
+
+/*
+ * This function is called from within following libcryptsetup calls
+ * provided conditions further below are met:
+ *
+ * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-tpm2'):
+ *
+ * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device
+ * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY
+ * and token is assigned to at least single keyslot).
+ *
+ * - if plugin defines validate function (see cryptsetup_token_validate below) it must have
+ * passed the check (aka return 0)
+ */
+_public_ int cryptsetup_token_open(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ int token /* is always >= 0 */,
+ char **ret_password, /* freed by cryptsetup_token_buffer_free */
+ size_t *ret_password_len,
+ void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
+
+ return cryptsetup_token_open_pin(cd, token, NULL, 0, ret_password, ret_password_len, usrptr);
+}
+
+/*
+ * libcryptsetup callback for memory deallocation of 'password' parameter passed in
+ * any crypt_token_open_* plugin function
+ */
+_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) {
+ erase_and_free(buffer);
+}
+
+/*
+ * prints systemd-tpm2 token content in crypt_dump().
+ * 'type' and 'keyslots' fields are printed by libcryptsetup
+ */
+_public_ void cryptsetup_token_dump(
+ struct crypt_device *cd /* is always LUKS2 context */,
+ const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
+
+ _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ size_t blob_size, policy_hash_size, pubkey_size;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
+ uint16_t pcr_bank, primary_alg;
+ TPM2Flags flags = 0;
+ int r;
+
+ assert(json);
+
+ r = json_parse(json, 0, &v, NULL, NULL);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m");
+
+ r = tpm2_parse_luks2_json(
+ v,
+ NULL,
+ &hash_pcr_mask,
+ &pcr_bank,
+ &pubkey,
+ &pubkey_size,
+ &pubkey_pcr_mask,
+ &primary_alg,
+ &blob,
+ &blob_size,
+ &policy_hash,
+ &policy_hash_size,
+ &flags);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
+
+ r = pcr_mask_to_string(hash_pcr_mask, &hash_pcrs_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
+
+ r = pcr_mask_to_string(pubkey_pcr_mask, &pubkey_pcrs_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
+
+ r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
+
+ r = crypt_dump_buffer_to_hex_string(pubkey, pubkey_size, &pubkey_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
+
+ r = crypt_dump_buffer_to_hex_string(policy_hash, policy_hash_size, &policy_hash_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
+
+ crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str));
+ crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank)));
+ crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str);
+ crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str));
+ crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg)));
+ crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
+ crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
+ crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
+}
+
+/*
+ * Note:
+ * If plugin is available in library path, it's called in before following libcryptsetup calls:
+ *
+ * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour
+ */
+_public_ int cryptsetup_token_validate(
+ struct crypt_device *cd, /* is always LUKS2 context */
+ const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) {
+
+ int r;
+ JsonVariant *w, *e;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+ assert(json);
+
+ r = json_parse(json, 0, &v, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m");
+
+ w = json_variant_by_key(v, "tpm2-pcrs");
+ if (!w || !json_variant_is_array(w)) {
+ crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-pcrs' field.");
+ return 1;
+ }
+
+ JSON_VARIANT_ARRAY_FOREACH(e, w) {
+ uint64_t u;
+
+ if (!json_variant_is_number(e)) {
+ crypt_log_debug(cd, "TPM2 PCR is not a number.");
+ return 1;
+ }
+
+ u = json_variant_unsigned(e);
+ if (u >= TPM2_PCRS_MAX) {
+ crypt_log_debug(cd, "TPM2 PCR number out of range.");
+ return 1;
+ }
+ }
+
+ /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded
+ * to SHA256. */
+ w = json_variant_by_key(v, "tpm2-pcr-bank");
+ if (w) {
+ /* The PCR bank field is optional */
+
+ if (!json_variant_is_string(w)) {
+ crypt_log_debug(cd, "TPM2 PCR bank is not a string.");
+ return 1;
+ }
+
+ if (tpm2_pcr_bank_from_string(json_variant_string(w)) < 0) {
+ crypt_log_debug(cd, "TPM2 PCR bank invalid or not supported: %s.", json_variant_string(w));
+ return 1;
+ }
+ }
+
+ /* The primary key algorithm field is optional, since it was also added in systemd 250 only. Before
+ * the algorithm was hardcoded to ECC. */
+ w = json_variant_by_key(v, "tpm2-primary-alg");
+ if (w) {
+ /* The primary key algorithm is optional */
+
+ if (!json_variant_is_string(w)) {
+ crypt_log_debug(cd, "TPM2 primary key algorithm is not a string.");
+ return 1;
+ }
+
+ if (tpm2_primary_alg_from_string(json_variant_string(w)) < 0) {
+ crypt_log_debug(cd, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w));
+ return 1;
+ }
+ }
+
+ w = json_variant_by_key(v, "tpm2-blob");
+ if (!w || !json_variant_is_string(w)) {
+ crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-blob' field.");
+ return 1;
+ }
+
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
+
+ w = json_variant_by_key(v, "tpm2-policy-hash");
+ if (!w || !json_variant_is_string(w)) {
+ crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-policy-hash' field.");
+ return 1;
+ }
+
+ r = unhexmem(json_variant_string(w), SIZE_MAX, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
+
+ w = json_variant_by_key(v, "tpm2-pin");
+ if (w) {
+ if (!json_variant_is_boolean(w)) {
+ crypt_log_debug(cd, "TPM2 PIN policy is not a boolean.");
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c
new file mode 100644
index 0000000..e305d8b
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "cryptsetup-token-util.h"
+#include "string-util.h"
+
+int crypt_dump_buffer_to_hex_string(
+ const char *buf,
+ size_t buf_size,
+ char **ret_dump_str) {
+
+ int r;
+ _cleanup_free_ char *dump_str = NULL;
+
+ assert(buf || !buf_size);
+ assert(ret_dump_str);
+
+ for (size_t i = 0; i < buf_size; i++) {
+ /* crypt_dump() breaks line after every
+ * 16th couple of chars in dumped hexstring */
+ r = strextendf_with_separator(
+ &dump_str,
+ (i && !(i % 16)) ? CRYPT_DUMP_LINE_SEP : " ",
+ "%02hhx", buf[i]);
+ if (r < 0)
+ return r;
+ }
+
+ *ret_dump_str = TAKE_PTR(dump_str);
+
+ return 0;
+}
+
+int crypt_dump_hex_string(const char *hex_str, char **ret_dump_str) {
+
+ int r;
+ size_t len;
+ _cleanup_free_ char *dump_str = NULL;
+
+ assert(hex_str);
+ assert(ret_dump_str);
+
+ len = strlen(hex_str) >> 1;
+
+ for (size_t i = 0; i < len; i++) {
+ /* crypt_dump() breaks line after every
+ * 16th couple of chars in dumped hexstring */
+ r = strextendf_with_separator(
+ &dump_str,
+ (i && !(i % 16)) ? CRYPT_DUMP_LINE_SEP : " ",
+ "%.2s", hex_str + (i<<1));
+ if (r < 0)
+ return r;
+ }
+
+ *ret_dump_str = TAKE_PTR(dump_str);
+
+ return 0;
+}
+
+int crypt_normalize_pin(const void *pin, size_t pin_size, char **ret_pin_string) {
+
+ _cleanup_free_ char *pin_string = NULL;
+
+ assert(pin || !pin_size);
+ assert(ret_pin_string);
+
+ if (!pin) {
+ *ret_pin_string = NULL;
+ return 0;
+ }
+
+ /* Refuse embedded NULL bytes, but allow trailing NULL */
+ if (memchr(pin, 0, pin_size - 1))
+ return -EINVAL;
+
+ /* Enforce trailing NULL byte if missing */
+ pin_string = memdup_suffix0(pin, pin_size);
+ if (!pin_string)
+ return -ENOMEM;
+
+ *ret_pin_string = TAKE_PTR(pin_string);
+
+ return 0;
+}
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h
new file mode 100644
index 0000000..146beff
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <libcryptsetup.h>
+
+/* crypt_dump() internal indentation magic */
+#define CRYPT_DUMP_LINE_SEP "\n\t "
+
+#define crypt_log_debug(cd, ...) crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__)
+#define crypt_log_error(cd, ...) crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__)
+#define crypt_log_verbose(cd, ...) crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__)
+#define crypt_log(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__)
+
+#define crypt_log_full_errno(cd, e, lvl, ...) ({ \
+ int _e = abs(e), _s = errno; \
+ errno = _e; \
+ crypt_logf(cd, lvl, __VA_ARGS__); \
+ errno = _s; \
+ -_e; \
+})
+
+#define crypt_log_debug_errno(cd, e, ...) \
+ crypt_log_full_errno(cd, e, CRYPT_LOG_DEBUG, __VA_ARGS__)
+
+#define crypt_log_error_errno(cd, e, ...) \
+ crypt_log_full_errno(cd, e, CRYPT_LOG_ERROR, __VA_ARGS__)
+
+#define crypt_log_oom(cd) crypt_log_error_errno(cd, ENOMEM, "Not enough memory.")
+
+int crypt_dump_buffer_to_hex_string(
+ const char *buf,
+ size_t buf_size,
+ char **ret_dump_str);
+
+int crypt_dump_hex_string(const char *hex_str, char **ret_dump_str);
+
+int crypt_normalize_pin(const void *pin, size_t pin_size, char **ret_pin_string);
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h
new file mode 100644
index 0000000..2a9d23f
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/* for more information see libcryptsetup.h crypt-tokens section */
+
+const char *cryptsetup_token_version(void);
+
+int cryptsetup_token_open(struct crypt_device *cd, int token,
+ char **password, size_t *password_len, void *usrptr);
+
+int cryptsetup_token_open_pin(struct crypt_device *cd, int token,
+ const char *pin, size_t pin_size,
+ char **password, size_t *password_len, void *usrptr);
+
+void cryptsetup_token_dump(struct crypt_device *cd, const char *json);
+
+int cryptsetup_token_validate(struct crypt_device *cd, const char *json);
+
+void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len);
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym
new file mode 100644
index 0000000..730e78e
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym
@@ -0,0 +1,19 @@
+/***
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+ 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.
+***/
+
+CRYPTSETUP_TOKEN_1.0 {
+global:
+ cryptsetup_token_open;
+ cryptsetup_token_open_pin;
+ cryptsetup_token_buffer_free;
+ cryptsetup_token_validate;
+ cryptsetup_token_dump;
+ cryptsetup_token_version;
+local: *;
+};
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c
new file mode 100644
index 0000000..a0e1ccb
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <libcryptsetup.h>
+
+#include "cryptsetup-token-util.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "luks2-fido2.h"
+#include "memory-util.h"
+#include "strv.h"
+
+int acquire_luks2_key(
+ struct crypt_device *cd,
+ const char *json,
+ const char *device,
+ const char *pin,
+ char **ret_keyslot_passphrase,
+ size_t *ret_keyslot_passphrase_size) {
+
+ int r;
+ Fido2EnrollFlags required;
+ size_t cid_size, salt_size, decrypted_key_size;
+ _cleanup_free_ void *cid = NULL, *salt = NULL;
+ _cleanup_free_ char *rp_id = NULL;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(erase_and_freep) char *base64_encoded = NULL;
+ _cleanup_strv_free_erase_ char **pins = NULL;
+
+ assert(ret_keyslot_passphrase);
+ assert(ret_keyslot_passphrase_size);
+
+ r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required);
+ if (r < 0)
+ return r;
+
+ if (pin) {
+ pins = strv_new(pin);
+ if (!pins)
+ return crypt_log_oom(cd);
+ }
+
+ /* configured to use pin but none was provided */
+ if ((required & FIDO2ENROLL_PIN) && strv_isempty(pins))
+ return -ENOANO;
+
+ r = fido2_use_hmac_hash(
+ device,
+ rp_id ?: "io.systemd.cryptsetup",
+ salt, salt_size,
+ cid, cid_size,
+ pins,
+ required,
+ &decrypted_key,
+ &decrypted_key_size);
+ if (r == -ENOLCK) /* libcryptsetup returns -ENOANO also on wrong PIN */
+ r = -ENOANO;
+ if (r < 0)
+ return r;
+
+ /* Before using this key as passphrase we base64 encode it, for compat with homed */
+ r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (r < 0)
+ return crypt_log_error_errno(cd, r, "Failed to base64 encode key: %m");
+
+ *ret_keyslot_passphrase = TAKE_PTR(base64_encoded);
+ *ret_keyslot_passphrase_size = strlen(*ret_keyslot_passphrase);
+
+ return 0;
+}
+
+/* this function expects valid "systemd-fido2" in json */
+int parse_luks2_fido2_data(
+ struct crypt_device *cd,
+ const char *json,
+ char **ret_rp_id,
+ void **ret_salt,
+ size_t *ret_salt_size,
+ void **ret_cid,
+ size_t *ret_cid_size,
+ Fido2EnrollFlags *ret_required) {
+
+ _cleanup_free_ void *cid = NULL, *salt = NULL;
+ size_t cid_size = 0, salt_size = 0;
+ _cleanup_free_ char *rp = NULL;
+ int r;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ JsonVariant *w;
+ Fido2EnrollFlags required = 0;
+
+ assert(json);
+ assert(ret_rp_id);
+ assert(ret_salt);
+ assert(ret_salt_size);
+ assert(ret_cid);
+ assert(ret_cid_size);
+ assert(ret_required);
+
+ r = json_parse(json, 0, &v, NULL, NULL);
+ if (r < 0)
+ return crypt_log_error_errno(cd, r, "Failed to parse JSON token data: %m");
+
+ w = json_variant_by_key(v, "fido2-credential");
+ if (!w)
+ return -EINVAL;
+
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size);
+ if (r < 0)
+ return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-credentials' field: %m");
+
+ w = json_variant_by_key(v, "fido2-salt");
+ if (!w)
+ return -EINVAL;
+
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size);
+ if (r < 0)
+ return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-salt' field: %m");
+
+ w = json_variant_by_key(v, "fido2-rp");
+ if (w) {
+ /* The "rp" field is optional. */
+ rp = strdup(json_variant_string(w));
+ if (!rp) {
+ crypt_log_error(cd, "Not enough memory.");
+ return -ENOMEM;
+ }
+ }
+
+ w = json_variant_by_key(v, "fido2-clientPin-required");
+ if (w)
+ /* The "fido2-clientPin-required" field is optional. */
+ SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w));
+ else
+ required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */
+
+ w = json_variant_by_key(v, "fido2-up-required");
+ if (w)
+ /* The "fido2-up-required" field is optional. */
+ SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w));
+ else
+ required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */
+
+ w = json_variant_by_key(v, "fido2-uv-required");
+ if (w)
+ /* The "fido2-uv-required" field is optional. */
+ SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w));
+ else
+ required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */
+
+ *ret_rp_id = TAKE_PTR(rp);
+ *ret_cid = TAKE_PTR(cid);
+ *ret_cid_size = cid_size;
+ *ret_salt = TAKE_PTR(salt);
+ *ret_salt_size = salt_size;
+ *ret_required = required;
+
+ return 0;
+}
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h
new file mode 100644
index 0000000..48416ec
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "libfido2-util.h"
+
+struct crypt_device;
+
+int acquire_luks2_key(
+ struct crypt_device *cd,
+ const char *json,
+ const char *device,
+ const char *pin,
+ char **ret_keyslot_passphrase,
+ size_t *ret_keyslot_passphrase_size);
+
+int parse_luks2_fido2_data(
+ struct crypt_device *cd,
+ const char *json,
+ char **ret_rp_id,
+ void **ret_salt,
+ size_t *ret_salt_size,
+ void **ret_cid,
+ size_t *ret_cid_size,
+ Fido2EnrollFlags *ret_required);
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c
new file mode 100644
index 0000000..885ba9d
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c
@@ -0,0 +1,271 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <p11-kit/p11-kit.h>
+#include <p11-kit/uri.h>
+
+#include "cryptsetup-token-util.h"
+#include "escape.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "luks2-pkcs11.h"
+#include "memory-util.h"
+#include "pkcs11-util.h"
+#include "time-util.h"
+
+struct luks2_pkcs11_callback_data {
+ struct crypt_device *cd;
+ const char *pin;
+ size_t pin_size;
+ void *encrypted_key;
+ size_t encrypted_key_size;
+ void *decrypted_key;
+ size_t decrypted_key_size;
+};
+
+static int luks2_pkcs11_callback(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_SLOT_ID slot_id,
+ const CK_SLOT_INFO *slot_info,
+ const CK_TOKEN_INFO *token_info,
+ P11KitUri *uri,
+ void *userdata) {
+
+ CK_OBJECT_HANDLE object;
+ CK_RV rv;
+ CK_TOKEN_INFO updated_token_info;
+ int r;
+ _cleanup_free_ char *token_label = NULL;
+ struct luks2_pkcs11_callback_data *data = ASSERT_PTR(userdata);
+
+ assert(m);
+ assert(slot_info);
+ assert(token_info);
+ assert(uri);
+
+ token_label = pkcs11_token_label(token_info);
+ if (!token_label)
+ return -ENOMEM;
+
+ /* Called for every token matching our URI */
+ r = pkcs11_token_login_by_pin(m, session, token_info, token_label, data->pin, data->pin_size);
+ if (r == -ENOLCK) {
+ /* Referesh the token info, so that we can prompt knowing the new flags if they changed. */
+ rv = m->C_GetTokenInfo(slot_id, &updated_token_info);
+ if (rv != CKR_OK) {
+ crypt_log_error(data->cd,
+ "Failed to acquire updated security token information for slot %lu: %s",
+ slot_id, p11_kit_strerror(rv));
+ return -EIO;
+ }
+ token_info = &updated_token_info;
+ r = -ENOANO;
+ }
+
+ if (r == -ENOANO) {
+ if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY))
+ crypt_log_error(data->cd, "Please enter correct PIN for security token "
+ "'%s' in order to unlock it (final try).", token_label);
+ else if (FLAGS_SET(token_info->flags, CKF_USER_PIN_COUNT_LOW))
+ crypt_log_error(data->cd, "PIN has been entered incorrectly previously, "
+ "please enter correct PIN for security token '%s' in order to unlock it.",
+ token_label);
+ }
+
+ if (r == -EPERM) /* pin is locked, but map it to -ENOANO anyway */
+ r = -ENOANO;
+
+ if (r < 0)
+ return r;
+
+ r = pkcs11_token_find_private_key(m, session, uri, &object);
+ if (r < 0)
+ return r;
+
+ r = pkcs11_token_decrypt_data(
+ m,
+ session,
+ object,
+ data->encrypted_key,
+ data->encrypted_key_size,
+ &data->decrypted_key,
+ &data->decrypted_key_size);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static void luks2_pkcs11_callback_data_release(struct luks2_pkcs11_callback_data *data) {
+ erase_and_free(data->decrypted_key);
+}
+
+static int acquire_luks2_key_by_pin(
+ struct crypt_device *cd,
+ const char *pkcs11_uri,
+ const void *pin,
+ size_t pin_size,
+ void *encrypted_key,
+ size_t encrypted_key_size,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size) {
+
+ int r;
+ _cleanup_(luks2_pkcs11_callback_data_release) struct luks2_pkcs11_callback_data data = {
+ .cd = cd,
+ .pin = pin,
+ .pin_size = pin_size,
+ .encrypted_key = encrypted_key,
+ .encrypted_key_size = encrypted_key_size,
+ };
+
+ assert(pkcs11_uri);
+ assert(encrypted_key);
+ assert(ret_decrypted_key);
+ assert(ret_decrypted_key_size);
+
+ r = pkcs11_find_token(pkcs11_uri, luks2_pkcs11_callback, &data);
+ if (r < 0)
+ return r;
+
+ *ret_decrypted_key = TAKE_PTR(data.decrypted_key);
+ *ret_decrypted_key_size = data.decrypted_key_size;
+
+ return 0;
+}
+
+/* called from within systemd utilities */
+static int acquire_luks2_key_systemd(
+ const char *pkcs11_uri,
+ systemd_pkcs11_plugin_params *params,
+ void *encrypted_key,
+ size_t encrypted_key_size,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size) {
+
+ int r;
+ _cleanup_(pkcs11_crypt_device_callback_data_release) pkcs11_crypt_device_callback_data data = {
+ .encrypted_key = encrypted_key,
+ .encrypted_key_size = encrypted_key_size,
+ .free_encrypted_key = false
+ };
+
+ assert(pkcs11_uri);
+ assert(encrypted_key);
+ assert(ret_decrypted_key);
+ assert(ret_decrypted_key_size);
+ assert(params);
+
+ data.friendly_name = params->friendly_name;
+ data.headless = params->headless;
+ data.askpw_flags = params->askpw_flags;
+ data.until = params->until;
+
+ /* The functions called here log about all errors, except for EAGAIN which means "token not found right now" */
+ r = pkcs11_find_token(pkcs11_uri, pkcs11_crypt_device_callback, &data);
+ if (r < 0)
+ return r;
+
+ *ret_decrypted_key = TAKE_PTR(data.decrypted_key);
+ *ret_decrypted_key_size = data.decrypted_key_size;
+
+ return 0;
+}
+
+int acquire_luks2_key(
+ struct crypt_device *cd,
+ const char *json,
+ void *userdata,
+ const void *pin,
+ size_t pin_size,
+ char **ret_password,
+ size_t *ret_password_size) {
+
+ int r;
+ size_t decrypted_key_size, encrypted_key_size;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(erase_and_freep) char *base64_encoded = NULL;
+ _cleanup_free_ char *pkcs11_uri = NULL;
+ _cleanup_free_ void *encrypted_key = NULL;
+ systemd_pkcs11_plugin_params *pkcs11_params = userdata;
+
+ assert(json);
+ assert(ret_password);
+ assert(ret_password_size);
+
+ r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &encrypted_key, &encrypted_key_size);
+ if (r < 0)
+ return r;
+
+ if (pkcs11_params && pin)
+ crypt_log_verbose(cd, "PIN parameter ignored in interactive mode.");
+
+ if (pkcs11_params) /* systemd based activation with interactive pin query callbacks */
+ r = acquire_luks2_key_systemd(
+ pkcs11_uri,
+ pkcs11_params,
+ encrypted_key, encrypted_key_size,
+ &decrypted_key, &decrypted_key_size);
+ else /* default activation that provides single PIN if needed */
+ r = acquire_luks2_key_by_pin(
+ cd, pkcs11_uri, pin, pin_size,
+ encrypted_key, encrypted_key_size,
+ &decrypted_key, &decrypted_key_size);
+ if (r < 0)
+ return r;
+
+ r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (r < 0)
+ return crypt_log_error_errno(cd, r, "Can not base64 encode key: %m");
+
+ *ret_password = TAKE_PTR(base64_encoded);
+ *ret_password_size = strlen(*ret_password);
+
+ return 0;
+}
+
+int parse_luks2_pkcs11_data(
+ struct crypt_device *cd,
+ const char *json,
+ char **ret_uri,
+ void **ret_encrypted_key,
+ size_t *ret_encrypted_key_size) {
+
+ int r;
+ size_t key_size;
+ _cleanup_free_ char *uri = NULL;
+ _cleanup_free_ void *key = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ JsonVariant *w;
+
+ assert(json);
+ assert(ret_uri);
+ assert(ret_encrypted_key);
+ assert(ret_encrypted_key_size);
+
+ r = json_parse(json, 0, &v, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ w = json_variant_by_key(v, "pkcs11-uri");
+ if (!w)
+ return -EINVAL;
+
+ uri = strdup(json_variant_string(w));
+ if (!uri)
+ return -ENOMEM;
+
+ w = json_variant_by_key(v, "pkcs11-key");
+ if (!w)
+ return -EINVAL;
+
+ r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m.");
+
+ *ret_uri = TAKE_PTR(uri);
+ *ret_encrypted_key = TAKE_PTR(key);
+ *ret_encrypted_key_size = key_size;
+
+ return 0;
+}
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h
new file mode 100644
index 0000000..41ce9f0
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#pragma once
+
+struct crypt_device;
+
+int acquire_luks2_key(
+ struct crypt_device *cd,
+ const char *json,
+ void *userdata,
+ const void *pin,
+ size_t pin_size,
+ char **password,
+ size_t *password_size);
+
+int parse_luks2_pkcs11_data(
+ struct crypt_device *cd,
+ const char *json,
+ char **ret_uri,
+ void **ret_encrypted_key,
+ size_t *ret_encrypted_key_size);
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
new file mode 100644
index 0000000..be496d4
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "ask-password-api.h"
+#include "env-util.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "log.h"
+#include "luks2-tpm2.h"
+#include "parse-util.h"
+#include "random-util.h"
+#include "strv.h"
+#include "tpm2-util.h"
+
+int acquire_luks2_key(
+ const char *device,
+ uint32_t hash_pcr_mask,
+ uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ const char *pin,
+ uint16_t primary_alg,
+ const void *key_data,
+ size_t key_data_size,
+ const void *policy_hash,
+ size_t policy_hash_size,
+ TPM2Flags flags,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size) {
+
+ _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
+ _cleanup_free_ char *auto_device = NULL;
+ int r;
+
+ assert(ret_decrypted_key);
+ assert(ret_decrypted_key_size);
+
+ if (!device) {
+ r = tpm2_find_device_auto(LOG_DEBUG, &auto_device);
+ if (r == -ENODEV)
+ return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */
+ if (r < 0)
+ return r;
+
+ device = auto_device;
+ }
+
+ if ((flags & TPM2_FLAGS_USE_PIN) && !pin)
+ return -ENOANO;
+
+ if (pubkey_pcr_mask != 0) {
+ r = tpm2_load_pcr_signature(signature_path, &signature_json);
+ if (r < 0)
+ return r;
+ }
+
+ return tpm2_unseal(
+ device,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature_json,
+ pin,
+ primary_alg,
+ key_data, key_data_size,
+ policy_hash, policy_hash_size,
+ ret_decrypted_key, ret_decrypted_key_size);
+}
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
new file mode 100644
index 0000000..f362512
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#pragma once
+
+#include "tpm2-util.h"
+
+struct crypt_device;
+
+int acquire_luks2_key(
+ const char *device,
+ uint32_t pcr_mask,
+ uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ const char *pin,
+ uint16_t primary_alg,
+ const void *key_data,
+ size_t key_data_size,
+ const void *policy_hash,
+ size_t policy_hash_size,
+ TPM2Flags flags,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size);
diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build
new file mode 100644
index 0000000..42c29f1
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/meson.build
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+cryptsetup_token_sym = files('cryptsetup-token.sym')
+cryptsetup_token_sym_path = meson.current_source_dir() / 'cryptsetup-token.sym'
+
+lib_cryptsetup_token_common = static_library(
+ 'cryptsetup-token-common',
+ 'cryptsetup-token.h',
+ 'cryptsetup-token-util.h',
+ 'cryptsetup-token-util.c',
+ include_directories : includes,
+ link_with : libshared,
+ build_by_default : false)
+
+cryptsetup_token_systemd_tpm2_sources = files(
+ 'cryptsetup-token-systemd-tpm2.c',
+ 'luks2-tpm2.c',
+ 'luks2-tpm2.h')
+
+cryptsetup_token_systemd_fido2_sources = files(
+ 'cryptsetup-token-systemd-fido2.c',
+ 'luks2-fido2.c',
+ 'luks2-fido2.h')
+
+cryptsetup_token_systemd_pkcs11_sources = files(
+ 'cryptsetup-token-systemd-pkcs11.c',
+ 'luks2-pkcs11.c',
+ 'luks2-pkcs11.h')
diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c
new file mode 100644
index 0000000..4f14c36
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tpm2.c
@@ -0,0 +1,256 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "ask-password-api.h"
+#include "cryptsetup-tpm2.h"
+#include "env-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "parse-util.h"
+#include "random-util.h"
+#include "tpm2-util.h"
+
+static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_pin_str) {
+ _cleanup_(erase_and_freep) char *pin_str = NULL;
+ _cleanup_strv_free_erase_ char **pin = NULL;
+ int r;
+
+ assert(ret_pin_str);
+
+ r = getenv_steal_erase("PIN", &pin_str);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire PIN from environment: %m");
+ if (!r) {
+ if (headless)
+ return log_error_errno(
+ SYNTHETIC_ERRNO(ENOPKG),
+ "PIN querying disabled via 'headless' option. "
+ "Use the '$PIN' environment variable.");
+
+ pin = strv_free_erase(pin);
+ r = ask_password_auto(
+ "Please enter TPM2 PIN:",
+ "drive-harddisk",
+ NULL,
+ "tpm2-pin",
+ "cryptsetup.tpm2-pin",
+ until,
+ ask_password_flags,
+ &pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to ask for user pin: %m");
+ assert(strv_length(pin) == 1);
+
+ pin_str = strdup(pin[0]);
+ if (!pin_str)
+ return log_oom();
+ }
+
+ *ret_pin_str = TAKE_PTR(pin_str);
+
+ return r;
+}
+
+int acquire_tpm2_key(
+ const char *volume_name,
+ const char *device,
+ uint32_t hash_pcr_mask,
+ uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ uint16_t primary_alg,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data,
+ size_t key_data_size,
+ const void *policy_hash,
+ size_t policy_hash_size,
+ TPM2Flags flags,
+ usec_t until,
+ bool headless,
+ AskPasswordFlags ask_password_flags,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size) {
+
+ _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
+ _cleanup_free_ void *loaded_blob = NULL;
+ _cleanup_free_ char *auto_device = NULL;
+ size_t blob_size;
+ const void *blob;
+ int r;
+
+ if (!device) {
+ r = tpm2_find_device_auto(LOG_DEBUG, &auto_device);
+ if (r == -ENODEV)
+ return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */
+ if (r < 0)
+ return r;
+
+ device = auto_device;
+ }
+
+ if (key_data) {
+ blob = key_data;
+ blob_size = key_data_size;
+ } else {
+ _cleanup_free_ char *bindname = NULL;
+
+ /* If we read the salt via AF_UNIX, make this client recognizable */
+ if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0)
+ return log_oom();
+
+ r = read_full_file_full(
+ AT_FDCWD, key_file,
+ key_file_offset == 0 ? UINT64_MAX : key_file_offset,
+ key_file_size == 0 ? SIZE_MAX : key_file_size,
+ READ_FULL_FILE_CONNECT_SOCKET,
+ bindname,
+ (char**) &loaded_blob, &blob_size);
+ if (r < 0)
+ return r;
+
+ blob = loaded_blob;
+ }
+
+ if (pubkey_pcr_mask != 0) {
+ r = tpm2_load_pcr_signature(signature_path, &signature_json);
+ if (r < 0)
+ return r;
+ }
+
+ if (!(flags & TPM2_FLAGS_USE_PIN))
+ return tpm2_unseal(
+ device,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature_json,
+ /* pin= */ NULL,
+ primary_alg,
+ blob,
+ blob_size,
+ policy_hash,
+ policy_hash_size,
+ ret_decrypted_key,
+ ret_decrypted_key_size);
+
+ for (int i = 5;; i--) {
+ _cleanup_(erase_and_freep) char *pin_str = NULL;
+
+ if (i <= 0)
+ return -EACCES;
+
+ r = get_pin(until, ask_password_flags, headless, &pin_str);
+ if (r < 0)
+ return r;
+
+ r = tpm2_unseal(device,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature_json,
+ pin_str,
+ primary_alg,
+ blob,
+ blob_size,
+ policy_hash,
+ policy_hash_size,
+ ret_decrypted_key,
+ ret_decrypted_key_size);
+ /* We get this error in case there is an authentication policy mismatch. This should
+ * not happen, but this avoids confusing behavior, just in case. */
+ if (IN_SET(r, -EPERM, -ENOLCK))
+ return r;
+ if (r < 0)
+ continue;
+
+ return r;
+ }
+}
+
+int find_tpm2_auto_data(
+ struct crypt_device *cd,
+ uint32_t search_pcr_mask,
+ int start_token,
+ uint32_t *ret_hash_pcr_mask,
+ uint16_t *ret_pcr_bank,
+ void **ret_pubkey,
+ size_t *ret_pubkey_size,
+ uint32_t *ret_pubkey_pcr_mask,
+ uint16_t *ret_primary_alg,
+ void **ret_blob,
+ size_t *ret_blob_size,
+ void **ret_policy_hash,
+ size_t *ret_policy_hash_size,
+ TPM2Flags *ret_flags,
+ int *ret_keyslot,
+ int *ret_token) {
+
+ int r, token;
+
+ assert(cd);
+
+ for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ size_t blob_size, policy_hash_size, pubkey_size;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
+ uint16_t pcr_bank, primary_alg;
+ TPM2Flags flags;
+ int keyslot;
+
+ r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
+ if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
+ continue;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read JSON token data off disk: %m");
+
+ r = tpm2_parse_luks2_json(
+ v,
+ &keyslot,
+ &hash_pcr_mask,
+ &pcr_bank,
+ &pubkey, &pubkey_size,
+ &pubkey_pcr_mask,
+ &primary_alg,
+ &blob, &blob_size,
+ &policy_hash, &policy_hash_size,
+ &flags);
+ if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
+ continue;
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse TPM2 JSON data: %m");
+
+ if (search_pcr_mask == UINT32_MAX ||
+ search_pcr_mask == hash_pcr_mask) {
+
+ if (start_token <= 0)
+ log_info("Automatically discovered security TPM2 token unlocks volume.");
+
+ *ret_hash_pcr_mask = hash_pcr_mask;
+ *ret_pcr_bank = pcr_bank;
+ *ret_pubkey = TAKE_PTR(pubkey);
+ *ret_pubkey_size = pubkey_size;
+ *ret_pubkey_pcr_mask = pubkey_pcr_mask;
+ *ret_primary_alg = primary_alg;
+ *ret_blob = TAKE_PTR(blob);
+ *ret_blob_size = blob_size;
+ *ret_policy_hash = TAKE_PTR(policy_hash);
+ *ret_policy_hash_size = policy_hash_size;
+ *ret_keyslot = keyslot;
+ *ret_token = token;
+ *ret_flags = flags;
+ return 0;
+ }
+
+ /* PCR mask doesn't match what is configured, ignore this entry, let's see next */
+ }
+
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found.");
+}
diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h
new file mode 100644
index 0000000..a34eb84
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tpm2.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#include "ask-password-api.h"
+#include "cryptsetup-util.h"
+#include "log.h"
+#include "time-util.h"
+#include "tpm2-util.h"
+
+#if HAVE_TPM2
+
+int acquire_tpm2_key(
+ const char *volume_name,
+ const char *device,
+ uint32_t hash_pcr_mask,
+ uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ uint16_t primary_alg,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data,
+ size_t key_data_size,
+ const void *policy_hash,
+ size_t policy_hash_size,
+ TPM2Flags flags,
+ usec_t until,
+ bool headless,
+ AskPasswordFlags ask_password_flags,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size);
+
+int find_tpm2_auto_data(
+ struct crypt_device *cd,
+ uint32_t search_pcr_mask,
+ int start_token,
+ uint32_t *ret_hash_pcr_mask,
+ uint16_t *ret_pcr_bank,
+ void **ret_pubkey,
+ size_t *ret_pubkey_size,
+ uint32_t *ret_pubkey_pcr_mask,
+ uint16_t *ret_primary_alg,
+ void **ret_blob,
+ size_t *ret_blob_size,
+ void **ret_policy_hash,
+ size_t *ret_policy_hash_size,
+ TPM2Flags *ret_flags,
+ int *ret_keyslot,
+ int *ret_token);
+
+#else
+
+static inline int acquire_tpm2_key(
+ const char *volume_name,
+ const char *device,
+ uint32_t hash_pcr_mask,
+ uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ uint16_t primary_alg,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const void *key_data,
+ size_t key_data_size,
+ const void *policy_hash,
+ size_t policy_hash_size,
+ TPM2Flags flags,
+ usec_t until,
+ bool headless,
+ AskPasswordFlags ask_password_flags,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size) {
+
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "TPM2 support not available.");
+}
+
+static inline int find_tpm2_auto_data(
+ struct crypt_device *cd,
+ uint32_t search_pcr_mask,
+ int start_token,
+ uint32_t *ret_hash_pcr_mask,
+ uint16_t *ret_pcr_bank,
+ void **ret_pubkey,
+ size_t *ret_pubkey_size,
+ uint32_t *ret_pubkey_pcr_mask,
+ uint16_t *ret_primary_alg,
+ void **ret_blob,
+ size_t *ret_blob_size,
+ void **ret_policy_hash,
+ size_t *ret_policy_hash_size,
+ TPM2Flags *ret_flags,
+ int *ret_keyslot,
+ int *ret_token) {
+
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "TPM2 support not available.");
+}
+
+#endif
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
new file mode 100644
index 0000000..268ea28
--- /dev/null
+++ b/src/cryptsetup/cryptsetup.c
@@ -0,0 +1,2134 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <mntent.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "ask-password-api.h"
+#include "cryptsetup-fido2.h"
+#include "cryptsetup-keyfile.h"
+#include "cryptsetup-pkcs11.h"
+#include "cryptsetup-tpm2.h"
+#include "cryptsetup-util.h"
+#include "device-util.h"
+#include "efi-api.h"
+#include "env-util.h"
+#include "escape.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "fstab-util.h"
+#include "hexdecoct.h"
+#include "libfido2-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "memory-util.h"
+#include "mount-util.h"
+#include "nulstr-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "pkcs11-util.h"
+#include "pretty-print.h"
+#include "process-util.h"
+#include "random-util.h"
+#include "string-table.h"
+#include "strv.h"
+#include "tpm2-util.h"
+
+/* internal helper */
+#define ANY_LUKS "LUKS"
+/* as in src/cryptsetup.h */
+#define CRYPT_SECTOR_SIZE 512U
+#define CRYPT_MAX_SECTOR_SIZE 4096U
+
+typedef enum PassphraseType {
+ PASSPHRASE_NONE,
+ PASSPHRASE_REGULAR = 1 << 0,
+ PASSPHRASE_RECOVERY_KEY = 1 << 1,
+ PASSPHRASE_BOTH = PASSPHRASE_REGULAR|PASSPHRASE_RECOVERY_KEY,
+ _PASSPHRASE_TYPE_MAX,
+ _PASSPHRASE_TYPE_INVALID = -1,
+} PassphraseType;
+
+static const char *arg_type = NULL; /* ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2, CRYPT_TCRYPT, CRYPT_BITLK or CRYPT_PLAIN */
+static char *arg_cipher = NULL;
+static unsigned arg_key_size = 0;
+static unsigned arg_sector_size = CRYPT_SECTOR_SIZE;
+static int arg_key_slot = CRYPT_ANY_SLOT;
+static unsigned arg_keyfile_size = 0;
+static uint64_t arg_keyfile_offset = 0;
+static bool arg_keyfile_erase = false;
+static bool arg_try_empty_password = false;
+static char *arg_hash = NULL;
+static char *arg_header = NULL;
+static unsigned arg_tries = 3;
+static bool arg_readonly = false;
+static bool arg_verify = false;
+static AskPasswordFlags arg_ask_password_flags = 0;
+static bool arg_discards = false;
+static bool arg_same_cpu_crypt = false;
+static bool arg_submit_from_crypt_cpus = false;
+static bool arg_no_read_workqueue = false;
+static bool arg_no_write_workqueue = false;
+static bool arg_tcrypt_hidden = false;
+static bool arg_tcrypt_system = false;
+static bool arg_tcrypt_veracrypt = false;
+static char **arg_tcrypt_keyfiles = NULL;
+static uint64_t arg_offset = 0;
+static uint64_t arg_skip = 0;
+static usec_t arg_timeout = USEC_INFINITY;
+static char *arg_pkcs11_uri = NULL;
+static bool arg_pkcs11_uri_auto = false;
+static char *arg_fido2_device = NULL;
+static bool arg_fido2_device_auto = false;
+static void *arg_fido2_cid = NULL;
+static size_t arg_fido2_cid_size = 0;
+static char *arg_fido2_rp_id = NULL;
+static char *arg_tpm2_device = NULL;
+static bool arg_tpm2_device_auto = false;
+static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
+static char *arg_tpm2_signature = NULL;
+static bool arg_tpm2_pin = false;
+static bool arg_headless = false;
+static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
+
+STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_header, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tcrypt_keyfiles, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_uri, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
+
+static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = {
+ [PASSPHRASE_REGULAR] = "passphrase",
+ [PASSPHRASE_RECOVERY_KEY] = "recovery key",
+ [PASSPHRASE_BOTH] = "passphrase or recovery key",
+};
+
+const char* passphrase_type_to_string(PassphraseType t);
+PassphraseType passphrase_type_from_string(const char *s);
+
+DEFINE_STRING_TABLE_LOOKUP(passphrase_type, PassphraseType);
+
+/* Options Debian's crypttab knows we don't:
+
+ check=
+ checkargs=
+ noearly
+ loud
+ quiet
+ keyscript=
+ initramfs
+*/
+
+static int parse_one_option(const char *option) {
+ const char *val;
+ int r;
+
+ assert(option);
+
+ /* Handled outside of this tool */
+ if (STR_IN_SET(option, "noauto", "auto", "nofail", "fail", "_netdev", "keyfile-timeout"))
+ return 0;
+
+ if (startswith(option, "keyfile-timeout="))
+ return 0;
+
+ if ((val = startswith(option, "cipher="))) {
+ r = free_and_strdup(&arg_cipher, val);
+ if (r < 0)
+ return log_oom();
+
+ } else if ((val = startswith(option, "size="))) {
+
+ r = safe_atou(val, &arg_key_size);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ if (arg_key_size % 8) {
+ log_error("size= not a multiple of 8, ignoring.");
+ return 0;
+ }
+
+ arg_key_size /= 8;
+
+ } else if ((val = startswith(option, "sector-size="))) {
+
+ r = safe_atou(val, &arg_sector_size);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ if (arg_sector_size % 2) {
+ log_error("sector-size= not a multiple of 2, ignoring.");
+ return 0;
+ }
+
+ if (arg_sector_size < CRYPT_SECTOR_SIZE || arg_sector_size > CRYPT_MAX_SECTOR_SIZE) {
+ log_error("sector-size= is outside of %u and %u, ignoring.", CRYPT_SECTOR_SIZE, CRYPT_MAX_SECTOR_SIZE);
+ return 0;
+ }
+
+ } else if ((val = startswith(option, "key-slot=")) ||
+ (val = startswith(option, "keyslot="))) {
+
+ arg_type = ANY_LUKS;
+ r = safe_atoi(val, &arg_key_slot);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ } else if ((val = startswith(option, "tcrypt-keyfile="))) {
+
+ arg_type = CRYPT_TCRYPT;
+ if (path_is_absolute(val)) {
+ if (strv_extend(&arg_tcrypt_keyfiles, val) < 0)
+ return log_oom();
+ } else
+ log_error("Key file path \"%s\" is not absolute. Ignoring.", val);
+
+ } else if ((val = startswith(option, "keyfile-size="))) {
+
+ r = safe_atou(val, &arg_keyfile_size);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ } else if ((val = startswith(option, "keyfile-offset="))) {
+
+ r = safe_atou64(val, &arg_keyfile_offset);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ } else if ((val = startswith(option, "keyfile-erase="))) {
+
+ r = parse_boolean(val);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ arg_keyfile_erase = r;
+
+ } else if (streq(option, "keyfile-erase"))
+ arg_keyfile_erase = true;
+
+ else if ((val = startswith(option, "hash="))) {
+ r = free_and_strdup(&arg_hash, val);
+ if (r < 0)
+ return log_oom();
+
+ } else if ((val = startswith(option, "header="))) {
+ arg_type = ANY_LUKS;
+
+ if (!path_is_absolute(val))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Header path \"%s\" is not absolute, refusing.", val);
+
+ if (arg_header)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Duplicate header= option, refusing.");
+
+ arg_header = strdup(val);
+ if (!arg_header)
+ return log_oom();
+
+ } else if ((val = startswith(option, "tries="))) {
+
+ r = safe_atou(val, &arg_tries);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ } else if (STR_IN_SET(option, "readonly", "read-only"))
+ arg_readonly = true;
+ else if (streq(option, "verify"))
+ arg_verify = true;
+ else if ((val = startswith(option, "password-echo="))) {
+ if (streq(val, "masked"))
+ arg_ask_password_flags &= ~(ASK_PASSWORD_ECHO|ASK_PASSWORD_SILENT);
+ else {
+ r = parse_boolean(val);
+ if (r < 0) {
+ log_warning_errno(r, "Invalid password-echo= option \"%s\", ignoring.", val);
+ return 0;
+ }
+
+ SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_ECHO, r);
+ SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_SILENT, !r);
+ }
+ } else if (STR_IN_SET(option, "allow-discards", "discard"))
+ arg_discards = true;
+ else if (streq(option, "same-cpu-crypt"))
+ arg_same_cpu_crypt = true;
+ else if (streq(option, "submit-from-crypt-cpus"))
+ arg_submit_from_crypt_cpus = true;
+ else if (streq(option, "no-read-workqueue"))
+ arg_no_read_workqueue = true;
+ else if (streq(option, "no-write-workqueue"))
+ arg_no_write_workqueue = true;
+ else if (streq(option, "luks"))
+ arg_type = ANY_LUKS;
+/* since cryptsetup 2.3.0 (Feb 2020) */
+#ifdef CRYPT_BITLK
+ else if (streq(option, "bitlk"))
+ arg_type = CRYPT_BITLK;
+#endif
+ else if (streq(option, "tcrypt"))
+ arg_type = CRYPT_TCRYPT;
+ else if (STR_IN_SET(option, "tcrypt-hidden", "tcrypthidden")) {
+ arg_type = CRYPT_TCRYPT;
+ arg_tcrypt_hidden = true;
+ } else if (streq(option, "tcrypt-system")) {
+ arg_type = CRYPT_TCRYPT;
+ arg_tcrypt_system = true;
+ } else if (STR_IN_SET(option, "tcrypt-veracrypt", "veracrypt")) {
+ arg_type = CRYPT_TCRYPT;
+ arg_tcrypt_veracrypt = true;
+ } else if (STR_IN_SET(option, "plain", "swap", "tmp") ||
+ startswith(option, "tmp="))
+ arg_type = CRYPT_PLAIN;
+ else if ((val = startswith(option, "timeout="))) {
+
+ r = parse_sec_fix_0(val, &arg_timeout);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ } else if ((val = startswith(option, "offset="))) {
+
+ r = safe_atou64(val, &arg_offset);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s: %m", option);
+
+ } else if ((val = startswith(option, "skip="))) {
+
+ r = safe_atou64(val, &arg_skip);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s: %m", option);
+
+ } else if ((val = startswith(option, "pkcs11-uri="))) {
+
+ if (streq(val, "auto")) {
+ arg_pkcs11_uri = mfree(arg_pkcs11_uri);
+ arg_pkcs11_uri_auto = true;
+ } else {
+ if (!pkcs11_uri_valid(val))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "pkcs11-uri= parameter expects a PKCS#11 URI, refusing");
+
+ r = free_and_strdup(&arg_pkcs11_uri, val);
+ if (r < 0)
+ return log_oom();
+
+ arg_pkcs11_uri_auto = false;
+ }
+
+ } else if ((val = startswith(option, "fido2-device="))) {
+
+ if (streq(val, "auto")) {
+ arg_fido2_device = mfree(arg_fido2_device);
+ arg_fido2_device_auto = true;
+ } else {
+ r = free_and_strdup(&arg_fido2_device, val);
+ if (r < 0)
+ return log_oom();
+
+ arg_fido2_device_auto = false;
+ }
+
+ } else if ((val = startswith(option, "fido2-cid="))) {
+
+ if (streq(val, "auto"))
+ arg_fido2_cid = mfree(arg_fido2_cid);
+ else {
+ _cleanup_free_ void *cid = NULL;
+ size_t cid_size;
+
+ r = unbase64mem(val, SIZE_MAX, &cid, &cid_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decode FIDO2 CID data: %m");
+
+ free(arg_fido2_cid);
+ arg_fido2_cid = TAKE_PTR(cid);
+ arg_fido2_cid_size = cid_size;
+ }
+
+ /* Turn on FIDO2 as side-effect, if not turned on yet. */
+ if (!arg_fido2_device && !arg_fido2_device_auto)
+ arg_fido2_device_auto = true;
+
+ } else if ((val = startswith(option, "fido2-rp="))) {
+
+ r = free_and_strdup(&arg_fido2_rp_id, val);
+ if (r < 0)
+ return log_oom();
+
+ } else if ((val = startswith(option, "tpm2-device="))) {
+
+ if (streq(val, "auto")) {
+ arg_tpm2_device = mfree(arg_tpm2_device);
+ arg_tpm2_device_auto = true;
+ } else {
+ r = free_and_strdup(&arg_tpm2_device, val);
+ if (r < 0)
+ return log_oom();
+
+ arg_tpm2_device_auto = false;
+ }
+
+ } else if ((val = startswith(option, "tpm2-pcrs="))) {
+
+ r = tpm2_parse_pcr_argument(val, &arg_tpm2_pcr_mask);
+ if (r < 0)
+ return r;
+
+ } else if ((val = startswith(option, "tpm2-signature="))) {
+
+ if (!path_is_absolute(val))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "TPM2 signature path \"%s\" is not absolute, refusing.", val);
+
+ r = free_and_strdup(&arg_tpm2_signature, val);
+ if (r < 0)
+ return log_oom();
+
+ } else if ((val = startswith(option, "tpm2-pin="))) {
+
+ r = parse_boolean(val);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ arg_tpm2_pin = r;
+
+ } else if ((val = startswith(option, "try-empty-password="))) {
+
+ r = parse_boolean(val);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ arg_try_empty_password = r;
+
+ } else if (streq(option, "try-empty-password"))
+ arg_try_empty_password = true;
+ else if ((val = startswith(option, "headless="))) {
+
+ r = parse_boolean(val);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ arg_headless = r;
+ } else if (streq(option, "headless"))
+ arg_headless = true;
+
+ else if ((val = startswith(option, "token-timeout="))) {
+
+ r = parse_sec_fix_0(val, &arg_token_timeout_usec);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ } else if (!streq(option, "x-initrd.attach"))
+ log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option);
+
+ return 0;
+}
+
+static int parse_options(const char *options) {
+ assert(options);
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ int r;
+
+ r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse options: %m");
+ if (r == 0)
+ break;
+
+ r = parse_one_option(word);
+ if (r < 0)
+ return r;
+ }
+
+ /* sanity-check options */
+ if (arg_type && !streq(arg_type, CRYPT_PLAIN)) {
+ if (arg_offset != 0)
+ log_warning("offset= ignored with type %s", arg_type);
+ if (arg_skip != 0)
+ log_warning("skip= ignored with type %s", arg_type);
+ }
+
+ return 0;
+}
+
+static char* disk_description(const char *path) {
+ static const char name_fields[] =
+ "DM_NAME\0"
+ "ID_MODEL_FROM_DATABASE\0"
+ "ID_MODEL\0";
+
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ const char *i, *name;
+ struct stat st;
+
+ assert(path);
+
+ if (stat(path, &st) < 0)
+ return NULL;
+
+ if (!S_ISBLK(st.st_mode))
+ return NULL;
+
+ if (sd_device_new_from_stat_rdev(&device, &st) < 0)
+ return NULL;
+
+ if (sd_device_get_property_value(device, "ID_PART_ENTRY_NAME", &name) >= 0) {
+ _cleanup_free_ char *unescaped = NULL;
+ ssize_t l;
+
+ /* ID_PART_ENTRY_NAME uses \x style escaping, using libblkid's blkid_encode_string(). Let's
+ * reverse this here to make the string more human friendly in case people embed spaces or
+ * other weird stuff. */
+
+ l = cunescape(name, UNESCAPE_RELAX, &unescaped);
+ if (l < 0) {
+ log_debug_errno(l, "Failed to unescape ID_PART_ENTRY_NAME, skipping device: %m");
+ return NULL;
+ }
+
+ if (!isempty(unescaped) && !string_has_cc(unescaped, NULL))
+ return TAKE_PTR(unescaped);
+ }
+
+ /* These need no unescaping. */
+ NULSTR_FOREACH(i, name_fields)
+ if (sd_device_get_property_value(device, i, &name) >= 0 &&
+ !isempty(name))
+ return strdup(name);
+
+ return NULL;
+}
+
+static char *disk_mount_point(const char *label) {
+ _cleanup_free_ char *device = NULL;
+ _cleanup_endmntent_ FILE *f = NULL;
+ struct mntent *m;
+
+ /* Yeah, we don't support native systemd unit files here for now */
+
+ device = strjoin("/dev/mapper/", label);
+ if (!device)
+ return NULL;
+
+ f = setmntent(fstab_path(), "re");
+ if (!f)
+ return NULL;
+
+ while ((m = getmntent(f)))
+ if (path_equal(m->mnt_fsname, device))
+ return strdup(m->mnt_dir);
+
+ return NULL;
+}
+
+static char *friendly_disk_name(const char *src, const char *vol) {
+ _cleanup_free_ char *description = NULL, *mount_point = NULL;
+ char *name_buffer = NULL;
+ int r;
+
+ assert(src);
+ assert(vol);
+
+ description = disk_description(src);
+ mount_point = disk_mount_point(vol);
+
+ /* If the description string is simply the volume name, then let's not show this twice */
+ if (description && streq(vol, description))
+ description = mfree(description);
+
+ if (mount_point && description)
+ r = asprintf(&name_buffer, "%s (%s) on %s", description, vol, mount_point);
+ else if (mount_point)
+ r = asprintf(&name_buffer, "%s on %s", vol, mount_point);
+ else if (description)
+ r = asprintf(&name_buffer, "%s (%s)", description, vol);
+ else
+ return strdup(vol);
+ if (r < 0)
+ return NULL;
+
+ return name_buffer;
+}
+
+static PassphraseType check_registered_passwords(struct crypt_device *cd) {
+ _cleanup_free_ bool *slots = NULL;
+ int slot_max;
+ PassphraseType passphrase_type = PASSPHRASE_NONE;
+
+ assert(cd);
+
+ if (!streq_ptr(crypt_get_type(cd), CRYPT_LUKS2)) {
+ log_debug("%s: not a LUKS2 device, only passphrases are supported", crypt_get_device_name(cd));
+ return PASSPHRASE_REGULAR;
+ }
+
+ /* Search all used slots */
+ assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
+ slots = new(bool, slot_max);
+ if (!slots)
+ return log_oom();
+
+ for (int slot = 0; slot < slot_max; slot++)
+ slots[slot] = IN_SET(crypt_keyslot_status(cd, slot), CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST);
+
+ /* Iterate all LUKS2 tokens and keep track of all their slots */
+ for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ const char *type;
+ JsonVariant *w, *z;
+ int tk;
+
+ tk = cryptsetup_get_token_as_json(cd, token, NULL, &v);
+ if (IN_SET(tk, -ENOENT, -EINVAL))
+ continue;
+ if (tk < 0) {
+ log_warning_errno(tk, "Failed to read JSON token data, ignoring: %m");
+ continue;
+ }
+
+ w = json_variant_by_key(v, "type");
+ if (!w || !json_variant_is_string(w)) {
+ log_warning("Token JSON data lacks type field, ignoring.");
+ continue;
+ }
+
+ type = json_variant_string(w);
+ if (STR_IN_SET(type, "systemd-recovery", "systemd-pkcs11", "systemd-fido2", "systemd-tpm2")) {
+
+ /* At least exists one recovery key */
+ if (streq(type, "systemd-recovery"))
+ passphrase_type |= PASSPHRASE_RECOVERY_KEY;
+
+ w = json_variant_by_key(v, "keyslots");
+ if (!w || !json_variant_is_array(w)) {
+ log_warning("Token JSON data lacks keyslots field, ignoring.");
+ continue;
+ }
+
+ JSON_VARIANT_ARRAY_FOREACH(z, w) {
+ unsigned u;
+ int at;
+
+ if (!json_variant_is_string(z)) {
+ log_warning("Token JSON data's keyslot field is not an array of strings, ignoring.");
+ continue;
+ }
+
+ at = safe_atou(json_variant_string(z), &u);
+ if (at < 0) {
+ log_warning_errno(at, "Token JSON data's keyslot field is not an integer formatted as string, ignoring.");
+ continue;
+ }
+
+ if (u >= (unsigned) slot_max) {
+ log_warning_errno(at, "Token JSON data's keyslot field exceeds the maximum value allowed, ignoring.");
+ continue;
+ }
+
+ slots[u] = false;
+ }
+ }
+ }
+
+ /* Check if any of the slots is not referenced by systemd tokens */
+ for (int slot = 0; slot < slot_max; slot++)
+ if (slots[slot]) {
+ passphrase_type |= PASSPHRASE_REGULAR;
+ break;
+ }
+
+ /* All the slots are referenced by systemd tokens, so if a recovery key is not enrolled,
+ * we will not be able to enter a passphrase. */
+ return passphrase_type;
+}
+
+static int get_password(
+ const char *vol,
+ const char *src,
+ usec_t until,
+ bool accept_cached,
+ PassphraseType passphrase_type,
+ char ***ret) {
+
+ _cleanup_free_ char *friendly = NULL, *text = NULL, *disk_path = NULL;
+ _cleanup_strv_free_erase_ char **passwords = NULL;
+ char *id;
+ int r = 0;
+ AskPasswordFlags flags = arg_ask_password_flags | ASK_PASSWORD_PUSH_CACHE;
+
+ assert(vol);
+ assert(src);
+ assert(ret);
+
+ if (arg_headless)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Password querying disabled via 'headless' option.");
+
+ friendly = friendly_disk_name(src, vol);
+ if (!friendly)
+ return log_oom();
+
+ if (asprintf(&text, "Please enter %s for disk %s:", passphrase_type_to_string(passphrase_type), friendly) < 0)
+ return log_oom();
+
+ disk_path = cescape(src);
+ if (!disk_path)
+ return log_oom();
+
+ id = strjoina("cryptsetup:", disk_path);
+
+ r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", "cryptsetup.passphrase", until,
+ flags | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED),
+ &passwords);
+ if (r < 0)
+ return log_error_errno(r, "Failed to query password: %m");
+
+ if (arg_verify) {
+ _cleanup_strv_free_erase_ char **passwords2 = NULL;
+
+ assert(strv_length(passwords) == 1);
+
+ if (asprintf(&text, "Please enter %s for disk %s (verification):", passphrase_type_to_string(passphrase_type), friendly) < 0)
+ return log_oom();
+
+ id = strjoina("cryptsetup-verification:", disk_path);
+
+ r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", "cryptsetup.passphrase", until, flags, &passwords2);
+ if (r < 0)
+ return log_error_errno(r, "Failed to query verification password: %m");
+
+ assert(strv_length(passwords2) == 1);
+
+ if (!streq(passwords[0], passwords2[0]))
+ return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN),
+ "Passwords did not match, retrying.");
+ }
+
+ strv_uniq(passwords);
+
+ STRV_FOREACH(p, passwords) {
+ char *c;
+
+ if (strlen(*p)+1 >= arg_key_size)
+ continue;
+
+ /* Pad password if necessary */
+ c = new(char, arg_key_size);
+ if (!c)
+ return log_oom();
+
+ strncpy(c, *p, arg_key_size);
+ erase_and_free(*p);
+ *p = TAKE_PTR(c);
+ }
+
+ *ret = TAKE_PTR(passwords);
+
+ return 0;
+}
+
+static int attach_tcrypt(
+ struct crypt_device *cd,
+ const char *name,
+ const char *key_file,
+ const void *key_data,
+ size_t key_data_size,
+ char **passwords,
+ uint32_t flags) {
+
+ int r = 0;
+ _cleanup_(erase_and_freep) char *passphrase = NULL;
+ struct crypt_params_tcrypt params = {
+ .flags = CRYPT_TCRYPT_LEGACY_MODES,
+ .keyfiles = (const char **)arg_tcrypt_keyfiles,
+ .keyfiles_count = strv_length(arg_tcrypt_keyfiles)
+ };
+
+ assert(cd);
+ assert(name);
+ assert(key_file || key_data || !strv_isempty(passwords));
+
+ if (arg_pkcs11_uri || arg_pkcs11_uri_auto || arg_fido2_device || arg_fido2_device_auto || arg_tpm2_device || arg_tpm2_device_auto)
+ /* Ask for a regular password */
+ return log_error_errno(SYNTHETIC_ERRNO(EAGAIN),
+ "Sorry, but tcrypt devices are currently not supported in conjunction with pkcs11/fido2/tpm2 support.");
+
+ if (arg_tcrypt_hidden)
+ params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER;
+
+ if (arg_tcrypt_system)
+ params.flags |= CRYPT_TCRYPT_SYSTEM_HEADER;
+
+ if (arg_tcrypt_veracrypt)
+ params.flags |= CRYPT_TCRYPT_VERA_MODES;
+
+ if (key_data) {
+ params.passphrase = key_data;
+ params.passphrase_size = key_data_size;
+ r = crypt_load(cd, CRYPT_TCRYPT, &params);
+ } else if (key_file) {
+ r = read_one_line_file(key_file, &passphrase);
+ if (r < 0) {
+ log_error_errno(r, "Failed to read password file '%s': %m", key_file);
+ return -EAGAIN; /* log with the actual error, but return EAGAIN */
+ }
+ params.passphrase = passphrase;
+ params.passphrase_size = strlen(passphrase);
+ r = crypt_load(cd, CRYPT_TCRYPT, &params);
+ } else {
+ r = -EINVAL;
+ STRV_FOREACH(p, passwords){
+ params.passphrase = *p;
+ params.passphrase_size = strlen(*p);
+ r = crypt_load(cd, CRYPT_TCRYPT, &params);
+ if (r >= 0)
+ break;
+ }
+ }
+
+ if (r < 0) {
+ if (r == -EPERM) {
+ if (key_data)
+ log_error_errno(r, "Failed to activate using discovered key. (Key not correct?)");
+ else if (key_file)
+ log_error_errno(r, "Failed to activate using password file '%s'. (Key data not correct?)", key_file);
+ else
+ log_error_errno(r, "Failed to activate using supplied passwords.");
+
+ return r;
+ }
+
+ return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd));
+ }
+
+ r = crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
+ if (r < 0)
+ return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd));
+
+ return 0;
+}
+
+static char *make_bindname(const char *volume) {
+ char *s;
+
+ if (asprintf(&s, "@%" PRIx64"/cryptsetup/%s", random_u64(), volume) < 0)
+ return NULL;
+
+ return s;
+}
+
+static int make_security_device_monitor(
+ sd_event **ret_event,
+ sd_device_monitor **ret_monitor) {
+ _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ int r;
+
+ assert(ret_event);
+ assert(ret_monitor);
+
+ /* Waits for a device with "security-device" tag to show up in udev */
+
+ r = sd_event_default(&event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate event loop: %m");
+
+ r = sd_event_add_time_relative(event, NULL, CLOCK_MONOTONIC, arg_token_timeout_usec, USEC_PER_SEC, NULL, INT_TO_PTR(-ETIMEDOUT));
+ if (r < 0)
+ return log_error_errno(r, "Failed to install timeout event source: %m");
+
+ r = sd_device_monitor_new(&monitor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate device monitor: %m");
+
+ (void) sd_device_monitor_set_description(monitor, "security-device");
+
+ r = sd_device_monitor_filter_add_match_tag(monitor, "security-device");
+ if (r < 0)
+ return log_error_errno(r, "Failed to configure device monitor: %m");
+
+ r = sd_device_monitor_attach_event(monitor, event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach device monitor: %m");
+
+ r = sd_device_monitor_start(monitor, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to start device monitor: %m");
+
+ *ret_event = TAKE_PTR(event);
+ *ret_monitor = TAKE_PTR(monitor);
+ return 0;
+}
+
+static int run_security_device_monitor(
+ sd_event *event,
+ sd_device_monitor *monitor) {
+ bool processed = false;
+ int r;
+
+ assert(event);
+ assert(monitor);
+
+ /* Runs the event loop for the device monitor until either something happens, or the time-out is
+ * hit. */
+
+ for (;;) {
+ int x;
+
+ r = sd_event_get_exit_code(event, &x);
+ if (r < 0) {
+ if (r != -ENODATA)
+ return log_error_errno(r, "Failed to query exit code from event loop: %m");
+
+ /* On ENODATA we aren't told to exit yet. */
+ } else {
+ assert(x == -ETIMEDOUT);
+ return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN),
+ "Timed out waiting for security device, aborting security device based authentication attempt.");
+ }
+
+ /* Wait for one event, and then eat all subsequent events until there are no further ones */
+ r = sd_event_run(event, processed ? 0 : UINT64_MAX);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run event loop: %m");
+ if (r == 0) /* no events queued anymore */
+ return 0;
+
+ processed = true;
+ }
+}
+
+static bool libcryptsetup_plugins_support(void) {
+#if HAVE_LIBCRYPTSETUP_PLUGINS
+ int r;
+
+ /* Permit a way to disable libcryptsetup token module support, for debugging purposes. */
+ r = getenv_bool("SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE");
+ if (r < 0 && r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE env var: %m");
+ if (r == 0)
+ return false;
+
+ return crypt_token_external_path();
+#else
+ return false;
+#endif
+}
+
+#if HAVE_LIBCRYPTSETUP_PLUGINS
+static int acquire_pins_from_env_variable(char ***ret_pins) {
+ _cleanup_(erase_and_freep) char *envpin = NULL;
+ _cleanup_strv_free_erase_ char **pins = NULL;
+ int r;
+
+ assert(ret_pins);
+
+ r = getenv_steal_erase("PIN", &envpin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire PIN from environment: %m");
+ if (r > 0) {
+ pins = strv_new(envpin);
+ if (!pins)
+ return log_oom();
+ }
+
+ *ret_pins = TAKE_PTR(pins);
+
+ return 0;
+}
+#endif
+
+static int crypt_activate_by_token_pin_ask_password(
+ struct crypt_device *cd,
+ const char *name,
+ const char *type,
+ usec_t until,
+ bool headless,
+ void *usrptr,
+ uint32_t activation_flags,
+ const char *message,
+ const char *key_name,
+ const char *credential_name) {
+
+#if HAVE_LIBCRYPTSETUP_PLUGINS
+ AskPasswordFlags flags = ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED;
+ _cleanup_strv_free_erase_ char **pins = NULL;
+ int r;
+
+ r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, NULL, 0, usrptr, activation_flags);
+ if (r > 0) /* returns unlocked keyslot id on success */
+ r = 0;
+ if (r != -ENOANO) /* needs pin or pin is wrong */
+ return r;
+
+ r = acquire_pins_from_env_variable(&pins);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(p, pins) {
+ r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags);
+ if (r > 0) /* returns unlocked keyslot id on success */
+ r = 0;
+ if (r != -ENOANO) /* needs pin or pin is wrong */
+ return r;
+ }
+
+ if (headless)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable.");
+
+ for (;;) {
+ pins = strv_free_erase(pins);
+ r = ask_password_auto(message, "drive-harddisk", NULL, key_name, credential_name, until, flags, &pins);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(p, pins) {
+ r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags);
+ if (r > 0) /* returns unlocked keyslot id on success */
+ r = 0;
+ if (r != -ENOANO) /* needs pin or pin is wrong */
+ return r;
+ }
+
+ flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
+ }
+ return r;
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+
+static int attach_luks2_by_fido2_via_plugin(
+ struct crypt_device *cd,
+ const char *name,
+ usec_t until,
+ bool headless,
+ void *usrptr,
+ uint32_t activation_flags) {
+
+ return crypt_activate_by_token_pin_ask_password(
+ cd,
+ name,
+ "systemd-fido2",
+ until,
+ headless,
+ usrptr,
+ activation_flags,
+ "Please enter security token PIN:",
+ "fido2-pin",
+ "cryptsetup.fido2-pin");
+}
+
+static int attach_luks_or_plain_or_bitlk_by_fido2(
+ struct crypt_device *cd,
+ const char *name,
+ const char *key_file,
+ const void *key_data,
+ size_t key_data_size,
+ usec_t until,
+ uint32_t flags,
+ bool pass_volume_key) {
+
+ _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ size_t decrypted_key_size, cid_size = 0;
+ _cleanup_free_ char *friendly = NULL;
+ int keyslot = arg_key_slot, r;
+ const char *rp_id = NULL;
+ const void *cid = NULL;
+ Fido2EnrollFlags required;
+ bool use_libcryptsetup_plugin = libcryptsetup_plugins_support();
+
+ assert(cd);
+ assert(name);
+ assert(arg_fido2_device || arg_fido2_device_auto);
+
+ if (arg_fido2_cid) {
+ if (!key_file && !key_data)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "FIDO2 mode with manual parameters selected, but no keyfile specified, refusing.");
+
+ rp_id = arg_fido2_rp_id;
+ cid = arg_fido2_cid;
+ cid_size = arg_fido2_cid_size;
+
+ /* For now and for compatibility, if the user explicitly configured FIDO2 support and we do
+ * not read FIDO2 metadata off the LUKS2 header, default to the systemd 248 logic, where we
+ * use PIN + UP when needed, and do not configure UV at all. Eventually, we should make this
+ * explicitly configurable. */
+ required = FIDO2ENROLL_PIN_IF_NEEDED | FIDO2ENROLL_UP_IF_NEEDED | FIDO2ENROLL_UV_OMIT;
+ }
+
+ friendly = friendly_disk_name(crypt_get_device_name(cd), name);
+ if (!friendly)
+ return log_oom();
+
+ for (;;) {
+ if (use_libcryptsetup_plugin && !arg_fido2_cid) {
+ r = attach_luks2_by_fido2_via_plugin(cd, name, until, arg_headless, arg_fido2_device, flags);
+ if (IN_SET(r, -ENOTUNIQ, -ENXIO, -ENOENT))
+ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN),
+ "Automatic FIDO2 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking.");
+
+ } else {
+ if (cid)
+ r = acquire_fido2_key(
+ name,
+ friendly,
+ arg_fido2_device,
+ rp_id,
+ cid, cid_size,
+ key_file, arg_keyfile_size, arg_keyfile_offset,
+ key_data, key_data_size,
+ until,
+ arg_headless,
+ required,
+ &decrypted_key, &decrypted_key_size,
+ arg_ask_password_flags);
+ else
+ r = acquire_fido2_key_auto(
+ cd,
+ name,
+ friendly,
+ arg_fido2_device,
+ key_file, arg_keyfile_size, arg_keyfile_offset,
+ until,
+ arg_headless,
+ &decrypted_key, &decrypted_key_size,
+ arg_ask_password_flags);
+ if (r >= 0)
+ break;
+ }
+
+ if (r != -EAGAIN) /* EAGAIN means: token not found */
+ return r;
+
+ if (!monitor) {
+ /* We didn't find the token. In this case, watch for it via udev. Let's
+ * create an event loop and monitor first. */
+
+ assert(!event);
+
+ r = make_security_device_monitor(&event, &monitor);
+ if (r < 0)
+ return r;
+
+ log_notice("Security token not present for unlocking volume %s, please plug it in.", friendly);
+
+ /* Let's immediately rescan in case the token appeared in the time we needed
+ * to create and configure the monitor */
+ continue;
+ }
+
+ r = run_security_device_monitor(event, monitor);
+ if (r < 0)
+ return r;
+
+ log_debug("Got one or more potentially relevant udev events, rescanning FIDO2...");
+ }
+
+ if (pass_volume_key)
+ r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ else {
+ _cleanup_(erase_and_freep) char *base64_encoded = NULL;
+
+ /* Before using this key as passphrase we base64 encode it, for compat with homed */
+
+ r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (r < 0)
+ return log_oom();
+
+ r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags);
+ }
+ if (r == -EPERM) {
+ log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)");
+ return -EAGAIN; /* log actual error, but return EAGAIN */
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to activate with FIDO2 acquired key: %m");
+
+ return 0;
+}
+
+static int attach_luks2_by_pkcs11_via_plugin(
+ struct crypt_device *cd,
+ const char *name,
+ const char *friendly_name,
+ usec_t until,
+ bool headless,
+ uint32_t flags) {
+
+#if HAVE_LIBCRYPTSETUP_PLUGINS
+ int r;
+
+ if (!streq_ptr(crypt_get_type(cd), CRYPT_LUKS2))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Automatic PKCS#11 metadata requires LUKS2 device.");
+
+ systemd_pkcs11_plugin_params params = {
+ .friendly_name = friendly_name,
+ .until = until,
+ .headless = headless,
+ .askpw_flags = arg_ask_password_flags,
+ };
+
+ r = crypt_activate_by_token_pin(cd, name, "systemd-pkcs11", CRYPT_ANY_TOKEN, NULL, 0, &params, flags);
+ if (r > 0) /* returns unlocked keyslot id on success */
+ r = 0;
+
+ return r;
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+
+static int attach_luks_or_plain_or_bitlk_by_pkcs11(
+ struct crypt_device *cd,
+ const char *name,
+ const char *key_file,
+ const void *key_data,
+ size_t key_data_size,
+ usec_t until,
+ uint32_t flags,
+ bool pass_volume_key) {
+
+ _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
+ _cleanup_free_ char *friendly = NULL, *discovered_uri = NULL;
+ size_t decrypted_key_size = 0, discovered_key_size = 0;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ _cleanup_free_ void *discovered_key = NULL;
+ int keyslot = arg_key_slot, r;
+ const char *uri = NULL;
+ bool use_libcryptsetup_plugin = libcryptsetup_plugins_support();
+
+ assert(cd);
+ assert(name);
+ assert(arg_pkcs11_uri || arg_pkcs11_uri_auto);
+
+ if (arg_pkcs11_uri_auto) {
+ if (!use_libcryptsetup_plugin) {
+ r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &keyslot);
+ if (IN_SET(r, -ENOTUNIQ, -ENXIO))
+ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN),
+ "Automatic PKCS#11 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking.");
+ if (r < 0)
+ return r;
+
+ uri = discovered_uri;
+ key_data = discovered_key;
+ key_data_size = discovered_key_size;
+ }
+ } else {
+ uri = arg_pkcs11_uri;
+
+ if (!key_file && !key_data)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PKCS#11 mode selected but no key file specified, refusing.");
+ }
+
+ friendly = friendly_disk_name(crypt_get_device_name(cd), name);
+ if (!friendly)
+ return log_oom();
+
+ for (;;) {
+ if (use_libcryptsetup_plugin && arg_pkcs11_uri_auto)
+ r = attach_luks2_by_pkcs11_via_plugin(cd, name, friendly, until, arg_headless, flags);
+ else {
+ r = decrypt_pkcs11_key(
+ name,
+ friendly,
+ uri,
+ key_file, arg_keyfile_size, arg_keyfile_offset,
+ key_data, key_data_size,
+ until,
+ arg_headless,
+ &decrypted_key, &decrypted_key_size);
+ if (r >= 0)
+ break;
+ }
+
+ if (r != -EAGAIN) /* EAGAIN means: token not found */
+ return r;
+
+ if (!monitor) {
+ /* We didn't find the token. In this case, watch for it via udev. Let's
+ * create an event loop and monitor first. */
+
+ assert(!event);
+
+ r = make_security_device_monitor(&event, &monitor);
+ if (r < 0)
+ return r;
+
+ log_notice("Security token%s%s not present for unlocking volume %s, please plug it in.",
+ uri ? " " : "", strempty(uri), friendly);
+
+ /* Let's immediately rescan in case the token appeared in the time we needed
+ * to create and configure the monitor */
+ continue;
+ }
+
+ r = run_security_device_monitor(event, monitor);
+ if (r < 0)
+ return r;
+
+ log_debug("Got one or more potentially relevant udev events, rescanning PKCS#11...");
+ }
+ assert(decrypted_key);
+
+ if (pass_volume_key)
+ r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ else {
+ _cleanup_(erase_and_freep) char *base64_encoded = NULL;
+
+ /* Before using this key as passphrase we base64 encode it. Why? For compatibility
+ * with homed's PKCS#11 hookup: there we want to use the key we acquired through
+ * PKCS#11 for other authentication/decryption mechanisms too, and some of them do
+ * not take arbitrary binary blobs, but require NUL-terminated strings — most
+ * importantly UNIX password hashes. Hence, for compatibility we want to use a string
+ * without embedded NUL here too, and that's easiest to generate from a binary blob
+ * via base64 encoding. */
+
+ r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (r < 0)
+ return log_oom();
+
+ r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags);
+ }
+ if (r == -EPERM) {
+ log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)");
+ return -EAGAIN; /* log actual error, but return EAGAIN */
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to activate with PKCS#11 acquired key: %m");
+
+ return 0;
+}
+
+static int make_tpm2_device_monitor(
+ sd_event **ret_event,
+ sd_device_monitor **ret_monitor) {
+
+ _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ int r;
+
+ assert(ret_event);
+ assert(ret_monitor);
+
+ r = sd_event_default(&event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate event loop: %m");
+
+ r = sd_event_add_time_relative(event, NULL, CLOCK_MONOTONIC, arg_token_timeout_usec, USEC_PER_SEC, NULL, INT_TO_PTR(-ETIMEDOUT));
+ if (r < 0)
+ return log_error_errno(r, "Failed to install timeout event source: %m");
+
+ r = sd_device_monitor_new(&monitor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate device monitor: %m");
+
+ (void) sd_device_monitor_set_description(monitor, "tpmrm");
+
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, "tpmrm", NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to configure device monitor: %m");
+
+ r = sd_device_monitor_attach_event(monitor, event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach device monitor: %m");
+
+ r = sd_device_monitor_start(monitor, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to start device monitor: %m");
+
+ *ret_event = TAKE_PTR(event);
+ *ret_monitor = TAKE_PTR(monitor);
+ return 0;
+}
+
+static int attach_luks2_by_tpm2_via_plugin(
+ struct crypt_device *cd,
+ const char *name,
+ usec_t until,
+ bool headless,
+ uint32_t flags) {
+
+#if HAVE_LIBCRYPTSETUP_PLUGINS
+ systemd_tpm2_plugin_params params = {
+ .search_pcr_mask = arg_tpm2_pcr_mask,
+ .device = arg_tpm2_device,
+ .signature_path = arg_tpm2_signature,
+ };
+
+ if (!libcryptsetup_plugins_support())
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Libcryptsetup has external plugins support disabled.");
+
+ return crypt_activate_by_token_pin_ask_password(
+ cd,
+ name,
+ "systemd-tpm2",
+ until,
+ headless,
+ &params,
+ flags,
+ "Please enter TPM2 PIN:",
+ "tpm2-pin",
+ "cryptsetup.tpm2-pin");
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+
+static int attach_luks_or_plain_or_bitlk_by_tpm2(
+ struct crypt_device *cd,
+ const char *name,
+ const char *key_file,
+ const void *key_data,
+ size_t key_data_size,
+ usec_t until,
+ uint32_t flags,
+ bool pass_volume_key) {
+
+ _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ _cleanup_free_ char *friendly = NULL;
+ int keyslot = arg_key_slot, r;
+ size_t decrypted_key_size;
+
+ assert(cd);
+ assert(name);
+ assert(arg_tpm2_device || arg_tpm2_device_auto);
+
+ friendly = friendly_disk_name(crypt_get_device_name(cd), name);
+ if (!friendly)
+ return log_oom();
+
+ for (;;) {
+ if (key_file || key_data) {
+ /* If key data is specified, use that */
+
+ r = acquire_tpm2_key(
+ name,
+ arg_tpm2_device,
+ arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask,
+ UINT16_MAX,
+ /* pubkey= */ NULL, /* pubkey_size= */ 0,
+ /* pubkey_pcr_mask= */ 0,
+ /* signature_path= */ NULL,
+ /* primary_alg= */ 0,
+ key_file, arg_keyfile_size, arg_keyfile_offset,
+ key_data, key_data_size,
+ /* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
+ arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0,
+ until,
+ arg_headless,
+ arg_ask_password_flags,
+ &decrypted_key, &decrypted_key_size);
+ if (r >= 0)
+ break;
+ if (IN_SET(r, -EACCES, -ENOLCK))
+ return log_error_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking.");
+ if (ERRNO_IS_NOT_SUPPORTED(r)) /* TPM2 support not compiled in? */
+ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available, falling back to traditional unlocking.");
+ /* EAGAIN means: no tpm2 chip found */
+ if (r != -EAGAIN) {
+ log_notice_errno(r, "TPM2 operation failed, falling back to traditional unlocking: %m");
+ return -EAGAIN; /* Mangle error code: let's make any form of TPM2 failure non-fatal. */
+ }
+ } else {
+ r = attach_luks2_by_tpm2_via_plugin(cd, name, until, arg_headless, flags);
+ if (r >= 0)
+ return 0;
+ /* EAGAIN means: no tpm2 chip found
+ * EOPNOTSUPP means: no libcryptsetup plugins support */
+ if (r == -ENXIO)
+ return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN),
+ "No TPM2 metadata matching the current system state found in LUKS2 header, falling back to traditional unlocking.");
+ if (r == -ENOENT)
+ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN),
+ "No TPM2 metadata enrolled in LUKS2 header or TPM2 support not available, falling back to traditional unlocking.");
+ if (!IN_SET(r, -EOPNOTSUPP, -EAGAIN)) {
+ log_notice_errno(r, "TPM2 operation failed, falling back to traditional unlocking: %m");
+ return -EAGAIN; /* Mangle error code: let's make any form of TPM2 failure non-fatal. */
+ }
+ }
+
+ if (r == -EOPNOTSUPP) { /* Plugin not available, let's process TPM2 stuff right here instead */
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL;
+ size_t blob_size, policy_hash_size;
+ bool found_some = false;
+ int token = 0; /* first token to look at */
+
+ /* If no key data is specified, look for it in the header. In order to support
+ * software upgrades we'll iterate through all suitable tokens, maybe one of them
+ * works. */
+
+ for (;;) {
+ _cleanup_free_ void *pubkey = NULL;
+ size_t pubkey_size = 0;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
+ uint16_t pcr_bank, primary_alg;
+ TPM2Flags tpm2_flags;
+
+ r = find_tpm2_auto_data(
+ cd,
+ arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */
+ token, /* search for the token with this index, or any later index than this */
+ &hash_pcr_mask,
+ &pcr_bank,
+ &pubkey, &pubkey_size,
+ &pubkey_pcr_mask,
+ &primary_alg,
+ &blob, &blob_size,
+ &policy_hash, &policy_hash_size,
+ &tpm2_flags,
+ &keyslot,
+ &token);
+ if (r == -ENXIO)
+ /* No further TPM2 tokens found in the LUKS2 header. */
+ return log_full_errno(found_some ? LOG_NOTICE : LOG_DEBUG,
+ SYNTHETIC_ERRNO(EAGAIN),
+ found_some
+ ? "No TPM2 metadata matching the current system state found in LUKS2 header, falling back to traditional unlocking."
+ : "No TPM2 metadata enrolled in LUKS2 header, falling back to traditional unlocking.");
+ if (ERRNO_IS_NOT_SUPPORTED(r)) /* TPM2 support not compiled in? */
+ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available, falling back to traditional unlocking.");
+ if (r < 0)
+ return r;
+
+ found_some = true;
+
+ r = acquire_tpm2_key(
+ name,
+ arg_tpm2_device,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ arg_tpm2_signature,
+ primary_alg,
+ /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
+ blob, blob_size,
+ policy_hash, policy_hash_size,
+ tpm2_flags,
+ until,
+ arg_headless,
+ arg_ask_password_flags,
+ &decrypted_key, &decrypted_key_size);
+ if (IN_SET(r, -EACCES, -ENOLCK))
+ return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking.");
+ if (r != -EPERM)
+ break;
+
+ token++; /* try a different token next time */
+ }
+
+ if (r >= 0)
+ break;
+ /* EAGAIN means: no tpm2 chip found */
+ if (r != -EAGAIN) {
+ log_notice_errno(r, "TPM2 operation failed, falling back to traditional unlocking: %m");
+ return -EAGAIN; /* Mangle error code: let's make any form of TPM2 failure non-fatal. */
+ }
+ }
+
+ if (!monitor) {
+ /* We didn't find the TPM2 device. In this case, watch for it via udev. Let's create
+ * an event loop and monitor first. */
+
+ assert(!event);
+
+ if (is_efi_boot() && !efi_has_tpm2())
+ return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN),
+ "No TPM2 hardware discovered and EFI firmware does not see it either, falling back to traditional unlocking.");
+
+ r = make_tpm2_device_monitor(&event, &monitor);
+ if (r < 0)
+ return r;
+
+ log_info("TPM2 device not present for unlocking %s, waiting for it to become available.", friendly);
+
+ /* Let's immediately rescan in case the device appeared in the time we needed
+ * to create and configure the monitor */
+ continue;
+ }
+
+ r = run_security_device_monitor(event, monitor);
+ if (r < 0)
+ return r;
+
+ log_debug("Got one or more potentially relevant udev events, rescanning for TPM2...");
+ }
+ assert(decrypted_key);
+
+ if (pass_volume_key)
+ r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ else {
+ _cleanup_(erase_and_freep) char *base64_encoded = NULL;
+
+ /* Before using this key as passphrase we base64 encode it, for compat with homed */
+
+ r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (r < 0)
+ return log_oom();
+
+ r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags);
+ }
+ if (r == -EPERM) {
+ log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)");
+ return -EAGAIN; /* log actual error, but return EAGAIN */
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to activate with TPM2 acquired key: %m");
+
+ return 0;
+}
+
+static int attach_luks_or_plain_or_bitlk_by_key_data(
+ struct crypt_device *cd,
+ const char *name,
+ const void *key_data,
+ size_t key_data_size,
+ uint32_t flags,
+ bool pass_volume_key) {
+
+ int r;
+
+ assert(cd);
+ assert(name);
+ assert(key_data);
+
+ if (pass_volume_key)
+ r = crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags);
+ else
+ r = crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags);
+ if (r == -EPERM) {
+ log_error_errno(r, "Failed to activate. (Key incorrect?)");
+ return -EAGAIN; /* Log actual error, but return EAGAIN */
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to activate: %m");
+
+ return 0;
+}
+
+static int attach_luks_or_plain_or_bitlk_by_key_file(
+ struct crypt_device *cd,
+ const char *name,
+ const char *key_file,
+ uint32_t flags,
+ bool pass_volume_key) {
+
+ _cleanup_(erase_and_freep) char *kfdata = NULL;
+ _cleanup_free_ char *bindname = NULL;
+ size_t kfsize;
+ int r;
+
+ assert(cd);
+ assert(name);
+ assert(key_file);
+
+ /* If we read the key via AF_UNIX, make this client recognizable */
+ bindname = make_bindname(name);
+ if (!bindname)
+ return log_oom();
+
+ r = read_full_file_full(
+ AT_FDCWD, key_file,
+ arg_keyfile_offset == 0 ? UINT64_MAX : arg_keyfile_offset,
+ arg_keyfile_size == 0 ? SIZE_MAX : arg_keyfile_size,
+ READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
+ bindname,
+ &kfdata, &kfsize);
+ if (r == -E2BIG) {
+ log_error_errno(r, "Failed to activate, key file '%s' too large.", key_file);
+ return -EAGAIN;
+ }
+ if (r == -ENOENT) {
+ log_error_errno(r, "Failed to activate, key file '%s' missing.", key_file);
+ return -EAGAIN; /* Log actual error, but return EAGAIN */
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to read key file '%s': %m", key_file);
+
+ if (pass_volume_key)
+ r = crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags);
+ else
+ r = crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags);
+ if (r == -EPERM) {
+ log_error_errno(r, "Failed to activate with key file '%s'. (Key data incorrect?)", key_file);
+ return -EAGAIN; /* Log actual error, but return EAGAIN */
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to activate with key file '%s': %m", key_file);
+
+ return 0;
+}
+
+static int attach_luks_or_plain_or_bitlk_by_passphrase(
+ struct crypt_device *cd,
+ const char *name,
+ char **passwords,
+ uint32_t flags,
+ bool pass_volume_key) {
+
+ int r;
+
+ assert(cd);
+ assert(name);
+
+ r = -EINVAL;
+ STRV_FOREACH(p, passwords) {
+ if (pass_volume_key)
+ r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
+ else
+ r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
+ if (r >= 0)
+ break;
+ }
+ if (r == -EPERM) {
+ log_error_errno(r, "Failed to activate with specified passphrase. (Passphrase incorrect?)");
+ return -EAGAIN; /* log actual error, but return EAGAIN */
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to activate with specified passphrase: %m");
+
+ return 0;
+}
+
+static int attach_luks_or_plain_or_bitlk(
+ struct crypt_device *cd,
+ const char *name,
+ const char *key_file,
+ const void *key_data,
+ size_t key_data_size,
+ char **passwords,
+ uint32_t flags,
+ usec_t until) {
+
+ bool pass_volume_key = false;
+ int r;
+
+ assert(cd);
+ assert(name);
+
+ if ((!arg_type && !crypt_get_type(cd)) || streq_ptr(arg_type, CRYPT_PLAIN)) {
+ struct crypt_params_plain params = {
+ .offset = arg_offset,
+ .skip = arg_skip,
+ .sector_size = arg_sector_size,
+ };
+ const char *cipher, *cipher_mode;
+ _cleanup_free_ char *truncated_cipher = NULL;
+
+ if (streq_ptr(arg_hash, "plain"))
+ /* plain isn't a real hash type. it just means "use no hash" */
+ params.hash = NULL;
+ else if (arg_hash)
+ params.hash = arg_hash;
+ else if (!key_file)
+ /* for CRYPT_PLAIN, the behaviour of cryptsetup package is to not hash when a key
+ * file is provided */
+ params.hash = "ripemd160";
+
+ if (arg_cipher) {
+ size_t l;
+
+ l = strcspn(arg_cipher, "-");
+ truncated_cipher = strndup(arg_cipher, l);
+ if (!truncated_cipher)
+ return log_oom();
+
+ cipher = truncated_cipher;
+ cipher_mode = arg_cipher[l] ? arg_cipher+l+1 : "plain";
+ } else {
+ cipher = "aes";
+ cipher_mode = "cbc-essiv:sha256";
+ }
+
+ /* for CRYPT_PLAIN limit reads from keyfile to key length, and ignore keyfile-size */
+ arg_keyfile_size = arg_key_size;
+
+ /* In contrast to what the name crypt_format() might suggest this doesn't actually format
+ * anything, it just configures encryption parameters when used for plain mode. */
+ r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, &params);
+ if (r < 0)
+ return log_error_errno(r, "Loading of cryptographic parameters failed: %m");
+
+ /* hash == NULL implies the user passed "plain" */
+ pass_volume_key = !params.hash;
+ }
+
+ log_info("Set cipher %s, mode %s, key size %i bits for device %s.",
+ crypt_get_cipher(cd),
+ crypt_get_cipher_mode(cd),
+ crypt_get_volume_key_size(cd)*8,
+ crypt_get_device_name(cd));
+
+ if (arg_tpm2_device || arg_tpm2_device_auto)
+ return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key);
+ if (arg_fido2_device || arg_fido2_device_auto)
+ return attach_luks_or_plain_or_bitlk_by_fido2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key);
+ if (arg_pkcs11_uri || arg_pkcs11_uri_auto)
+ return attach_luks_or_plain_or_bitlk_by_pkcs11(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key);
+ if (key_data)
+ return attach_luks_or_plain_or_bitlk_by_key_data(cd, name, key_data, key_data_size, flags, pass_volume_key);
+ if (key_file)
+ return attach_luks_or_plain_or_bitlk_by_key_file(cd, name, key_file, flags, pass_volume_key);
+
+ return attach_luks_or_plain_or_bitlk_by_passphrase(cd, name, passwords, flags, pass_volume_key);
+}
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-cryptsetup@.service", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s attach VOLUME SOURCEDEVICE [KEY-FILE] [OPTIONS]\n"
+ "%s detach VOLUME\n\n"
+ "Attaches or detaches an encrypted block device.\n"
+ "\nSee the %s for details.\n",
+ program_invocation_short_name,
+ program_invocation_short_name,
+ link);
+
+ return 0;
+}
+
+static uint32_t determine_flags(void) {
+ uint32_t flags = 0;
+
+ if (arg_readonly)
+ flags |= CRYPT_ACTIVATE_READONLY;
+
+ if (arg_discards)
+ flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
+
+ if (arg_same_cpu_crypt)
+ flags |= CRYPT_ACTIVATE_SAME_CPU_CRYPT;
+
+ if (arg_submit_from_crypt_cpus)
+ flags |= CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS;
+
+ if (arg_no_read_workqueue)
+ flags |= CRYPT_ACTIVATE_NO_READ_WORKQUEUE;
+
+ if (arg_no_write_workqueue)
+ flags |= CRYPT_ACTIVATE_NO_WRITE_WORKQUEUE;
+
+#ifdef CRYPT_ACTIVATE_SERIALIZE_MEMORY_HARD_PBKDF
+ /* Try to decrease the risk of OOM event if memory hard key derivation function is in use */
+ /* https://gitlab.com/cryptsetup/cryptsetup/issues/446/ */
+ flags |= CRYPT_ACTIVATE_SERIALIZE_MEMORY_HARD_PBKDF;
+#endif
+
+ return flags;
+}
+
+static void remove_and_erasep(const char **p) {
+ int r;
+
+ if (!*p)
+ return;
+
+ r = unlinkat_deallocate(AT_FDCWD, *p, UNLINK_ERASE);
+ if (r < 0 && r != -ENOENT)
+ log_warning_errno(r, "Unable to erase key file '%s', ignoring: %m", *p);
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+ const char *verb;
+ int r;
+
+ if (argv_looks_like_help(argc, argv))
+ return help();
+
+ if (argc < 3)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "This program requires at least two arguments.");
+
+ log_setup();
+
+ cryptsetup_enable_logging(NULL);
+
+ umask(0022);
+
+ verb = argv[1];
+
+ if (streq(verb, "attach")) {
+ _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL;
+ _cleanup_(erase_and_freep) void *key_data = NULL;
+ const char *volume, *source, *key_file, *options;
+ crypt_status_info status;
+ size_t key_data_size = 0;
+ uint32_t flags = 0;
+ unsigned tries;
+ usec_t until;
+ PassphraseType passphrase_type = PASSPHRASE_NONE;
+
+ /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [KEY-FILE] [OPTIONS] */
+
+ if (argc < 4)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least two arguments.");
+
+ volume = argv[2];
+ source = argv[3];
+ key_file = mangle_none(argc >= 5 ? argv[4] : NULL);
+ options = mangle_none(argc >= 6 ? argv[5] : NULL);
+
+ if (!filename_is_valid(volume))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
+
+ if (key_file && !path_is_absolute(key_file)) {
+ log_warning("Password file path '%s' is not absolute. Ignoring.", key_file);
+ key_file = NULL;
+ }
+
+ if (options) {
+ r = parse_options(options);
+ if (r < 0)
+ return r;
+ }
+
+ log_debug("%s %s ← %s type=%s cipher=%s", __func__,
+ volume, source, strempty(arg_type), strempty(arg_cipher));
+
+ /* A delicious drop of snake oil */
+ (void) mlockall(MCL_FUTURE);
+
+ if (!key_file) {
+ _cleanup_free_ char *bindname = NULL;
+ const char *fn;
+
+ bindname = make_bindname(volume);
+ if (!bindname)
+ return log_oom();
+
+ /* If a key file is not explicitly specified, search for a key in a well defined
+ * search path, and load it. */
+
+ fn = strjoina(volume, ".key");
+ r = find_key_file(
+ fn,
+ STRV_MAKE("/etc/cryptsetup-keys.d", "/run/cryptsetup-keys.d"),
+ bindname,
+ &key_data, &key_data_size);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ log_debug("Automatically discovered key for volume '%s'.", volume);
+ } else if (arg_keyfile_erase)
+ destroy_key_file = key_file; /* let's get this baby erased when we leave */
+
+ if (arg_header) {
+ log_debug("LUKS header: %s", arg_header);
+ r = crypt_init(&cd, arg_header);
+ } else
+ r = crypt_init(&cd, source);
+ if (r < 0)
+ return log_error_errno(r, "crypt_init() failed: %m");
+
+ cryptsetup_enable_logging(cd);
+
+ status = crypt_status(cd, volume);
+ if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) {
+ log_info("Volume %s already active.", volume);
+ return 0;
+ }
+
+ flags = determine_flags();
+
+ until = usec_add(now(CLOCK_MONOTONIC), arg_timeout);
+ if (until == USEC_INFINITY)
+ until = 0;
+
+ if (arg_key_size == 0)
+ arg_key_size = 256U / 8U;
+
+ if (key_file) {
+ struct stat st;
+
+ /* Ideally we'd do this on the open fd, but since this is just a
+ * warning it's OK to do this in two steps. */
+ if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005))
+ log_warning("Key file %s is world-readable. This is not a good idea!", key_file);
+ }
+
+ if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) {
+ r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd));
+
+ if (arg_header) {
+ r = crypt_set_data_device(cd, source);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set LUKS data device %s: %m", source);
+ }
+
+ /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */
+ if (!key_file && !key_data && getenv_bool("SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE") != 0) {
+ r = crypt_activate_by_token_pin_ask_password(
+ cd,
+ volume,
+ NULL,
+ until,
+ arg_headless,
+ NULL,
+ flags,
+ "Please enter LUKS2 token PIN:",
+ "luks2-pin",
+ "cryptsetup.luks2-pin");
+ if (r >= 0) {
+ log_debug("Volume %s activated with LUKS token id %i.", volume, r);
+ return 0;
+ }
+
+ log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd));
+ }
+ }
+
+/* since cryptsetup 2.3.0 (Feb 2020) */
+#ifdef CRYPT_BITLK
+ if (streq_ptr(arg_type, CRYPT_BITLK)) {
+ r = crypt_load(cd, CRYPT_BITLK, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd));
+ }
+#endif
+
+ for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) {
+ _cleanup_strv_free_erase_ char **passwords = NULL;
+
+ /* When we were able to acquire multiple keys, let's always process them in this order:
+ *
+ * 1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip
+ * 2. The discovered key: i.e. key_data + key_data_size
+ * 3. The configured key: i.e. key_file + arg_keyfile_offset + arg_keyfile_size
+ * 4. The empty password, in case arg_try_empty_password is set
+ * 5. We enquire the user for a password
+ */
+
+ if (!key_file && !key_data && !arg_pkcs11_uri && !arg_pkcs11_uri_auto && !arg_fido2_device && !arg_fido2_device_auto && !arg_tpm2_device && !arg_tpm2_device_auto) {
+
+ if (arg_try_empty_password) {
+ /* Hmm, let's try an empty password now, but only once */
+ arg_try_empty_password = false;
+
+ key_data = strdup("");
+ if (!key_data)
+ return log_oom();
+
+ key_data_size = 0;
+ } else {
+ /* Ask the user for a passphrase or recovery key only as last resort, if we have
+ * nothing else to check for */
+ if (passphrase_type == PASSPHRASE_NONE) {
+ passphrase_type = check_registered_passwords(cd);
+ if (passphrase_type == PASSPHRASE_NONE)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered.");
+ }
+
+ r = get_password(volume, source, until, tries == 0 && !arg_verify, passphrase_type, &passwords);
+ if (r == -EAGAIN)
+ continue;
+ if (r < 0)
+ return r;
+ }
+ }
+
+ if (streq_ptr(arg_type, CRYPT_TCRYPT))
+ r = attach_tcrypt(cd, volume, key_file, key_data, key_data_size, passwords, flags);
+ else
+ r = attach_luks_or_plain_or_bitlk(cd, volume, key_file, key_data, key_data_size, passwords, flags, until);
+ if (r >= 0)
+ break;
+ if (r != -EAGAIN)
+ return r;
+
+ /* Key not correct? Let's try again! */
+
+ key_file = NULL;
+ key_data = erase_and_free(key_data);
+ key_data_size = 0;
+ arg_pkcs11_uri = mfree(arg_pkcs11_uri);
+ arg_pkcs11_uri_auto = false;
+ arg_fido2_device = mfree(arg_fido2_device);
+ arg_fido2_device_auto = false;
+ arg_tpm2_device = mfree(arg_tpm2_device);
+ arg_tpm2_device_auto = false;
+ }
+
+ if (arg_tries != 0 && tries >= arg_tries)
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Too many attempts to activate; giving up.");
+
+ } else if (streq(verb, "detach")) {
+ const char *volume;
+
+ volume = argv[2];
+
+ if (!filename_is_valid(volume))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
+
+ r = crypt_init_by_name(&cd, volume);
+ if (r == -ENODEV) {
+ log_info("Volume %s already inactive.", volume);
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "crypt_init_by_name() failed: %m");
+
+ cryptsetup_enable_logging(cd);
+
+ r = crypt_deactivate(cd, volume);
+ if (r < 0)
+ return log_error_errno(r, "Failed to deactivate: %m");
+
+ } else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb);
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/cryptsetup/meson.build b/src/cryptsetup/meson.build
new file mode 100644
index 0000000..26267fb
--- /dev/null
+++ b/src/cryptsetup/meson.build
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+systemd_cryptsetup_sources = files(
+ 'cryptsetup-fido2.h',
+ 'cryptsetup-keyfile.c',
+ 'cryptsetup-keyfile.h',
+ 'cryptsetup-pkcs11.h',
+ 'cryptsetup-tpm2.h',
+ 'cryptsetup.c')
+
+if conf.get('HAVE_P11KIT') == 1
+ systemd_cryptsetup_sources += files('cryptsetup-pkcs11.c')
+endif
+
+if conf.get('HAVE_LIBFIDO2') == 1
+ systemd_cryptsetup_sources += files('cryptsetup-fido2.c')
+endif
+
+if conf.get('HAVE_TPM2') == 1
+ systemd_cryptsetup_sources += files('cryptsetup-tpm2.c')
+endif