diff --git a/.ci/system-tests/system_test.py b/.ci/system-tests/system_test.py index 1a2077b6..ad7716f9 100644 --- a/.ci/system-tests/system_test.py +++ b/.ci/system-tests/system_test.py @@ -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) diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java index e49ba51e..d2366bee 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java @@ -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); diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/CredentialManagementHelper.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/CredentialManagementHelper.java index e1dbd778..fcb47793 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/CredentialManagementHelper.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/CredentialManagementHelper.java @@ -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" diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java index 465bd219..13dca0ab 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/service/SupplyChainValidationServiceImpl.java @@ -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 validations = new LinkedList<>(); Map 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 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 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 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 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 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 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 */ diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateRequestPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateRequestPageController.java index 9f3b4f08..80f428e8 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateRequestPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificateRequestPageController.java @@ -153,17 +153,6 @@ public class CertificateRequestPageController extends PageController
- +
diff --git a/HIRS_Utils/src/main/java/hirs/data/persist/AppraisalStatus.java b/HIRS_Utils/src/main/java/hirs/data/persist/AppraisalStatus.java index ade46dc5..87491887 100644 --- a/HIRS_Utils/src/main/java/hirs/data/persist/AppraisalStatus.java +++ b/HIRS_Utils/src/main/java/hirs/data/persist/AppraisalStatus.java @@ -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; + } } diff --git a/HIRS_Utils/src/main/java/hirs/data/persist/certificate/PlatformCredential.java b/HIRS_Utils/src/main/java/hirs/data/persist/certificate/PlatformCredential.java index 45a43965..ce35d6e1 100644 --- a/HIRS_Utils/src/main/java/hirs/data/persist/certificate/PlatformCredential.java +++ b/HIRS_Utils/src/main/java/hirs/data/persist/certificate/PlatformCredential.java @@ -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; diff --git a/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/ComponentClass.java b/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/ComponentClass.java index 491daf97..91669b25 100644 --- a/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/ComponentClass.java +++ b/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/ComponentClass.java @@ -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. diff --git a/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/ComponentIdentifier.java b/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/ComponentIdentifier.java index c3f78f39..f0207502 100644 --- a/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/ComponentIdentifier.java +++ b/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/ComponentIdentifier.java @@ -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; + 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(); diff --git a/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/V2/ComponentIdentifierV2.java b/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/V2/ComponentIdentifierV2.java index a9e5fdb0..6232c89f 100644 --- a/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/V2/ComponentIdentifierV2.java +++ b/HIRS_Utils/src/main/java/hirs/data/persist/certificate/attributes/V2/ComponentIdentifierV2.java @@ -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(); diff --git a/HIRS_Utils/src/main/java/hirs/data/persist/info/ComponentInfo.java b/HIRS_Utils/src/main/java/hirs/data/persist/info/ComponentInfo.java index 745cd22f..4febbd67 100644 --- a/HIRS_Utils/src/main/java/hirs/data/persist/info/ComponentInfo.java +++ b/HIRS_Utils/src/main/java/hirs/data/persist/info/ComponentInfo.java @@ -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); } } diff --git a/HIRS_Utils/src/main/java/hirs/validation/SupplyChainCredentialValidator.java b/HIRS_Utils/src/main/java/hirs/validation/SupplyChainCredentialValidator.java index 6aa80ff2..da294f42 100644 --- a/HIRS_Utils/src/main/java/hirs/validation/SupplyChainCredentialValidator.java +++ b/HIRS_Utils/src/main/java/hirs/validation/SupplyChainCredentialValidator.java @@ -95,8 +95,6 @@ public final class SupplyChainCredentialValidator implements CredentialValidator */ public static final String FIRMWARE_VALID = "Firmware validated"; - private static final Map 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 getV2PaccorOutput( + final String paccorOutput) throws IOException { + List 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 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 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 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 deltaMapping, final List origPcComponents) { boolean fieldValidation = true; StringBuilder resultMessage = new StringBuilder(); + String tempStringMessage = ""; List validOrigPcComponents = origPcComponents.stream() .filter(identifier -> identifier.getComponentManufacturer() != null && identifier.getComponentModel() != null) @@ -568,11 +601,7 @@ public final class SupplyChainCredentialValidator implements CredentialValidator List chainCertificates = new LinkedList<>(deltaMapping.keySet()); // map the components throughout the chain - Map chainCiMapping = new HashMap<>(); - List deltaBuildList = new LinkedList<>(validOrigPcComponents); - deltaBuildList.stream().forEach((ci) -> { - chainCiMapping.put(ci.getComponentSerial().toString(), ci); - }); + List baseCompList = new LinkedList<>(validOrigPcComponents); Collections.sort(chainCertificates, new Comparator() { @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 leftOverDeltas = new ArrayList<>(); + List 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 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 componentInfoList - = getComponentInfoFromPaccorOutput(paccorOutputString); - unmatchedComponents = validateV2p0PlatformCredentialComponentsExpectingExactMatch( - new LinkedList<>(chainCiMapping.values()), componentInfoList); + // compare based on component class + List 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 fullDeltaChainComponents, + final List allDeviceInfoComponents) { + ComponentIdentifierV2 ciV2; + StringBuilder invalidPcIds = new StringBuilder(); + List subCompIdList = fullDeltaChainComponents + .stream().collect(Collectors.toList()); + List 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 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 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 templist = new ArrayList<>(pcComponentsFromManufacturer); for (ComponentIdentifier ci : templist) { - ComponentIdentifier pcComponent = ci; Iterator 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 deltaMapping, + final List baseCompList, + final List leftOvers, + final List absentSerials, + final List chainCertificates) { + StringBuilder resultMessage = new StringBuilder(); + List 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 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 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 getDeltaFailures() { - return Collections.unmodifiableMap(DELTA_FAILURES); - } }