aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/secvar/secboot_tpm.rst175
-rw-r--r--include/secvar.h1
-rw-r--r--libstb/secvar/secvar.h4
-rw-r--r--libstb/secvar/storage/Makefile.inc5
-rw-r--r--libstb/secvar/storage/secboot_tpm.c737
-rw-r--r--libstb/secvar/storage/secboot_tpm.h61
-rw-r--r--libstb/secvar/storage/tpmnv_ops.c15
7 files changed, 994 insertions, 4 deletions
diff --git a/doc/secvar/secboot_tpm.rst b/doc/secvar/secboot_tpm.rst
new file mode 100644
index 0000000..8da0c2f
--- /dev/null
+++ b/doc/secvar/secboot_tpm.rst
@@ -0,0 +1,175 @@
+.. _secvar/secboot_tpm:
+
+secboot_tpm secvar storage driver for P9 platforms
+==================================================
+
+Overview
+--------
+
+This storage driver utilizes the SECBOOT PNOR partition and TPM NV space to
+persist secure variables across reboots in a tamper-resistant manner. While
+writes to PNOR cannot be completely prevented, writes CAN be prevented to TPM
+NV. On the other hand, there is limited available space in TPM NV.
+
+Therefore, this driver uses both in conjunction: large variable data is written
+to SECBOOT, and a hash of the variable data is stored in TPM NV. When the
+variables are loaded from SECBOOT, this hash is recalculated and compared
+against the value stored in the TPM. If they do not match, then the variables
+must have been altered and are not loaded.
+
+See the following sections for more information on the internals of the driver.
+
+
+Storage Layouts
+---------------
+
+At a high-level, there are a few major logical components:
+
+ - (PNOR) Variable storage (split in half, active/staging)
+ - (PNOR) Update storage
+ - (TPM) Protected variable storage
+ - (TPM) Bank hashes & active bit
+
+Variable storage consists of two smaller banks, variable bank 0 and variable
+bank 1. Either of the banks may be designated "active" by setting the active
+bank bit to either 0 or 1, indicating that the corresponding bank is now
+"active". The other bank is then considered "staging". See the "Persisting
+Variable Bank Updates" for more on the active/staging bank logic.
+
+Protected variable storage is stored in ``VARS`` TPM NV index. Unlike the other
+variable storage, there is only one bank due to limited storage space. See the
+TPM NV Indices section for more.
+
+
+Persisting the Variable Bank
+----------------------------
+
+When writing a new variable bank to storage, this is (roughly) the procedure the
+driver will follow:
+
+1. write variables to the staging bank
+2. calculate hash of the staging bank
+3. store the staging bank hash in the TPM NV
+4. flip the active bank bit
+
+This procedure ensures that the switch-over from the old variables to the
+new variables is as atomic as possible. This should prevent any possible
+issues caused by an interruption during the writing process, such as power loss.
+
+The bank hashes are a SHA256 hash calculated over the whole region of
+storage space allocated to the bank, including unused storage. For consistency,
+unused space is always written as zeroes. Like the active/staging variable
+banks, there are also two corresponding active/staging bank hashes stored in
+the TPM.
+
+
+TPM NV Indices
+--------------
+
+The driver utilizes two TPM NV indices:
+
+.. code-block:: c
+
+ # size). datadefine SECBOOT_TPMNV_VARS_INDEX 0x01c10190
+ #define SECBOOT_TPMNV_CONTROL_INDEX 0x01c10191
+
+The ``VARS`` index stores variables flagged with ``SECVAR_FLAG_PROTECTED``.
+These variables are critical to the state of OS secure boot, and therefore
+cannot be safely stored in the SECBOOT partition. This index is defined to be
+1024 bytes in size, which is enough for the current implementation on P9. It
+is kept small by default to preserve the very limited NV index space.
+
+The ``CONTROL`` index stores the bank hashes, and the bit to determine which
+bank is active. See the Active/Staging Bank Swapping section for more.
+
+Both indices are defined on first boot with the same set of attributes. If the
+indices are already defined but not in the expected state, (different
+attributes, size, etc), then the driver will halt the boot. Asserting physical
+presence will redefine the indices in the correct state.
+
+
+Locking
+-------
+
+PNOR cannot be locked, however the TPM can be. The TPM NV indices are double
+protected via two locking mechanisms:
+
+ - The driver's ``.lock()`` hook sends the ``TSS_NV_WriteLock`` TPM command.
+This sets the ``WRITELOCKED`` attribute, which is cleared on the next
+TPM reset.
+
+ - The TPM NV indices are defined under the platform hierarchy. Skiboot will add
+a global lock to all the NV indices under this hierarchy prior to loading a
+kernel. This is also reset on the next TPM reset.
+
+NOTE: The TPM is only reset during a cold reboot. Fast reboots or kexecs will
+NOT unlock the TPM.
+
+
+Resetting Storage / Physical Presence
+-------------------------------------
+
+In the case that secure boot/secvar has been rendered unusable, (for example:
+corrupted data, lost/compromised private key, improperly defined NV indices, etc)
+this storage driver responds to physical presence assertion as a last-resort
+method to recover the system.
+
+Asserting physical presence undefines, and immediately redefines the TPM NV
+indices. Defining the NV indices then causes a cascading set of reformats for
+the remaining components of storage, similar to a first-boot scenario.
+
+This driver considers physical presence to be asserted if any of the following
+device tree nodes are present in ``ibm,secureboot``:
+ - ``clear-os-keys``
+ - ``clear-all-keys``
+ - ``clear-mfg-keys``
+
+
+Storage Formats/Layouts
+=======================
+
+SECBOOT (PNOR)
+--------------
+
+Partition Format:
+ - 8b secboot header
+ - 4b: u32. magic number, always 0x5053424b
+ - 1b: u8. version, always 1
+ - 3b: unused padding
+ - 32k: secvars. variable bank 0
+ - 32k: secvars. variable bank 1
+ - 32k: secvars. update bank
+
+Variable Format (secvar):
+ - 8b: u64. key length
+ - 8b: u64. data size
+ - 1k: string. key
+ - (data size). data
+
+TPM VARS (NV)
+-------------
+
+NV Index Format:
+ - 8b secboot header
+ - 4b: u32. magic number, always 0x5053424b
+ - 1b: u8. version, always 1
+ - 3b: unused padding
+ - 1016b: packed secvars. protected variable storage
+
+Variable Format (packed secvar):
+ - 8b: u64. key length
+ - 8b: u64. data size
+ - (key length): string. key
+ - (data size). data
+
+TPM CONTROL (NV)
+----------------
+
+ - 8b secboot header
+ - 4b: u32. magic number, always 0x5053424b
+ - 1b: u8. version, always 1
+ - 3b: unused padding
+ - 1b: u8. active bit, 0 or 1
+ - 32b: sha256 hash of variable bank 0
+ - 32b: sha256 hash of variable bank 1
+
diff --git a/include/secvar.h b/include/secvar.h
index db10395..2121027 100644
--- a/include/secvar.h
+++ b/include/secvar.h
@@ -36,6 +36,7 @@ struct secvar_backend_driver {
const char *compatible;
};
+extern struct secvar_storage_driver secboot_tpm_driver;
int secvar_main(struct secvar_storage_driver, struct secvar_backend_driver);
diff --git a/libstb/secvar/secvar.h b/libstb/secvar/secvar.h
index fe66021..33cd0a0 100644
--- a/libstb/secvar/secvar.h
+++ b/libstb/secvar/secvar.h
@@ -16,8 +16,8 @@ enum {
};
-#define SECVAR_FLAG_VOLATILE 0x1 // Instructs storage driver to ignore variable on writes
-#define SECVAR_FLAG_SECURE_STORAGE 0x2 // Hint for storage driver to select storage location
+#define SECVAR_FLAG_VOLATILE 0x1 /* Instructs storage driver to ignore variable on writes */
+#define SECVAR_FLAG_PROTECTED 0x2 /* Instructs storage driver to store in lockable flash */
struct secvar {
struct list_node link;
diff --git a/libstb/secvar/storage/Makefile.inc b/libstb/secvar/storage/Makefile.inc
index 3fd9543..35fba72 100644
--- a/libstb/secvar/storage/Makefile.inc
+++ b/libstb/secvar/storage/Makefile.inc
@@ -1,11 +1,12 @@
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
# -*-Makefile-*-
-SECVAR_STORAGE_DIR = libstb/secvar/storage
+SECVAR_STORAGE_DIR = $(SRC)/libstb/secvar/storage
SUBDIRS += $(SECVAR_STORAGE_DIR)
-SECVAR_STORAGE_SRCS =
+SECVAR_STORAGE_SRCS = secboot_tpm.c tpmnv_ops.c
+#SECVAR_STORAGE_SRCS = secboot_tpm.c fakenv_ops.c
SECVAR_STORAGE_OBJS = $(SECVAR_STORAGE_SRCS:%.c=%.o)
SECVAR_STORAGE = $(SECVAR_STORAGE_DIR)/built-in.a
diff --git a/libstb/secvar/storage/secboot_tpm.c b/libstb/secvar/storage/secboot_tpm.c
new file mode 100644
index 0000000..b6a294b
--- /dev/null
+++ b/libstb/secvar/storage/secboot_tpm.c
@@ -0,0 +1,737 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2020 IBM Corp. */
+#ifndef pr_fmt
+#define pr_fmt(fmt) "SECBOOT_TPM: " fmt
+#endif
+
+#include <stdlib.h>
+#include <skiboot.h>
+#include <opal.h>
+#include <mbedtls/sha256.h>
+#include "../secvar.h"
+#include "../secvar_devtree.h"
+#include "secboot_tpm.h"
+#include <tssskiboot.h>
+#include <ibmtss/TPM_Types.h>
+
+#define CYCLE_BIT(b) (b^0x1)
+
+#define SECBOOT_TPM_MAX_VAR_SIZE 8192
+
+struct secboot *secboot_image = NULL;
+struct tpmnv_vars *tpmnv_vars_image = NULL;
+struct tpmnv_control *tpmnv_control_image = NULL;
+
+const size_t tpmnv_vars_size = 1024;
+
+/* Expected TPM NV index name field from NV_ReadPublic given our known
+ * set of attributes (see tss_nv_define_space).
+ * See Part 1 Section 16, and Part 2 Section 13.5 of the TPM Specification
+ * for how this is calculated
+ *
+ * These hashes are calculated and checked BEFORE TPM2_NV_WriteLock is called,
+ * which alters the hash slightly as it sets TPMA_NV_WRITELOCKED
+ */
+const uint8_t tpmnv_vars_name[] = {
+ 0x00, 0x0b, 0x94, 0x64, 0x36, 0x25, 0xfc, 0xc1, 0x1d, 0xc1, 0x0e, 0x28, 0xe7,
+ 0xac, 0xaf, 0xc6, 0x08, 0x8e, 0xda, 0x21, 0xd6, 0x43, 0xd2, 0x77, 0xe7, 0x2d,
+ 0x83, 0x39, 0x0f, 0xa6, 0xdf, 0xc0, 0x59, 0x37,
+};
+
+const uint8_t tpmnv_control_name[] = {
+ 0x00, 0x0b, 0xad, 0x47, 0x6b, 0xa5, 0xdf, 0xb1, 0xe2, 0x18, 0x50, 0xf6, 0x05,
+ 0x67, 0xe8, 0x8b, 0xa9, 0x0f, 0x86, 0x1f, 0x06, 0xab, 0x43, 0x96, 0x7f, 0x6e,
+ 0x85, 0x33, 0x5b, 0xa6, 0xf0, 0x63, 0x73, 0xd0,
+};
+
+const uint8_t tpmnv_vars_prov_name[] = {
+ 0x00, 0x0b, 0x58, 0x36, 0x2c, 0xbf, 0xec, 0x0e, 0xcc, 0xbf, 0xa9, 0x41, 0x94,
+ 0xe9, 0x95, 0xe8, 0x3b, 0xd7, 0x8b, 0x52, 0xac, 0x61, 0x6f, 0xe6, 0x42, 0x93,
+ 0xbb, 0x5a, 0x79, 0x9f, 0xcc, 0x60, 0x5e, 0x8d,
+};
+
+const uint8_t tpmnv_control_prov_name[] = {
+ 0x00, 0x0b, 0x7b, 0xd6, 0x02, 0xac, 0xf5, 0x34, 0x54, 0x5c, 0x3e, 0xda, 0xe5,
+ 0xb2, 0xe4, 0x93, 0x4f, 0x36, 0xfb, 0x7f, 0xea, 0xbe, 0xfa, 0x3c, 0xfe, 0xed,
+ 0x6a, 0x12, 0xfb, 0xc8, 0xf7, 0x92, 0x0e, 0xd3,
+};
+
+/* Calculate a SHA256 hash over the supplied buffer */
+static int calc_bank_hash(char *target_hash, const char *source_buf, uint64_t size)
+{
+ mbedtls_sha256_context ctx;
+ int rc;
+
+ mbedtls_sha256_init(&ctx);
+
+ rc = mbedtls_sha256_update_ret(&ctx, source_buf, size);
+ if (rc)
+ goto out;
+
+ mbedtls_sha256_finish_ret(&ctx, target_hash);
+ if (rc)
+ goto out;
+
+out:
+ mbedtls_sha256_free(&ctx);
+ return rc;
+}
+
+/* Reformat the TPMNV space */
+static int tpmnv_format(void)
+{
+ int rc;
+
+ memset(tpmnv_vars_image, 0x00, tpmnv_vars_size);
+ memset(tpmnv_control_image, 0x00, sizeof(struct tpmnv_control));
+
+ tpmnv_vars_image->header.magic_number = SECBOOT_MAGIC_NUMBER;
+ tpmnv_vars_image->header.version = SECBOOT_VERSION;
+ tpmnv_control_image->header.magic_number = SECBOOT_MAGIC_NUMBER;
+ tpmnv_control_image->header.version = SECBOOT_VERSION;
+
+ /* Counts as first write to the TPM NV, which sets the
+ * TPMA_NVA_WRITTEN attribute */
+ rc = tpmnv_ops.write(SECBOOT_TPMNV_VARS_INDEX,
+ tpmnv_vars_image,
+ tpmnv_vars_size, 0);
+ if (rc) {
+ prlog(PR_ERR, "Could not write new formatted data to VARS index, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = tpmnv_ops.write(SECBOOT_TPMNV_CONTROL_INDEX,
+ tpmnv_control_image,
+ sizeof(struct tpmnv_control), 0);
+ if (rc)
+ prlog(PR_ERR, "Could not write new formatted data to CONTROL index, rc=%d\n", rc);
+
+ return rc;
+}
+
+/* Reformat the secboot PNOR space */
+static int secboot_format(void)
+{
+ int rc;
+
+ memset(secboot_image, 0x00, sizeof(struct secboot));
+
+ secboot_image->header.magic_number = SECBOOT_MAGIC_NUMBER;
+ secboot_image->header.version = SECBOOT_VERSION;
+
+ /* Write the hash of the empty bank to the tpm so future loads work */
+ rc = calc_bank_hash(tpmnv_control_image->bank_hash[0],
+ secboot_image->bank[0],
+ SECBOOT_VARIABLE_BANK_SIZE);
+ if (rc) {
+ prlog(PR_ERR, "Bank hash failed to calculate somehow\n");
+ return rc;
+ }
+
+ rc = tpmnv_ops.write(SECBOOT_TPMNV_CONTROL_INDEX,
+ tpmnv_control_image->bank_hash[0],
+ SHA256_DIGEST_SIZE,
+ offsetof(struct tpmnv_control,
+ bank_hash[0]));
+ if (rc) {
+ prlog(PR_ERR, "Could not write fresh formatted bank hashes to CONTROL index, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = flash_secboot_write(0, secboot_image, sizeof(struct secboot));
+ if (rc)
+ prlog(PR_ERR, "Could not write formatted data to PNOR, rc=%d\n", rc);
+
+ return rc;
+}
+
+
+/*
+ * Serialize one variable to a target memory location.
+ * Returns the advanced target pointer,
+ * NULL if advanced pointer would exceed the supplied bound
+ */
+static char *secboot_serialize_secvar(char *target, const struct secvar *var, const char *end)
+{
+ if ((target + sizeof(uint64_t) + sizeof(uint64_t)
+ + var->key_len + var->data_size) > end)
+ return NULL;
+
+ *((uint64_t*) target) = cpu_to_be64(var->key_len);
+ target += sizeof(var->key_len);
+ *((uint64_t*) target) = cpu_to_be64(var->data_size);
+ target += sizeof(var->data_size);
+ memcpy(target, var->key, var->key_len);
+ target += var->key_len;
+ memcpy(target, var->data, var->data_size);
+ target += var->data_size;
+
+ return target;
+}
+
+
+/* Flattens a linked-list bank into a contiguous buffer for writing */
+static int secboot_serialize_bank(const struct list_head *bank, char *target,
+ size_t target_size, int flags)
+{
+ struct secvar *var;
+ char *end = target + target_size;
+
+ assert(bank);
+ assert(target);
+
+ memset(target, 0x00, target_size);
+
+ list_for_each(bank, var, link) {
+ if (var->flags != flags)
+ continue;
+
+ target = secboot_serialize_secvar(target, var, end);
+ if (!target) {
+ prlog(PR_ERR, "Ran out of %s space, giving up!",
+ (flags & SECVAR_FLAG_PROTECTED) ? "TPMNV" : "PNOR");
+ return OPAL_EMPTY;
+ }
+ }
+
+ return OPAL_SUCCESS;
+}
+
+/* Helper for the variable-bank specific writing logic */
+static int secboot_tpm_write_variable_bank(const struct list_head *bank)
+{
+ int rc;
+ uint64_t bit;
+
+ bit = CYCLE_BIT(tpmnv_control_image->active_bit);
+ /* Serialize TPMNV variables */
+ rc = secboot_serialize_bank(bank, tpmnv_vars_image->vars, tpmnv_vars_size - sizeof(struct tpmnv_vars), SECVAR_FLAG_PROTECTED);
+ if (rc)
+ goto out;
+
+
+ /* Write TPMNV variables to actual NV */
+ rc = tpmnv_ops.write(SECBOOT_TPMNV_VARS_INDEX, tpmnv_vars_image, tpmnv_vars_size, 0);
+ if (rc)
+ goto out;
+
+ /* Serialize the PNOR variables, but don't write to flash until after the bank hash */
+ rc = secboot_serialize_bank(bank, secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE, 0);
+ if (rc)
+ goto out;
+
+ /* Calculate the bank hash, and write to TPM NV */
+ rc = calc_bank_hash(tpmnv_control_image->bank_hash[bit], secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE);
+ if (rc)
+ goto out;
+
+ rc = tpmnv_ops.write(SECBOOT_TPMNV_CONTROL_INDEX, tpmnv_control_image->bank_hash[bit],
+ SHA256_DIGEST_LENGTH, offsetof(struct tpmnv_control, bank_hash[bit]));
+ if (rc)
+ goto out;
+
+ /* Write new variable bank to pnor */
+ rc = flash_secboot_write(0, secboot_image, sizeof(struct secboot));
+ if (rc)
+ goto out;
+
+ /* Flip the bit, and write to TPM NV */
+ tpmnv_control_image->active_bit = bit;
+ rc = tpmnv_ops.write(SECBOOT_TPMNV_CONTROL_INDEX,
+ &tpmnv_control_image->active_bit,
+ sizeof(tpmnv_control_image->active_bit),
+ offsetof(struct tpmnv_control, active_bit));
+out:
+
+ return rc;
+}
+
+static int secboot_tpm_write_bank(struct list_head *bank, int section)
+{
+ int rc;
+
+ switch (section) {
+ case SECVAR_VARIABLE_BANK:
+ rc = secboot_tpm_write_variable_bank(bank);
+ break;
+ case SECVAR_UPDATE_BANK:
+ memset(secboot_image->update, 0, SECBOOT_UPDATE_BANK_SIZE);
+ rc = secboot_serialize_bank(bank, secboot_image->update,
+ SECBOOT_UPDATE_BANK_SIZE, 0);
+ if (rc)
+ break;
+
+ rc = flash_secboot_write(0, secboot_image,
+ sizeof(struct secboot));
+ break;
+ default:
+ rc = OPAL_HARDWARE;
+ }
+
+ return rc;
+}
+
+
+/*
+ * Deserialize a single secvar from a buffer.
+ * Returns an advanced pointer, and an allocated secvar in *var.
+ * Returns NULL if out of bounds reached, or out of memory.
+ */
+static int secboot_deserialize_secvar(struct secvar **var, char **src, const char *end)
+{
+ uint64_t key_len;
+ uint64_t data_size;
+ struct secvar *ret;
+
+ assert(var);
+
+ /* Load in the two header values */
+ key_len = be64_to_cpu(*((uint64_t *) *src));
+ *src += sizeof(uint64_t);
+ data_size = be64_to_cpu(*((uint64_t *) *src));
+ *src += sizeof(uint64_t);
+
+ /* Check if we've reached the last var to deserialize */
+ if ((key_len == 0) && (data_size == 0)) {
+ return OPAL_EMPTY;
+ }
+
+ if (key_len > SECVAR_MAX_KEY_LEN) {
+ prlog(PR_ERR, "Deserialization failed: key length exceeded maximum value"
+ "%llu > %u", key_len, SECVAR_MAX_KEY_LEN);
+ return OPAL_RESOURCE;
+ }
+ if (data_size > SECBOOT_TPM_MAX_VAR_SIZE) {
+ prlog(PR_ERR, "Deserialization failed: data size exceeded maximum value"
+ "%llu > %u", key_len, SECBOOT_TPM_MAX_VAR_SIZE);
+ return OPAL_RESOURCE;
+ }
+
+ /* Make sure these fields aren't oversized... */
+ if ((*src + key_len + data_size) > end) {
+ *var = NULL;
+ prlog(PR_ERR, "key_len or data_size exceeded the expected bounds");
+ return OPAL_RESOURCE;
+ }
+
+ ret = alloc_secvar(key_len, data_size);
+ if (!ret) {
+ *var = NULL;
+ prlog(PR_ERR, "Out of memory, could not allocate new secvar");
+ return OPAL_NO_MEM;
+ }
+
+ /* Load in variable-sized data */
+ memcpy(ret->key, *src, ret->key_len);
+ *src += ret->key_len;
+ memcpy(ret->data, *src, ret->data_size);
+ *src += ret->data_size;
+
+ *var = ret;
+
+ return OPAL_SUCCESS;
+}
+
+
+/* Load variables from a flattened buffer into a bank list */
+static int secboot_tpm_deserialize_from_buffer(struct list_head *bank, char *src,
+ uint64_t size, uint64_t flags)
+{
+ struct secvar *var;
+ char *cur;
+ char *end;
+ int rc = 0;
+
+ cur = src;
+ end = src + size;
+
+ while (cur < end) {
+ /* Ensure there is enough space to even check for another var header */
+ if ((end - cur) < (sizeof(uint64_t) * 2))
+ break;
+
+ rc = secboot_deserialize_secvar(&var, &cur, end);
+ switch (rc) {
+ case OPAL_RESOURCE:
+ case OPAL_NO_MEM:
+ goto fail;
+ case OPAL_EMPTY:
+ goto done;
+ default: assert(1);
+ }
+
+ var->flags |= flags;
+
+ list_add_tail(bank, &var->link);
+ }
+done:
+ return OPAL_SUCCESS;
+fail:
+ clear_bank_list(bank);
+ return rc;
+}
+
+static int secboot_tpm_load_variable_bank(struct list_head *bank)
+{
+ char bank_hash[SHA256_DIGEST_LENGTH];
+ uint64_t bit = tpmnv_control_image->active_bit;
+ int rc;
+
+ /* Check the hash of the bank we loaded from PNOR
+ * versus the expected hash in TPM NV */
+ rc = calc_bank_hash(bank_hash,
+ secboot_image->bank[bit],
+ SECBOOT_VARIABLE_BANK_SIZE);
+ if (rc)
+ return rc;
+
+ if (memcmp(bank_hash,
+ tpmnv_control_image->bank_hash[bit],
+ SHA256_DIGEST_LENGTH))
+ /* Tampered pnor space detected, abandon ship */
+ return OPAL_PERMISSION;
+
+ rc = secboot_tpm_deserialize_from_buffer(bank, tpmnv_vars_image->vars, tpmnv_vars_size, SECVAR_FLAG_PROTECTED);
+ if (rc)
+ return rc;
+
+ return secboot_tpm_deserialize_from_buffer(bank, secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE, 0);
+}
+
+
+static int secboot_tpm_load_bank(struct list_head *bank, int section)
+{
+ switch (section) {
+ case SECVAR_VARIABLE_BANK:
+ return secboot_tpm_load_variable_bank(bank);
+ case SECVAR_UPDATE_BANK:
+ return secboot_tpm_deserialize_from_buffer(bank, secboot_image->update, SECBOOT_UPDATE_BANK_SIZE, 0);
+ }
+
+ return OPAL_HARDWARE;
+}
+
+static int secboot_tpm_get_tpmnv_names(char *nv_vars_name, char *nv_control_name)
+{
+ TPMS_NV_PUBLIC nv_public; /* Throwaway, we only want the name field */
+ TPM2B_NAME vars_tmp;
+ TPM2B_NAME control_tmp;
+ int rc;
+
+ rc = tpmnv_ops.readpublic(SECBOOT_TPMNV_VARS_INDEX,
+ &nv_public,
+ &vars_tmp);
+ if (rc) {
+ prlog(PR_ERR, "Failed to readpublic from the VARS index, rc=%d\n", rc);
+ return rc;
+ }
+ rc = tpmnv_ops.readpublic(SECBOOT_TPMNV_CONTROL_INDEX,
+ &nv_public,
+ &control_tmp);
+ if (rc) {
+ prlog(PR_ERR, "Failed to readpublic from the CONTROL index, rc=%d\n", rc);
+ return rc;
+ }
+
+ memcpy(nv_vars_name, vars_tmp.t.name, MIN(sizeof(tpmnv_vars_name), vars_tmp.t.size));
+ memcpy(nv_control_name, control_tmp.t.name, MIN(sizeof(tpmnv_control_name), control_tmp.t.size));
+
+ return OPAL_SUCCESS;
+}
+
+
+/* Ensure the NV indices were defined with the correct set of attributes */
+static int secboot_tpm_check_tpmnv_attrs(char *nv_vars_name, char *nv_control_name)
+{
+ if (memcmp(tpmnv_vars_name,
+ nv_vars_name,
+ sizeof(tpmnv_vars_name))) {
+ prlog(PR_ERR, "VARS index not defined with the correct attributes\n");
+ return OPAL_RESOURCE;
+ }
+ if (memcmp(tpmnv_control_name,
+ nv_control_name,
+ sizeof(tpmnv_control_name))) {
+ prlog(PR_ERR, "CONTROL index not defined with the correct attributes\n");
+ return OPAL_RESOURCE;
+ }
+
+ return OPAL_SUCCESS;
+}
+
+static bool secboot_tpm_check_provisioned_indices(char *nv_vars_name, char *nv_control_name)
+{
+ /* Check for provisioned NV indices, redefine them if detected. */
+ if (!memcmp(tpmnv_vars_prov_name,
+ nv_vars_name,
+ sizeof(tpmnv_vars_prov_name)) &&
+ !memcmp(tpmnv_control_prov_name,
+ nv_control_name,
+ sizeof(tpmnv_control_prov_name))) {
+ return true;
+ }
+
+ /*
+ * If one matches but the other doesn't, do NOT redefine.
+ * The next step should detect they don't match the expected values
+ * and fail the boot.
+ */
+ return false;
+}
+
+static int secboot_tpm_define_indices(void)
+{
+ int rc = OPAL_SUCCESS;
+
+ rc = tpmnv_ops.definespace(SECBOOT_TPMNV_VARS_INDEX, tpmnv_vars_size);
+ if (rc) {
+ prlog(PR_ERR, "Failed to define the VARS index, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = tpmnv_ops.definespace(SECBOOT_TPMNV_CONTROL_INDEX, sizeof(struct tpmnv_control));
+ if (rc) {
+ prlog(PR_ERR, "Failed to define the CONTROL index, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = tpmnv_format();
+ if (rc)
+ return rc;
+
+ /* TPM NV just got redefined, so unconditionally format the SECBOOT partition */
+ return secboot_format();
+}
+
+static int secboot_tpm_undefine_indices(bool *vars_defined, bool *control_defined)
+{
+ int rc;
+
+ if (vars_defined) {
+ rc = tpmnv_ops.undefinespace(SECBOOT_TPMNV_VARS_INDEX);
+ if (rc) {
+ prlog(PR_ERR, "Failed to undefine VARS, something is seriously wrong\n");
+ return rc;
+ }
+ }
+
+ if (control_defined) {
+ rc = tpmnv_ops.undefinespace(SECBOOT_TPMNV_CONTROL_INDEX);
+ if (rc) {
+ prlog(PR_ERR, "Failed to undefine CONTROL, something is seriously wrong\n");
+ return rc;
+ }
+ }
+
+ *vars_defined = *control_defined = false;
+
+ return OPAL_SUCCESS;
+}
+
+
+static int secboot_tpm_store_init(void)
+{
+ int rc;
+ unsigned int secboot_size;
+
+ TPMI_RH_NV_INDEX *indices = NULL;
+ char nv_vars_name[sizeof(tpmnv_vars_name)];
+ char nv_control_name[sizeof(tpmnv_control_name)];
+ size_t count = 0;
+ bool control_defined = false;
+ bool vars_defined = false;
+ int i;
+
+ if (secboot_image)
+ return OPAL_SUCCESS;
+
+ prlog(PR_DEBUG, "Initializing for pnor+tpm based platform\n");
+
+ /* Initialize SECBOOT first, we may need to format this later */
+ rc = flash_secboot_info(&secboot_size);
+ if (rc) {
+ prlog(PR_ERR, "error %d retrieving keystore info\n", rc);
+ goto error;
+ }
+ if (sizeof(struct secboot) > secboot_size) {
+ prlog(PR_ERR, "secboot partition %d KB too small. min=%ld\n",
+ secboot_size >> 10, sizeof(struct secboot));
+ rc = OPAL_RESOURCE;
+ goto error;
+ }
+
+ secboot_image = memalign(0x1000, sizeof(struct secboot));
+ if (!secboot_image) {
+ prlog(PR_ERR, "Failed to allocate space for the secboot image\n");
+ rc = OPAL_NO_MEM;
+ goto error;
+ }
+
+ /* Read in the PNOR data, bank hash is checked on call to .load_bank() */
+ rc = flash_secboot_read(secboot_image, 0, sizeof(struct secboot));
+ if (rc) {
+ prlog(PR_ERR, "failed to read the secboot partition, rc=%d\n", rc);
+ goto error;
+ }
+
+ /* Allocate the tpmnv data buffers */
+ tpmnv_vars_image = zalloc(tpmnv_vars_size);
+ if (!tpmnv_vars_image)
+ return OPAL_NO_MEM;
+ tpmnv_control_image = zalloc(sizeof(struct tpmnv_control));
+ if (!tpmnv_control_image)
+ return OPAL_NO_MEM;
+
+ /* Check if the NV indices have been defined already */
+ rc = tpmnv_ops.getindices(&indices, &count);
+ if (rc) {
+ prlog(PR_ERR, "Could not load defined indicies from TPM, rc=%d\n", rc);
+ goto error;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (indices[i] == SECBOOT_TPMNV_VARS_INDEX)
+ vars_defined = true;
+ else if (indices[i] == SECBOOT_TPMNV_CONTROL_INDEX)
+ control_defined = true;
+ }
+ free(indices);
+
+ /* Undefine the NV indices if physical presence has been asserted */
+ if (secvar_check_physical_presence()) {
+ prlog(PR_INFO, "Physical presence asserted, redefining NV indices, and resetting keystore\n");
+ rc = secboot_tpm_undefine_indices(&vars_defined, &control_defined);
+ if (rc)
+ goto error;
+
+ rc = secboot_tpm_define_indices();
+ if (rc)
+ goto error;
+
+ /* Indices got defined and formatted, we're done here */
+ goto done;
+ }
+ /* Determine if we need to define the indices. These should BOTH be false or true */
+ if (!vars_defined && !control_defined) {
+ rc = secboot_tpm_define_indices();
+ if (rc)
+ goto error;
+
+ /* Indices got defined and formatted, we're done here */
+ goto done;
+ }
+ if (vars_defined ^ control_defined) {
+ /* This should never happen. Both indices should be defined at the same
+ * time. Otherwise something seriously went wrong. */
+ prlog(PR_ERR, "NV indices defined with unexpected attributes. Assert physical presence to clear\n");
+ goto error;
+ }
+
+ /* Both indices are defined, now need to validate their contents */
+
+ rc = secboot_tpm_get_tpmnv_names(nv_vars_name, nv_control_name);
+ if (rc)
+ goto error;
+
+ /* Check for provisioned TPMNV indices, redefine them if detected */
+ if (secboot_tpm_check_provisioned_indices(nv_vars_name, nv_control_name)) {
+ prlog(PR_INFO, "Provisioned TPM NV indices detected, redefining NV indices, and resetting keystore\n");
+ rc = secboot_tpm_undefine_indices(&vars_defined, &control_defined);
+ if (rc)
+ goto error;
+
+ rc = secboot_tpm_define_indices();
+ if (rc)
+ goto error;
+
+ /* Indices got defined and formatted, we're done here */
+ goto done;
+ }
+
+ /* Otherwise, ensure the NV indices were defined with the correct set of attributes */
+ rc = secboot_tpm_check_tpmnv_attrs(nv_vars_name, nv_control_name);
+ if (rc)
+ goto error;
+
+
+ /* TPMNV indices exist, are correct, and weren't just formatted, so read them in */
+ rc = tpmnv_ops.read(SECBOOT_TPMNV_VARS_INDEX,
+ tpmnv_vars_image,
+ tpmnv_vars_size, 0);
+ if (rc) {
+ prlog(PR_ERR, "Failed to read from the VARS index\n");
+ goto error;
+ }
+
+ rc = tpmnv_ops.read(SECBOOT_TPMNV_CONTROL_INDEX,
+ tpmnv_control_image,
+ sizeof(struct tpmnv_control), 0);
+ if (rc) {
+ prlog(PR_ERR, "Failed to read from the CONTROL index\n");
+ goto error;
+ }
+
+ /* Verify the header information is correct */
+ if (tpmnv_vars_image->header.magic_number != SECBOOT_MAGIC_NUMBER ||
+ tpmnv_control_image->header.magic_number != SECBOOT_MAGIC_NUMBER ||
+ tpmnv_vars_image->header.version != SECBOOT_VERSION ||
+ tpmnv_control_image->header.version != SECBOOT_VERSION) {
+ prlog(PR_ERR, "TPMNV indices defined, but contain bad data. Assert physical presence to clear\n");
+ goto error;
+ }
+
+ /* Verify the secboot partition header information,
+ * reformat if incorrect
+ * Note: Future variants should attempt to handle older versions safely
+ */
+ if (secboot_image->header.magic_number != SECBOOT_MAGIC_NUMBER ||
+ secboot_image->header.version != SECBOOT_VERSION) {
+ rc = secboot_format();
+ if (rc)
+ goto error;
+ }
+
+done:
+ return OPAL_SUCCESS;
+
+error:
+ free(secboot_image);
+ secboot_image = NULL;
+ free(tpmnv_vars_image);
+ tpmnv_vars_image = NULL;
+ free(tpmnv_control_image);
+ tpmnv_control_image = NULL;
+
+ return rc;
+}
+
+
+static void secboot_tpm_lockdown(void)
+{
+ /* Note: While write lock is called here on the two NV indices,
+ * both indices are also defined on the platform hierarchy.
+ * The platform hierarchy auth is set later in the skiboot
+ * initialization process, and not by any secvar-related code.
+ */
+ int rc;
+
+ rc = tpmnv_ops.writelock(SECBOOT_TPMNV_VARS_INDEX);
+ if (rc) {
+ prlog(PR_EMERG, "TSS Write Lock failed on VARS index, halting.\n");
+ abort();
+ }
+
+ rc = tpmnv_ops.writelock(SECBOOT_TPMNV_CONTROL_INDEX);
+ if (rc) {
+ prlog(PR_EMERG, "TSS Write Lock failed on CONTROL index, halting.\n");
+ abort();
+ }
+}
+
+struct secvar_storage_driver secboot_tpm_driver = {
+ .load_bank = secboot_tpm_load_bank,
+ .write_bank = secboot_tpm_write_bank,
+ .store_init = secboot_tpm_store_init,
+ .lockdown = secboot_tpm_lockdown,
+ .max_var_size = SECBOOT_TPM_MAX_VAR_SIZE,
+};
diff --git a/libstb/secvar/storage/secboot_tpm.h b/libstb/secvar/storage/secboot_tpm.h
new file mode 100644
index 0000000..30a747a
--- /dev/null
+++ b/libstb/secvar/storage/secboot_tpm.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2020 IBM Corp. */
+#ifndef _SECBOOT_TPM_H_
+#define _SECBOOT_TPM_H_
+
+#include <ibmtss/tss.h>
+
+#define SECBOOT_VARIABLE_BANK_SIZE 32000
+#define SECBOOT_UPDATE_BANK_SIZE 32000
+
+#define SECBOOT_VARIABLE_BANK_NUM 2
+
+/* Because mbedtls doesn't define this? */
+#define SHA256_DIGEST_LENGTH 32
+
+/* 0x5053424b = "PSBK" or Power Secure Boot Keystore */
+#define SECBOOT_MAGIC_NUMBER 0x5053424b
+#define SECBOOT_VERSION 1
+
+#define SECBOOT_TPMNV_VARS_INDEX 0x01c10190
+#define SECBOOT_TPMNV_CONTROL_INDEX 0x01c10191
+
+struct secboot_header {
+ uint32_t magic_number;
+ uint8_t version;
+ uint8_t reserved[3]; /* Fix alignment */
+} __attribute__((packed));
+
+struct secboot {
+ struct secboot_header header;
+ char bank[SECBOOT_VARIABLE_BANK_NUM][SECBOOT_VARIABLE_BANK_SIZE];
+ char update[SECBOOT_UPDATE_BANK_SIZE];
+} __attribute__((packed));
+
+struct tpmnv_vars {
+ struct secboot_header header;
+ char vars[0];
+} __attribute__((packed));
+
+struct tpmnv_control {
+ struct secboot_header header;
+ uint8_t active_bit;
+ char bank_hash[SECBOOT_VARIABLE_BANK_NUM][SHA256_DIGEST_LENGTH];
+} __attribute__((packed));
+
+struct tpmnv_ops_s {
+ int (*read)(TPMI_RH_NV_INDEX nv, void*, size_t, uint16_t);
+ int (*write)(TPMI_RH_NV_INDEX nv, void*, size_t, uint16_t);
+ int (*writelock)(TPMI_RH_NV_INDEX);
+ int (*definespace)(TPMI_RH_NV_INDEX, uint16_t);
+ int (*getindices)(TPMI_RH_NV_INDEX**, size_t*);
+ int (*undefinespace)(TPMI_RH_NV_INDEX);
+ int (*readpublic)(TPMI_RH_NV_INDEX, TPMS_NV_PUBLIC*, TPM2B_NAME*);
+};
+
+extern struct tpmnv_ops_s tpmnv_ops;
+
+extern const uint8_t tpmnv_vars_name[];
+extern const uint8_t tpmnv_control_name[];
+
+#endif
diff --git a/libstb/secvar/storage/tpmnv_ops.c b/libstb/secvar/storage/tpmnv_ops.c
new file mode 100644
index 0000000..d6135c3
--- /dev/null
+++ b/libstb/secvar/storage/tpmnv_ops.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2020 IBM Corp. */
+#include <tssskiboot.h>
+#include "secboot_tpm.h"
+
+struct tpmnv_ops_s tpmnv_ops = {
+ .read = tss_nv_read,
+ .write = tss_nv_write,
+ .writelock = tss_nv_write_lock,
+ .definespace = tss_nv_define_space,
+ .getindices = tss_get_defined_nv_indices,
+ .undefinespace = tss_nv_undefine_space,
+ .readpublic = tss_nv_read_public,
+};
+