]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptenroll: collect all enrollment parameters in an EnrollContext
authorLennart Poettering <lennart@amutable.com>
Thu, 28 May 2026 10:35:21 +0000 (12:35 +0200)
committerLennart Poettering <lennart@amutable.com>
Sat, 27 Jun 2026 15:28:39 +0000 (17:28 +0200)
Introduce an EnrollContext structure that carries everything the enrollment
and unlocking helpers need, and route all enroll_*()/load_volume_key_*()/
wipe_slots() calls through it. The command line still populates the existing
arg_* globals as before; once parsing is complete they are copied into a
self-contained EnrollContext (which owns its strings/arrays) and the rest of
the code only ever reads from the context.

This is preparation for the upcoming varlinkification of systemd-cryptenroll:
a Varlink dispatcher (and later an interactive first-boot wizard) can populate
the very same EnrollContext without going through the arg_* parsing layer.

To support non-interactive (e.g. Varlink) callers, the context carries an
'interactive' flag: when false, every credential prompt is disabled and the
helpers fail with -ENOPKG (the established "querying disabled via headless"
code) instead of blocking on a tty. Passwords, FIDO2 PINs and PKCS#11 PINs are
all covered, and an optional FIDO2 PIN can be supplied directly via the
context. enroll_recovery() additionally grows a quiet mode that returns the
recovery key instead of printing it.

This also adds one new field to EnrollContext which didn't exist before:
the unlock_password is useful for the Varlink hookup later.

No change in command line behaviour.

17 files changed:
src/cryptenroll/cryptenroll-fido2.c
src/cryptenroll/cryptenroll-fido2.h
src/cryptenroll/cryptenroll-password.c
src/cryptenroll/cryptenroll-password.h
src/cryptenroll/cryptenroll-pkcs11.c
src/cryptenroll/cryptenroll-pkcs11.h
src/cryptenroll/cryptenroll-recovery.c
src/cryptenroll/cryptenroll-recovery.h
src/cryptenroll/cryptenroll-tpm2.c
src/cryptenroll/cryptenroll-tpm2.h
src/cryptenroll/cryptenroll-wipe.c
src/cryptenroll/cryptenroll-wipe.h
src/cryptenroll/cryptenroll.c
src/cryptenroll/cryptenroll.h
src/home/homectl-fido2.c
src/shared/libfido2-util.c
src/shared/libfido2-util.h

