[#236] Firmware validation update part 1 (#243)

* This commit includes changes to the provisioner for what is sent up.  Originally only SHA256 was being used, this change includes both.
* This last commit cover the items 2-4 in issue #236.  The Provisioner sends up and updated list of pcrs that include 256, not just sha1.  The validation and policy pages have been updated.  A second pull request will be created to address parsing the information into a baseline.
This commit is contained in:
Cyrus 2020-03-27 10:13:37 -04:00 committed by GitHub
parent 8b36d2636b
commit 2805df9f8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 272 additions and 23 deletions

View File

@ -120,7 +120,7 @@ public abstract class AbstractAttestationCertificateAuthority
private static final String AK_NAME_PREFIX = "000b";
private static final String AK_NAME_HASH_PREFIX =
"0001000b00050072000000100014000b0800000000000100";
private static final String TPM_SIGNATURE_ALG = "sha256";
private static final String TPM_SIGNATURE_ALG = "sha";
private static final int MAC_BYTES = 6;
@ -156,8 +156,10 @@ public abstract class AbstractAttestationCertificateAuthority
private final DeviceManager deviceManager;
private final DBManager<TPM2ProvisionerState> tpm2ProvisionerStateDBManager;
private String[] pcrsList;
private String[] pcrs256List;
private String tpmQuoteHash;
private String tpmSignatureHash;
private String pcrValues;
/**
* Constructor.
@ -216,7 +218,6 @@ public abstract class AbstractAttestationCertificateAuthority
// struct converter to generate it.
IdentityProof proof = structConverter.convert(identityProof, IdentityProof.class);
// convert the credential into an actual key.
LOG.debug("assembling public endorsement key");
PublicKey ekPublicKey = null;
@ -372,7 +373,8 @@ public abstract class AbstractAttestationCertificateAuthority
* Basic implementation of the ACA processIdentityClaimTpm2 method. Parses the claim,
* stores the device info, performs supply chain validation, generates a nonce,
* and wraps that nonce with the make credential process before returning it to the client.
*
* attCert.setPcrValues(pcrValues);
* @param identityClaim the request to process, cannot be null
* @return an identity claim response for the specified request containing a wrapped blob
*/
@ -395,7 +397,6 @@ public abstract class AbstractAttestationCertificateAuthority
RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray());
AppraisalStatus.Status validationResult = doSupplyChainValidation(claim, ekPub);
if (validationResult == AppraisalStatus.Status.PASS) {
RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray());
@ -504,7 +505,10 @@ public abstract class AbstractAttestationCertificateAuthority
parseTPMQuote(request.getQuote().toStringUtf8());
}
if (request.getPcrslist() != null && !request.getPcrslist().isEmpty()) {
parsePCRValues(request.getPcrslist().toStringUtf8());
this.pcrValues = request.getPcrslist().toStringUtf8();
String[] pcrsSet = this.pcrValues.split("\\+");
this.pcrsList = parsePCRValues(pcrsSet[0]);
this.pcrs256List = parsePCRValues(pcrsSet[1]);
}
// Get device name and device
@ -557,21 +561,24 @@ public abstract class AbstractAttestationCertificateAuthority
* This method splits all hashed pcr values into an array.
* @param pcrValues contains the full list of 24 pcr values
*/
private void parsePCRValues(final String pcrValues) {
private String[] parsePCRValues(final String pcrValues) {
String[] pcrs = null;
if (pcrValues != null) {
int counter = 0;
String[] lines = pcrValues.split("\\r?\\n");
pcrs = new String[lines.length - 1];
for (String line : lines) {
if (!line.contains(TPM_SIGNATURE_ALG)) {
if (!line.isEmpty()
&& !line.contains(TPM_SIGNATURE_ALG)) {
LOG.error(line);
pcrs[counter++] = line.split(":")[1].trim();
}
}
}
this.pcrsList = pcrs;
return pcrs;
}
/**
@ -1055,7 +1062,7 @@ public abstract class AbstractAttestationCertificateAuthority
builder.addExtension(subjectAlternativeName);
// identify cert as an AIK with this extension
if (null != IssuedCertificateAttributeHelper.EXTENDED_KEY_USAGE_EXTENSION) {
if (IssuedCertificateAttributeHelper.EXTENDED_KEY_USAGE_EXTENSION != null) {
builder.addExtension(IssuedCertificateAttributeHelper.EXTENDED_KEY_USAGE_EXTENSION);
} else {
LOG.warn("Failed to build extended key usage extension and add to AIK");
@ -1466,6 +1473,7 @@ public abstract class AbstractAttestationCertificateAuthority
// save issued certificate
IssuedAttestationCertificate attCert = new IssuedAttestationCertificate(
derEncodedAttestationCertificate, endorsementCredential, platformCredentials);
attCert.setPcrValues(pcrValues);
attCert.setDevice(device);
certificateManager.save(attCert);
} catch (Exception e) {

View File

@ -31,6 +31,7 @@ import hirs.data.persist.certificate.Certificate;
import hirs.data.persist.certificate.CertificateAuthorityCredential;
import hirs.data.persist.certificate.EndorsementCredential;
import hirs.data.persist.certificate.PlatformCredential;
import hirs.data.persist.certificate.IssuedAttestationCertificate;
import hirs.persist.AppraiserManager;
import hirs.persist.CertificateManager;
import hirs.persist.CertificateSelector;
@ -111,7 +112,7 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
if (policy.isEcValidationEnabled()) {
validations.add(validateEndorsementCredential(ec, acceptExpiredCerts));
// store the device with the credential
if (null != ec) {
if (ec != null) {
ec.setDevice(device);
this.certificateManager.update(ec);
}
@ -214,6 +215,19 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
}
}
if (policy.isFirmwareValidationEnabled()) {
// may need to associated with device to pull the correct info
// compare tpm quote with what is pulled from RIM associated file
IssuedAttestationCertificate attCert = IssuedAttestationCertificate
.select(this.certificateManager)
.byDeviceId(device.getId())
.getCertificate();
if (attCert != null) {
LOGGER.error(attCert.getPcrValues());
}
}
// Generate validation summary, save it, and return it.
SupplyChainValidationSummary summary =
new SupplyChainValidationSummary(device, validations);

View File

@ -12,11 +12,13 @@ public class PolicyPageModel {
private boolean enableEcValidation;
private boolean enablePcCertificateValidation;
private boolean enablePcCertificateAttributeValidation;
private boolean enableFirmwareValidation;
// Variables to get policy settings from page
private String pcValidate;
private String pcAttributeValidate;
private String ecValidate;
private String fmValidate;
/**
* Constructor. Sets fields from policy.
@ -27,6 +29,7 @@ public class PolicyPageModel {
this.enableEcValidation = policy.isEcValidationEnabled();
this.enablePcCertificateValidation = policy.isPcValidationEnabled();
this.enablePcCertificateAttributeValidation = policy.isPcAttributeValidationEnabled();
this.enableFirmwareValidation = policy.isFirmwareValidationEnabled();
}
/**
@ -62,6 +65,15 @@ public class PolicyPageModel {
return enablePcCertificateAttributeValidation;
}
/**
* Gets the Firmware Validation state.
*
* @return the validation state.
*/
public boolean getEnableFirmwareValidation() {
return enableFirmwareValidation;
}
/**
* Gets the EC Validation value.
*
@ -89,6 +101,15 @@ public class PolicyPageModel {
return pcAttributeValidate;
}
/**
* Gets the Firmware Validation value.
*
* @return the model string representation of this field (checked or unchecked)
*/
public String getFmValidate() {
return fmValidate;
}
/**
* Sets the EC Validation state.
*
@ -117,6 +138,15 @@ public class PolicyPageModel {
this.enablePcCertificateAttributeValidation = enablePcCertificateAttributeValidation;
}
/**
* Sets the Firmware Validation state.
*
* @param enableFirmwareValidation true if performing validation, false otherwise
*/
public void setEnableFirmwareValidation(final boolean enableFirmwareValidation) {
this.enableFirmwareValidation = enableFirmwareValidation;
}
/**
* Sets the Platform Certificate Validation state.
*
@ -144,12 +174,22 @@ public class PolicyPageModel {
this.pcAttributeValidate = pcAttributeValidate;
}
/**
* Sets the Firmware state.
*
* @param fmValidate "checked" if enabling validation, false otherwise
*/
public void setFmValidate(final String fmValidate) {
this.fmValidate = fmValidate;
}
@Override
public String toString() {
return "PolicyPageModel{"
+ "enableEcValidation=" + enableEcValidation
+ ", enablePcCertificateValidation=" + enablePcCertificateValidation
+ ", enablePcCertificateAttributeValidation="
+ enablePcCertificateAttributeValidation + '}';
+ enablePcCertificateAttributeValidation
+ ", enableFirmwareValidation=" + enableFirmwareValidation + '}';
}
}

View File

@ -221,7 +221,6 @@ public class PolicyPageController extends PageController<NoPageParams> {
ppModel.getEcValidate().equalsIgnoreCase(ENABLED_PARAMETER_VALUE);
try {
SupplyChainPolicy policy = getDefaultPolicyAndSetInModel(ppModel, model);
//If PC Validation is enabled without EC Validation, disallow change
@ -252,7 +251,60 @@ public class PolicyPageController extends PageController<NoPageParams> {
// return the redirect
return redirectToSelf(new NoPageParams(), model, attr);
}
/**
* Updates the Endorsement Credential Validation policy setting and redirects back
* to the original page.
*
* @param ppModel The data posted by the form mapped into an object.
* @param attr RedirectAttributes used to forward data back to the original page.
* @return View containing the url and parameters
* @throws URISyntaxException if malformed URI
*/
@RequestMapping(value = "update-firmware-validation", method = RequestMethod.POST)
public RedirectView updateFirmwareVal(@ModelAttribute final PolicyPageModel ppModel,
final RedirectAttributes attr) throws URISyntaxException {
// set the data received to be populated back into the form
Map<String, Object> model = new HashMap<>();
PageMessages messages = new PageMessages();
String successMessage;
boolean firmwareValidationOptionEnabled = ppModel.getFmValidate()
.equalsIgnoreCase(ENABLED_PARAMETER_VALUE);
try {
SupplyChainPolicy policy = getDefaultPolicyAndSetInModel(ppModel, model);
//If PC Validation is enabled without EC Validation, disallow change
// if (!isPolicyValid(firmwareValidationOptionEnabled,
//policy.isFirmwareValidationEnabled(),
// policy.isFirmwareValidationEnabled())) {
// handleUserError(model, messages,
// "To disable Endorsement Credential Validation, Platform Validation"
// + " must also be disabled.");
// return redirectToSelf(new NoPageParams(), model, attr);
// }
// set the policy option and create success message
if (firmwareValidationOptionEnabled) {
policy.setFirmwareValidationEnabled(true);
successMessage = "Firmware validation enabled";
} else {
policy.setFirmwareValidationEnabled(false);
successMessage = "Firmware validation disabled";
}
savePolicyAndApplySuccessMessage(ppModel, model, messages, successMessage, policy);
} catch (PolicyManagerException e) {
handlePolicyManagerUpdateError(model, messages, e,
"Error changing ACA endorsement validation policy",
"Error updating policy. \n" + e.getMessage());
}
// return the redirect
return redirectToSelf(new NoPageParams(), model, attr);
}
private void handlePolicyManagerUpdateError(final Map<String, Object> model,

View File

@ -47,6 +47,7 @@
<%-- Platform attribute validation --%>
<div class="aca-input-box">
<form:form method="POST" modelAttribute="initialData" action="policy/update-pc-attribute-validation">
<ul>
<li>Platform Attribute Credential Validation: ${initialData.enablePcCertificateAttributeValidation ? 'Enabled' : 'Disabled'}
<my:editor id="pcAttributePolicyEditor" label="Edit Settings">
<div class="radio">
@ -57,6 +58,22 @@
</div>
</my:editor>
</li>
</ul>
</form:form>
</div>
<div class="aca-input-box">
<form:form method="POST" modelAttribute="initialData" action="policy/update-firmware-validation">
<li>Firmware Validation: ${initialData.enableFirmwareValidation ? 'Enabled' : 'Disabled'}
<my:editor id="firmwarePolicyEditor" label="Edit Settings">
<div class="radio">
<label><input id="firmwareTop" type="radio" name="fmValidate" ${initialData.enableFirmwareValidation ? 'checked' : ''} value="checked"/> Firmware will be validated</label>
</div>
<div class="radio">
<label><input id="firmwareBot" type="radio" name="fmValidate" ${initialData.enableFirmwareValidation ? '' : 'checked'} value="unchecked"/> Firmware will not be validated</label>
</div>
</my:editor>
</li>
</form:form>
</div>
</ul>

View File

@ -30,7 +30,6 @@
<thead>
<tr>
<th>Tag ID</th>
<th>Type</th>
<th>Manufacturer</th>
<th>Model</th>
<th>Version</th>
@ -45,7 +44,6 @@
var url = pagePath +'/list';
var columns = [
{data: 'tagId'},
{data: 'rimType'},
{data: 'platformManufacturer'},
{data: 'platformModel'},
{data: 'firmwareVersion'},

View File

@ -32,11 +32,12 @@
<th rowspan="2">Result</th>
<th rowspan="2">Timestamp</th>
<th rowspan="2">Device</th>
<th colspan="2">Credential Validations</th>
<th colspan="3">Credential Validations</th>
</tr>
<tr>
<th style="text-align:center">Endorsement</th>
<th style="text-align:center">Platform</th>
<th style="text-align:center">Firmware</th>
</tr>
</thead>
</table>
@ -110,6 +111,14 @@
return getValidationDisplayHtml(full, "PLATFORM_CREDENTIAL")
}
},
{
data: 'id',
searchable: false,
orderable: false,
render: function (data, type, full, meta) {
return getValidationDisplayHtml(full, "FIRMWARE")
}
}
];
//Set data tables

View File

@ -93,6 +93,7 @@ public class PolicyPageControllerTest extends PageControllerTest {
boolean ec = policy.isEcValidationEnabled();
boolean pc = policy.isPcValidationEnabled();
boolean fm = policy.isFirmwareValidationEnabled();
// perform test
getMockMvc()
@ -102,8 +103,9 @@ public class PolicyPageControllerTest extends PageControllerTest {
.andExpect(model().attribute(PolicyPageController.INITIAL_DATA,
hasProperty("enableEcValidation", is(ec))))
.andExpect(model().attribute(PolicyPageController.INITIAL_DATA,
hasProperty("enablePcCertificateValidation",
is(pc))));
hasProperty("enablePcCertificateValidation", is(pc))))
.andExpect(model().attribute(PolicyPageController.INITIAL_DATA,
hasProperty("enableFirmwareValidation", is(fm))));
}
/**
@ -149,6 +151,7 @@ public class PolicyPageControllerTest extends PageControllerTest {
policy = getDefaultPolicy();
policy.setPcValidationEnabled(false);
policy.setEcValidationEnabled(true);
policy.setFirmwareValidationEnabled(false);
policyManager.updatePolicy(policy);
// perform the mock request
@ -170,6 +173,7 @@ public class PolicyPageControllerTest extends PageControllerTest {
//reset database for invalid policy test
policy.setEcValidationEnabled(true);
policy.setPcValidationEnabled(true);
policy.setFirmwareValidationEnabled(false);
policyManager.updatePolicy(policy);
// perform the mock request
@ -206,6 +210,7 @@ public class PolicyPageControllerTest extends PageControllerTest {
policy = getDefaultPolicy();
policy.setEcValidationEnabled(true);
policy.setPcValidationEnabled(false);
policy.setFirmwareValidationEnabled(false);
policyManager.updatePolicy(policy);
// perform the mock request
@ -227,6 +232,7 @@ public class PolicyPageControllerTest extends PageControllerTest {
//reset database for invalid policy test
policy.setEcValidationEnabled(false);
policy.setPcValidationEnabled(false);
policy.setFirmwareValidationEnabled(false);
policyManager.updatePolicy(policy);
// perform the mock request
@ -262,6 +268,7 @@ public class PolicyPageControllerTest extends PageControllerTest {
policy = getDefaultPolicy();
policy.setPcValidationEnabled(true);
policy.setPcAttributeValidationEnabled(false);
policy.setFirmwareValidationEnabled(false);
policyManager.updatePolicy(policy);
// perform the mock request
@ -283,6 +290,7 @@ public class PolicyPageControllerTest extends PageControllerTest {
//reset database for invalid policy test
policy.setPcAttributeValidationEnabled(true);
policy.setPcValidationEnabled(true);
policy.setFirmwareValidationEnabled(false);
policyManager.updatePolicy(policy);
// perform the mock request
@ -319,6 +327,7 @@ public class PolicyPageControllerTest extends PageControllerTest {
policy = getDefaultPolicy();
policy.setPcAttributeValidationEnabled(false);
policy.setPcValidationEnabled(true);
policy.setFirmwareValidationEnabled(false);
policyManager.updatePolicy(policy);
// perform the mock request

View File

@ -61,7 +61,8 @@ class CommandTpm2 {
static const char* const kTpm2ToolsGetQuoteCommand;
static const char* const kTpm2DefaultQuoteFilename;
static const char* const kTpm2DefaultSigFilename;
static const char* const kTpm2DefaultSigAlgorithm;
static const char* const kTpm2Sha1SigAlgorithm;
static const char* const kTpm2Sha256SigAlgorithm;
static const char* const kTpm2ToolsPcrListCommand;
const hirs::tpm2_tools_utils::Tpm2ToolsVersion version;
@ -138,6 +139,7 @@ class CommandTpm2 {
const std::string& nonce);
std::string getPcrsList();
std::string getPcrs256List();
};
} // namespace tpm2

View File

@ -123,7 +123,8 @@ 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::kTpm2DefaultSigAlgorithm = "sha256";
const char* const CommandTpm2::kTpm2Sha1SigAlgorithm = "sha1";
const char* const CommandTpm2::kTpm2Sha256SigAlgorithm = "sha256";
/**
* Constructor to create an interface to TPM 2.0 devices.
@ -540,7 +541,7 @@ string CommandTpm2::getQuote(const string& pcr_selection,
string hexNonce(ss.str());
argsStream << " -k " << kDefaultAkHandle
<< " -g " << kTpm2DefaultSigAlgorithm
<< " -g " << kTpm2Sha256SigAlgorithm
<< " -l " << pcr_selection
<< " -q " << hexNonce // this needs to be a hex string
<< endl;
@ -562,7 +563,7 @@ string CommandTpm2::getPcrsList() {
string pcrslist;
stringstream argsStream;
argsStream << " -g " << kTpm2DefaultSigAlgorithm
argsStream << " -g " << kTpm2Sha1SigAlgorithm
<< endl;
LOGGER.info("Running tpm2_pcrlist with arguments: " + argsStream.str());
@ -574,6 +575,25 @@ string CommandTpm2::getPcrsList() {
return pcrslist;
}
/**
* Method to get the full list of pcrs from the TPM as SHA256. *
*/
string CommandTpm2::getPcrs256List() {
string pcrslist;
stringstream argsStream;
argsStream << " -g " << kTpm2Sha256SigAlgorithm
<< endl;
LOGGER.info("Running tpm2_pcrlist with arguments: " + argsStream.str());
pcrslist = runTpm2CommandWithRetry(kTpm2ToolsPcrListCommand,
argsStream.str(),
__LINE__);
LOGGER.info("TPM PCRS (SHA 256) 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.

View File

@ -105,7 +105,10 @@ int provision() {
"0,1,2,3,4,5,6,7,8,9,10,11,12,13,"
"14,15,16,17,18,19,20,21,22,23",
decryptedNonce));
certificateRequest.set_pcrslist(tpm2.getPcrsList());
stringstream pcrStream;
pcrStream << tpm2.getPcrsList() << "\n+\n" << tpm2.getPcrs256List();
certificateRequest.set_pcrslist(pcrStream.str());
const string& akCertificateByteString
= provisioner.sendAttestationCertificateRequest(certificateRequest);

View File

@ -24,6 +24,9 @@ public class SupplyChainPolicy extends Policy {
@Column(nullable = false)
private boolean enablePcAttributeValidation = false;
@Column(nullable = false)
private boolean enableFirmwareValidation = false;
@Column(nullable = false)
private boolean enableUtcValidation = false;
@ -126,6 +129,24 @@ public class SupplyChainPolicy extends Policy {
this.enablePcAttributeValidation = enablePcAttributeValidation;
}
/**
* Returns whether or not to validate the firmware on the device.
*
* @return whether or not to validate the firmware.
*/
public boolean isFirmwareValidationEnabled() {
return enableFirmwareValidation;
}
/**
* Sets whether or not to validate the firmware on the device.
*
* @param enableFirmwareValidation whether or not to validate the firmware.
*/
public void setFirmwareValidationEnabled(final boolean enableFirmwareValidation) {
this.enableFirmwareValidation = enableFirmwareValidation;
}
/**
* Returns whether or not to allow expired credentials and certificates to be considered
* valid if their supply chain is otherwise verified.

View File

@ -34,7 +34,12 @@ public class SupplyChainValidation extends ArchivableEntity {
/**
* Validation of a platform credential's attributes.
*/
PLATFORM_CREDENTIAL_ATTRIBUTES
PLATFORM_CREDENTIAL_ATTRIBUTES,
/**
* Validation of the device firmware.
*/
FIRMWARE
}
@Column

View File

@ -10,6 +10,7 @@ import java.util.Set;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
@ -20,6 +21,8 @@ import javax.persistence.ManyToOne;
@Entity
public class IssuedAttestationCertificate extends DeviceAssociatedCertificate {
private static final int MAX_CERT_LENGTH_BYTES = 4096;
/**
* AIC label that must be used.
*/
@ -33,6 +36,9 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate {
@JoinColumn(name = "pc_id")
private Set<PlatformCredential> platformCredentials;
@Column(nullable = true, length = MAX_CERT_LENGTH_BYTES)
private String pcrValues;
/**
* This class enables the retrieval of IssuedAttestationCertificate by their attributes.
*/
@ -123,4 +129,20 @@ public class IssuedAttestationCertificate extends DeviceAssociatedCertificate {
public Set<PlatformCredential> getPlatformCredentials() {
return Collections.unmodifiableSet(platformCredentials);
}
/**
* Getter for the pcrValues passed up by the client.
* @return a string blob of pcrs
*/
public String getPcrValues() {
return pcrValues;
}
/**
* Setter for the pcrValues passed up by the client.
* @param pcrValues to be stored.
*/
public void setPcrValues(final String pcrValues) {
this.pcrValues = pcrValues;
}
}

View File

@ -24,6 +24,8 @@ import java.util.UUID;
* @param <ReferenceManifest> the type of referenceManifest that will be retrieved
*/
public abstract class ReferenceManifestSelector<ReferenceManifest> {
private static final String PLATFORM_MANUFACTURER = "platformManufacturer";
private static final String PLATFORM_MODEL = "platformModel";
private final ReferenceManifestManager referenceManifestManager;
@ -68,6 +70,28 @@ public abstract class ReferenceManifestSelector<ReferenceManifest> {
return this;
}
/**
* Specify the platform manufacturer that rims must have to be considered
* as matching.
* @param manufacturer string for the manufacturer
* @return this instance
*/
public ReferenceManifestSelector byManufacturer(final String manufacturer) {
setFieldValue(PLATFORM_MANUFACTURER, manufacturer);
return this;
}
/**
* Specify the platform model that rims must have to be considered
* as matching.
* @param model string for the model
* @return this instance
*/
public ReferenceManifestSelector byModel(final String model) {
setFieldValue(PLATFORM_MODEL, model);
return this;
}
/**
* Specify the hash code of the bytes that rim must match.
*

View File

@ -90,6 +90,11 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
public static final String PLATFORM_ATTRIBUTES_VALID =
"Platform credential attributes validated";
/**
* AppraisalStatus message for a valid platform credential appraisal.
*/
public static final String FIRMWARE_VALID = "Firmware validated";
private static final Map<PlatformCredential, StringBuilder> DELTA_FAILURES = new HashMap<>();
/*