aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2024-04-18 21:11:27 +0200
committerGitHub <noreply@github.com>2024-04-18 21:11:27 +0200
commitdd37963affade1938db73df25f8c1b4892dcd2d1 (patch)
tree353193324aa1e88b6e076d17bd192929dc5085b6
parentMerge pull request #32121 from CodethinkLabs/basic-mkosi-integration-tests (diff)
parentci: update tests to showcase new option a bit (diff)
downloadsystemd-dd37963affade1938db73df25f8c1b4892dcd2d1.tar.xz
systemd-dd37963affade1938db73df25f8c1b4892dcd2d1.zip
Merge pull request #31790 from poettering/pcrlock-policy-fix
Replace PolicyAuthValue by PolicySigned as access policy for pcrlock policy nvindex
-rw-r--r--NEWS36
-rw-r--r--man/systemd-pcrlock.xml17
-rw-r--r--src/pcrlock/pcrlock.c76
-rw-r--r--src/shared/tpm2-util.c252
-rw-r--r--src/shared/tpm2-util.h9
-rwxr-xr-xtest/units/testsuite-70.pcrlock.sh8
6 files changed, 328 insertions, 70 deletions
diff --git a/NEWS b/NEWS
index f91e8e8914d..a454c0d1d42 100644
--- a/NEWS
+++ b/NEWS
@@ -461,20 +461,8 @@ CHANGES WITH 256-rc1:
* confexts are loaded by systemd-stub from the ESP as well.
- * The pcrlock policy is saved in an unencrypted credential file
- "pcrlock.<entry-token>.cred" under XBOOTLDR/ESP in the
- /loader/credentials/ directory. It will be picked up at boot by
- systemd-stub and passed to the initrd, where it can be used to unlock
- the root file system.
-
* kernel-install gained support for --root= for the 'list' verb.
- * systemd-pcrlock gained an --entry-token= option to configure the
- entry-token.
-
- * systemd-pcrlock now provides a basic Varlink interface and can be run
- as a daemon via a template unit.
-
* bootctl now provides a basic Varlink interface and can be run as a
daemon via a template unit.
@@ -498,6 +486,30 @@ CHANGES WITH 256-rc1:
for enrolling "dbx" too (Previously, only db/KEK/PK enrollment was
supported). It also now supports UEFI "Custom" mode.
+ * The pcrlock policy is saved in an unencrypted credential file
+ "pcrlock.<entry-token>.cred" under XBOOTLDR/ESP in the
+ /loader/credentials/ directory. It will be picked up at boot by
+ systemd-stub and passed to the initrd, where it can be used to unlock
+ the root file system.
+
+ * systemd-pcrlock gained an --entry-token= option to configure the
+ entry-token.
+
+ * systemd-pcrlock now provides a basic Varlink interface and can be run
+ as a daemon via a template unit.
+
+ * systemd-pcrlock's TPM nvindex access policy has been modified, this
+ means that previous pcrlock policies stored in nvindexes are
+ invalidated. They must be removed (systemd-pcrlock remove-policy) and
+ recreated (systemd-pcrlock make-policy). For the time being
+ systemd-pcrlock remains an experimental feature, but it is expected
+ to become stable in the next release, i.e. v257.
+
+ * systemd-pcrlock's --recovery-pin= switch now takes three values:
+ "hide", "show", "query". If "show" is selected the automatically
+ generated recovery PIN is shown to the user. If "query" is selected
+ then the PIN is queried from the user.
+
systemd-run/run0:
* systemd-run is now a multi-call binary. When invoked as 'run0', it
diff --git a/man/systemd-pcrlock.xml b/man/systemd-pcrlock.xml
index 2c674a34b4c..e2e861b2467 100644
--- a/man/systemd-pcrlock.xml
+++ b/man/systemd-pcrlock.xml
@@ -504,13 +504,16 @@
<varlistentry>
<term><option>--recovery-pin=</option></term>
- <listitem><para>Takes a boolean. Defaults to false. Honoured by <command>make-policy</command>. If
- true, will query the user for a PIN to unlock the TPM2 NV index with. If no policy was created before
- this PIN is used to protect the newly allocated NV index. If a policy has been created before the PIN
- is used to unlock write access to the NV index. If this option is not used a PIN is automatically
- generated. Regardless if user supplied or automatically generated, it is stored in encrypted form in
- the policy metadata file. The recovery PIN may be used to regain write access to an NV index in case
- the access policy became out of date.</para>
+ <listitem><para>Takes one of <literal>hide</literal>, <literal>show</literal> or
+ <literal>query</literal>. Defaults to <literal>hide</literal>. Honoured by
+ <command>make-policy</command>. If <literal>query</literal>, will query the user for a PIN to unlock
+ the TPM2 NV index with. If no policy was created before, this PIN is used to protect the newly
+ allocated NV index. If a policy has been created before, the PIN is used to unlock write access to
+ the NV index. If either <literal>hide</literal> or <literal>show</literal> is used, a PIN is
+ automatically generated, and — only in case of <literal>show</literal> — displayed on
+ screen. Regardless if user supplied or automatically generated, it is stored in encrypted form in the
+ policy metadata file. The recovery PIN may be used to regain write access to an NV index in case the
+ access policy became out of date.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c
index f6b76d291bc..2b2720978a7 100644
--- a/src/pcrlock/pcrlock.c
+++ b/src/pcrlock/pcrlock.c
@@ -43,6 +43,7 @@
#include "random-util.h"
#include "recovery-key.h"
#include "sort-util.h"
+#include "string-table.h"
#include "terminal-util.h"
#include "tpm2-util.h"
#include "unaligned.h"
@@ -52,6 +53,14 @@
#include "varlink-io.systemd.PCRLock.h"
#include "verbs.h"
+typedef enum RecoveryPinMode {
+ RECOVERY_PIN_HIDE, /* generate a recovery PIN automatically, but don't show it (alias: "no") */
+ RECOVERY_PIN_SHOW, /* generate a recovery PIN automatically, and display it to the user */
+ RECOVERY_PIN_QUERY, /* asks the user for a PIN to use interactively (alias: "yes") */
+ _RECOVERY_PIN_MODE_MAX,
+ _RECOVERY_PIN_MODE_INVALID = -EINVAL,
+} RecoveryPinMode;
+
static PagerFlags arg_pager_flags = 0;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF|JSON_FORMAT_NEWLINE;
static char **arg_components = NULL;
@@ -62,7 +71,7 @@ static bool arg_raw_description = false;
static char *arg_location_start = NULL;
static char *arg_location_end = NULL;
static TPM2_HANDLE arg_nv_index = 0;
-static bool arg_recovery_pin = false;
+static RecoveryPinMode arg_recovery_pin = RECOVERY_PIN_HIDE;
static char *arg_policy_path = NULL;
static bool arg_force = false;
static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
@@ -104,6 +113,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep);
(UINT32_C(1) << TPM2_PCR_SHIM_POLICY) | \
(UINT32_C(1) << TPM2_PCR_SYSTEM_IDENTITY))
+static const char* recovery_pin_mode_table[_RECOVERY_PIN_MODE_MAX] = {
+ [RECOVERY_PIN_HIDE] = "hide",
+ [RECOVERY_PIN_SHOW] = "show",
+ [RECOVERY_PIN_QUERY] = "query",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(recovery_pin_mode, RecoveryPinMode, RECOVERY_PIN_QUERY);
+
typedef struct EventLogRecordBank EventLogRecordBank;
typedef struct EventLogRecord EventLogRecord;
typedef struct EventLogRegisterBank EventLogRegisterBank;
@@ -4320,7 +4337,7 @@ static int write_boot_policy_file(const char *json_text) {
return 1;
}
-static int make_policy(bool force, bool recovery_pin) {
+static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) {
int r;
/* Here's how this all works: after predicting all possible PCR values for next boot (with
@@ -4444,7 +4461,7 @@ static int make_policy(bool force, bool recovery_pin) {
/* Acquire a recovery PIN, either from the user, or create a randomized one */
_cleanup_(erase_and_freep) char *pin = NULL;
- if (recovery_pin) {
+ if (recovery_pin_mode == RECOVERY_PIN_QUERY) {
r = getenv_steal_erase("PIN", &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire PIN from environment: %m");
@@ -4473,16 +4490,16 @@ static int make_policy(bool force, bool recovery_pin) {
}
} else if (!have_old_policy) {
- char rnd[256];
-
- r = crypto_random_bytes(rnd, sizeof(rnd));
+ r = make_recovery_key(&pin);
if (r < 0)
return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m");
- (void) base64mem(rnd, sizeof(rnd), &pin);
- explicit_bzero_safe(rnd, sizeof(rnd));
- if (!pin)
- return log_oom();
+ if (recovery_pin_mode == RECOVERY_PIN_SHOW)
+ printf("%s Selected recovery PIN is: %s%s%s\n",
+ special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY),
+ ansi_highlight_cyan(),
+ pin,
+ ansi_normal());
}
_cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
@@ -4500,7 +4517,7 @@ static int make_policy(bool force, bool recovery_pin) {
CLEANUP_ERASE(auth);
if (pin) {
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth);
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth);
if (r < 0)
return log_error_errno(r, "Failed to hash PIN: %m");
} else {
@@ -4567,15 +4584,28 @@ static int make_policy(bool force, bool recovery_pin) {
log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1));
}
- TPM2B_NV_PUBLIC nv_public = {};
+ /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL;
+ r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle);
+ if (r < 0)
+ return r;
+ TPM2B_NV_PUBLIC nv_public = {};
usec_t nv_index_start_usec = now(CLOCK_MONOTONIC);
if (!iovec_is_set(&nv_blob)) {
+ _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL;
+ r = tpm2_get_name(
+ tc,
+ pin_handle,
+ &pin_name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get name of PIN from TPM2: %m");
+
TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
- r = tpm2_calculate_policy_auth_value(&recovery_policy_digest);
+ r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name);
if (r < 0)
- return log_error_errno(r, "Failed to calculate authentication value policy: %m");
+ return log_error_errno(r, "Failed to calculate PolicySigned policy: %m");
log_debug("Allocating NV index to write PCR policy to...");
r = tpm2_define_policy_nv_index(
@@ -4583,8 +4613,6 @@ static int make_policy(bool force, bool recovery_pin) {
encryption_session,
arg_nv_index,
&recovery_policy_digest,
- pin,
- &auth,
&nv_index,
&nv_handle,
&nv_public);
@@ -4594,10 +4622,6 @@ static int make_policy(bool force, bool recovery_pin) {
return log_error_errno(r, "Failed to allocate NV index: %m");
}
- r = tpm2_set_auth_binary(tc, nv_handle, &auth);
- if (r < 0)
- return log_error_errno(r, "Failed to set authentication value on NV index: %m");
-
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
r = tpm2_make_policy_session(
tc,
@@ -4607,9 +4631,11 @@ static int make_policy(bool force, bool recovery_pin) {
if (r < 0)
return log_error_errno(r, "Failed to allocate policy session: %m");
- r = tpm2_policy_auth_value(
+ r = tpm2_policy_signed_hmac_sha256(
tc,
policy_session,
+ pin_handle,
+ &IOVEC_MAKE(auth.buffer, auth.size),
/* ret_policy_digest= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to submit authentication value policy: %m");
@@ -5044,9 +5070,9 @@ static int parse_argv(int argc, char *argv[]) {
}
case ARG_RECOVERY_PIN:
- r = parse_boolean_argument("--recovery-pin", optarg, &arg_recovery_pin);
- if (r < 0)
- return r;
+ arg_recovery_pin = recovery_pin_mode_from_string(optarg);
+ if (arg_recovery_pin < 0)
+ return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", optarg);
break;
case ARG_PCRLOCK:
@@ -5210,7 +5236,7 @@ static int vl_method_make_policy(Varlink *link, JsonVariant *parameters, Varlink
if (r != 0)
return r;
- r = make_policy(p.force, /* recovery_key= */ false);
+ r = make_policy(p.force, /* recovery_key= */ RECOVERY_PIN_HIDE);
if (r < 0)
return r;
if (r == 0)
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index af2f265bab9..7ec9c9a9747 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -29,6 +29,7 @@
#include "recurse-dir.h"
#include "sha256.h"
#include "sort-util.h"
+#include "sparse-endian.h"
#include "stat-util.h"
#include "string-table.h"
#include "sync-util.h"
@@ -65,6 +66,7 @@ static DLSYM_FUNCTION(Esys_PolicyAuthorizeNV);
static DLSYM_FUNCTION(Esys_PolicyGetDigest);
static DLSYM_FUNCTION(Esys_PolicyOR);
static DLSYM_FUNCTION(Esys_PolicyPCR);
+static DLSYM_FUNCTION(Esys_PolicySigned);
static DLSYM_FUNCTION(Esys_ReadPublic);
static DLSYM_FUNCTION(Esys_StartAuthSession);
static DLSYM_FUNCTION(Esys_Startup);
@@ -77,6 +79,7 @@ static DLSYM_FUNCTION(Esys_TR_GetTpmHandle);
static DLSYM_FUNCTION(Esys_TR_Serialize);
static DLSYM_FUNCTION(Esys_TR_SetAuth);
static DLSYM_FUNCTION(Esys_TRSess_GetAttributes);
+static DLSYM_FUNCTION(Esys_TRSess_GetNonceTPM);
static DLSYM_FUNCTION(Esys_TRSess_SetAttributes);
static DLSYM_FUNCTION(Esys_Unseal);
static DLSYM_FUNCTION(Esys_VerifySignature);
@@ -132,6 +135,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_PolicyGetDigest),
DLSYM_ARG(Esys_PolicyOR),
DLSYM_ARG(Esys_PolicyPCR),
+ DLSYM_ARG(Esys_PolicySigned),
DLSYM_ARG(Esys_ReadPublic),
DLSYM_ARG(Esys_StartAuthSession),
DLSYM_ARG(Esys_Startup),
@@ -143,6 +147,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_TR_Serialize),
DLSYM_ARG(Esys_TR_SetAuth),
DLSYM_ARG(Esys_TRSess_GetAttributes),
+ DLSYM_ARG(Esys_TRSess_GetNonceTPM),
DLSYM_ARG(Esys_TRSess_SetAttributes),
DLSYM_ARG(Esys_Unseal),
DLSYM_ARG(Esys_VerifySignature));
@@ -2238,9 +2243,9 @@ static int tpm2_load_external(
#if HAVE_TSS2_ESYS3
/* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested
* hierarchy, older versions need TPM2_RH_* instead. */
- ESYS_TR_RH_OWNER,
+ private ? ESYS_TR_RH_NULL : ESYS_TR_RH_OWNER,
#else
- TPM2_RH_OWNER,
+ private ? TPM2_RH_NULL : TPM2_RH_OWNER,
#endif
&handle->esys_handle);
if (rc != TSS2_RC_SUCCESS)
@@ -3058,7 +3063,7 @@ static void tpm2_trim_auth_value(TPM2B_AUTH *auth) {
log_debug("authValue ends in 0, trimming as required by the TPM2 specification Part 1 section 'HMAC Computation' authValue Note 2.");
}
-int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) {
+int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) {
TPM2B_AUTH auth = {};
int r;
@@ -3105,7 +3110,7 @@ int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin) {
CLEANUP_ERASE(auth);
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth);
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth);
if (r < 0)
return r;
@@ -3392,7 +3397,7 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name)
*
* The handle must reference a key already present in the TPM. It may be either a public key only, or a
* public/private keypair. */
-static int tpm2_get_name(
+int tpm2_get_name(
Tpm2Context *c,
const Tpm2Handle *handle,
TPM2B_NAME **ret_name) {
@@ -3530,6 +3535,150 @@ int tpm2_policy_auth_value(
return tpm2_get_policy_digest(c, session, ret_policy_digest);
}
+/* Extend 'digest' with the PolicySigned calculated hash. */
+int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name) {
+ TPM2_CC command = TPM2_CC_PolicySigned;
+ TSS2_RC rc;
+ int r;
+
+ assert(digest);
+ assert(digest->size == SHA256_DIGEST_SIZE);
+ assert(name);
+
+ r = dlopen_tpm2();
+ if (r < 0)
+ return log_debug_errno(r, "TPM2 support not installed: %m");
+
+ uint8_t buf[sizeof(command)];
+ size_t offset = 0;
+
+ rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal PolicySigned command: %s", sym_Tss2_RC_Decode(rc));
+
+ if (offset != sizeof(command))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Offset 0x%zx wrong after marshalling PolicySigned command", offset);
+
+ struct iovec data[] = {
+ IOVEC_MAKE(buf, offset),
+ IOVEC_MAKE(name->name, name->size),
+ };
+
+ r = tpm2_digest_many(TPM2_ALG_SHA256, digest, data, ELEMENTSOF(data), /* extend= */ true);
+ if (r < 0)
+ return r;
+
+ const TPM2B_NONCE policyRef = {}; /* For now, we do not make use of the policyRef stuff */
+
+ r = tpm2_digest_buffer(TPM2_ALG_SHA256, digest, policyRef.buffer, policyRef.size, /* extend= */ true);
+ if (r < 0)
+ return r;
+
+ tpm2_log_debug_digest(digest, "PolicySigned calculated digest");
+
+ return 0;
+}
+
+int tpm2_policy_signed_hmac_sha256(
+ Tpm2Context *c,
+ const Tpm2Handle *session,
+ const Tpm2Handle *hmac_key_handle,
+ const struct iovec *hmac_key,
+ TPM2B_DIGEST **ret_policy_digest) {
+
+#if HAVE_OPENSSL
+ TSS2_RC rc;
+ int r;
+
+ assert(c);
+ assert(session);
+ assert(hmac_key_handle);
+ assert(iovec_is_set(hmac_key));
+
+ /* This sends a TPM2_PolicySigned command to the tpm. As signature key we use an HMAC-SHA256 key
+ * specified in the hmac_key parameter. The secret key must be loaded into the TPM already and
+ * referenced in hmac_key_handle. */
+
+ log_debug("Submitting PolicySigned policy for HMAC-SHA256.");
+
+ /* Acquire the nonce from the TPM that we shall sign */
+ _cleanup_(Esys_Freep) TPM2B_NONCE *nonce = NULL;
+ rc = sym_Esys_TRSess_GetNonceTPM(
+ c->esys_context,
+ session->esys_handle,
+ &nonce);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to determine NoneTPM of auth session: %s",
+ sym_Tss2_RC_Decode(rc));
+
+ be32_t expiration = htobe64(0);
+ const TPM2B_DIGEST cpHashA = {}; /* For now, we do not make use of the cpHashA stuff */
+ const TPM2B_NONCE policyRef = {}; /* ditto, we do not bother with policyRef */
+
+ /* Put together the data to sign, as per TPM2 Spec Part 3, 23.3.1 */
+ struct iovec data_to_sign[] = {
+ IOVEC_MAKE(nonce->buffer, nonce->size),
+ IOVEC_MAKE(&expiration, sizeof(expiration)),
+ IOVEC_MAKE(cpHashA.buffer, cpHashA.size),
+ IOVEC_MAKE(policyRef.buffer, policyRef.size),
+ };
+
+ /* Now calculate the digest of the data we put together */
+ TPM2B_DIGEST digest_to_sign;
+ r = tpm2_digest_many(TPM2_ALG_SHA256, &digest_to_sign, data_to_sign, ELEMENTSOF(data_to_sign), /* extend= */ false);
+ if (r < 0)
+ return r;
+
+ unsigned char hmac_signature[SHA256_DIGEST_SIZE];
+ unsigned hmac_signature_size = sizeof(hmac_signature);
+
+ /* And sign this with our key */
+ if (!HMAC(EVP_sha256(),
+ hmac_key->iov_base,
+ hmac_key->iov_len,
+ digest_to_sign.buffer,
+ digest_to_sign.size,
+ hmac_signature,
+ &hmac_signature_size))
+ return -ENOTRECOVERABLE;
+
+ /* Now bring the signature into a format that the TPM understands */
+ TPMT_SIGNATURE sig = {
+ .sigAlg = TPM2_ALG_HMAC,
+ .signature.hmac.hashAlg = TPM2_ALG_SHA256,
+ };
+ assert(hmac_signature_size == sizeof(sig.signature.hmac.digest.sha256));
+ memcpy(sig.signature.hmac.digest.sha256, hmac_signature, hmac_signature_size);
+
+ /* And submit the whole shebang to the TPM */
+ rc = sym_Esys_PolicySigned(
+ c->esys_context,
+ hmac_key_handle->esys_handle,
+ session->esys_handle,
+ /* shandle1= */ ESYS_TR_NONE,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ nonce,
+ &cpHashA,
+ &policyRef,
+ expiration,
+ &sig,
+ /* timeout= */ NULL,
+ /* policyTicket= */ NULL);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to add PolicySigned policy to TPM: %s",
+ sym_Tss2_RC_Decode(rc));
+
+ return tpm2_get_policy_digest(c, session, ret_policy_digest);
+#else /* HAVE_OPENSSL */
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
+#endif
+}
+
int tpm2_calculate_policy_authorize_nv(
const TPM2B_NV_PUBLIC *public_info,
TPM2B_DIGEST *digest) {
@@ -4785,7 +4934,7 @@ static int tpm2_calculate_seal_private(
TPM2B_AUTH auth = {};
if (pin) {
- r = tpm2_get_pin_auth(parent->publicArea.nameAlg, pin, &auth);
+ r = tpm2_auth_value_from_pin(parent->publicArea.nameAlg, pin, &auth);
if (r < 0)
return r;
}
@@ -5250,7 +5399,7 @@ int tpm2_seal(Tpm2Context *c,
CLEANUP_ERASE(hmac_sensitive);
if (pin) {
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth);
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth);
if (r < 0)
return r;
}
@@ -5622,8 +5771,6 @@ int tpm2_define_policy_nv_index(
const Tpm2Handle *session,
TPM2_HANDLE requested_nv_index,
const TPM2B_DIGEST *write_policy,
- const char *pin,
- const TPM2B_AUTH *auth,
TPM2_HANDLE *ret_nv_index,
Tpm2Handle **ret_nv_handle,
TPM2B_NV_PUBLIC *ret_nv_public) {
@@ -5633,7 +5780,10 @@ int tpm2_define_policy_nv_index(
int r;
assert(c);
- assert(pin || auth);
+
+ /* Allocates an nvindex to store a policy for use in PolicyAuthorizeNV in. This is where pcrlock then
+ * stores its predicted PCR policies in. If 'requested_nv_index' will try to allocate the specified
+ * nvindex, otherwise will find a free one, and use that. */
r = tpm2_handle_new(c, &new_handle);
if (r < 0)
@@ -5641,17 +5791,6 @@ int tpm2_define_policy_nv_index(
new_handle->flush = false; /* This is a persistent NV index, don't flush hence */
- TPM2B_AUTH _auth = {};
- CLEANUP_ERASE(_auth);
-
- if (!auth) {
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &_auth);
- if (r < 0)
- return r;
-
- auth = &_auth;
- }
-
for (unsigned try = 0; try < 25U; try++) {
TPM2_HANDLE nv_index;
@@ -5679,7 +5818,7 @@ int tpm2_define_policy_nv_index(
/* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD,
/* shandle2= */ ESYS_TR_NONE,
/* shandle3= */ ESYS_TR_NONE,
- auth,
+ /* auth= */ NULL,
&public_info,
&new_handle->esys_handle);
@@ -7031,6 +7170,75 @@ int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) {
*ret = device_key_public;
return 0;
}
+
+int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret) {
+ int r;
+
+ assert(c);
+ assert(pin);
+ assert(ret);
+
+ log_debug("Converting PIN into TPM2 HMAC-SHA256 object.");
+
+ /* Load the PIN (which we have stored in the "auth" TPM2B_AUTH) into the TPM as an HMAC key so that
+ * we can use it in a TPM2_PolicySigned() to write to the nvindex. For that we'll prep a pair of
+ * TPM2B_PUBLIC and TPM2B_SENSITIVE that defines an HMAC-SHA256 keyed hash function, and initialize
+ * it based on on the provided PIN data. */
+
+ TPM2B_PUBLIC auth_hmac_public = {
+ .publicArea = {
+ .type = TPM2_ALG_KEYEDHASH,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_SIGN_ENCRYPT,
+ .parameters.keyedHashDetail.scheme = {
+ .scheme = TPM2_ALG_HMAC,
+ .details.hmac.hashAlg = TPM2_ALG_SHA256,
+ },
+ .unique.keyedHash.size = SHA256_DIGEST_SIZE,
+ },
+ };
+
+ TPM2B_SENSITIVE auth_hmac_private = {
+ .sensitiveArea = {
+ .sensitiveType = TPM2_ALG_KEYEDHASH,
+ .sensitive.bits.size = pin->size,
+ .seedValue.size = SHA256_DIGEST_SIZE,
+ },
+ };
+
+ /* Copy in the key data */
+ memcpy_safe(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, pin->buffer, pin->size);
+
+ /* NB: We initialize the seed of the TPMT_SENSITIVE structure to all zeroes, since we want a stable
+ * "name" of the PIN object */
+
+ /* Now calculate the "unique" field for the public area, based on the sensitive data, according to
+ * the algorithm in the TPM2 spec, part 1, Section 27.5.3.2 */
+ struct iovec sensitive_data[] = {
+ IOVEC_MAKE(auth_hmac_private.sensitiveArea.seedValue.buffer, auth_hmac_private.sensitiveArea.seedValue.size),
+ IOVEC_MAKE(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, auth_hmac_private.sensitiveArea.sensitive.bits.size),
+ };
+ r = tpm2_digest_many(
+ auth_hmac_public.publicArea.nameAlg,
+ &auth_hmac_public.publicArea.unique.keyedHash,
+ sensitive_data,
+ ELEMENTSOF(sensitive_data),
+ /* extend= */ false);
+ if (r < 0)
+ return r;
+
+ /* And now load the public/private parts into the TPM and get a handle back */
+ r = tpm2_load_external(
+ c,
+ session,
+ &auth_hmac_public,
+ &auth_hmac_private,
+ ret);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load PIN into TPM2: %m");
+
+ return 0;
+}
#endif
char *tpm2_pcr_mask_to_string(uint32_t mask) {
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index f9f29e310d9..f09b71f84c5 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -131,6 +131,7 @@ int tpm2_marshal_nv_public(const TPM2B_NV_PUBLIC *nv_public, void **ret, size_t
int tpm2_unmarshal_nv_public(const void *data, size_t size, TPM2B_NV_PUBLIC *ret_nv_public);
int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, const TPM2B_ENCRYPTED_SECRET *seed, void **ret_blob, size_t *ret_blob_size);
int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private, TPM2B_ENCRYPTED_SECRET *ret_seed);
+int tpm2_get_name(Tpm2Context *c, const Tpm2Handle *handle, TPM2B_NAME **ret_name);
bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg);
bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command);
@@ -256,7 +257,7 @@ int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE
int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values);
int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values);
-int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth);
+int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth);
int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin);
int tpm2_set_auth_binary(Tpm2Context *c, const Tpm2Handle *handle, const TPM2B_AUTH *auth);
@@ -267,6 +268,7 @@ int tpm2_policy_authorize_nv(Tpm2Context *c, const Tpm2Handle *session, const Tp
int tpm2_policy_pcr(Tpm2Context *c, const Tpm2Handle *session, const TPML_PCR_SELECTION *pcr_selection, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_or(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_super_pcr(Tpm2Context *c, const Tpm2Handle *session, const Tpm2PCRPrediction *prediction, uint16_t algorithm);
+int tpm2_policy_signed_hmac_sha256(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *hmac_key_handle, const struct iovec *hmac_key, TPM2B_DIGEST **ret_policy_digest);
int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name);
int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name);
@@ -277,6 +279,7 @@ int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGE
int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy);
+int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name);
int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest);
int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, struct iovec *ret_serialized_parent);
@@ -298,7 +301,7 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret)
int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret);
int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fingerprint, size_t *ret_fingerprint_size);
-int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, const char *pin, const TPM2B_AUTH *auth, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public);
+int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public);
int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest);
int tpm2_undefine_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle);
@@ -310,6 +313,8 @@ int tpm2_deserialize(Tpm2Context *c, const void *serialized, size_t serialized_s
int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret);
+int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret);
+
/* The tpm2-tss library has many structs that are simply a combination of an array (or object) and
* size. These macros allow easily initializing or assigning instances of such structs from an existing
* buffer/object and size, while also checking the size for safety with the struct buffer/object size. If the
diff --git a/test/units/testsuite-70.pcrlock.sh b/test/units/testsuite-70.pcrlock.sh
index ecdd910c28c..fbb93738b24 100755
--- a/test/units/testsuite-70.pcrlock.sh
+++ b/test/units/testsuite-70.pcrlock.sh
@@ -74,7 +74,7 @@ if [[ -n "$SD_STUB" ]]; then
"$SD_PCRLOCK" lock-uki <"$SD_STUB"
fi
-PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=yes
+PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=query
# Repeat immediately (this call will have to reuse the nvindex, rather than create it)
"$SD_PCRLOCK" make-policy --pcr="$PCRS"
"$SD_PCRLOCK" make-policy --pcr="$PCRS" --force
@@ -102,7 +102,7 @@ systemd-cryptsetup detach pcrlock
# work.
echo -n test70 | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/910-test70.pcrlock --pcr=16
(! "$SD_PCRLOCK" make-policy --pcr="$PCRS")
-PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=yes
+PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=query
systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless
systemd-cryptsetup detach pcrlock
@@ -110,6 +110,10 @@ systemd-cryptsetup detach pcrlock
# And now let's do it the clean way, and generate the right policy ahead of time.
echo -n test70-take-two | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/920-test70.pcrlock --pcr=16
"$SD_PCRLOCK" make-policy --pcr="$PCRS"
+# the next one should be skipped because redundant
+"$SD_PCRLOCK" make-policy --pcr="$PCRS"
+# but this one should not be skipped, even if redundant, because we force it
+"$SD_PCRLOCK" make-policy --pcr="$PCRS" --force --recovery-pin=show
"$SD_PCREXTEND" --pcr=16 test70-take-two