1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-19 22:50:17 +03:00

Merge pull request #22563 from grigorig/cryptenroll-tpm2-pin

sd-cryptenroll TPM2 PIN protected unlock
This commit is contained in:
Lennart Poettering 2022-03-16 18:04:28 +01:00 committed by GitHub
commit c2c7eea1e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 516 additions and 29 deletions

View File

@ -677,6 +677,14 @@
of the current PCR state.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>tpm2-pin=</option></term>
<listitem><para>Takes a boolean argument, defaults to <literal>false</literal>. Controls whether
TPM2 volume unlocking is bound to a PIN in addition to PCRs. Similarly, this option is only useful
when TPM2 enrollment metadata is not available.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>token-timeout=</option></term>

View File

@ -299,6 +299,24 @@
signatures likely will validate against pre-existing certificates.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-with-pin=</option><replaceable>BOOL</replaceable></term>
<listitem><para>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 <literal>no</literal>. Despite being called PIN, any character can be used, not just numbers.
</para>
<para>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,
<command>systemd-cryptenroll</command> does not control or configure the lockout mechanism. You may
use tpm2-tss tools to inspect or configure the dictionary attack lockout, with
<citerefentry><refentrytitle>tpm2_getcap</refentrytitle><manvolnum>1</manvolnum></citerefentry> and
<citerefentry><refentrytitle>tpm2_dictionarylockout</refentrytitle><manvolnum>1</manvolnum></citerefentry>
commands, respectively.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--wipe-slot=</option><arg rep="repeat">SLOT</arg></term>

View File

