tpm_manager: Expose TPM version information.

This adds version information fields to the result of the GetTpmStatus
operation and adds the corresponding logic to acquire the relevant
information via trunks / trousers and fill in the reply.

BUG=chromium:728134
TEST=unit tests

Change-Id: I0006e69b2d92837574960d2f306e1a04376e29ea
Reviewed-on: https://chromium-review.googlesource.com/517797
Commit-Ready: Mattias Nissler <mnissler@chromium.org>
Tested-by: Mattias Nissler <mnissler@chromium.org>
Reviewed-by: Andrey Pronin <apronin@chromium.org>
diff --git a/tpm_manager/common/tpm_manager.proto b/tpm_manager/common/tpm_manager.proto
index 61292d9..ae8552b 100644
--- a/tpm_manager/common/tpm_manager.proto
+++ b/tpm_manager/common/tpm_manager.proto
@@ -230,6 +230,24 @@
   optional bool dictionary_attack_lockout_in_effect = 7;
   // The number of seconds remaining in the lockout.
   optional uint32 dictionary_attack_lockout_seconds_remaining = 8;
+  // TPM version information.
+  message TpmVersionInfo {
+    // TPM family. We use the TPM 2.0 style encoding, e.g.:
+    //  * TPM 1.2: "1.2" -> 0x312e3200
+    //  * TPM 2.0: "2.0" -> 0x322e3000
+    optional uint32 family = 1;
+    // TPM spec level.
+    optional uint64 spec_level = 2;
+    // Manufacturer code.
+    optional uint32 manufacturer = 3;
+    // TPM model number.
+    optional uint32 tpm_model = 4;
+    // Firmware version.
+    optional uint64 firmware_version = 5;
+    // Vendor specific information.
+    optional bytes vendor_specific = 6;
+  }
+  optional TpmVersionInfo version_info = 9;
 }
 
 message TakeOwnershipRequest {
diff --git a/tpm_manager/server/mock_tpm_status.cc b/tpm_manager/server/mock_tpm_status.cc
index 54d8dd2..1df7594 100644
--- a/tpm_manager/server/mock_tpm_status.cc
+++ b/tpm_manager/server/mock_tpm_status.cc
@@ -16,9 +16,9 @@
 
 #include "tpm_manager/server/mock_tpm_status.h"
 
-using testing::_;
 using testing::Invoke;
 using testing::Return;
+using testing::_;
 
 namespace tpm_manager {
 
@@ -33,11 +33,28 @@
   return true;
 }
 
+bool GetDefaultVersionInfo(uint32_t* family,
+                           uint64_t* spec_level,
+                           uint32_t* manufacturer,
+                           uint32_t* tpm_model,
+                           uint64_t* firmware_version,
+                           std::vector<uint8_t>* vendor_specific) {
+  *family = 0x312e3200;
+  *spec_level = (0ULL << 32) | 138;
+  *manufacturer = 0x90091;
+  *tpm_model = 0x1234;
+  *firmware_version = 0xdeadc0de;
+  *vendor_specific = { 0xda, 0x7a };
+  return true;
+}
+
 MockTpmStatus::MockTpmStatus() {
   ON_CALL(*this, IsTpmEnabled()).WillByDefault(Return(true));
   ON_CALL(*this, IsTpmOwned()).WillByDefault(Return(true));
   ON_CALL(*this, GetDictionaryAttackInfo(_, _, _, _))
       .WillByDefault(Invoke(GetDefaultDictionaryAttackInfo));
+  ON_CALL(*this, GetVersionInfo(_, _, _, _, _, _))
+      .WillByDefault(Invoke(GetDefaultVersionInfo));
 }
 MockTpmStatus::~MockTpmStatus() {}
 
diff --git a/tpm_manager/server/mock_tpm_status.h b/tpm_manager/server/mock_tpm_status.h
index 35f00dc..7269b49 100644
--- a/tpm_manager/server/mock_tpm_status.h
+++ b/tpm_manager/server/mock_tpm_status.h
@@ -35,6 +35,13 @@
                     int* threshold,
                     bool* lockout,
                     int* seconds_remaining));
