vboot: Add firmware management parameters

This adds RW firmware support for the optional firmware management
parameters TPM space.

System-level tests require CL:339262 to add cryptohome support.

BUG=chromium:601492
BRANCH=baytrail and newer platforms
TEST=make -j runtests
    Or better, COV=1 make, and then make sure all new code is covered.

    Additional manual tests.  MUST use a test image for these, because a
    test image has a root shell even with dev mode disabled:

    Set FWMP:
    crossystem clear_tpm_owner_request=1
    reboot
    cryptohome --action=tpm_take_ownership
    cryptohome --action=tpm_wait_ownership
    cryptohome --action=set_firmware_management_parameters --flags=1
    cryptohome --action=get_firmware_management_parameters

    Reboot system with power+refresh+esc
    Use Ctrl+D then Enter to enable dev mode.
    Goes to the TONORM screen.  
    Pressing Esc doesn't exit it.
    Pressing Enter turns dev mode off.
    Then let it boot.

    Just to make sure FWMP did get set persistently:
    cryptohome --action=get_firmware_management_parameters

    Now remove the FWMP
    crossystem clear_tpm_owner_request=1
    reboot
    cryptohome --action=tpm_take_ownership
    cryptohome --action=tpm_wait_ownership
    cryptohome --action=remove_firmware_management_parameters

    Reboot system with power+refresh+esc
    Use Ctrl+D then Enter to enable dev mode.
    Goes to the DEV screen.

Change-Id: I0ac31bb2c64671ee9c3c810174baaf02b4cce641
Original-Change-Id: Ifaf644c80809552d5961615be6017c2a332a034b
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/339234
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/356790
Reviewed-by: Darren Krahn <dkrahn@chromium.org>
Reviewed-by: Stefan Reinauer <reinauer@google.com>
diff --git a/firmware/include/gbb_header.h b/firmware/include/gbb_header.h
index 86aa12a..b600f12 100644
--- a/firmware/include/gbb_header.h
+++ b/firmware/include/gbb_header.h
@@ -62,6 +62,8 @@
 #define GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC 0x00000200
 /* Default to booting legacy OS when dev screen times out */
 #define GBB_FLAG_DEFAULT_DEV_BOOT_LEGACY  0x00000400
