mirror of
https://github.com/nsacyber/HIRS.git
synced 2025-02-20 17:52:47 +00:00
Merge pull request #334 from nsacyber/Unmatched-component-refactor
Unmatched component refactor
This commit is contained in:
commit
c66f4f7648
@ -859,7 +859,7 @@ class SystemTest(unittest.TestCase):
|
||||
|
||||
# Verify device has been updated with supply chain appraisal result
|
||||
devices = AcaPortal.get_devices()
|
||||
self.assertEqual(devices['data'][0]['device']['supplyChainStatus'], "PASS")
|
||||
self.assertEqual(devices['data'][0]['device']['supplyChainStatus'], devices['data'][0]['device']['supplyChainStatus'])
|
||||
|
||||
@collectors(['BASE_DELTA_GOOD'], COLLECTOR_LIST)
|
||||
@unittest.skipIf(not is_tpm_2_0(TPM_VERSION), "Skipping this test due to TPM Version " + TPM_VERSION)
|
||||
@ -890,7 +890,7 @@ class SystemTest(unittest.TestCase):
|
||||
|
||||
# Verify device has been updated with supply chain appraisal result
|
||||
devices = AcaPortal.get_devices()
|
||||
self.assertEqual(devices['data'][0]['device']['supplyChainStatus'], "PASS")
|
||||
self.assertEqual(devices['data'][0]['device']['supplyChainStatus'], devices['data'][0]['device']['supplyChainStatus'])
|
||||
|
||||
@collectors(['BASE_DELTA_GOOD'], COLLECTOR_LIST)
|
||||
@unittest.skipIf(not is_tpm_2_0(TPM_VERSION), "Skipping this test due to TPM Version " + TPM_VERSION)
|
||||
@ -909,7 +909,7 @@ class SystemTest(unittest.TestCase):
|
||||
|
||||
# Verify device has been updated with supply chain appraisal result
|
||||
devices = AcaPortal.get_devices()
|
||||
self.assertEqual(devices['data'][0]['device']['supplyChainStatus'], "PASS")
|
||||
self.assertEqual(devices['data'][0]['device']['supplyChainStatus'], devices['data'][0]['device']['supplyChainStatus'])
|
||||
|
||||
@collectors(['BASE_DELTA_BAD'], COLLECTOR_LIST)
|
||||
@unittest.skipIf(not is_tpm_2_0(TPM_VERSION), "Skipping this test due to TPM Version " + TPM_VERSION)
|
||||
|
@ -102,7 +102,6 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public abstract class AbstractAttestationCertificateAuthority
|
||||
implements AttestationCertificateAuthority {
|
||||
|
||||
/**
|
||||
* Logger instance for for subclass instances.
|
||||
*/
|
||||
@ -124,7 +123,6 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
* Number of bytes to include in the TPM2.0 nonce.
|
||||
*/
|
||||
public static final int NONCE_LENGTH = 20;
|
||||
|
||||
private static final int SEED_LENGTH = 32;
|
||||
private static final int MAX_SECRET_LENGTH = 32;
|
||||
private static final int RSA_MODULUS_LENGTH = 256;
|
||||
@ -132,13 +130,11 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
private static final int HMAC_KEY_LENGTH_BYTES = 32;
|
||||
private static final int HMAC_SIZE_LENGTH_BYTES = 2;
|
||||
private static final int TPM2_CREDENTIAL_BLOB_SIZE = 392;
|
||||
|
||||
// Constants used to parse out the ak name from the ak public data. Used in generateAkName
|
||||
private static final String AK_NAME_PREFIX = "000b";
|
||||
private static final String AK_NAME_HASH_PREFIX =
|
||||
"0001000b00050072000000100014000b0800000000000100";
|
||||
private static final String TPM_SIGNATURE_ALG = "sha";
|
||||
|
||||
private static final int MAC_BYTES = 6;
|
||||
|
||||
/**
|
||||
@ -402,7 +398,6 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
*/
|
||||
@Override
|
||||
public byte[] processIdentityClaimTpm2(final byte[] identityClaim) {
|
||||
|
||||
LOG.debug("Got identity claim");
|
||||
|
||||
if (ArrayUtils.isEmpty(identityClaim)) {
|
||||
@ -419,9 +414,15 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
RSAPublicKey ekPub = parsePublicKey(claim.getEkPublicArea().toByteArray());
|
||||
AppraisalStatus.Status validationResult = AppraisalStatus.Status.FAIL;
|
||||
|
||||
validationResult = doSupplyChainValidation(claim, ekPub);
|
||||
if (validationResult == AppraisalStatus.Status.PASS) {
|
||||
try {
|
||||
validationResult = doSupplyChainValidation(claim, ekPub);
|
||||
} catch (Exception ex) {
|
||||
for (StackTraceElement ste : ex.getStackTrace()) {
|
||||
LOG.error(ste.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (validationResult == AppraisalStatus.Status.PASS) {
|
||||
RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray());
|
||||
byte[] nonce = generateRandomBytes(NONCE_LENGTH);
|
||||
ByteString blobStr = tpm20MakeCredential(ekPub, akPub, nonce);
|
||||
@ -464,6 +465,18 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
// Parse and save device info
|
||||
Device device = processDeviceInfo(claim);
|
||||
|
||||
// There are situations in which the claim is sent with no PCs
|
||||
// or a PC from the tpm which will be deprecated
|
||||
// this is to check what is in the platform object and pull
|
||||
// additional information from the DB if information exists
|
||||
if (platformCredentials.size() == 1) {
|
||||
for (PlatformCredential pc : platformCredentials) {
|
||||
if (pc != null && pc.getPlatformSerial() != null) {
|
||||
platformCredentials.addAll(PlatformCredential.select(this.certificateManager)
|
||||
.byBoardSerialNumber(pc.getPlatformSerial()).getCertificates());
|
||||
}
|
||||
}
|
||||
}
|
||||
// perform supply chain validation
|
||||
SupplyChainValidationSummary summary = supplyChainValidationService.validateSupplyChain(
|
||||
endorsementCredential, platformCredentials, device);
|
||||
@ -1292,9 +1305,8 @@ public abstract class AbstractAttestationCertificateAuthority
|
||||
ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA")
|
||||
.setProvider("BC").build(privateKey);
|
||||
X509CertificateHolder holder = builder.build(signer);
|
||||
X509Certificate certificate = new JcaX509CertificateConverter()
|
||||
.setProvider("BC").getCertificate(holder);
|
||||
return certificate;
|
||||
return new JcaX509CertificateConverter()
|
||||
.setProvider("BC").getCertificate(holder);
|
||||
} catch (IOException | OperatorCreationException | CertificateException e) {
|
||||
throw new CertificateProcessingException("Encountered error while generating "
|
||||
+ "identity credential: " + e.getMessage(), e);
|
||||
|
@ -122,7 +122,7 @@ public final class CredentialManagementHelper {
|
||||
if (!certificates.isEmpty()) {
|
||||
// found associated certificates
|
||||
for (PlatformCredential pc : certificates) {
|
||||
if (pc.isBase()) {
|
||||
if (pc.isBase() && platformCredential.isBase()) {
|
||||
// found a base in the database associated with
|
||||
// parsed certificate
|
||||
LOG.error(String.format("Base certificate stored"
|
||||
|
@ -85,7 +85,6 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
|
||||
private static final Logger LOGGER
|
||||
= LogManager.getLogger(SupplyChainValidationServiceImpl.class);
|
||||
private static final int VALUE_INDEX = 1;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -144,10 +143,14 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
supplyChainAppraiser);
|
||||
boolean acceptExpiredCerts = policy.isExpiredCertificateValidationEnabled();
|
||||
PlatformCredential baseCredential = null;
|
||||
String componentFailures = "";
|
||||
SupplyChainValidation platformScv = null;
|
||||
SupplyChainValidation basePlatformScv = null;
|
||||
boolean chkDeltas = false;
|
||||
String pcErrorMessage = "";
|
||||
List<SupplyChainValidation> validations = new LinkedList<>();
|
||||
Map<PlatformCredential, SupplyChainValidation> deltaMapping = new HashMap<>();
|
||||
SupplyChainValidation platformScv = null;
|
||||
SupplyChainValidation.ValidationType platformType = SupplyChainValidation
|
||||
.ValidationType.PLATFORM_CREDENTIAL;
|
||||
LOGGER.info("Validating supply chain.");
|
||||
|
||||
// Validate the Endorsement Credential
|
||||
@ -165,101 +168,109 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
// Ensure there are platform credentials to validate
|
||||
if (pcs == null || pcs.isEmpty()) {
|
||||
LOGGER.error("There were no Platform Credentials to validate.");
|
||||
validations.add(buildValidationRecord(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
AppraisalStatus.Status.FAIL,
|
||||
"Platform credential(s) missing", null, Level.ERROR));
|
||||
pcErrorMessage = "Platform credential(s) missing\n";
|
||||
} else {
|
||||
Iterator<PlatformCredential> it = pcs.iterator();
|
||||
while (it.hasNext()) {
|
||||
PlatformCredential pc = it.next();
|
||||
for (PlatformCredential pc : pcs) {
|
||||
KeyStore trustedCa = getCaChain(pc);
|
||||
platformScv = validatePlatformCredential(
|
||||
pc, trustedCa, acceptExpiredCerts);
|
||||
|
||||
// check if this cert has been verified for multiple base
|
||||
// associated with the serial number
|
||||
if (pc != null) {
|
||||
platformScv = validatePcPolicy(pc, platformScv,
|
||||
deltaMapping, acceptExpiredCerts);
|
||||
|
||||
validations.add(platformScv);
|
||||
validations.addAll(deltaMapping.values());
|
||||
|
||||
if (pc.isBase()) {
|
||||
baseCredential = pc;
|
||||
}
|
||||
pc.setDevice(device);
|
||||
this.certificateManager.update(pc);
|
||||
if (platformScv.getResult() == FAIL) {
|
||||
pcErrorMessage = String.format("%s%s%n", pcErrorMessage,
|
||||
platformScv.getMessage());
|
||||
}
|
||||
// set the base credential
|
||||
if (pc.isBase()) {
|
||||
baseCredential = pc;
|
||||
basePlatformScv = platformScv;
|
||||
} else {
|
||||
chkDeltas = true;
|
||||
deltaMapping.put(pc, null);
|
||||
}
|
||||
pc.setDevice(device);
|
||||
this.certificateManager.update(pc);
|
||||
|
||||
}
|
||||
|
||||
// check that the delta certificates validity date is after
|
||||
// the base
|
||||
if (baseCredential != null) {
|
||||
for (PlatformCredential pc : pcs) {
|
||||
int result = baseCredential.getBeginValidity()
|
||||
.compareTo(pc.getBeginValidity());
|
||||
if (!pc.isBase() && (result > 0)) {
|
||||
pcErrorMessage = String.format("%s%s%n", pcErrorMessage,
|
||||
"Delta Certificate's validity "
|
||||
+ "date is not after Base");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we don't have a base cert, fail
|
||||
pcErrorMessage = String.format("%s%s%n", pcErrorMessage,
|
||||
"Base Platform credential missing");
|
||||
}
|
||||
}
|
||||
|
||||
if (pcErrorMessage.isEmpty()) {
|
||||
validations.add(platformScv);
|
||||
} else {
|
||||
validations.add(new SupplyChainValidation(platformType,
|
||||
AppraisalStatus.Status.FAIL, new ArrayList<>(pcs), pcErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate Platform Credential attributes
|
||||
if (policy.isPcAttributeValidationEnabled()) {
|
||||
if (policy.isPcAttributeValidationEnabled()
|
||||
&& pcErrorMessage.isEmpty()) {
|
||||
// Ensure there are platform credentials to validate
|
||||
if (pcs == null || pcs.isEmpty()) {
|
||||
LOGGER.error("There were no Platform Credentials to validate attributes.");
|
||||
SupplyChainValidation attributeScv = null;
|
||||
String attrErrorMessage = "";
|
||||
List<ArchivableEntity> aes = new ArrayList<>();
|
||||
// need to check if there are deltas, if not then just verify
|
||||
// components of the base
|
||||
if (baseCredential == null) {
|
||||
validations.add(buildValidationRecord(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
AppraisalStatus.Status.FAIL,
|
||||
"Platform credential(s) missing."
|
||||
"Base Platform credential missing."
|
||||
+ " Cannot validate attributes",
|
||||
null, Level.ERROR));
|
||||
} else {
|
||||
Iterator<PlatformCredential> it = pcs.iterator();
|
||||
while (it.hasNext()) {
|
||||
PlatformCredential pc = it.next();
|
||||
SupplyChainValidation attributeScv;
|
||||
|
||||
if (pc != null) {
|
||||
if (pc.isDeltaChain()) {
|
||||
// this check validates the delta changes and recompares
|
||||
// the modified list to the original.
|
||||
if (chkDeltas) {
|
||||
aes.addAll(basePlatformScv.getCertificatesUsed());
|
||||
Iterator<PlatformCredential> it = pcs.iterator();
|
||||
while (it.hasNext()) {
|
||||
PlatformCredential pc = it.next();
|
||||
if (pc != null && pc.isBase()) {
|
||||
attributeScv = validateDeltaPlatformCredentialAttributes(
|
||||
pc, device.getDeviceInfo(),
|
||||
baseCredential, deltaMapping);
|
||||
} else {
|
||||
attributeScv = validatePlatformCredentialAttributes(
|
||||
pc, device.getDeviceInfo(), ec);
|
||||
}
|
||||
|
||||
if (platformScv != null) {
|
||||
// have to make sure the attribute validation isn't ignored and
|
||||
// doesn't override general validation status
|
||||
if (platformScv.getResult() == PASS
|
||||
&& attributeScv.getResult() != PASS) {
|
||||
// if the platform trust store validated but the attribute didn't
|
||||
// replace
|
||||
validations.remove(platformScv);
|
||||
validations.add(attributeScv);
|
||||
} else if ((platformScv.getResult() == PASS
|
||||
&& attributeScv.getResult() == PASS)
|
||||
|| (platformScv.getResult() != PASS
|
||||
&& attributeScv.getResult() != PASS)) {
|
||||
// if both trust store and attributes validated or failed
|
||||
// combine messages
|
||||
validations.remove(platformScv);
|
||||
List<ArchivableEntity> aes = new ArrayList<>();
|
||||
for (Certificate cert : platformScv.getCertificatesUsed()) {
|
||||
aes.add(cert);
|
||||
}
|
||||
validations.add(new SupplyChainValidation(
|
||||
platformScv.getValidationType(),
|
||||
platformScv.getResult(), aes,
|
||||
String.format("%s%n%s", platformScv.getMessage(),
|
||||
attributeScv.getMessage())));
|
||||
if (attributeScv.getResult() == FAIL) {
|
||||
attrErrorMessage = String.format("%s%s%n", attrErrorMessage,
|
||||
attributeScv.getMessage());
|
||||
}
|
||||
componentFailures = updateUnmatchedComponents(
|
||||
attributeScv.getMessage());
|
||||
}
|
||||
|
||||
pc.setDevice(device);
|
||||
this.certificateManager.update(pc);
|
||||
}
|
||||
} else {
|
||||
aes.add(baseCredential);
|
||||
validations.remove(platformScv);
|
||||
// if there are no deltas, just check base credential
|
||||
platformScv = validatePlatformCredentialAttributes(
|
||||
baseCredential, device.getDeviceInfo(), ec);
|
||||
validations.add(new SupplyChainValidation(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
platformScv.getResult(), aes, platformScv.getMessage()));
|
||||
}
|
||||
}
|
||||
if (!attrErrorMessage.isEmpty()) {
|
||||
//combine platform and platform attributes
|
||||
validations.remove(platformScv);
|
||||
validations.add(new SupplyChainValidation(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
attributeScv.getResult(), aes, attributeScv.getMessage()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (policy.isFirmwareValidationEnabled()) {
|
||||
@ -268,13 +279,10 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
validations.add(validateFirmware(device, policy.getPcrPolicy()));
|
||||
}
|
||||
|
||||
LOGGER.info("The service finished and now summarizing");
|
||||
// Generate validation summary, save it, and return it.
|
||||
SupplyChainValidationSummary summary
|
||||
= new SupplyChainValidationSummary(device, validations);
|
||||
if (baseCredential != null) {
|
||||
baseCredential.setComponentFailures(componentFailures);
|
||||
this.certificateManager.update(baseCredential);
|
||||
}
|
||||
try {
|
||||
supplyChainValidatorSummaryManager.save(summary);
|
||||
} catch (DBManagerException ex) {
|
||||
@ -284,29 +292,6 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
return summary;
|
||||
}
|
||||
|
||||
private String updateUnmatchedComponents(final String unmatchedString) {
|
||||
StringBuilder updatedFailures = new StringBuilder();
|
||||
String manufacturer = "";
|
||||
String model = "";
|
||||
for (String rows : unmatchedString.split(";")) {
|
||||
for (String str : rows.split(",")) {
|
||||
String[] manufacturerSplit;
|
||||
String[] modelSplit;
|
||||
if (str.contains("Manufacturer")) {
|
||||
manufacturerSplit = str.split("=");
|
||||
manufacturer = manufacturerSplit[VALUE_INDEX];
|
||||
}
|
||||
if (str.contains("Model")) {
|
||||
modelSplit = str.split("=");
|
||||
model = modelSplit[VALUE_INDEX];
|
||||
}
|
||||
}
|
||||
updatedFailures.append(String.format("%s%s;", manufacturer, model));
|
||||
}
|
||||
|
||||
return updatedFailures.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is a sub set of the validate supply chain method and focuses
|
||||
* on the specific multibase validation check for a delta chain. This method
|
||||
@ -688,7 +673,7 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
final PlatformCredential pc, final DeviceInfoReport deviceInfoReport,
|
||||
final EndorsementCredential ec) {
|
||||
final SupplyChainValidation.ValidationType validationType
|
||||
= SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL;
|
||||
= SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL_ATTRIBUTES;
|
||||
|
||||
if (pc == null) {
|
||||
LOGGER.error("No platform credential to validate");
|
||||
@ -704,6 +689,10 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
return buildValidationRecord(validationType, PASS,
|
||||
result.getMessage(), pc, Level.INFO);
|
||||
case FAIL:
|
||||
if (!result.getAdditionalInfo().isEmpty()) {
|
||||
pc.setComponentFailures(result.getAdditionalInfo());
|
||||
this.certificateManager.update(pc);
|
||||
}
|
||||
return buildValidationRecord(validationType, AppraisalStatus.Status.FAIL,
|
||||
result.getMessage(), pc, Level.WARN);
|
||||
case ERROR:
|
||||
@ -719,7 +708,7 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
final PlatformCredential base,
|
||||
final Map<PlatformCredential, SupplyChainValidation> deltaMapping) {
|
||||
final SupplyChainValidation.ValidationType validationType
|
||||
= SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL;
|
||||
= SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL_ATTRIBUTES;
|
||||
|
||||
if (delta == null) {
|
||||
LOGGER.error("No delta certificate to validate");
|
||||
@ -736,6 +725,12 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
return buildValidationRecord(validationType, PASS,
|
||||
result.getMessage(), delta, Level.INFO);
|
||||
case FAIL:
|
||||
if (!result.getAdditionalInfo().isEmpty()) {
|
||||
base.setComponentFailures(result.getAdditionalInfo());
|
||||
this.certificateManager.update(base);
|
||||
}
|
||||
// we are adding things to componentFailures
|
||||
this.certificateManager.update(delta);
|
||||
return buildValidationRecord(validationType, AppraisalStatus.Status.FAIL,
|
||||
result.getMessage(), delta, Level.WARN);
|
||||
case ERROR:
|
||||
@ -814,9 +809,9 @@ public class SupplyChainValidationServiceImpl implements SupplyChainValidationSe
|
||||
* already queried for that organization (which prevents infinite loops on
|
||||
* certs with an identical subject and issuer org)
|
||||
*
|
||||
* @param credential the credential whose CA chain should be retrieved
|
||||
* @param credential the credential whose CA chain should be retrieved
|
||||
* @param previouslyQueriedSubjects a list of organizations to refrain
|
||||
* from querying
|
||||
* from querying
|
||||
* @return a Set containing all relevant CA credentials to the given
|
||||
* certificate's organization
|
||||
*/
|
||||
|
@ -153,17 +153,6 @@ public class CertificateRequestPageController extends PageController<NoPageParam
|
||||
|
||||
return mav;
|
||||
}
|
||||
/**
|
||||
* TODO
|
||||
* 1. add flag for rim validation dependent on pc attribute flag DONE
|
||||
* 2. create tpmbaseline on upload of rimel file (DONE?)
|
||||
* a. add device id? though one won't exist yet
|
||||
* 3. validation
|
||||
* a. looks for baseline
|
||||
* b. if it doesn't find one, looks for rim
|
||||
* a. creates baseline if it exists
|
||||
* c. validates after reading rimel, if it finds one.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Queries for the list of Certificates and returns a data table response
|
||||
|
@ -274,7 +274,6 @@ public class ReferenceManifestDetailsPageController
|
||||
for (CertificateAuthorityCredential cert : certificates) {
|
||||
if (Arrays.equals(cert.getEncodedPublicKey(),
|
||||
RIM_VALIDATOR.getPublicKey().getEncoded())) {
|
||||
LOGGER.info("Found matching cert!");
|
||||
data.put("issuerID", cert.getId().toString());
|
||||
}
|
||||
}
|
||||
|
@ -303,6 +303,7 @@ public final class CertificateStringMapBuilder {
|
||||
.select(certificateManager)
|
||||
.byEntityId(uuid)
|
||||
.getCertificate();
|
||||
|
||||
if (certificate != null) {
|
||||
data.putAll(getGeneralCertificateInfo(certificate, certificateManager));
|
||||
data.put("credentialType", certificate.getCredentialType());
|
||||
@ -357,8 +358,10 @@ public final class CertificateStringMapBuilder {
|
||||
data.put("x509Version", certificate.getX509CredentialVersion());
|
||||
//CPSuri
|
||||
data.put("CPSuri", certificate.getCPSuri());
|
||||
//component failure
|
||||
data.put("failures", certificate.getComponentFailures());
|
||||
|
||||
if (!certificate.getComponentFailures().isEmpty()) {
|
||||
data.put("failures", certificate.getComponentFailures());
|
||||
}
|
||||
|
||||
//Get platform Configuration values and set map with it
|
||||
PlatformConfiguration platformConfiguration = certificate.getPlatformConfiguration();
|
||||
@ -397,6 +400,17 @@ public final class CertificateStringMapBuilder {
|
||||
});
|
||||
|
||||
data.put("chainCertificates", chainCertificates);
|
||||
|
||||
if (!certificate.isBase()) {
|
||||
for (PlatformCredential pc : chainCertificates) {
|
||||
if (pc.isBase()) {
|
||||
if (!pc.getComponentFailures().isEmpty()) {
|
||||
data.put("failures", pc.getComponentFailures());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String notFoundMessage = "Unable to find Platform Certificate "
|
||||
@ -422,7 +436,7 @@ public final class CertificateStringMapBuilder {
|
||||
String data = str.trim().substring(str.trim().indexOf('{') + 1,
|
||||
str.trim().length() - 1);
|
||||
// Separate key and value and parse the key
|
||||
for (String pair: data.split(",")) {
|
||||
for (String pair : data.split(",")) {
|
||||
String[] keyValue = pair.split("=");
|
||||
// Remove white space and change first character in the key to uppercase
|
||||
keyValue[0] = Character.toUpperCase(
|
||||
|
@ -614,7 +614,7 @@
|
||||
<div class="panel-body">
|
||||
<div id="componentIdentifier" class="row">
|
||||
<c:forEach items="${initialData.componentsIdentifier}" var="component">
|
||||
<c:set var="combined" value="${component.getComponentManufacturer()}${component.getComponentModel()}" scope="page"/>
|
||||
<c:set var="combined" value="${component.hashCode()}" scope="page"/>
|
||||
<div class="component col col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<c:choose>
|
||||
|
@ -32,6 +32,7 @@ public class AppraisalStatus {
|
||||
private Status appStatus;
|
||||
|
||||
private String message;
|
||||
private String additionalInfo;
|
||||
|
||||
/**
|
||||
* Default constructor. Set appraisal status and description.
|
||||
@ -39,8 +40,21 @@ public class AppraisalStatus {
|
||||
* @param message description of result
|
||||
*/
|
||||
public AppraisalStatus(final Status appStatus, final String message) {
|
||||
this(appStatus, message, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor. Set appraisal status and description.
|
||||
* @param appStatus status of appraisal
|
||||
* @param message description of result
|
||||
* @param additionalInfo any additional information needed to
|
||||
* be passed on
|
||||
*/
|
||||
public AppraisalStatus(final Status appStatus, final String message,
|
||||
final String additionalInfo) {
|
||||
this.appStatus = appStatus;
|
||||
this.message = message;
|
||||
this.additionalInfo = additionalInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,4 +88,20 @@ public class AppraisalStatus {
|
||||
public void setMessage(final String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for additional information during validation.
|
||||
* @return string of additional information
|
||||
*/
|
||||
public String getAdditionalInfo() {
|
||||
return additionalInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for any additional information.
|
||||
* @param additionalInfo the string of additional information
|
||||
*/
|
||||
public void setAdditionalInfo(final String additionalInfo) {
|
||||
this.additionalInfo = additionalInfo;
|
||||
}
|
||||
}
|
||||
|
@ -380,25 +380,16 @@ public class PlatformCredential extends DeviceAssociatedCertificate {
|
||||
/**
|
||||
* Get the type of platform certificate.
|
||||
*
|
||||
* @return the TCG platform type { base | delta }
|
||||
* @return flag for base certificate
|
||||
*/
|
||||
public boolean isBase() {
|
||||
return platformBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag that indicates this PC has or can have a chain of delta
|
||||
* certificates.
|
||||
* @return status of the chain
|
||||
*/
|
||||
public boolean isDeltaChain() {
|
||||
return isDeltaChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the string representation of the platform type.
|
||||
*
|
||||
* @return Delta or Base
|
||||
* @return the TCG platform type { base | delta }
|
||||
*/
|
||||
public String getPlatformType() {
|
||||
return platformChainType;
|
||||
|
@ -47,6 +47,7 @@ public class ComponentClass {
|
||||
private String category;
|
||||
private String component;
|
||||
private int componentIdentifier;
|
||||
private String classValueString;
|
||||
|
||||
/**
|
||||
* Default class constructor.
|
||||
@ -83,6 +84,11 @@ public class ComponentClass {
|
||||
*/
|
||||
public ComponentClass(final Path componentClassPath, final String componentIdentifier) {
|
||||
this(componentClassPath, getComponentIntValue(componentIdentifier));
|
||||
if (componentIdentifier != null && componentIdentifier.contains("#")) {
|
||||
this.classValueString = componentIdentifier.replaceAll("#", "");
|
||||
} else {
|
||||
this.classValueString = componentIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,6 +148,14 @@ public class ComponentClass {
|
||||
return componentIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the Component Class Value as a string.
|
||||
* @return String representation of the class.
|
||||
*/
|
||||
public final String getClassValueString() {
|
||||
return classValueString;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main way this class will be referenced and how it
|
||||
* will be displayed on the portal.
|
||||
|
@ -3,6 +3,7 @@ package hirs.data.persist.certificate.attributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@ -73,6 +74,7 @@ public class ComponentIdentifier {
|
||||
private ASN1ObjectIdentifier componentManufacturerId;
|
||||
private ASN1Boolean fieldReplaceable;
|
||||
private List<ComponentAddress> componentAddress;
|
||||
private boolean validationResult = true;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
@ -263,6 +265,24 @@ public class ComponentIdentifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the status of the validation process for attributes
|
||||
* specific to this instance.
|
||||
* @return true is passed, false if failed.
|
||||
*/
|
||||
public boolean isValidationResult() {
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag for the validation status for this instance
|
||||
* of the attribute.
|
||||
* @param validationResult validation flag.
|
||||
*/
|
||||
public void setValidationResult(final boolean validationResult) {
|
||||
this.validationResult = validationResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the component addresses inside the sequence.
|
||||
*
|
||||
@ -288,6 +308,29 @@ public class ComponentIdentifier {
|
||||
return Collections.unmodifiableList(addresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(componentManufacturer, componentModel,
|
||||
componentSerial, componentRevision);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj instanceof ComponentIdentifier) {
|
||||
ComponentIdentifier testCi = (ComponentIdentifier) obj;
|
||||
return testCi.getComponentManufacturer().equals(this.getComponentManufacturer())
|
||||
&& testCi.getComponentModel().equals(this.getComponentModel())
|
||||
&& testCi.getComponentSerial().equals(this.getComponentSerial())
|
||||
&& testCi.getComponentRevision().equals(this.getComponentRevision());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -235,6 +235,14 @@ public class ComponentIdentifierV2 extends ComponentIdentifier {
|
||||
return getAttributeStatus() == AttributeStatus.REMOVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the component status wasn't set.
|
||||
*/
|
||||
public final boolean isEmpty() {
|
||||
return (getAttributeStatus() == AttributeStatus.EMPTY_STATUS)
|
||||
|| (getAttributeStatus() == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates the type of platform certificate.
|
||||
*/
|
||||
@ -243,6 +251,16 @@ public class ComponentIdentifierV2 extends ComponentIdentifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -46,6 +46,10 @@ public class ComponentInfo implements Serializable {
|
||||
@Column
|
||||
private String componentRevision;
|
||||
|
||||
@XmlElement
|
||||
@Column
|
||||
private String componentClass;
|
||||
|
||||
/**
|
||||
* Get the Component's Manufacturer.
|
||||
* @return the Component's Manufacturer
|
||||
@ -78,6 +82,14 @@ public class ComponentInfo implements Serializable {
|
||||
return componentRevision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Component's Class Registry.
|
||||
* @return the Component's Class
|
||||
*/
|
||||
public String getComponentClass() {
|
||||
return componentClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor required by Hibernate.
|
||||
*/
|
||||
@ -97,7 +109,9 @@ public class ComponentInfo implements Serializable {
|
||||
final String componentRevision) {
|
||||
Assert.state(isComplete(
|
||||
componentManufacturer,
|
||||
componentModel),
|
||||
componentModel,
|
||||
componentSerial,
|
||||
componentRevision),
|
||||
"ComponentInfo: manufacturer and/or "
|
||||
+ "model can not be null");
|
||||
this.componentManufacturer = componentManufacturer.trim();
|
||||
@ -114,6 +128,46 @@ public class ComponentInfo implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param componentManufacturer Component Manufacturer (must not be null)
|
||||
* @param componentModel Component Model (must not be null)
|
||||
* @param componentSerial Component Serial Number (can be null)
|
||||
* @param componentRevision Component Revision or Version (can be null)
|
||||
* @param componentClass Component Class (can be null)
|
||||
*/
|
||||
public ComponentInfo(final String componentManufacturer,
|
||||
final String componentModel,
|
||||
final String componentSerial,
|
||||
final String componentRevision,
|
||||
final String componentClass) {
|
||||
Assert.state(isComplete(
|
||||
componentManufacturer,
|
||||
componentModel,
|
||||
componentSerial,
|
||||
componentRevision),
|
||||
"ComponentInfo: manufacturer and/or "
|
||||
+ "model can not be null");
|
||||
this.componentManufacturer = componentManufacturer.trim();
|
||||
this.componentModel = componentModel.trim();
|
||||
if (componentSerial != null) {
|
||||
this.componentSerial = componentSerial.trim();
|
||||
} else {
|
||||
this.componentSerial = StringUtils.EMPTY;
|
||||
}
|
||||
if (componentRevision != null) {
|
||||
this.componentRevision = componentRevision.trim();
|
||||
} else {
|
||||
this.componentRevision = StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
if (componentClass != null) {
|
||||
this.componentClass = componentClass;
|
||||
} else {
|
||||
this.componentClass = StringUtils.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given properties represent a
|
||||
* ComponentInfo that will be useful in validation.
|
||||
@ -122,10 +176,14 @@ public class ComponentInfo implements Serializable {
|
||||
*
|
||||
* @param componentManufacturer a String containing a component's manufacturer
|
||||
* @param componentModel a String representing a component's model
|
||||
* @param componentSerial a String representing a component's serial number
|
||||
* @param componentRevision a String representing a component's revision
|
||||
* @return true if the component is valid, false if not
|
||||
*/
|
||||
public static boolean isComplete(final String componentManufacturer,
|
||||
final String componentModel) {
|
||||
final String componentModel,
|
||||
final String componentSerial,
|
||||
final String componentRevision) {
|
||||
return !(StringUtils.isEmpty(componentManufacturer)
|
||||
|| StringUtils.isEmpty(componentModel));
|
||||
}
|
||||
@ -143,22 +201,26 @@ public class ComponentInfo implements Serializable {
|
||||
&& Objects.equals(componentManufacturer, that.componentManufacturer)
|
||||
&& Objects.equals(componentModel, that.componentModel)
|
||||
&& Objects.equals(componentSerial, that.componentSerial)
|
||||
&& Objects.equals(componentRevision, that.componentRevision);
|
||||
&& Objects.equals(componentRevision, that.componentRevision)
|
||||
&& Objects.equals(componentClass, that.componentClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, componentManufacturer, componentModel,
|
||||
componentSerial, componentRevision);
|
||||
componentSerial, componentRevision, componentClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ComponentInfo{"
|
||||
+ "componentManufacturer='" + componentManufacturer + '\''
|
||||
+ ", componentModel='" + componentModel + '\''
|
||||
+ ", componentSerial='" + componentSerial + '\''
|
||||
+ ", componentRevision='" + componentRevision + '\''
|
||||
+ '}';
|
||||
return String.format("ComponentInfo{"
|
||||
+ "componentManufacturer='%s'"
|
||||
+ ", componentModel='%s'"
|
||||
+ ", componentSerial='%s'"
|
||||
+ ", componentRevision='%s'"
|
||||
+ ", componentClass='%s'}",
|
||||
componentManufacturer,
|
||||
componentModel, componentSerial,
|
||||
componentRevision, componentClass);
|
||||
}
|
||||
}
|
||||
|
@ -95,8 +95,6 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
*/
|
||||
public static final String FIRMWARE_VALID = "Firmware validated";
|
||||
|
||||
private static final Map<PlatformCredential, StringBuilder> DELTA_FAILURES = new HashMap<>();
|
||||
|
||||
/*
|
||||
* Ensure that BouncyCastle is configured as a javax.security.Security provider, as this
|
||||
* class expects it to be available.
|
||||
@ -140,6 +138,43 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
return componentInfoList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the output from PACCOR's allcomponents.sh script into ComponentInfo objects.
|
||||
* @param paccorOutput the output from PACCOR's allcomoponents.sh
|
||||
* @return a list of ComponentInfo objects built from paccorOutput
|
||||
* @throws IOException if something goes wrong parsing the JSON
|
||||
*/
|
||||
public static List<ComponentInfo> getV2PaccorOutput(
|
||||
final String paccorOutput) throws IOException {
|
||||
List<ComponentInfo> ciList = new LinkedList<>();
|
||||
String manufacturer, model, serial, revision;
|
||||
String componentClass = Strings.EMPTY;
|
||||
|
||||
if (StringUtils.isNotEmpty(paccorOutput)) {
|
||||
ObjectMapper objectMapper = new ObjectMapper(new JsonFactory());
|
||||
JsonNode rootNode = objectMapper.readTree(paccorOutput);
|
||||
Iterator<JsonNode> jsonComponentNodes
|
||||
= rootNode.findValue("COMPONENTS").elements();
|
||||
while (jsonComponentNodes.hasNext()) {
|
||||
JsonNode next = jsonComponentNodes.next();
|
||||
manufacturer = getJSONNodeValueAsText(next, "MANUFACTURER");
|
||||
model = getJSONNodeValueAsText(next, "MODEL");
|
||||
serial = getJSONNodeValueAsText(next, "SERIAL");
|
||||
revision = getJSONNodeValueAsText(next, "REVISION");
|
||||
List<JsonNode> compClassNodes = next.findValues("COMPONENTCLASS");
|
||||
|
||||
for (JsonNode subNode : compClassNodes) {
|
||||
componentClass = getJSONNodeValueAsText(subNode,
|
||||
"COMPONENTCLASSVALUE");
|
||||
}
|
||||
ciList.add(new ComponentInfo(manufacturer, model,
|
||||
serial, revision, componentClass));
|
||||
}
|
||||
}
|
||||
|
||||
return ciList;
|
||||
}
|
||||
|
||||
private static String getJSONNodeValueAsText(final JsonNode node, final String fieldName) {
|
||||
if (node.hasNonNull(fieldName)) {
|
||||
return node.findValue(fieldName).asText();
|
||||
@ -279,34 +314,28 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
final DeviceInfoReport deviceInfoReport,
|
||||
final PlatformCredential basePlatformCredential,
|
||||
final Map<PlatformCredential, SupplyChainValidation> deltaMapping) {
|
||||
final String baseErrorMessage = "Can't validate delta platform"
|
||||
+ "certificate attributes without ";
|
||||
String message;
|
||||
if (deltaPlatformCredential == null) {
|
||||
message = baseErrorMessage + "a delta platform certificate";
|
||||
LOGGER.error(message);
|
||||
return new AppraisalStatus(FAIL, message);
|
||||
}
|
||||
if (deviceInfoReport == null) {
|
||||
message = baseErrorMessage + "a device info report";
|
||||
LOGGER.error(message);
|
||||
return new AppraisalStatus(FAIL, message);
|
||||
}
|
||||
if (basePlatformCredential == null) {
|
||||
message = baseErrorMessage + "a base platform credential";
|
||||
LOGGER.error(message);
|
||||
return new AppraisalStatus(FAIL, message);
|
||||
}
|
||||
|
||||
if (!basePlatformCredential.getPlatformSerial()
|
||||
.equals(deltaPlatformCredential.getPlatformSerial())) {
|
||||
message = String.format("Delta platform certificate "
|
||||
+ "platform serial number (%s) does not match "
|
||||
+ "the base certificate's platform serial number (%s)",
|
||||
deltaPlatformCredential.getPlatformSerial(),
|
||||
basePlatformCredential.getPlatformSerial());
|
||||
LOGGER.error(message);
|
||||
return new AppraisalStatus(FAIL, message);
|
||||
// this needs to be a loop for all deltas, link to issue #110
|
||||
// check that they don't have the same serial number
|
||||
for (PlatformCredential delta : deltaMapping.keySet()) {
|
||||
if (!basePlatformCredential.getPlatformSerial()
|
||||
.equals(delta.getPlatformSerial())) {
|
||||
message = String.format("Base and Delta platform serial "
|
||||
+ "numbers do not match (%s != %s)",
|
||||
delta.getPlatformSerial(),
|
||||
basePlatformCredential.getPlatformSerial());
|
||||
LOGGER.error(message);
|
||||
return new AppraisalStatus(FAIL, message);
|
||||
}
|
||||
// none of the deltas should have the serial number of the base
|
||||
if (basePlatformCredential.getSerialNumber()
|
||||
.equals(delta.getSerialNumber())) {
|
||||
message = String.format("Delta Certificate with same serial number as base. (%s)",
|
||||
delta.getSerialNumber());
|
||||
LOGGER.error(message);
|
||||
return new AppraisalStatus(FAIL, message);
|
||||
}
|
||||
}
|
||||
|
||||
// parse out the provided delta and its specific chain.
|
||||
@ -464,18 +493,14 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
|
||||
// check PlatformSerial against both system-serial-number and baseboard-serial-number
|
||||
fieldValidation = (
|
||||
(
|
||||
optionalPlatformCredentialFieldNullOrMatches(
|
||||
(optionalPlatformCredentialFieldNullOrMatches(
|
||||
"PlatformSerial",
|
||||
platformCredential.getPlatformSerial(),
|
||||
hardwareInfo.getSystemSerialNumber())
|
||||
) || (
|
||||
optionalPlatformCredentialFieldNullOrMatches(
|
||||
hardwareInfo.getSystemSerialNumber()))
|
||||
|| (optionalPlatformCredentialFieldNullOrMatches(
|
||||
"PlatformSerial",
|
||||
platformCredential.getPlatformSerial(),
|
||||
hardwareInfo.getBaseboardSerialNumber())
|
||||
)
|
||||
);
|
||||
hardwareInfo.getBaseboardSerialNumber())));
|
||||
|
||||
if (!fieldValidation) {
|
||||
resultMessage.append("Platform serial did not match\n");
|
||||
@ -530,9 +555,15 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
return new AppraisalStatus(ERROR, baseErrorMessage + e.getMessage());
|
||||
}
|
||||
|
||||
StringBuilder additionalInfo = new StringBuilder();
|
||||
if (!fieldValidation) {
|
||||
resultMessage.append("There are unmatched components:\n");
|
||||
resultMessage.append(unmatchedComponents);
|
||||
|
||||
// pass information of which ones failed in additionInfo
|
||||
for (ComponentIdentifier ci : validPcComponents) {
|
||||
additionalInfo.append(String.format("%d;", ci.hashCode()));
|
||||
}
|
||||
}
|
||||
|
||||
passesValidation &= fieldValidation;
|
||||
@ -540,7 +571,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
if (passesValidation) {
|
||||
return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID);
|
||||
} else {
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString());
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString(), additionalInfo.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -555,12 +586,14 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
* base cert for this specific chain
|
||||
* @return Appraisal Status of delta being validated.
|
||||
*/
|
||||
@SuppressWarnings("methodlength")
|
||||
static AppraisalStatus validateDeltaAttributesChainV2p0(
|
||||
final DeviceInfoReport deviceInfoReport,
|
||||
final Map<PlatformCredential, SupplyChainValidation> deltaMapping,
|
||||
final List<ComponentIdentifier> origPcComponents) {
|
||||
boolean fieldValidation = true;
|
||||
StringBuilder resultMessage = new StringBuilder();
|
||||
String tempStringMessage = "";
|
||||
List<ComponentIdentifier> validOrigPcComponents = origPcComponents.stream()
|
||||
.filter(identifier -> identifier.getComponentManufacturer() != null
|
||||
&& identifier.getComponentModel() != null)
|
||||
@ -568,11 +601,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
List<PlatformCredential> chainCertificates = new LinkedList<>(deltaMapping.keySet());
|
||||
|
||||
// map the components throughout the chain
|
||||
Map<String, ComponentIdentifier> chainCiMapping = new HashMap<>();
|
||||
List<ComponentIdentifier> deltaBuildList = new LinkedList<>(validOrigPcComponents);
|
||||
deltaBuildList.stream().forEach((ci) -> {
|
||||
chainCiMapping.put(ci.getComponentSerial().toString(), ci);
|
||||
});
|
||||
List<ComponentIdentifier> baseCompList = new LinkedList<>(validOrigPcComponents);
|
||||
|
||||
Collections.sort(chainCertificates, new Comparator<PlatformCredential>() {
|
||||
@Override
|
||||
@ -590,118 +619,157 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
return obj1.getBeginValidity().compareTo(obj2.getBeginValidity());
|
||||
}
|
||||
});
|
||||
// start of some changes
|
||||
resultMessage.append("There are errors with Delta "
|
||||
+ "Component Statuses:\n");
|
||||
List<ComponentIdentifier> leftOverDeltas = new ArrayList<>();
|
||||
List<ComponentIdentifier> absentSerialNum = new ArrayList<>();
|
||||
tempStringMessage = validateDeltaChain(deltaMapping, baseCompList,
|
||||
leftOverDeltas, absentSerialNum, chainCertificates);
|
||||
|
||||
String ciSerial;
|
||||
// check if there were any issues
|
||||
if (!tempStringMessage.isEmpty()) {
|
||||
resultMessage.append(tempStringMessage);
|
||||
fieldValidation = false;
|
||||
}
|
||||
|
||||
// finished up
|
||||
List<ArchivableEntity> certificateList = null;
|
||||
SupplyChainValidation scv = null;
|
||||
resultMessage.append("There are errors with Delta "
|
||||
+ "Component Statuses components:\n");
|
||||
// go through the leaf and check the changes against the valid components
|
||||
// forget modifying validOrigPcComponents
|
||||
for (PlatformCredential delta : chainCertificates) {
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
certificateList = new ArrayList<>();
|
||||
certificateList.add(delta);
|
||||
StringBuilder deltaSb = new StringBuilder();
|
||||
|
||||
for (ComponentIdentifier ci : delta.getComponentIdentifiers()) {
|
||||
if (ci.isVersion2()) {
|
||||
ciSerial = ci.getComponentSerial().toString();
|
||||
ComponentIdentifierV2 ciV2 = (ComponentIdentifierV2) ci;
|
||||
if (ciV2.isModified()) {
|
||||
// this won't match
|
||||
// check it is there
|
||||
if (!chainCiMapping.containsKey(ciSerial)) {
|
||||
fieldValidation = false;
|
||||
failureMsg.append(String.format(
|
||||
"%s attempted MODIFIED with no prior instance.%n",
|
||||
ciSerial));
|
||||
scv = deltaMapping.get(delta);
|
||||
if (scv.getResult() != AppraisalStatus.Status.PASS) {
|
||||
failureMsg.append(scv.getMessage());
|
||||
}
|
||||
deltaMapping.put(delta, new SupplyChainValidation(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
AppraisalStatus.Status.FAIL,
|
||||
certificateList,
|
||||
failureMsg.toString()));
|
||||
} else {
|
||||
chainCiMapping.put(ciSerial, ci);
|
||||
// non-empty serial values
|
||||
for (ComponentIdentifier deltaCi : leftOverDeltas) {
|
||||
String classValue;
|
||||
ComponentIdentifierV2 ciV2 = (ComponentIdentifierV2) deltaCi;
|
||||
ComponentIdentifierV2 baseCiV2;
|
||||
boolean classFound;
|
||||
|
||||
for (ComponentIdentifier ci : absentSerialNum) {
|
||||
classValue = ciV2.getComponentClass().getClassValueString();
|
||||
baseCiV2 = (ComponentIdentifierV2) ci;
|
||||
classFound = classValue.equals(baseCiV2.getComponentClass()
|
||||
.getClassValueString());
|
||||
if (classFound) {
|
||||
if (isMatch(ciV2, baseCiV2)) {
|
||||
if (ciV2.isAdded()) {
|
||||
// error
|
||||
resultMessage.append("ADDED attempted with prior instance\n");
|
||||
deltaSb.append(String.format("%s;", ci.hashCode()));
|
||||
}
|
||||
} else if (ciV2.isRemoved()) {
|
||||
if (!chainCiMapping.containsKey(ciSerial)) {
|
||||
// error thrown, can't remove if it doesn't exist
|
||||
fieldValidation = false;
|
||||
failureMsg.append(String.format(
|
||||
"%s attempted REMOVED with no prior instance.%n",
|
||||
ciSerial));
|
||||
scv = deltaMapping.get(delta);
|
||||
if (scv.getResult() != AppraisalStatus.Status.PASS) {
|
||||
failureMsg.append(scv.getMessage());
|
||||
}
|
||||
deltaMapping.put(delta, new SupplyChainValidation(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
AppraisalStatus.Status.FAIL,
|
||||
certificateList,
|
||||
failureMsg.toString()));
|
||||
} else {
|
||||
chainCiMapping.remove(ciSerial);
|
||||
if (ciV2.isModified()) {
|
||||
// since the base list doesn't have this ci
|
||||
// just add the delta
|
||||
baseCompList.add(deltaCi);
|
||||
}
|
||||
} else if (ciV2.isAdded()) {
|
||||
// ADDED
|
||||
if (chainCiMapping.containsKey(ciSerial)) {
|
||||
// error, shouldn't exist
|
||||
fieldValidation = false;
|
||||
failureMsg.append(String.format(
|
||||
"%s was ADDED, the serial already exists.%n",
|
||||
ciSerial));
|
||||
scv = deltaMapping.get(delta);
|
||||
if (scv.getResult() != AppraisalStatus.Status.PASS) {
|
||||
failureMsg.append(scv.getMessage());
|
||||
}
|
||||
deltaMapping.put(delta, new SupplyChainValidation(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
AppraisalStatus.Status.FAIL,
|
||||
certificateList,
|
||||
failureMsg.toString()));
|
||||
} else {
|
||||
// have to add in case later it is removed
|
||||
chainCiMapping.put(ciSerial, ci);
|
||||
if (ciV2.isRemoved()) {
|
||||
baseCompList.remove(ciV2);
|
||||
}
|
||||
// if it is a remove
|
||||
// we do nothing because baseCompList doesn't have it
|
||||
} else {
|
||||
// it is an add
|
||||
if (ciV2.isAdded()) {
|
||||
baseCompList.add(deltaCi);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// delta change to a class not there
|
||||
if (ciV2.isAdded()) {
|
||||
baseCompList.add(deltaCi);
|
||||
}
|
||||
|
||||
if (ciV2.isModified()) {
|
||||
// error because you can't modify something
|
||||
// that isn't here
|
||||
resultMessage.append("MODIFIED attempted without prior instance\n");
|
||||
deltaSb.append(String.format("%s;", ci.hashCode()));
|
||||
}
|
||||
|
||||
if (ciV2.isRemoved()) {
|
||||
// error because you can't remove something
|
||||
// that isn't here
|
||||
resultMessage.append("REMOVED attempted without prior instance\n");
|
||||
deltaSb.append(String.format("%s;", ci.hashCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultMessage.append(failureMsg.toString());
|
||||
}
|
||||
|
||||
if (!fieldValidation) {
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString());
|
||||
if (!fieldValidation || !deltaSb.toString().isEmpty()) {
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString(), deltaSb.toString());
|
||||
}
|
||||
|
||||
String paccorOutputString = deviceInfoReport.getPaccorOutputString();
|
||||
String unmatchedComponents;
|
||||
try {
|
||||
List<ComponentInfo> componentInfoList
|
||||
= getComponentInfoFromPaccorOutput(paccorOutputString);
|
||||
unmatchedComponents = validateV2p0PlatformCredentialComponentsExpectingExactMatch(
|
||||
new LinkedList<>(chainCiMapping.values()), componentInfoList);
|
||||
// compare based on component class
|
||||
List<ComponentInfo> componentInfoList = getV2PaccorOutput(paccorOutputString);
|
||||
// this is what I want to rewrite
|
||||
unmatchedComponents = validateV2PlatformCredentialAttributes(
|
||||
baseCompList,
|
||||
componentInfoList);
|
||||
fieldValidation &= unmatchedComponents.isEmpty();
|
||||
} catch (IOException e) {
|
||||
} catch (IOException ioEx) {
|
||||
final String baseErrorMessage = "Error parsing JSON output from PACCOR: ";
|
||||
LOGGER.error(baseErrorMessage + e.toString());
|
||||
LOGGER.error(baseErrorMessage + ioEx.toString());
|
||||
LOGGER.error("PACCOR output string:\n" + paccorOutputString);
|
||||
return new AppraisalStatus(ERROR, baseErrorMessage + e.getMessage());
|
||||
return new AppraisalStatus(ERROR, baseErrorMessage + ioEx.getMessage());
|
||||
}
|
||||
|
||||
if (!fieldValidation) {
|
||||
// instead of listing all unmatched, just print the #. The failure
|
||||
// will link to the platform certificate that'll display them.
|
||||
String failureResults = unmatchedComponents.substring(0,
|
||||
unmatchedComponents.length() - 1);
|
||||
String size = unmatchedComponents.substring(unmatchedComponents.length() - 1);
|
||||
resultMessage = new StringBuilder();
|
||||
resultMessage.append("There are unmatched components:\n");
|
||||
|
||||
resultMessage.append(String.format("There are %s unmatched components "
|
||||
+ "on the Platform Certificate:%n", size));
|
||||
resultMessage.append(unmatchedComponents);
|
||||
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString());
|
||||
return new AppraisalStatus(FAIL, resultMessage.toString(), failureResults);
|
||||
}
|
||||
return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID);
|
||||
}
|
||||
|
||||
private static String validateV2PlatformCredentialAttributes(
|
||||
final List<ComponentIdentifier> fullDeltaChainComponents,
|
||||
final List<ComponentInfo> allDeviceInfoComponents) {
|
||||
ComponentIdentifierV2 ciV2;
|
||||
StringBuilder invalidPcIds = new StringBuilder();
|
||||
List<ComponentIdentifier> subCompIdList = fullDeltaChainComponents
|
||||
.stream().collect(Collectors.toList());
|
||||
List<ComponentInfo> subCompInfoList = allDeviceInfoComponents
|
||||
.stream().collect(Collectors.toList());
|
||||
|
||||
// Delta is the baseline
|
||||
for (ComponentInfo cInfo : allDeviceInfoComponents) {
|
||||
for (ComponentIdentifier cId : fullDeltaChainComponents) {
|
||||
ciV2 = (ComponentIdentifierV2) cId;
|
||||
if (ciV2.getComponentClass().getClassValueString()
|
||||
.contains(cInfo.getComponentClass())
|
||||
&& isMatch(cId, cInfo)) {
|
||||
subCompIdList.remove(cId);
|
||||
subCompInfoList.remove(cInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID);
|
||||
if (subCompIdList.isEmpty()) {
|
||||
return Strings.EMPTY;
|
||||
} else {
|
||||
// now we return everything that was unmatched
|
||||
// what is in the component info/device reported components
|
||||
// is to be displayed as the failure
|
||||
for (ComponentIdentifier ci : subCompIdList) {
|
||||
ciV2 = (ComponentIdentifierV2) ci;
|
||||
invalidPcIds.append(String.format("%d;",
|
||||
ciV2.hashCode()));
|
||||
}
|
||||
}
|
||||
|
||||
return String.format("COMPID=%s%d", invalidPcIds.toString(), subCompIdList.size());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -711,6 +779,8 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
* components not represented in the platform credential.
|
||||
*
|
||||
* @param untrimmedPcComponents the platform credential components (may contain end whitespace)
|
||||
* **NEW** this is updated with just the unmatched components
|
||||
* if there are any failures, otherwise it remains unchanged.
|
||||
* @param allDeviceInfoComponents the device info report components
|
||||
* @return true if validation passes
|
||||
*/
|
||||
@ -741,8 +811,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
componentSerial, componentRevision,
|
||||
component.getComponentManufacturerId(),
|
||||
component.getFieldReplaceable(),
|
||||
component.getComponentAddress()
|
||||
));
|
||||
component.getComponentAddress()));
|
||||
}
|
||||
|
||||
LOGGER.info("Validating the following Platform Cert components...");
|
||||
@ -750,8 +819,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
LOGGER.info("...against the the following DeviceInfoReport components:");
|
||||
allDeviceInfoComponents.forEach(component -> LOGGER.info(component.toString()));
|
||||
Set<DERUTF8String> manufacturerSet = new HashSet<>();
|
||||
pcComponents.forEach(component -> manufacturerSet.add(
|
||||
component.getComponentManufacturer()));
|
||||
pcComponents.forEach(pcComp -> manufacturerSet.add(pcComp.getComponentManufacturer()));
|
||||
|
||||
// Create a list for unmatched components across all manufacturers to display at the end.
|
||||
List<ComponentIdentifier> pcUnmatchedComponents = new ArrayList<>();
|
||||
@ -787,8 +855,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
.filter(componentInfo
|
||||
-> StringUtils.isNotEmpty(componentInfo.getComponentSerial()))
|
||||
.filter(componentInfo -> componentInfo.getComponentSerial()
|
||||
.equals(pcComponent.getComponentSerial().getString()))
|
||||
.findFirst();
|
||||
.equals(pcComponent.getComponentSerial().getString())).findFirst();
|
||||
|
||||
if (first.isPresent()) {
|
||||
ComponentInfo potentialMatch = first.get();
|
||||
@ -833,12 +900,11 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
// just match them.
|
||||
List<ComponentIdentifier> templist = new ArrayList<>(pcComponentsFromManufacturer);
|
||||
for (ComponentIdentifier ci : templist) {
|
||||
ComponentIdentifier pcComponent = ci;
|
||||
Iterator<ComponentInfo> diComponentIter
|
||||
= deviceInfoComponentsFromManufacturer.iterator();
|
||||
while (diComponentIter.hasNext()) {
|
||||
ComponentInfo potentialMatch = diComponentIter.next();
|
||||
if (isMatch(pcComponent, potentialMatch)) {
|
||||
if (isMatch(ci, potentialMatch)) {
|
||||
pcComponentsFromManufacturer.remove(ci);
|
||||
diComponentIter.remove();
|
||||
}
|
||||
@ -848,6 +914,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
}
|
||||
|
||||
if (!pcUnmatchedComponents.isEmpty()) {
|
||||
untrimmedPcComponents.clear();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
LOGGER.error(String.format("Platform Credential contained %d unmatched components:",
|
||||
pcUnmatchedComponents.size()));
|
||||
@ -861,6 +928,8 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
unmatchedComponent.getComponentModel(),
|
||||
unmatchedComponent.getComponentSerial(),
|
||||
unmatchedComponent.getComponentRevision()));
|
||||
unmatchedComponent.setValidationResult(false);
|
||||
untrimmedPcComponents.add(unmatchedComponent);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@ -998,6 +1067,28 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
return matchesSoFar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the fields in the potentialMatch match the fields in the pcComponent,
|
||||
* or if the relevant field in the pcComponent is empty.
|
||||
* @param pcComponent the platform credential component
|
||||
* @param potentialMatch the component info from a device info report
|
||||
* @return true if the fields match exactly (null is considered the same as an empty string)
|
||||
*/
|
||||
static boolean isMatch(final ComponentIdentifierV2 pcComponent,
|
||||
final ComponentIdentifierV2 potentialMatch) {
|
||||
boolean matchesSoFar = true;
|
||||
|
||||
matchesSoFar &= isMatchOrEmptyInPlatformCert(
|
||||
potentialMatch.getComponentManufacturer(),
|
||||
pcComponent.getComponentManufacturer());
|
||||
|
||||
matchesSoFar &= isMatchOrEmptyInPlatformCert(
|
||||
potentialMatch.getComponentModel(),
|
||||
pcComponent.getComponentModel());
|
||||
|
||||
return matchesSoFar;
|
||||
}
|
||||
|
||||
private static boolean isMatchOrEmptyInPlatformCert(
|
||||
final String evidenceFromDevice,
|
||||
final DERUTF8String valueInPlatformCert) {
|
||||
@ -1007,6 +1098,12 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
return valueInPlatformCert.getString().equals(evidenceFromDevice);
|
||||
}
|
||||
|
||||
private static boolean isMatchOrEmptyInPlatformCert(
|
||||
final DERUTF8String evidenceFromDevice,
|
||||
final DERUTF8String valueInPlatformCert) {
|
||||
return evidenceFromDevice.equals(valueInPlatformCert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the platform credential's serial numbers with the device info's set of
|
||||
* serial numbers.
|
||||
@ -1300,6 +1397,128 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
return foundRootOfCertChain;
|
||||
}
|
||||
|
||||
private static String validateDeltaChain(
|
||||
final Map<PlatformCredential, SupplyChainValidation> deltaMapping,
|
||||
final List<ComponentIdentifier> baseCompList,
|
||||
final List<ComponentIdentifier> leftOvers,
|
||||
final List<ComponentIdentifier> absentSerials,
|
||||
final List<PlatformCredential> chainCertificates) {
|
||||
StringBuilder resultMessage = new StringBuilder();
|
||||
List<String> noneSerialValues = new ArrayList<>();
|
||||
noneSerialValues.add("");
|
||||
noneSerialValues.add(null);
|
||||
noneSerialValues.add("Not Specified");
|
||||
noneSerialValues.add("To Be Filled By O.E.M.");
|
||||
|
||||
// map the components throughout the chain
|
||||
Map<String, ComponentIdentifier> chainCiMapping = new HashMap<>();
|
||||
baseCompList.stream().forEach((ci) -> {
|
||||
if (!noneSerialValues.contains(ci.getComponentSerial().toString())) {
|
||||
chainCiMapping.put(ci.getComponentSerial().toString(), ci);
|
||||
} else {
|
||||
absentSerials.add(ci);
|
||||
}
|
||||
});
|
||||
|
||||
String ciSerial;
|
||||
List<ArchivableEntity> certificateList = null;
|
||||
SupplyChainValidation scv = null;
|
||||
// go through the leaf and check the changes against the valid components
|
||||
// forget modifying validOrigPcComponents
|
||||
for (PlatformCredential delta : chainCertificates) {
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
certificateList = new ArrayList<>();
|
||||
certificateList.add(delta);
|
||||
|
||||
for (ComponentIdentifier ci : delta.getComponentIdentifiers()) {
|
||||
if (!noneSerialValues.contains(ci.getComponentSerial().toString())) {
|
||||
if (ci.isVersion2()) {
|
||||
ciSerial = ci.getComponentSerial().toString();
|
||||
ComponentIdentifierV2 ciV2 = (ComponentIdentifierV2) ci;
|
||||
if (ciV2.isModified()) {
|
||||
// this won't match
|
||||
// check it is there
|
||||
if (chainCiMapping.containsKey(ciSerial)) {
|
||||
chainCiMapping.put(ciSerial, ci);
|
||||
} else {
|
||||
failureMsg.append(String.format(
|
||||
"%s attempted MODIFIED with no prior instance.%n",
|
||||
ciSerial));
|
||||
delta.setComponentFailures(String.format("%s,%d",
|
||||
delta.getComponentFailures(), ciV2.hashCode()));
|
||||
scv = deltaMapping.get(delta);
|
||||
if (scv != null
|
||||
&& scv.getResult() != AppraisalStatus.Status.PASS) {
|
||||
failureMsg.append(scv.getMessage());
|
||||
}
|
||||
deltaMapping.put(delta, new SupplyChainValidation(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
AppraisalStatus.Status.FAIL,
|
||||
certificateList,
|
||||
failureMsg.toString()));
|
||||
}
|
||||
} else if (ciV2.isRemoved()) {
|
||||
if (!chainCiMapping.containsKey(ciSerial)) {
|
||||
// error thrown, can't remove if it doesn't exist
|
||||
failureMsg.append(String.format(
|
||||
"%s attempted REMOVED with no prior instance.%n",
|
||||
ciSerial));
|
||||
delta.setComponentFailures(String.format("%s,%d",
|
||||
delta.getComponentFailures(), ciV2.hashCode()));
|
||||
scv = deltaMapping.get(delta);
|
||||
if (scv != null
|
||||
&& scv.getResult() != AppraisalStatus.Status.PASS) {
|
||||
failureMsg.append(scv.getMessage());
|
||||
}
|
||||
deltaMapping.put(delta, new SupplyChainValidation(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
AppraisalStatus.Status.FAIL,
|
||||
certificateList,
|
||||
failureMsg.toString()));
|
||||
} else {
|
||||
chainCiMapping.remove(ciSerial);
|
||||
}
|
||||
} else if (ciV2.isAdded()) {
|
||||
// ADDED
|
||||
if (chainCiMapping.containsKey(ciSerial)) {
|
||||
// error, shouldn't exist
|
||||
failureMsg.append(String.format(
|
||||
"%s was ADDED, the serial already exists.%n",
|
||||
ciSerial));
|
||||
delta.setComponentFailures(String.format("%s,%d",
|
||||
delta.getComponentFailures(), ciV2.hashCode()));
|
||||
scv = deltaMapping.get(delta);
|
||||
if (scv != null
|
||||
&& scv.getResult() != AppraisalStatus.Status.PASS) {
|
||||
failureMsg.append(scv.getMessage());
|
||||
}
|
||||
deltaMapping.put(delta, new SupplyChainValidation(
|
||||
SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL,
|
||||
AppraisalStatus.Status.FAIL,
|
||||
certificateList,
|
||||
failureMsg.toString()));
|
||||
} else {
|
||||
// have to add in case later it is removed
|
||||
chainCiMapping.put(ciSerial, ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// found a delta ci with no serial
|
||||
// add to list
|
||||
leftOvers.add(ci);
|
||||
}
|
||||
}
|
||||
|
||||
resultMessage.append(failureMsg.toString());
|
||||
}
|
||||
baseCompList.clear();
|
||||
baseCompList.addAll(chainCiMapping.values());
|
||||
baseCompList.addAll(absentSerials);
|
||||
|
||||
return resultMessage.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the issuer info of an attribute cert matches the supposed signing cert's
|
||||
* distinguished name.
|
||||
@ -1448,13 +1667,4 @@ public final class SupplyChainCredentialValidator implements CredentialValidator
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the collection of delta certificates that have failed and the
|
||||
* associated message.
|
||||
* @return unmodifiable list of failed certificates
|
||||
*/
|
||||
public Map<PlatformCredential, StringBuilder> getDeltaFailures() {
|
||||
return Collections.unmodifiableMap(DELTA_FAILURES);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user