]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptenroll: Implement support for unlocking via FIDO2 tokens 25777/head
authorPeter Cai <peter@typeblog.net>
Sun, 18 Dec 2022 01:33:05 +0000 (20:33 -0500)
committerPeter Cai <peter@typeblog.net>
Thu, 22 Dec 2022 22:33:25 +0000 (17:33 -0500)
This allows FIDO2 users to wipe out password slots and still be able to
enroll new key slots via systemd-cryptenroll. Note that when the user
wants to both unlock with a FIDO2 token and enroll a new FIDO2 token,
they cannot be set to automatic discovery. This is to safeguard against
confusion, because there will be multiple tokens connected to the system
when doing so -- and we require users to explicitly confirm which one to
use for unlocking and which one to use for enrollment.

Addresses #20230 for the FIDO2 case.

src/cryptenroll/cryptenroll-fido2.c
src/cryptenroll/cryptenroll-fido2.h
src/cryptenroll/cryptenroll-password.c
src/cryptenroll/cryptenroll-password.h
src/cryptenroll/cryptenroll.c
src/cryptenroll/cryptenroll.h

index 80adaefa17485d305e518d09e9a2ed5e55af7eb0..e49b4a0cfe9b8a73791136ef950229818c7b7139 100644 (file)
@@ -1,12 +1,65 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include "ask-password-api.h"
 #include "cryptenroll-fido2.h"
+#include "cryptsetup-fido2.h"
 #include "hexdecoct.h"
 #include "json.h"
 #include "libfido2-util.h"
 #include "memory-util.h"
 #include "random-util.h"
 
+int load_volume_key_fido2(
+                struct crypt_device *cd,
+                const char *cd_node,
+                const char *device,
+                void *ret_vk,
+                size_t *ret_vks) {
+
+        _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+        _cleanup_(erase_and_freep) char *passphrase = NULL;
+        size_t decrypted_key_size;
+        int r;
+
+        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,
+                        /* until= */ 0,
+                        /* headless= */ false,
+                        &decrypted_key,
+                        &decrypted_key_size,
+                        ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED);
+        if (r == -EAGAIN)
+                return log_error_errno(r, "FIDO2 token does not exist, or UV is blocked. Please try again.");
+        if (r < 0)
+                return r;
+
+        /* Because cryptenroll requires a LUKS header, we can assume that this device is not
+         * a PLAIN device. In this case, we need to base64 encode the secret to use as the passphrase */
+        r = base64mem(decrypted_key, decrypted_key_size, &passphrase);
+        if (r < 0)
+                return log_oom();
+
+        r = crypt_volume_key_get(
+                        cd,
+                        CRYPT_ANY_SLOT,
+                        ret_vk,
+                        ret_vks,
+                        passphrase,
+                        /* passphrase_size= */ r);
+        if (r < 0)
+                return log_error_errno(r, "Unlocking via FIDO2 device failed: %m");
+
+        return r;
+}
+
 int enroll_fido2(
                 struct crypt_device *cd,
                 const void *volume_key,
index 11667afe9ca3d37243c6e5311903c964126e92d2..3315308e4d4f6bfc193d979ab01d198fb98e1221 100644 (file)
@@ -8,8 +8,15 @@
 #include "log.h"
 
 #if HAVE_LIBFIDO2
+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 void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg);
+
 #else
+static inline int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) {
+        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                               "FIDO2 unlocking not supported.");
+}
+
 static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg) {
         return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                "FIDO2 key enrollment not supported.");
index 9b7c8b5400c86dbacdd85e71ae7169e9a76dfcc6..25a90b5905304d7e80528c1456ca94078a55494d 100644 (file)
@@ -8,6 +8,86 @@
 #include "pwquality-util.h"
 #include "strv.h"
 