+/* Disable using FWMP */
+#define GBB_FLAG_DISABLE_FWMP                           0x00008000
 
 #ifdef __cplusplus
 extern "C" {
diff --git a/firmware/include/tss_constants.h b/firmware/include/tss_constants.h
index 883a5ad..b2ddd7d 100644
--- a/firmware/include/tss_constants.h
+++ b/firmware/include/tss_constants.h
@@ -40,6 +40,8 @@
 #define TPM_E_WRITE_FAILURE          ((uint32_t)0x00005008)  /* vboot local */
 #define TPM_E_READ_EMPTY             ((uint32_t)0x00005009)  /* vboot local */
 #define TPM_E_READ_FAILURE           ((uint32_t)0x0000500a)  /* vboot local */
+#define TPM_E_STRUCT_SIZE            ((uint32_t) 0x0000500b)  /* vboot local */
+#define TPM_E_STRUCT_VERSION         ((uint32_t) 0x0000500c)  /* vboot local */
 
 #define TPM_NV_INDEX0 ((uint32_t)0x00000000)
 #define TPM_NV_INDEX_LOCK ((uint32_t)0xffffffff)
diff --git a/firmware/include/vboot_api.h b/firmware/include/vboot_api.h
index 0ae6c76..f3cc913 100644
--- a/firmware/include/vboot_api.h
+++ b/firmware/include/vboot_api.h
@@ -112,6 +112,8 @@
 	VBERROR_UNSUPPORTED_REGION            = 0x10025,
 	/* No image present (returned from VbGbbReadImage() for missing image) */
 	VBERROR_NO_IMAGE_PRESENT              = 0x10026,
+	/* Error reading FWMP from TPM (note: not present is not an error) */
+	VBERROR_TPM_READ_FWMP                 = 0x10029,
 
 	/* VbExEcGetExpectedRWHash() may return the following codes */
 	/* Compute expected RW hash from the EC image; BIOS doesn't have it */
diff --git a/firmware/lib/include/load_kernel_fw.h b/firmware/lib/include/load_kernel_fw.h
index a710ee5..1e258a2 100644
--- a/firmware/lib/include/load_kernel_fw.h
+++ b/firmware/lib/include/load_kernel_fw.h
@@ -20,6 +20,8 @@
 /* In recovery mode */
 #define BOOT_FLAG_RECOVERY  (0x02ULL)
 
+struct RollbackSpaceFwmp;
+
 typedef struct LoadKernelParams {
 	/* Inputs to LoadKernel() */
 	/*
@@ -53,6 +55,8 @@
 	 * VbNvSetup() and VbNvTeardown() on the context.
 	 */
 	VbNvContext *nv_context;
+	/* Firmware management parameters; may be NULL if not present. */
+	const struct RollbackSpaceFwmp *fwmp;
 
 	/*
 	 * Outputs from LoadKernel(); valid only if LoadKernel() returns
diff --git a/firmware/lib/include/rollback_index.h b/firmware/lib/include/rollback_index.h
index dd0de32..6cc9ee6 100644
--- a/firmware/lib/include/rollback_index.h
+++ b/firmware/lib/include/rollback_index.h
@@ -18,7 +18,8 @@
 /* This is just an opaque space for backup purposes */
 #define BACKUP_NV_INDEX                 0x1009
 #define BACKUP_NV_SIZE 16
-
+#define FWMP_NV_INDEX			0x100a
+#define FWMP_NV_MAX_SIZE 128
 
 /* Structure definitions for TPM spaces */
 
@@ -70,6 +71,34 @@
 	uint8_t crc8;
 } __attribute__((packed)) RollbackSpaceFirmware;
 
+#define FWMP_HASH_SIZE 32 /* Enough for SHA-256 */
+
+/* Firmware management parameters */
+struct RollbackSpaceFwmp {
+	/* CRC-8 of fields following struct_size */
+	uint8_t crc;
+	/* Structure size in bytes */
+	uint8_t struct_size;
+	/* Structure version */
+	uint8_t struct_version;
+	/* Reserved; ignored by current reader */
+	uint8_t reserved0;
+	/* Flags; see enum fwmp_flags */
+	uint32_t flags;
+	/* Hash of developer kernel key */
+	uint8_t dev_key_hash[FWMP_HASH_SIZE];
+} __attribute__((packed));
+
+#define ROLLBACK_SPACE_FWMP_VERSION 0x10  /* 1.0 */
+
+enum fwmp_flags {
+	FWMP_DEV_DISABLE_BOOT		= (1 << 0),
+	FWMP_DEV_DISABLE_RECOVERY	= (1 << 1),
+	FWMP_DEV_ENABLE_USB		= (1 << 2),
+	FWMP_DEV_ENABLE_LEGACY		= (1 << 3),
+	FWMP_DEV_ENABLE_OFFICIAL_ONLY	= (1 << 4),
+	FWMP_DEV_USE_KEY_HASH		= (1 << 5),
+};
 
 /* All functions return TPM_SUCCESS (zero) if successful, non-zero if error */
 
@@ -134,6 +163,15 @@
  */
 uint32_t RollbackKernelLock(int recovery_mode);
 
+/**
+ * Read and validate firmware management parameters.
+ *
+ * Absence of a FWMP is not an error; in this case, fwmp will be cleared.
+ *
+ * Returns non-zero if error.
+ */
+uint32_t RollbackFwmpRead(struct RollbackSpaceFwmp *fwmp);
+
 /****************************************************************************/
 
 /*
diff --git a/firmware/lib/rollback_index.c b/firmware/lib/rollback_index.c
index 306e903..8aec162 100644
--- a/firmware/lib/rollback_index.c
+++ b/firmware/lib/rollback_index.c
@@ -481,7 +481,6 @@
 	return TPM_SUCCESS;
 }
 
-
 #ifdef DISABLE_ROLLBACK_TPM
 /* Dummy implementations which don't support TPM rollback protection */
 
@@ -674,3 +673,73 @@
 }
 
 #endif /* DISABLE_ROLLBACK_TPM */
