From: fuldeka <260289618+fuldeka@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:42:47 +0000 (+0200) Subject: cryptsetup: add Argon2id-based PIN mode for TPM2 enrollment X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f3fe668cdf71b42485a6a2151d4f025788a35a95;p=thirdparty%2Fsystemd.git cryptsetup: add Argon2id-based PIN mode for TPM2 enrollment The current TPM2 PIN mode is flawed as a compromised TPM directly exposes the sealed secret which is the LUKS volume key itself (#27502 and #37386). Goal: add Argon2id-based PIN hardening to TPM2 enrollment, making the TPM a second factor rather than a single point of failure: 1. Password + salt -> Argon2id -> 512-bit key split into Key1 + Key2 2. Key2 (base64-encoded) is used as the PIN to seal a random secret in the TPM 3. Key1 + unsealed secret -> HKDF-SHA256 -> final LUKS volume key This implementation ensures that if the TPM is compromised, an attacker still needs the password to derive Key1 and combine it with the unsealed secret. The --tpm2-with-pin= option now accepts three values: - false (no PIN used) - true (PIN hardened with Argon2id - default) - "direct" (legacy PIN without Argon2id for backward compatibility) Argon2id parameters are customizable via: --tpm2-argon2id-memory= --tpm2-argon2id-iterations= --tpm2-argon2id-parallelism= --tpm2-argon2id-iter-time= These default to a function of available CPUs and physical memory, with a benchmark that scales iterations to the target time (default: 2s) and falls back to ARGON2ID_PARAMETERS_DEFAULT (64 MiB, 8 iter, 4 lanes) when auto detection fails. Also if the runtime OpenSSL lacks Argon2id support (< 3.2), the feature silently falls back to direct PIN mode with a warning. Added includes: - src/cryptenroll/cryptenroll.c: cpu-set-util.h, limits-util.h, time-util.h for Argon2id benchmark auto-tuning (cpus_online, physical_memory_scale, now/usec_t) - src/cryptenroll/cryptenroll-tpm2.c: crypto-util.h for Argon2IdParameters struct in load_volume_key_tpm2() - src/shared/tpm2-util.h: crypto-util.h for Argon2IdParameters in tpm2_make_luks2_json() API - src/shared/tpm2-util.c: limits-util.h, tpm2-util.h for physical_memory() validation of Argon2id memory cost and function prototypes - src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c: crypto-util.h for kdf_argon2id_derive()/kdf_hkdf_sha256() on the token unlock path --- diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 7cbc783edcc..94cab54ad7a 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -677,13 +677,19 @@ - + When enrolling a TPM2 device, controls whether to require the user to enter a PIN - when unlocking the volume in addition to PCR binding, based on TPM2 policy authentication. Defaults - to no. Despite being called PIN, any character can be used, not just numbers. + when unlocking the volume in addition to PCR binding, based on TPM2 policy authentication. Takes a + boolean or the special value direct. Defaults to no. + Despite being called PIN, any character can be used, not just numbers. + When enabled (yes), the PIN is hardened with Argon2id before it is passed + to the TPM, making the TPM a second factor rather than a single point of failure: a compromised TPM + alone does not expose the volume key without the PIN. When direct is used, the PIN + is hashed with PBKDF2 and used directly (compatible with systemd v253 and later). + Note that incorrect PIN entry when unlocking increments the TPM dictionary attack lockout mechanism, and may lock out users for a prolonged time, depending on its configuration. The lockout mechanism is a global property of the TPM, systemd-cryptenroll does not control or @@ -694,7 +700,51 @@ project='mankier'>tpm2_dictionarylockout1 commands, respectively. - + + + + + + + When enrolling a TPM2 with PIN hardening enabled, sets the Argon2id memory cost, + in bytes. By default, the memory cost is auto-detected from available RAM (up to 50% of total + physical memory), with a minimum of 64 MiB. When specified, the benchmark calibrates only + iterations to the target time; the memory cost is used as specified. To express large values + comfortably, use suffixes such as K, M, G + (for kilobytes, megabytes, gigabytes). + + + + + + + + When enrolling a TPM2 with PIN hardening enabled, sets the Argon2id iteration + count. Defaults to auto-calibrated by a benchmark to reach the target unlock time. When + specified, the benchmark calibrates only memory cost to the target time; the iteration count + is used as specified. + + + + + + + + When enrolling a TPM2 with PIN hardening enabled, sets the Argon2id + parallelism (lane count). Defaults to the number of online CPUs. + + + + + + + + When enrolling a TPM2 with PIN hardening enabled, sets the target benchmark + time for the Argon2id parameter calibration, in seconds. Defaults to 2 seconds. The benchmark + adjusts memory cost and/or iteration count so that the actual derivation time falls within a + tolerance window of the specified target time. + + diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index bc52a18c6ab..6165a316422 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "cryptenroll-tpm2.h" +#include "crypto-util.h" #include "cryptsetup-tpm2.h" #include "cryptsetup-util.h" #include "env-util.h" @@ -194,6 +195,7 @@ int load_volume_key_tpm2( size_t n_blobs = 0, n_policy_hash = 0; uint32_t hash_pcr_mask, pubkey_pcr_mask; uint16_t pcr_bank, primary_alg; + Argon2IdParameters ap = {}; TPM2Flags tpm2_flags; int keyslot; @@ -219,7 +221,8 @@ int load_volume_key_tpm2( &pcrlock_nv, &tpm2_flags, &keyslot, - &token); + &token, + &ap); if (r == -ENXIO) return log_full_errno(LOG_NOTICE, SYNTHETIC_ERRNO(EAGAIN), @@ -257,6 +260,7 @@ int load_volume_key_tpm2( /* until= */ 0, "cryptenroll.tpm2-pin", c->interactive ? 0 : ASK_PASSWORD_HEADLESS, + /* argon2id_params= */ FLAGS_SET(tpm2_flags, TPM2_FLAGS_USE_ARGON2ID) ? &ap : NULL, &decrypted_key); if (IN_SET(r, -EACCES, -ENOLCK)) return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed"); @@ -304,6 +308,7 @@ int enroll_tpm2(const EnrollContext *c, _cleanup_(iovec_done_erase) struct iovec secret = {}; const char *node; _cleanup_(erase_and_freep) char *pin_str = NULL; + _cleanup_(iovec_done_erase) struct iovec key1 = {}; ssize_t base64_encoded_size; int r, keyslot, slot_to_wipe = -1; TPM2Flags flags = 0; @@ -328,7 +333,7 @@ int enroll_tpm2(const EnrollContext *c, assert_se(node = sym_crypt_get_device_name(cd)); - if (c->tpm2_pin) { + if (IN_SET(c->tpm2_pin, TPM2_WITH_PIN_YES, TPM2_WITH_PIN_DIRECT)) { r = get_pin(&pin_str, &flags); if (r < 0) return r; @@ -337,17 +342,38 @@ int enroll_tpm2(const EnrollContext *c, if (r < 0) return log_error_errno(r, "Failed to acquire random salt: %m"); - uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; - CLEANUP_ERASE(salted_pin); - r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), binary_salt, sizeof(binary_salt), salted_pin); - if (r < 0) - return log_error_errno(r, "Failed to perform PBKDF2: %m"); + if (c->tpm2_pin == TPM2_WITH_PIN_YES) { + if (isempty(pin_str)) + return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Argon2id PIN requires a non-empty PIN."); + + _cleanup_(erase_and_freep) char *b64_tpm_pin = NULL; + r = tpm2_argon2id_derive_split( + pin_str, + &IOVEC_MAKE(binary_salt, sizeof(binary_salt)), + &c->tpm2_argon2id_params, + &key1, + &b64_tpm_pin); + if (r < 0) + return log_error_errno(r, "Failed to perform Argon2id: %m"); + + /* Key2 = last 32 bytes, used as TPM PIN */ + pin_str = erase_and_free(pin_str); + pin_str = TAKE_PTR(b64_tpm_pin); + + flags |= TPM2_FLAGS_USE_ARGON2ID; + } else { + uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; + CLEANUP_ERASE(salted_pin); + r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), binary_salt, sizeof(binary_salt), salted_pin); + if (r < 0) + return log_error_errno(r, "Failed to perform PBKDF2: %m"); - pin_str = erase_and_free(pin_str); - /* re-stringify pin_str */ - base64_encoded_size = base64mem(salted_pin, sizeof(salted_pin), &pin_str); - if (base64_encoded_size < 0) - return log_error_errno(base64_encoded_size, "Failed to base64 encode salted pin: %m"); + pin_str = erase_and_free(pin_str); + /* re-stringify pin_str */ + base64_encoded_size = base64mem(salted_pin, sizeof(salted_pin), &pin_str); + if (base64_encoded_size < 0) + return log_error_errno(base64_encoded_size, "Failed to base64 encode salted pin: %m"); + } } TPM2B_PUBLIC public = {}; @@ -462,7 +488,7 @@ int enroll_tpm2(const EnrollContext *c, c->tpm2_n_hash_pcr_values, iovec_is_set(&pubkey) ? &public : NULL, iovec_is_set(&pubkey) ? c->tpm2_public_key_policyref : NULL, - c->tpm2_pin, + IN_SET(c->tpm2_pin, TPM2_WITH_PIN_YES, TPM2_WITH_PIN_DIRECT), c->tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL, policy_hash + 0); if (r < 0) @@ -474,7 +500,7 @@ int enroll_tpm2(const EnrollContext *c, c->tpm2_n_hash_pcr_values, /* public= */ NULL, /* This one is off now */ /* pubkey_policy_ref= */ NULL, - c->tpm2_pin, + IN_SET(c->tpm2_pin, TPM2_WITH_PIN_YES, TPM2_WITH_PIN_DIRECT), &pcrlock_policy, /* And this one on instead. */ policy_hash + 1); if (r < 0) @@ -533,7 +559,7 @@ int enroll_tpm2(const EnrollContext *c, log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now."); else if (r < 0) return r; - else if (c->tpm2_pin) { + else if (IN_SET(c->tpm2_pin, TPM2_WITH_PIN_YES, TPM2_WITH_PIN_DIRECT)) { log_debug("This PCR set is already enrolled, re-enrolling anyway to update PIN."); slot_to_wipe = r; } else { @@ -570,8 +596,16 @@ int enroll_tpm2(const EnrollContext *c, return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed."); } - /* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */ - base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded); + if (FLAGS_SET(flags, TPM2_FLAGS_USE_ARGON2ID)) { + _cleanup_(iovec_done_erase) struct iovec final_key = {}; + r = tpm2_argon2id_hkdf(&key1, &secret, &final_key); + if (r < 0) + return log_error_errno(r, "Failed to derive final volume key via HKDF: %m"); + + base64_encoded_size = base64mem(final_key.iov_base, final_key.iov_len, &base64_encoded); + } else + /* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */ + base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded); if (base64_encoded_size < 0) return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); @@ -601,10 +635,11 @@ int enroll_tpm2(const EnrollContext *c, n_blobs, policy_hash_as_iovec, n_policy_hash, - c->tpm2_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL, + IN_SET(c->tpm2_pin, TPM2_WITH_PIN_YES, TPM2_WITH_PIN_DIRECT) ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL, &srk, c->tpm2_pcrlock ? &pcrlock_policy.nv_handle : NULL, flags, + &c->tpm2_argon2id_params, &v); if (r < 0) return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index ae708146615..f295585ff9d 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -10,6 +10,7 @@ #include "blockdev-util.h" #include "build.h" #include "cleanup-util.h" +#include "cpu-set-util.h" #include "cryptenroll.h" #include "cryptenroll-fido2.h" #include "cryptenroll-interactive.h" @@ -27,6 +28,7 @@ #include "help-util.h" #include "initrd-util.h" #include "libfido2-util.h" +#include "limits-util.h" #include "log.h" #include "main-func.h" #include "memory-util.h" @@ -40,9 +42,25 @@ #include "string-table.h" #include "string-util.h" #include "terminal-util.h" +#include "time-util.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#define ARGON2ID_BENCHMARK_DEFAULT_TARGET_MS 2000U +#define ARGON2ID_BENCHMARK_MAX_ATTEMPTS 8U +#define ARGON2ID_BENCHMARK_MIN_MEMORY (64ULL * 1024 * 1024) +#define ARGON2ID_BENCHMARK_MIN_MS 250U +#define ARGON2ID_BENCHMARK_PERCENT_MAX 110U +#define ARGON2ID_BENCHMARK_PERCENT_MIN 95U + +static const char * const tpm2_with_pin_table[_TPM2_WITH_PIN_MAX] = { + [TPM2_WITH_PIN_NO] = "no", + [TPM2_WITH_PIN_YES] = "yes", /* with argon2id */ + [TPM2_WITH_PIN_DIRECT] = "direct", /* without argon2id, i.e. traditional mode as in v251 and before */ +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(tpm2_with_pin, Tpm2WithPin, TPM2_WITH_PIN_YES); + static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID; static char *arg_unlock_keyfile = NULL; static UnlockType arg_unlock_type = UNLOCK_PASSWORD; @@ -57,7 +75,9 @@ static uint32_t arg_tpm2_seal_key_handle = 0; static char *arg_tpm2_device_key = NULL; static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; static size_t arg_tpm2_n_hash_pcr_values = 0; -static bool arg_tpm2_pin = false; +static Tpm2WithPin arg_tpm2_pin = _TPM2_WITH_PIN_INVALID; +static Argon2IdParameters arg_tpm2_argon2id_params = {}; +static usec_t arg_tpm2_argon2id_iter_time = 0; static char *arg_tpm2_public_key = NULL; static bool arg_tpm2_load_public_key = true; static char *arg_tpm2_public_key_policyref = NULL; @@ -715,11 +735,56 @@ static int parse_argv(int argc, char *argv[]) { auto_pcrlock = false; break; - OPTION_LONG("tpm2-with-pin", "BOOL", - "Whether to require entering a PIN to unlock the volume"): - r = parse_boolean_argument("--tpm2-with-pin=", opts.arg, &arg_tpm2_pin); + OPTION_LONG("tpm2-with-pin", "BOOL|direct", + "Whether to require entering a PIN to unlock the volume. " + "Takes a boolean or the special value \"direct\". " + "When enabled (true), Argon2id is used for PIN hardening. " + "When \"direct\", the PIN is used directly without Argon2id " + "(compatible with older systemd versions)"): { + Tpm2WithPin v = tpm2_with_pin_from_string(opts.arg); + if (v < 0) + return log_error_errno(v, "Failed to parse --tpm2-with-pin=: %s", opts.arg); + arg_tpm2_pin = v; + break; + } + + OPTION_LONG("tpm2-argon2id-memory", "BYTES", + "Argon2id memory cost in bytes (default: 64M)"): { + uint64_t mem; + r = parse_size(opts.arg, 1024, &mem); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --tpm2-argon2id-memory=: %s", opts.arg); + if (mem == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argon2id memory must be non-zero."); + arg_tpm2_argon2id_params.memcost_bytes = mem; + break; + } + + OPTION_LONG("tpm2-argon2id-iterations", "NUM", + "Argon2id iteration count (default: 8)"): + r = safe_atou(opts.arg, &arg_tpm2_argon2id_params.iterations); + if (r < 0) + return log_error_errno(r, "Failed to parse --tpm2-argon2id-iterations=: %s", opts.arg); + if (arg_tpm2_argon2id_params.iterations == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argon2id iterations must be non-zero."); + break; + + OPTION_LONG("tpm2-argon2id-parallelism", "NUM", + "Argon2id parallelism/lane count (default: 4)"): + r = safe_atou(opts.arg, &arg_tpm2_argon2id_params.lanes); + if (r < 0) + return log_error_errno(r, "Failed to parse --tpm2-argon2id-parallelism=: %s", opts.arg); + if (arg_tpm2_argon2id_params.lanes == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argon2id parallelism must be non-zero."); + break; + + OPTION_LONG("tpm2-argon2id-iter-time", "TIME", + "Target Argon2id benchmark time in seconds (default: 2s)"): + r = parse_sec(opts.arg, &arg_tpm2_argon2id_iter_time); + if (r < 0) + return log_error_errno(r, "Failed to parse --tpm2-argon2id-iter-time=: %s", opts.arg); + if (arg_tpm2_argon2id_iter_time == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Target time must be non-zero."); break; } @@ -783,13 +848,22 @@ static int parse_argv(int argc, char *argv[]) { } if (arg_tpm2_n_hash_pcr_values == 0 && - !arg_tpm2_pin && + !IN_SET(arg_tpm2_pin, TPM2_WITH_PIN_YES, TPM2_WITH_PIN_DIRECT) && arg_tpm2_public_key_pcr_mask == 0 && !arg_tpm2_pcrlock) log_notice("Notice: enrolling TPM2 with an empty policy, i.e. without any state or access restrictions.\n" "Use --tpm2-public-key=, --tpm2-pcrlock=, --tpm2-with-pin= or --tpm2-pcrs= to enable one or more restrictions."); } + if (arg_tpm2_pin < 0) + arg_tpm2_pin = TPM2_WITH_PIN_NO; + + if (arg_tpm2_pin == TPM2_WITH_PIN_YES && !dlopen_libcrypto_has_argon2id()) { + log_warning("Argon2id not supported by libcrypto (OpenSSL >= 3.2 required), " + "falling back to direct PIN mode."); + arg_tpm2_pin = TPM2_WITH_PIN_DIRECT; + } + return 1; } @@ -923,6 +997,8 @@ static int enroll_context_from_args(EnrollContext *c) { 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->tpm2_argon2id_params = arg_tpm2_argon2id_params; + c->tpm2_argon2id_iter_time = arg_tpm2_argon2id_iter_time; c->wipe_slots_scope = arg_wipe_slots_scope; c->wipe_slots_mask = arg_wipe_slots_mask; @@ -1014,6 +1090,166 @@ int enroll_now( } } +static void argon2id_parameters_init_autotune(Argon2IdParameters *ret_params) { + assert(ret_params); + + unsigned lanes = ARGON2ID_PARAMETERS_DEFAULT.lanes; + (void) cpus_online(&lanes); + *ret_params = (Argon2IdParameters) { + .lanes = lanes, + }; +} + +static int argon2id_benchmark_once( + const struct iovec *password, + const struct iovec *salt, + uint64_t memcost_bytes, + uint32_t iterations, + uint32_t lanes, + usec_t *ret_elapsed) { + + assert(ret_elapsed); + + _cleanup_(iovec_done_erase) struct iovec result = {}; + Argon2IdParameters bp = { + .memcost_bytes = memcost_bytes, + .iterations = iterations, + .lanes = lanes, + }; + + usec_t start = now(CLOCK_MONOTONIC); + int r = kdf_argon2id_derive(password, salt, &bp, /* derive_size= */ 64, &result); + *ret_elapsed = now(CLOCK_MONOTONIC) - start; + + return r; +} + +static void argon2id_parameters_benchmark(Argon2IdParameters *p, usec_t target_time) { + int r; + + assert(p); + assert(target_time > 0); + + bool mem_fixed = p->memcost_bytes > 0; + bool iter_fixed = p->iterations > 0; + + if (mem_fixed && iter_fixed) + return; + + struct iovec password = IOVEC_MAKE_STRING("benchmark"); + struct iovec salt = IOVEC_MAKE_STRING("benchmark-salt"); + + uint64_t target_ms = MAX(target_time / USEC_PER_MSEC, 1U); + + uint32_t iterations = iter_fixed ? p->iterations : 2; + uint64_t memcost_bytes = mem_fixed ? p->memcost_bytes : ARGON2ID_BENCHMARK_MIN_MEMORY; + + uint64_t max_mem_bytes; + if (mem_fixed) + max_mem_bytes = memcost_bytes; + else { + max_mem_bytes = physical_memory_scale(1, 2); + if (max_mem_bytes == 0 || max_mem_bytes == UINT64_MAX) + max_mem_bytes = ARGON2ID_PARAMETERS_DEFAULT.memcost_bytes; + if (memcost_bytes > max_mem_bytes) + memcost_bytes = max_mem_bytes; + } + + target_ms = MIN(target_ms, UINT64_MAX / MAX(1u, MAX(max_mem_bytes, (uint64_t) UINT32_MAX))); + + usec_t actual_elapsed = 0; + + for (;;) { + usec_t elapsed; + r = argon2id_benchmark_once(&password, &salt, memcost_bytes, iterations, p->lanes, &elapsed); + log_debug("Benchmarking Argon2id with %"PRIu64" memory, %u iterations, %u lanes…", + memcost_bytes, iterations, p->lanes); + if (r < 0) { + log_debug_errno(r, "Argon2id benchmark failed, using default parameters: %m"); + *p = ARGON2ID_PARAMETERS_DEFAULT; + return; + } + + actual_elapsed = elapsed; + + if (elapsed >= ARGON2ID_BENCHMARK_MIN_MS * USEC_PER_MSEC) + break; + + if (!mem_fixed && memcost_bytes < max_mem_bytes) { + uint64_t new_mem = MIN(memcost_bytes * 2, max_mem_bytes); + if (new_mem > memcost_bytes) + memcost_bytes = new_mem; + else + memcost_bytes = max_mem_bytes; + } else if (!iter_fixed) { + uint32_t new_iter = MIN(2u * iterations, UINT32_MAX / 2u); + if (new_iter > iterations) + iterations = new_iter; + else + break; + } else + break; + } + + p->memcost_bytes = memcost_bytes; + + for (unsigned attempt = 0; attempt < ARGON2ID_BENCHMARK_MAX_ATTEMPTS; attempt++) { + usec_t elapsed; + r = argon2id_benchmark_once(&password, &salt, memcost_bytes, iterations, p->lanes, &elapsed); + if (r < 0) { + log_debug_errno(r, "Argon2id fine-tuning failed, keeping coarse parameters: %m"); + break; + } + + actual_elapsed = elapsed; + + uint64_t ms = MAX(elapsed / USEC_PER_MSEC, 1U); + + uint64_t lower = target_ms * ARGON2ID_BENCHMARK_PERCENT_MIN / 100; + uint64_t upper = target_ms * ARGON2ID_BENCHMARK_PERCENT_MAX / 100; + if (ms >= lower && ms <= upper) + break; + + uint64_t new_mem = memcost_bytes; + uint32_t new_iter = iterations; + + if (ms < target_ms) { + if (!mem_fixed) { + new_mem = MIN(memcost_bytes * target_ms / ms, max_mem_bytes); + if (new_mem >= max_mem_bytes && !iter_fixed) + new_iter = (uint32_t) MIN( + (uint64_t) iterations * target_ms / ms, + UINT32_MAX); + } else if (!iter_fixed) + new_iter = (uint32_t) MIN(iterations * target_ms / ms, UINT32_MAX); + } else { + if (!iter_fixed) { + new_iter = MAX((uint64_t) iterations * target_ms / ms, 2ULL); + if (new_iter <= 2 && !mem_fixed) + new_mem = MAX( + memcost_bytes * target_ms / ms, + ARGON2ID_BENCHMARK_MIN_MEMORY); + } else if (!mem_fixed) + new_mem = MAX( + memcost_bytes * target_ms / ms, + ARGON2ID_BENCHMARK_MIN_MEMORY); + } + + if (new_iter == iterations && new_mem == memcost_bytes) + break; + + iterations = new_iter; + memcost_bytes = new_mem; + } + + p->memcost_bytes = memcost_bytes; + p->iterations = iterations; + + log_notice("Argon2id benchmark: %u iterations, %"PRIu64" MiB, %u lanes, ~%"PRIu64"ms.", + p->iterations, p->memcost_bytes / 1024 / 1024, p->lanes, + actual_elapsed > 0 ? actual_elapsed / USEC_PER_MSEC : target_ms); +} + static int run(int argc, char *argv[]) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_(iovec_done_erase) struct iovec vk = {}; @@ -1040,6 +1276,8 @@ static int run(int argc, char *argv[]) { if (r > 0) return cryptenroll_varlink_server(); + argon2id_parameters_init_autotune(&arg_tpm2_argon2id_params); + r = parse_argv(argc, argv); if (r <= 0) return r; @@ -1089,6 +1327,14 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; + /* Benchmark Argon2id parameters before TPM2 enrollment with PIN */ + if (c.enroll_type == ENROLL_TPM2 && c.tpm2_pin == TPM2_WITH_PIN_YES) + argon2id_parameters_benchmark( + &c.tpm2_argon2id_params, + c.tpm2_argon2id_iter_time > 0 + ? c.tpm2_argon2id_iter_time + : (usec_t) ARGON2ID_BENCHMARK_DEFAULT_TARGET_MS * USEC_PER_MSEC); + slot = enroll_now(&c, cd, &vk, /* ret_recovery_key= */ NULL); if (slot < 0) { r = slot; diff --git a/src/cryptenroll/cryptenroll.h b/src/cryptenroll/cryptenroll.h index 9bc51bceadb..4e96976f50a 100644 --- a/src/cryptenroll/cryptenroll.h +++ b/src/cryptenroll/cryptenroll.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "crypto-util.h" #include "libfido2-util.h" #include "shared-forward.h" @@ -33,6 +34,14 @@ typedef enum WipeScope { _WIPE_SCOPE_INVALID = -EINVAL, } WipeScope; +typedef enum Tpm2WithPin { + TPM2_WITH_PIN_NO, + TPM2_WITH_PIN_YES, /* with argon2id */ + TPM2_WITH_PIN_DIRECT, /* without argon2id (legacy mode, v251 and before) */ + _TPM2_WITH_PIN_MAX, + _TPM2_WITH_PIN_INVALID = -EINVAL, +} Tpm2WithPin; + DECLARE_STRING_TABLE_LOOKUP(enroll_type, EnrollType); DECLARE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType); @@ -74,7 +83,9 @@ typedef struct EnrollContext { char *tpm2_device_key; Tpm2PCRValue *tpm2_hash_pcr_values; size_t tpm2_n_hash_pcr_values; - bool tpm2_pin; + Tpm2WithPin tpm2_pin; + Argon2IdParameters tpm2_argon2id_params; + usec_t tpm2_argon2id_iter_time; char *tpm2_public_key; bool tpm2_load_public_key; char *tpm2_public_key_policyref; @@ -106,7 +117,9 @@ typedef struct EnrollContext { .unlock_type = UNLOCK_PASSWORD, \ .fido2_parameters_in_header = true, \ .fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP, \ + .tpm2_pin = _TPM2_WITH_PIN_INVALID, \ .tpm2_load_public_key = true, \ + .tpm2_argon2id_params = ARGON2ID_PARAMETERS_DEFAULT, \ .wipe_slots_scope = WIPE_EXPLICIT, \ .wipe_except_slot = -1, \ .interactive = true, \ diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index dc67ebeb7aa..126982b6b27 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -103,6 +103,8 @@ _public_ int cryptsetup_token_open_pin( CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free); + Argon2IdParameters argon2id_params = {}; + r = tpm2_parse_luks2_json( v, /* ret_keyslot= */ NULL, @@ -119,7 +121,8 @@ _public_ int cryptsetup_token_open_pin( &salt, &srk, &pcrlock_nv, - &flags); + &flags, + &argon2id_params); if (r < 0) return log_debug_open_error(cd, token, r); @@ -148,6 +151,7 @@ _public_ int cryptsetup_token_open_pin( &srk, &pcrlock_nv, flags, + /* argon2id_params= */ FLAGS_SET(flags, TPM2_FLAGS_USE_ARGON2ID) ? &argon2id_params : NULL, &decrypted_key); if (r < 0) return log_debug_open_error(cd, token, r); @@ -227,7 +231,7 @@ _public_ void cryptsetup_token_dump( r = tpm2_parse_luks2_json( v, - NULL, + /* ret_keyslot= */ NULL, &hash_pcr_mask, &pcr_bank, &pubkey, @@ -241,7 +245,8 @@ _public_ void cryptsetup_token_dump( &salt, &srk, &pcrlock_nv, - &flags); + &flags, + /* ret_argon2id_params= */ NULL); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m"); @@ -266,6 +271,7 @@ _public_ void cryptsetup_token_dump( crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg))); crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK)); + crypt_log(cd, "\ttpm2-argon2id: %s\n", true_false(flags & TPM2_FLAGS_USE_ARGON2ID)); crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt))); crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk))); crypt_log(cd, "\ttpm2-pcrlock-nv: %s\n", true_false(iovec_is_set(&pcrlock_nv))); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index e88d1594747..de26e400e35 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "ask-password-api.h" +#include "crypto-util.h" #include "env-util.h" #include "hexdecoct.h" #include "log.h" @@ -33,11 +34,14 @@ int acquire_luks2_key( const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, + const Argon2IdParameters *argon2id_params, struct iovec *ret_decrypted_key) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *signature_json = NULL; _cleanup_free_ char *auto_device = NULL; - _cleanup_(erase_and_freep) char *b64_salted_pin = NULL; + _cleanup_(erase_and_freep) char *b64_pin = NULL; + _cleanup_(iovec_done_erase) struct iovec key1 = {}; + bool argon2id = FLAGS_SET(flags, TPM2_FLAGS_USE_ARGON2ID); int r; assert(iovec_is_valid(salt)); @@ -56,17 +60,28 @@ int acquire_luks2_key( if ((flags & TPM2_FLAGS_USE_PIN) && !pin) return -ENOANO; - if (pin && iovec_is_set(salt)) { + if (argon2id) { + if (isempty(pin)) + return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Argon2id PIN requires a non-empty PIN."); + if (!iovec_is_set(salt)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argon2id PIN requires salt in LUKS2 token."); + + r = tpm2_argon2id_derive_split(pin, salt, argon2id_params, &key1, &b64_pin); + if (r < 0) + return log_error_errno(r, "Failed to perform Argon2id: %m"); + + pin = b64_pin; + } else if (pin && iovec_is_set(salt)) { uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; CLEANUP_ERASE(salted_pin); r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt->iov_base, salt->iov_len, salted_pin); if (r < 0) return log_error_errno(r, "Failed to perform PBKDF2: %m"); - r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); + r = base64mem(salted_pin, sizeof(salted_pin), &b64_pin); if (r < 0) return log_error_errno(r, "Failed to base64 encode salted pin: %m"); - pin = b64_salted_pin; + pin = b64_pin; } if (pubkey_pcr_mask != 0) { @@ -81,7 +96,6 @@ int acquire_luks2_key( if (r < 0) return r; if (r == 0) { - /* Not found? Then search among passed credentials */ r = tpm2_pcrlock_policy_from_credentials(srk, pcrlock_nv, &pcrlock_policy); if (r < 0) return r; @@ -95,6 +109,7 @@ int acquire_luks2_key( if (r < 0) return r; + _cleanup_(iovec_done_erase) struct iovec unsealed = {}; r = tpm2_unseal(tpm2_context, hash_pcr_mask, pcr_bank, @@ -110,13 +125,14 @@ int acquire_luks2_key( policy_hash, n_policy_hash, srk, - ret_decrypted_key); + argon2id ? &unsealed : ret_decrypted_key); if (r == -EREMOTE) return log_warning_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM."); if (r == -EADDRNOTAVAIL) return log_warning_errno(r, "NV index referenced by token is missing, unwritten, or unusable, it could be for another system."); if (r == -EILSEQ) - return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Bad PIN."); /* cryptsetup docs say we should return ENOANO on bad PIN */ + /* cryptsetup docs say we should return ENOANO on bad PIN */ + return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Bad %s.", argon2id ? "password" : "PIN"); if (r == -ENOLCK) return log_error_errno(r, "TPM is in dictionary attack lock-out mode."); if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r)) { @@ -129,5 +145,14 @@ int acquire_luks2_key( if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); - return r; + if (argon2id) { + _cleanup_(iovec_done_erase) struct iovec volume_key = {}; + r = tpm2_argon2id_hkdf(&key1, &unsealed, &volume_key); + if (r < 0) + return log_error_errno(r, "Failed to derive volume key via HKDF: %m"); + + *ret_decrypted_key = TAKE_STRUCT(volume_key); + } + + return 0; } diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h index 7668d4bc932..a12e8731f80 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -25,4 +25,5 @@ int acquire_luks2_key( const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, + const Argon2IdParameters *argon2id_params, struct iovec *decrypted_key); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index db34b7e9747..e105794bb75 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -2070,6 +2070,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( until, "cryptsetup.tpm2-pin", arg_ask_password_flags, + /* argon2id_params= */ NULL, &decrypted_key); if (r >= 0) break; @@ -2115,6 +2116,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( uint32_t hash_pcr_mask, pubkey_pcr_mask; size_t n_blobs = 0, n_policy_hash = 0; uint16_t pcr_bank, primary_alg; + Argon2IdParameters argon2id_params = {}; TPM2Flags tpm2_flags; CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); @@ -2139,7 +2141,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( &pcrlock_nv, &tpm2_flags, &keyslot, - &token); + &token, + &argon2id_params); if (r == -ENXIO) /* No further TPM2 tokens found in the LUKS2 header. */ return log_full_errno(found_some ? LOG_NOTICE : LOG_DEBUG, @@ -2179,6 +2182,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( until, "cryptsetup.tpm2-pin", arg_ask_password_flags, + &argon2id_params, &decrypted_key); if (IN_SET(r, -EACCES, -ENOLCK)) return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking."); diff --git a/src/repart/repart.c b/src/repart/repart.c index 3eada3bc42a..b7992eefbe9 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -5816,6 +5816,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta &srk, &pcrlock_policy.nv_handle, flags, + &(Argon2IdParameters) {}, &v); if (r < 0) return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m"); diff --git a/src/shared/crypto-util.c b/src/shared/crypto-util.c index d285c16b8b3..55b28f21359 100644 --- a/src/shared/crypto-util.c +++ b/src/shared/crypto-util.c @@ -27,6 +27,11 @@ # include # endif +/* Forward declarations for OpenSSL thread pool API (optional, available in OpenSSL >= 3.2). These + * are resolved at runtime via DLSYM_OPTIONAL later. */ +int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads); +uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx); + struct OpenSSLAskPasswordUI { AskPasswordRequest request; UI_METHOD *method; @@ -226,8 +231,11 @@ DLSYM_PROTOTYPE(OSSL_PARAM_BLD_free) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_BLD_new) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_BN) = NULL; static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_octet_string) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_uint) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_utf8_string) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_BLD_to_param) = NULL; +static DLSYM_PROTOTYPE(OSSL_set_max_threads) = NULL; +static DLSYM_PROTOTYPE(OSSL_get_max_threads) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_construct_BN) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_construct_end) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_construct_octet_string) = NULL; @@ -547,6 +555,7 @@ int dlopen_libcrypto(int log_level) { DLSYM_ARG(OSSL_PARAM_BLD_new), DLSYM_ARG(OSSL_PARAM_BLD_push_BN), DLSYM_ARG(OSSL_PARAM_BLD_push_octet_string), + DLSYM_ARG(OSSL_PARAM_BLD_push_uint), DLSYM_ARG(OSSL_PARAM_BLD_push_utf8_string), DLSYM_ARG(OSSL_PARAM_BLD_to_param), DLSYM_ARG(OSSL_PARAM_construct_BN), @@ -660,6 +669,11 @@ int dlopen_libcrypto(int log_level) { DLSYM_OPTIONAL(libcrypto_dl, ENGINE_load_private_key); #endif + /* Optional thread pool API (OpenSSL >= 3.2). These symbols are resolved at runtime — if the + * running libcrypto doesn't provide them, the function pointers stay NULL and threading is + * skipped in kdf_argon2id_derive(). */ + DLSYM_OPTIONAL(libcrypto_dl, OSSL_set_max_threads); + DLSYM_OPTIONAL(libcrypto_dl, OSSL_get_max_threads); return r; #else return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -667,6 +681,18 @@ int dlopen_libcrypto(int log_level) { #endif } +bool dlopen_libcrypto_has_argon2id(void) { +#if HAVE_OPENSSL + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return false; + + _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(/* propq= */ NULL, "ARGON2ID", /* propq= */ NULL); + return !!kdf; +#else + return false; +#endif +} + #if HAVE_OPENSSL int openssl_to_errno(unsigned long e) { @@ -1223,6 +1249,168 @@ int kdf_kb_hmac_derive( return 0; } +/* Perform Argon2id KDF, producing derive_size bytes of output. + * + * For more details see: https://docs.openssl.org/master/man7/EVP_KDF-ARGON2/ */ +int kdf_argon2id_derive( + const struct iovec *password, + const struct iovec *salt, + const Argon2IdParameters *params, + size_t derive_size, + struct iovec *ret) { + + int r; + + assert(!password || password->iov_len > 0); + assert(!salt || salt->iov_len > 0); + assert(derive_size > 0); + assert(ret); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(/* propq= */ NULL, "ARGON2ID", /* propq= */ NULL); + if (!kdf) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF for ARGON2ID"); + + _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF_CTX"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) + return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD"); + + assert(params); + assert(params->memcost_bytes > 0); + assert(params->iterations > 0); + assert(params->lanes > 0); + + _cleanup_(erase_and_freep) void *buf = malloc(derive_size); + if (!buf) + return log_oom_debug(); + + if (password && !sym_OSSL_PARAM_BLD_push_octet_string(bld, "pass", password->iov_base, password->iov_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to add ARGON2ID pass"); + + if (salt && !sym_OSSL_PARAM_BLD_push_octet_string(bld, "salt", salt->iov_base, salt->iov_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to add ARGON2ID salt"); + + uint64_t memcost_kb = params->memcost_bytes / 1024; + + if (memcost_kb > UINT_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Argon2id memory cost too large: %"PRIu64" bytes", params->memcost_bytes); + + if (!sym_OSSL_PARAM_BLD_push_uint(bld, "memcost", (unsigned) memcost_kb)) + return log_openssl_errors(LOG_DEBUG, "Failed to add ARGON2ID memcost"); + + if (!sym_OSSL_PARAM_BLD_push_uint(bld, "iter", params->iterations)) + return log_openssl_errors(LOG_DEBUG, "Failed to add ARGON2ID iter"); + + if (!sym_OSSL_PARAM_BLD_push_uint(bld, "lanes", params->lanes)) + return log_openssl_errors(LOG_DEBUG, "Failed to add ARGON2ID lanes"); + + /* FIXME: drop sym_OSSL_set_max_threads() conditionalization once OpenSSL 3.2 becomes the minimum baseline */ + if (params->lanes > 1 && sym_OSSL_set_max_threads && !sym_OSSL_PARAM_BLD_push_uint(bld, "threads", params->lanes)) + return log_openssl_errors(LOG_DEBUG, "Failed to add ARGON2ID threads"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *openssl_params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!openssl_params) + return log_openssl_errors(LOG_DEBUG, "Failed to build ARGON2ID OSSL_PARAM"); + + /* FIXME: drop sym_OSSL_set_max_threads() conditionalization once OpenSSL 3.2 becomes the minimum baseline */ + uint64_t saved_max_threads = 0; + if (params->lanes > 1 && sym_OSSL_set_max_threads) { + if (sym_OSSL_get_max_threads) + saved_max_threads = sym_OSSL_get_max_threads(/* ctx= */ NULL); + if (!sym_OSSL_set_max_threads(/* ctx= */ NULL, params->lanes)) + return log_openssl_errors(LOG_DEBUG, "Failed to set Argon2id thread pool size"); + } + + if (sym_EVP_KDF_derive(ctx, buf, derive_size, openssl_params) <= 0) { + if (params->lanes > 1 && sym_OSSL_set_max_threads) + sym_OSSL_set_max_threads(/* ctx= */ NULL, saved_max_threads); + return log_openssl_errors(LOG_DEBUG, "OpenSSL ARGON2ID derive failed"); + } + + if (params->lanes > 1 && sym_OSSL_set_max_threads) + sym_OSSL_set_max_threads(/* ctx= */ NULL, saved_max_threads); + + ret->iov_base = TAKE_PTR(buf); + ret->iov_len = derive_size; + + return 0; +} + +/* Perform HKDF-SHA256 derivation, producing derive_size bytes of output. + * + * For more details see: https://docs.openssl.org/master/man7/EVP_KDF-HKDF.html */ +int kdf_hkdf_sha256( + const struct iovec *key, + const struct iovec *salt, + const struct iovec *info, + size_t derive_size, + struct iovec *ret) { + + int r; + + assert(!key || key->iov_len > 0); + assert(!salt || salt->iov_len > 0); + assert(!info || info->iov_len > 0); + assert(derive_size > 0); + assert(ret); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(/* propq= */ NULL, "HKDF", /* propq= */ NULL); + if (!kdf) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF for HKDF"); + + _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF_CTX"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) + return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD"); + + _cleanup_(erase_and_freep) void *buf = malloc(derive_size); + if (!buf) + return log_oom_debug(); + + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, "digest", "SHA256", 0)) + return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF digest"); + + if (key) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "key", key->iov_base, key->iov_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF key"); + + if (salt) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "salt", salt->iov_base, salt->iov_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF salt"); + + if (info) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "info", info->iov_base, info->iov_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF info"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *openssl_params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!openssl_params) + return log_openssl_errors(LOG_DEBUG, "Failed to build HKDF OSSL_PARAM"); + + if (sym_EVP_KDF_derive(ctx, buf, derive_size, openssl_params) <= 0) + return log_openssl_errors(LOG_DEBUG, "OpenSSL HKDF derive failed"); + + ret->iov_base = TAKE_PTR(buf); + ret->iov_len = derive_size; + + return 0; +} + +/* Encrypt the key data using RSA-OAEP with the provided label and specified digest algorithm. Returns 0 on + * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */ int rsa_oaep_encrypt_bytes( const EVP_PKEY *pkey, const char *digest_alg, @@ -2413,6 +2601,28 @@ int openssl_load_x509_certificate( } #endif +#if !HAVE_OPENSSL +int kdf_argon2id_derive( + const struct iovec *password, + const struct iovec *salt, + const Argon2IdParameters *params, + size_t derive_size, + struct iovec *ret) { + + return -EOPNOTSUPP; +} + +int kdf_hkdf_sha256( + const struct iovec *key, + const struct iovec *salt, + const struct iovec *info, + size_t derive_size, + struct iovec *ret) { + + return -EOPNOTSUPP; +} +#endif + int parse_openssl_certificate_source_argument( const char *argument, char **certificate_source, diff --git a/src/shared/crypto-util.h b/src/shared/crypto-util.h index ebf36e2df03..b2705998a6a 100644 --- a/src/shared/crypto-util.h +++ b/src/shared/crypto-util.h @@ -447,9 +447,27 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_ #endif int dlopen_libcrypto(int log_level) _dlopen_loader_; +bool dlopen_libcrypto_has_argon2id(void); #define DLOPEN_LIBCRYPTO(log_level, priority) \ ({ \ LIBCRYPTO_NOTE(priority); \ dlopen_libcrypto(log_level); \ }) + +typedef struct Argon2IdParameters { + uint64_t memcost_bytes; + uint32_t iterations; + uint32_t lanes; +} Argon2IdParameters; + +#define ARGON2ID_PARAMETERS_DEFAULT \ + (Argon2IdParameters) { \ + .memcost_bytes = 64ULL * 1024 * 1024, \ + .iterations = 8, \ + .lanes = 4, \ + } + +int kdf_argon2id_derive(const struct iovec *password, const struct iovec *salt, const Argon2IdParameters *params, size_t derive_size, struct iovec *ret); + +int kdf_hkdf_sha256(const struct iovec *key, const struct iovec *salt, const struct iovec *info, size_t derive_size, struct iovec *ret); diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c index a34c87cb917..0d8f675b313 100644 --- a/src/shared/cryptsetup-tpm2.c +++ b/src/shared/cryptsetup-tpm2.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "ask-password-api.h" +#include "crypto-util.h" #include "cryptsetup-tpm2.h" #include "cryptsetup-util.h" #include "env-util.h" @@ -88,6 +89,7 @@ int acquire_tpm2_key( usec_t until, const char *askpw_credential, AskPasswordFlags askpw_flags, + const Argon2IdParameters *argon2id_params, struct iovec *ret_decrypted_key) { #if HAVE_LIBCRYPTSETUP && HAVE_TPM2 @@ -96,8 +98,6 @@ int acquire_tpm2_key( _cleanup_free_ char *auto_device = NULL; int r; - assert(iovec_is_valid(salt)); - if (!device) { r = tpm2_find_device_auto(&auto_device); if (r == -ENODEV) @@ -190,33 +190,53 @@ int acquire_tpm2_key( return r; } + bool argon2id = FLAGS_SET(flags, TPM2_FLAGS_USE_ARGON2ID); + for (int i = 5;; i--) { - _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL; + _cleanup_(erase_and_freep) char *input_str = NULL, *b64_key2 = NULL; + _cleanup_(iovec_done_erase) struct iovec key1 = {}; + const char *pin_used; if (i <= 0) return -EACCES; - r = get_pin(until, askpw_credential, askpw_flags, &pin_str); + r = get_pin(until, askpw_credential, askpw_flags, &input_str); if (r < 0) return r; askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; - if (iovec_is_set(salt)) { - uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; - CLEANUP_ERASE(salted_pin); - - r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt->iov_base, salt->iov_len, salted_pin); - if (r < 0) - return log_error_errno(r, "Failed to perform PBKDF2: %m"); + if (argon2id) { + if (isempty(input_str)) + return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Argon2id PIN requires a non-empty PIN."); + if (!iovec_is_set(salt)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argon2id PIN requires salt in LUKS2 token."); - r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); + r = tpm2_argon2id_derive_split(input_str, salt, argon2id_params, &key1, &b64_key2); if (r < 0) - return log_error_errno(r, "Failed to base64 encode salted pin: %m"); - } else - /* no salting needed, backwards compat with non-salted pins */ - b64_salted_pin = TAKE_PTR(pin_str); + return log_error_errno(r, "Failed to perform Argon2id: %m"); + + pin_used = b64_key2; + } else { + if (iovec_is_set(salt)) { + uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; + CLEANUP_ERASE(salted_pin); + + r = tpm2_util_pbkdf2_hmac_sha256(input_str, strlen(input_str), salt->iov_base, salt->iov_len, salted_pin); + if (r < 0) + return log_error_errno(r, "Failed to perform PBKDF2: %m"); + + r = base64mem(salted_pin, sizeof(salted_pin), &b64_key2); + if (r < 0) + return log_error_errno(r, "Failed to base64 encode salted pin: %m"); + + pin_used = b64_key2; + } else + /* else: no salting needed, backwards compat with non-salted pins */ + pin_used = input_str; + } + _cleanup_(iovec_done_erase) struct iovec unsealed = {}; r = tpm2_unseal(tpm2_context, hash_pcr_mask, pcr_bank, @@ -224,7 +244,7 @@ int acquire_tpm2_key( pubkey_policy_ref, pubkey_pcr_mask, signature_json, - b64_salted_pin, + pin_used, FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, primary_alg, blobs, @@ -232,7 +252,7 @@ int acquire_tpm2_key( policy_hash, n_policy_hash, srk, - ret_decrypted_key); + argon2id ? &unsealed : ret_decrypted_key); if (r == -EREMOTE) return log_warning_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM."); if (r == -EADDRNOTAVAIL) @@ -247,13 +267,22 @@ int acquire_tpm2_key( if (r == -ENOLCK) return log_error_errno(r, "TPM is in dictionary attack lock-out mode."); if (r == -EILSEQ) { - log_warning_errno(r, "Bad PIN."); + log_warning_errno(r, "Bad %s.", argon2id ? "password" : "PIN"); continue; } if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); - return r; + if (argon2id) { + _cleanup_(iovec_done_erase) struct iovec volume_key = {}; + r = tpm2_argon2id_hkdf(&key1, &unsealed, &volume_key); + if (r < 0) + return log_error_errno(r, "Failed to derive volume key via HKDF: %m"); + + *ret_decrypted_key = TAKE_STRUCT(volume_key); + } + + return 0; } #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support not available."); @@ -279,7 +308,8 @@ int find_tpm2_auto_data( struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags, int *ret_keyslot, - int *ret_token) { + int *ret_token, + Argon2IdParameters *ret_argon2id_params) { #if HAVE_LIBCRYPTSETUP && HAVE_TPM2 int r, token; @@ -310,6 +340,7 @@ int find_tpm2_auto_data( size_t n_blobs = 0, n_policy_hash = 0; uint32_t hash_pcr_mask, pubkey_pcr_mask; uint16_t pcr_bank, primary_alg; + Argon2IdParameters ap = {}; TPM2Flags flags; int keyslot; @@ -338,7 +369,8 @@ int find_tpm2_auto_data( &salt, &srk, &pcrlock_nv, - &flags); + &flags, + &ap); if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */ continue; if (r < 0) @@ -366,6 +398,8 @@ int find_tpm2_auto_data( *ret_srk = TAKE_STRUCT(srk); *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv); *ret_flags = flags; + if (ret_argon2id_params) + *ret_argon2id_params = ap; return 0; } diff --git a/src/shared/cryptsetup-tpm2.h b/src/shared/cryptsetup-tpm2.h index f27204dda16..7c06ebd2005 100644 --- a/src/shared/cryptsetup-tpm2.h +++ b/src/shared/cryptsetup-tpm2.h @@ -3,6 +3,7 @@ #include +#include "crypto-util.h" #include "shared-forward.h" int acquire_tpm2_key( @@ -30,6 +31,7 @@ int acquire_tpm2_key( usec_t until, const char *askpw_credential, AskPasswordFlags askpw_flags, + const Argon2IdParameters *argon2id_params, struct iovec *ret_decrypted_key); int find_tpm2_auto_data( @@ -51,4 +53,5 @@ int find_tpm2_auto_data( struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags, int *ret_keyslot, - int *ret_token); + int *ret_token, + Argon2IdParameters *ret_argon2id_params); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 788b153c5ab..b57fb092bb3 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -30,6 +30,7 @@ #include "initrd-util.h" #include "io-util.h" #include "json-util.h" +#include "limits-util.h" #include "log.h" #include "logarithm.h" #include "memory-util.h" @@ -9658,6 +9659,63 @@ int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2 } #endif +int tpm2_argon2id_derive_split( + const char *pin, + const struct iovec *salt, + const Argon2IdParameters *argon2id_params, + struct iovec *ret_key1, + char **ret_b64_pin) { + + int r; + _cleanup_(iovec_done_erase) struct iovec derived = {}; + + assert(pin); + assert(salt); + assert(argon2id_params); + assert(ret_key1); + assert(ret_b64_pin); + + r = kdf_argon2id_derive( + &IOVEC_MAKE(pin, strlen(pin)), + salt, + argon2id_params, + /* derive_size= */ 64, &derived); + if (r < 0) + return r; + + assert(derived.iov_len == 64); + + const uint8_t *derived_bytes = derived.iov_base; + + ret_key1->iov_base = memdup(derived_bytes, 32); + if (!ret_key1->iov_base) + return log_oom(); + ret_key1->iov_len = 32; + + ssize_t n = base64mem(derived_bytes + 32, 32, ret_b64_pin); + if (n < 0) + return log_oom(); + + return 0; +} + +int tpm2_argon2id_hkdf( + const struct iovec *key1, + const struct iovec *unsealed, + struct iovec *ret_volume_key) { + + assert(key1); + assert(key1->iov_len > 0); + assert(unsealed); + assert(ret_volume_key); + + return kdf_hkdf_sha256( + key1, + unsealed, + &IOVEC_MAKE_STRING("systemd-tpm2-argon2id-lock"), + /* derive_size= */ 32, ret_volume_key); +} + char* tpm2_pcr_mask_to_string(uint32_t mask) { _cleanup_free_ char *s = NULL; @@ -9781,6 +9839,7 @@ int tpm2_make_luks2_json( const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, + const Argon2IdParameters *argon2id_params, sd_json_variant **ret) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *hmj = NULL, *pkmj = NULL; @@ -9792,6 +9851,8 @@ int tpm2_make_luks2_json( assert(n_blobs >= 1); assert(n_policy_hash >= 1); + POINTER_MAY_BE_NULL(argon2id_params); + if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) return -ENOMEM; @@ -9815,6 +9876,11 @@ int tpm2_make_luks2_json( if (r < 0) return r; + const Argon2IdParameters argon2id_params_safe = + FLAGS_SET(flags, TPM2_FLAGS_USE_ARGON2ID) && argon2id_params + ? *argon2id_params + : (Argon2IdParameters) {}; + /* Note: We made the mistake of using "-" in the field names, which isn't particular compatible with * other programming languages. Let's not make things worse though, i.e. future additions to the JSON * object should use "_" rather than "-" in field names. */ @@ -9835,7 +9901,10 @@ int tpm2_make_luks2_json( SD_JSON_BUILD_PAIR_CONDITION(pubkey_policy_ref != NULL, "tpm2_pubkey_ref", SD_JSON_BUILD_STRING(pubkey_policy_ref)), SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(salt), "tpm2_salt", JSON_BUILD_IOVEC_BASE64(salt)), SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk)), - SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(pcrlock_nv), "tpm2_pcrlock_nv", JSON_BUILD_IOVEC_BASE64(pcrlock_nv))); + SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(pcrlock_nv), "tpm2_pcrlock_nv", JSON_BUILD_IOVEC_BASE64(pcrlock_nv)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_ARGON2ID), "tpm2_argon2id_memcost", SD_JSON_BUILD_UNSIGNED(argon2id_params_safe.memcost_bytes)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_ARGON2ID), "tpm2_argon2id_iterations", SD_JSON_BUILD_UNSIGNED(argon2id_params_safe.iterations)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_ARGON2ID), "tpm2_argon2id_lanes", SD_JSON_BUILD_UNSIGNED(argon2id_params_safe.lanes))); if (r < 0) return r; @@ -9918,7 +9987,8 @@ int tpm2_parse_luks2_json( struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *ret_pcrlock_nv, - TPM2Flags *ret_flags) { + TPM2Flags *ret_flags, + Argon2IdParameters *ret_argon2id_params) { _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; _cleanup_free_ char *pubkey_ref = NULL; @@ -10068,6 +10138,52 @@ int tpm2_parse_luks2_json( return log_debug_errno(r, "Invalid base64 data in 'tpm2_pcrlock_nv' field."); } + Argon2IdParameters ap = {}; + + w = sd_json_variant_by_key(v, "tpm2_argon2id_memcost"); + if (w) { + if (!sd_json_variant_is_unsigned(w)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "tpm2_argon2id_memcost is not an unsigned integer."); + ap.memcost_bytes = sd_json_variant_unsigned(w); + if (ap.memcost_bytes == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), "tpm2_argon2id_memcost must be non-zero."); + } + + w = sd_json_variant_by_key(v, "tpm2_argon2id_iterations"); + if (w) { + uint64_t raw_iterations; + + if (!sd_json_variant_is_unsigned(w)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "tpm2_argon2id_iterations is not an unsigned integer."); + raw_iterations = sd_json_variant_unsigned(w); + if (raw_iterations == 0 || raw_iterations > UINT32_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), "tpm2_argon2id_iterations value out of range."); + ap.iterations = (uint32_t) raw_iterations; + } + + w = sd_json_variant_by_key(v, "tpm2_argon2id_lanes"); + if (w) { + uint64_t raw_lanes; + + if (!sd_json_variant_is_unsigned(w)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "tpm2_argon2id_lanes is not an unsigned integer."); + raw_lanes = sd_json_variant_unsigned(w); + if (raw_lanes == 0 || raw_lanes > UINT32_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), "tpm2_argon2id_lanes value out of range."); + ap.lanes = (uint32_t) raw_lanes; + } + + if (ap.memcost_bytes > 0 && ap.iterations > 0 && ap.lanes > 0) { + if (!iovec_is_set(&salt)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Argon2id PIN requires salt in LUKS2 token."); + + if (ap.memcost_bytes > physical_memory()) + return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), "Argon2id memory cost exceeds physical memory."); + + SET_FLAG(flags, TPM2_FLAGS_USE_ARGON2ID, true); + } else if (ap.memcost_bytes > 0 || ap.iterations > 0 || ap.lanes > 0) + return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), "Incomplete Argon2id parameters in LUKS2 token."); + if (ret_keyslot) *ret_keyslot = keyslot; if (ret_hash_pcr_mask) @@ -10098,6 +10214,8 @@ int tpm2_parse_luks2_json( *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv); if (ret_flags) *ret_flags = flags; + if (ret_argon2id_params) + *ret_argon2id_params = ap; return 0; } diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 4d2420ba03c..9daf2466a18 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -2,14 +2,16 @@ #pragma once #include "bitfield.h" +#include "crypto-util.h" #include "dlopen-note.h" #include "iovec-util.h" #include "shared-forward.h" #include "sha256.h" typedef enum TPM2Flags { - TPM2_FLAGS_USE_PIN = 1 << 0, - TPM2_FLAGS_USE_PCRLOCK = 1 << 1, + TPM2_FLAGS_USE_PIN = 1 << 0, + TPM2_FLAGS_USE_PCRLOCK = 1 << 1, + TPM2_FLAGS_USE_ARGON2ID = 1 << 2, } TPM2Flags; /* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a @@ -467,14 +469,17 @@ static inline int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, ch } #endif /* HAVE_TPM2 */ +int tpm2_argon2id_derive_split(const char *pin, const struct iovec *salt, const Argon2IdParameters *argon2id_params, struct iovec *ret_key1, char **ret_b64_pin); +int tpm2_argon2id_hkdf(const struct iovec *key1, const struct iovec *unsealed, struct iovec *ret_volume_key); + int tpm2_list_devices(bool legend, bool quiet); int tpm2_find_device_auto(char **ret); int tpm2_make_pcr_json_array(uint32_t pcr_mask, sd_json_variant **ret); int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret); -int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, const char *pubkey_policy_ref, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, sd_json_variant **ret); -int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, char **ret_pubkey_policy_ref, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec **ret_blobs, size_t *ret_n_blobs, struct iovec **ret_policy_hash, size_t *ret_n_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags); +int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, const char *pubkey_policy_ref, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, const Argon2IdParameters *argon2id_params, sd_json_variant **ret); +int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, char **ret_pubkey_policy_ref, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec **ret_blobs, size_t *ret_n_blobs, struct iovec **ret_policy_hash, size_t *ret_n_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags, Argon2IdParameters *ret_argon2id_params); /* Before v258 we used to bind to PCR 7 by default at various places if no explicit PCR mask was set. With * v258 we stopped doing that (since the SecureBoot DB is as much subject to regular updates by tools such as diff --git a/test/test-dlopen-note.py b/test/test-dlopen-note.py index 7882ab3dd46..295a0bc69c3 100755 --- a/test/test-dlopen-note.py +++ b/test/test-dlopen-note.py @@ -37,6 +37,7 @@ FUNCTION_FILTER = { 'dlopen_verbose': [], 'dlopen_many_sym_or_warn_sentinel': [], 'dlopen_dw_has_dwfl_set_sysroot': [], + 'dlopen_libcrypto_has_argon2id': [], # Mapped to multiple functions (expands generic wrappers into specific sub-layers) 'dlopen_tpm2': [ 'dlopen_tpm2_esys',