mirror of
https://github.com/nsacyber/HIRS.git
synced 2025-03-03 13:14:39 +00:00
837 lines
31 KiB
C++
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;
|
|
}
|
|
}
|
|
}
|