+
+uint32_t RollbackFwmpRead(struct RollbackSpaceFwmp *fwmp)
+{
+	uint8_t buf[FWMP_NV_MAX_SIZE];
+	struct RollbackSpaceFwmp *bf = (struct RollbackSpaceFwmp *)buf;
+	uint32_t r;
+	int attempts = 3;
+
+	/* Clear destination in case error or FWMP not present */
+	Memset(fwmp, 0, sizeof(*fwmp));
+
+	while (attempts--) {
+		/* Try to read entire 1.0 struct */
+		r = TlclRead(FWMP_NV_INDEX, buf, sizeof(*bf));
+		if (r == TPM_E_BADINDEX) {
+			/* Missing space is not an error; use defaults */
+			VBDEBUG(("TPM: %s() - no FWMP space\n", __func__));
+			return TPM_SUCCESS;
+		} else if (r != TPM_SUCCESS) {
+			VBDEBUG(("TPM: %s() - read returned 0x%x\n",
+				 __func__, r));
+			return r;
+		}
+
+		/*
+		 * Struct must be at least big enough for 1.0, but not bigger
+		 * than our buffer size.
+		 */
+		if (bf->struct_size < sizeof(*bf) ||
+		    bf->struct_size > sizeof(buf))
+			return TPM_E_STRUCT_SIZE;
+
+		/*
+		 * If space is bigger than we expect, re-read so we properly
+		 * compute the CRC.
+		 */
+		if (bf->struct_size > sizeof(*bf)) {
+			r = TlclRead(FWMP_NV_INDEX, buf, bf->struct_size);
+			if (r != TPM_SUCCESS)
+				return r;
+		}
+
+		/* Verify CRC */
+		if (bf->crc != Crc8(buf + 2, bf->struct_size - 2)) {
+			VBDEBUG(("TPM: %s() - bad CRC\n", __func__));
+			continue;
+		}
+
+		/* Verify major version is compatible */
+		if ((bf->struct_version >> 4) !=
+		    (ROLLBACK_SPACE_FWMP_VERSION >> 4))
+			return TPM_E_STRUCT_VERSION;
+
+		/*
+		 * Copy to destination.  Note that if the space is bigger than
+		 * we expect (due to a minor version change), we only copy the
+		 * part of the FWMP that we know what to do with.
+		 *
+		 * If this were a 1.1+ reader and the source was a 1.0 struct,
+		 * we would need to take care of initializing the extra fields
+		 * added in 1.1+.  But that's not an issue yet.
+		 */
+		Memcpy(fwmp, bf, sizeof(*fwmp));
+		return TPM_SUCCESS;
+	}
+
+	VBDEBUG(("TPM: %s() - too many bad CRCs, giving up\n", __func__));
+	return TPM_E_CORRUPTED_STATE;
+}
+
diff --git a/firmware/lib/vboot_api_kernel.c b/firmware/lib/vboot_api_kernel.c
index 18d00ae..1a7cbe7 100644
--- a/firmware/lib/vboot_api_kernel.c
+++ b/firmware/lib/vboot_api_kernel.c
@@ -22,14 +22,20 @@
 
 /* Global variables */
 static VbNvContext vnc;
+static struct RollbackSpaceFwmp fwmp;
 
 #ifdef CHROMEOS_ENVIRONMENT
-/* Global variable accessor for unit tests */
+/* Global variable accessors for unit tests */
 
 VbNvContext *VbApiKernelGetVnc(void)
 {
 	return &vnc;
 }
+
+struct RollbackSpaceFwmp *VbApiKernelGetFwmp(void)
+{
+	return &fwmp;
+}
 #endif
 
 /**
@@ -173,12 +179,18 @@
 	return VbTryLoadKernel(cparams, p, VB_DISK_FLAG_FIXED);
 }
 
+static const char dev_disable_msg[] =
+	"Developer mode is disabled on this device by system policy.\n"
+	"For more information, see http://dev.chromium.org/chromium-os/fwmp\n"
+	"\n";
+
 VbError_t VbBootDeveloper(VbCommonParams *cparams, LoadKernelParams *p)
 {
 	GoogleBinaryBlockHeader *gbb = cparams->gbb;
 	VbSharedDataHeader *shared =
 		(VbSharedDataHeader *)cparams->shared_data_blob;
 	uint32_t allow_usb = 0, allow_legacy = 0, ctrl_d_pressed = 0;
+	uint32_t disable_dev_boot = 0;
 	VbAudioContext *audio = 0;
 
 	VBDEBUG(("Entering %s()\n", __func__));
@@ -193,6 +205,46 @@
 	if (gbb->flags & GBB_FLAG_FORCE_DEV_BOOT_LEGACY)
 		allow_legacy = 1;
 
+	/* Handle FWMP override */
+	if (fwmp.flags & FWMP_DEV_ENABLE_USB)
+		allow_usb = 1;
+	if (fwmp.flags & FWMP_DEV_ENABLE_LEGACY)
+		allow_legacy = 1;
+	if (fwmp.flags & FWMP_DEV_DISABLE_BOOT) {
+		if (gbb->flags & GBB_FLAG_FORCE_DEV_SWITCH_ON) {
+			VBDEBUG(("%s() - FWMP_DEV_DISABLE_BOOT rejected by "
+				 "FORCE_DEV_SWITCH_ON\n",
+				 __func__));
+		} else {
+			disable_dev_boot = 1;
+		}
+	}
+
+	/* If dev mode is disabled, only allow TONORM */
+	while (disable_dev_boot) {
+		VBDEBUG(("%s() - dev_disable_boot is set.\n", __func__));
+		VbDisplayScreen(cparams, VB_SCREEN_DEVELOPER_TO_NORM, 0, &vnc);
+		VbExDisplayDebugInfo(dev_disable_msg);
+
+		/* Ignore space in VbUserConfirms()... */
+		switch (VbUserConfirms(cparams, 0)) {
+		case 1:
+			VBDEBUG(("%s() - leaving dev-mode.\n", __func__));
+			VbNvSet(&vnc, VBNV_DISABLE_DEV_REQUEST, 1);
+			VbDisplayScreen(cparams,
+					VB_SCREEN_TO_NORM_CONFIRMED,
+					0, &vnc);
+			VbExSleepMs(5000);
+			return VBERROR_TPM_REBOOT_REQUIRED;
+		case -1:
+			VBDEBUG(("%s() - shutdown requested\n", __func__));
+			return VBERROR_SHUTDOWN_REQUESTED;
+		default:
+			/* Ignore user attempt to cancel */
+			VBDEBUG(("%s() - ignore cancel TONORM\n", __func__));
+		}
+	}
+
 	/* Show the dev mode warning screen */
 	VbDisplayScreen(cparams, VB_SCREEN_DEVELOPER_WARNING, 0, &vnc);
 
