summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Cai <peter@typeblog.net>2022-12-18 02:33:05 +0100
committerPeter Cai <peter@typeblog.net>2022-12-22 23:33:25 +0100
commit48765191d295aefe902205f4a9eb0f6dd12fe99d (patch)
tree2ee58588271825de87f0d77037876d6819beedc5
parentcryptsetup-fido2: Remove plain mode parameters from `acquire_fido2_key_auto()` (diff)
downloadsystemd-48765191d295aefe902205f4a9eb0f6dd12fe99d.tar.xz
systemd-48765191d295aefe902205f4a9eb0f6dd12fe99d.zip
cryptenroll: Implement support for unlocking via FIDO2 tokens
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.
-rw-r--r--src/cryptenroll/cryptenroll-fido2.c53
-rw-r--r--src/cryptenroll/cryptenroll-fido2.h7
-rw-r--r--src/cryptenroll/cryptenroll-password.c80
-rw-r--r--src/cryptenroll/cryptenroll-password.h1
-rw-r--r--src/cryptenroll/cryptenroll.c194
-rw-r--r--src/cryptenroll/cryptenroll.h8
6 files changed, 249 insertions, 94 deletions
diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c
index 80adaefa17..e49b4a0cfe 100644
--- a/src/cryptenroll/cryptenroll-fido2.c
+++ b/src/cryptenroll/cryptenroll-fido2.c
@@ -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,
diff --git a/src/cryptenroll/cryptenroll-fido2.h b/src/cryptenroll/cryptenroll-fido2.h
index 11667afe9c..3315308e4d 100644
--- a/src/cryptenroll/cryptenroll-fido2.h
+++ b/src/cryptenroll/cryptenroll-fido2.h
@@ -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.");
diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c
index 9b7c8b5400..25a90b5905 100644
--- a/src/cryptenroll/cryptenroll-password.c
+++ b/src/cryptenroll/cryptenroll-password.c
@@ -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,
diff --git a/src/cryptenroll/cryptenroll-password.h b/src/cryptenroll/cryptenroll-password.h
index ddeee1318f..aa07a6f97b 100644
--- a/src/cryptenroll/cryptenroll-password.h
+++ b/src/cryptenroll/cryptenroll-password.h
@@ -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);
diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c
index dbf6cf45af..29360ef0fc 100644
--- a/src/cryptenroll/cryptenroll.c
+++ b/src/cryptenroll/cryptenroll.c
@@ -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;
diff --git a/src/cryptenroll/cryptenroll.h b/src/cryptenroll/cryptenroll.h
index b28d9db990..335d9ccd71 100644
--- a/src/cryptenroll/cryptenroll.h
+++ b/src/cryptenroll/cryptenroll.h
@@ -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 */