From fdf6c27cbaea5af63b474b6160c1effa5f3a3b46 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Aug 2022 22:15:12 +0200 Subject: [PATCH] tpm2-util: add common parser for the LUKS2 TPM2 JSON structure This splits out the JSON parser used by the systemd-cryptsetup code. This is preparation for later work to reuse it in the tpm2 cryptsetup token module, which currently uses a separate but very similar parser for the same data. No change in behaviour. --- src/cryptsetup/cryptsetup-tpm2.c | 183 ++++++++----------------------- src/cryptsetup/cryptsetup-tpm2.h | 8 +- src/cryptsetup/cryptsetup.c | 4 +- src/shared/cryptsetup-util.c | 46 ++++---- src/shared/cryptsetup-util.h | 3 +- src/shared/tpm2-util.c | 160 +++++++++++++++++++++++++-- src/shared/tpm2-util.h | 1 + 7 files changed, 229 insertions(+), 176 deletions(-) diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index 7469e7da1b..838c02bfc9 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -188,24 +188,22 @@ int find_tpm2_auto_data( size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, + TPM2Flags *ret_flags, int *ret_keyslot, - int *ret_token, - TPM2Flags *ret_flags) { + int *ret_token) { - _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL; - size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0; - int r, keyslot = -1, token = -1; - TPM2Flags flags = 0; - uint32_t hash_pcr_mask = 0, pubkey_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 */ + int r, token; assert(cd); for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { + _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - JsonVariant *w; - int ks; + size_t blob_size, policy_hash_size, pubkey_size; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags flags; + int keyslot; r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v); if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) @@ -213,137 +211,46 @@ int find_tpm2_auto_data( if (r < 0) return log_error_errno(r, "Failed to read JSON token data off disk: %m"); - ks = cryptsetup_get_keyslot_from_token(v); - if (ks < 0) { - /* Handle parsing errors of the keyslots field gracefully, since it's not 'owned' by - * us, but by the LUKS2 spec */ - log_warning_errno(ks, "Failed to extract keyslot index from TPM2 JSON data token %i, skipping: %m", token); + r = tpm2_parse_luks2_json( + v, + &keyslot, + &hash_pcr_mask, + &pcr_bank, + &pubkey, &pubkey_size, + &pubkey_pcr_mask, + &primary_alg, + &blob, &blob_size, + &policy_hash, &policy_hash_size, + &flags); + if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */ continue; - } - - w = json_variant_by_key(v, "tpm2-pcrs"); - if (!w) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "TPM2 token data lacks 'tpm2-pcrs' field."); - - r = tpm2_parse_pcr_json_array(w, &hash_pcr_mask); - if (r < 0) - return log_error_errno(r, "Failed to parse TPM2 PCR mask: %m"); - - if (search_pcr_mask != UINT32_MAX && - search_pcr_mask != hash_pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */ - continue; - - assert(keyslot < 0); - keyslot = ks; - - assert(pcr_bank == UINT16_MAX); - assert(primary_alg == TPM2_ALG_ECC); - - /* The bank field is optional, since it was added in systemd 250 only. Before the bank was - * hardcoded to SHA256. */ - w = json_variant_by_key(v, "tpm2-pcr-bank"); - if (w) { - /* The PCR bank field is optional */ - - if (!json_variant_is_string(w)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "TPM2 PCR bank is not a string."); - - r = tpm2_pcr_bank_from_string(json_variant_string(w)); - if (r < 0) - return log_error_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w)); - - pcr_bank = r; - } - - /* The primary key algorithm field is optional, since it was also added in systemd 250 - * only. Before the algorithm was hardcoded to ECC. */ - w = json_variant_by_key(v, "tpm2-primary-alg"); - if (w) { - /* The primary key algorithm is optional */ - - if (!json_variant_is_string(w)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "TPM2 primary key algorithm is not a string."); - - r = tpm2_primary_alg_from_string(json_variant_string(w)); - if (r < 0) - return log_error_errno(r, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); - - primary_alg = r; - } - - assert(!blob); - w = json_variant_by_key(v, "tpm2-blob"); - if (!w || !json_variant_is_string(w)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "TPM2 token data lacks 'tpm2-blob' field."); - - r = unbase64mem(json_variant_string(w), SIZE_MAX, &blob, &blob_size); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid base64 data in 'tpm2-blob' field."); - - assert(!policy_hash); - w = json_variant_by_key(v, "tpm2-policy-hash"); - if (!w || !json_variant_is_string(w)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "TPM2 token data lacks 'tpm2-policy-hash' field."); - - r = unhexmem(json_variant_string(w), SIZE_MAX, &policy_hash, &policy_hash_size); if (r < 0) - 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; + return log_error_errno(r, "Failed to parse TPM2 JSON data: %m"); + + if (search_pcr_mask == UINT32_MAX || + search_pcr_mask == hash_pcr_mask) { + + if (start_token <= 0) + log_info("Automatically discovered security TPM2 token unlocks volume."); + + *ret_hash_pcr_mask = hash_pcr_mask; + *ret_pcr_bank = pcr_bank; + *ret_pubkey = TAKE_PTR(pubkey); + *ret_pubkey_size = pubkey_size; + *ret_pubkey_pcr_mask = pubkey_pcr_mask; + *ret_primary_alg = primary_alg; + *ret_blob = TAKE_PTR(blob); + *ret_blob_size = blob_size; + *ret_policy_hash = TAKE_PTR(policy_hash); + *ret_policy_hash_size = policy_hash_size; + *ret_keyslot = keyslot; + *ret_token = token; + *ret_flags = flags; + return 0; } - w = json_variant_by_key(v, "tpm2_pubkey_pcrs"); - if (w) { - r = tpm2_parse_pcr_json_array(w, &pubkey_pcr_mask); - if (r < 0) - return r; - } - - w = json_variant_by_key(v, "tpm2_pubkey"); - if (w) { - r = json_variant_unbase64(w, &pubkey, &pubkey_size); - if (r < 0) - return log_error_errno(r, "Failed to decode PCR public key."); - } else if (pubkey_pcr_mask != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing."); - - break; + /* PCR mask doesn't match what is configured, ignore this entry, let's see next */ } - if (!blob) - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), - "No valid TPM2 token data found."); - - if (start_token <= 0) - log_info("Automatically discovered security TPM2 token unlocks volume."); - - *ret_hash_pcr_mask = hash_pcr_mask; - *ret_pcr_bank = pcr_bank; - *ret_pubkey = TAKE_PTR(pubkey); - *ret_pubkey_size = pubkey_size; - *ret_pubkey_pcr_mask = pubkey_pcr_mask; - *ret_primary_alg = primary_alg; - *ret_blob = TAKE_PTR(blob); - *ret_blob_size = blob_size; - *ret_policy_hash = TAKE_PTR(policy_hash); - *ret_policy_hash_size = policy_hash_size; - *ret_keyslot = keyslot; - *ret_token = token; - *ret_flags = flags; - - return 0; + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found."); } diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h index b1bcf6de10..a34eb8443d 100644 --- a/src/cryptsetup/cryptsetup-tpm2.h +++ b/src/cryptsetup/cryptsetup-tpm2.h @@ -49,9 +49,9 @@ int find_tpm2_auto_data( size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, + TPM2Flags *ret_flags, int *ret_keyslot, - int *ret_token, - TPM2Flags *ret_flags); + int *ret_token); #else @@ -97,9 +97,9 @@ static inline int find_tpm2_auto_data( size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, + TPM2Flags *ret_flags, int *ret_keyslot, - int *ret_token, - TPM2Flags *ret_flags) { + int *ret_token) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support not available."); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index c419354bf5..406369329c 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1522,9 +1522,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( &primary_alg, &blob, &blob_size, &policy_hash, &policy_hash_size, + &tpm2_flags, &keyslot, - &token, - &tpm2_flags); + &token); if (r == -ENXIO) /* No further TPM2 tokens found in the LUKS2 header. */ return log_full_errno(found_some ? LOG_NOTICE : LOG_DEBUG, diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index b93f702aff..da6dcb2f09 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#if HAVE_LIBCRYPTSETUP #include "alloc-util.h" #include "cryptsetup-util.h" #include "dlfcn-util.h" #include "log.h" #include "parse-util.h" +#if HAVE_LIBCRYPTSETUP static void *cryptsetup_dl = NULL; int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags); @@ -224,6 +224,28 @@ int cryptsetup_get_token_as_json( return 0; } +int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v) { + _cleanup_free_ char *text = NULL; + int r; + + r = dlopen_cryptsetup(); + if (r < 0) + return r; + + r = json_variant_format(v, 0, &text); + if (r < 0) + return log_debug_errno(r, "Failed to format token data for LUKS: %m"); + + log_debug("Adding token text <%s>", text); + + r = sym_crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text); + if (r < 0) + return log_debug_errno(r, "Failed to write token data to LUKS: %m"); + + return 0; +} +#endif + int cryptsetup_get_keyslot_from_token(JsonVariant *v) { int keyslot, r; JsonVariant *w; @@ -252,25 +274,3 @@ int cryptsetup_get_keyslot_from_token(JsonVariant *v) { return keyslot; } - -int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v) { - _cleanup_free_ char *text = NULL; - int r; - - r = dlopen_cryptsetup(); - if (r < 0) - return r; - - r = json_variant_format(v, 0, &text); - if (r < 0) - return log_debug_errno(r, "Failed to format token data for LUKS: %m"); - - log_debug("Adding token text <%s>", text); - - r = sym_crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text); - if (r < 0) - return log_debug_errno(r, "Failed to write token data to LUKS: %m"); - - return 0; -} -#endif diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 2a17820d63..fc80edb411 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -74,7 +74,6 @@ void cryptsetup_enable_logging(struct crypt_device *cd); int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd); int cryptsetup_get_token_as_json(struct crypt_device *cd, int idx, const char *verify_type, JsonVariant **ret); -int cryptsetup_get_keyslot_from_token(JsonVariant *v); int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v); #else @@ -87,6 +86,8 @@ static inline void sym_crypt_freep(struct crypt_device** cd) {} #endif +int cryptsetup_get_keyslot_from_token(JsonVariant *v); + static inline const char *mangle_none(const char *s) { /* A helper that turns cryptsetup/integritysetup/veritysetup "options" strings into NULL if they are effectively empty */ return isempty(s) || STR_IN_SET(s, "-", "none") ? NULL : s; diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 254e63334a..f5b97fae73 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -1,17 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "efi-api.h" -#include "extract-word.h" -#include "parse-util.h" -#include "stat-util.h" -#include "tpm2-util.h" -#include "virt.h" - -#if HAVE_TPM2 #include "alloc-util.h" +#include "cryptsetup-util.h" #include "def.h" #include "dirent-util.h" #include "dlfcn-util.h" +#include "efi-api.h" +#include "extract-word.h" #include "fd-util.h" #include "fileio.h" #include "format-table.h" @@ -19,10 +14,15 @@ #include "hexdecoct.h" #include "memory-util.h" #include "openssl-util.h" +#include "parse-util.h" #include "random-util.h" #include "sha256.h" +#include "stat-util.h" #include "time-util.h" +#include "tpm2-util.h" +#include "virt.h" +#if HAVE_TPM2 static void *libtss2_esys_dl = NULL; static void *libtss2_rc_dl = NULL; static void *libtss2_mu_dl = NULL; @@ -1881,6 +1881,150 @@ int tpm2_make_luks2_json( return keyslot; } +int tpm2_parse_luks2_json( + JsonVariant *v, + int *ret_keyslot, + uint32_t *ret_hash_pcr_mask, + uint16_t *ret_pcr_bank, + void **ret_pubkey, + size_t *ret_pubkey_size, + uint32_t *ret_pubkey_pcr_mask, + uint16_t *ret_primary_alg, + void **ret_blob, + size_t *ret_blob_size, + void **ret_policy_hash, + size_t *ret_policy_hash_size, + TPM2Flags *ret_flags) { + + _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL; + size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0; + uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0; + uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */ + uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */ + int r, keyslot = -1; + TPM2Flags flags = 0; + JsonVariant *w; + + assert(v); + + if (ret_keyslot) { + keyslot = cryptsetup_get_keyslot_from_token(v); + if (keyslot < 0) { + /* Return a recognizable error when parsing this field, so that callers can handle parsing + * errors of the keyslots field gracefully, since it's not 'owned' by us, but by the LUKS2 + * spec */ + log_debug_errno(keyslot, "Failed to extract keyslot index from TPM2 JSON data token, skipping: %m"); + return -EUCLEAN; + } + } + + w = json_variant_by_key(v, "tpm2-pcrs"); + if (!w) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-pcrs' field."); + + r = tpm2_parse_pcr_json_array(w, &hash_pcr_mask); + if (r < 0) + return log_debug_errno(r, "Failed to parse TPM2 PCR mask: %m"); + + /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded + * to SHA256. */ + w = json_variant_by_key(v, "tpm2-pcr-bank"); + if (w) { + /* The PCR bank field is optional */ + + if (!json_variant_is_string(w)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 PCR bank is not a string."); + + r = tpm2_pcr_bank_from_string(json_variant_string(w)); + if (r < 0) + return log_debug_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w)); + + pcr_bank = r; + } + + /* The primary key algorithm field is optional, since it was also added in systemd 250 only. Before + * the algorithm was hardcoded to ECC. */ + w = json_variant_by_key(v, "tpm2-primary-alg"); + if (w) { + /* The primary key algorithm is optional */ + + if (!json_variant_is_string(w)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 primary key algorithm is not a string."); + + r = tpm2_primary_alg_from_string(json_variant_string(w)); + if (r < 0) + return log_debug_errno(r, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); + + primary_alg = r; + } + + w = json_variant_by_key(v, "tpm2-blob"); + if (!w) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field."); + + r = json_variant_unbase64(w, &blob, &blob_size); + if (r < 0) + return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field."); + + w = json_variant_by_key(v, "tpm2-policy-hash"); + if (!w) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field."); + + r = json_variant_unhex(w, &policy_hash, &policy_hash_size); + if (r < 0) + return log_debug_errno(r, "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_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 PIN policy is not a boolean."); + + SET_FLAG(flags, TPM2_FLAGS_USE_PIN, json_variant_boolean(w)); + } + + w = json_variant_by_key(v, "tpm2_pubkey_pcrs"); + if (w) { + r = tpm2_parse_pcr_json_array(w, &pubkey_pcr_mask); + if (r < 0) + return r; + } + + w = json_variant_by_key(v, "tpm2_pubkey"); + if (w) { + r = json_variant_unbase64(w, &pubkey, &pubkey_size); + if (r < 0) + return log_debug_errno(r, "Failed to decode PCR public key."); + } else if (pubkey_pcr_mask != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing."); + + if (ret_keyslot) + *ret_keyslot = keyslot; + if (ret_hash_pcr_mask) + *ret_hash_pcr_mask = hash_pcr_mask; + if (ret_pcr_bank) + *ret_pcr_bank = pcr_bank; + if (ret_pubkey) + *ret_pubkey = TAKE_PTR(pubkey); + if (ret_pubkey_size) + *ret_pubkey_size = pubkey_size; + if (ret_pubkey_pcr_mask) + *ret_pubkey_pcr_mask = pubkey_pcr_mask; + if (ret_primary_alg) + *ret_primary_alg = primary_alg; + if (ret_blob) + *ret_blob = TAKE_PTR(blob); + if (ret_blob_size) + *ret_blob_size = blob_size; + if (ret_policy_hash) + *ret_policy_hash = TAKE_PTR(policy_hash); + if (ret_policy_hash_size) + *ret_policy_hash_size = policy_hash_size; + if (ret_flags) + *ret_flags = flags; + + return 0; +} + const char *tpm2_pcr_bank_to_string(uint16_t bank) { if (bank == TPM2_ALG_SHA1) return "sha1"; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 3bdeba8f11..2ff7c0eb63 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -82,6 +82,7 @@ int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret); int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret); int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, JsonVariant **ret); +int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, TPM2Flags *ret_flags); #define TPM2_PCRS_MAX 24U -- 2.25.1