diff mbox series

[v3,09/15] secvar/storage: add secvar storage driver for pnor-based p9

Message ID 20200401003426.7198-10-erichte@linux.ibm.com
State Superseded
Headers show
Series Add initial secure variable storage and backend drivers | expand

Checks

Context Check Description
snowpatch_ozlabs/apply_patch warning Failed to apply on branch master (6826095796c91583f344950ff76ef8ccf6e756b5)
snowpatch_ozlabs/apply_patch fail Failed to apply to any branch

Commit Message

Eric Richter April 1, 2020, 12:34 a.m. UTC
This patch implements the platform specific logic for persisting the
secure variable storage banks across reboots via the SECBOOT PNOR
partition.

For POWER 9, all secure variables and updates are stored in the
in the SECBOOT PNOR partition. The partition is split into three
sections: two variable bank sections, and a section for storing
updates. The driver alternates writes between the two variable
sections, so that the final switch from one set of variables to
the next can be as atomic as possible by flipping an "active bit"
stored in TPM NV.

PNOR space provides no lock protection, so prior to writing the
variable bank, a sha256 hash is calculated and stored in TPM NV.
This hash is compared against the hash of the variables loaded from
PNOR to ensure consistency -- otherwise a failure is reported, no keys
are loaded (which should cause skiroot to refuse to boot if secure boot
support is enabled).

Signed-off-by: Eric Richter <erichte@linux.ibm.com>

---

V3:
 - introduced priority variables (which are stored in TPM NV)
 - checks if nv indices are defined rather than relying on an error code
 - now handles physical presence, redefines NV indices if asserted
    (which forces full reformat, resets secure state)
 - checks if NV indices are defined with the correct attributes
 - added .lock() hook
 - increased error verbosity
 - addressed comments and reviews from the V2 set

 include/secvar.h                    |   1 +
 libstb/secvar/secvar.h              |   1 +
 libstb/secvar/storage/Makefile.inc  |   5 +-
 libstb/secvar/storage/secboot_tpm.c | 625 ++++++++++++++++++++++++++++
 libstb/secvar/storage/secboot_tpm.h |  61 +++
 libstb/secvar/storage/tpmnv_ops.c   |  15 +
 6 files changed, 706 insertions(+), 2 deletions(-)
 create mode 100644 libstb/secvar/storage/secboot_tpm.c
 create mode 100644 libstb/secvar/storage/secboot_tpm.h
 create mode 100644 libstb/secvar/storage/tpmnv_ops.c
diff mbox series

Patch

diff --git a/include/secvar.h b/include/secvar.h
index 3759356e..9d1245b6 100644
--- a/include/secvar.h
+++ b/include/secvar.h
@@ -27,6 +27,7 @@  struct secvar_backend_driver {
 	const char *compatible;				     // String to use for compatible in secvar node
 };
 
