HIRS/HIRS_ProvisionerTPM2/src/CommandTpm2.cpp
2021-05-27 15:43:44 -04:00

837 lines
31 KiB
C++

/**
* Copyright (C) 2017-2018, U.S. Government
*/
#include <CommandTpm2.h>
#include <DeviceInfoCollector.h>
#include <HirsRuntimeException.h>
#include <Process.h>
#include <Utils.h>
#include <chrono>
#include <fstream>
#include <string>
#include <sstream>
#include <thread>
#include <utility>
#include <vector>
#include <iostream>
#include <iomanip>
using hirs::exception::HirsRuntimeException;
using hirs::file_utils::fileToString;
using hirs::file_utils::writeBinaryFile;
using hirs::file_utils::fileExists;
using hirs::log::Logger;
using hirs::pb::IdentityClaim;
using hirs::tpm2::CommandTpm2;
using hirs::string_utils::binaryToHex;
using hirs::string_utils::contains;
using hirs::string_utils::longToHex;
using hirs::string_utils::hexToLong;
using hirs::tpm2_tools_utils::Tpm2ToolsVersion;
using hirs::tpm2_tools_utils::Tpm2ToolsOutputParser;
using hirs::utils::Process;
using std::chrono::milliseconds;
using std::cout;
using std::endl;
using std::string;
using std::stringstream;
using std::ifstream;
using std::this_thread::sleep_for;
using std::to_string;
using std::vector;
const Logger CommandTpm2::LOGGER = Logger::getDefaultLogger();
const int CommandTpm2::kMaxRetryCommandAttempts = 5;
const char* const CommandTpm2::kTpm2ToolsTakeOwnershipCommand
= "tpm2_takeownership";
const char* const CommandTpm2::kTpm2ToolsNvDefineCommand = "tpm2_nvdefine";
const char* const CommandTpm2::kTpm2ToolsNvListCommand = "tpm2_nvlist";
const char* const CommandTpm2::kTpm2ToolsNvReadCommand = "tpm2_nvread";
const char* const CommandTpm2::kTpm2ToolsNvReleaseCommand = "tpm2_nvrelease";
const char* const CommandTpm2::kTpm2ToolsNvWriteCommand = "tpm2_nvwrite";
const char* const CommandTpm2::kTpm2ToolsGetPubAkCommand = "tpm2_getpubak";
const char* const CommandTpm2::kTpm2ToolsGetPubEkCommand = "tpm2_getpubek";
const char* const CommandTpm2::kTpm2ToolsListPersistentCommand
= "tpm2_listpersistent";
const char* const CommandTpm2::kTpm2ToolsReadPublicCommand = "tpm2_readpublic";
const char* const CommandTpm2::kTpm2ToolsActivateCredential
= "tpm2_activatecredential";
const char* const CommandTpm2::kTpm2ToolsEvictControlCommand
= "tpm2_evictcontrol";
const char* const CommandTpm2::kTpm2ToolsGetQuoteCommand = "tpm2_quote";
const char* const CommandTpm2::kTpm2ToolsPcrListCommand = "tpm2_pcrlist";
/**
* The value for the TPM_RC_RETRY was obtained from Table 16 (pgs. 37-41) of
* the "Trusted Platform Module Library Part 2: Structures" Revision 1.38
* document.
*/
const char* const CommandTpm2::kTpm2RetryCommandCode = "0x922";
const char* const CommandTpm2::kWellKnownSecret = "00";
/**
* The value for kDefaultAttributeValue can be understood by checking Part 2 of
* the TPM 2.0 Specification, Table 204: Definition of (UINT32) TPMA_NV Bits.
* The value of 0x2000A means the following 3 bits are set:
* Bit 1: The index data can be written if Owner Authorization is provided.
* Bit 3: Authorizations to change the Index contents that require USER role
* may not be provided with a policy session.
* Bit 17: The index data can be read if Owner Authorization is provided.
*/
const char* const CommandTpm2::kDefaultAttributeValue = "0x2000A";
/**
* The following algorithm IDs were obtained from Table 9 (pgs. 26-28) of
* the "Trusted Platform Module Library Part 2: Structures" Revision 1.38
* document.
*/
const char* const CommandTpm2::kRsaAlgorithmId = "0x01";
const char* const CommandTpm2::kEccAlgorithmId = "0x23";
/**
* The following default memory address was obtained from Table 28 (pg. 57) of
* the "Trusted Platform Module Library Part 2: Structures" Revision 1.38
* document.
*/
const char* const CommandTpm2::kDefaultOwnerAuthHandle = "0x40000001";
/**
* The following default memory addresses were obtained from Table 2 (pg. 29) of
* the "TCG TPM v2.0 Provisioning Guidance", Revision 1.0 document.
*/
const char* const CommandTpm2::kDefaultRsaEkCredentialHandle = "0x1c00002";
const char* const CommandTpm2::kDefaultEccEkCredentialHandle = "0x1c0000a";
const char* const CommandTpm2::kDefaultPlatformCredentialHandle = "0x1c90000";
const char* const CommandTpm2::kDefaultEkHandle = "0x81010001";
const char* const CommandTpm2::kDefaultAkHandle = "0x81010002";
const char* const CommandTpm2::kAKCertificateHandle = "0x1c0000c";
const char* const CommandTpm2::kDefaultAkCertFilename =
"/etc/hirs/ak.cer";
const char* const CommandTpm2::kDefaultAkNameFilename = "ak.name";
const char* const CommandTpm2::kDefaultAkPubFilename = "ak.pub";
const char* const CommandTpm2::kDefaultEkPubFilename = "ek.pub";
const char* const CommandTpm2::kDefaultIdentityClaimResponseFilename
= "identityClaimResponse";
const char* const CommandTpm2::kDefaultActivatedIdentityFilename
= "activatedIdentity.secret";
const char* const CommandTpm2::kTpm2DefaultQuoteFilename = "/tmp/quote.bin";
const char* const CommandTpm2::kTpm2DefaultSigFilename = "/tmp/sig.bin";
const char* const CommandTpm2::kTpm2Sha256SigAlgorithm = "sha256";
/**
* Constructor to create an interface to TPM 2.0 devices.
* @param version version of tpm2-tools to be used when making commands
*/
CommandTpm2::CommandTpm2(const Tpm2ToolsVersion& version)
: version(version) {
}
/**
* Method to set the auth data (passwords) of the TPM 2.0 device.
*
* @throws HirsRuntimeException on failure
*/
void CommandTpm2::setAuthData() {
stringstream argsStream;
switch (version) {
case Tpm2ToolsVersion::VERSION_1_1_0:
case Tpm2ToolsVersion::VERSION_2_1_0:
argsStream << " -X -o " << kWellKnownSecret
<< " -e " << kWellKnownSecret
<< " -l " << kWellKnownSecret
<< endl;
break;
case Tpm2ToolsVersion::VERSION_3:
argsStream << " -o hex:" << kWellKnownSecret
<< " -e hex:" << kWellKnownSecret
<< " -l hex:" << kWellKnownSecret
<< endl;
break;
}
LOGGER.info("Attempting to set auth data.");
runTpm2CommandWithRetry(kTpm2ToolsTakeOwnershipCommand, argsStream.str(),
__LINE__);
LOGGER.info("Auth data set successfully.");
}
/**
* Method to get the Endorsement Credential from a default address
* as a byte-based, DER-encoded X509 credential.
* @param keyType must be RSA or ECC
* @return string of hex-encoded bytes representing DER-encoded X509 credential
*/
string CommandTpm2::getEndorsementCredentialDefault(
const AsymmetricKeyType& keyType) {
LOGGER.info("Attempting to retrieve endorsement credential");
string endorsementCredential;
switch (keyType) {
case AsymmetricKeyType::RSA:
endorsementCredential = getStoredCredential(
kDefaultRsaEkCredentialHandle);
break;
case AsymmetricKeyType::ECC:
endorsementCredential = getStoredCredential(
kDefaultEccEkCredentialHandle);
break;
}
if (endorsementCredential == "") {
LOGGER.info("Unable to retrieve endorsement credential");
cout << "------> Unable to retrieve endorsement credential" << endl;
} else {
LOGGER.info("Successfully retrieved endorsement credential");
}
return endorsementCredential;
}
/**
* Method to get the Platform Credential from a default address
* as a byte-based, DER-encoded X509 credential. If platform credential
* does not exist, an empty string will be returned.
*
* @return string of hex-encoded bytes representing DER-encoded X509
* credential or an empty string if no credential exists
*/
string CommandTpm2::getPlatformCredentialDefault() {
LOGGER.info("Attempting to retrieve platform credential");
string platformCredential = getStoredCredential(
kDefaultPlatformCredentialHandle);
if (platformCredential == "") {
LOGGER.info("Unable to retrieve platform credential");
cout << "------> Unable to retrieve platform credential" << endl;
} else {
LOGGER.info("Successfully retrieved platform credential");
}
return platformCredential;
}
/**
* Method to get a stored credential such as the Endorsement Credential and
* Platform Credential as a byte-based, DER-encoded X509 credential.
*
* @param credentialHandle NVRAM address for Endorsement key pair
* @throws HirsRuntimeException on failure
* @return string of hex-encoded bytes representing DER-encoded X509 credential
*/
string CommandTpm2::getStoredCredential(
const string& credentialHandle) {
LOGGER.info("Attempting to determine key size.");
uint16_t dataSize = getNvIndexDataSize(credentialHandle);
if (dataSize == 0) {
stringstream errStream;
errStream << "Could not parse NV List command. It did not contain the "
<< "handle: " << credentialHandle;
LOGGER.warn(errStream.str());
return "";
}
stringstream logstream;
logstream << "Key size acquired. Attempting credential retrieval at "
<< "address " << credentialHandle;
LOGGER.info(logstream.str());
string credential = readNvIndex(credentialHandle, dataSize);
if (credential.empty()) {
stringstream errStream;
errStream << "Could not parse NV Read command. Verify the size and "
<< "location were correct: " << to_string(dataSize)
<< " bytes at " << credentialHandle;
LOGGER.warn(errStream.str());
return "";
}
LOGGER.info("Credential retrieval successful.");
return credential;
}
/**
* Method to generate an Endorsement Key (EK) pair at the default address of
* 0x81010001, if one does not already exist at that location.
* Currently, this uses tpm2_tools and, consequently, creates a file containing
* the public area of the newly created EK pair.
*
* @param keyType determines the algorithm used to generate the EK pair
*/
void CommandTpm2::createEndorsementKey(const AsymmetricKeyType& keyType) {
LOGGER.info("Creating Endorsement Key.");
if (hasPersistentObject(kDefaultEkHandle)) {
LOGGER.info("Endorsement key already exists at default address.");
createPublicAreaFile(kDefaultEkHandle, kDefaultEkPubFilename);
return;
}
LOGGER.info("Attempting to create EK at: " + string(kDefaultEkHandle));
stringstream argsStream;
switch (keyType) {
case AsymmetricKeyType::RSA:
argsStream << " -g " << kRsaAlgorithmId;
break;
case AsymmetricKeyType::ECC:
argsStream << " -g " << kEccAlgorithmId;
break;
}
argsStream << " -H " << kDefaultEkHandle
<< " -f " << kDefaultEkPubFilename
<< endl;
runTpm2CommandWithRetry(kTpm2ToolsGetPubEkCommand, argsStream.str(),
__LINE__);
LOGGER.info("Endorsement Key successfully created.");
}
/**
* Method to retrieve the public area of a TPM's endorsement key as a
* byte-encoded string.
*
* @return string of bytes representing an endorsement key public area
*/
string CommandTpm2::getEndorsementKeyPublicArea() {
LOGGER.info("Attempting to read EK public area from file: "
+ string(kDefaultEkPubFilename));
string binaryEncodedPublicArea = getPublicArea(kDefaultEkPubFilename);
LOGGER.info("Public area successfully read.");
return binaryEncodedPublicArea;
}
/**
* Method to generate an Attestation Key (AK) pair
* (AKA a restricted-use signing key pair.) Calls down to
* tpm2-tools to generate the ak and persist it in TPM
* storage as a child key under the default Endorsement Key.
* Generates two files: one containing the ak public data and
* one containing the ak name. Ak is persisted at the default location.
* The current algorithm for key generation defaults to RSA.
*/
void CommandTpm2::createAttestationKey() {
if (hasPersistentObject(kDefaultAkHandle)) {
LOGGER.info(string("Attestation key already exists at default address")
+ "\nFlushing key...");
flushPersistentObject(kDefaultAkHandle);
}
stringstream argsStream;
argsStream << " -E " << kDefaultEkHandle
<< " -k " << kDefaultAkHandle
<< " -f " << kDefaultAkPubFilename
<< " -n " << kDefaultAkNameFilename
<< endl;
LOGGER.info("Running getpubak with arguments: "
+ argsStream.str());
runTpm2CommandWithRetry(kTpm2ToolsGetPubAkCommand, argsStream.str(),
__LINE__);
LOGGER.info("AK created successfully");
}
/**
* Method to get the byte-encoded public key portion of the AK pair.
* Assumes createAk has been called and default filenames were used.
* Takes generated public data and name file and packages them into
* a protobuf data structure for transmission.
*
* @return protobuf encoded Attestation Public Key Data
*/
string CommandTpm2::getAttestationKeyPublicArea() {
LOGGER.info("Attempting to read AK public area from file: "
+ string(kDefaultAkPubFilename));
string binaryEncodedPublicArea = getPublicArea(kDefaultAkPubFilename);
LOGGER.info("Public area successfully read.");
return binaryEncodedPublicArea;
}
/**
* Method to create identity claim to send to the Attestation Certificate
* Authority (ACA).
*
* @param deviceInfo device specific info that can be verified
* @param akPublicArea the public key area blob for the AK
* @param ekPublicArea the public key area blob for the endorsement key
* @param endorsementCredential endorsement credential for verification
* @param platformCredentials platform credentials for verification
*/
IdentityClaim CommandTpm2::createIdentityClaim(
const hirs::pb::DeviceInfo& deviceInfo,
const string& akPublicArea,
const string& ekPublicArea,
const string& endorsementCredential,
const vector<string>& platformCredentials) {
IdentityClaim identityClaim;
identityClaim.set_allocated_dv(new hirs::pb::DeviceInfo(deviceInfo));
identityClaim.set_ak_public_area(akPublicArea);
identityClaim.set_ek_public_area(ekPublicArea);
if (endorsementCredential != "") {
identityClaim.set_endorsement_credential(endorsementCredential);
}
for (const auto& platformCredential : platformCredentials) {
if (platformCredential != "") {
identityClaim.add_platform_credential(platformCredential);
}
}
return identityClaim;
}
/**
* Method to activate a given attested identity with the TPM.
* Decodes a nonce blob provided by the ACA when the Identity Claim Request
* was made.
*
* @returns the decrypted, binary encoded nonce
*/
string CommandTpm2::activateIdentity() {
string binaryEncodedNonce;
// response blob has already been written to a file
// verify file exists
if (!fileExists(kDefaultIdentityClaimResponseFilename)) {
throw HirsRuntimeException(
"Identity claim response file does not exist",
"CommandTpm2::activateIdentity");
}
// TPM2 Tools major version 3.X.X prepends 4 bytes of a MAGIC NUMBER and
// 4 bytes of a version number to the file containing the cert and secret,
// but the ACA does not, nor does the ACA know which version of TPM2 Tools
// is running on the client machine. So we add the bytes here.
if (version == Tpm2ToolsVersion::VERSION_3) {
string s = fileToString(kDefaultIdentityClaimResponseFilename);
union {
UINT16 value;
BYTE bytes[2];
} uint16byteConverter;
uint16byteConverter.bytes[0] = s[0];
uint16byteConverter.bytes[1] = s[1];
UINT16 numBytesInCred = uint16byteConverter.value;
// Shift from Little Endian to Big Endian encoding for size of
// credential structure and secret structure, respectively
std::swap(s[0], s[1]);
std::swap(s[134], s[135]);
// Erase unnecessary zero padding due
s.erase(2 + numBytesInCred, 134 - 2 - numBytesInCred);
// Prepend header: MAGIC_NUMBER (0xBADCC0DE) + Version (0x00000001)
s.insert(s.begin(), {static_cast<char>(0xBA), static_cast<char>(0xDC),
static_cast<char>(0xC0), static_cast<char>(0xDE),
0x00, 0x00, 0x00, 0x01});
writeBinaryFile(s, kDefaultIdentityClaimResponseFilename);
}
stringstream argsStream;
argsStream << " -H " << kDefaultAkHandle
<< " -k " << kDefaultEkHandle
<< " -f " << kDefaultIdentityClaimResponseFilename
<< " -o " << kDefaultActivatedIdentityFilename
<< endl;
runTpm2CommandWithRetry(kTpm2ToolsActivateCredential, argsStream.str(),
__LINE__);
try {
binaryEncodedNonce = fileToString(kDefaultActivatedIdentityFilename);
} catch (HirsRuntimeException& ex) {
throw HirsRuntimeException("Unable to open Activate Identity file",
"CommandTpm2::activateIdentity");
}
LOGGER.debug("Identity activated successfully");
return binaryEncodedNonce;
}
/**
* Stores the AK Certificate to the TPM.
*
* @param akCertificateByteString string containing the raw bytes of the certificate
*/
void CommandTpm2::storeAKCertificate(
const string& akCertificateByteString) {
if (hasNvIndexDefined(kAKCertificateHandle)) {
LOGGER.info("AK Cert found at "
+ string(kAKCertificateHandle)
+ ". Releasing from NV Space...");
releaseNvIndex(kAKCertificateHandle);
}
std::ofstream akCertificateFile(kDefaultAkCertFilename);
akCertificateFile << akCertificateByteString;
akCertificateFile.close();
stringstream argsStream;
size_t akCertificateByteStringSize = akCertificateByteString.size();
argsStream << " -x " << kAKCertificateHandle
<< " -a " << kDefaultOwnerAuthHandle
<< " -t " << kDefaultAttributeValue
<< " -s " << akCertificateByteStringSize
<< endl;
runTpm2CommandWithRetry(kTpm2ToolsNvDefineCommand, argsStream.str(),
__LINE__);
try {
LOGGER.debug(string("Beginning to write to NV Index: ")
+ kAKCertificateHandle);
LOGGER.debug("Provided data size: "
+ to_string(akCertificateByteStringSize));
string nvWriteArguments
= createNvWriteCommandArgs(kAKCertificateHandle,
kDefaultAkCertFilename);
runTpm2CommandWithRetry(kTpm2ToolsNvWriteCommand, nvWriteArguments,
__LINE__);
} catch (HirsRuntimeException& ex) {
LOGGER.warn(string("Attempt to write AK Certificate to TPM failed.")
+ string(" The following output was given:\n")
+ string(ex.what()));
}
}
/**
* Private helper method that builds the command arguments for tpm2_tools
* NV Write tool.
*
* @param nvIndex the index at which to peform the write command
* @param writeFile the filename of the file to write to the NV index
* @return the argument string to be affixed to the NV Write command
*/
string CommandTpm2::createNvWriteCommandArgs(const string& nvIndex,
const string& writeFile) {
stringstream argumentsStringStream;
argumentsStringStream << " -x " << nvIndex
<< " -a " << kDefaultOwnerAuthHandle
<< " ";
switch (version) {
case Tpm2ToolsVersion::VERSION_1_1_0:
case Tpm2ToolsVersion::VERSION_2_1_0:
argumentsStringStream << "-f ";
break;
}
argumentsStringStream << writeFile
<< endl;
return argumentsStringStream.str();
}
/**
* Method to get a quote (signed pcr selection) from the TPM 2.0 device.
*
* @param pcr_selection selection of pcrs to sign
* @param nonce blob provided by the ACA when the Identity Claim Request
* @return the argument string to be affixed to tpm quote
*/
string CommandTpm2::getQuote(const string& pcr_selection,
const string& nonce) {
string quote;
stringstream argsStream;
int result = 0;
for (size_t count = 0; count < nonce.length(); ++count) {
result *=2;
result += nonce[count] == '1'? 1 : 0;
}
stringstream ss;
ss << std::hex << std::setw(8) << std::setfill('0') << result;
string hexNonce(ss.str());
argsStream << " -k " << kDefaultAkHandle
<< " -g " << kTpm2Sha256SigAlgorithm
<< " -l " << pcr_selection
<< " -q " << hexNonce // this needs to be a hex string
<< endl;
LOGGER.info("Running tpm2_quote with arguments: " + argsStream.str());
quote = runTpm2CommandWithRetry(kTpm2ToolsGetQuoteCommand,
argsStream.str(),
__LINE__);
LOGGER.info("TPM Quote successful");
return quote;
}
/**
* Method to get the full list of pcrs from the TPM.
*
*/
string CommandTpm2::getPcrList() {
string pcrslist;
stringstream argsStream;
argsStream << endl;
LOGGER.info("Running tpm2_pcrlist with arguments: " + argsStream.str());
pcrslist = runTpm2CommandWithRetry(kTpm2ToolsPcrListCommand,
argsStream.str(),
__LINE__);
LOGGER.info("TPM PCR List successful");
return pcrslist;
}
/**
* Private helper method to offload the process of running tpm2_nvlist
* and parsing the output for the data size at a particular nvIndex.
*
* @param nvIndex the memory address whose data size is of interest
* @throws HirsRuntimeException if tpm2_nvlist is not run successfully
* @return the size of the data at nvIndex, or 0 if it's not found
*/
uint16_t CommandTpm2::getNvIndexDataSize(const string& nvIndex) {
string listOutput;
try {
listOutput = runTpm2CommandWithRetry(kTpm2ToolsNvListCommand, "",
__LINE__);
} catch (HirsRuntimeException& ex) {
// Due to bug in tpm2-tools 2.1.0, check to see if error was success
if (contains(ex.what(), "NV indexes defined.")) {
listOutput = ex.what();
} else {
throw;
}
}
return Tpm2ToolsOutputParser::parseNvDataSize(nvIndex, listOutput);
}
/**
* Private helper method to offload the process of running tpm2_nvread
* and converting the data output to a hex-encoded byte string.
*
* @param nvIndex the starting memory address to read data from
* @param dataSize the amount of data to read at the nvIndex
* @throws HirsRuntimeException if tpm2_nvread is not run successfully
* @return the output in a hex-encoded byte string, or an empty string if
* nothing is found at nvIndex
*/
string CommandTpm2::readNvIndex(const string& nvIndex,
const uint16_t& dataSize) {
LOGGER.info("Beginning to read at NV Index: " + nvIndex);
LOGGER.info("Provided dataSize: " + to_string(dataSize));
uint16_t maxNvBufferSize = 128;
uint16_t nvReadIterations = dataSize / maxNvBufferSize;
uint16_t nvBufferRemainder = dataSize % maxNvBufferSize;
uint16_t offset = 0;
stringstream nvReadOutput;
for (int i = 0; i <= nvReadIterations; i++) {
string nvReadArguments;
if (i == nvReadIterations) {
nvReadArguments = createNvReadCommandArgs(nvIndex, offset,
nvBufferRemainder);
} else {
nvReadArguments = createNvReadCommandArgs(nvIndex, offset,
maxNvBufferSize);
}
LOGGER.info("Command args: " + nvReadArguments);
string rawNvReadOutput = runTpm2CommandWithRetry(
kTpm2ToolsNvReadCommand, nvReadArguments, __LINE__);
switch (version) {
case Tpm2ToolsVersion::VERSION_1_1_0:
case Tpm2ToolsVersion::VERSION_2_1_0:
nvReadOutput << Tpm2ToolsOutputParser::parseNvReadOutput(
rawNvReadOutput);
break;
case Tpm2ToolsVersion::VERSION_3:
nvReadOutput << rawNvReadOutput;
break;
}
if (i != nvReadIterations) {
offset += maxNvBufferSize;
}
}
return nvReadOutput.str();
}
/**
* Private helper method to determine if an NV Index has been previously
* defined.
*
* @param nvIndex memory address at which to check for defined NV Index
* @throws HirsRuntimeException if failed to retrieve dataSize successfully
* @return true, if a definition exists at the given index / false, otherwise
*/
bool CommandTpm2::hasNvIndexDefined(const string& nvIndex) {
uint16_t dataSize = getNvIndexDataSize(nvIndex);
return dataSize != 0;
}
/**
* Private helper method to offload the process of running tpm2_nvrelease
* on an NV Index. This should release the NV Index from the
* TPM and allow the space to be redefined and used again.
*
* @param nvIndex NV memory address to release
* @throws HirsRuntimeException if tpm2_nvrelease is not run successfully
*/
void CommandTpm2::releaseNvIndex(const string& nvIndex) {
LOGGER.info("Releasing NV Index at: " + nvIndex);
stringstream argsStream;
switch (version) {
case Tpm2ToolsVersion::VERSION_1_1_0:
case Tpm2ToolsVersion::VERSION_2_1_0:
argsStream << " -X -P " << kWellKnownSecret;
break;
case Tpm2ToolsVersion::VERSION_3:
argsStream << " -P hex:" << kWellKnownSecret;
break;
}
argsStream << " -a " << kDefaultOwnerAuthHandle
<< " -x " << nvIndex;
runTpm2CommandWithRetry(kTpm2ToolsNvReleaseCommand, argsStream.str(),
__LINE__);
LOGGER.info("NV Index released successfully");
}
/**
* Private helper method that builds the command arguments for tpm2_tools
* NV Read tool.
*
* @param nvIndex the index at which to peform the read command
* @param readSize the size (in bytes) to pull with the read command
* @return the argument string to be affixed to the NV Read command
*/
string CommandTpm2::createNvReadCommandArgs(const string& nvIndex,
const uint16_t& offset,
const uint16_t& readSize) {
stringstream argumentsStringStream;
argumentsStringStream << " -x " << nvIndex
<< " -a " << kDefaultOwnerAuthHandle
<< " -o " << to_string(offset)
<< " -s " << to_string(readSize)
<< endl;
return argumentsStringStream.str();
}
/**
* Private helper method to offload the process of running tpm2_listpersistent
* and determining if a persistent object exists on the TPM at the given
* memory address.
*
* @param handle memory address at which to check for a persistent object
* @throws HirsRuntimeException if tpm2_listpersistent is not run successfully
* @return true, if an object exists at the given handle / false, otherwise
*/
bool CommandTpm2::hasPersistentObject(const string& handle) {
string listOutput
= runTpm2CommandWithRetry(kTpm2ToolsListPersistentCommand, "",
__LINE__);
return Tpm2ToolsOutputParser::parsePersistentObjectExists(handle,
listOutput);
}
/**
* Private helper method to offload the process of running tpm2_evictcontrol
* on a persistent object. This should flush the persistent object from the
* TPM should there be an object at the specified handle.
*
* @param handle memory address at which to flush a persistent object
* @throws HirsRuntimeException if tpm2_evictcontrol is not run successfully
*/
void CommandTpm2::flushPersistentObject(const string& handle) {
stringstream argsStream;
argsStream << " -A " << "o" // Owner Auth Flag
<< " -H " << handle
<< " -S " << handle
<< endl;
LOGGER.info("Running evictcontrol with arguments: "
+ argsStream.str());
runTpm2CommandWithRetry(kTpm2ToolsEvictControlCommand, argsStream.str(),
__LINE__);
LOGGER.info("Object flushed successfully");
}
/**
* Private helper method that reads and then writes the public area
* for a key in the TPM to a file in the local directory.
*
* @param keyHandle memory address at which the public key is stored
* @param filename name to be given to the file that stores the key's public
* area
*/
void CommandTpm2::createPublicAreaFile(const string& keyHandle,
const string& filename) {
// Note: we always need to write the file in the event tpm2-tools has been
// updated between provisioner runs. Thus, no short circuit logic to check
// for the file can be performed and avoid rewriting it can be performed.
if (fileExists(filename)) {
LOGGER.info("Public area file exists. Deleting for rewrite.");
remove(filename.c_str());
}
LOGGER.info("Creating public area file.");
stringstream argumentsStringStream;
argumentsStringStream << " -H " << keyHandle
<< " -o " << filename
<< endl;
runTpm2CommandWithRetry(kTpm2ToolsReadPublicCommand,
argumentsStringStream.str(),
__LINE__);
LOGGER.info("Public area file successfully created.");
}
string CommandTpm2::getPublicArea(const std::string& filename) {
string binaryEncodedPublicArea;
// need to read data from files
try {
binaryEncodedPublicArea = fileToString(filename);
} catch (HirsRuntimeException& ex) {
throw HirsRuntimeException("Unable to open public area file",
"CommandTpm2::getPublicArea");
}
// TPM2 Tools versions 1.1.0 and 2.1.0 affix 2 bytes of zeroes to files
// containing a public area, but the ACA does not know which version of
// TPM2 Tools is running on the client machine. So we remove the extra
// bytes here.
switch (version) {
case Tpm2ToolsVersion::VERSION_1_1_0:
case Tpm2ToolsVersion::VERSION_2_1_0:
binaryEncodedPublicArea.erase(binaryEncodedPublicArea.end() - 2,
binaryEncodedPublicArea.end());
break;
}
LOGGER.debug("Successfully read public data");
return binaryEncodedPublicArea;
}
string CommandTpm2::runTpm2CommandWithRetry(const string& command,
const string& args,
int sourceCodeLineNumber) {
string tpmErrorCode;
for (int i = 0;; ++i) {
try {
if (command.compare(kTpm2ToolsNvReadCommand) == 0) {
return hirs::utils::Process::runData(command, args,
"CommandTpm2.cpp", sourceCodeLineNumber);
} else {
return hirs::utils::Process::run(command, args,
"CommandTpm2.cpp", sourceCodeLineNumber);
}
} catch (HirsRuntimeException& ex) {
tpmErrorCode = Tpm2ToolsOutputParser::parseTpmErrorCode(ex.what());
if (tpmErrorCode == kTpm2RetryCommandCode &&
i < kMaxRetryCommandAttempts) {
LOGGER.warn("Waiting 100 ms and Retrying Command: " + command);
sleep_for(milliseconds(100));
continue;
}
throw;
}
}
}