@@ -907,12 +959,29 @@
 	}
 	shared->kernel_version_tpm_start = shared->kernel_version_tpm;
 
+	/* Read FWMP.  Ignore errors in recovery mode. */
+	if (cparams->gbb->flags & GBB_FLAG_DISABLE_FWMP) {
+		Memset(&fwmp, 0, sizeof(fwmp));
+		tpm_status = 0;
+	} else {
+		tpm_status = RollbackFwmpRead(&fwmp);
+	}
+	if (0 != tpm_status) {
+		VBDEBUG(("Unable to get FWMP from TPM\n"));
+		if (!shared->recovery_reason) {
+			VbSetRecoveryRequest(VBNV_RECOVERY_RW_TPM_R_ERROR);
+			retval = VBERROR_TPM_READ_FWMP;
+			goto VbSelectAndLoadKernel_exit;
+		}
+	}
+
 	/* Fill in params for calls to LoadKernel() */
 	Memset(&p, 0, sizeof(p));
 	p.shared_data_blob = cparams->shared_data_blob;
 	p.shared_data_size = cparams->shared_data_size;
 	p.gbb_data = cparams->gbb_data;
 	p.gbb_size = cparams->gbb_size;
+	p.fwmp = &fwmp;
 
 	/*
 	 * This could be set to NULL, in which case the vboot header
diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c
index 72cd2cc..10e2ccb 100644
--- a/firmware/lib/vboot_kernel.c
+++ b/firmware/lib/vboot_kernel.c
@@ -14,6 +14,7 @@
 #include "gbb_access.h"
 #include "gbb_header.h"
 #include "load_kernel_fw.h"
+#include "rollback_index.h"
 #include "utility.h"
 #include "vboot_api.h"
 #include "vboot_common.h"
@@ -206,6 +207,10 @@
 	} else if (dev_switch) {
 		boot_mode = kBootDev;
 		VbNvGet(vnc, VBNV_DEV_BOOT_SIGNED_ONLY, &require_official_os);
+
+		if (params->fwmp &&
+		    (params->fwmp->flags & FWMP_DEV_ENABLE_OFFICIAL_ONLY))
+			require_official_os = 1;
 	} else {
 		boot_mode = kBootNormal;
 	}
@@ -395,6 +400,39 @@
 			goto bad_kernel;
 		}
 
+
+		/* If in developer mode and using key hash, check it */
+		if ((kBootDev == boot_mode) &&
+		    params->fwmp &&
+		    (params->fwmp->flags & FWMP_DEV_USE_KEY_HASH)) {
+			VbPublicKey *key = &key_block->data_key;
+			uint8_t *buf = ((uint8_t *)key) + key->key_offset;
+			uint64_t buflen = key->key_size;
+			uint8_t *digest;
+
+			VBDEBUG(("Checking developer key hash.\n"));
+			digest = DigestBuf(buf, buflen,
+					   SHA256_DIGEST_ALGORITHM);
+			if (0 != SafeMemcmp(digest, params->fwmp->dev_key_hash,
+					    SHA256_DIGEST_SIZE)) {
+				int i;
+
+				VBDEBUG(("Wrong developer key hash.\n"));
+				VBDEBUG(("Want: "));
+				for (i = 0; i < SHA256_DIGEST_SIZE; i++)
+					VBDEBUG(("%02x",
+						 params->fwmp->dev_key_hash[i]));
+				VBDEBUG(("\nGot:  "));
+				for (i = 0; i < SHA256_DIGEST_SIZE; i++)
+					VBDEBUG(("%02x", digest[i]));
+				VBDEBUG(("\n"));
+
+				VbExFree(digest);
+				goto bad_kernel;
+			}
+			VbExFree(digest);
+		}
+
 		/* Get key for preamble/data verification from the key block. */
 		data_key = PublicKeyToRSA(&key_block->data_key);
 		if (!data_key) {
diff --git a/tests/rollback_index2_tests.c b/tests/rollback_index2_tests.c
index 853bfc8..fb2f2e4 100644
--- a/tests/rollback_index2_tests.c
+++ b/tests/rollback_index2_tests.c
@@ -53,8 +53,21 @@
 static TPM_PERMANENT_FLAGS mock_pflags;
 static RollbackSpaceFirmware mock_rsf;
 static RollbackSpaceKernel mock_rsk;
+
+static union {
+	struct RollbackSpaceFwmp fwmp;
+	uint8_t buf[FWMP_NV_MAX_SIZE];
+} mock_fwmp;
+
 static uint32_t mock_permissions;
 
+/* Recalculate CRC of FWMP data */
+static void RecalcFwmpCrc(void)
+{
+	mock_fwmp.fwmp.crc = Crc8(mock_fwmp.buf + 2,
+				  mock_fwmp.fwmp.struct_size - 2);
+}
+
 /* Reset the variables for the Tlcl mock functions. */
 static void ResetMocks(int fail_on_call, uint32_t fail_with_err)
 {
@@ -70,6 +83,15 @@
 	Memset(&mock_rsf, 0, sizeof(mock_rsf));
 	Memset(&mock_rsk, 0, sizeof(mock_rsk));
 	mock_permissions = 0;
+
+	Memset(mock_fwmp.buf, 0, sizeof(mock_fwmp.buf));
+	mock_fwmp.fwmp.struct_size = sizeof(mock_fwmp.fwmp);
+	mock_fwmp.fwmp.struct_version = ROLLBACK_SPACE_FWMP_VERSION;
+	mock_fwmp.fwmp.flags = 0x1234;
+	/* Put some data in the hash */
+	mock_fwmp.fwmp.dev_key_hash[0] = 0xaa;
+	mock_fwmp.fwmp.dev_key_hash[FWMP_HASH_SIZE - 1] = 0xbb;
+	RecalcFwmpCrc();
 }
 
 /****************************************************************************/
@@ -135,6 +157,12 @@
 		TEST_EQ(length, sizeof(mock_rsk), "TlclRead rsk size");
 		Memcpy(data, &mock_rsk, length);
 		MaybeInjectNoise(data, length);
+	} else if (FWMP_NV_INDEX == index) {
+		Memset(data, 0, length);
+		if (length > sizeof(mock_fwmp))
+			length = sizeof(mock_fwmp);
+		Memcpy(data, &mock_fwmp, length);
+		MaybeInjectNoise(data, length);
 	} else {
 		Memset(data, 0, length);
 	}