+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 58fc1ac5..7cf9e9a0 100644
--- a/libstb/secvar/secvar.h
+++ b/libstb/secvar/secvar.h
@@ -25,6 +25,7 @@  struct secvar_node {
 
 #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_PRIORITY		0x4 // Hint for storage driver to prioritize writing this variable
 
 struct secvar {
 	uint64_t key_len;
diff --git a/libstb/secvar/storage/Makefile.inc b/libstb/secvar/storage/Makefile.inc
index b7a821ec..65685cea 100644
--- a/libstb/secvar/storage/Makefile.inc
+++ b/libstb/secvar/storage/Makefile.inc
@@ -1,11 +1,12 @@ 
 # SPDX-License-Identifier: Apache-2.0
 # -*-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 00000000..17963f98
--- /dev/null
+++ b/libstb/secvar/storage/secboot_tpm.c
@@ -0,0 +1,625 @@ 
+// 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 Part 1 Section 16, and Part 2 Section 13.5 of the TPM Specification
+ * for how this is calculated
+ */
+const uint8_t tpmnv_vars_name[] = {0x00, 0x0b, 0xe7, 0x60, 0x2c, 0xc9, 0x6b,
+0x56, 0xb7, 0x20, 0x0c, 0xbe, 0x27, 0xfc, 0x98, 0xdb, 0x21, 0xf4, 0xbe, 0x77,
+0x79, 0xb1, 0xb1, 0x61, 0x45, 0x7e, 0xc0, 0x19, 0x54, 0x79, 0x83, 0xd0, 0x2e,
+0x63};
+
+const uint8_t tpmnv_control_name[] = {0x00, 0x0b, 0x34, 0x9b, 0x02, 0xf2, 0xb9,
+0x23, 0x6c, 0xec, 0x1e, 0xdf, 0x53, 0xb9, 0x8d, 0x87, 0xd6, 0x74, 0x8d, 0x0e,
+0x97, 0x54, 0x1d, 0xa1, 0xd6, 0x20, 0x1e, 0xcc, 0x61, 0xd2, 0x75, 0x9e, 0x9a,
+0x47};
+
+
+/* Calculate a SHA256 hash over the supplied buffer */
+static int calc_bank_hash(char *target_hash, 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, as required by fresh NV indices */
+	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 loads work in the future */
+	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 = platform.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 priority variable using a tighter packing scheme
+ * Returns the advanced target pointer */
+static char *secboot_serialize_priority(char *target, struct secvar_node *node, char *end)
+{
+	if ((target + node->var->key_len + node->var->data_size + offsetof(struct secvar, key))	> end)
+		return NULL;
+
+	memcpy(target, &node->var->key_len, sizeof(node->var->key_len));
+	target += sizeof(node->var->key_len);
+	memcpy(target, &node->var->data_size, sizeof(node->var->data_size));
+	target += sizeof(node->var->data_size);
+	memcpy(target, node->var->key, node->var->key_len);
+	target += node->var->key_len;
+	memcpy(target, node->var->data, node->var->data_size);
+
+	return target;
+}
+
+
+/* Flattens a linked-list bank into a contiguous buffer for writing */
+static int secboot_serialize_bank(struct list_head *bank, char *target, size_t target_size, int flags)
+{
+	struct secvar_node *node;
+	char *tmp = target;
+	char *end = target + target_size;
+
+	if (!bank)
+		return OPAL_INTERNAL_ERROR;
+	if (!target)
+		return OPAL_INTERNAL_ERROR;
+
+	memset(target, 0x00, target_size);
+
+	list_for_each(bank, node, link) {
+		if (node->flags != flags)
+			continue;
+
+		/* Priority variable has a different packing scheme */
+		if (flags & SECVAR_FLAG_PRIORITY) {
+			target = secboot_serialize_priority(target, node, end);
+			if (!target)
+				return OPAL_EMPTY;
+			continue;
+		}
+
+		/* Bail early if we are out of storage space */
+		if ((target - tmp) + sizeof(struct secvar) + node->var->data_size > target_size) {
+			prlog(PR_ERR, "Ran out of PNOR space, giving up!\n");
+			return OPAL_EMPTY;
+		}
+
+		memcpy(target, node->var, sizeof(struct secvar) + node->var->data_size);
+
+		target += sizeof(struct secvar) + node->var->data_size;
+	}
+
+	return OPAL_SUCCESS;
+}
+
+/* Loads in a flattened list of variables from a buffer into a linked list */
+static int secboot_load_from_pnor(struct list_head *bank, char *source, size_t max_size)
+{
+	char *src;
+	struct secvar_node *tmp;
+	struct secvar *hdr;
+
+	src = source;
+
+	while (src < (source + max_size)) {
+		/* Load in the header first to get the size, and check if we are at the end
+		 * Banks are zeroized after each write, thus key_len == 0 indicates end of the list */
+		hdr = (struct secvar *) src;
+		if (hdr->key_len == 0) {
+			break;
+		} else if (hdr->key_len > SECVAR_MAX_KEY_LEN) {
+			prlog(PR_ERR, "Attempted to load a key larger than max, len = %llu\n", hdr->key_len);
+			return OPAL_INTERNAL_ERROR;
+		}
+
+		if (hdr->data_size > SECBOOT_TPM_MAX_VAR_SIZE) {
+			prlog(PR_ERR, "Attempted to load a data payload larger than max, "
+				      "size = %llu\n", hdr->data_size);
+			return OPAL_INTERNAL_ERROR;
+		}
+
+		tmp = alloc_secvar(hdr->data_size);
+		if (!tmp) {
+			prlog(PR_ERR, "Could not allocate memory for loading secvar from image\n");
+			return OPAL_NO_MEM;
+		}
+
+		memcpy(tmp->var, src, sizeof(struct secvar) + hdr->data_size);
+
+		list_add_tail(bank, &tmp->link);
+		src += sizeof(struct secvar) + hdr->data_size;
+	}
+
+	return OPAL_SUCCESS;
+}
+
+
+/* Helper for the variable-bank specific writing logic */
+static int secboot_tpm_write_variable_bank(struct list_head *bank)
+{
+	int rc;
+	uint64_t bit;
+
+	bit = CYCLE_BIT(tpmnv_control_image->active_bit);
+	rc = secboot_serialize_bank(bank, tpmnv_vars_image->vars, tpmnv_vars_size - sizeof(struct tpmnv_vars), SECVAR_FLAG_PRIORITY);
+	if (rc)
+		goto out;
+
+	rc = tpmnv_ops.write(SECBOOT_TPMNV_VARS_INDEX, tpmnv_vars_image, tpmnv_vars_size, 0);
+	if (rc)
+		goto out;
+
+	/* Calculate the bank hash, and write to TPM NV */
+	rc = secboot_serialize_bank(bank, secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE, 0);
+	if (rc)
+		goto out;
+
+	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 = platform.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 = platform.secboot_write(0, secboot_image, sizeof(struct secboot));
+		break;
+	default:
+		rc = OPAL_HARDWARE;
+	}
+
+	return rc;
+}
+
+/* Priority variables stored in TPMNV have to be packed tighter to make the most
+ * out of the small amount of space available */
+static int secboot_tpm_load_from_tpmnv(struct list_head *bank)
+{
+	struct secvar *hdr;
+	struct secvar_node *node;
+	char *cur;
+	char *end;
+
+	cur = tpmnv_vars_image->vars;
+	end = ((char *) tpmnv_vars_image) + tpmnv_vars_size;
+
+	while (cur < end) {
+		/* Ensure there is enough space to even check for another var header */
+		if ((end - cur) < offsetof(struct secvar, key))
+			break;
+
+		/* Temporary cast to check sizes in the header */
+		hdr = (struct secvar *) cur;
+
+		/* Check if we have a priority variable to load
+		 * Should be zeroes if nonexistent */
+		if ((hdr->key_len == 0) && (hdr->data_size == 0))
+			break;
+
+		/* Sanity check our potential priority variables */
+		if ((hdr->key_len > SECVAR_MAX_KEY_LEN)
+		     || (hdr->data_size > SECBOOT_TPM_MAX_VAR_SIZE)) {
+			prlog(PR_ERR, "TPM NV Priority variable has impossible sizes, probably internal bug. "
+				      "len = %llu, size = %llu\n", hdr->key_len, hdr->data_size);
+			return OPAL_INTERNAL_ERROR;
+		}
+
+		/* Advance cur over the two size values */
+		cur += sizeof(hdr->key_len);
+		cur += sizeof(hdr->data_size);
+
+		/* Ensure the expected key/data size doesn't exceed the remaining buffer */
+		if ((end - cur) < (hdr->data_size + hdr->key_len))
+			return OPAL_INTERNAL_ERROR;
+
+		node = alloc_secvar(hdr->data_size);
+		if (!node)
+			return OPAL_NO_MEM;
+
+		node->var->key_len = hdr->key_len;
+		node->var->data_size = hdr->data_size;
+		node->flags |= SECVAR_FLAG_PRIORITY;
+
+		memcpy(node->var->key, cur, hdr->key_len);
+		cur += hdr->key_len;
+		memcpy(node->var->data, cur, hdr->data_size);
+		cur += hdr->data_size;
+
+		list_add_tail(bank, &node->link);
+	}
+
+	return OPAL_SUCCESS;
+}
+
+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))
+		return OPAL_PERMISSION; /* Tampered pnor space detected, abandon ship */
+
+	rc = secboot_tpm_load_from_tpmnv(bank);
+	if (rc)
+		return rc;
+
+	return secboot_load_from_pnor(bank, secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE);
+}
+
+
+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_load_from_pnor(bank, secboot_image->update, SECBOOT_UPDATE_BANK_SIZE);
+	}
+
+	return OPAL_HARDWARE;
+}
+
+
+/* Ensure the NV indices were defined with the correct set of attributes */
+static int secboot_tpm_check_tpmnv_attrs(void)
+{
+	TPMS_NV_PUBLIC nv_public; /* Throwaway, we only want the name field */
+	TPM2B_NAME nv_vars_name;
+	TPM2B_NAME nv_control_name;
+	int rc;
+
+	rc = tpmnv_ops.readpublic(SECBOOT_TPMNV_VARS_INDEX, &nv_public, &nv_vars_name);
+	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, &nv_control_name);
+	if (rc) {
+		prlog(PR_ERR, "Failed to readpublic from the CONTROL index, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (memcmp(tpmnv_vars_name, nv_vars_name.t.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.t.name, sizeof(tpmnv_control_name))) {
+		prlog(PR_ERR, "CONTROL index not defined with the correct attributes\n");
+		return OPAL_RESOURCE;
+	}
+
+	return OPAL_SUCCESS;
+}
+
+
+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_store_init(void)
+{
+	int rc;
+	unsigned int secboot_size;
+
+	TPMI_RH_NV_INDEX *indices = NULL;
+	size_t count = 0;
+	bool control_defined = false;
+	bool vars_defined = false;
+	int i;
+
+	if (secboot_image)
+		return OPAL_SUCCESS;
+
+	if (!platform.secboot_info)
+		return OPAL_UNSUPPORTED;
+
+	prlog(PR_DEBUG, "Initializing for pnor+tpm based platform\n");
+
+	/* Initialize SECBOOT first, we may need to format this later */
+	rc = platform.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 = platform.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");
+
+		if (vars_defined) {
+			rc = tpmnv_ops.undefinespace(SECBOOT_TPMNV_VARS_INDEX);
+			if (rc) {
+				prlog(PR_ERR, "Physical presence failed to undefine VARS, something is seriously wrong\n");
+				goto error;
+			}
+		}
+
+		if (control_defined) {
+			rc = tpmnv_ops.undefinespace(SECBOOT_TPMNV_CONTROL_INDEX);
+			if (rc) {
+				prlog(PR_ERR, "Physical presence failed to undefine CONTROL, something is seriously wrong\n");
+				goto error;
+			}
+		}
+
+		vars_defined = control_defined = false;
+	}
+
+	/* 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;
+
+		/* Indicies got defined and formatted, we're done here */
+		goto done;
+	} else 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;
+	}
+
+	/* Ensure the NV indices were defined with the correct set of attributes */
+	rc = secboot_tpm_check_tpmnv_attrs();
+	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_lock(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,
+	.lock = secboot_tpm_lock,
+	.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 00000000..30a747a7
--- /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 00000000..d6135c31
--- /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,
+};
+