+  MOCK_METHOD6(GetVersionInfo,
+               bool(uint32_t* family,
+                    uint64_t* spec_level,
+                    uint32_t* manufacturer,
+                    uint32_t* tpm_model,
+                    uint64_t* firmware_version,
+                    std::vector<uint8_t>* vendor_specific));
 };
 
 }  // namespace tpm_manager
diff --git a/tpm_manager/server/tpm2_status_impl.cc b/tpm_manager/server/tpm2_status_impl.cc
index 9cd0a00..d172ba6 100644
--- a/tpm_manager/server/tpm2_status_impl.cc
+++ b/tpm_manager/server/tpm2_status_impl.cc
@@ -68,6 +68,42 @@
   return true;
 }
 
+bool Tpm2StatusImpl::GetVersionInfo(uint32_t* family,
+                                    uint64_t* spec_level,
+                                    uint32_t* manufacturer,
+                                    uint32_t* tpm_model,
+                                    uint64_t* firmware_version,
+                                    std::vector<uint8_t>* vendor_specific) {
+  if (!Refresh()) {
+    return false;
+  }
+
+  if (family) {
+    *family = trunks_tpm_state_->GetTpmFamily();
+  }
+  if (spec_level) {
+    uint64_t level = trunks_tpm_state_->GetSpecificationLevel();
+    uint64_t revision = trunks_tpm_state_->GetSpecificationRevision();
+    *spec_level = (level << 32) | revision;
+  }
+  if (manufacturer) {
+    *manufacturer = trunks_tpm_state_->GetManufacturer();
+  }
+  if (tpm_model) {
+    *tpm_model = trunks_tpm_state_->GetTpmModel();
+  }
+  if (firmware_version) {
+    *firmware_version = trunks_tpm_state_->GetFirmwareVersion();
+  }
+  if (vendor_specific) {
+    std::string vendor_id_string = trunks_tpm_state_->GetVendorIDString();
+    const uint8_t* data =
+        reinterpret_cast<const uint8_t*>(vendor_id_string.data());
+    vendor_specific->assign(data, data + vendor_id_string.size());
+  }
+  return true;
+}
+
 bool Tpm2StatusImpl::Refresh() {
   TPM_RC result = trunks_tpm_state_->Initialize();
   if (result != TPM_RC_SUCCESS) {
diff --git a/tpm_manager/server/tpm2_status_impl.h b/tpm_manager/server/tpm2_status_impl.h
index 6fe4218..0cb1373 100644
--- a/tpm_manager/server/tpm2_status_impl.h
+++ b/tpm_manager/server/tpm2_status_impl.h
@@ -33,13 +33,19 @@
   explicit Tpm2StatusImpl(const trunks::TrunksFactory& factory);
   ~Tpm2StatusImpl() override = default;
 
-  // TpmState methods.
+  // TpmStatus methods.
   bool IsTpmEnabled() override;
   bool IsTpmOwned() override;
   bool GetDictionaryAttackInfo(int* counter,
                                int* threshold,
                                bool* lockout,
                                int* seconds_remaining) override;
+  bool GetVersionInfo(uint32_t* family,
+                      uint64_t* spec_level,
+                      uint32_t* manufacturer,
+                      uint32_t* tpm_model,
+                      uint64_t* firmware_version,
+                      std::vector<uint8_t>* vendor_specific) override;
 
  private:
   // Refreshes the Tpm state information. Can be called as many times as needed
diff --git a/tpm_manager/server/tpm_manager_service.cc b/tpm_manager/server/tpm_manager_service.cc
index ecbebea..39db1cc 100644
--- a/tpm_manager/server/tpm_manager_service.cc
+++ b/tpm_manager/server/tpm_manager_service.cc
@@ -146,6 +146,26 @@
     reply->set_dictionary_attack_lockout_seconds_remaining(
         lockout_time_remaining);
   }
+  uint32_t family;
+  uint64_t spec_level;
+  uint32_t manufacturer;
+  uint32_t tpm_model;
+  uint64_t firmware_version;
+  std::vector<uint8_t> vendor_specific;
+  if (tpm_status_->GetVersionInfo(&family, &spec_level, &manufacturer,
+                                  &tpm_model, &firmware_version,
+                                  &vendor_specific)) {
+    GetTpmStatusReply::TpmVersionInfo* version_info =
+        reply->mutable_version_info();
+    version_info->set_family(family);
+    version_info->set_spec_level(spec_level);
+    version_info->set_manufacturer(manufacturer);
+    version_info->set_tpm_model(tpm_model);
+    version_info->set_firmware_version(firmware_version);
+    version_info->set_vendor_specific(
+        reinterpret_cast<char*>(vendor_specific.data()),
+        vendor_specific.size());
+  }
   reply->set_status(STATUS_SUCCESS);
 }
 