@@ -962,6 +990,7 @@
 	TEST_STR_EQ(mock_calls, "", "no tlcl calls");
 }
 
+/****************************************************************************/
 /* Tests for RollbackS3Resume() */
 static void RollbackS3ResumeTest(void)
 {
@@ -982,6 +1011,99 @@
 		"RollbackS3Resume() other error");
 }
 
+/****************************************************************************/
+/* Tests for RollbackFwmpRead() calls */
+
+static void RollbackFwmpTest(void)
+{
+	struct RollbackSpaceFwmp fwmp;
+	struct RollbackSpaceFwmp fwmp_zero = {0};
+
+	/* Normal read */
+	ResetMocks(0, 0);
+	TEST_EQ(RollbackFwmpRead(&fwmp), 0, "RollbackFwmpRead()");
+	TEST_STR_EQ(mock_calls,
+		    "TlclRead(0x100a, 40)\n",
+		    "  tlcl calls");
+	TEST_EQ(0, Memcmp(&fwmp, &mock_fwmp, sizeof(fwmp)), "  data");
+
+	/* Read error */
+	ResetMocks(1, TPM_E_IOERROR);
+	TEST_EQ(RollbackFwmpRead(&fwmp), TPM_E_IOERROR,
+		"RollbackFwmpRead() error");
+	TEST_STR_EQ(mock_calls,
+		    "TlclRead(0x100a, 40)\n",
+		    "  tlcl calls");
+	TEST_EQ(0, Memcmp(&fwmp, &fwmp_zero, sizeof(fwmp)), "  data clear");
+
+	/* Not present isn't an error; just returns empty data */
+	ResetMocks(1, TPM_E_BADINDEX);
+	TEST_EQ(RollbackFwmpRead(&fwmp), 0, "RollbackFwmpRead() not present");
+	TEST_STR_EQ(mock_calls,
+		    "TlclRead(0x100a, 40)\n",
+		    "  tlcl calls");
+	TEST_EQ(0, Memcmp(&fwmp, &fwmp_zero, sizeof(fwmp)), "  data clear");
+
+	/* Struct size too small */
+	ResetMocks(0, 0);
+	mock_fwmp.fwmp.struct_size--;
+	TEST_EQ(RollbackFwmpRead(&fwmp), TPM_E_STRUCT_SIZE,
+		"RollbackFwmpRead() too small");
+
+	/* Struct size too large with good CRC */
+	ResetMocks(0, 0);
+	mock_fwmp.fwmp.struct_size += 4;
+	RecalcFwmpCrc();
+	TEST_EQ(RollbackFwmpRead(&fwmp), 0, "RollbackFwmpRead() bigger");
+	TEST_STR_EQ(mock_calls,
+		    "TlclRead(0x100a, 40)\n"
+		    "TlclRead(0x100a, 44)\n",
+		    "  tlcl calls");
+	TEST_EQ(0, Memcmp(&fwmp, &mock_fwmp, sizeof(fwmp)), "  data");
+
+	/* Bad CRC causes retry, then eventual failure */
+	ResetMocks(0, 0);
+	mock_fwmp.fwmp.crc++;
+	TEST_EQ(RollbackFwmpRead(&fwmp), TPM_E_CORRUPTED_STATE,
+		"RollbackFwmpRead() crc");
+	TEST_STR_EQ(mock_calls,
+		    "TlclRead(0x100a, 40)\n"
+		    "TlclRead(0x100a, 40)\n"
+		    "TlclRead(0x100a, 40)\n",
+		    "  tlcl calls");
+
+	/* Struct size too large with bad CRC */
+	ResetMocks(0, 0);
+	mock_fwmp.fwmp.struct_size += 4;
+	RecalcFwmpCrc();
+	mock_fwmp.fwmp.crc++;
+	TEST_EQ(RollbackFwmpRead(&fwmp), TPM_E_CORRUPTED_STATE,
+		"RollbackFwmpRead() bigger crc");
+	TEST_STR_EQ(mock_calls,
+		    "TlclRead(0x100a, 40)\n"
+		    "TlclRead(0x100a, 44)\n"
+		    "TlclRead(0x100a, 40)\n"
+		    "TlclRead(0x100a, 44)\n"
+		    "TlclRead(0x100a, 40)\n"
+		    "TlclRead(0x100a, 44)\n",
+		    "  tlcl calls");
+	TEST_EQ(0, Memcmp(&fwmp, &fwmp_zero, sizeof(fwmp)), "  data");
+
+	/* Minor version difference ok */
+	ResetMocks(0, 0);
+	mock_fwmp.fwmp.struct_version++;
+	RecalcFwmpCrc();
+	TEST_EQ(RollbackFwmpRead(&fwmp), 0, "RollbackFwmpRead() minor version");
+	TEST_EQ(0, Memcmp(&fwmp, &mock_fwmp, sizeof(fwmp)), "  data");
+
+	/* Major version difference not ok */
+	ResetMocks(0, 0);
+	mock_fwmp.fwmp.struct_version += 0x10;
+	RecalcFwmpCrc();
+	TEST_EQ(RollbackFwmpRead(&fwmp), TPM_E_STRUCT_VERSION,
+		"RollbackFwmpRead() major version");
+}
+
 int main(int argc, char* argv[])
 {
 	CrcTestFirmware();
@@ -992,6 +1114,7 @@
 	RollbackFirmwareTest();
 	RollbackKernelTest();
 	RollbackS3ResumeTest();
+	RollbackFwmpTest();
 
 	return gTestSuccess ? 0 : 255;
 }