+int load_volume_key_password(
+                struct crypt_device *cd,
+                const char *cd_node,
+                void *ret_vk,
+                size_t *ret_vks) {
+
+        _cleanup_(erase_and_freep) char *envpw = NULL;
+        int r;
+
+        assert_se(cd);
+        assert_se(cd_node);
+        assert_se(ret_vk);
+        assert_se(ret_vks);
+
+        r = getenv_steal_erase("PASSWORD", &envpw);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire password from environment: %m");
+        if (r > 0) {
+                r = crypt_volume_key_get(
+                                cd,
+                                CRYPT_ANY_SLOT,
+                                ret_vk,
+                                ret_vks,
+                                envpw,
+                                strlen(envpw));
+                if (r < 0)
+                        return log_error_errno(r, "Password from environment variable $PASSWORD did not work.");
+        } else {
+                AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED;
+                _cleanup_free_ char *question = NULL, *disk_path = NULL;
+                unsigned i = 5;
+                const char *id;
+
+                question = strjoin("Please enter current passphrase for disk ", cd_node, ":");
+                if (!question)
+                        return log_oom();
+
+                disk_path = cescape(cd_node);
+                if (!disk_path)
+                        return log_oom();
+
+                id = strjoina("cryptsetup:", disk_path);
+
+                for (;;) {
+                        _cleanup_strv_free_erase_ char **passwords = NULL;
+
+                        if (--i == 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
+                                                       "Too many attempts, giving up:");
+
+                        r = ask_password_auto(
+                                        question, "drive-harddisk", id, "cryptenroll", "cryptenroll.passphrase", USEC_INFINITY,
+                                        ask_password_flags,
+                                        &passwords);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to query password: %m");
+
+                        r = -EPERM;
+                        STRV_FOREACH(p, passwords) {
+                                r = crypt_volume_key_get(
+                                                cd,
+                                                CRYPT_ANY_SLOT,
+                                                ret_vk,
+                                                ret_vks,
+                                                *p,
+                                                strlen(*p));
+                                if (r >= 0)
+                                        break;
+                        }
+                        if (r >= 0)
+                                break;
+
+                        log_error_errno(r, "Password not correct, please try again.");
+                        ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
+                }
+        }
+
+        return r;
+}
+
 int enroll_password(
                 struct crypt_device *cd,
                 const void *volume_key,
index ddeee1318f140f93d6c6943a671f175192dc99b7..aa07a6f97b8b709d677224d9e021ee171b5d5873 100644 (file)
@@ -5,4 +5,5 @@
 
 #include "cryptsetup-util.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 void *volume_key, size_t volume_key_size);
index dbf6cf45af4cc545d010c7f6be67d0f458208c46..29360ef0fc96387351265eceb836c798ce625713 100644 (file)
@@ -32,6 +32,8 @@
 
 static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID;
 static char *arg_unlock_keyfile = NULL;
+static UnlockType arg_unlock_type = UNLOCK_PASSWORD;
+static char *arg_unlock_fido2_device = NULL;
 static char *arg_pkcs11_token_uri = NULL;
 static char *arg_fido2_device = NULL;
 static char *arg_tpm2_device = NULL;
@@ -55,6 +57,7 @@ static int arg_fido2_cred_alg = 0;
 assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX);
 
 STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
@@ -105,6 +108,8 @@ static int help(void) {
                "     --recovery-key    Enroll a recovery key\n"
                "     --unlock-key-file=PATH\n"
                "                       Use a file to unlock the volume\n"
+               "     --unlock-fido2-device=PATH\n"
+               "                       Use a FIDO2 device to unlock the volume\n"
                "     --pkcs11-token-uri=URI\n"
                "                       Specify PKCS#11 security token URI\n"
                "     --fido2-credential-algorithm=STRING\n"
@@ -148,6 +153,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_PASSWORD,
                 ARG_RECOVERY_KEY,
                 ARG_UNLOCK_KEYFILE,
+                ARG_UNLOCK_FIDO2_DEVICE,
                 ARG_PKCS11_TOKEN_URI,
                 ARG_FIDO2_DEVICE,
                 ARG_TPM2_DEVICE,
@@ -169,6 +175,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "password",                     no_argument,       NULL, ARG_PASSWORD              },
                 { "recovery-key",                 no_argument,       NULL, ARG_RECOVERY_KEY          },
                 { "unlock-key-file",              required_argument, NULL, ARG_UNLOCK_KEYFILE        },