diff --git a/tpm_manager/server/tpm_manager_service_test.cc b/tpm_manager/server/tpm_manager_service_test.cc
index 50664c3..52575fc 100644
--- a/tpm_manager/server/tpm_manager_service_test.cc
+++ b/tpm_manager/server/tpm_manager_service_test.cc
@@ -25,13 +25,13 @@
 #include "tpm_manager/server/mock_tpm_status.h"
 #include "tpm_manager/server/tpm_manager_service.h"
 
-using testing::_;
 using testing::AtLeast;
 using testing::Invoke;
 using testing::NiceMock;
 using testing::Return;
 using testing::SaveArg;
 using testing::SetArgPointee;
+using testing::_;
 
 namespace {
 
@@ -142,6 +142,19 @@
         *seconds_remaining = 7;
         return true;
       }));
+  EXPECT_CALL(mock_tpm_status_, GetVersionInfo(_, _, _, _, _, _))
+      .WillRepeatedly(Invoke([](uint32_t* family, uint64_t* spec_level,
+                                uint32_t* manufacturer, uint32_t* tpm_model,
+                                uint64_t* firmware_version,
+                                std::vector<uint8_t>* vendor_specific) {
+        *family = 8;
+        *spec_level = 9;
+        *manufacturer = 10;
+        *tpm_model = 11;
+        *firmware_version = 12;
+        *vendor_specific = { 0xda, 0x7a };
+        return true;
+      }));
   LocalData local_data;
   local_data.set_owner_password(kOwnerPassword);
   EXPECT_CALL(mock_local_data_store_, Read(_))
@@ -156,6 +169,12 @@
     EXPECT_EQ(6, reply.dictionary_attack_threshold());
     EXPECT_TRUE(reply.dictionary_attack_lockout_in_effect());
     EXPECT_EQ(7, reply.dictionary_attack_lockout_seconds_remaining());
+    EXPECT_EQ(8, reply.version_info().family());
+    EXPECT_EQ(9, reply.version_info().spec_level());
+    EXPECT_EQ(10, reply.version_info().manufacturer());
+    EXPECT_EQ(11, reply.version_info().tpm_model());
+    EXPECT_EQ(12, reply.version_info().firmware_version());
+    EXPECT_EQ("\xda\x7a", reply.version_info().vendor_specific());
     Quit();
   };
   GetTpmStatusRequest request;
diff --git a/tpm_manager/server/tpm_status.h b/tpm_manager/server/tpm_status.h
index a6be635..51bf25b 100644
--- a/tpm_manager/server/tpm_status.h
+++ b/tpm_manager/server/tpm_status.h
@@ -17,6 +17,10 @@
 #ifndef TPM_MANAGER_SERVER_TPM_STATUS_H_
 #define TPM_MANAGER_SERVER_TPM_STATUS_H_
 
+#include <stdint.h>
+
+#include <vector>
+
 namespace tpm_manager {
 
 // TpmStatus is an interface class that reports status information for some kind
@@ -35,6 +39,13 @@
                                        int* threshold,
                                        bool* lockout,
                                        int* seconds_remaining) = 0;
+  // Get TPM hardware and software version information.
+  virtual bool GetVersionInfo(uint32_t* family,
+                              uint64_t* spec_level,
+                              uint32_t* manufacturer,
+                              uint32_t* tpm_model,
+                              uint64_t* firmware_version,
+                              std::vector<uint8_t>* vendor_specific) = 0;
 };
 
 }  // namespace tpm_manager