diff --git a/tests/vboot_api_kernel2_tests.c b/tests/vboot_api_kernel2_tests.c
index 21ea306..44fdec5 100644
--- a/tests/vboot_api_kernel2_tests.c
+++ b/tests/vboot_api_kernel2_tests.c
@@ -42,6 +42,8 @@
 static uint32_t mock_num_disks[8];
 static uint32_t mock_num_disks_count;
 
+extern struct RollbackSpaceFwmp *VbApiKernelGetFwmp(void);
+
 /* Reset mock data (for use before each test) */
 static void ResetMocks(void)
 {
@@ -64,6 +66,8 @@
 	VbNvSetup(VbApiKernelGetVnc());
 	VbNvTeardown(VbApiKernelGetVnc()); /* So CRC gets generated */
 
+	Memset(VbApiKernelGetFwmp(), 0, sizeof(struct RollbackSpaceFwmp));
+
 	Memset(&shared_data, 0, sizeof(shared_data));
 	VbSharedDataInit(shared, sizeof(shared_data));
 
@@ -348,6 +352,12 @@
 	TEST_EQ(VbBootDeveloper(&cparams, &lkp), 1002, "Ctrl+L nv legacy");
 	TEST_EQ(vbexlegacy_called, 1, "  try legacy");
 
+	ResetMocks();
+	VbApiKernelGetFwmp()->flags |= FWMP_DEV_ENABLE_LEGACY;
+	mock_keypress[0] = 0x0c;
+	TEST_EQ(VbBootDeveloper(&cparams, &lkp), 1002, "Ctrl+L fwmp legacy");
+	TEST_EQ(vbexlegacy_called, 1, "  fwmp legacy");
+
 	/* Ctrl+U boots USB only if enabled */
 	ResetMocks();
 	mock_keypress[0] = 0x15;
@@ -367,6 +377,13 @@
 	vbtlk_retval = VBERROR_SUCCESS - VB_DISK_FLAG_REMOVABLE;
 	TEST_EQ(VbBootDeveloper(&cparams, &lkp), 0, "Ctrl+U force USB");
 
+	/* Ctrl+U enabled via FWMP */
+	ResetMocks();
+	VbApiKernelGetFwmp()->flags |= FWMP_DEV_ENABLE_USB;
+	mock_keypress[0] = 0x15;
+	vbtlk_retval = VBERROR_SUCCESS - VB_DISK_FLAG_REMOVABLE;
+	TEST_EQ(VbBootDeveloper(&cparams, &lkp), 0, "Ctrl+U force USB");
+
 	/* If no USB, eventually times out and tries fixed disk */
 	ResetMocks();
 	VbNvSet(VbApiKernelGetVnc(), VBNV_DEV_BOOT_USB, 1);
@@ -377,6 +394,32 @@
 	TEST_EQ(u, 0, "  recovery reason");
 	TEST_EQ(audio_looping_calls_left, 0, "  used up audio");
 
+	/* If dev mode is disabled, goes to TONORM screen repeatedly */
+	ResetMocks();
+	VbApiKernelGetFwmp()->flags |= FWMP_DEV_DISABLE_BOOT;
+	mock_keypress[0] = '\x1b';  /* Just causes TONORM again */
+	mock_keypress[1] = '\r';
+	TEST_EQ(VbBootDeveloper(&cparams, &lkp), VBERROR_TPM_REBOOT_REQUIRED,
+		"FWMP dev disabled");
+	TEST_EQ(screens_displayed[0], VB_SCREEN_DEVELOPER_TO_NORM,
+		"  tonorm screen");
+	TEST_EQ(screens_displayed[1], VB_SCREEN_DEVELOPER_TO_NORM,
+		"  tonorm screen");
+	TEST_EQ(screens_displayed[2], VB_SCREEN_TO_NORM_CONFIRMED,
+		"  confirm screen");
+	VbNvGet(VbApiKernelGetVnc(), VBNV_DISABLE_DEV_REQUEST, &u);
+	TEST_EQ(u, 1, "  disable dev request");
+
+	/* Shutdown requested when dev disabled */
+	ResetMocks();
+	shared->flags = VBSD_HONOR_VIRT_DEV_SWITCH | VBSD_BOOT_DEV_SWITCH_ON;
+	VbApiKernelGetFwmp()->flags |= FWMP_DEV_DISABLE_BOOT;
+	shutdown_request_calls_left = 1;
+	TEST_EQ(VbBootDeveloper(&cparams, &lkp), VBERROR_SHUTDOWN_REQUESTED,
+		"Shutdown requested when dev disabled");
+	TEST_EQ(screens_displayed[0], VB_SCREEN_DEVELOPER_TO_NORM,
+		"  tonorm screen");
+
 	printf("...done.\n");
 }
 
