diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java index ccb38c09c..3652ee323 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/AbstractAttestationCertificateAuthority.java @@ -101,7 +101,6 @@ */ public abstract class AbstractAttestationCertificateAuthority implements AttestationCertificateAuthority { - /** * Logger instance for for subclass instances. */ @@ -122,7 +121,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; @@ -130,13 +128,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; /** @@ -165,7 +161,6 @@ public abstract class AbstractAttestationCertificateAuthority * certificates issued by this ACA are valid for. */ private final Integer validDays; - private final CertificateManager certificateManager; private final ReferenceManifestManager referenceManifestManager; private final DeviceRegister deviceRegister; @@ -395,7 +390,6 @@ private IdentityResponseEnvelope generateIdentityResponseEnvelopeAndStoreIssuedC */ @Override public byte[] processIdentityClaimTpm2(final byte[] identityClaim) { - LOG.debug("Got identity claim"); if (ArrayUtils.isEmpty(identityClaim)) { @@ -412,9 +406,13 @@ public byte[] processIdentityClaimTpm2(final byte[] identityClaim) { 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) { + LOG.error(ex.getMessage()); + } + if (validationResult == AppraisalStatus.Status.PASS) { RSAPublicKey akPub = parsePublicKey(claim.getAkPublicArea().toByteArray()); byte[] nonce = generateRandomBytes(NONCE_LENGTH); ByteString blobStr = tpm20MakeCredential(ekPub, akPub, nonce); diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/CredentialManagementHelper.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/CredentialManagementHelper.java index e1dbd7788..fcb477934 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 static PlatformCredential storePlatformCredential( 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 30a79d1cd..e22fcddc5 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. @@ -133,10 +132,17 @@ public SupplyChainValidationSummary validateSupplyChain(final EndorsementCredent supplyChainAppraiser); boolean acceptExpiredCerts = policy.isExpiredCertificateValidationEnabled(); PlatformCredential baseCredential = null; - String componentFailures = ""; + SupplyChainValidation platformScv = null; + boolean chkDeltas = false; + String pcErrorMessage = ""; List validations = new LinkedList<>(); Map deltaMapping = new HashMap<>(); - SupplyChainValidation platformScv = null; + SupplyChainValidation.ValidationType platformType = SupplyChainValidation + .ValidationType.PLATFORM_CREDENTIAL; + SupplyChainValidation.ValidationType platformAttrType = SupplyChainValidation + .ValidationType.PLATFORM_CREDENTIAL_ATTRIBUTES; + Map validationTypeMap + = new HashMap<>(); LOGGER.info("Validating supply chain."); // Validate the Endorsement Credential @@ -154,99 +160,111 @@ public SupplyChainValidationSummary validateSupplyChain(final EndorsementCredent // 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); + if (platformScv.getResult() == FAIL) { + pcErrorMessage = String.format("%s%s%n", pcErrorMessage, + platformScv.getMessage()); + } + // set the base credential + if (pc.isBase()) { + baseCredential = pc; + } else { + chkDeltas = true; + deltaMapping.put(pc, null); + } + pc.setDevice(device); + this.certificateManager.update(pc); - validations.add(platformScv); - validations.addAll(deltaMapping.values()); + } - if (pc.isBase()) { - baseCredential = 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; } - pc.setDevice(device); - this.certificateManager.update(pc); } + } 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. - 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); + if (chkDeltas) { + if (platformScv != null) { + aes.addAll(platformScv.getCertificatesUsed()); + } + Iterator it = pcs.iterator(); + while (it.hasNext()) { + PlatformCredential pc = it.next(); + if (pc != null) { + if (!pc.isBase()) { + attributeScv = validateDeltaPlatformCredentialAttributes( + pc, device.getDeviceInfo(), + baseCredential, deltaMapping); + if (attributeScv.getResult() == FAIL) { + attrErrorMessage = String.format("%s%s%n", attrErrorMessage, + attributeScv.getMessage()); } - validations.add(new SupplyChainValidation( - platformScv.getValidationType(), - platformScv.getResult(), aes, - String.format("%s%n%s", platformScv.getMessage(), - 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); + if (platformScv != null) { + validations.add(new SupplyChainValidation( + SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL, + attributeScv.getResult(), aes, attributeScv.getMessage())); } } } @@ -257,13 +275,10 @@ public SupplyChainValidationSummary validateSupplyChain(final EndorsementCredent 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) { @@ -273,29 +288,6 @@ public SupplyChainValidationSummary validateSupplyChain(final EndorsementCredent 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 @@ -677,7 +669,7 @@ private SupplyChainValidation validatePlatformCredentialAttributes( 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"); @@ -693,6 +685,10 @@ private SupplyChainValidation validatePlatformCredentialAttributes( 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: @@ -708,7 +704,7 @@ private SupplyChainValidation validateDeltaPlatformCredentialAttributes( 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"); @@ -725,6 +721,10 @@ private SupplyChainValidation validateDeltaPlatformCredentialAttributes( return buildValidationRecord(validationType, PASS, result.getMessage(), delta, Level.INFO); case FAIL: + if (!result.getAdditionalInfo().isEmpty()) { + base.setComponentFailures(result.getAdditionalInfo()); + this.certificateManager.update(base); + } return buildValidationRecord(validationType, AppraisalStatus.Status.FAIL, result.getMessage(), delta, Level.WARN); case ERROR: @@ -803,9 +803,9 @@ public KeyStore getCaChain(final Certificate credential) { * 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 9f3b4f08d..80f428e81 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 ModelAndView initPage(@PathVariable("certificateType") final String certi 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 diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java index 41ac09ed4..3f5ef629e 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java @@ -274,7 +274,6 @@ private static HashMap getBaseRimInfo( for (CertificateAuthorityCredential cert : certificates) { if (Arrays.equals(cert.getEncodedPublicKey(), RIM_VALIDATOR.getPublicKey().getEncoded())) { - LOGGER.info("Found matching cert!"); data.put("issuerID", cert.getId().toString()); } } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/util/CertificateStringMapBuilder.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/util/CertificateStringMapBuilder.java index 1fe8dc0c6..17908de8c 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/util/CertificateStringMapBuilder.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/util/CertificateStringMapBuilder.java @@ -303,6 +303,7 @@ public static HashMap getPlatformInformation(final UUID uuid, .select(certificateManager) .byEntityId(uuid) .getCertificate(); + if (certificate != null) { data.putAll(getGeneralCertificateInfo(certificate, certificateManager)); data.put("credentialType", certificate.getCredentialType()); @@ -357,8 +358,10 @@ public static HashMap getPlatformInformation(final UUID uuid, 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 int compare(final PlatformCredential obj1, }); 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 " diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp index e6158a3b8..c9711c0be 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp @@ -614,7 +614,7 @@
- +
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 ade46dc5a..874918876 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 enum Status { private Status appStatus; private String message; + private String additionalInfo; /** * Default constructor. Set appraisal status and description. @@ -39,8 +40,21 @@ public enum Status { * @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 String getMessage() { 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 45a439651..ce35d6e16 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 String getCredentialType() { /** * 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 491daf97f..91669b254 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 ComponentClass(final String componentIdentifier) { */ 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 final int getValue() { 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 c3f78f397..f02075026 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 @@ 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 boolean isVersion2() { 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 static List retrieveComponentAddress(final ASN1Sequence 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 a9e5fdb0c..6232c89f1 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 final boolean isRemoved() { 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 boolean isVersion2() { 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 745cd22f3..4febbd67a 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 String getComponentRevision() { 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 ComponentInfo(final String componentManufacturer, 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 ComponentInfo(final String componentManufacturer, } } + /** + * 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 ComponentInfo(final String componentManufacturer, * * @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 boolean equals(final Object o) { && 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 6aa80ff21..2322bf06e 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 static List getComponentInfoFromPaccorOutput(final String 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 AppraisalStatus validateDeltaPlatformCredentialAttributes( 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 @@ static AppraisalStatus validatePlatformCredentialAttributesV2p0( // 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 @@ static AppraisalStatus validatePlatformCredentialAttributesV2p0( 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 @@ static AppraisalStatus validatePlatformCredentialAttributesV2p0( 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 @@ static AppraisalStatus validatePlatformCredentialAttributesV2p0( * 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 @@ static AppraisalStatus validateDeltaAttributesChainV2p0( 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,165 @@ public int compare(final PlatformCredential obj1, 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); - - 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); + StringBuilder deltaSb = new StringBuilder(); + + // 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()); + 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(unmatchedComponents); + resultMessage.append(String.format("There are %s unmatched components", + size)); + 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 invalidDeviceInfo = new StringBuilder(); + 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(FAIL, resultMessage.toString()); + if (subCompIdList.isEmpty() && subCompInfoList.isEmpty()) { + return Strings.EMPTY; } - return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID); + // now we return everything that was unmatched + // what is in the component info/device reported components + // is to be displayed as the failure + if (!subCompIdList.isEmpty()) { + for (ComponentIdentifier ci : subCompIdList) { + ciV2 = (ComponentIdentifierV2) ci; + invalidPcIds.append(String.format("%d;", + ciV2.hashCode())); + } + } + + if (!subCompInfoList.isEmpty()) { + for (ComponentInfo ci : subCompInfoList) { + invalidDeviceInfo.append(String.format("%d;", + ci.hashCode())); + } + } + + return String.format("DEVICEINFO=%s?COMPID=%s%d", + invalidDeviceInfo.toString(), invalidPcIds.toString(), subCompIdList.size()); } /** @@ -711,6 +787,8 @@ public int compare(final PlatformCredential obj1, * 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 +819,7 @@ private static String validateV2p0PlatformCredentialComponentsExpectingExactMatc componentSerial, componentRevision, component.getComponentManufacturerId(), component.getFieldReplaceable(), - component.getComponentAddress() - )); + component.getComponentAddress())); } LOGGER.info("Validating the following Platform Cert components..."); @@ -750,8 +827,7 @@ private static String validateV2p0PlatformCredentialComponentsExpectingExactMatc 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 +863,7 @@ private static String validateV2p0PlatformCredentialComponentsExpectingExactMatc .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 +908,11 @@ private static String validateV2p0PlatformCredentialComponentsExpectingExactMatc // 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 +922,7 @@ private static String validateV2p0PlatformCredentialComponentsExpectingExactMatc } if (!pcUnmatchedComponents.isEmpty()) { + untrimmedPcComponents.clear(); StringBuilder sb = new StringBuilder(); LOGGER.error(String.format("Platform Credential contained %d unmatched components:", pcUnmatchedComponents.size())); @@ -861,6 +936,8 @@ private static String validateV2p0PlatformCredentialComponentsExpectingExactMatc unmatchedComponent.getComponentModel(), unmatchedComponent.getComponentSerial(), unmatchedComponent.getComponentRevision())); + unmatchedComponent.setValidationResult(false); + untrimmedPcComponents.add(unmatchedComponent); } return sb.toString(); } @@ -998,6 +1075,28 @@ static boolean isMatch(final ComponentIdentifier pcComponent, 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 +1106,12 @@ private static boolean isMatchOrEmptyInPlatformCert( 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 +1405,122 @@ && signatureMatchesPublicKey(cert, trustedCert)) { 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)); + 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)); + 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)); + 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 +1669,4 @@ private static boolean isSelfSigned(final X509Certificate cert) 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); - } }