@ -4,7 +4,7 @@
#include <stdint.h>
#include <stdlib.h>
#define SHA256_DIGEST_SIZE 32
#include "sha256.h"
/* Unoptimized implementation based on FIPS 198. 'res' has to be allocated by
* the caller. Prefer external OpenSSL functions, and use this only when

View File

@ -1,7 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "ask-password-api.h"
#include "cryptenroll-tpm2.h"
#include "env-util.h"
#include "hexdecoct.h"
#include "json.h"
#include "memory-util.h"
@ -58,11 +60,78 @@ static int search_policy_hash(
return -ENOENT; /* Not found */
}
static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) {
_cleanup_free_ char *pin_str = NULL;
int r;
TPM2Flags flags = 0;
assert(ret_pin_str);
assert(ret_flags);
r = getenv_steal_erase("NEWPIN", &pin_str);
if (r < 0)
return log_error_errno(r, "Failed to acquire PIN from environment: %m");
if (r > 0)
flags |= TPM2_FLAGS_USE_PIN;
else {
for (size_t i = 5;; i--) {
_cleanup_strv_free_erase_ char **pin = NULL, **pin2 = NULL;
if (i <= 0)
return log_error_errno(
SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up.");
pin = strv_free_erase(pin);
r = ask_password_auto(
"Please enter TPM2 PIN:",
"drive-harddisk",
NULL,
"tpm2-pin",
"cryptenroll.tpm2-pin",
USEC_INFINITY,
0,
&pin);
if (r < 0)
return log_error_errno(r, "Failed to ask for user pin: %m");
assert(strv_length(pin) == 1);
r = ask_password_auto(
"Please enter TPM2 PIN (repeat):",
"drive-harddisk",
NULL,
"tpm2-pin",
"cryptenroll.tpm2-pin",
USEC_INFINITY,
0,
&pin2);
if (r < 0)
return log_error_errno(r, "Failed to ask for user pin: %m");
assert(strv_length(pin) == 1);
if (strv_equal(pin, pin2)) {
pin_str = strdup(*pin);
if (!pin_str)
return log_oom();
flags |= TPM2_FLAGS_USE_PIN;
break;
}
log_error("PINs didn't match, please try again!");
}
}
*ret_flags = flags;
*ret_pin_str = TAKE_PTR(pin_str);
return 0;
}
int enroll_tpm2(struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size,
const char *device,
uint32_t pcr_mask) {
uint32_t pcr_mask,
bool use_pin) {
_cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
@ -71,7 +140,9 @@ int enroll_tpm2(struct crypt_device *cd,
_cleanup_free_ void *blob = NULL, *hash = NULL;
uint16_t pcr_bank, primary_alg;
const char *node;
_cleanup_(erase_and_freep) char *pin_str = NULL;
int r, keyslot;
TPM2Flags flags = 0;
assert(cd);
assert(volume_key);
@ -80,7 +151,13 @@ int enroll_tpm2(struct crypt_device *cd,
assert_se(node = crypt_get_device_name(cd));
r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
if (use_pin) {
r = get_pin(&pin_str, &flags);
if (r < 0)
return r;
}
r = tpm2_seal(device, pcr_mask, pin_str, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
if (r < 0)
return r;
@ -97,7 +174,7 @@ int enroll_tpm2(struct crypt_device *cd,
/* Quick verification that everything is in order, we are not in a hurry after all. */
log_debug("Unsealing for verification...");
r = tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, &secret2, &secret2_size);
r = tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, pin_str, &secret2, &secret2_size);
if (r < 0)
return r;
@ -123,7 +200,7 @@ int enroll_tpm2(struct crypt_device *cd,
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
r = tpm2_make_luks2_json(keyslot, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, &v);
r = tpm2_make_luks2_json(keyslot, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, flags, &v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");

View File

@ -7,9 +7,9 @@
#include "log.h"
#if HAVE_TPM2
int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask);
int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, bool use_pin);
#else
static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask) {
static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, bool use_pin) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 key enrollment not supported.");
}

View File

@ -32,6 +32,7 @@ static char *arg_pkcs11_token_uri = NULL;
static char *arg_fido2_device = NULL;
static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static bool arg_tpm2_pin = false;
static char *arg_node = NULL;
static int *arg_wipe_slots = NULL;
static size_t arg_n_wipe_slots = 0;
@ -100,6 +101,8 @@ static int help(void) {
" Enroll a TPM2 device\n"
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
" Specify TPM2 PCRs to seal against\n"
" --tpm2-with-pin=BOOL\n"
" Whether to require entering a PIN to unlock the volume\n"
" --wipe-slot=SLOT1,SLOT2,…\n"
" Wipe specified slots\n"
"\nSee the %s for details.\n",
@ -121,6 +124,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_FIDO2_DEVICE,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
ARG_TPM2_PIN,
ARG_WIPE_SLOT,
ARG_FIDO2_WITH_PIN,
ARG_FIDO2_WITH_UP,
@ -139,6 +143,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "tpm2-with-pin", required_argument, NULL, ARG_TPM2_PIN },
{ "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
{}
};
@ -301,6 +306,14 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_TPM2_PIN: {
r = parse_boolean_argument("--tpm2-with-pin=", optarg, &arg_tpm2_pin);
if (r < 0)
return r;
break;
}
case ARG_WIPE_SLOT: {
const char *p = optarg;
@ -558,7 +571,7 @@ static int run(int argc, char *argv[]) {
break;
case ENROLL_TPM2:
slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask);
slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_pin);
break;
case _ENROLL_TYPE_INVALID:

View File

@ -6,8 +6,10 @@
#include "cryptsetup-token.h"
#include "cryptsetup-token-util.h"
#include "hexdecoct.h"
#include "json.h"
#include "luks2-tpm2.h"
#include "memory-util.h"
#include "strv.h"
#include "tpm2-util.h"
#include "version.h"
@ -78,7 +80,8 @@ _public_ int cryptsetup_token_open(
if (usrptr)
params = *(systemd_tpm2_plugin_params *)usrptr;
r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash);
TPM2Flags flags = 0;
r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash, &flags);
if (r < 0)
return log_debug_open_error(cd, r);
@ -101,6 +104,7 @@ _public_ int cryptsetup_token_open(
blob_size,
policy_hash,
policy_hash_size,
flags,
&decrypted_key,
&decrypted_key_size);
if (r < 0)
@ -135,6 +139,7 @@ _public_ void cryptsetup_token_dump(
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
int r;
TPM2Flags flags = 0;
uint32_t pcr_mask;
uint16_t pcr_bank, primary_alg;
size_t decoded_blob_size;
@ -144,7 +149,7 @@ _public_ void cryptsetup_token_dump(
assert(json);
r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash);
r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash, &flags);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m.");
@ -171,6 +176,7 @@ _public_ void cryptsetup_token_dump(
crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg)));
crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
}
/*
@ -268,5 +274,13 @@ _public_ int cryptsetup_token_validate(
if (r < 0)
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
w = json_variant_by_key(v, "tpm2-pin");
if (w) {
if (!json_variant_is_boolean(w)) {
crypt_log_debug(cd, "TPM2 PIN policy is not a boolean.");
return 1;
}
}
return 0;
}

View File

@ -1,11 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "ask-password-api.h"
#include "env-util.h"
#include "hexdecoct.h"
#include "json.h"
#include "log.h"
#include "luks2-tpm2.h"
#include "parse-util.h"
#include "random-util.h"
#include "strv.h"
#include "tpm2-util.h"
int acquire_luks2_key(
@ -17,10 +21,12 @@ int acquire_luks2_key(
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
_cleanup_free_ char *auto_device = NULL;
_cleanup_(erase_and_freep) char *pin_str = NULL;
int r;
assert(ret_decrypted_key);
@ -36,12 +42,22 @@ int acquire_luks2_key(
device = auto_device;
}
r = getenv_steal_erase("PIN", &pin_str);
if (r < 0)
return log_error_errno(r, "Failed to acquire PIN from environment: %m");
if (!r) {
/* PIN entry is not supported by plugin, let it fallback, possibly to sd-cryptsetup's
* internal handling. */
if (flags & TPM2_FLAGS_USE_PIN)
return -EOPNOTSUPP;
}
return tpm2_unseal(
device,
pcr_mask, pcr_bank,
primary_alg,
key_data, key_data_size,
policy_hash, policy_hash_size,
policy_hash, policy_hash_size, pin_str,
ret_decrypted_key, ret_decrypted_key_size);
}
@ -53,7 +69,8 @@ int parse_luks2_tpm2_data(
uint16_t *ret_pcr_bank,
uint16_t *ret_primary_alg,
char **ret_base64_blob,
char **ret_hex_policy_hash) {
char **ret_hex_policy_hash,
TPM2Flags *ret_flags) {
int r;
JsonVariant *w, *e;
@ -61,6 +78,7 @@ int parse_luks2_tpm2_data(
uint16_t pcr_bank = UINT16_MAX, primary_alg = TPM2_ALG_ECC;
_cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
TPM2Flags flags = 0;
assert(json);
assert(ret_pcr_mask);
@ -138,11 +156,21 @@ int parse_luks2_tpm2_data(
if (!hex_policy_hash)
return -ENOMEM;
w = json_variant_by_key(v, "tpm2-pin");
if (w) {
if (!json_variant_is_boolean(w))
return -EINVAL;
if (json_variant_boolean(w))
flags |= TPM2_FLAGS_USE_PIN;
}
*ret_pcr_mask = pcr_mask;
*ret_pcr_bank = pcr_bank;
*ret_primary_alg = primary_alg;
*ret_base64_blob = TAKE_PTR(base64_blob);
*ret_hex_policy_hash = TAKE_PTR(hex_policy_hash);
*ret_flags = flags;
return 0;
}

View File

@ -2,6 +2,8 @@
#pragma once
#include "tpm2-util.h"
struct crypt_device;
int acquire_luks2_key(
@ -13,6 +15,7 @@ int acquire_luks2_key(
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
@ -23,4 +26,5 @@ int parse_luks2_tpm2_data(
uint16_t *ret_pcr_bank,
uint16_t *ret_primary_alg,
char **ret_base64_blob,
char **ret_hex_policy_hash);
char **ret_hex_policy_hash,
TPM2Flags *ret_flags);

View File

@ -1,7 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "ask-password-api.h"
#include "cryptsetup-tpm2.h"
#include "env-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "json.h"
@ -9,6 +11,47 @@
#include "random-util.h"
#include "tpm2-util.h"
static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_pin_str) {
_cleanup_free_ char *pin_str = NULL;
_cleanup_strv_free_erase_ char **pin = NULL;
int r;
assert(ret_pin_str);
r = getenv_steal_erase("PIN", &pin_str);
if (r < 0)
return log_error_errno(r, "Failed to acquire PIN from environment: %m");
if (!r) {
if (headless)
return log_error_errno(
SYNTHETIC_ERRNO(ENOPKG),
"PIN querying disabled via 'headless' option. "
"Use the '$PIN' environment variable.");
pin = strv_free_erase(pin);
r = ask_password_auto(
"Please enter TPM2 PIN:",
"drive-harddisk",
NULL,
"tpm2-pin",
"cryptsetup.tpm2-pin",
until,
ask_password_flags,
&pin);
if (r < 0)
return log_error_errno(r, "Failed to ask for user pin: %m");
assert(strv_length(pin) == 1);
pin_str = strdup(pin[0]);
if (!pin_str)
return log_oom();
}
*ret_pin_str = TAKE_PTR(pin_str);
return r;
}
int acquire_tpm2_key(
const char *volume_name,
const char *device,
@ -22,6 +65,10 @@ int acquire_tpm2_key(
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
@ -64,7 +111,51 @@ int acquire_tpm2_key(
blob = loaded_blob;
}
return tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size);
if (!(flags & TPM2_FLAGS_USE_PIN))
return tpm2_unseal(
device,
pcr_mask,
pcr_bank,
primary_alg,
blob,
blob_size,
policy_hash,
policy_hash_size,
NULL,
ret_decrypted_key,
ret_decrypted_key_size);
for (int i = 5;; i--) {
_cleanup_(erase_and_freep) char *pin_str = NULL;
if (i <= 0)
return -EACCES;
r = get_pin(until, ask_password_flags, headless, &pin_str);
if (r < 0)
return r;
r = tpm2_unseal(
device,
pcr_mask,
pcr_bank,
primary_alg,
blob,
blob_size,
policy_hash,
policy_hash_size,
pin_str,
ret_decrypted_key,
ret_decrypted_key_size);
/* We get this error in case there is an authentication policy mismatch. This should
* not happen, but this avoids confusing behavior, just in case. */
if (IN_SET(r, -EPERM, -ENOLCK))
return r;
if (r < 0)
continue;
return r;
}
}
int find_tpm2_auto_data(
@ -79,11 +170,13 @@ int find_tpm2_auto_data(
void **ret_policy_hash,
size_t *ret_policy_hash_size,
int *ret_keyslot,
int *ret_token) {
int *ret_token,
TPM2Flags *ret_flags) {
_cleanup_free_ void *blob = NULL, *policy_hash = NULL;
size_t blob_size = 0, policy_hash_size = 0;
int r, keyslot = -1, token = -1;
TPM2Flags flags = 0;
uint32_t pcr_mask = 0;
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
@ -196,6 +289,16 @@ int find_tpm2_auto_data(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'tpm2-policy-hash' field.");
w = json_variant_by_key(v, "tpm2-pin");
if (w) {
if (!json_variant_is_boolean(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 PIN policy is not a boolean.");
if (json_variant_boolean(w))
flags |= TPM2_FLAGS_USE_PIN;
}
break;
}
@ -215,6 +318,7 @@ int find_tpm2_auto_data(
*ret_token = token;
*ret_pcr_bank = pcr_bank;
*ret_primary_alg = primary_alg;
*ret_flags = flags;
return 0;
}

View File

@ -3,9 +3,11 @@
#include <sys/types.h>
#include "ask-password-api.h"
#include "cryptsetup-util.h"
#include "log.h"
#include "time-util.h"
#include "tpm2-util.h"
#if HAVE_TPM2
@ -22,6 +24,10 @@ int acquire_tpm2_key(
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
@ -37,7 +43,8 @@ int find_tpm2_auto_data(
void **ret_policy_hash,
size_t *ret_policy_hash_size,
int *ret_keyslot,
int *ret_token);
int *ret_token,
TPM2Flags *ret_flags);
#else
@ -54,6 +61,10 @@ static inline int acquire_tpm2_key(
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
usec_t until,
bool headless,
AskPasswordFlags ask_password_flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
@ -73,7 +84,8 @@ static inline int find_tpm2_auto_data(
void **ret_policy_hash,
size_t *ret_policy_hash_size,
int *ret_keyslot,
int *ret_token) {
int *ret_token,
TPM2Flags *ret_flags) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support not available.");

View File

@ -82,6 +82,7 @@ static char *arg_fido2_rp_id = NULL;
static char *arg_tpm2_device = NULL;
static bool arg_tpm2_device_auto = false;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static bool arg_tpm2_pin = false;
static bool arg_headless = false;
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
@ -387,6 +388,16 @@ static int parse_one_option(const char *option) {
arg_tpm2_pcr_mask |= mask;
}
} else if ((val = startswith(option, "tpm2-pin="))) {
r = parse_boolean(val);
if (r < 0) {
log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
return 0;
}
arg_tpm2_pin = r;
} else if ((val = startswith(option, "try-empty-password="))) {
r = parse_boolean(val);
@ -1301,9 +1312,15 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
key_file, arg_keyfile_size, arg_keyfile_offset,
key_data, key_data_size,
NULL, 0, /* we don't know the policy hash */
arg_tpm2_pin,
until,
arg_headless,
arg_ask_password_flags,
&decrypted_key, &decrypted_key_size);
if (r >= 0)
break;
if (IN_SET(r, -EACCES, -ENOLCK))
return log_error_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking.");
if (ERRNO_IS_NOT_SUPPORTED(r)) /* TPM2 support not compiled in? */
return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available, falling back to traditional unlocking.");
if (r != -EAGAIN) /* EAGAIN means: no tpm2 chip found */
@ -1335,6 +1352,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
for (;;) {
uint32_t pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
r = find_tpm2_auto_data(
cd,
@ -1346,7 +1364,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
&blob, &blob_size,
&policy_hash, &policy_hash_size,
&keyslot,
&token);
&token,
&tpm2_flags);
if (r == -ENXIO)
/* No further TPM2 tokens found in the LUKS2 header. */
return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN),
@ -1369,7 +1388,13 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
NULL, 0, 0, /* no key file */
blob, blob_size,
policy_hash, policy_hash_size,
tpm2_flags,
until,
arg_headless,
arg_ask_password_flags,
&decrypted_key, &decrypted_key_size);
if (IN_SET(r, -EACCES, -ENOLCK))
return log_error_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking.");
if (r != -EPERM)
break;

View File

@ -8,6 +8,8 @@
#include "types-fundamental.h"
#define SHA256_DIGEST_SIZE 32
struct sha256_ctx {
uint32_t H[8];

View File

@ -2656,7 +2656,7 @@ static int partition_encrypt(
uint16_t pcr_bank, primary_alg;
int keyslot;
r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, NULL, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
@ -2678,7 +2678,7 @@ static int partition_encrypt(
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, &v);
r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, 0, &v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");

View File

@ -534,6 +534,7 @@ int encrypt_credential_and_warn(
r = tpm2_seal(tpm2_device,
tpm2_pcr_mask,
NULL,
&tpm2_key,
&tpm2_key_size,
&tpm2_blob,
@ -803,6 +804,7 @@ int decrypt_credential_and_warn(
le32toh(t->blob_size),
t->policy_hash_and_blob + le32toh(t->blob_size),
le32toh(t->policy_hash_size),
NULL,
&tpm2_key,
&tpm2_key_size);
if (r < 0)

View File

@ -14,6 +14,7 @@
#include "hexdecoct.h"
#include "memory-util.h"
#include "random-util.h"
#include "sha256.h"
#include "time-util.h"
static void *libtss2_esys_dl = NULL;
@ -30,10 +31,12 @@ TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_
TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL;
TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues);
TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL;
TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL;
TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL;
const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL;
@ -58,10 +61,12 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_Initialize),
DLSYM_ARG(Esys_Load),
DLSYM_ARG(Esys_PCR_Read),
DLSYM_ARG(Esys_PolicyAuthValue),
DLSYM_ARG(Esys_PolicyGetDigest),
DLSYM_ARG(Esys_PolicyPCR),
DLSYM_ARG(Esys_StartAuthSession),
DLSYM_ARG(Esys_Startup),
DLSYM_ARG(Esys_TR_SetAuth),
DLSYM_ARG(Esys_Unseal));
if (r < 0)
return r;
@ -594,6 +599,7 @@ static int tpm2_make_pcr_session(
ESYS_CONTEXT *c,
uint32_t pcr_mask,
uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */
bool use_pin,
ESYS_TR *ret_session,
TPM2B_DIGEST **ret_policy_digest,
TPMI_ALG_HASH *ret_pcr_bank) {
@ -669,6 +675,21 @@ static int tpm2_make_pcr_session(
goto finish;
}
if (use_pin) {
rc = sym_Esys_PolicyAuthValue(
c,
session,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to add authValue policy to TPM: %s",
sym_Tss2_RC_Decode(rc));
goto finish;
}
}
if (DEBUG_LOGGING || ret_policy_digest) {
log_debug("Acquiring policy digest.");
@ -717,9 +738,22 @@ finish:
return r;
}
static void hash_pin(const char *pin, size_t len, uint8_t ret_digest[static SHA256_DIGEST_SIZE]) {
struct sha256_ctx hash;
assert(pin);
sha256_init_ctx(&hash);
sha256_process_bytes(pin, len, &hash);
sha256_finish_ctx(&hash, ret_digest);
explicit_bzero_safe(&hash, sizeof(hash));
}
int tpm2_seal(
const char *device,
uint32_t pcr_mask,
const char *pin,
void **ret_secret,
size_t *ret_secret_size,
void **ret_blob,
@ -782,7 +816,8 @@ int tpm2_seal(
if (r < 0)
return r;
r = tpm2_make_pcr_session(c.esys_context, pcr_mask, UINT16_MAX, NULL, &policy_digest, &pcr_bank);
r = tpm2_make_pcr_session(c.esys_context, pcr_mask, UINT16_MAX, !!pin, NULL, &policy_digest,
&pcr_bank);
if (r < 0)
goto finish;
@ -813,6 +848,10 @@ int tpm2_seal(
.size = sizeof(hmac_sensitive.sensitive),
.sensitive.data.size = 32,
};
if (pin) {
hash_pin(pin, strlen(pin), hmac_sensitive.sensitive.userAuth.buffer);
hmac_sensitive.sensitive.userAuth.size = SHA256_DIGEST_SIZE;
}
assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size);
(void) tpm2_credit_random(c.esys_context);
@ -910,6 +949,7 @@ int tpm2_seal(
r = 0;
finish:
explicit_bzero_safe(&hmac_sensitive, sizeof(hmac_sensitive));
primary = flush_context_verbose(c.esys_context, primary);
return r;
}
@ -923,6 +963,7 @@ int tpm2_unseal(
size_t blob_size,
const void *known_policy_hash,
size_t known_policy_hash_size,
const char *pin,
void **ret_secret,
size_t *ret_secret_size) {
@ -978,7 +1019,7 @@ int tpm2_unseal(
if (r < 0)
return r;
r = tpm2_make_pcr_session(c.esys_context, pcr_mask, pcr_bank, &session, &policy_digest, NULL);
r = tpm2_make_pcr_session(c.esys_context, pcr_mask, pcr_bank, !!pin, &session, &policy_digest, NULL);
if (r < 0)
goto finish;
@ -1005,11 +1046,38 @@ int tpm2_unseal(
&public,
&hmac_key);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to load HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
/* If we're in dictionary attack lockout mode, we should see a lockout error here, which we
* need to translate for the caller. */
if (rc == TPM2_RC_LOCKOUT)
r = log_error_errno(
SYNTHETIC_ERRNO(ENOLCK),
"TPM2 device is in dictionary attack lockout mode.");
else
r = log_error_errno(
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to load HMAC key in TPM: %s",
sym_Tss2_RC_Decode(rc));
goto finish;
}
if (pin) {
TPM2B_AUTH auth = {
.size = SHA256_DIGEST_SIZE
};
hash_pin(pin, strlen(pin), auth.buffer);
rc = sym_Esys_TR_SetAuth(c.esys_context, hmac_key, &auth);
explicit_bzero_safe(&auth, sizeof(auth));
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to load PIN in TPM: %s",
sym_Tss2_RC_Decode(rc));
goto finish;
}
}
log_debug("Unsealing HMAC key.");
rc = sym_Esys_Unseal(
@ -1223,6 +1291,7 @@ int tpm2_make_luks2_json(
size_t blob_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *a = NULL;
@ -1263,7 +1332,9 @@ int tpm2_make_luks2_json(
JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(a)),
JSON_BUILD_PAIR_CONDITION(!!tpm2_pcr_bank_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_pcr_bank_to_string(pcr_bank))),
JSON_BUILD_PAIR_CONDITION(!!tpm2_primary_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_primary_alg_to_string(primary_alg))),
JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size))));
JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)),
JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)))
);
if (r < 0)
return r;

