cryptenroll: collect all enrollment parameters in an EnrollContext

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.
This commit is contained in:
Lennart Poettering
2026-06-27 17:28:39 +02:00
parent 846b0efb04
commit 39d08de81b
17 changed files with 436 additions and 234 deletions
+23 -25
View File
@@ -15,11 +15,9 @@
#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();
+3 -2
View 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);
+32 -15
View File
@@ -14,18 +14,17 @@
#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();
+3 -2
View 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);
+7 -6
View 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))));
+2 -1
View 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);
+32 -22
View File
@@ -12,8 +12,10 @@
#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:
+4 -1
View 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);
+53 -62
View 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)
+3 -2
View 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);
+13 -16
View 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;
}
+4 -8
View 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);
+121 -37
View 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 */
else
r = prepare_luks(&cd, &vk);
r = enroll_context_from_args(&c);
if (r < 0)
return r;
switch (arg_enroll_type) {
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(&c, &cd, &vk);
if (r < 0)
return r;
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;
+77
View 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);
+3
View 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,
+54 -35
View 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;
_cleanup_strv_free_erase_ char **pins = NULL;
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;
}
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();
} 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;
}
_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();
}
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;
}
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, askpw_flags, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire user PIN: %m");
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));
+2
View 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,