+                { "unlock-fido2-device",          required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE   },
                 { "pkcs11-token-uri",             required_argument, NULL, ARG_PKCS11_TOKEN_URI      },
                 { "fido2-credential-algorithm",   required_argument, NULL, ARG_FIDO2_CRED_ALG        },
                 { "fido2-device",                 required_argument, NULL, ARG_FIDO2_DEVICE          },
@@ -250,11 +257,37 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_UNLOCK_KEYFILE:
+                        if (arg_unlock_type != UNLOCK_PASSWORD)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Multiple unlock methods specified at once, refusing.");
+
                         r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_unlock_keyfile);
                         if (r < 0)
                                 return r;
+
+                        arg_unlock_type = UNLOCK_KEYFILE;
                         break;
 
+                case ARG_UNLOCK_FIDO2_DEVICE: {
+                        _cleanup_free_ char *device = NULL;
+
+                        if (arg_unlock_type != UNLOCK_PASSWORD)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Multiple unlock methods specified at once, refusing.");
+
+                        assert(!arg_unlock_fido2_device);
+
+                        if (!streq(optarg, "auto")) {
+                                device = strdup(optarg);
+                                if (!device)
+                                        return log_oom();
+                        }
+
+                        arg_unlock_type = UNLOCK_FIDO2;
+                        arg_unlock_fido2_device = TAKE_PTR(device);
+                        break;
+                }
+
                 case ARG_PKCS11_TOKEN_URI: {
                         _cleanup_free_ char *uri = NULL;
 
@@ -299,11 +332,7 @@ static int parse_argv(int argc, char *argv[]) {
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                        "Multiple operations specified at once, refusing.");
 
-                        if (streq(optarg, "auto")) {
-                                r = fido2_find_device_auto(&device);
-                                if (r < 0)
-                                        return r;
-                        } else {
+                        if (!streq(optarg, "auto")) {
                                 device = strdup(optarg);
                                 if (!device)
                                         return log_oom();
@@ -432,6 +461,18 @@ static int parse_argv(int argc, char *argv[]) {
                 }
         }
 
+        if ((arg_enroll_type == ENROLL_FIDO2 && arg_unlock_type == UNLOCK_FIDO2)
+                        && !(arg_fido2_device && arg_unlock_fido2_device))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. "
+                                       "Please specify device paths for enrolling and unlocking respectively.");
+
+        if (arg_enroll_type == ENROLL_FIDO2 && !arg_fido2_device) {
+                r = fido2_find_device_auto(&arg_fido2_device);
+                if (r < 0)
+                        return r;
+        }
+
         if (optind >= argc)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "No block device node specified, refusing.");
@@ -474,13 +515,50 @@ static int check_for_homed(struct crypt_device *cd) {
         return 0;
 }
 
