diff options
author | Lennart Poettering <lennart@poettering.net> | 2024-04-18 21:11:27 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-18 21:11:27 +0200 |
commit | dd37963affade1938db73df25f8c1b4892dcd2d1 (patch) | |
tree | 353193324aa1e88b6e076d17bd192929dc5085b6 | |
parent | Merge pull request #32121 from CodethinkLabs/basic-mkosi-integration-tests (diff) | |
parent | ci: update tests to showcase new option a bit (diff) | |
download | systemd-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-- | NEWS | 36 | ||||
-rw-r--r-- | man/systemd-pcrlock.xml | 17 | ||||
-rw-r--r-- | src/pcrlock/pcrlock.c | 76 | ||||
-rw-r--r-- | src/shared/tpm2-util.c | 252 | ||||
-rw-r--r-- | src/shared/tpm2-util.h | 9 | ||||
-rwxr-xr-x | test/units/testsuite-70.pcrlock.sh | 8 |
6 files changed, 328 insertions, 70 deletions
@@ -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 |