View File

@ -1,9 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
#include "json.h"
#include "macro.h"
typedef enum TPM2Flags {
TPM2_FLAGS_USE_PIN = 1 << 0,
} TPM2Flags;
#if HAVE_TPM2
#include <tss2/tss2_esys.h>
@ -20,10 +26,12 @@ extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1
extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion);
extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle);
extern TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues);
extern TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3);
extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest);
extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs);
extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle);
extern TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType);
extern TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue);
extern TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData);
extern const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc);
@ -35,8 +43,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz
int dlopen_tpm2(void);
int tpm2_seal(const char *device, uint32_t pcr_mask, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, void **ret_secret, size_t *ret_secret_size);
int tpm2_seal(const char *device, uint32_t pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, const char *pin, void **ret_secret, size_t *ret_secret_size);
#endif
@ -45,7 +53,7 @@ int tpm2_find_device_auto(int log_level, char **ret);
int tpm2_parse_pcrs(const char *s, uint32_t *ret);
int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret);
int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, JsonVariant **ret);
#define TPM2_PCRS_MAX 24

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
all setup run clean clean-again:
@TEST_BASE_DIR=../ ./test.sh --$@
.PHONY: all setup run clean clean-again

40
test/TEST-70-TPM2/test.sh Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
TEST_DESCRIPTION="cryptenroll/cryptsetup with TPM2 devices"
IMAGE_NAME="tpm2"
TEST_NO_NSPAWN=1
TEST_REQUIRE_INSTALL_TESTS=0
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
command -v swtpm >/dev/null 2>&1 || exit 0
command -v tpm2_pcrextend >/dev/null 2>&1 || exit 0
test_append_files() {
(
local workspace="${1:?}"
instmods tpm tpm_tis tpm_ibmvtpm
install_dmevent
generate_module_dependencies
inst_binary tpm2_pcrextend
)
}
machine="$(uname -m)"
tpmdevice="tpm-tis"
if [ "$machine" = "ppc64le" ]; then
# tpm-spapr support was introduced in qemu 5.0.0. Skip test for old qemu versions.
qemu_min_version "5.0.0" || exit 0
tpmdevice="tpm-spapr"
fi
tpmstate=$(mktemp -d)
swtpm socket --tpm2 --tpmstate dir="$tpmstate" --ctrl type=unixio,path="$tpmstate/sock" &
trap 'kill %%; rm -rf $tpmstate' SIGINT EXIT
QEMU_OPTIONS="-chardev socket,id=chrtpm,path=$tpmstate/sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device $tpmdevice,tpmdev=tpm0"
do_test "$@"

