tlcl, tpmc: extend GetVersion to report vendor specific data

1) Extend TlclGetVersion to return vendor specific data, if
   requested.
2) Extend 'tpmc getver' to include vendor specific data.

BRANCH=none
BUG=chromium:771561
TEST=unit tests, running 'tpmc getver'

Change-Id: Ic04c242d4e6f33b45a80479be9ab9777b317ebe2
Reviewed-on: https://chromium-review.googlesource.com/706240
Commit-Ready: Andrey Pronin <apronin@chromium.org>
Tested-by: Andrey Pronin <apronin@chromium.org>
Reviewed-by: Andrey Pronin <apronin@chromium.org>
diff --git a/firmware/include/tlcl.h b/firmware/include/tlcl.h
index fba764c..f8d9472 100644
--- a/firmware/include/tlcl.h
+++ b/firmware/include/tlcl.h
@@ -208,8 +208,17 @@
 
 /**
  * Requests version information from the TPM.
+ * If vendor_specific_buf_size != NULL, requests also the vendor-specific
+ * variable-length part of the version:
+ *   if vendor_specific_buf == NULL, determines its size and returns in
+ *       *vendor_specific_buf_size;
+ *   if vendor_specific_buf != NULL, fills the buffer until either the
+ *       end of the vendor specific data or the end of the buffer, sets
+ *       *vendor_specific_buf_size to the length of the filled data.
  */
-uint32_t TlclGetVersion(uint32_t *vendor, uint64_t *firmware_version);
+uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version,
+                        uint8_t* vendor_specific_buf,
+                        size_t* vendor_specific_buf_size);
 
 /**
  * Issues the IFX specific FieldUpgradeInfoRequest2 TPM_FieldUpgrade subcommand
diff --git a/firmware/include/tpm2_tss_constants.h b/firmware/include/tpm2_tss_constants.h
index 53be3fb..f44dc71 100644
--- a/firmware/include/tpm2_tss_constants.h
+++ b/firmware/include/tpm2_tss_constants.h
@@ -55,6 +55,8 @@
 #define PT_GROUP                        ((TPM_PT)0x00000100)
 #define PT_FIXED                        PT_GROUP
 #define TPM_PT_MANUFACTURER             (PT_FIXED + 5)
+#define TPM_PT_VENDOR_STRING_1          (PT_FIXED + 6)
+#define TPM_PT_VENDOR_STRING_4          (PT_FIXED + 9)
 #define TPM_PT_FIRMWARE_VERSION_1       (PT_FIXED + 11)
 #define TPM_PT_FIRMWARE_VERSION_2       (PT_FIXED + 12)
 #define PT_VAR                          (PT_GROUP * 2)
diff --git a/firmware/lib/tpm2_lite/tlcl.c b/firmware/lib/tpm2_lite/tlcl.c
index bb212d2..89bf25a 100644
--- a/firmware/lib/tpm2_lite/tlcl.c
+++ b/firmware/lib/tpm2_lite/tlcl.c
@@ -523,7 +523,25 @@
 	return TPM_E_IOERROR;
 }
 
-uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version)
+// Converts TPM_PT_VENDOR_STRING_x |value| to an array of bytes in |buf|.
+// Returns the number of bytes in the array.
+// |buf| should be at least 4 bytes long.
+size_t tlcl_vendor_string_parse(uint32_t value, uint8_t* buf)
+{
+	size_t len = 0;
+	int shift = 24;
+	for (; len < 4; shift -= 8) {
+		uint8_t byte = (value >> shift) & 0xffu;
+		if (!byte)
+			break;
+		buf[len++] = byte;
+	}
+	return len;
+}
+
+uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version,
+                        uint8_t* vendor_specific_buf,
+                        size_t* vendor_specific_buf_size)
 {
 	uint32_t result =  tlcl_get_tpm_property(TPM_PT_MANUFACTURER, vendor);
 	if (result != TPM_SUCCESS)
@@ -539,6 +557,35 @@
 		return result;
 
 	*firmware_version = ((uint64_t) version_1 << 32) | version_2;
+
+	if (!vendor_specific_buf_size)
+		return TPM_SUCCESS;
+
+	size_t total_size = 0;
+	uint32_t prop_id;
+	uint8_t prop_string[16];
+	for (prop_id = TPM_PT_VENDOR_STRING_1;
+	     prop_id <= TPM_PT_VENDOR_STRING_4;
+	     ++prop_id) {
+		uint32_t prop_value;
+		result = tlcl_get_tpm_property(prop_id, &prop_value);
+		if (result != TPM_SUCCESS)
+			break;
+
+		size_t prop_len = tlcl_vendor_string_parse(
+				prop_value, prop_string + total_size);
+		VbAssert(prop_len <= 4 &&
+			 total_size + prop_len <= sizeof(prop_string));
+		total_size += prop_len;
+		if (prop_len < 4)
+			break;
+	}
+	if (vendor_specific_buf) {
+		if (total_size > *vendor_specific_buf_size)
+			total_size = *vendor_specific_buf_size;
+		memcpy(vendor_specific_buf, prop_string, total_size);
+	}
+	*vendor_specific_buf_size = total_size;
 	return TPM_SUCCESS;
 }
 
diff --git a/firmware/lib/tpm_lite/mocked_tlcl.c b/firmware/lib/tpm_lite/mocked_tlcl.c
index def4810..0abcb7c 100644
--- a/firmware/lib/tpm_lite/mocked_tlcl.c
+++ b/firmware/lib/tpm_lite/mocked_tlcl.c
@@ -186,10 +186,15 @@
 	return TPM_SUCCESS;
 }
 
-uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version)
+uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version,
+                        uint8_t* vendor_specific_buf,
+                        size_t* vendor_specific_buf_size)
 {
 	*vendor = 0x4e4f4e45;
 	*firmware_version = 0x1;
+	if (vendor_specific_buf_size) {
+		*vendor_specific_buf_size = 0;
+	}
 	return TPM_SUCCESS;
 }
 
diff --git a/firmware/lib/tpm_lite/tlcl.c b/firmware/lib/tpm_lite/tlcl.c
index 59dd120..6f71f2b 100644
--- a/firmware/lib/tpm_lite/tlcl.c
+++ b/firmware/lib/tpm_lite/tlcl.c
@@ -513,7 +513,10 @@
 	return result;
 }
 
-uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version) {
+uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version,
+                        uint8_t* vendor_specific_buf,
+                        size_t* vendor_specific_buf_size)
+{
 	uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE];
 	uint32_t result = TlclSendReceive(tpm_getversionval_cmd.buffer,
 					  response, sizeof(response));
@@ -528,7 +531,9 @@
 
 	/* Verify size >= sizeof(TPM_CAP_VERSION_INFO). */
 	const uint32_t kSizeofCapVersionInfo = 15;
