#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;
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)
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)
}
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 = {};
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,
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 */
/* 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,
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();
/* 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);
#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)
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();
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)
}
int enroll_password(
+ const EnrollContext *c,
struct crypt_device *cd,
const struct iovec *volume_key) {
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();
/* 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);
}
#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;
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);
/* 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))));
/* 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);
#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;
int keyslot, r, q;
const char *node;
+ assert_se(c);
assert_se(cd);
assert_se(iovec_is_set(volume_key));
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();
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:
/* 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);
#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 = {};
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 */
found_some = true;
r = acquire_tpm2_key(
- cd_node,
- device,
+ c->node,
+ c->unlock_tpm2_device,
hash_pcr_mask,
pcr_bank,
&pubkey,
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");
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)
#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
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
*/
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;
}
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");
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");
}
} 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;
_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");
}
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");
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. */
/* 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)
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.");
n_blobs = 1;
r = tpm2_calculate_seal(
- seal_key_handle,
+ c->tpm2_seal_key_handle,
&device_key_public,
/* attributes= */ NULL,
/* secret= */ NULL,
&srk);
} else
r = tpm2_seal(tpm2_context,
- seal_key_handle,
+ c->tpm2_seal_key_handle,
policy_hash,
n_policy_hash,
pin_str,
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 {
}
/* 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...");
pubkey_pcr_mask,
signature_json,
pin_str,
- pcrlock_path ? &pcrlock_policy : NULL,
+ c->tpm2_pcrlock ? &pcrlock_policy : NULL,
primary_alg,
blobs,
n_blobs,
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)
/* 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);
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;
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:
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 */
}
/* 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;
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;
}
/* 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);
#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"
#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"
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;
}
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,
&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)
}
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");
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)
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;
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();
/* 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;
}
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.");
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;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include "libfido2-util.h"
#include "shared-forward.h"
typedef enum EnrollType {
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);
/* 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"
/* user_icon= */ NULL,
/* askpw_icon= */ "user-home",
/* askpw_credential= */ "home.token-pin",
+ /* askpw_flags= */ 0,
+ /* pin= */ NULL,
lock_with,
cred_alg,
&salt,
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,
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;
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.");
}
}
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));
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,