diff --git a/tpm_manager/server/tpm_status_impl.cc b/tpm_manager/server/tpm_status_impl.cc
index 5a9da2d..a021286 100644
--- a/tpm_manager/server/tpm_status_impl.cc
+++ b/tpm_manager/server/tpm_status_impl.cc
@@ -26,6 +26,8 @@
 
 // Minimum size of TPM_DA_INFO struct.
 const size_t kMinimumDaInfoSize = 21;
+// Minimum size of TPM_CAP_VERSION_INFO struct.
+const size_t kMinimumVersionInfoSize = 17;
 
 bool TpmStatusImpl::IsTpmEnabled() {
   if (!is_enable_initialized_) {
@@ -73,6 +75,57 @@
   return true;
 }
 
+bool TpmStatusImpl::GetVersionInfo(uint32_t* family,
+                                   uint64_t* spec_level,
+                                   uint32_t* manufacturer,
+                                   uint32_t* tpm_model,
+                                   uint64_t* firmware_version,
+                                   std::vector<uint8_t>* vendor_specific) {
+  std::string capability_data;
+  if (!GetCapability(TSS_TPMCAP_VERSION_VAL, 0, &capability_data, nullptr) ||
+      capability_data.size() < kMinimumVersionInfoSize ||
+      static_cast<uint16_t>(capability_data[1]) != TPM_TAG_CAP_VERSION_INFO) {
+    LOG(ERROR) << "Error getting TPM version capability data.";
+    return false;
+  }
+
+  TPM_CAP_VERSION_INFO tpm_version;
+  uint64_t offset = 0;
+  std::vector<BYTE> bytes(capability_data.begin(), capability_data.end());
+  Trspi_UnloadBlob_CAP_VERSION_INFO(&offset, bytes.data(), &tpm_version);
+  if (family) {
+    *family = 0x312e3200;
+  }
+  if (spec_level) {
+    *spec_level = (static_cast<uint64_t>(tpm_version.specLevel) << 32) |
+                  tpm_version.errataRev;
+  }
+  if (manufacturer) {
+    *manufacturer = (tpm_version.tpmVendorID[0] << 24) |
+                    (tpm_version.tpmVendorID[1] << 16) |
+                    (tpm_version.tpmVendorID[2] << 8) |
+                    (tpm_version.tpmVendorID[3] << 0);
+  }
+  if (tpm_model) {
+    // There's no generic model field in the spec. Model information might be
+    // present in the vendor-specific data returned by CAP_VERSION_INFO, so if
+    // we ever require to know the model, we'll need to check with hardware
+    // vendors for the best way to determine it.
+    *tpm_model = ~0;
+  }
+  if (firmware_version) {
+    *firmware_version =
+        (tpm_version.version.revMajor << 8) | tpm_version.version.revMinor;
+  }
+  if (vendor_specific) {
+    const uint8_t* data =
+        reinterpret_cast<const uint8_t*>(tpm_version.vendorSpecific);
+    vendor_specific->assign(data, data + tpm_version.vendorSpecificSize);
+  }
+  free(tpm_version.vendorSpecific);
+  return true;
+}
+
 void TpmStatusImpl::RefreshOwnedEnabledInfo() {
   TSS_RESULT result;
   std::string capability_data;
diff --git a/tpm_manager/server/tpm_status_impl.h b/tpm_manager/server/tpm_status_impl.h
index c86157d..be6e075 100644
--- a/tpm_manager/server/tpm_status_impl.h
+++ b/tpm_manager/server/tpm_status_impl.h
@@ -42,6 +42,12 @@
                                int* threshold,
                                bool* lockout,
                                int* seconds_remaining) override;
+  bool GetVersionInfo(uint32_t* family,
+                      uint64_t* spec_level,
+                      uint32_t* manufacturer,
+                      uint32_t* tpm_model,
+                      uint64_t* firmware_version,
+                      std::vector<uint8_t>* vendor_specific) override;
 
  private:
   // This method refreshes the |is_owned_| and |is_enabled_| status of the