summaryrefslogtreecommitdiffstats
path: root/src/cryptsetup/cryptsetup-tokens
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:49:52 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:49:52 +0000
commit55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch)
tree33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/cryptsetup/cryptsetup-tokens
parentInitial commit. (diff)
downloadsystemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz
systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cryptsetup/cryptsetup-tokens')
-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.c352
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c70
-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.c158
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-fido2.h24
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c272
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h21
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c109
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h30
-rw-r--r--src/cryptsetup/cryptsetup-tokens/meson.build75
14 files changed, 1551 insertions, 0 deletions
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..fdb3b17
--- /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, "Cannot 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, "Cannot 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, "Cannot 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..2ac8a27
--- /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, "Cannot 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..6fee831
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
@@ -0,0 +1,352 @@
+/* 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, *salt = NULL, *srk_buf = NULL;
+ size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
+ _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;
+ ssize_t base64_encoded_size;
+ 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, "Cannot 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,
+ &salt,
+ &salt_size,
+ &srk_buf,
+ &srk_buf_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,
+ params.pcrlock_path,
+ primary_alg,
+ blob,
+ blob_size,
+ policy_hash,
+ policy_hash_size,
+ salt,
+ salt_size,
+ srk_buf,
+ srk_buf_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 */
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return log_debug_open_error(cd, base64_encoded_size);
+
+ /* free'd automatically by libcryptsetup */
+ *ret_password = TAKE_PTR(base64_encoded);
+ *ret_password_len = base64_encoded_size;
+
+ 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, *salt = NULL, *srk_buf = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
+ 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,
+ &salt,
+ &salt_size,
+ &srk_buf,
+ &srk_buf_size,
+ &flags);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
+
+ hash_pcrs_str = tpm2_pcr_mask_to_string(hash_pcr_mask);
+ if (!hash_pcrs_str)
+ return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m");
+
+ pubkey_pcrs_str = tpm2_pcr_mask_to_string(pubkey_pcr_mask);
+ if (!pubkey_pcrs_str)
+ return (void) crypt_log_debug_errno(cd, ENOMEM, "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, "Cannot 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, "Cannot 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, "Cannot 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_hash_alg_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_asym_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));
+ crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK));
+ crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt));
+ crypt_log(cd, "\ttpm2-srk: %s\n", true_false(srk_buf));
+}
+
+/*
+ * 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 (!TPM2_PCR_INDEX_VALID(u)) {
+ 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_hash_alg_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_asym_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..4e3090b
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c
@@ -0,0 +1,70 @@
+/* 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) {
+ assert(pin || pin_size == 0);
+ assert(ret_pin_string);
+
+ if (pin_size == 0) {
+ *ret_pin_string = NULL;
+ return 0;
+ }
+
+ return make_cstring(pin, pin_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, ret_pin_string);
+}
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..a1c85e6
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c
@@ -0,0 +1,158 @@
+/* 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;
+ ssize_t base64_encoded_size;
+
+ 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 */
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return crypt_log_error_errno(cd, (int) base64_encoded_size, "Failed to base64 encode key: %m");
+
+ *ret_keyslot_passphrase = TAKE_PTR(base64_encoded);
+ *ret_keyslot_passphrase_size = base64_encoded_size;
+
+ 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..178fc7a
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c
@@ -0,0 +1,272 @@
+/* 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) {
+ /* Refresh 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, sym_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;
+ ssize_t base64_encoded_size;
+
+ 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;
+
+ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+ if (base64_encoded_size < 0)
+ return crypt_log_error_errno(cd, (int) base64_encoded_size, "Cannot base64 encode key: %m");
+
+ *ret_password = TAKE_PTR(base64_encoded);
+ *ret_password_size = base64_encoded_size;
+
+ 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..72be5cc
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
@@ -0,0 +1,109 @@
+/* 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 "sha256.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,
+ const char *pcrlock_path,
+ uint16_t primary_alg,
+ const void *key_data,
+ size_t key_data_size,
+ const void *policy_hash,
+ size_t policy_hash_size,
+ const void *salt,
+ size_t salt_size,
+ const void *srk_buf,
+ size_t srk_buf_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;
+ _cleanup_(erase_and_freep) char *b64_salted_pin = NULL;
+ int r;
+
+ assert(salt || salt_size == 0);
+ assert(ret_decrypted_key);
+ assert(ret_decrypted_key_size);
+
+ if (!device) {
+ r = tpm2_find_device_auto(&auto_device);
+ if (r == -ENODEV)
+ return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */
+ if (r < 0)
+ return log_error_errno(r, "Could not find TPM2 device: %m");
+
+ device = auto_device;
+ }
+
+ if ((flags & TPM2_FLAGS_USE_PIN) && !pin)
+ return -ENOANO;
+
+ if (pin && salt_size > 0) {
+ uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
+ CLEANUP_ERASE(salted_pin);
+ r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt, salt_size, salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to perform PBKDF2: %m");
+
+ r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to base64 encode salted pin: %m");
+ pin = b64_salted_pin;
+ }
+
+ if (pubkey_pcr_mask != 0) {
+ r = tpm2_load_pcr_signature(signature_path, &signature_json);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load PCR signature: %m");
+ }
+
+ _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {};
+ if (FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK)) {
+ r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy);
+ if (r < 0)
+ return r;
+ }
+
+ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
+ r = tpm2_context_new(device, &tpm2_context);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create TPM2 context: %m");
+
+ r = tpm2_unseal(tpm2_context,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature_json,
+ pin,
+ FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
+ primary_alg,
+ key_data, key_data_size,
+ policy_hash, policy_hash_size,
+ srk_buf, srk_buf_size,
+ ret_decrypted_key, ret_decrypted_key_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
+
+ return r;
+}
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
new file mode 100644
index 0000000..d84e5a3
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
@@ -0,0 +1,30 @@
+/* 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 *pcrlock_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,
+ const void *salt,
+ size_t salt_size,
+ const void *srk_buf,
+ size_t srk_buf_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..b26940c
--- /dev/null
+++ b/src/cryptsetup/cryptsetup-tokens/meson.build
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+lib_cryptsetup_token_common = static_library(
+ 'cryptsetup-token-common',
+ 'cryptsetup-token-util.c',
+ include_directories : includes,
+ dependencies : userspace,
+ link_with : libshared,
+ build_by_default : false)
+
+cryptsetup_token_systemd_tpm2_sources = files(
+ 'cryptsetup-token-systemd-tpm2.c',
+ 'luks2-tpm2.c',
+)
+
+cryptsetup_token_systemd_fido2_sources = files(
+ 'cryptsetup-token-systemd-fido2.c',
+ 'luks2-fido2.c',
+)
+
+cryptsetup_token_systemd_pkcs11_sources = files(
+ 'cryptsetup-token-systemd-pkcs11.c',
+ 'luks2-pkcs11.c',
+)
+
+template = {
+ 'include_directories' : includes,
+ 'link_with' : [
+ lib_cryptsetup_token_common,
+ libshared,
+ ],
+ 'version-script' : meson.current_source_dir() / 'cryptsetup-token.sym',
+ 'install_rpath' : pkglibdir,
+ 'install' : true,
+ 'install_dir' : libcryptsetup_plugins_dir,
+}
+
+modules += [
+ template + {
+ 'name' : 'cryptsetup-token-systemd-tpm2',
+ 'conditions' : [
+ 'HAVE_LIBCRYPTSETUP_PLUGINS',
+ 'HAVE_TPM2',
+ ],
+ 'sources' : cryptsetup_token_systemd_tpm2_sources,
+ 'dependencies' : [
+ libcryptsetup,
+ tpm2,
+ ],
+ },
+ template + {
+ 'name' : 'cryptsetup-token-systemd-fido2',
+ 'conditions' : [
+ 'HAVE_LIBCRYPTSETUP_PLUGINS',
+ 'HAVE_LIBFIDO2',
+ ],
+ 'sources' : cryptsetup_token_systemd_fido2_sources,
+ 'dependencies' : [
+ libcryptsetup,
+ libfido2,
+ ],
+ },
+ template + {
+ 'name' : 'cryptsetup-token-systemd-pkcs11',
+ 'conditions' : [
+ 'HAVE_LIBCRYPTSETUP_PLUGINS',
+ 'HAVE_P11KIT',
+ ],
+ 'sources' : cryptsetup_token_systemd_pkcs11_sources,
+ 'dependencies' : [
+ libcryptsetup,
+ libp11kit_cflags,
+ ],
+ },
+]