diff --git a/meson.build b/meson.build index 24efc9f7c3..6c4b67d28d 100644 --- a/meson.build +++ b/meson.build @@ -1343,6 +1343,7 @@ tpm2 = dependency('tss2-esys tss2-rc tss2-mu tss2-tcti-device', tpm2_cflags = tpm2.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_TPM2', tpm2.found()) conf.set10('HAVE_TSS2_ESYS3', tpm2.found() and tpm2.version().version_compare('>= 3.0.0')) +conf.set('TPM2_NVPCR_BASE', get_option('tpm2-nvpcr-base')) libdw = dependency('libdw', required : get_option('elfutils')) @@ -3028,6 +3029,7 @@ summary({ 'default user $PATH' : default_user_path != '' ? default_user_path : '(same as system services)', 'systemd service watchdog' : service_watchdog == '' ? 'disabled' : service_watchdog, 'time epoch' : f'@time_epoch@ (@alt_time_epoch@)', + 'TPM2 nvpcr base' : run_command(sh, '-c', 'printf 0x%x @0@'.format(get_option('tpm2-nvpcr-base')), check : true).stdout() }) # TODO: diff --git a/meson_options.txt b/meson_options.txt index f7ec39322c..d44030ef8b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -449,6 +449,8 @@ option('libfido2', type : 'feature', deprecated : { 'true' : 'enabled', 'false' description : 'FIDO2 support') option('tpm2', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, description : 'TPM2 support') +option('tpm2-nvpcr-base', type : 'integer', value: 0x01d10200, + description : 'Base for TPM2 nvindex based PCRs') option('elfutils', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, description : 'elfutils support') option('zlib', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 86fe383956..49da185271 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -6,12 +6,15 @@ #include "alloc-util.h" #include "ansi-color.h" #include "bitfield.h" +#include "bootspec.h" +#include "boot-entry.h" #include "constants.h" #include "creds-util.h" #include "cryptsetup-util.h" #include "dirent-util.h" #include "dlfcn-util.h" #include "efi-api.h" +#include "errno-util.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" @@ -39,6 +42,7 @@ #include "sync-util.h" #include "time-util.h" #include "tpm2-pcr.h" +#include "tmpfile-util.h" #include "tpm2-util.h" #include "virt.h" @@ -65,6 +69,9 @@ static DLSYM_PROTOTYPE(Esys_Initialize) = NULL; static DLSYM_PROTOTYPE(Esys_Load) = NULL; static DLSYM_PROTOTYPE(Esys_LoadExternal) = NULL; static DLSYM_PROTOTYPE(Esys_NV_DefineSpace) = NULL; +static DLSYM_PROTOTYPE(Esys_NV_Extend) = NULL; +static DLSYM_PROTOTYPE(Esys_NV_Read) = NULL; +static DLSYM_PROTOTYPE(Esys_NV_ReadPublic) = NULL; static DLSYM_PROTOTYPE(Esys_NV_UndefineSpace) = NULL; static DLSYM_PROTOTYPE(Esys_NV_Write) = NULL; static DLSYM_PROTOTYPE(Esys_PCR_Extend) = NULL; @@ -139,6 +146,9 @@ static int dlopen_tpm2_esys(void) { DLSYM_ARG(Esys_Load), DLSYM_ARG(Esys_LoadExternal), DLSYM_ARG(Esys_NV_DefineSpace), + DLSYM_ARG(Esys_NV_Extend), + DLSYM_ARG(Esys_NV_Read), + DLSYM_ARG(Esys_NV_ReadPublic), DLSYM_ARG(Esys_NV_UndefineSpace), DLSYM_ARG(Esys_NV_Write), DLSYM_ARG(Esys_PCR_Extend), @@ -5915,6 +5925,224 @@ int tpm2_undefine_nv_index( return 0; } +int tpm2_define_nvpcr_nv_index( + Tpm2Context *c, + const Tpm2Handle *session, + TPM2_HANDLE nv_index, + TPMI_ALG_HASH algorithm, + Tpm2Handle **ret_nv_handle) { + + _cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL; + TSS2_RC rc; + int r; + + assert(c); + + /* Allocates an nvindex to use as a "fake" PCR. We call these "NvPCR" in our codebase */ + + if (algorithm == 0) + algorithm = TPM2_ALG_SHA256; + + int digest_size = tpm2_hash_alg_to_size(algorithm); + if (digest_size < 0) + return digest_size; + + if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer)) + return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Digest too large for extension."); + + r = tpm2_handle_new(c, &new_handle); + if (r < 0) + return r; + + new_handle->flush = false; /* This is a persistent NV index, don't flush hence */ + + TPM2B_NV_PUBLIC public_info = { + .size = sizeof_field(TPM2B_NV_PUBLIC, nvPublic), + .nvPublic = { + .nvIndex = nv_index, + .nameAlg = algorithm, + .attributes = TPMA_NV_CLEAR_STCLEAR | + TPMA_NV_ORDERLY | + TPMA_NV_OWNERWRITE | + TPMA_NV_AUTHWRITE | + TPMA_NV_OWNERREAD | + TPMA_NV_AUTHREAD | + (TPM2_NT_EXTEND << TPMA_NV_TPM2_NT_SHIFT), + .dataSize = digest_size, + }, + }; + + rc = sym_Esys_NV_DefineSpace( + c->esys_context, + /* authHandle= */ ESYS_TR_RH_OWNER, + /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + /* auth= */ NULL, + &public_info, + &new_handle->esys_handle); + if (rc == TPM2_RC_NV_DEFINED) { + log_debug("NV index 0x%" PRIu32 " already registered.", nv_index); + + new_handle = tpm2_handle_free(new_handle); + + r = tpm2_index_to_handle( + c, + nv_index, + session, + /* ret_public= */ NULL, + /* ret_name= */ NULL, + /* ret_qname= */ NULL, + &new_handle); + if (r < 0) + return log_debug_errno(r, "Failed to acquire handle to existing NV index 0x%" PRIu32 ".", nv_index); + + log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", nv_index); + + _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public_real = NULL; + rc = sym_Esys_NV_ReadPublic( + c->esys_context, + /* nvIndex= */ new_handle->esys_handle, + /* shandle1= */ ESYS_TR_NONE, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + &nv_public_real, + /* ret_nv_name= */ NULL); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed read public data of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc)); + + log_debug("Read public info for nvindex 0x%x.", nv_index); + + if (nv_public_real->size < endoffsetof_field(TPMS_NV_PUBLIC, attributes) + sizeof_field(TPMS_NV_PUBLIC, dataSize) || + nv_public_real->nvPublic.nvIndex != public_info.nvPublic.nvIndex || + nv_public_real->nvPublic.nameAlg != public_info.nvPublic.nameAlg || + ((nv_public_real->nvPublic.attributes ^ public_info.nvPublic.attributes) & ~TPMA_NV_WRITTEN) != 0 || + nv_public_real->nvPublic.dataSize != public_info.nvPublic.dataSize) + return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), + "Public data of nvindex 0x%x does not match our expectations.", nv_index); + + log_debug("Public info for nvindex 0x%x checks out, using.", nv_index); + + if (ret_nv_handle) + *ret_nv_handle = TAKE_PTR(new_handle); + + return 0; + } + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to allocate NV index: %s", sym_Tss2_RC_Decode(rc)); + + log_debug("NV Index 0x%" PRIx32 " successfully allocated.", nv_index); + + if (ret_nv_handle) + *ret_nv_handle = TAKE_PTR(new_handle); + + return 1; +} + +int tpm2_extend_nvpcr_nv_index( + Tpm2Context *c, + TPM2_HANDLE nv_index, + const Tpm2Handle *nv_handle, + const struct iovec *digest) { + + TPM2_RC rc; + + assert(c); + assert(nv_index); + assert(nv_handle); + assert(iovec_is_set(digest)); + + if (digest->iov_len > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer)) + return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash value to extend too long."); + + TPM2B_MAX_NV_BUFFER buf = { + .size = digest->iov_len, + }; + memcpy(buf.buffer, digest->iov_base, digest->iov_len); + + rc = sym_Esys_NV_Extend( + c->esys_context, + /* authHandle= */ nv_handle->esys_handle, + /* nvIndex= */ nv_handle->esys_handle, + /* shandle1= */ ESYS_TR_PASSWORD, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + &buf); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to extend NV index: %s", sym_Tss2_RC_Decode(rc)); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *h = NULL; + h = hexmem(digest->iov_base, digest->iov_len); + log_debug("Written hash %s to NV index 0x%x", strnull(h), nv_index); + } + + return 0; +} + +int tpm2_read_nv_index( + Tpm2Context *c, + const Tpm2Handle *session, + TPM2_HANDLE nv_index, + const Tpm2Handle *nv_handle, + struct iovec *ret_value) { + + TPM2_RC rc; + + assert(c); + assert(nv_index); + assert(nv_handle); + + _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public = NULL; + rc = sym_Esys_NV_ReadPublic( + c->esys_context, + /* nvIndex= */ nv_handle->esys_handle, + /* shandle1= */ ESYS_TR_NONE, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + &nv_public, + /* ret_nv_name= */ NULL); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed read public data of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc)); + + log_debug("Read public info for nvindex 0x%x, value size is %zu", nv_index, (size_t) nv_public->nvPublic.dataSize); + + _cleanup_(Esys_Freep) TPM2B_MAX_NV_BUFFER *value = NULL; + rc = sym_Esys_NV_Read( + c->esys_context, + /* authHandle= */ nv_handle->esys_handle, + /* nvIndex= */ nv_handle->esys_handle, + /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + nv_public->nvPublic.dataSize, + /* offset= */ 0, + &value); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed read contents of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc)); + + if (ret_value) { + assert(value); + + struct iovec result = { + .iov_base = memdup(value->buffer, value->size), + .iov_len = value->size, + }; + + if (!result.iov_base) + return log_oom_debug(); + + *ret_value = TAKE_STRUCT(result); + } + + return 0; +} + int tpm2_seal_data( Tpm2Context *c, const struct iovec *data, @@ -6160,6 +6388,7 @@ int tpm2_find_device_auto(char **ret) { #endif } +#if HAVE_TPM2 const uint16_t tpm2_hash_algorithms[] = { TPM2_ALG_SHA1, TPM2_ALG_SHA256, @@ -6190,7 +6419,6 @@ static int json_dispatch_tpm2_algorithm(const char *name, sd_json_variant *varia return 0; } -#if HAVE_TPM2 static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MAX] = { [TPM2_EVENT_PHASE] = "phase", [TPM2_EVENT_FILESYSTEM] = "filesystem", @@ -6211,7 +6439,6 @@ const char* tpm2_firmware_log_path(void) { #if HAVE_OPENSSL static int tpm2_userspace_log_open(void) { _cleanup_close_ int fd = -EBADF; - struct stat st; const char *e; int r; @@ -6228,29 +6455,61 @@ static int tpm2_userspace_log_open(void) { if (flock(fd, LOCK_EX) < 0) return log_debug_errno(errno, "Failed to lock TPM log file '%s', ignoring: %m", e); - if (fstat(fd, &st) < 0) - return log_debug_errno(errno, "Failed to fstat TPM log file '%s', ignoring: %m", e); - - r = stat_verify_regular(&st); + r = fd_verify_regular(fd); if (r < 0) return log_debug_errno(r, "TPM log file '%s' is not regular, ignoring: %m", e); + return TAKE_FD(fd); +} + +static int tpm2_userspace_log_dirty(int fd) { + struct stat st; + + if (fd < 0) /* Apparently tpm2_local_log_open() failed earlier, let's not complain again */ + return 0; + /* We set the sticky bit when we are about to append to the log file. We'll unset it afterwards * again. If we manage to take a lock on a file that has it set we know we didn't write it fully and * it is corrupted. Ideally we'd like to use user xattrs for this, but unfortunately tmpfs (which is * our assumed backend fs) doesn't know user xattrs. */ + + if (fstat(fd, &st) < 0) + return log_debug_errno(errno, "Failed to fstat TPM log file, ignoring: %m"); + if (st.st_mode & S_ISVTX) - return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "TPM log file '%s' aborted, ignoring.", e); + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "TPM log file aborted, ignoring."); if (fchmod(fd, 0600 | S_ISVTX) < 0) - return log_debug_errno(errno, "Failed to chmod() TPM log file '%s', ignoring: %m", e); + return log_debug_errno(errno, "Failed to chmod() TPM log file, ignoring: %m"); - return TAKE_FD(fd); + return 0; +} + +static int tpm2_userspace_log_clean(int fd) { + int r; + + if (fd < 0) /* Apparently tpm2_local_log_open() failed earlier, let's not complain again */ + return 0; + + if (fsync(fd) < 0) + return log_debug_errno(errno, "Failed to sync JSON data: %m"); + + /* Unset S_ISVTX again */ + if (fchmod(fd, 0600) < 0) + return log_debug_errno(errno, "Failed to chmod() TPM log file, ignoring: %m"); + + r = fsync_full(fd); + if (r < 0) + return log_debug_errno(r, "Failed to sync JSON log: %m"); + + return 0; } static int tpm2_userspace_log( int fd, unsigned pcr_index, + uint32_t nv_index, + const char *nv_index_name, const TPML_DIGEST_VALUES *values, Tpm2UserspaceEventType event_type, const char *description) { @@ -6314,10 +6573,12 @@ static int tpm2_userspace_log( r = sd_json_buildo( &v, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(pcr_index)), + SD_JSON_BUILD_PAIR_CONDITION(pcr_index != UINT_MAX, "pcr", SD_JSON_BUILD_UNSIGNED(pcr_index)), + SD_JSON_BUILD_PAIR_CONDITION(nv_index != UINT32_MAX, "nv_index", SD_JSON_BUILD_UNSIGNED(nv_index)), SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(array)), SD_JSON_BUILD_PAIR("content_type", SD_JSON_BUILD_STRING("systemd")), SD_JSON_BUILD_PAIR("content", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_CONDITION(!!nv_index_name, "nvIndexName", SD_JSON_BUILD_STRING(nv_index_name)), SD_JSON_BUILD_PAIR_CONDITION(!!description, "string", SD_JSON_BUILD_STRING(description)), SD_JSON_BUILD_PAIR("bootId", SD_JSON_BUILD_ID128(boot_id)), SD_JSON_BUILD_PAIR("timestamp", SD_JSON_BUILD_UNSIGNED(now(CLOCK_BOOTTIME))), @@ -6336,16 +6597,9 @@ static int tpm2_userspace_log( if (r < 0) return log_debug_errno(r, "Failed to write JSON data to log: %m"); - if (fsync(fd) < 0) - return log_debug_errno(errno, "Failed to sync JSON data: %m"); - - /* Unset S_ISVTX again */ - if (fchmod(fd, 0600) < 0) - return log_debug_errno(errno, "Failed to chmod() TPM log file, ignoring: %m"); - - r = fsync_full(fd); + r = tpm2_userspace_log_clean(fd); if (r < 0) - return log_debug_errno(r, "Failed to sync JSON log: %m"); + return r; return 1; } @@ -6415,6 +6669,7 @@ int tpm2_pcr_extend_bytes( * and our measurement and change either */ log_fd = tpm2_userspace_log_open(); + (void) tpm2_userspace_log_dirty(log_fd); rc = sym_Esys_PCR_Extend( c->esys_context, ESYS_TR_PCR0 + pcr_index, @@ -6430,7 +6685,739 @@ int tpm2_pcr_extend_bytes( sym_Tss2_RC_Decode(rc)); /* Now, write what we just extended to the log, too. */ - (void) tpm2_userspace_log(log_fd, pcr_index, &values, event_type, description); + (void) tpm2_userspace_log( + log_fd, + pcr_index, + /* nv_index= */ UINT32_MAX, + /* nv_index_name= */ NULL, + &values, + event_type, + description); + + return 0; +#else /* HAVE_OPENSSL */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + +typedef struct NvPCRData { + char *name; + uint16_t algorithm; + uint32_t nv_index; +} NvPCRData; + +static void nvpcr_data_done(NvPCRData *d) { + assert(d); + + free(d->name); +} + +static int nvpcr_data_load(const char *name, NvPCRData *ret) { + int r; + + assert(ret); + + if (!tpm2_nvpcr_name_is_valid(name)) + return -EINVAL; + + const char *fname = strjoina(name, ".nvpcr"); + + _cleanup_free_ char *path = NULL; + _cleanup_fclose_ FILE *f = NULL; + r = search_and_fopen_nulstr(fname, "re", /* root= */ NULL, CONF_PATHS_NULSTR("nvpcr"), &f, &path); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = sd_json_parse_file( + f, + path, + /* flags= */ 0, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); + if (r < 0) + return log_debug_errno(r, "Failed to load '%s': %m", path); + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(NvPCRData, name), SD_JSON_MANDATORY }, + { "algorithm", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_tpm2_algorithm, offsetof(NvPCRData, algorithm), 0 }, + { "nvIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(NvPCRData, nv_index), SD_JSON_MANDATORY }, + {}, + }; + + _cleanup_(nvpcr_data_done) NvPCRData p = { + .algorithm = TPM2_ALG_SHA256, + }; + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return r; + + if (!streq_ptr(p.name, name)) + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "NvPCR name doesn't match filename, refusing."); + + *ret = TAKE_STRUCT(p); + return 0; +} + +int tpm2_nvpcr_get_index(const char *name, uint32_t *ret) { + int r; + + _cleanup_(nvpcr_data_done) NvPCRData p = {}; + r = nvpcr_data_load(name, &p); + if (r < 0) + return r; + + if (ret) + *ret = p.nv_index; + + return 0; +} + +int tpm2_nvpcr_extend_bytes( + Tpm2Context *c, + const Tpm2Handle *session, + const char *name, + const struct iovec *data, + const struct iovec *secret, + Tpm2UserspaceEventType event_type, + const char *description) { + +#if HAVE_OPENSSL + _cleanup_close_ int log_fd = -EBADF; + int r; + + assert(c); + assert(name); + assert(iovec_is_valid(data)); + assert(iovec_is_valid(secret)); + + _cleanup_(nvpcr_data_done) NvPCRData p = {}; + r = nvpcr_data_load(name, &p); + if (r < 0) + return r; + + /* Open + lock the log file *before* we start measuring, so that no one else can come between our log + * and our measurement and change either */ + log_fd = tpm2_userspace_log_open(); + + /* Check if this NvPCR is already anchored */ + const char *anchor_fname = strjoina("/run/systemd/nvpcr/", name, ".anchor"); + if (faccessat(AT_FDCWD, anchor_fname, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) + return log_debug_errno(errno, "Failed to check if '%s' exists: %m", anchor_fname); + + return log_debug_errno(SYNTHETIC_ERRNO(ENETDOWN), "NvPCR '%s' not anchored yet, refusing.", name); + } + + const char *an = tpm2_hash_alg_to_string(p.algorithm); + if (!an) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported algorithm for NvPCR, refusing."); + + const EVP_MD *implementation; + assert_se(implementation = EVP_get_digestbyname(an)); + + _cleanup_(iovec_done) struct iovec digest = { + .iov_len = EVP_MD_size(implementation), + }; + + digest.iov_base = malloc(digest.iov_len); + if (!digest.iov_base) + return log_oom_debug(); + + if (!iovec_is_set(data)) + data = &iovec_empty; + + if (iovec_is_set(secret)) { + if (!HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, digest.iov_base, NULL)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); + } else if (EVP_Digest(data->iov_base, data->iov_len, digest.iov_base, NULL, implementation, NULL) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure."); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + r = tpm2_index_to_handle( + c, + p.nv_index, + session, + /* ret_public= */ NULL, + /* ret_name= */ NULL, + /* ret_qname= */ NULL, + &nv_handle); + if (r < 0) + return log_debug_errno(r, "Failed to acquire handle to NV index 0x%" PRIu32 ".", p.nv_index); + + log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", p.nv_index); + + (void) tpm2_userspace_log_dirty(log_fd); + + r = tpm2_extend_nvpcr_nv_index( + c, + p.nv_index, + nv_handle, + &digest); + if (r < 0) + return r; + + TPML_DIGEST_VALUES digest_values = { + .count = 1, + .digests[0].hashAlg = p.algorithm, + }; + memcpy(&digest_values.digests[0].digest, digest.iov_base, digest.iov_len); + + /* Now, write what we just extended to the log, too. */ + (void) tpm2_userspace_log( + log_fd, + /* pcr_index= */ UINT_MAX, + p.nv_index, + name, + &digest_values, + event_type, + description); + + return 0; +#else /* HAVE_OPENSSL */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + +#if HAVE_OPENSSL +static int tpm2_nvpcr_write_anchor_secret( + const char *dir, + const char *fname, + const struct iovec *credential) { + + int r; + + assert(dir); + assert(fname); + assert(iovec_is_set(credential)); + + /* Writes the encrypted credential of the anchor secret to directory 'dir' and file 'fname' */ + + _cleanup_close_ int dfd = open_mkdir(dir, O_CLOEXEC, 0755); + if (dfd < 0) + return log_error_errno(dfd, "Failed to create '%s' directory: %m", dir); + + _cleanup_free_ char *joined = path_join(dir, fname); + if (!joined) + return log_oom(); + + _cleanup_(iovec_done) struct iovec existing = {}; + r = read_full_file_full( + dfd, + fname, + /* offset= */ UINT64_MAX, + CREDENTIAL_ENCRYPTED_SIZE_MAX, + READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, + /* bind_name= */ NULL, + (char**) &existing.iov_base, + &existing.iov_len); + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Failed to read '%s' file: %m", joined); + } else if (iovec_memcmp(&existing, credential) == 0) { + log_debug("Anchor secret file '%s' already matches expectations, not updating.", joined); + return 0; + } else + log_notice("Anchor secret file '%s' different from current anchor secret, updating.", joined); + + r = write_base64_file_at( + dfd, + fname, + credential, + WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC); + if (r < 0) + return log_error_errno(r, "Failed to write anchor secret file to '%s': %m", joined); + + log_info("Successfully written anchor secret to '%s'.", joined); + return 1; +} + +static int tpm2_nvpcr_write_anchor_secret_to_var(const struct iovec *credential) { + return tpm2_nvpcr_write_anchor_secret("/var/lib/systemd/nvpcr", "nvpcr-anchor.cred", credential); +} + +static int tpm2_nvpcr_write_anchor_secret_to_boot(const struct iovec *credential) { + int r; + + assert(iovec_is_set(credential)); + + _cleanup_free_ char *dir = NULL; + r = get_global_boot_credentials_path(&dir); + if (r < 0) + return r; + if (r == 0) { + log_debug("No XBOOTLDR/ESP partition found, not writing boot anchor secret file."); + return 0; + } + + sd_id128_t machine_id; + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return log_error_errno(r, "Failed to read machine ID: %m"); + + BootEntryTokenType entry_token_type = BOOT_ENTRY_TOKEN_AUTO; + _cleanup_free_ char *entry_token = NULL; + r = boot_entry_token_ensure( + /* root= */ NULL, + /* conf_root= */ NULL, + machine_id, + /* machine_id_is_random = */ false, + &entry_token_type, + &entry_token); + if (r < 0) + return r; + + _cleanup_free_ char *fname = strjoin("nvpcr-anchor.", entry_token, ".cred"); + if (!fname) + return log_oom(); + + if (!filename_is_valid(fname)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fname); + + return tpm2_nvpcr_write_anchor_secret(dir, fname, credential); +} + +static int tpm2_nvpcr_acquire_anchor_secret_from_var(struct iovec *ret_credential) { + int r; + + assert(ret_credential); + + r = read_full_file_full( + AT_FDCWD, + "/var/lib/systemd/nvpcr/nvpcr-anchor.cred", + /* offset= */ UINT64_MAX, + CREDENTIAL_ENCRYPTED_SIZE_MAX, + READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_VERIFY_REGULAR, + /* bind_name= */ NULL, + (char**) &ret_credential->iov_base, + &ret_credential->iov_len); + if (r == -ENOENT) { + log_debug_errno(r, "No '/var/lib/systemd/nvpcr/nvpcr-anchor.cred' file."); + *ret_credential = (struct iovec) {}; + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read '/var/lib/systemd/nvpcr/nvpcr-anchor.cred': %m"); + + return 1; +} + +static int tpm2_nvpcr_acquire_anchor_secret_from_credential(struct iovec *ret_credential, struct iovec *ret_secret) { + int r; + + assert(ret_credential); + assert(ret_secret); + + /* We need the anchor secret before the first measurement into an NvPCR. That means very early. Hence + * we'll try to pass it into the system via the system credentials logic. Because we must expect a + * multi-boot scenario it's hard to know which secret to use for which system. Hence we'll just try + * to unlock all of the available ones, until we can decrypt one of them, and then we'll use that. */ + + const char *dp; + r = get_encrypted_system_credentials_dir(&dp); + if (r < 0) + return log_error_errno(r, "Failed to get encrypted system credentials directory: %m"); + + /* Define early, so that it is definitely initialized, even if we take "goto not_found" branch below. */ + _cleanup_free_ DirectoryEntries *de = NULL; + + _cleanup_close_ int dfd = open(dp, O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) { + if (errno == ENOENT) { + log_debug("No encrypted system credentials passed."); + goto not_found; + } + + return log_error_errno(errno, "Failed to open system credentials directory."); + } + + r = readdir_all(dfd, RECURSE_DIR_IGNORE_DOT, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate system credentials: %m"); + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + _cleanup_(iovec_done) struct iovec credential = {}; + struct dirent *d = *i; + + if (!startswith_no_case(d->d_name, "nvpcr-anchor.")) /* VFAT is case-insensitive, hence don't be too strict here */ + continue; + + r = read_full_file_full( + dfd, + d->d_name, + /* offset= */ UINT64_MAX, + CREDENTIAL_ENCRYPTED_SIZE_MAX, + READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, + /* bind_name= */ NULL, + (char**) &credential.iov_base, + &credential.iov_len); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read anchor secret file '%s/%s', skipping: %m", dp, d->d_name); + continue; + } + + r = decrypt_credential_and_warn( + "nvpcr-anchor.cred", + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + /* uid= */ UID_INVALID, + &credential, + /* flags= */ 0, + ret_secret); + if (r < 0) + log_debug_errno(r, "Failed to decrypt anchor secret file '%s' passed in as system credential, skipping: %m", d->d_name); + else { + *ret_credential = TAKE_STRUCT(credential); + return 1; + } + } + + log_debug("No suitable anchor secret passed as system credential."); + +not_found: + *ret_credential = (struct iovec) {}; + *ret_secret = (struct iovec) {}; + return 0; +} +#endif + +#define ANCHOR_SECRET_SIZE 4096U + +int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary) { +#if HAVE_OPENSSL + _cleanup_close_ int fd = -EBADF; + int r; + + /* Acquires the anchor secret. We store it in a credential. The primary location (and primary truth) + * for it is /run/systemd/nvpcr/ (i.e. volatile) [this file also doubles as lock file for the whole + * logic]. But something has to place it there once. We do keep two copies of it: one in + * /var/lib/systemd/nvpcr/, which is the persistent place for it, but which is only available at late + * boot, potentially. And one in the ESP/XBOOTLDR which will make it available in the initrd + * already via system credentials. */ + + _cleanup_close_ int dfd = open_mkdir("/run/systemd/nvpcr", O_CLOEXEC, 0755); + if (dfd < 0) + return log_error_errno(dfd, "Failed to open directory '/run/systemd/nvpcr': %m"); + + /* Use restrictive access mode of 0600. Not because the data inside needs to be kept inaccessible + * (it's encrypted, hence that'd be fine), but because we need to lock it, and unprivileged clients + * shouldn't be permitted to lock it. */ + fd = openat(dfd, "nvpcr-anchor.cred", O_RDWR|O_CLOEXEC|O_CREAT|O_NOCTTY, 0644); + if (fd < 0) + return log_error_errno(errno, "Failed to open anchor secret: %m"); + + r = lock_generic(fd, LOCK_BSD, LOCK_SH); + if (r < 0) + return log_error_errno(r, "Failed to lock anchor secret file: %m"); + + struct stat st; + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat() anchor secret: %m"); + + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(r, "Anchor secret file is not a regular file: %m"); + + if (st.st_size == 0) { + /* If this is not initialized yet, then let's update the lock to an exclusive lock */ + r = lock_generic(fd, LOCK_BSD, LOCK_EX); + if (r < 0) + return log_error_errno(r, "Failed to upgrade lock on anchor secret file: %m"); + + /* Refresh size info, in case someone else has initialized it by now */ + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat() anchor secret: %m"); + } + + bool copy_to_var = true, copy_to_boot = true; + + _cleanup_(iovec_done) struct iovec credential = {}; + _cleanup_(iovec_done_erase) struct iovec secret = {}; + if (st.st_size == 0) { /* No initialized yet? */ + + /* Check if we have a secret in /var/lib/systemd/nvpcr/. If so, import the secret from there */ + if (!sync_secondary) { + r = tpm2_nvpcr_acquire_anchor_secret_from_var(&credential); + if (r < 0) + return r; + if (r > 0) + copy_to_var = false; /* We read the secret from /var/, hence we don't have to copy it there. */ + } + + /* Did the copy_source logic work? If not, let's search for the secret among passed system credentials. */ + if (!iovec_is_set(&credential)) { + r = tpm2_nvpcr_acquire_anchor_secret_from_credential(&credential, &secret); + if (r < 0) + return r; + if (r > 0) + copy_to_boot = false; /* We read the secret from the boot partition, hence we don't have to copy it there. */ + } + + /* Did the copy_source or system credential logic work? If not, let's generate a new random one */ + if (!iovec_is_set(&credential)) { + r = crypto_random_bytes_allocate_iovec(ANCHOR_SECRET_SIZE, &secret); + if (r < 0) + return log_error_errno(r, "Failed to acquire entropy for anchor secret: %m"); + + r = encrypt_credential_and_warn( + _CRED_AUTO_TPM2, + "nvpcr-anchor.cred", + now(CLOCK_REALTIME), + /* not_after= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_hash_pcr_mask= */ 0, + /* tpm2_pubkey_path= */ NULL, + /* tpm2_pubkey_pcrs= */ UINT32_MAX, + /* uid= */ UID_INVALID, + &secret, + /* flags= */ 0, + &credential); + if (r < 0) + return r; + } + + _cleanup_free_ char *encoded = NULL; + ssize_t n = base64mem_full(credential.iov_base, credential.iov_len, 79, &encoded); + if (n < 0) + return log_error_errno(n, "Failed to base64 encode credential: %m"); + + if (!strextend(&encoded, "\n")) + return log_oom(); + + n++; + + r = loop_write(fd, encoded, n); + if (r < 0) + return log_error_errno(r, "Failed to write anchor secret to disk: %m"); + } else { + /* The file was already initialized? Then just read it. */ + r = read_full_file_full( + fd, + /* filename= */ NULL, + /* offset= */ UINT64_MAX, + CREDENTIAL_ENCRYPTED_SIZE_MAX, + READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, + /* bind_name= */ NULL, + (char**) &credential.iov_base, + &credential.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to read anchor secret file: %m"); + } + + /* if we don't have the plaintext secret yet, then decrypt it now. */ + if (!iovec_is_set(&secret)) { + assert(iovec_is_set(&credential)); + + r = decrypt_credential_and_warn( + "nvpcr-anchor.cred", + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + /* uid= */ UID_INVALID, + &credential, + /* flags= */ 0, + &secret); + if (r < 0) + return r; + } + + if (sync_secondary) { + if (copy_to_var) { + r = tpm2_nvpcr_write_anchor_secret_to_var(&credential); + if (r < 0) + return r; + } + + if (copy_to_boot) { + r = tpm2_nvpcr_write_anchor_secret_to_boot(&credential); + if (r < 0) + return r; + } + } + + if (ret) + *ret = TAKE_STRUCT(secret); + return 0; +#else /* HAVE_OPENSSL */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + +int tpm2_nvpcr_initialize( + Tpm2Context *c, + const Tpm2Handle *session, + const char *name, + const struct iovec *anchor_secret) { + +#if HAVE_OPENSSL + TPM2_RC rc; + int r; + + assert(c); + assert(name); + + _cleanup_(nvpcr_data_done) NvPCRData p = {}; + r = nvpcr_data_load(name, &p); + if (r < 0) + return r; + + /* Open + lock the log file *before* we check for the *.anchor flag file. */ + _cleanup_close_ int log_fd = tpm2_userspace_log_open(); + + _cleanup_close_ int dfd = open_mkdir("/run/systemd/nvpcr", O_CLOEXEC, 0755); + if (dfd < 0) + return log_error_errno(dfd, "Failed to open directory '/run/systemd/nvpcr': %m"); + + const char *anchor_fname = strjoina(name, ".anchor"); + if (faccessat(dfd, anchor_fname, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) + return log_debug_errno(errno, "Failed to check if /run/systemd/nvpcr/%s exists: %m", anchor_fname); + } else { + log_debug("NvPCR '%s' is already anchored.", name); + return 0; + } + + if (!iovec_is_set(anchor_secret)) + return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "Need anchor secret."); + + const char *an = tpm2_hash_alg_to_string(p.algorithm); + if (!an) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported algorithm for NvPCR, refusing."); + + const EVP_MD *implementation; + assert_se(implementation = EVP_get_digestbyname(an)); + + int digest_size = EVP_MD_get_size(implementation); + assert_se(digest_size > 0); + + if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer)) + return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash function result too large for TPM, refusing."); + + /* Put together a buffer consisting if the nvindex number and the NvPCR name, that we can calculate an HMAC() off, see below */ + size_t hmac_buffer_size = sizeof(le32_t) + strlen(p.name); + _cleanup_free_ void* hmac_buffer = malloc(hmac_buffer_size); + if (!hmac_buffer) + return log_oom_debug(); + + *(le32_t*) hmac_buffer = htole32(p.nv_index); + memcpy((uint8_t*) hmac_buffer + sizeof(le32_t), name, strlen(name)); + + TPM2B_MAX_NV_BUFFER buf = { + .size = digest_size, + }; + CLEANUP_ERASE(buf); + + /* We measure HMAC(anchor_secret, name) into the NvPCR to anchor it on our secret. */ + if (!HMAC(implementation, anchor_secret->iov_base, anchor_secret->iov_len, hmac_buffer, hmac_buffer_size, buf.buffer, NULL)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + r = tpm2_define_nvpcr_nv_index( + c, + session, + p.nv_index, + p.algorithm, + &nv_handle); + if (r < 0) + return r; + + log_debug("Successfully acquired handle to NV index 0x%" PRIx32 ".", p.nv_index); + + tpm2_userspace_log_dirty(log_fd); + rc = sym_Esys_NV_Extend( + c->esys_context, + /* authHandle= */ nv_handle->esys_handle, + /* nvIndex= */ nv_handle->esys_handle, + /* shandle1= */ ESYS_TR_PASSWORD, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + &buf); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to extend NV index: %s", sym_Tss2_RC_Decode(rc)); + + log_debug("Successfully extended NvPCR '%s' with anchor secret.", name); + + /* Now pre-calculate the initial measurement of an "anchor" secret. This makes sure that others + * cannot delete and reproduce the same fake PCR, unless they also know the "anchor" secret. */ + TPM2B_DIGEST start = { /* initialize to zero */ + .size = digest_size, + }; + r = tpm2_digest_buffer( + p.algorithm, + &start, + buf.buffer, + buf.size, + /* extend= */ true); + if (r < 0) + return log_debug_errno(r, "Failed to calculate initial value: %m"); + + /* Now create the anchor flag file */ + _cleanup_free_ char *h = hexmem(start.buffer, start.size); + if (!h) + return log_oom_debug(); + + r = write_string_file_at(dfd, anchor_fname, h, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return log_debug_errno(r, "Failed to write anchor file: %m"); + + tpm2_userspace_log_clean(log_fd); + return 1; +#else /* HAVE_OPENSSL */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + +int tpm2_nvpcr_read( + Tpm2Context *c, + const Tpm2Handle *session, + const char *name, + struct iovec *ret_value, + uint32_t *ret_nv_index) { + +#if HAVE_OPENSSL + int r; + + assert(c); + assert(name); + + if (!tpm2_nvpcr_name_is_valid(name)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Attempt to read from NvPCR with invalid name, refusing: %s", name); + + _cleanup_(nvpcr_data_done) NvPCRData p = {}; + r = nvpcr_data_load(name, &p); + if (r < 0) + return r; + + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + r = tpm2_index_to_handle( + c, + p.nv_index, + session, + /* ret_public= */ NULL, + /* ret_name= */ NULL, + /* ret_qname= */ NULL, + &nv_handle); + if (r < 0) + return log_debug_errno(r, "Failed to acquire handle to NV index 0x%" PRIu32 ".", p.nv_index); + + log_debug("Successfully acquired handle to NV index 0x%" PRIx32 ".", p.nv_index); + + r = tpm2_read_nv_index( + c, + /* session= */ NULL, + p.nv_index, + nv_handle, + ret_value); + if (r < 0) + return r; + + if (ret_nv_index) + *ret_nv_index = p.nv_index; return 0; #else /* HAVE_OPENSSL */ @@ -8140,3 +9127,9 @@ static const char* const tpm2_pcr_index_table[_TPM2_PCR_INDEX_MAX_DEFINED] = { DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(tpm2_pcr_index, int, TPM2_PCRS_MAX - 1); DEFINE_STRING_TABLE_LOOKUP_TO_STRING(tpm2_pcr_index, int); + +bool tpm2_nvpcr_name_is_valid(const char *name) { + return filename_is_valid(name) && + string_is_safe(name) && + tpm2_pcr_index_from_string(name) < 0; /* don't allow nvpcrs to be name like pcrs */ +} diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 67b035b442..564a3e46ad 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -150,6 +150,11 @@ const char* tpm2_userspace_event_type_to_string(Tpm2UserspaceEventType type) _co Tpm2UserspaceEventType tpm2_userspace_event_type_from_string(const char *s) _pure_; int tpm2_pcr_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const struct iovec *data, const struct iovec *secret, Tpm2UserspaceEventType event, const char *description); +int tpm2_nvpcr_get_index(const char *name, uint32_t *ret); +int tpm2_nvpcr_extend_bytes(Tpm2Context *c, const Tpm2Handle *session, const char *name, const struct iovec *data, const struct iovec *secret, Tpm2UserspaceEventType event_type, const char *description); +int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary); +int tpm2_nvpcr_initialize(Tpm2Context *c, const Tpm2Handle *session, const char *name, const struct iovec *anchor_secret); +int tpm2_nvpcr_read(Tpm2Context *c, const Tpm2Handle *session, const char *name, struct iovec *ret, uint32_t *ret_nv_index); uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s); void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret); @@ -296,7 +301,10 @@ int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fing 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_define_nvpcr_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, TPMI_ALG_HASH algorithm, Tpm2Handle **ret_nv_handle); +int tpm2_extend_nvpcr_nv_index(Tpm2Context *c, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const struct iovec *digest); int tpm2_undefine_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle); +int tpm2_read_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, struct iovec *ret_value); int tpm2_seal_data(Tpm2Context *c, const struct iovec *data, const Tpm2Handle *primary_handle, const Tpm2Handle *encryption_session, const TPM2B_DIGEST *policy, struct iovec *ret_public, struct iovec *ret_private); int tpm2_unseal_data(Tpm2Context *c, const struct iovec *public, const struct iovec *private, const Tpm2Handle *primary_handle, const Tpm2Handle *policy_session, const Tpm2Handle *encryption_session, struct iovec *ret_data); @@ -510,3 +518,5 @@ const char* tpm2_pcr_index_to_string(int pcr) _const_; assert_cc(TPM2_NV_INDEX_UNASSIGNED_FIRST >= TPM2_NV_INDEX_FIRST); assert_cc(TPM2_NV_INDEX_UNASSIGNED_LAST <= TPM2_NV_INDEX_LAST); #endif + +bool tpm2_nvpcr_name_is_valid(const char *name); diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build index 39fc97b91e..36082486d3 100644 --- a/src/tpm2-setup/meson.build +++ b/src/tpm2-setup/meson.build @@ -25,6 +25,23 @@ executables += [ generator_template + { 'name' : 'systemd-tpm2-generator', 'sources' : files('tpm2-generator.c'), + 'conditions' : [ + 'ENABLE_BOOTLOADER', + 'HAVE_OPENSSL', + 'HAVE_TPM2', + ], }, ] + +if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1 + nvpcrs = [] + foreach n : nvpcrs + custom_target( + input : 'nvpcr/' + n + '.nvpcr.in', + output : n + '.nvpcr', + command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + install : true, + install_dir : prefixdir / 'lib/nvpcr') + endforeach +endif