+static int load_volume_key_keyfile(
+                struct crypt_device *cd,
+                void *ret_vk,
+                size_t *ret_vks) {
+
+        _cleanup_(erase_and_freep) char *password = NULL;
+        size_t password_len;
+        int r;
+
+        assert_se(cd);
+        assert_se(ret_vk);
+        assert_se(ret_vks);
+
+        r = read_full_file_full(
+                        AT_FDCWD,
+                        arg_unlock_keyfile,
+                        0,
+                        SIZE_MAX,
+                        READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
+                        NULL,
+                        &password,
+                        &password_len);
+        if (r < 0)
+                return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile);
+
+        r = crypt_volume_key_get(
+                        cd,
+                        CRYPT_ANY_SLOT,
+                        ret_vk,
+                        ret_vks,
+                        password,
+                        password_len);
+        if (r < 0)
+                return log_error_errno(r, "Unlocking via keyfile failed: %m");
+
+        return r;
+}
+
 static int prepare_luks(
                 struct crypt_device **ret_cd,
                 void **ret_volume_key,
                 size_t *ret_volume_key_size) {
 
         _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
-        _cleanup_(erase_and_freep) char *envpw = NULL;
         _cleanup_(erase_and_freep) void *vk = NULL;
         size_t vks;
         int r;
@@ -516,99 +594,27 @@ static int prepare_luks(
         if (!vk)
                 return log_oom();
 
-        if (arg_unlock_keyfile) {
-                _cleanup_(erase_and_freep) char *password = NULL;
-                size_t password_len;
-
-                r = read_full_file_full(
-                                AT_FDCWD,
-                                arg_unlock_keyfile,
-                                0,
-                                SIZE_MAX,
-                                READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
-                                NULL,
-                                &password,
-                                &password_len);
-                if (r < 0)
-                        return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile);
-
-                r = crypt_volume_key_get(
-                                cd,
-                                CRYPT_ANY_SLOT,
-                                vk,
-                                &vks,
-                                password,
-                                password_len);
-                if (r < 0)
-                        return log_error_errno(r, "Unlocking via keyfile failed: %m");
+        switch (arg_unlock_type) {
 
-                goto out;
-        }
+        case UNLOCK_KEYFILE:
+                r = load_volume_key_keyfile(cd, vk, &vks);
+                break;
 
-        r = getenv_steal_erase("PASSWORD", &envpw);
-        if (r < 0)
-                return log_error_errno(r, "Failed to acquire password from environment: %m");
-        if (r > 0) {
-                r = crypt_volume_key_get(
-                                cd,
-                                CRYPT_ANY_SLOT,
-                                vk,
-                                &vks,
-                                envpw,
-                                strlen(envpw));
-                if (r < 0)
-                        return log_error_errno(r, "Password from environment variable $PASSWORD did not work.");
-        } else {
-                AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED;
-                _cleanup_free_ char *question = NULL, *disk_path = NULL;
-                unsigned i = 5;
-                const char *id;
-
-                question = strjoin("Please enter current passphrase for disk ", arg_node, ":");
-                if (!question)
-                        return log_oom();
-
-                disk_path = cescape(arg_node);
-                if (!disk_path)
-                        return log_oom();
-
-                id = strjoina("cryptsetup:", disk_path);
-
-                for (;;) {
-                        _cleanup_strv_free_erase_ char **passwords = NULL;
-
-                        if (--i == 0)
-                                return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
-                                                       "Too many attempts, giving up:");
-
-                        r = ask_password_auto(
-                                        question, "drive-harddisk", id, "cryptenroll", "cryptenroll.passphrase", USEC_INFINITY,
-                                        ask_password_flags,
-                                        &passwords);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to query password: %m");
-
-                        r = -EPERM;
-                        STRV_FOREACH(p, passwords) {
-                                r = crypt_volume_key_get(
-                                                cd,
-                                                CRYPT_ANY_SLOT,
-                                                vk,
-                                                &vks,
-                                                *p,
-                                                strlen(*p));
-                                if (r >= 0)
-                                        break;
-                        }
-                        if (r >= 0)
-                                break;
+        case UNLOCK_FIDO2:
+                r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk, &vks);
+                break;
 
-                        log_error_errno(r, "Password not correct, please try again.");
-                        ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
-                }
+        case UNLOCK_PASSWORD:
+                r = load_volume_key_password(cd, arg_node, vk, &vks);
+                break;
+
+        default:
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown LUKS unlock method");
         }
 
-out:
+        if (r < 0)
+                return r;
+
         *ret_cd = TAKE_PTR(cd);
         *ret_volume_key = TAKE_PTR(vk);
         *ret_volume_key_size = vks;
index b28d9db990b6516a15e26245ab0b0f7943a09a2f..335d9ccd715467c03e29d4bc2bddccdb38275de8 100644 (file)
@@ -13,6 +13,14 @@ typedef enum EnrollType {
         _ENROLL_TYPE_INVALID = -EINVAL,
 } EnrollType;
 
+typedef enum UnlockType {
+        UNLOCK_PASSWORD,
+        UNLOCK_KEYFILE,
+        UNLOCK_FIDO2,
+        _UNLOCK_TYPE_MAX,
+        _UNLOCK_TYPE_INVALID = -EINVAL,
+} UnlockType;
+
 typedef enum WipeScope {
         WIPE_EXPLICIT,          /* only wipe the listed slots */
         WIPE_ALL,               /* wipe all slots */