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.

Change-Id: Ifaf644c80809552d5961615be6017c2a332a034b
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/339234
diff --git a/firmware/include/gbb_header.h b/firmware/include/gbb_header.h
index c7e2a05..0eef731 100644
--- a/firmware/include/gbb_header.h
+++ b/firmware/include/gbb_header.h
@@ -74,6 +74,9 @@
 #define GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP	0x00002000
 /* Enable serial console */
 #define GBB_FLAG_ENABLE_SERIAL				0x00004000
+/* 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 ed8ff9b..edfdc3c 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 5046d2a..1a16634 100644
--- a/firmware/include/vboot_api.h
+++ b/firmware/include/vboot_api.h
@@ -124,6 +124,8 @@
 	VBERROR_SCREEN_DRAW                   = 0x10027,
 	/* failed to jump to RW image */
 	VBERROR_RW_JUMP_FAILED                = 0x10028,
+	/* 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 da418a3..679da16 100644
--- a/firmware/lib/include/load_kernel_fw.h
+++ b/firmware/lib/include/load_kernel_fw.h
@@ -22,6 +22,8 @@
 /* GPT is external */
 #define BOOT_FLAG_EXTERNAL_GPT (0x04ULL)
 
+struct RollbackSpaceFwmp;
+
 typedef struct LoadKernelParams {
 	/* Inputs to LoadKernel() */
 	/*
@@ -57,6 +59,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 49b3a84..4436df5 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 */
 
@@ -680,3 +679,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 c336278..5adeac4 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
 
 /**
@@ -280,6 +286,11 @@
 	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;
@@ -288,6 +299,7 @@
 
 	uint32_t allow_usb = 0;
 	uint32_t allow_legacy = 0;
+	uint32_t disable_dev_boot = 0;
 	uint32_t use_usb = 0;
 	uint32_t use_legacy = 0;
 	uint32_t default_boot = 0;
@@ -319,6 +331,46 @@
 		use_usb = 0;
 	}
 
+	/* 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);
 
@@ -1162,12 +1214,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 c6d4e27..6ab52b7 100644
--- a/firmware/lib/vboot_kernel.c
+++ b/firmware/lib/vboot_kernel.c
@@ -15,6 +15,7 @@
 #include "gbb_header.h"
 #include "gpt_misc.h"
 #include "load_kernel_fw.h"
+#include "rollback_index.h"
 #include "utility.h"
 #include "vboot_api.h"
 #include "vboot_common.h"
@@ -77,6 +78,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;
 	}
@@ -268,6 +273,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 84655fa..9e9f5b9 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 @@
 		    "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 aebfab6..c0965a6 100644
--- a/tests/vboot_api_kernel2_tests.c
+++ b/tests/vboot_api_kernel2_tests.c
@@ -46,6 +46,7 @@
 static uint32_t mock_num_disks_count;
 
 extern enum VbEcBootMode_t VbGetMode(void);
+extern struct RollbackSpaceFwmp *VbApiKernelGetFwmp(void);
 
 /* Reset mock data (for use before each test) */
 static void ResetMocks(void)
@@ -69,6 +70,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));
 
@@ -452,6 +455,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;
@@ -471,6 +480,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);
@@ -481,6 +497,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 6cdd91b..c1ba3d9 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 e690bc3..16cf1a9 100644
--- a/tests/vboot_kernel_tests.c
+++ b/tests/vboot_kernel_tests.c
@@ -13,10 +13,12 @@
 #include "cgptlib.h"
 #include "cgptlib_internal.h"
 #include "crc32.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"
@@ -63,12 +65,13 @@
 static VbKeyBlockHeader kbh;
 static VbKernelPreambleHeader kph;
 static VbCommonParams cparams;
+static struct RollbackSpaceFwmp fwmp;
 static uint8_t mock_disk[MOCK_SECTOR_SIZE * MOCK_SECTOR_COUNT];
 static GptHeader *mock_gpt_primary =
 	(GptHeader*)&mock_disk[MOCK_SECTOR_SIZE * 1];
 static GptHeader *mock_gpt_secondary =
 	(GptHeader*)&mock_disk[MOCK_SECTOR_SIZE * (MOCK_SECTOR_COUNT - 1)];
-
+static uint8_t mock_digest[SHA256_DIGEST_SIZE] = {12, 34, 56, 78};
 
 /**
  * Prepare a valid GPT header that will pass CheckHeader() tests
@@ -172,6 +175,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 */
@@ -287,6 +293,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
@@ -640,6 +653,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 =
@@ -725,6 +746,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,