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:
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user