View File

@ -1213,7 +1213,7 @@ install_missing_libraries() {
local lib path
# A number of dependencies is now optional via dlopen, so the install
# script will not pick them up, since it looks at linkage.
for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu libfido2 libbpf libelf libdw; do
for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu tss2-tcti-device libfido2 libbpf libelf libdw; do
ddebug "Searching for $lib via pkg-config"
if pkg-config --exists "$lib"; then
path="$(pkg-config --variable=libdir "$lib")"

View File

@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-70-TPM2
[Service]
Type=oneshot
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh

48
test/units/testsuite-70.sh Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -ex
export SYSTEMD_LOG_LEVEL=debug
# Prepare fresh disk image
img="/var/tmp/test.img"
dd if=/dev/zero of=$img bs=1024k count=20 status=none
echo -n passphrase >/tmp/passphrase
cryptsetup luksFormat -q --use-urandom $img /tmp/passphrase
# Enroll unlock with default PCR policy
env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto $img
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
/usr/lib/systemd/systemd-cryptsetup detach test-volume
# Check with wrong PCR
tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
# Enroll unlock with PCR+PIN policy
systemd-cryptenroll --wipe-slot=tpm2 $img
env PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true $img
env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
/usr/lib/systemd/systemd-cryptsetup detach test-volume
# Check failure with wrong PIN
env PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
# Check failure with wrong PCR (and correct PIN)
tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000
env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
# Enroll unlock with PCR 0+7
systemd-cryptenroll --wipe-slot=tpm2 $img
env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $img
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
/usr/lib/systemd/systemd-cryptsetup detach test-volume
# Check with wrong PCR 0
tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1
echo OK >/testok
exit 0