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 { private final CrudManager supplyChainValidatorSummaryManager; + private final CertificateManager certificateManager; + private final DeviceManager deviceManager; + private static String columnHeaders = "Verified Manufacturer," + + "Model,SN,Verification Date,Device Status," + + "Component name,Component manufacturer,Component model," + + "Component SN,Component status"; + private static final String DEFAULT_COMPANY = "AllDevices"; + private static final String UNDEFINED = "undefined"; private static final Logger LOGGER = getLogger(ValidationReportsPageController.class); /** * Constructor providing the Page's display and routing specification. * @param supplyChainValidatorSummaryManager the manager + * @param certificateManager the certificate manager + * @param deviceManager the device manager */ @Autowired public ValidationReportsPageController( - final CrudManager supplyChainValidatorSummaryManager) { + final CrudManager supplyChainValidatorSummaryManager, + final CertificateManager certificateManager, + final DeviceManager deviceManager) { super(VALIDATION_REPORTS); this.supplyChainValidatorSummaryManager = supplyChainValidatorSummaryManager; + this.certificateManager = certificateManager; + this.deviceManager = deviceManager; } /** @@ -97,4 +131,160 @@ public class ValidationReportsPageController extends PageController(records, input); } + + /** + * This method handles downloading a validation report. + * @param request object + * @param response object + * @throws IOException thrown by BufferedWriter object + */ + @RequestMapping(value = "download", method = RequestMethod.POST) + public void download(final HttpServletRequest request, + final HttpServletResponse response) throws IOException { + + LOGGER.info("Downloading validation report"); + String company = ""; + String contractNumber = ""; + Pattern pattern = Pattern.compile("^\\w*$"); + DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("uuuu-MM-dd"); + DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss"); + LocalDate startDate = null; + LocalDate endDate = null; + ArrayList createTimes = new ArrayList(); + String[] deviceNames = new String[]{}; + + Enumeration parameters = request.getParameterNames(); + while (parameters.hasMoreElements()) { + String parameter = (String) parameters.nextElement(); + String parameterValue = request.getParameter(parameter); + LOGGER.info(parameter + ": " + parameterValue); + switch (parameter) { + case "company": + Matcher companyMatcher = pattern.matcher(parameterValue); + if (companyMatcher.matches()) { + company = parameterValue; + } else { + company = DEFAULT_COMPANY; + } + break; + case "contract": + Matcher contractMatcher = pattern.matcher(parameterValue); + if (contractMatcher.matches()) { + contractNumber = parameterValue; + } else { + contractNumber = "none"; + } + break; + case "dateStart": + if (parameterValue != null && !parameterValue.isEmpty()) { + startDate = LocalDate.parse(parameterValue, dateFormat); + } else { + startDate = LocalDate.ofEpochDay(0); + } + break; + case "dateEnd": + if (parameterValue != null && !parameterValue.isEmpty()) { + endDate = LocalDate.parse(parameterValue, dateFormat); + } else { + endDate = LocalDate.now(); + } + break; + case "createTimes": + if (!parameterValue.equals(UNDEFINED)) { + String[] timestamps = parameterValue.split(","); + for (String timestamp : timestamps) { + createTimes.add(LocalDateTime.parse(timestamp, + dateTimeFormat).toLocalDate()); + } + } + break; + case "deviceNames": + if (!parameterValue.equals(UNDEFINED)) { + deviceNames = parameterValue.split(","); + } + break; + default: + } + } + + response.setHeader("Content-Type", "text/csv"); + response.setHeader("Content-Disposition", + "attachment;filename=validation_report.csv"); + BufferedWriter bufferedWriter = new BufferedWriter( + new OutputStreamWriter(response.getOutputStream(), "UTF-8")); + StringBuilder reportData = new StringBuilder(); + bufferedWriter.append("Company: " + company + "\n"); + bufferedWriter.append("Contract number: " + contractNumber + "\n"); + for (int i = 0; i < deviceNames.length; i++) { + if ((createTimes.get(i).isAfter(startDate) || createTimes.get(i).isEqual(startDate)) + && (createTimes.get(i).isBefore(endDate) + || createTimes.get(i).isEqual(endDate))) { + UUID deviceId = deviceManager.getDevice(deviceNames[i]).getId(); + LOGGER.info(deviceId); + PlatformCredential pc = PlatformCredential.select(certificateManager) + .byDeviceId(deviceId).getCertificate(); + LOGGER.info("Found platform credential: " + pc.toString()); + reportData.append(pc.getManufacturer() + "," + + pc.getModel() + "," + + pc.getPlatformSerial() + "," + + LocalDateTime.now().toString() + "," + + pc.getDevice().getSupplyChainStatus() + ","); + ArrayList> parsedComponents = parseComponents(pc); + for (ArrayList component : parsedComponents) { + for (String data : component) { + reportData.append(data + ","); + } + reportData.deleteCharAt(reportData.length() - 1); + reportData.append("\n,,,,,"); + } + reportData.delete(reportData.lastIndexOf("\n"), reportData.length()); + } + } + bufferedWriter.append(columnHeaders + "\n"); + bufferedWriter.append(reportData.toString() + "\n"); + LOGGER.info(columnHeaders); + LOGGER.info(reportData.toString()); + bufferedWriter.flush(); + } + + /** + * This method parses the following ComponentIdentifier fields into an ArrayList of ArrayLists. + * - ComponentClass + * - Manufacturer + * - Model + * - Serial number + * - Pass/fail status (based on componentFailures string) + * @param pc the platform credential. + * @return the ArrayList of ArrayLists containing the parsed component data. + */ + private ArrayList> parseComponents(final PlatformCredential pc) { + ArrayList> parsedComponents = new ArrayList>(); + if (pc.getComponentIdentifiers() != null + && pc.getComponentIdentifiers().size() > 0) { + LOGGER.info("Component failures: " + pc.getComponentFailures()); + ArrayList componentFailures = + new ArrayList(Arrays.asList(pc.getComponentFailures().split(";"))); + for (ComponentIdentifier ci : pc.getComponentIdentifiers()) { + ArrayList componentData = new ArrayList(); + if (ci instanceof ComponentIdentifierV2) { + componentData.add(((ComponentIdentifierV2) ci).getComponentClass().toString()); + } else { + componentData.add("Platform Component"); + } + componentData.add(ci.getComponentManufacturer().getString()); + componentData.add(ci.getComponentModel().getString()); + componentData.add(ci.getComponentSerial().getString()); + //Failing components are identified by manufacturer + model + if (componentFailures.contains(componentData.get(1) + componentData.get(2))) { + componentData.add("Fail"); + } else { + componentData.add("Pass"); + } + parsedComponents.add(componentData); + LOGGER.info(String.join(",", componentData)); + } + } + + return parsedComponents; + } } 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 1fe8dc0c..ebdee349 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 final class CertificateStringMapBuilder { .select(certificateManager) .byEntityId(uuid) .getCertificate(); + if (certificate != null) { data.putAll(getGeneralCertificateInfo(certificate, certificateManager)); data.put("credentialType", certificate.getCredentialType()); @@ -357,8 +358,10 @@ public final class CertificateStringMapBuilder { data.put("x509Version", certificate.getX509CredentialVersion()); //CPSuri data.put("CPSuri", certificate.getCPSuri()); - //component failure - data.put("failures", certificate.getComponentFailures()); + + if (!certificate.getComponentFailures().isEmpty()) { + data.put("failures", certificate.getComponentFailures()); + } //Get platform Configuration values and set map with it PlatformConfiguration platformConfiguration = certificate.getPlatformConfiguration(); @@ -397,6 +400,17 @@ public final class CertificateStringMapBuilder { }); data.put("chainCertificates", chainCertificates); + + if (!certificate.isBase()) { + for (PlatformCredential pc : chainCertificates) { + if (pc.isBase()) { + if (!pc.getComponentFailures().isEmpty()) { + data.put("failures", pc.getComponentFailures()); + } + break; + } + } + } } } else { String notFoundMessage = "Unable to find Platform Certificate " @@ -422,7 +436,7 @@ public final class CertificateStringMapBuilder { String data = str.trim().substring(str.trim().indexOf('{') + 1, str.trim().length() - 1); // Separate key and value and parse the key - for (String pair: data.split(",")) { + for (String pair : data.split(",")) { String[] keyValue = pair.split("="); // Remove white space and change first character in the key to uppercase keyValue[0] = Character.toUpperCase( 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 e6158a3b..c9711c0b 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_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/validation-reports.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/validation-reports.jsp index 530d4f3c..a70dd6b0 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/validation-reports.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/validation-reports.jsp @@ -25,6 +25,19 @@ + + Download Validation Reports + + + +
+ + +
+
+
@@ -126,6 +139,39 @@ dataTable.order([1, 'desc']).draw(); //order by createTime }); + $("#download").submit(function(e) { + var tableLength = $("#reportTable").rows; + var createTimes = ""; + var deviceNames = ""; + $('#reportTable tr').not('thead tr').each(function() { + createTimes += $(this).find("td").eq(1).html() + ","; + deviceNames += $(this).find("td").eq(2).html() + ","; + }); + createTimes = createTimes.substring(0, createTimes.length - 1); + deviceNames = deviceNames.substring(0, deviceNames.length - 1); + var params = [ + { + name: 'createTimes', + value: createTimes + }, + { + name: 'deviceNames', + value: deviceNames + } + ]; + $(this).append($.map(params, function(param) { + return $('', { + type: 'hidden', + name: param.name, + value: param.value + }); + })); + }); + + $(".btn-primary").click(function() { + $("#validationReportsDownload").modal('hide'); + }); + /** * Gets HTML to display (icon tag) for the specified validation type. * If a validation for the requested type is not found, an empty diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/tags/download-info.tag b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/tags/download-info.tag new file mode 100644 index 00000000..f14e33e0 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/tags/download-info.tag @@ -0,0 +1,14 @@ +<%@tag description="download icon that opens modal dialog with form" pageEncoding="UTF-8"%> + +<%@attribute name="id"%> +<%@attribute name="label"%> +<%@attribute name="customButtons" fragment="true" required="false"%> + +<%@ taglib prefix="my" tagdir="/WEB-INF/tags" %> + + + + + + + \ No newline at end of file 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); - } }