-	if (size < kSizeofCapVersionInfo) {
+	if (size < kSizeofCapVersionInfo ||
+	    kTpmResponseHeaderLength + sizeof(size) + size >
+			TPM_LARGE_ENOUGH_COMMAND_SIZE) {
 		return TPM_E_IOERROR;
 	}
 
@@ -546,6 +551,26 @@
 	FromTpmUint32(cursor, vendor);
 	cursor += sizeof(*vendor);
 
+	if (vendor_specific_buf_size) {
+		uint16_t vendor_specific_size;
+		FromTpmUint16(cursor, &vendor_specific_size);
+		cursor += sizeof(vendor_specific_size);
+
+		if (size < kSizeofCapVersionInfo + vendor_specific_size) {
+			return TPM_E_IOERROR;
+		}
+		if (vendor_specific_buf) {
+			if (vendor_specific_size > *vendor_specific_buf_size) {
+				vendor_specific_size =
+					*vendor_specific_buf_size;
+			}
+			memcpy(vendor_specific_buf, cursor,
+			       vendor_specific_size);
+			cursor += vendor_specific_size;
+		}
+		*vendor_specific_buf_size = vendor_specific_size;
+	}
+
 	return TPM_SUCCESS;
 }
 
@@ -563,7 +588,8 @@
 uint32_t TlclIFXFieldUpgradeInfo(TPM_IFX_FIELDUPGRADEINFO* info) {
 	uint32_t vendor;
 	uint64_t firmware_version;
-	uint32_t result = TlclGetVersion(&vendor, &firmware_version);
+	uint32_t result =
+			TlclGetVersion(&vendor, &firmware_version, NULL, NULL);
 	if (result != TPM_SUCCESS) {
 		return result;
 	}
diff --git a/tests/tlcl_tests.c b/tests/tlcl_tests.c
index b8d64a8..c2f3681 100644
--- a/tests/tlcl_tests.c
+++ b/tests/tlcl_tests.c
@@ -351,19 +351,54 @@
 
 	uint32_t vendor;
 	uint64_t firmware_version;
+	uint8_t vendor_specific[32];
+	size_t vendor_specific_size;
 
 	ResetMocks();
 	calls[0].rsp = response;
 	calls[0].rsp_size = sizeof(response);
-	TEST_EQ(TlclGetVersion(&vendor, &firmware_version), 0, "GetVersion");
+	TEST_EQ(TlclGetVersion(&vendor, &firmware_version, NULL, NULL), 0,
+		"GetVersion");
 	TEST_EQ(calls[0].req_cmd, TPM_ORD_GetCapability, "  cmd");
 	TEST_EQ(vendor, 0x49465800, "  vendor");
 	TEST_EQ(firmware_version, 0x420, "  firmware_version");
 
 	ResetMocks();
+	calls[0].rsp = response;
+	calls[0].rsp_size = sizeof(response);
+	vendor_specific_size = 100;
+	TEST_EQ(TlclGetVersion(&vendor, &firmware_version,
+		NULL, &vendor_specific_size), 0,
+		"GetVersion - vendor specific size");
+	TEST_EQ(vendor_specific_size, 0xd, "  vendor specific size");
+
+	ResetMocks();
+	calls[0].rsp = response;
+	calls[0].rsp_size = sizeof(response);
+	vendor_specific_size = sizeof(vendor_specific);
+	TEST_EQ(TlclGetVersion(&vendor, &firmware_version,
+		vendor_specific, &vendor_specific_size), 0,
+		"GetVersion - vendor specific data");
+	TEST_EQ(vendor_specific_size, 0xd, "  vendor specific size");
+	TEST_EQ(memcmp(vendor_specific, response + 29, 0xd), 0,
+		"  vendor specific data check");
+
+	ResetMocks();
+	calls[0].rsp = response;
+	calls[0].rsp_size = sizeof(response);
+	vendor_specific_size = 4;
+	TEST_EQ(TlclGetVersion(&vendor, &firmware_version,
+		vendor_specific, &vendor_specific_size), 0,
+		"GetVersion - vendor specific data, short buf");
+	TEST_EQ(vendor_specific_size, 4,
+		"  min(vendor specific size, buf size)");
+	TEST_EQ(memcmp(vendor_specific, response + 29, 4), 0,
+		"  vendor specific data check");
+
+	ResetMocks();
 	SetResponse(0, TPM_E_IOERROR, 0);
-	TEST_EQ(TlclGetVersion(&vendor, &firmware_version), TPM_E_IOERROR,
-		"GetVersion - error");
+	TEST_EQ(TlclGetVersion(&vendor, &firmware_version, NULL, NULL),
+		TPM_E_IOERROR, "GetVersion - error");
 	TEST_EQ(calls[0].req_cmd, TPM_ORD_GetCapability, "  cmd");
 
 	/* Adjust response to indicate a 1 byte too short payload size. */
@@ -371,8 +406,32 @@
 	ResetMocks();
 	calls[0].rsp = response;
 	calls[0].rsp_size = sizeof(response);
-	TEST_EQ(TlclGetVersion(&vendor, &firmware_version), TPM_E_IOERROR,
-		"GetVersion -- short");
+	TEST_EQ(TlclGetVersion(&vendor, &firmware_version, NULL, NULL),
+		TPM_E_IOERROR, "GetVersion -- short");
+	TEST_EQ(calls[0].req_cmd, TPM_ORD_GetCapability, "  cmd");
+
+	/* Adjust response to indicate a payload size too long for the
+	 * response buffer. */
+	ToTpmUint32(response + kTpmResponseHeaderLength,
+			TPM_LARGE_ENOUGH_COMMAND_SIZE - sizeof(uint32_t) -
+			kTpmResponseHeaderLength + 1);
+	ResetMocks();
+	calls[0].rsp = response;
+	calls[0].rsp_size = sizeof(response);
+	TEST_EQ(TlclGetVersion(&vendor, &firmware_version, NULL, NULL),
+		TPM_E_IOERROR, "GetVersion -- long");
+	TEST_EQ(calls[0].req_cmd, TPM_ORD_GetCapability, "  cmd");
+
+	/* Restore the original payload length and adjust response to contain
+	 * less vendor specific data than indicated in its size. */
+	ToTpmUint32(response + kTpmResponseHeaderLength, 0x1c);
+	ToTpmUint16(response + 27, 0xd + 1);
+	ResetMocks();
+	calls[0].rsp = response;
+	calls[0].rsp_size = sizeof(response);
+	TEST_EQ(TlclGetVersion(&vendor, &firmware_version,
+		NULL, &vendor_specific_size), TPM_E_IOERROR,
+		"GetVersion -- short with vendor specific");
 	TEST_EQ(calls[0].req_cmd, TPM_ORD_GetCapability, "  cmd");
 }
 
diff --git a/utility/tpmc.c b/utility/tpmc.c
index a36f7e9..0584e32 100644
--- a/utility/tpmc.c
+++ b/utility/tpmc.c
@@ -460,10 +460,18 @@
 static uint32_t HandlerGetVersion(void) {
   uint32_t vendor;
   uint64_t firmware_version;
-  uint32_t result = TlclGetVersion(&vendor, &firmware_version);
+  uint8_t vendor_specific[32];
+  size_t vendor_specific_size = sizeof(vendor_specific);
+  uint32_t result = TlclGetVersion(&vendor, &firmware_version, vendor_specific,
+                                   &vendor_specific_size);
   if (result == 0) {
-    printf("vendor %08x\nfirmware_version %016" PRIx64 "\n",
+    printf("vendor %08x\nfirmware_version %016" PRIx64 "\nvendor_specific ",
            vendor, firmware_version);
+    size_t n;
+    for (n = 0; n < vendor_specific_size; ++n) {
+      printf("%02x", vendor_specific[n]);
+    }
+    printf("\n");
   }
   return result;
 }