index 600207e947b83b24d1515a1016fbafaadf265b93..7500e84c181919cbfce969c67028914808fc204d 100644 (file)
 #include "string-util.h"
 
 int load_volume_key_fido2(
+                const EnrollContext *c,
                 struct crypt_device *cd,
-                const char *cd_node,
-                const char *device,
-                void *ret_vk,
-                size_t *ret_vks) {
+                struct iovec *ret_vk) {
 
 #if HAVE_LIBFIDO2
         _cleanup_(erase_and_freep) void *decrypted_key = NULL;
@@ -28,19 +26,19 @@ int load_volume_key_fido2(
         ssize_t passphrase_size;
         int r;
 
+        assert_se(c);
+        assert_se(c->node);
         assert_se(cd);
-        assert_se(cd_node);
         assert_se(ret_vk);
-        assert_se(ret_vks);
 
         r = acquire_fido2_key_auto(
                         cd,
-                        cd_node,
-                        cd_node,
-                        device,
+                        c->node,
+                        c->node,
+                        c->unlock_fido2_device,
                         /* until= */ 0,
                         "cryptenroll.fido2-pin",
-                        ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED,
+                        ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED|(c->interactive ? 0 : ASK_PASSWORD_HEADLESS),
                         &decrypted_key,
                         &decrypted_key_size);
         if (r == -EAGAIN)
@@ -57,8 +55,8 @@ int load_volume_key_fido2(
         r = sym_crypt_volume_key_get(
                         cd,
                         CRYPT_ANY_SLOT,
-                        ret_vk,
-                        ret_vks,
+                        ret_vk->iov_base,
+                        &ret_vk->iov_len,
                         passphrase,
                         passphrase_size);
         if (r < 0)
@@ -71,13 +69,9 @@ int load_volume_key_fido2(
 }
 
 int enroll_fido2(
+                const EnrollContext *c,
                 struct crypt_device *cd,
-                const struct iovec *volume_key,
-                const char *device,
-                Fido2EnrollFlags lock_with,
-                int cred_alg,
-                const char *salt_file,
-                bool parameters_in_header) {
+                const struct iovec *volume_key) {
 
 #if HAVE_LIBFIDO2
         _cleanup_(iovec_done_erase) struct iovec salt = {};
@@ -88,20 +82,22 @@ int enroll_fido2(
         size_t cid_size, secret_size;
         _cleanup_free_ void *cid = NULL;
         ssize_t base64_encoded_size;
+        Fido2EnrollFlags lock_with;     /* receives the flags actually locked with, see below */
         const char *node, *un;
         int r, keyslot;
 
+        assert_se(c);
         assert_se(cd);
         assert_se(iovec_is_set(volume_key));
-        assert_se(device);
+        assert_se(c->fido2_device);
 
         assert_se(node = sym_crypt_get_device_name(cd));
 
         un = strempty(sym_crypt_get_uuid(cd));
 
-        if (salt_file)
+        if (c->fido2_salt_file)
                 r = fido2_read_salt_file(
-                                salt_file,
+                                c->fido2_salt_file,
                                 /* offset= */ UINT64_MAX,
                                 /* client= */ "cryptenroll",
                                 /* node= */ un,
@@ -112,7 +108,7 @@ int enroll_fido2(
                 return r;
 
         r = fido2_generate_hmac_hash(
-                        device,
+                        c->fido2_device,
                         /* rp_id= */ "io.systemd.cryptsetup",
                         /* rp_name= */ "Encrypted Volume",
                         /* user_id= */ un, strlen(un), /* We pass the user ID and name as the same: the disk's UUID if we have it */
@@ -121,8 +117,10 @@ int enroll_fido2(
                         /* user_icon= */ NULL,
                         /* askpw_icon= */ "drive-harddisk",
                         /* askpw_credential= */ "cryptenroll.fido2-pin",
-                        lock_with,
-                        cred_alg,
+                        c->interactive ? 0 : ASK_PASSWORD_HEADLESS,
+                        c->fido2_pin,
+                        c->fido2_lock_with,
+                        c->fido2_cred_alg != 0 ? c->fido2_cred_alg : COSE_ES256,
                         &salt,
                         &cid, &cid_size,
                         &secret, &secret_size,
@@ -150,7 +148,7 @@ int enroll_fido2(
         if (keyslot < 0)
                 return log_error_errno(keyslot, "Failed to add new FIDO2 key to %s: %m", node);
 
-        if (parameters_in_header) {
+        if (c->fido2_parameters_in_header) {
                 if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
                         return log_oom();
 
index 7d0c25c02a957dc839c9ce5a32590e939898933d..c5d57f12dbad38f5ae7073c233158fc5166d9d15 100644 (file)
@@ -1,7 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "cryptenroll.h"
 #include "shared-forward.h"
 
-int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks);
-int enroll_fido2(struct crypt_device *cd, const struct iovec *volume_key, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header);
+int load_volume_key_fido2(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk);
+int enroll_fido2(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key);
index e189cce23aba049167e6b516fed7bae0f8984a00..6e78d6ab2fe6254799736847143f7b8eeff52a1a 100644 (file)
 #include "strv.h"
 
 int load_volume_key_password(
+                const EnrollContext *c,
                 struct crypt_device *cd,
-                const char *cd_node,
-                void *ret_vk,
-                size_t *ret_vks) {
+                struct iovec *ret_vk) {
 
         _cleanup_(erase_and_freep) char *envpw = NULL;
         int r;
 
+        assert_se(c);
+        assert_se(c->node);
         assert_se(cd);
-        assert_se(cd_node);
         assert_se(ret_vk);
-        assert_se(ret_vks);
 
         r = getenv_steal_erase("PASSWORD", &envpw);
         if (r < 0)
@@ -34,22 +33,27 @@ int load_volume_key_password(
                 r = sym_crypt_volume_key_get(
                                 cd,
                                 CRYPT_ANY_SLOT,
-                                ret_vk,
-                                ret_vks,
+                                ret_vk->iov_base,
+                                &ret_vk->iov_len,
                                 envpw,
                                 strlen(envpw));
                 if (r < 0)
                         return log_error_errno(r, "Password from environment variable $PASSWORD did not work: %m");
         } else {
                 AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED;
+
+                if (!c->interactive)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOPKG),
+                                               "Password querying disabled via 'headless' option, but no password provided for disk %s.",
+                                               c->node);
                 _cleanup_free_ char *question = NULL, *id = NULL, *disk_path = NULL;
                 unsigned i = 5;
 
-                question = strjoin("Please enter current passphrase for disk ", cd_node, ":");
+                question = strjoin("Please enter current passphrase for disk ", c->node, ":");
                 if (!question)
                         return log_oom();
 
-                disk_path = cescape(cd_node);
+                disk_path = cescape(c->node);
                 if (!disk_path)
                         return log_oom();
 
@@ -84,8 +88,8 @@ int load_volume_key_password(
                                 r = sym_crypt_volume_key_get(
                                                 cd,
                                                 CRYPT_ANY_SLOT,
-                                                ret_vk,
-                                                ret_vks,
+                                                ret_vk->iov_base,
+                                                &ret_vk->iov_len,
                                                 *p,
                                                 strlen(*p));
                                 if (r >= 0)
@@ -103,6 +107,7 @@ int load_volume_key_password(
 }
 
 int enroll_password(
+                const EnrollContext *c,
                 struct crypt_device *cd,
                 const struct iovec *volume_key) {
 
@@ -111,18 +116,30 @@ int enroll_password(
         const char *node;
         int r, keyslot;
 
+        assert(c);
         assert(cd);
         assert(iovec_is_set(volume_key));
 
         assert_se(node = sym_crypt_get_device_name(cd));
 
-        r = getenv_steal_erase("NEWPASSWORD", &new_password);
-        if (r < 0)
-                return log_error_errno(r, "Failed to acquire password from environment: %m");
-        if (r == 0) {
+        if (c->passphrase) {
+                new_password = memdup_suffix0(c->passphrase, c->passphrase_size);
+                if (!new_password)
+                        return log_oom();
+        } else {
+                r = getenv_steal_erase("NEWPASSWORD", &new_password);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire password from environment: %m");
+        }
+
+        if (!new_password) {
                 _cleanup_free_ char *disk_path = NULL, *id = NULL;
                 unsigned i = 5;
 
+                if (!c->interactive)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOPKG),
+                                               "Password querying disabled via 'headless' option, but no new password provided.");
+
                 assert_se(node = sym_crypt_get_device_name(cd));
 
                 (void) suggest_passwords();
index f0c97ced2987157e5396c7c028d0fa7b543e4924..897a8a27910f21483269ea3dba70a391e79de184 100644 (file)
@@ -1,7 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "cryptenroll.h"
 #include "shared-forward.h"
 
-int load_volume_key_password(struct crypt_device *cd, const char* cd_node, void *ret_vk, size_t *ret_vks);
-int enroll_password(struct crypt_device *cd, const struct iovec *volume_key);
+int load_volume_key_password(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk);
+int enroll_password(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key);
index 13feda47c4f05de31bffe118475b6683b2bd6698..c46fcca10a125589fbaf43b7c5553cfaf582fb7b 100644 (file)
@@ -36,7 +36,7 @@ static int uri_set_private_class(const char *uri, char **ret_uri) {
 }
 #endif
 
-int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const char *uri) {
+int enroll_pkcs11(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key) {
 #if HAVE_P11KIT && HAVE_OPENSSL
         _cleanup_(erase_and_freep) void *decrypted_key = NULL;
         _cleanup_(erase_and_freep) char *base64_encoded = NULL;
@@ -50,18 +50,19 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const
         const char *node;
         int r;
 
+        assert_se(c);
         assert_se(cd);
         assert_se(iovec_is_set(volume_key));
-        assert_se(uri);
+        assert_se(c->pkcs11_token_uri);
 
         assert_se(node = sym_crypt_get_device_name(cd));
 
         r = pkcs11_acquire_public_key(
-                        uri,
+                        c->pkcs11_token_uri,
                         "volume enrollment operation",
                         "drive-harddisk",
                         "cryptenroll.pkcs11-pin",
-                        /* askpw_flags= */ 0,
+                        c->interactive ? 0 : ASK_PASSWORD_HEADLESS,
                         &pkey,
                         &rsa_padding,
                         /* ret_pin_used= */ NULL);
@@ -100,14 +101,14 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const
         /* Change 'type=cert' or 'type=public' in the provided URI to 'type=private' before storing in
            a LUKS2 header. This allows users to use output of some PKCS#11 tools directly without
            modifications. */
-        r = uri_set_private_class(uri, &private_uri);
+        r = uri_set_private_class(c->pkcs11_token_uri, &private_uri);
         if (r < 0)
                 return r;
 
         r = sd_json_buildo(&v,
                            SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")),
                            SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
-                           SD_JSON_BUILD_PAIR_STRING("pkcs11-uri", private_uri ?: uri),
+                           SD_JSON_BUILD_PAIR_STRING("pkcs11-uri", private_uri ?: c->pkcs11_token_uri),
                            SD_JSON_BUILD_PAIR_BASE64("pkcs11-key", saved_key, saved_key_size),
                            SD_JSON_BUILD_PAIR_CONDITION(rsa_padding > PKCS11_RSA_PADDING_PKCS1V15,
                                                         "pkcs11-padding", SD_JSON_BUILD_STRING(pkcs11_rsa_padding_to_string(rsa_padding))));
index b3e6e4584af93ab21c4b310e7227d9ca5de8b5d9..614c6d466cd780bd547b57a655f3a20632afe861 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "cryptenroll.h"
 #include "shared-forward.h"
 
-int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key, const char *uri);
+int enroll_pkcs11(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key);
index 85ec13f12829039a4c5a4aa28821c25956d4a992..da7b7a1018dd37b110a3dd6c7fa8de484371ed03 100644 (file)
 #include "recovery-key.h"
 
 int enroll_recovery(
+                const EnrollContext *c,
                 struct crypt_device *cd,
-                const struct iovec *volume_key) {
+                const struct iovec *volume_key,
+                char **ret_recovery_key) {
 
         _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
         _cleanup_(erase_and_freep) char *password = NULL;
@@ -21,6 +23,7 @@ int enroll_recovery(
         int keyslot, r, q;
         const char *node;
 
+        assert_se(c);
         assert_se(cd);
         assert_se(iovec_is_set(volume_key));
 
@@ -44,31 +47,33 @@ int enroll_recovery(
         if (keyslot < 0)
                 return log_error_errno(keyslot, "Failed to add new recovery key to %s: %m", node);
 
-        fflush(stdout);
-        fprintf(stderr,
-                "A secret recovery key has been generated for this volume:\n\n"
-                "    %s%s%s",
-                emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "",
-                emoji_enabled() ? " " : "",
-                ansi_highlight());
-        fflush(stderr);
+        if (c->interactive) {
+                fflush(stdout);
+                fprintf(stderr,
+                        "A secret recovery key has been generated for this volume:\n\n"
+                        "    %s%s%s",
+                        emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "",
+                        emoji_enabled() ? " " : "",
+                        ansi_highlight());
+                fflush(stderr);
 
-        fputs(password, stdout);
-        fflush(stdout);
+                fputs(password, stdout);
+                fflush(stdout);
 
-        fputs(ansi_normal(), stderr);
-        fflush(stderr);
+                fputs(ansi_normal(), stderr);
+                fflush(stderr);
 
-        fputc('\n', stdout);
-        fflush(stdout);
+                fputc('\n', stdout);
+                fflush(stdout);
 
-        fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n"
-              "regain access to the volume if the other configured access credentials have\n"
-              "been lost or forgotten. The recovery key may be entered in place of a password\n"
-              "whenever authentication is requested.\n", stderr);
-        fflush(stderr);
+                fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n"
+                      "regain access to the volume if the other configured access credentials have\n"
+                      "been lost or forgotten. The recovery key may be entered in place of a password\n"
+                      "whenever authentication is requested.\n", stderr);
+                fflush(stderr);
 
-        (void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password);
+                (void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password);
+        }
 
         if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) {
                 r = log_oom();
@@ -89,7 +94,12 @@ int enroll_recovery(
                 goto rollback;
         }
 
-        log_info("New recovery key enrolled as key slot %i.", keyslot);
+        if (c->interactive)
+                log_info("New recovery key enrolled as key slot %i.", keyslot);
+
+        if (ret_recovery_key)
+                *ret_recovery_key = TAKE_PTR(password);
+
         return keyslot;
 
 rollback:
index 0c76d86108a7b43bdb43639dfd428e81939b70b2..1655f714453a7c4429a4530c4e2eaceb34a2230e 100644 (file)
@@ -1,6 +1,9 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "cryptenroll.h"
 #include "shared-forward.h"
 
-int enroll_recovery(struct crypt_device *cd, const struct iovec *volume_key);
+/* When c->interactive is unset, the generated recovery key is returned via ret_recovery_key
+ * instead of being printed to stdout/stderr + rendered as a QR code. */
+int enroll_recovery(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key, char **ret_recovery_key);
index 2f6c7ecd7284b8276fe65239933c2ed77483a54b..eb08abdec6a566ba3c6fc4b439fd9cb2fe911dd8 100644 (file)
@@ -169,11 +169,9 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) {
 #endif
 
 int load_volume_key_tpm2(
+                const EnrollContext *c,
                 struct crypt_device *cd,
-                const char *cd_node,
-                const char *device,
-                void *ret_vk,
-                size_t *ret_vks) {
+                struct iovec *ret_vk) {
 
 #if HAVE_TPM2
         _cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
@@ -181,10 +179,10 @@ int load_volume_key_tpm2(
         ssize_t passphrase_size;
         int r;
 
+        assert_se(c);
+        assert_se(c->node);
         assert_se(cd);
-        assert_se(cd_node);
         assert_se(ret_vk);
-        assert_se(ret_vks);
 
         bool found_some = false;
         int token = 0; /* first token to look at */
@@ -235,8 +233,8 @@ int load_volume_key_tpm2(
                 found_some = true;
 
                 r = acquire_tpm2_key(
-                                cd_node,
-                                device,
+                                c->node,
+                                c->unlock_tpm2_device,
                                 hash_pcr_mask,
                                 pcr_bank,
                                 &pubkey,
@@ -255,7 +253,7 @@ int load_volume_key_tpm2(
                                 tpm2_flags,
                                 /* until= */ 0,
                                 "cryptenroll.tpm2-pin",
-                                /* askpw_flags= */ 0,
+                                c->interactive ? 0 : ASK_PASSWORD_HEADLESS,
                                 &decrypted_key);
                 if (IN_SET(r, -EACCES, -ENOLCK))
                         return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed");
@@ -275,8 +273,8 @@ int load_volume_key_tpm2(
         r = sym_crypt_volume_key_get(
                         cd,
                         CRYPT_ANY_SLOT,
-                        ret_vk,
-                        ret_vks,
+                        ret_vk->iov_base,
+                        &ret_vk->iov_len,
                         passphrase,
                         passphrase_size);
         if (r < 0)
@@ -288,19 +286,9 @@ int load_volume_key_tpm2(
 #endif
 }
 
-int enroll_tpm2(struct crypt_device *cd,
+int enroll_tpm2(const EnrollContext *c,
+                struct crypt_device *cd,
                 const struct iovec *volume_key,
-                const char *device,
-                uint32_t seal_key_handle,
-                const char *device_key,
-                Tpm2PCRValue *hash_pcr_values,
-                size_t n_hash_pcr_values,
-                const char *pcr_pubkey_path,
-                bool load_pcr_pubkey,
-                uint32_t pubkey_pcr_mask,
-                const char *signature_path,
-                bool use_pin,
-                const char *pcrlock_path,
                 int *ret_slot_to_wipe) {
 
 #if HAVE_TPM2
@@ -314,6 +302,8 @@ int enroll_tpm2(struct crypt_device *cd,
         int r, keyslot, slot_to_wipe = -1;
         TPM2Flags flags = 0;
         uint16_t primary_alg = 0;
+        /* Mutable copy: cleared on the no-public-key fallback paths below. */
+        uint32_t pubkey_pcr_mask = c->tpm2_public_key_pcr_mask;
         uint8_t binary_salt[SHA256_DIGEST_SIZE] = {};
         /*
          * erase the salt, we'd rather attempt to not have this in a coredump
@@ -323,15 +313,16 @@ int enroll_tpm2(struct crypt_device *cd,
          */
         CLEANUP_ERASE(binary_salt);
 
+        assert(c);
         assert(cd);
         assert(iovec_is_set(volume_key));
-        assert(tpm2_pcr_values_valid(hash_pcr_values, n_hash_pcr_values));
-        assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
+        assert(tpm2_pcr_values_valid(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values));
+        assert(TPM2_PCR_MASK_VALID(c->tpm2_public_key_pcr_mask));
         assert(ret_slot_to_wipe);
 
         assert_se(node = sym_crypt_get_device_name(cd));
 
-        if (use_pin) {
+        if (c->tpm2_pin) {
                 r = get_pin(&pin_str, &flags);
                 if (r < 0)
                         return r;
@@ -354,10 +345,10 @@ int enroll_tpm2(struct crypt_device *cd,
         }
 
         TPM2B_PUBLIC public = {};
-        if (pcr_pubkey_path || load_pcr_pubkey) {
-                r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
+        if (c->tpm2_public_key || c->tpm2_load_public_key) {
+                r = tpm2_load_pcr_public_key(c->tpm2_public_key, &pubkey.iov_base, &pubkey.iov_len);
                 if (r < 0) {
-                        if (pcr_pubkey_path || signature_path || r != -ENOENT)
+                        if (c->tpm2_public_key || c->tpm2_signature || r != -ENOENT)
                                 return log_error_errno(r, "Failed to read TPM PCR public key: %m");
 
                         log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
@@ -367,11 +358,11 @@ int enroll_tpm2(struct crypt_device *cd,
                         if (r < 0)
                                 return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
 
-                        if (signature_path) {
+                        if (c->tpm2_signature) {
                                 /* Also try to load the signature JSON object, to verify that our enrollment will work.
                                  * This is optional however, skip it if it's not explicitly provided. */
 
-                                r = tpm2_load_pcr_signature(signature_path, &signature_json);
+                                r = tpm2_load_pcr_signature(c->tpm2_signature, &signature_json);
                                 if (r < 0)
                                         return log_error_errno(r, "Failed to read TPM PCR signature: %m");
                         }
@@ -379,15 +370,15 @@ int enroll_tpm2(struct crypt_device *cd,
         } else
                 pubkey_pcr_mask = 0;
 
-        bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values);
+        bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values);
 
         _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {};
-        if (pcrlock_path) {
-                r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy);
+        if (c->tpm2_pcrlock) {
+                r = tpm2_pcrlock_policy_load(c->tpm2_pcrlock, &pcrlock_policy);
                 if (r < 0)
                         return r;
                 if (r == 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find pcrlock policy %s.", pcrlock_path);
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find pcrlock policy %s.", c->tpm2_pcrlock);
 
                 any_pcr_value_specified = true;
                 flags |= TPM2_FLAGS_USE_PCRLOCK;
@@ -395,23 +386,23 @@ int enroll_tpm2(struct crypt_device *cd,
 
         _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
         TPM2B_PUBLIC device_key_public = {};
-        if (device_key) {
-                r = tpm2_load_public_key_file(device_key, &device_key_public);
+        if (c->tpm2_device_key) {
+                r = tpm2_load_public_key_file(c->tpm2_device_key, &device_key_public);
                 if (r < 0)
                         return r;
 
-                if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values))
+                if (!tpm2_pcr_values_has_all_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values))
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "Must provide all PCR values when using TPM2 device key.");
 
                 primary_alg = device_key_public.publicArea.type;
         } else {
-                r = tpm2_context_new_or_warn(device, &tpm2_context);
+                r = tpm2_context_new_or_warn(c->tpm2_device, &tpm2_context);
                 if (r < 0)
                         return r;
 
-                if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) {
-                        r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values);
+                if (!tpm2_pcr_values_has_all_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values)) {
+                        r = tpm2_pcr_read_missing_values(tpm2_context, c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values);
                         if (r < 0)
                                 return log_error_errno(r, "Could not read pcr values: %m");
                 }
@@ -420,10 +411,10 @@ int enroll_tpm2(struct crypt_device *cd,
         uint16_t hash_pcr_bank = 0;
         uint32_t hash_pcr_mask = 0;
 
-        if (n_hash_pcr_values > 0) {
+        if (c->tpm2_n_hash_pcr_values > 0) {
                 size_t hash_count;
 
-                r = tpm2_pcr_values_hash_count(hash_pcr_values, n_hash_pcr_values, &hash_count);
+                r = tpm2_pcr_values_hash_count(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values, &hash_count);
                 if (r < 0)
                         return log_error_errno(r, "Could not get hash count: %m");
 
@@ -431,12 +422,12 @@ int enroll_tpm2(struct crypt_device *cd,
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected.");
 
                 /* If we use a literal PCR value policy, derive the bank to use from the algorithm specified on the hash values */
-                hash_pcr_bank = hash_pcr_values[0].hash;
-                r = tpm2_pcr_values_to_mask(hash_pcr_values, n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask);
+                hash_pcr_bank = c->tpm2_hash_pcr_values[0].hash;
+                r = tpm2_pcr_values_to_mask(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask);
                 if (r < 0)
                         return log_error_errno(r, "Could not get hash mask: %m");
 
-        } else if (pubkey_pcr_mask != 0 && !device_key) {
+        } else if (pubkey_pcr_mask != 0 && !c->tpm2_device_key) {
 
                 /* If no literal PCR value policy is used, then let's determine the mask to use automatically
                  * from the measurements of the TPM. */
@@ -461,21 +452,21 @@ int enroll_tpm2(struct crypt_device *cd,
 
         /* If both PCR public key unlock and pcrlock unlock is selected, then we create the one for PCR public key unlock first. */
         r = tpm2_calculate_sealing_policy(
-                        hash_pcr_values,
-                        n_hash_pcr_values,
+                        c->tpm2_hash_pcr_values,
+                        c->tpm2_n_hash_pcr_values,
                         iovec_is_set(&pubkey) ? &public : NULL,
-                        use_pin,
-                        pcrlock_path && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
+                        c->tpm2_pin,
+                        c->tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
                         policy_hash + 0);
         if (r < 0)
                 return r;
 
-        if (pcrlock_path && iovec_is_set(&pubkey)) {
+        if (c->tpm2_pcrlock && iovec_is_set(&pubkey)) {
                 r = tpm2_calculate_sealing_policy(
-                                hash_pcr_values,
-                                n_hash_pcr_values,
+                                c->tpm2_hash_pcr_values,
+                                c->tpm2_n_hash_pcr_values,
                                 /* public= */ NULL, /* This one is off now */
-                                use_pin,
+                                c->tpm2_pin,
                                 &pcrlock_policy,    /* And this one on instead. */
                                 policy_hash + 1);
                 if (r < 0)
@@ -488,7 +479,7 @@ int enroll_tpm2(struct crypt_device *cd,
         size_t n_blobs = 0;
         CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
 
-        if (device_key) {
+        if (c->tpm2_device_key) {
                 if (n_policy_hash > 1)
                         return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                                "Combined signed PCR policies and pcrlock policies cannot be calculated offline, currently.");
@@ -500,7 +491,7 @@ int enroll_tpm2(struct crypt_device *cd,
                 n_blobs = 1;
 
                 r = tpm2_calculate_seal(
-                                seal_key_handle,
+                                c->tpm2_seal_key_handle,
                                 &device_key_public,
                                 /* attributes= */ NULL,
                                 /* secret= */ NULL,
@@ -511,7 +502,7 @@ int enroll_tpm2(struct crypt_device *cd,
                                 &srk);
         } else
                 r = tpm2_seal(tpm2_context,
-                              seal_key_handle,
+                              c->tpm2_seal_key_handle,
                               policy_hash,
                               n_policy_hash,
                               pin_str,
@@ -534,7 +525,7 @@ int enroll_tpm2(struct crypt_device *cd,
                 log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now.");
         else if (r < 0)
                 return r;
-        else if (use_pin) {
+        else if (c->tpm2_pin) {
                 log_debug("This PCR set is already enrolled, re-enrolling anyway to update PIN.");
                 slot_to_wipe = r;
         } else {
@@ -544,7 +535,7 @@ int enroll_tpm2(struct crypt_device *cd,
         }
 
         /* If possible, verify the sealed data object. */
-        if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !device_key) {
+        if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !c->tpm2_device_key) {
                 _cleanup_(iovec_done_erase) struct iovec secret2 = {};
 
                 log_debug("Unsealing for verification...");
@@ -555,7 +546,7 @@ int enroll_tpm2(struct crypt_device *cd,
                                 pubkey_pcr_mask,
                                 signature_json,
                                 pin_str,
-                                pcrlock_path ? &pcrlock_policy : NULL,
+                                c->tpm2_pcrlock ? &pcrlock_policy : NULL,
                                 primary_alg,
                                 blobs,
                                 n_blobs,
@@ -600,9 +591,9 @@ int enroll_tpm2(struct crypt_device *cd,
                         n_blobs,
                         policy_hash_as_iovec,
                         n_policy_hash,
-                        use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
+                        c->tpm2_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
                         &srk,
-                        pcrlock_path ? &pcrlock_policy.nv_handle : NULL,
+                        c->tpm2_pcrlock ? &pcrlock_policy.nv_handle : NULL,
                         flags,
                         &v);
         if (r < 0)
index 48f6c12b7f196d0b9d5a2b6762788dccb46ed56b..3d57b8f8f38fba632c217d8c3c626aa628612478 100644 (file)
@@ -1,7 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "cryptenroll.h"
 #include "shared-forward.h"
 
-int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks);
-int enroll_tpm2(struct crypt_device *cd, const struct iovec *volume_key, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcr_values, size_t n_hash_pcr_values, const char *pubkey_path, bool load_pcr_pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *ret_slot_to_wipe);
+int load_volume_key_tpm2(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk);
+int enroll_tpm2(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key, int *ret_slot_to_wipe);
index a8638d2b3a77704d0c95926029798198fc8ecfd8..b7d554c3904d71430669b34e4c0c357f7e55bb60 100644 (file)
@@ -296,12 +296,8 @@ static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slo
         return false;
 }
 
-int wipe_slots(struct crypt_device *cd,
-               const int explicit_slots[],
-               size_t n_explicit_slots,
-               WipeScope by_scope,
-               unsigned by_mask,
-               int except_slot) {
+int wipe_slots(const EnrollContext *c,
+               struct crypt_device *cd) {
 
         _cleanup_set_free_ Set *wipe_slots = NULL, *wipe_tokens = NULL, *keep_slots = NULL;
         _cleanup_free_ int *ordered_slots = NULL, *ordered_tokens = NULL;
@@ -309,10 +305,11 @@ int wipe_slots(struct crypt_device *cd,
         int r, slot_max, ret;
         void *e;
 
+        assert_se(c);
         assert_se(cd);
 
         /* Shortcut if nothing to wipe. */
-        if (n_explicit_slots == 0 && by_mask == 0 && by_scope == WIPE_EXPLICIT)
+        if (c->n_wipe_slots == 0 && c->wipe_slots_mask == 0 && c->wipe_slots_scope == WIPE_EXPLICIT)
                 return 0;
 
         /* So this is a bit more complicated than I'd wish, but we want support three different axis for wiping slots:
@@ -334,23 +331,23 @@ int wipe_slots(struct crypt_device *cd,
                 return log_oom();
 
         /* Let's maintain one set of slots for the slots we definitely want to keep */
-        if (except_slot >= 0)
-                if (set_put(keep_slots, INT_TO_PTR(except_slot)) < 0)
+        if (c->wipe_except_slot >= 0)
+                if (set_put(keep_slots, INT_TO_PTR(c->wipe_except_slot)) < 0)
                         return log_oom();
 
         assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0);
 
         /* Maintain another set of the slots we intend to wipe */
-        for (size_t i = 0; i < n_explicit_slots; i++) {
-                if (explicit_slots[i] >= slot_max)
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Slot index %i out of range.", explicit_slots[i]);
+        for (size_t i = 0; i < c->n_wipe_slots; i++) {
+                if (c->wipe_slots[i] >= slot_max)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Slot index %i out of range.", c->wipe_slots[i]);
 
-                if (set_put(wipe_slots, INT_TO_PTR(explicit_slots[i])) < 0)
+                if (set_put(wipe_slots, INT_TO_PTR(c->wipe_slots[i])) < 0)
                         return log_oom();
         }
 
         /* Now, handle the "all" and "empty passphrase" cases. */
-        switch (by_scope) {
+        switch (c->wipe_slots_scope) {
 
         case WIPE_EXPLICIT:
                 break; /* Nothing to do here */
@@ -373,7 +370,7 @@ int wipe_slots(struct crypt_device *cd,
         }
 
         /* Then add all slots that match a token type */
-        r = find_slots_by_mask(cd, wipe_slots, keep_slots, by_mask);
+        r = find_slots_by_mask(cd, wipe_slots, keep_slots, c->wipe_slots_mask);
         if (r < 0)
                 return r;
 
@@ -409,7 +406,7 @@ int wipe_slots(struct crypt_device *cd,
         typesafe_qsort(ordered_tokens, n_ordered_tokens, cmp_int);
 
         if (n_ordered_slots == 0 && n_ordered_tokens == 0) {
-                log_full(except_slot < 0 ? LOG_NOTICE : LOG_DEBUG,
+                log_full(c->wipe_except_slot < 0 ? LOG_NOTICE : LOG_DEBUG,
                          "No slots to remove selected.");
                 return 0;
         }
index aa82f9dbbd34274a67ba0014d9344d71d1f93d3e..4e715191818c6eec8ea5187dd183b1879738c755 100644 (file)
@@ -1,13 +1,9 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "cryptenroll.h"
 #include "shared-forward.h"
 
-typedef enum WipeScope WipeScope;
-
-int wipe_slots(struct crypt_device *cd,
-               const int explicit_slots[],
-               size_t n_explicit_slots,
-               WipeScope by_scope,
-               unsigned by_mask,
-               int except_slot);
+/* Wipes the slots selected by c->wipe_slots / c->n_wipe_slots / c->wipe_slots_scope /
+ * c->wipe_slots_mask, except for c->wipe_except_slot (set to -1 for none). */
+int wipe_slots(const EnrollContext *c, struct crypt_device *cd);
index eed0a11aeba7539d70b0e0d0c6e93c093aa06dd5..0099c3c4a5cabf1129bfa1d204fb6c5cf7567f65 100644 (file)
@@ -3,7 +3,9 @@
 #include <sys/mman.h>
 
 #include "sd-device.h"
+#include "sd-varlink.h"
 
+#include "alloc-util.h"
 #include "blockdev-list.h"
 #include "blockdev-util.h"
 #include "build.h"
@@ -22,6 +24,7 @@
 #include "libfido2-util.h"
 #include "log.h"
 #include "main-func.h"
+#include "memory-util.h"
 #include "options.h"
 #include "pager.h"
 #include "parse-argument.h"
@@ -110,6 +113,29 @@ static const char *const luks2_token_type_table[_ENROLL_TYPE_MAX] = {
 
 DEFINE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType);
 
+void enroll_context_done(EnrollContext *c) {
+        if (!c)
+                return;
+
+        c->node = mfree(c->node);
+        c->unlock_keyfile = mfree(c->unlock_keyfile);
+        c->unlock_fido2_device = mfree(c->unlock_fido2_device);
+        c->unlock_tpm2_device = mfree(c->unlock_tpm2_device);
+        c->passphrase = erase_and_free(c->passphrase);
+        c->fido2_device = mfree(c->fido2_device);
+        c->fido2_salt_file = mfree(c->fido2_salt_file);
+        c->fido2_pin = erase_and_free(c->fido2_pin);
+        c->pkcs11_token_uri = mfree(c->pkcs11_token_uri);
+        c->tpm2_device = mfree(c->tpm2_device);
+        c->tpm2_device_key = mfree(c->tpm2_device_key);
+        c->tpm2_hash_pcr_values = mfree(c->tpm2_hash_pcr_values);
+        c->tpm2_public_key = mfree(c->tpm2_public_key);
+        c->tpm2_signature = mfree(c->tpm2_signature);
+        c->tpm2_pcrlock = mfree(c->tpm2_pcrlock);
+        c->wipe_slots = mfree(c->wipe_slots);
+        c->link = sd_varlink_unref(c->link);
+}
+
 static int determine_default_node(void) {
         int r;
 
@@ -661,21 +687,21 @@ static int check_for_homed(struct crypt_device *cd) {
 }
 
 static int load_volume_key_keyfile(
+                const EnrollContext *c,
                 struct crypt_device *cd,
-                void *ret_vk,
-                size_t *ret_vks) {
+                struct iovec *ret_vk) {
 
         _cleanup_(erase_and_freep) char *password = NULL;
         size_t password_len;
         int r;
 
+        assert_se(c);
         assert_se(cd);
         assert_se(ret_vk);
-        assert_se(ret_vks);
 
         r = read_full_file_full(
                         AT_FDCWD,
-                        arg_unlock_keyfile,
+                        c->unlock_keyfile,
                         UINT64_MAX,
                         SIZE_MAX,
                         READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
@@ -683,13 +709,13 @@ static int load_volume_key_keyfile(
                         &password,
                         &password_len);
         if (r < 0)
-                return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile);
+                return log_error_errno(r, "Reading keyfile %s failed: %m", c->unlock_keyfile);
 
         r = sym_crypt_volume_key_get(
                         cd,
                         CRYPT_ANY_SLOT,
-                        ret_vk,
-                        ret_vks,
+                        ret_vk->iov_base,
+                        &ret_vk->iov_len,
                         password,
                         password_len);
         if (r < 0)
@@ -699,15 +725,17 @@ static int load_volume_key_keyfile(
 }
 
 static int prepare_luks(
+                const EnrollContext *c,
                 struct crypt_device **ret_cd,
                 struct iovec *ret_volume_key) {
 
         _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
         int r;
 
+        assert(c);
         assert(ret_cd);
 
-        r = sym_crypt_init(&cd, arg_node);
+        r = sym_crypt_init(&cd, c->node);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
 
@@ -715,7 +743,7 @@ static int prepare_luks(
 
         r = sym_crypt_load(cd, CRYPT_LUKS2, NULL);
         if (r < 0)
-                return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", arg_node);
+                return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", c->node);
 
         r = check_for_homed(cd);
         if (r < 0)
@@ -731,35 +759,33 @@ static int prepare_luks(
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
 
         _cleanup_(iovec_done_erase) struct iovec vk = {};
-
         vk.iov_base = malloc(r);
         if (!vk.iov_base)
                 return log_oom();
 
         vk.iov_len = (size_t) r;
 
-        switch (arg_unlock_type) {
+        switch (c->unlock_type) {
 
         case UNLOCK_PASSWORD:
-                r = load_volume_key_password(cd, arg_node, vk.iov_base, &vk.iov_len);
+                r = load_volume_key_password(c, cd, &vk);
                 break;
 
         case UNLOCK_KEYFILE:
-                r = load_volume_key_keyfile(cd, vk.iov_base, &vk.iov_len);
+                r = load_volume_key_keyfile(c, cd, &vk);
                 break;
 
         case UNLOCK_FIDO2:
-                r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk.iov_base, &vk.iov_len);
+                r = load_volume_key_fido2(c, cd, &vk);
                 break;
 
         case UNLOCK_TPM2:
-                r = load_volume_key_tpm2(cd, arg_node, arg_unlock_tpm2_device, vk.iov_base, &vk.iov_len);
+                r = load_volume_key_tpm2(c, cd, &vk);
                 break;
 
         default:
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown LUKS unlock method");
         }
-
         if (r < 0)
                 return r;
 
@@ -769,9 +795,62 @@ static int prepare_luks(
         return 0;
 }
 
+static int enroll_context_from_args(EnrollContext *c) {
+        assert(c);
+
+        /* Copies the parsed command line parameters from the static arg_* globals into a self-contained
+         * EnrollContext. The context owns its own copies of all strings/arrays, so it can be torn down
+         * independently of the arg_* destructors. */
+
+        *c = ENROLL_CONTEXT_NULL;
+
+        c->enroll_type = arg_enroll_type;
+        c->unlock_type = arg_unlock_type;
+        c->fido2_parameters_in_header = arg_fido2_parameters_in_header;
+        c->fido2_lock_with = arg_fido2_lock_with;
+        c->fido2_cred_alg = arg_fido2_cred_alg;
+        c->tpm2_seal_key_handle = arg_tpm2_seal_key_handle;
+        c->tpm2_pin = arg_tpm2_pin;
+        c->tpm2_load_public_key = arg_tpm2_load_public_key;
+        c->tpm2_public_key_pcr_mask = arg_tpm2_public_key_pcr_mask;
+        c->wipe_slots_scope = arg_wipe_slots_scope;
+        c->wipe_slots_mask = arg_wipe_slots_mask;
+
+        if (strdup_to(&c->node, arg_node) < 0 ||
+            strdup_to(&c->unlock_keyfile, arg_unlock_keyfile) < 0 ||
+            strdup_to(&c->unlock_fido2_device, arg_unlock_fido2_device) < 0 ||
+            strdup_to(&c->unlock_tpm2_device, arg_unlock_tpm2_device) < 0 ||
+            strdup_to(&c->fido2_device, arg_fido2_device) < 0 ||
+            strdup_to(&c->fido2_salt_file, arg_fido2_salt_file) < 0 ||
+            strdup_to(&c->pkcs11_token_uri, arg_pkcs11_token_uri) < 0 ||
+            strdup_to(&c->tpm2_device, arg_tpm2_device) < 0 ||
+            strdup_to(&c->tpm2_device_key, arg_tpm2_device_key) < 0 ||
+            strdup_to(&c->tpm2_public_key, arg_tpm2_public_key) < 0 ||
+            strdup_to(&c->tpm2_signature, arg_tpm2_signature) < 0 ||
+            strdup_to(&c->tpm2_pcrlock, arg_tpm2_pcrlock) < 0)
+                return log_oom();
+
+        if (arg_n_wipe_slots > 0) {
+                c->wipe_slots = newdup(int, arg_wipe_slots, arg_n_wipe_slots);
+                if (!c->wipe_slots)
+                        return log_oom();
+                c->n_wipe_slots = arg_n_wipe_slots;
+        }
+
+        if (arg_tpm2_n_hash_pcr_values > 0) {
+                c->tpm2_hash_pcr_values = newdup(Tpm2PCRValue, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values);
+                if (!c->tpm2_hash_pcr_values)
+                        return log_oom();
+                c->tpm2_n_hash_pcr_values = arg_tpm2_n_hash_pcr_values;
+        }
+
+        return 0;
+}
+
 static int run(int argc, char *argv[]) {
         _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
         _cleanup_(iovec_done_erase) struct iovec vk = {};
+        _cleanup_(enroll_context_done) EnrollContext c = ENROLL_CONTEXT_NULL;
         int slot, slot_to_wipe, r;
 
         log_setup();
@@ -787,45 +866,49 @@ static int run(int argc, char *argv[]) {
         /* A delicious drop of snake oil */
         (void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT);
 
-        if (arg_enroll_type < 0)
-                r = prepare_luks(&cd, /* ret_volume_key= */ NULL); /* No need to unlock device if we don't need the volume key because we don't need to enroll anything */
+        r = enroll_context_from_args(&c);
+        if (r < 0)
+                return r;
+
+        if (c.enroll_type < 0)
+                r = prepare_luks(&c, &cd, /* ret_volume_key= */ NULL); /* No need to unlock device if we don't need the volume key because we don't need to enroll anything */
         else
-                r = prepare_luks(&cd, &vk);
+                r = prepare_luks(&c, &cd, &vk);
         if (r < 0)
                 return r;
 
-        switch (arg_enroll_type) {
+        switch (c.enroll_type) {
 
         case ENROLL_PASSWORD:
-                slot = enroll_password(cd, &vk);
+                slot = enroll_password(&c, cd, &vk);
                 break;
 
         case ENROLL_RECOVERY:
-                slot = enroll_recovery(cd, &vk);
+                slot = enroll_recovery(&c, cd, &vk, /* ret_recovery_key= */ NULL);
                 break;
 
         case ENROLL_PKCS11:
-                slot = enroll_pkcs11(cd, &vk, arg_pkcs11_token_uri);
+                slot = enroll_pkcs11(&c, cd, &vk);
                 break;
 
         case ENROLL_FIDO2:
-                slot = enroll_fido2(cd, &vk, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg, arg_fido2_salt_file, arg_fido2_parameters_in_header);
+                slot = enroll_fido2(&c, cd, &vk);
                 break;
 
         case ENROLL_TPM2:
-                slot = enroll_tpm2(cd, &vk, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_load_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin, arg_tpm2_pcrlock, &slot_to_wipe);
+                slot = enroll_tpm2(&c, cd, &vk, &slot_to_wipe);
 
                 if (slot >= 0 && slot_to_wipe >= 0) {
                         assert(slot != slot_to_wipe);
 
-                        /* Updating PIN on an existing enrollment */
-                        r = wipe_slots(
-                                        cd,
-                                        &slot_to_wipe,
-                                        /* n_explicit_slots= */ 1,
-                                        WIPE_EXPLICIT,
-                                        /* by_mask= */ 0,
-                                        /* except_slot= */ -1);
+                        /* Updating PIN on an existing enrollment: wipe just that one slot. This is an
+                         * internal one-off wipe that is unrelated to the user's wipe selection, so use a
+                         * throwaway context referencing a single explicit slot. */
+                        EnrollContext wipe_ctx = ENROLL_CONTEXT_NULL;
+                        wipe_ctx.wipe_slots = &slot_to_wipe;
+                        wipe_ctx.n_wipe_slots = 1;
+
+                        r = wipe_slots(&wipe_ctx, cd);
                         if (r < 0)
                                 return r;
                 }
@@ -836,7 +919,7 @@ static int run(int argc, char *argv[]) {
                         return list_enrolled(cd);
 
                 /* Only slot wiping selected */
-                return wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, -1);
+                return wipe_slots(&c, cd);
 
         default:
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet.");
@@ -844,8 +927,9 @@ static int run(int argc, char *argv[]) {
         if (slot < 0)
                 return slot;
 
-        /* After we completed enrolling, remove user selected slots */
-        r = wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, slot);
+        /* After we completed enrolling, remove user selected slots (keeping the one we just added) */
+        c.wipe_except_slot = slot;
+        r = wipe_slots(&c, cd);
         if (r < 0)
                 return r;
 
index 1066ecc18d9aa9483bfdd628ded4f38d2dd9bf1d..3e598487042e6276d2e21dfa4bb35c189a39a95f 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "libfido2-util.h"
 #include "shared-forward.h"
 
 typedef enum EnrollType {
@@ -32,3 +33,79 @@ typedef enum WipeScope {
 
 DECLARE_STRING_TABLE_LOOKUP(enroll_type, EnrollType);
 DECLARE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType);
+
+/* A single bag of parameters consumed by the enrollment helpers. Populated either from the command line
+ * (see enroll_context_from_args() in cryptenroll.c), from a Varlink request (see cryptenroll-varlink.c),
+ * or by the interactive wizard (see cryptenroll-interactive.c). Owns all strings/arrays it points to. */
+typedef struct EnrollContext {
+        EnrollType enroll_type;
+        UnlockType unlock_type;
+
+        /* Target device */
+        char *node;
+
+        /* Unlock side */
+        char *unlock_keyfile;
+        char *unlock_fido2_device;
+        char *unlock_tpm2_device;
+
+        /* New password to enroll (mechanism == password). When NULL the helpers fall back to
+         * $NEWPASSWORD / askpw. */
+        char *passphrase;
+        size_t passphrase_size;
+
+        /* FIDO2 */
+        char *fido2_device;
+        char *fido2_salt_file;
+        bool fido2_parameters_in_header;
+        Fido2EnrollFlags fido2_lock_with;
+        int fido2_cred_alg;
+        char *fido2_pin;                /* optional pre-supplied token PIN; NULL means prompt (if interactive) */
+
+        /* PKCS#11 */
+        char *pkcs11_token_uri;
+
+        /* TPM2 */
+        char *tpm2_device;
+        uint32_t tpm2_seal_key_handle;
+        char *tpm2_device_key;
+        Tpm2PCRValue *tpm2_hash_pcr_values;
+        size_t tpm2_n_hash_pcr_values;
+        bool tpm2_pin;
+        char *tpm2_public_key;
+        bool tpm2_load_public_key;
+        uint32_t tpm2_public_key_pcr_mask;
+        char *tpm2_signature;
+        char *tpm2_pcrlock;
+
+        /* Wipe selection */
+        int *wipe_slots;
+        size_t n_wipe_slots;
+        WipeScope wipe_slots_scope;
+        unsigned wipe_slots_mask;
+        int wipe_except_slot;           /* slot to never wipe (e.g. the one we just enrolled); -1 for none */
+
+        /* If false, the enrollment helpers must never prompt the user (no askpw, no terminal I/O,
+         * no log printing of credential material). They use the fields in this context as the
+         * sole input, and fail with ENOPKG if a required piece of input is missing. CLI and
+         * interactive callers set this to true; the Varlink dispatcher sets it to false. */
+        bool interactive;
+
+        /* Varlink link the request came in on, if the caller asked for 'more'. NULL otherwise.
+         * Owned (sd_varlink_ref'd) by the context. */
+        sd_varlink *link;
+} EnrollContext;
+
+#define ENROLL_CONTEXT_NULL                                             \
+        (EnrollContext) {                                               \
+                .enroll_type = _ENROLL_TYPE_INVALID,                    \
+                .unlock_type = UNLOCK_PASSWORD,                         \
+                .fido2_parameters_in_header = true,                     \
+                .fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP,    \
+                .tpm2_load_public_key = true,                           \
+                .wipe_slots_scope = WIPE_EXPLICIT,                      \
+                .wipe_except_slot = -1,                                 \
+                .interactive = true,                                    \
+        }
+
+void enroll_context_done(EnrollContext *c);
index d351b1db5e113b693554b851a17c4d1af530a6de..210c050284d3b586292dde34ff36832b4c1a2c24 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "alloc-util.h"
+#include "ask-password-api.h" /* IWYU pragma: keep */
 #include "errno-util.h"
 #include "fido2-util.h"
 #include "hexdecoct.h"
@@ -174,6 +175,8 @@ int identity_add_fido2_parameters(
                         /* user_icon= */ NULL,
                         /* askpw_icon= */ "user-home",
                         /* askpw_credential= */ "home.token-pin",
+                        /* askpw_flags= */ 0,
+                        /* pin= */ NULL,
                         lock_with,
                         cred_alg,
                         &salt,
index 5f69dc8924917ae1e63b0b05858ebbe5dd865790..a7c776d0ce3a1f0b2d1cae8a0575eefeac8be3e8 100644 (file)
@@ -746,6 +746,8 @@ int fido2_generate_hmac_hash(
                 const char *user_icon,
                 const char *askpw_icon,
                 const char *askpw_credential,
+                AskPasswordFlags askpw_flags,
+                const char *pin,
                 Fido2EnrollFlags lock_with,
                 int cred_alg,
                 const struct iovec *salt,
@@ -920,48 +922,57 @@ int fido2_generate_hmac_hash(
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "Token asks for PIN but doesn't advertise 'clientPin' feature.");
 
-                AskPasswordFlags askpw_flags = ASK_PASSWORD_ACCEPT_CACHED;
+                AskPasswordFlags pin_askpw_flags = askpw_flags | ASK_PASSWORD_ACCEPT_CACHED;
 
                 for (;;) {
-                        _cleanup_strv_free_erase_ char **pin = NULL;
-                        _cleanup_free_ char *ask_pin_msg = NULL;
-                        int pin_retries = -1;
-
-                        r = sym_fido_dev_get_retry_count(d, &pin_retries);
-                        if (r != FIDO_OK) {
-                                log_warning("Failed to obtain number of retries before lock-out for PIN "
-                                            "authentication, ignoring: %s", sym_fido_strerr(r));
-                                pin_retries = -1;
-                        }
+                        _cleanup_strv_free_erase_ char **pins = NULL;
 
-                        if (pin_retries >= 0) {
-                                ask_pin_msg = asprintf_safe(_("Please enter security token PIN "
-                                                            "(remaining attempts before lock-out: %d):"),
-                                                            pin_retries);
-                                if (!ask_pin_msg)
+                        if (pin) {
+                                /* A PIN was supplied by the caller: use it directly, don't prompt. */
+                                pins = strv_new(pin);
+                                if (!pins)
                                         return log_oom();
-                        }
-
-                        AskPasswordRequest req = {
-                                .tty_fd = -EBADF,
-                                .message = pin_retries >= 0
-                                        ? ask_pin_msg
-                                        : _("Please enter security token PIN:"),
-                                .icon = askpw_icon,
-                                .keyring = "fido2-pin",
-                                .credential = askpw_credential,
-                                .until = USEC_INFINITY,
-                                .hup_fd = -EBADF,
-                        };
+                        } else if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS))
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option.");
+                        else {
+                                int pin_retries = -1;
+                                r = sym_fido_dev_get_retry_count(d, &pin_retries);
+                                if (r != FIDO_OK) {
+                                        log_warning("Failed to obtain number of retries before lock-out for PIN "
+                                                    "authentication, ignoring: %s", sym_fido_strerr(r));
+                                        pin_retries = -1;
+                                }
 
-                        r = ask_password_auto(&req, askpw_flags, &pin);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to acquire user PIN: %m");
+                                _cleanup_free_ char *ask_pin_msg = NULL;
+                                if (pin_retries >= 0) {
+                                        ask_pin_msg = asprintf_safe(_("Please enter security token PIN "
+                                                                      "(remaining attempts before lock-out: %d):"),
+                                                                    pin_retries);
+                                        if (!ask_pin_msg)
+                                                return log_oom();
+                                }
 
-                        askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
+                                AskPasswordRequest req = {
+                                        .tty_fd = -EBADF,
+                                        .message = pin_retries >= 0
+                                                   ? ask_pin_msg
+                                                   : _("Please enter security token PIN:"),
+                                        .icon = askpw_icon,
+                                        .keyring = "fido2-pin",
+                                        .credential = askpw_credential,
+                                        .until = USEC_INFINITY,
+                                        .hup_fd = -EBADF,
+                                };
+
+                                r = ask_password_auto(&req, pin_askpw_flags, &pins);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to acquire user PIN: %m");
+
+                                pin_askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
+                        }
 
                         r = FIDO_ERR_PIN_INVALID;
-                        STRV_FOREACH(i, pin) {
+                        STRV_FOREACH(i, pins) {
                                 if (isempty(*i)) {
                                         log_notice("PIN may not be empty.");
                                         continue;
@@ -981,6 +992,11 @@ int fido2_generate_hmac_hash(
                         if (r != FIDO_ERR_PIN_INVALID)
                                 break;
 
+                        /* A caller-supplied PIN that's wrong won't get better by retrying: fail instead of
+                         * looping forever (we'd never prompt). */
+                        if (pin)
+                                break;
+
                         log_notice("PIN incorrect, please try again.");
                 }
         }
@@ -996,6 +1012,9 @@ int fido2_generate_hmac_hash(
         if (r == FIDO_ERR_UNSUPPORTED_ALGORITHM)
                 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                        "Token doesn't support credential algorithm %s.", fido2_algorithm_to_string(cred_alg));
+        if (r == FIDO_ERR_PIN_INVALID)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "PIN incorrect.");
         if (r != FIDO_OK)
                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
                                        "Failed to generate FIDO2 credential: %s", sym_fido_strerr(r));
index bbf6d1ca66a0814087957288eb68743d90d8a620..6b23b62688bd871d4713f0400546f41c984e2eb9 100644 (file)
@@ -117,6 +117,8 @@ int fido2_generate_hmac_hash(
                 const char *user_icon,
                 const char *askpw_icon,
                 const char *askpw_credential,
+                AskPasswordFlags askpw_flags,
+                const char *pin,
                 Fido2EnrollFlags lock_with,
                 int cred_alg,
                 const struct iovec *salt,