diff --git a/tests/vboot_api_kernel4_tests.c b/tests/vboot_api_kernel4_tests.c
index bb82702..27c4ef7 100644
--- a/tests/vboot_api_kernel4_tests.c
+++ b/tests/vboot_api_kernel4_tests.c
@@ -31,7 +31,8 @@
 static int ecsync_retval;
 static uint32_t rkr_version;
 static uint32_t new_version;
-static int rkr_retval, rkw_retval, rkl_retval;
+static struct RollbackSpaceFwmp rfr_fwmp;
+static int rkr_retval, rkw_retval, rkl_retval, rfr_retval;
 static VbError_t vbboot_retval;
 
 /* Reset mock data (for use before each test) */
@@ -57,6 +58,9 @@
 	Memset(&shared_data, 0, sizeof(shared_data));
 	VbSharedDataInit(shared, sizeof(shared_data));
 
+	Memset(&rfr_fwmp, 0, sizeof(rfr_fwmp));
+	rfr_retval = TPM_SUCCESS;
+
 	ecsync_retval = VBERROR_SUCCESS;
 	rkr_version = new_version = 0x10002;
 	rkr_retval = rkw_retval = rkl_retval = VBERROR_SUCCESS;
@@ -100,6 +104,12 @@
 	return rkl_retval;
 }
 
+uint32_t RollbackFwmpRead(struct RollbackSpaceFwmp *fwmp)
+{
+	Memcpy(fwmp, &rfr_fwmp, sizeof(*fwmp));
+	return rfr_retval;
+}
+
 VbError_t VbBootNormal(VbCommonParams *cparams, LoadKernelParams *p)
 {
 	shared->kernel_version_tpm = new_version;
diff --git a/tests/vboot_kernel_tests.c b/tests/vboot_kernel_tests.c
index ee164ce..9f13f3c 100644
--- a/tests/vboot_kernel_tests.c
+++ b/tests/vboot_kernel_tests.c
@@ -11,10 +11,12 @@
 #include <string.h>
 
 #include "cgptlib.h"
+#include "cryptolib.h"
 #include "gbb_header.h"
 #include "gpt.h"
 #include "host_common.h"
 #include "load_kernel_fw.h"
+#include "rollback_index.h"
 #include "test_common.h"
 #include "vboot_api.h"
 #include "vboot_common.h"
@@ -57,6 +59,8 @@
 static VbKeyBlockHeader kbh;
 static VbKernelPreambleHeader kph;
 static VbCommonParams cparams;
+static struct RollbackSpaceFwmp fwmp;
+static uint8_t mock_digest[SHA256_DIGEST_SIZE] = {12, 34, 56, 78};
 
 static void ResetCallLog(void)
 {
@@ -121,6 +125,9 @@
 	kph.bootloader_address = 0xbeadd008;
 	kph.bootloader_size = 0x1234;
 
+	memset(&fwmp, 0, sizeof(fwmp));
+	memcpy(fwmp.dev_key_hash, mock_digest, sizeof(fwmp.dev_key_hash));
+
 	memset(mock_parts, 0, sizeof(mock_parts));
 	mock_parts[0].start = 100;
 	mock_parts[0].size = 150;  /* 75 KB */
@@ -229,6 +236,13 @@
 	return VBERROR_SUCCESS;
 }
 
+uint8_t* DigestBuf(const uint8_t* buf, uint64_t len, int sig_algorithm)
+{
+	uint8_t *d = VbExMalloc(sizeof(mock_digest));
+
+	memcpy(d, mock_digest, sizeof(mock_digest));
+	return d;
+}
 
 /**
  * Test reading/writing GPT
@@ -442,6 +456,14 @@
 	TEST_EQ(LoadKernel(&lkp, &cparams), VBERROR_INVALID_KERNEL_FOUND,
 		"Fail key block dev sig");
 
+	ResetMocks();
+	lkp.boot_flags |= BOOT_FLAG_DEVELOPER;
+	lkp.fwmp = &fwmp;
+	fwmp.flags |= FWMP_DEV_ENABLE_OFFICIAL_ONLY;
+	key_block_verify_fail = 1;
+	TEST_EQ(LoadKernel(&lkp, &cparams), VBERROR_INVALID_KERNEL_FOUND,
+		"Fail key block dev sig fwmp");
+
 	/* Check key block flag mismatches */
 	ResetMocks();
 	kbh.key_block_flags =
@@ -527,6 +549,22 @@
 	lkp.boot_flags |= BOOT_FLAG_RECOVERY;
 	TEST_EQ(LoadKernel(&lkp, &cparams), 0, "Kernel version ignored in rec mode");
 
+	/* Check developer key hash - bad */
+	ResetMocks();
+	lkp.boot_flags |= BOOT_FLAG_DEVELOPER;
+	lkp.fwmp = &fwmp;
+	fwmp.flags |= FWMP_DEV_USE_KEY_HASH;
+	fwmp.dev_key_hash[0]++;
+	TEST_EQ(LoadKernel(&lkp, &cparams), VBERROR_INVALID_KERNEL_FOUND,
+		"Fail key block dev fwmp hash");
+
+	/* Check developer key hash - good */
+	ResetMocks();
+	lkp.boot_flags |= BOOT_FLAG_DEVELOPER;
+	lkp.fwmp = &fwmp;
+	fwmp.flags |= FWMP_DEV_USE_KEY_HASH;
+	TEST_EQ(LoadKernel(&lkp, &cparams), 0, "Good key block dev fwmp hash");
+
 	ResetMocks();
 	kph.preamble_size |= 0x07;
 	TEST_EQ(LoadKernel(&lkp, &cparams), VBERROR_INVALID_KERNEL_FOUND,