diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ValidationManager.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ValidationManager.java new file mode 100644 index 00000000..f1dbaeee --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ValidationManager.java @@ -0,0 +1,338 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.entity.ArchivableEntity; +import hirs.attestationca.persist.entity.manager.CACredentialRepository; +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.manager.ComponentResultRepository; +import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; +import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.Device; +import hirs.attestationca.persist.entity.userdefined.PolicySettings; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; +import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.ComponentResult; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.validation.CertificateAttributeScvValidator; +import hirs.attestationca.persist.validation.CredentialValidator; +import hirs.attestationca.persist.validation.FirmwareScvValidator; +import hirs.utils.BouncyCastleUtils; +import lombok.extern.log4j.Log4j2; +import org.apache.logging.log4j.Level; +import org.bouncycastle.util.encoders.Hex; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Log4j2 +public class ValidationManager { + + public static SupplyChainValidation evaluateEndorsementCredentialStatus( + final EndorsementCredential ec, + final CACredentialRepository caCredentialRepository, + final boolean acceptExpiredCerts) { + final SupplyChainValidation.ValidationType validationType + = SupplyChainValidation.ValidationType.ENDORSEMENT_CREDENTIAL; + log.info("Validating endorsement credential"); + if (ec == null) { + log.error("No endorsement credential to validate"); + return buildValidationRecord(validationType, + AppraisalStatus.Status.FAIL, "Endorsement credential is missing", + null, Level.ERROR); + } + + KeyStore ecStore = getCaChain(ec, caCredentialRepository); + AppraisalStatus result = CredentialValidator. + validateEndorsementCredential(ec, ecStore, acceptExpiredCerts); + switch (result.getAppStatus()) { + case PASS: + return buildValidationRecord(validationType, AppraisalStatus.Status.PASS, + result.getMessage(), ec, Level.INFO); + case FAIL: + return buildValidationRecord(validationType, AppraisalStatus.Status.FAIL, + result.getMessage(), ec, Level.WARN); + case ERROR: + default: + return buildValidationRecord(validationType, AppraisalStatus.Status.ERROR, + result.getMessage(), ec, Level.ERROR); + } + } + + public static SupplyChainValidation evaluatePlatformCredentialStatus( + final PlatformCredential pc, + final KeyStore trustedCertificateAuthority, final boolean acceptExpiredCerts) { + final SupplyChainValidation.ValidationType validationType + = SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL; + + if (pc == null) { + log.error("No platform credential to validate"); + return buildValidationRecord(validationType, + AppraisalStatus.Status.FAIL, "Empty Platform credential", null, Level.ERROR); + } + log.info("Validating Platform Credential"); + AppraisalStatus result = CredentialValidator.validatePlatformCredential(pc, + trustedCertificateAuthority, acceptExpiredCerts); + switch (result.getAppStatus()) { + case PASS: + return buildValidationRecord(validationType, AppraisalStatus.Status.PASS, + result.getMessage(), pc, Level.INFO); + case FAIL: + return buildValidationRecord(validationType, AppraisalStatus.Status.FAIL, + result.getMessage(), pc, Level.WARN); + case ERROR: + default: + return buildValidationRecord(validationType, AppraisalStatus.Status.ERROR, + result.getMessage(), pc, Level.ERROR); + } + } + + public static SupplyChainValidation evaluatePCAttributesStatus( + final PlatformCredential pc, final DeviceInfoReport deviceInfoReport, + final EndorsementCredential ec, + final CertificateRepository certificateRepository, + final ComponentResultRepository componentResultRepository) { + final SupplyChainValidation.ValidationType validationType + = SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL_ATTRIBUTES; + + if (pc == null) { + log.error("No platform credential to validate"); + return buildValidationRecord(validationType, + AppraisalStatus.Status.FAIL, "Platform credential is missing", + null, Level.ERROR); + } + log.info("Validating platform credential attributes"); + AppraisalStatus result = CredentialValidator. + validatePlatformCredentialAttributes(pc, deviceInfoReport, ec); + switch (result.getAppStatus()) { + case PASS: + return buildValidationRecord(validationType, AppraisalStatus.Status.PASS, + result.getMessage(), pc, Level.INFO); + case FAIL: + if (!result.getAdditionalInfo().isEmpty()) { + pc.setComponentFailures(result.getAdditionalInfo()); + pc.setComponentFailureMessage(result.getMessage()); + certificateRepository.save(pc); + for (ComponentResult componentResult + : CertificateAttributeScvValidator.getComponentResultList()) { + componentResultRepository.save(componentResult); + } + } + return buildValidationRecord(validationType, AppraisalStatus.Status.FAIL, + result.getMessage(), pc, Level.WARN); + case ERROR: + default: + return buildValidationRecord(validationType, AppraisalStatus.Status.ERROR, + result.getMessage(), pc, Level.ERROR); + } + } + + public static SupplyChainValidation evaluateDeltaAttributesStatus( + final PlatformCredential delta, + final DeviceInfoReport deviceInfoReport, + final PlatformCredential base, + final Map deltaMapping, + final CertificateRepository certificateRepository) { + final SupplyChainValidation.ValidationType validationType + = SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL_ATTRIBUTES; + + if (delta == null) { + log.error("No delta certificate to validate"); + return buildValidationRecord(validationType, + AppraisalStatus.Status.FAIL, "Delta platform certificate is missing", + null, Level.ERROR); + } + log.info("Validating delta platform certificate attributes"); + AppraisalStatus result = CertificateAttributeScvValidator. + validateDeltaPlatformCredentialAttributes(delta, deviceInfoReport, + base, deltaMapping); + switch (result.getAppStatus()) { + case PASS: + return buildValidationRecord(validationType, AppraisalStatus.Status.PASS, + result.getMessage(), delta, Level.INFO); + case FAIL: + if (!result.getAdditionalInfo().isEmpty()) { + base.setComponentFailures(result.getAdditionalInfo()); + base.setComponentFailureMessage(result.getMessage()); + certificateRepository.save(base); + } + // we are adding things to componentFailures + certificateRepository.save(delta); + return buildValidationRecord(validationType, AppraisalStatus.Status.FAIL, + result.getMessage(), delta, Level.WARN); + case ERROR: + default: + return buildValidationRecord(validationType, AppraisalStatus.Status.ERROR, + result.getMessage(), delta, Level.ERROR); + } + } + + public static SupplyChainValidation evaluateFirmwareStatus( + final Device device, + final PolicySettings policySettings, final ReferenceManifestRepository rimRepo, + final ReferenceDigestValueRepository rdvRepo, + final CACredentialRepository caRepo) { + final SupplyChainValidation.ValidationType validationType + = SupplyChainValidation.ValidationType.FIRMWARE; + + AppraisalStatus result = FirmwareScvValidator.validateFirmware(device, policySettings, + rimRepo, rdvRepo, caRepo); + Level logLevel; + + switch (result.getAppStatus()) { + case PASS: + logLevel = Level.INFO; + break; + case FAIL: + logLevel = Level.WARN; + break; + case ERROR: + default: + logLevel = Level.ERROR; + } + return buildValidationRecord(validationType, result.getAppStatus(), + result.getMessage(), null, logLevel); + } + + /** + * Creates a supply chain validation record and logs the validation message + * at the specified log level. + * + * @param validationType the type of validation + * @param result the appraisal status + * @param message the validation message to include in the summary and log + * @param archivableEntity the archivableEntity associated with the + * validation + * @param logLevel the log level + * @return a SupplyChainValidation + */ + public static SupplyChainValidation buildValidationRecord( + final SupplyChainValidation.ValidationType validationType, + final AppraisalStatus.Status result, final String message, + final ArchivableEntity archivableEntity, final Level logLevel) { + List aeList = new ArrayList<>(); + if (archivableEntity != null) { + aeList.add(archivableEntity); + } + + log.log(logLevel, message); + return new SupplyChainValidation(validationType, result, aeList, message); + } + + /** + * This method is used to retrieve the entire CA chain (up to a trusted + * self-signed certificate) for the given certificate. This method will look + * up CA certificates that have a matching issuer organization as the given + * certificate, and will perform that operation recursively until all + * certificates for all relevant organizations have been retrieved. For that + * reason, the returned set of certificates may be larger than the the + * single trust chain for the queried certificate, but is guaranteed to + * include the trust chain if it exists in this class' CertificateManager. + * Returns the certificate authority credentials in a KeyStore. + * + * @param certificate the credential whose CA chain should be retrieved + * @param caCredentialRepository db service to get CA Certs + * @return A keystore containing all relevant CA credentials to the given + * certificate's organization or null if the keystore can't be assembled + */ + public static KeyStore getCaChain(final Certificate certificate, + final CACredentialRepository caCredentialRepository) { + KeyStore caKeyStore = null; + try { + caKeyStore = caCertSetToKeystore(getCaChainRec(certificate, Collections.emptySet(), + caCredentialRepository)); + } catch (KeyStoreException | IOException e) { + log.error("Unable to assemble CA keystore", e); + } + return caKeyStore; + } + + /** + * This is a recursive method which is used to retrieve the entire CA chain + * (up to a trusted self-signed certificate) for the given certificate. This + * method will look up CA certificates that have a matching issuer + * organization as the given certificate, and will perform that operation + * recursively until all certificates for all relevant organizations have + * been retrieved. For that reason, the returned set of certificates may be + * larger than the the single trust chain for the queried certificate, but + * is guaranteed to include the trust chain if it exists in this class' + * CertificateManager. + *

+ * Implementation notes: 1. Queries for CA certs with a subject org matching + * the given (argument's) issuer org 2. Add that org to + * queriedOrganizations, so we don't search for that organization again 3. + * For each returned CA cert, add that cert to the result set, and recurse + * with that as the argument (to go up the chain), if and only if we haven't + * 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 previouslyQueriedSubjects a list of organizations to refrain + * from querying + * @return a Set containing all relevant CA credentials to the given + * certificate's organization + */ + public static Set getCaChainRec( + final Certificate credential, + final Set previouslyQueriedSubjects, + final CACredentialRepository caCredentialRepository) { + CertificateAuthorityCredential skiCA = null; + List certAuthsWithMatchingIssuer = new LinkedList<>(); + if (credential.getAuthorityKeyIdentifier() != null + && !credential.getAuthorityKeyIdentifier().isEmpty()) { + byte[] bytes = Hex.decode(credential.getAuthorityKeyIdentifier()); + skiCA = caCredentialRepository.findBySubjectKeyIdentifier(bytes); + } + + if (skiCA == null) { + if (credential.getIssuerSorted() == null + || credential.getIssuerSorted().isEmpty()) { + certAuthsWithMatchingIssuer = caCredentialRepository.findBySubject(credential.getIssuer()); + } else { + //Get certificates by subject organization + certAuthsWithMatchingIssuer = caCredentialRepository.findBySubjectSorted(credential.getIssuerSorted()); + } + } else { + certAuthsWithMatchingIssuer.add(skiCA); + } + Set queriedOrganizations = new HashSet<>(previouslyQueriedSubjects); + queriedOrganizations.add(credential.getIssuer()); + + HashSet caCreds = new HashSet<>(); + for (CertificateAuthorityCredential cred : certAuthsWithMatchingIssuer) { + caCreds.add(cred); + if (!BouncyCastleUtils.x500NameCompare(cred.getIssuer(), + cred.getSubject())) { + caCreds.addAll(getCaChainRec(cred, queriedOrganizations, caCredentialRepository)); + } + } + return caCreds; + } + + public static KeyStore caCertSetToKeystore(final Set certs) + throws KeyStoreException, IOException { + KeyStore keyStore = KeyStore.getInstance("JKS"); + try { + keyStore.load(null, "".toCharArray()); + for (Certificate cert : certs) { + keyStore.setCertificateEntry(cert.getId().toString(), cert.getX509Certificate()); + } + } catch (IOException | CertificateException | NoSuchAlgorithmException e) { + throw new IOException("Could not create and populate keystore", e); + } + + return keyStore; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CertificateAttributeScvValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CertificateAttributeScvValidator.java new file mode 100644 index 00000000..5140632a --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CertificateAttributeScvValidator.java @@ -0,0 +1,1125 @@ +package hirs.attestationca.persist.validation; + +import hirs.attestationca.persist.entity.ArchivableEntity; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; +import hirs.attestationca.persist.entity.userdefined.certificate.ComponentResult; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.attributes.ComponentIdentifier; +import hirs.attestationca.persist.entity.userdefined.certificate.attributes.V2.ComponentIdentifierV2; +import hirs.attestationca.persist.entity.userdefined.info.ComponentInfo; +import hirs.attestationca.persist.entity.userdefined.info.HardwareInfo; +import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.util.PciIds; +import hirs.utils.enums.DeviceInfoEnums; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; +import org.bouncycastle.asn1.DERUTF8String; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.ERROR; +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.FAIL; +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.PASS; + +@Log4j2 +public class CertificateAttributeScvValidator extends SupplyChainCredentialValidator { + + @Setter + @Getter + private static List componentResultList = new LinkedList<>(); + + /** + * Checks if the delta credential's attributes are valid. + * @param deltaPlatformCredential the delta credential to verify + * @param deviceInfoReport The device info report containing + * serial number of the platform to be validated. + * @param basePlatformCredential the base credential from the same identity request + * as the delta credential. + * @param deltaMapping delta certificates associated with the + * delta supply validation. + * @return the result of the validation. + */ + public static AppraisalStatus validateDeltaPlatformCredentialAttributes( + final PlatformCredential deltaPlatformCredential, + final DeviceInfoReport deviceInfoReport, + final PlatformCredential basePlatformCredential, + final Map deltaMapping) { + String 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 pc : deltaMapping.keySet()) { + if (!basePlatformCredential.getPlatformSerial() + .equals(pc.getPlatformSerial())) { + message = String.format("Base and Delta platform serial " + + "numbers do not match (%s != %s)", + pc.getPlatformSerial(), + basePlatformCredential.getPlatformSerial()); + log.error(message); + return new AppraisalStatus(FAIL, message); + } + // none of the deltas should have the serial number of the base + if (!pc.isPlatformBase() && basePlatformCredential.getSerialNumber() + .equals(pc.getSerialNumber())) { + message = String.format("Delta Certificate with same serial number as base. (%s)", + pc.getSerialNumber()); + log.error(message); + return new AppraisalStatus(FAIL, message); + } + } + + // parse out the provided delta and its specific chain. + List origPcComponents + = new LinkedList<>(basePlatformCredential.getComponentIdentifiers()); + + return validateDeltaAttributesChainV2p0(deltaPlatformCredential.getId(), + deviceInfoReport, deltaMapping, origPcComponents); + } + + public static AppraisalStatus validatePlatformCredentialAttributesV1p2( + final PlatformCredential platformCredential, + final DeviceInfoReport deviceInfoReport) { + + // check the device's board serial number, and compare against this + // platform credential's board serial number. + // Retrieve the various device serial numbers. + String credentialBoardSerialNumber = platformCredential.getPlatformSerial(); + String credentialChassisSerialNumber = platformCredential.getChassisSerialNumber(); + + HardwareInfo hardwareInfo = deviceInfoReport.getHardwareInfo(); + String deviceBaseboardSerialNumber = hardwareInfo.getBaseboardSerialNumber(); + String deviceChassisSerialNumber = hardwareInfo.getChassisSerialNumber(); + String deviceSystemSerialNumber = hardwareInfo.getSystemSerialNumber(); + + // log serial numbers that weren't collected. Force "not specified" serial numbers + // to be ignored in below case checks + Map deviceInfoSerialNumbers = new HashMap<>(); + + if (StringUtils.isEmpty(deviceBaseboardSerialNumber) + || DeviceInfoEnums.NOT_SPECIFIED.equalsIgnoreCase(deviceBaseboardSerialNumber)) { + log.error("Failed to retrieve device baseboard serial number"); + deviceBaseboardSerialNumber = null; + } else { + deviceInfoSerialNumbers.put("board serial number", deviceBaseboardSerialNumber); + log.info("Using device board serial number for validation: " + + deviceBaseboardSerialNumber); + } + + if (StringUtils.isEmpty(deviceChassisSerialNumber) + || DeviceInfoEnums.NOT_SPECIFIED.equalsIgnoreCase(deviceChassisSerialNumber)) { + log.error("Failed to retrieve device chassis serial number"); + } else { + deviceInfoSerialNumbers.put("chassis serial number", deviceChassisSerialNumber); + log.info("Using device chassis serial number for validation: " + + deviceChassisSerialNumber); + } + if (StringUtils.isEmpty(deviceSystemSerialNumber) + || DeviceInfoEnums.NOT_SPECIFIED.equalsIgnoreCase(deviceSystemSerialNumber)) { + log.error("Failed to retrieve device system serial number"); + } else { + deviceInfoSerialNumbers.put("system serial number", deviceSystemSerialNumber); + log.info("Using device system serial number for validation: " + + deviceSystemSerialNumber); + } + + AppraisalStatus status; + + // Test 1: If the board serial number or chassis is set on the PC, + // compare with each of the device serial numbers for any match + if (StringUtils.isNotEmpty(credentialBoardSerialNumber) + || StringUtils.isNotEmpty(credentialChassisSerialNumber)) { + status = validatePlatformSerialsWithDeviceSerials(credentialBoardSerialNumber, + credentialChassisSerialNumber, deviceInfoSerialNumbers); + // Test 2: If the board and chassis serial numbers are not set on the PC, + // compare the SHA1 hash of the device baseboard serial number to + // the certificate serial number + } else { + String message; + log.debug("Credential Serial Number was null"); + if (StringUtils.isEmpty(deviceBaseboardSerialNumber)) { + message = "Device Serial Number was null"; + log.error(message); + status = new AppraisalStatus(FAIL, message); + } else { + // Calculate the SHA1 hash of the UTF8 encoded baseboard serial number + BigInteger baseboardSha1 = new BigInteger(1, + DigestUtils.sha1(deviceBaseboardSerialNumber.getBytes(StandardCharsets.UTF_8))); + BigInteger certificateSerialNumber = platformCredential.getSerialNumber(); + + // compare the SHA1 hash of the baseboard serial number to the certificate SN + if (certificateSerialNumber != null + && certificateSerialNumber.equals(baseboardSha1)) { + log.info("Device Baseboard Serial Number matches " + + "the Certificate Serial Number"); + status = new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID); + } else if (certificateSerialNumber != null + && certificateSerialNumber.equals( + baseboardSha1.clearBit(NUC_VARIABLE_BIT))) { + log.info("Warning! The Certificate serial number had the most significant " + + "bit truncated. 159 bits of it matched the device baseboard " + + "serial number."); + status = new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID); + } else { + message = "The SHA1 hash of the Device Baseboard Serial Number " + + deviceBaseboardSerialNumber + + " did not match the Certificate's Serial Number"; + log.error(message); + status = new AppraisalStatus(FAIL, message); + + } + } + } + + return status; + } + + + /** + * Validates device info report against the new platform credential. + * @param platformCredential the Platform Credential + * @param deviceInfoReport the Device Info Report + * @return either PASS or FAIL + */ + public static AppraisalStatus validatePlatformCredentialAttributesV2p0( + final PlatformCredential platformCredential, + final DeviceInfoReport deviceInfoReport) { + boolean passesValidation = true; + StringBuilder resultMessage = new StringBuilder(); + + HardwareInfo hardwareInfo = deviceInfoReport.getHardwareInfo(); + + boolean fieldValidation; + fieldValidation = requiredPlatformCredentialFieldIsNonEmptyAndMatches( + "PlatformManufacturerStr", + platformCredential.getManufacturer(), + hardwareInfo.getManufacturer()); + + if (!fieldValidation) { + resultMessage.append("Platform manufacturer did not match\n"); + } + + passesValidation &= fieldValidation; + + fieldValidation = requiredPlatformCredentialFieldIsNonEmptyAndMatches( + "PlatformModel", + platformCredential.getModel(), + hardwareInfo.getProductName()); + + if (!fieldValidation) { + resultMessage.append("Platform model did not match\n"); + } + + passesValidation &= fieldValidation; + + fieldValidation = requiredPlatformCredentialFieldIsNonEmptyAndMatches( + "PlatformVersion", + platformCredential.getVersion(), + hardwareInfo.getVersion()); + + if (!fieldValidation) { + resultMessage.append("Platform version did not match\n"); + } + + passesValidation &= fieldValidation; + + // check PlatformSerial against both system-serial-number and baseboard-serial-number + fieldValidation = ( + (optionalPlatformCredentialFieldNullOrMatches( + "PlatformSerial", + platformCredential.getPlatformSerial(), + hardwareInfo.getSystemSerialNumber())) + || (optionalPlatformCredentialFieldNullOrMatches( + "PlatformSerial", + platformCredential.getPlatformSerial(), + hardwareInfo.getBaseboardSerialNumber()))); + + if (!fieldValidation) { + resultMessage.append("Platform serial did not match\n"); + } + + passesValidation &= fieldValidation; + + // Retrieve the list of all components from the Platform Credential + List allPcComponents + = new ArrayList<>(platformCredential.getComponentIdentifiers()); + + // All components listed in the Platform Credential must have a manufacturer and model + for (ComponentIdentifier pcComponent : allPcComponents) { + fieldValidation = !hasEmptyValueForRequiredField("componentManufacturer", + pcComponent.getComponentManufacturer()); + + if (!fieldValidation) { + resultMessage.append("Component manufacturer is empty\n"); + } + + passesValidation &= fieldValidation; + + fieldValidation = !hasEmptyValueForRequiredField("componentModel", + pcComponent.getComponentModel()); + + if (!fieldValidation) { + resultMessage.append("Component model is empty\n"); + } + + passesValidation &= fieldValidation; + } + + // There is no need to do comparisons with components that are invalid because + // they did not have a manufacturer or model. + List validPcComponents = allPcComponents.stream() + .filter(identifier -> identifier.getComponentManufacturer() != null + && identifier.getComponentModel() != null) + .collect(Collectors.toList()); + + String paccorOutputString = deviceInfoReport.getPaccorOutputString(); + String unmatchedComponents; + try { + List componentInfoList + = getComponentInfoFromPaccorOutput(paccorOutputString); + unmatchedComponents = validateV2p0PlatformCredentialComponentsExpectingExactMatch( + platformCredential.getId(), + validPcComponents, componentInfoList); + fieldValidation &= unmatchedComponents.isEmpty(); + } catch (IOException e) { + final String baseErrorMessage = "Error parsing JSON output from PACCOR: "; + log.error(baseErrorMessage + e.toString()); + log.error("PACCOR output string:\n" + paccorOutputString); + 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 + int counter = 0; + for (ComponentIdentifier ci : validPcComponents) { + counter++; + additionalInfo.append(String.format("%d;", ci.hashCode())); + } + if (counter > 0) { + additionalInfo.insert(0, "COMPID="); + additionalInfo.append(counter); + } + } + + passesValidation &= fieldValidation; + + if (passesValidation) { + return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID); + } else { + return new AppraisalStatus(FAIL, resultMessage.toString(), additionalInfo.toString()); + } + } + + /** + * The main purpose of this method, the in process of validation, is to + * pick out the changes that lead to the delta cert and make sure the changes + * are valid. + * + * @param deviceInfoReport The paccor profile of device being validated against. + * @param deltaMapping map of delta certificates to their validated status + * @param origPcComponents The component identifier list associated with the + * base cert for this specific chain + * @return Appraisal Status of delta being validated. + */ + @SuppressWarnings("methodlength") + static AppraisalStatus validateDeltaAttributesChainV2p0( + final UUID certificateId, + 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) + .collect(Collectors.toList()); + List chainCertificates = new LinkedList<>(deltaMapping.keySet()); + + // map the components throughout the chain + List baseCompList = new LinkedList<>(validOrigPcComponents); + + Collections.sort(chainCertificates, new Comparator() { + @Override + public int compare(final PlatformCredential obj1, + final PlatformCredential obj2) { + if (obj1 == null) { + return 0; + } + if (obj2 == null) { + return 0; + } + if (obj1.getBeginValidity() == null || obj2.getBeginValidity() == null) { + return 0; + } + 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); + + // check if there were any issues + if (!tempStringMessage.isEmpty()) { + resultMessage.append(tempStringMessage); + fieldValidation = false; + } + + // finished up + List certificateList = null; + SupplyChainValidation scv = null; + 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().getComponentIdentifier(); + baseCiV2 = (ComponentIdentifierV2) ci; + classFound = classValue.equals(baseCiV2.getComponentClass() + .getComponentIdentifier()); + if (classFound) { + if (isMatch(ciV2, baseCiV2)) { + if (ciV2.isAdded() || ciV2.isModified()) { + // since the base list doesn't have this ci + // just add the delta + baseCompList.add(deltaCi); + break; + } + if (ciV2.isRemoved()) { + baseCompList.remove(ciV2); + break; + } + // 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())); + } + } + } + } + + if (!fieldValidation || !deltaSb.toString().isEmpty()) { + deltaSb.insert(0, "COMPID="); + return new AppraisalStatus(FAIL, resultMessage.toString(), deltaSb.toString()); + } + + String paccorOutputString = deviceInfoReport.getPaccorOutputString(); + String unmatchedComponents; + try { + // compare based on component class + List componentInfoList = getV2PaccorOutput(paccorOutputString); + // this is what I want to rewrite + unmatchedComponents = validateV2PlatformCredentialAttributes( + certificateId, + baseCompList, + componentInfoList); + fieldValidation &= unmatchedComponents.isEmpty(); + } catch (IOException ioEx) { + final String baseErrorMessage = "Error parsing JSON output from PACCOR: "; + log.error(baseErrorMessage + ioEx.toString()); + log.error("PACCOR output string:\n" + paccorOutputString); + return new AppraisalStatus(ERROR, baseErrorMessage + ioEx.getMessage()); + } + StringBuilder additionalInfo = new StringBuilder(); + if (!fieldValidation) { + resultMessage = new StringBuilder(); + resultMessage.append("There are unmatched components:\n"); + resultMessage.append(unmatchedComponents); + + // pass information of which ones failed in additionInfo + int counter = 0; + for (ComponentIdentifier ci : baseCompList) { + counter++; + additionalInfo.append(String.format("%d;", ci.hashCode())); + } + if (counter > 0) { + additionalInfo.insert(0, "COMPID="); + additionalInfo.append(counter); + } + } + + if (fieldValidation) { + return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID); + } else { + return new AppraisalStatus(FAIL, resultMessage.toString(), additionalInfo.toString()); + } + } + + private static String validateV2PlatformCredentialAttributes( + final UUID certificateId, + 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 (cInfo.getComponentClass().contains( + ciV2.getComponentClass().getComponentIdentifier()) + && isMatch(certificateId, cId, cInfo)) { + subCompIdList.remove(cId); + subCompInfoList.remove(cInfo); + } + } + } + + 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 + fullDeltaChainComponents.clear(); + for (ComponentIdentifier ci : subCompIdList) { + if (ci.isVersion2() && PciIds.DB.isReady()) { + ci = PciIds.translate((ComponentIdentifierV2) ci); + } + log.error("Unmatched component: " + ci); + fullDeltaChainComponents.add(ci); + invalidPcIds.append(String.format( + "Manufacturer=%s, Model=%s, Serial=%s, Revision=%s;%n", + ci.getComponentManufacturer(), + ci.getComponentModel(), + ci.getComponentSerial(), + ci.getComponentRevision())); + } + } + + return invalidPcIds.toString(); + } + + 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.getValidationResult() != PASS) { + failureMsg.append(scv.getMessage()); + } + deltaMapping.put(delta, new SupplyChainValidation( + SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL, + 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.getValidationResult() != PASS) { + failureMsg.append(scv.getMessage()); + } + deltaMapping.put(delta, new SupplyChainValidation( + SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL, + 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.getValidationResult() != PASS) { + failureMsg.append(scv.getMessage()); + } + deltaMapping.put(delta, new SupplyChainValidation( + SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL, + FAIL, + certificateList, + failureMsg.toString())); + } else { + // have to add in case later it is removed + chainCiMapping.put(ciSerial, ci); + } + } + } + } else { + if (ci.isVersion2() && ((ComponentIdentifierV2) ci).isModified()) { + ComponentIdentifierV2 ciV2 = (ComponentIdentifierV2) ci; + // Look for singular component of same make/model/class + ComponentIdentifier candidate = null; + for (ComponentIdentifier search : absentSerials) { + if (!search.isVersion2()) { + continue; + } + ComponentIdentifierV2 noSerialV2 = (ComponentIdentifierV2) search; + + if (noSerialV2.getComponentClass().getComponentIdentifier().equals( + ciV2.getComponentClass().getComponentIdentifier()) + && isMatch(noSerialV2, ciV2)) { + if (candidate == null) { + candidate = noSerialV2; + } else { + // This only works if there is one matching component + candidate = null; + break; + } + } + } + + if (candidate != null) { + absentSerials.remove(candidate); + absentSerials.add(ciV2); + } else { + leftOvers.add(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(); + } + + /** + * Compares the component information from the device info report against those of the + * platform credential. All components in the platform credential should exactly match one + * component in the device info report. The device info report is allowed to have extra + * 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 + */ + private static String validateV2p0PlatformCredentialComponentsExpectingExactMatch( + final UUID certificateId, + final List untrimmedPcComponents, + final List allDeviceInfoComponents) { + // For each manufacturer listed in the platform credential, create two lists: + // 1. a list of components listed in the platform credential for the manufacturer, and + // 2. a list of components listed in the device info for the same manufacturer + // Then eliminate matches from both lists. Finally, decide if the validation passes based + // on the leftovers in the lists and the policy in place. + final List pcComponents = new ArrayList<>(); + for (ComponentIdentifier component : untrimmedPcComponents) { + if (component.getComponentManufacturer() != null) { + component.setComponentManufacturer(new DERUTF8String( + component.getComponentManufacturer().getString().trim())); + } + if (component.getComponentModel() != null) { + component.setComponentModel(new DERUTF8String( + component.getComponentModel().getString().trim())); + } + if (component.getComponentSerial() != null) { + component.setComponentSerial(new DERUTF8String( + component.getComponentSerial().getString().trim())); + } + if (component.getComponentRevision() != null) { + component.setComponentRevision(new DERUTF8String( + component.getComponentRevision().getString().trim())); + } + pcComponents.add(component); + } + + log.info("Validating the following Platform Cert components..."); + pcComponents.forEach(component -> log.info(component.toString())); + log.info("...against the the following DeviceInfoReport components:"); + allDeviceInfoComponents.forEach(component -> log.info(component.toString())); + Set manufacturerSet = new HashSet<>(); + 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<>(); + + for (DERUTF8String derUtf8Manufacturer : manufacturerSet) { + List pcComponentsFromManufacturer + = pcComponents.stream().filter(compIdentifier + -> compIdentifier.getComponentManufacturer().equals(derUtf8Manufacturer)) + .collect(Collectors.toList()); + + String pcManufacturer = derUtf8Manufacturer.getString(); + List deviceInfoComponentsFromManufacturer + = allDeviceInfoComponents.stream().filter(componentInfo + -> componentInfo.getComponentManufacturer().equals(pcManufacturer)) + .collect(Collectors.toList()); + // For each component listed in the platform credential from this manufacturer + // find the ones that specify a serial number so we can match the most specific ones + // first. + List pcComponentsFromManufacturerWithSerialNumber + = pcComponentsFromManufacturer.stream().filter(compIdentifier + -> compIdentifier.getComponentSerial() != null + && StringUtils.isNotEmpty(compIdentifier.getComponentSerial().getString())) + .collect(Collectors.toList()); + // Now match up the components from the device info that are from the same + // manufacturer and have a serial number. As matches are found, remove them from + // both lists. + for (ComponentIdentifier pcComponent + : pcComponentsFromManufacturerWithSerialNumber) { + Optional first + = deviceInfoComponentsFromManufacturer.stream() + .filter(componentInfo + -> StringUtils.isNotEmpty(componentInfo.getComponentSerial())) + .filter(componentInfo -> componentInfo.getComponentSerial() + .equals(pcComponent.getComponentSerial().getString())).findFirst(); + + if (first.isPresent()) { + ComponentInfo potentialMatch = first.get(); + if (isMatch(certificateId, pcComponent, potentialMatch)) { + pcComponentsFromManufacturer.remove(pcComponent); + deviceInfoComponentsFromManufacturer.remove(potentialMatch); + } + } + } + // For each component listed in the platform credential from this manufacturer + // find the ones that specify value for the revision field so we can match the most + // specific ones first. + List pcComponentsFromManufacturerWithRevision + = pcComponentsFromManufacturer.stream().filter(compIdentifier + -> compIdentifier.getComponentRevision() != null + && StringUtils.isNotEmpty(compIdentifier.getComponentRevision().getString())) + .collect(Collectors.toList()); + // Now match up the components from the device info that are from the same + // manufacturer and specify a value for the revision field. As matches are found, + // remove them from both lists. + for (ComponentIdentifier pcComponent + : pcComponentsFromManufacturerWithRevision) { + Optional first + = deviceInfoComponentsFromManufacturer.stream() + .filter(info -> StringUtils.isNotEmpty(info.getComponentRevision())) + .filter(info -> info.getComponentRevision() + .equals(pcComponent.getComponentRevision().getString())) + .findFirst(); + + if (first.isPresent()) { + ComponentInfo potentialMatch = first.get(); + if (isMatch(certificateId, pcComponent, potentialMatch)) { + pcComponentsFromManufacturer.remove(pcComponent); + deviceInfoComponentsFromManufacturer.remove(potentialMatch); + } + } + } + // The remaining components from the manufacturer have only the 2 required fields so + // just match them. + List templist = new ArrayList<>(pcComponentsFromManufacturer); + for (ComponentIdentifier ci : templist) { + Iterator diComponentIter + = deviceInfoComponentsFromManufacturer.iterator(); + while (diComponentIter.hasNext()) { + ComponentInfo potentialMatch = diComponentIter.next(); + if (isMatch(certificateId, ci, potentialMatch)) { + pcComponentsFromManufacturer.remove(ci); + diComponentIter.remove(); + } + } + } + pcUnmatchedComponents.addAll(pcComponentsFromManufacturer); + } + + if (!pcUnmatchedComponents.isEmpty()) { + untrimmedPcComponents.clear(); + StringBuilder sb = new StringBuilder(); + log.error(String.format("Platform Credential contained %d unmatched components:", + pcUnmatchedComponents.size())); + + int unmatchedComponentCounter = 1; + for (ComponentIdentifier unmatchedComponent : pcUnmatchedComponents) { + if (unmatchedComponent.isVersion2() && PciIds.DB.isReady()) { + unmatchedComponent = + PciIds.translate((ComponentIdentifierV2) unmatchedComponent); + } + log.error("Unmatched component " + unmatchedComponentCounter++ + ": " + + unmatchedComponent); + sb.append(String.format("Manufacturer=%s, Model=%s, Serial=%s, Revision=%s;%n", + unmatchedComponent.getComponentManufacturer(), + unmatchedComponent.getComponentModel(), + unmatchedComponent.getComponentSerial(), + unmatchedComponent.getComponentRevision())); + unmatchedComponent.setValidationResult(false); + untrimmedPcComponents.add(unmatchedComponent); + } + return sb.toString(); + } + return Strings.EMPTY; + } + + /** + * Checks if the fields in the potentialMatch match the fields in the pcComponent, + * or if the relevant field in the pcComponent is empty. + * @param certificateId the certificate id + * @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) + */ + private static boolean isMatch(final UUID certificateId, + final ComponentIdentifier pcComponent, + final ComponentInfo potentialMatch) { + boolean matchesSoFar = true; + + matchesSoFar &= isMatchOrEmptyInPlatformCert( + potentialMatch.getComponentManufacturer(), + pcComponent.getComponentManufacturer() + ); + + if (matchesSoFar) { + componentResultList.add(new ComponentResult(certificateId, pcComponent.hashCode(), + potentialMatch.getComponentSerial(), + pcComponent.getComponentSerial().getString())); + } + + matchesSoFar &= isMatchOrEmptyInPlatformCert( + potentialMatch.getComponentModel(), + pcComponent.getComponentModel() + ); + + if (matchesSoFar) { + componentResultList.add(new ComponentResult(certificateId, pcComponent.hashCode(), + potentialMatch.getComponentSerial(), + pcComponent.getComponentSerial().getString())); + } + + matchesSoFar &= isMatchOrEmptyInPlatformCert( + potentialMatch.getComponentSerial(), + pcComponent.getComponentSerial() + ); + + if (matchesSoFar) { + componentResultList.add(new ComponentResult(certificateId, pcComponent.hashCode(), + potentialMatch.getComponentSerial(), + pcComponent.getComponentSerial().getString())); + } + + matchesSoFar &= isMatchOrEmptyInPlatformCert( + potentialMatch.getComponentRevision(), + pcComponent.getComponentRevision() + ); + + if (matchesSoFar) { + componentResultList.add(new ComponentResult(certificateId, pcComponent.hashCode(), + potentialMatch.getComponentSerial(), + pcComponent.getComponentSerial().getString())); + } + + 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) + */ + private 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) { + if (valueInPlatformCert == null || StringUtils.isEmpty(valueInPlatformCert.getString())) { + return true; + } + 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. + * @param credentialBoardSerialNumber the PC board S/N + * @param credentialChassisSerialNumber the PC chassis S/N + * @param deviceInfoSerialNumbers the map of device info serial numbers with descriptions. + * @return the changed validation status + */ + private static AppraisalStatus validatePlatformSerialsWithDeviceSerials( + final String credentialBoardSerialNumber, final String credentialChassisSerialNumber, + final Map deviceInfoSerialNumbers) { + boolean boardSerialNumberFound = false; + boolean chassisSerialNumberFound = false; + + if (StringUtils.isNotEmpty(credentialBoardSerialNumber)) { + boardSerialNumberFound = deviceInfoContainsPlatformSerialNumber( + credentialBoardSerialNumber, "board serial number", deviceInfoSerialNumbers); + } + if (StringUtils.isNotEmpty(credentialChassisSerialNumber)) { + chassisSerialNumberFound = deviceInfoContainsPlatformSerialNumber( + credentialChassisSerialNumber, + "chassis serial number", deviceInfoSerialNumbers); + } + + if (boardSerialNumberFound || chassisSerialNumberFound) { + log.info("The platform credential's board or chassis serial number matched" + + " with a serial number from the client's device information"); + return new AppraisalStatus(PASS, PLATFORM_ATTRIBUTES_VALID); + } + log.error("The platform credential's board and chassis serial numbers did" + + " not match with any device info's serial numbers"); + + return new AppraisalStatus(FAIL, "Platform serial did not match device info"); + } + + /** + * Checks if a platform credential's serial number matches ANY of the device information's + * set of serial numbers. + * @param platformSerialNumber the platform serial number to compare + * @param platformSerialNumberDescription description of the serial number for logging purposes. + * @param deviceInfoSerialNumbers the map of device info serial numbers + * (key = description, value = serial number) + * @return true if the platform serial number was found (case insensitive search), + * false otherwise + */ + private static boolean deviceInfoContainsPlatformSerialNumber( + final String platformSerialNumber, final String platformSerialNumberDescription, + final Map deviceInfoSerialNumbers) { + // check to see if the platform serial number is contained in the map of device info's + // serial numbers + for (Map.Entry entry : deviceInfoSerialNumbers.entrySet()) { + if (entry.getValue().equalsIgnoreCase(platformSerialNumber)) { + log.info("Device info contained platform {} {}" + + " in the device info's {}", platformSerialNumberDescription, + platformSerialNumber, entry.getKey()); + return true; + } + } + + log.warn("Platform {}, {}, did not match any device info serial numbers", + platformSerialNumberDescription, platformSerialNumber); + return false; + } + + /** + * Validates the information supplied for the Platform Credential. This + * method checks if the field is required and therefore if the value is + * present then verifies that the values match. + * @param platformCredentialFieldName name of field to be compared + * @param platformCredentialFieldValue first value to compare + * @param otherValue second value to compare + * @return true if values match + */ + private static boolean requiredPlatformCredentialFieldIsNonEmptyAndMatches( + final String platformCredentialFieldName, + final String platformCredentialFieldValue, + final String otherValue) { + if (hasEmptyValueForRequiredField(platformCredentialFieldName, + platformCredentialFieldValue)) { + return false; + } + + return platformCredentialFieldMatches(platformCredentialFieldName, + platformCredentialFieldValue, otherValue); + } + + /** + * Validates the information supplied for the Platform Credential. This + * method checks if the value is present then verifies that the values match. + * If not present, then returns true. + * @param platformCredentialFieldName name of field to be compared + * @param platformCredentialFieldValue first value to compare + * @param otherValue second value to compare + * @return true if values match or null + */ + private static boolean optionalPlatformCredentialFieldNullOrMatches( + final String platformCredentialFieldName, + final String platformCredentialFieldValue, + final String otherValue) { + if (platformCredentialFieldValue == null) { + return true; + } + + return platformCredentialFieldMatches(platformCredentialFieldName, + platformCredentialFieldValue, otherValue); + } + + /** + * Returns true if fieldValue is null or empty. + * @param description description of the value + * @param fieldValue value of the field + * @return true if fieldValue is null or empty; false otherwise + */ + private static boolean hasEmptyValueForRequiredField(final String description, + final String fieldValue) { + if (StringUtils.isEmpty(fieldValue)) { + log.error("Required field was empty or null in Platform Credential: " + + description); + return true; + } + return false; + } + + private static boolean platformCredentialFieldMatches( + final String platformCredentialFieldName, + final String platformCredentialFieldValue, + final String otherValue) { + String trimmedFieldValue = platformCredentialFieldValue.trim(); + String trimmedOtherValue = otherValue.trim(); + + if (!trimmedFieldValue.equals(trimmedOtherValue)) { + log.debug(String.format("%s field in Platform Credential (%s) does not match " + + "a related field in the DeviceInfoReport (%s)", + platformCredentialFieldName, trimmedFieldValue, trimmedOtherValue)); + return false; + } + + log.debug(String.format("%s field in Platform Credential matches " + + "a related field in the DeviceInfoReport (%s)", + platformCredentialFieldName, trimmedFieldValue) + ); + + return true; + } + + /** + * Returns true if fieldValue is null or empty. + * @param description description of the value + * @param fieldValue value of the field + * @return true if fieldValue is null or empty; false otherwise + */ + private static boolean hasEmptyValueForRequiredField(final String description, + final DERUTF8String fieldValue) { + if (fieldValue == null || StringUtils.isEmpty(fieldValue.getString().trim())) { + log.error("Required field was empty or null in Platform Credential: " + + description); + return true; + } + return false; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/FirmwareScvValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/FirmwareScvValidator.java new file mode 100644 index 00000000..d34b8233 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/FirmwareScvValidator.java @@ -0,0 +1,258 @@ +package hirs.attestationca.persist.validation; + +import hirs.attestationca.persist.entity.manager.CACredentialRepository; +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; +import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; +import hirs.attestationca.persist.entity.userdefined.Device; +import hirs.attestationca.persist.entity.userdefined.PolicySettings; +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; +import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; +import hirs.attestationca.persist.entity.userdefined.rim.BaseReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.rim.EventLogMeasurements; +import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.service.ValidationManager; +import hirs.utils.SwidResource; +import hirs.utils.tpm.eventlog.TCGEventLog; +import hirs.utils.tpm.eventlog.TpmPcrEvent; +import lombok.extern.log4j.Log4j2; +import org.apache.logging.log4j.Level; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.FAIL; +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.PASS; + +@Log4j2 +public class FirmwareScvValidator extends SupplyChainCredentialValidator { + + private static PcrValidator pcrValidator; + + @SuppressWarnings("methodlength") + public static AppraisalStatus validateFirmware( + final Device device, final PolicySettings policySettings, + final ReferenceManifestRepository referenceManifestRepository, + final ReferenceDigestValueRepository referenceDigestValueRepository, + final CACredentialRepository caCredentialRepository) { + boolean passed = true; + String[] baseline = new String[Integer.SIZE]; + AppraisalStatus fwStatus = null; + String manufacturer = device.getDeviceInfo() + .getHardwareInfo().getManufacturer(); + String model = device.getDeviceInfo() + .getHardwareInfo().getProductName(); + ReferenceManifest validationObject; + List baseReferenceManifests = null; + BaseReferenceManifest baseReferenceManifest = null; + ReferenceManifest supportReferenceManifest = null; + EventLogMeasurements measurement = null; + + baseReferenceManifests = referenceManifestRepository.findAllBaseRims(); + + for (BaseReferenceManifest bRim : baseReferenceManifests) { + if (bRim.getPlatformManufacturer().equals(manufacturer) + && !bRim.isSwidSupplemental() && !bRim.isSwidPatch()) { + baseReferenceManifest = bRim; + } + } + + String failedString = ""; + if (baseReferenceManifest == null) { + failedString = "Base Reference Integrity Manifest\n"; + passed = false; + } else { + measurement = (EventLogMeasurements) referenceManifestRepository.findByHexDecHash( + baseReferenceManifest.getEventLogHash()); + + if (measurement == null) { + measurement = referenceManifestRepository.getLogByModel( + baseReferenceManifest.getPlatformModel()); + } + } + + if (measurement == null) { + failedString += "Bios measurement"; + passed = false; + } + validationObject = measurement; + + if (passed) { + List resources = + ((BaseReferenceManifest) baseReferenceManifest).getFileResources(); + fwStatus = new AppraisalStatus(PASS, + SupplyChainCredentialValidator.FIRMWARE_VALID); + + // verify signatures + ReferenceManifestValidator referenceManifestValidator = + new ReferenceManifestValidator(); + referenceManifestValidator.setRim(baseReferenceManifest); + + //Validate signing cert + List allCerts = caCredentialRepository.findAll(); + CertificateAuthorityCredential signingCert = null; + for (CertificateAuthorityCredential cert : allCerts) { + signingCert = cert; + KeyStore keyStore = ValidationManager.getCaChain(signingCert, + caCredentialRepository); + if (referenceManifestValidator.validateXmlSignature(signingCert)) { + try { + if (!SupplyChainCredentialValidator.verifyCertificate( + signingCert.getX509Certificate(), keyStore)) { + passed = false; + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: invalid certificate path."); + validationObject = baseReferenceManifest; + } + } catch (IOException e) { + log.error("Error getting X509 cert from manager: " + e.getMessage()); + } catch (SupplyChainValidatorException e) { + log.error("Error validating cert against keystore: " + e.getMessage()); + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: invalid certificate path."); + } + break; + } + } + + for (SwidResource swidRes : resources) { + supportReferenceManifest = referenceManifestRepository.findByHexDecHash( + swidRes.getHashValue()); + if (supportReferenceManifest != null) { + // Removed the filename check from this if statement + referenceManifestValidator.validateSupportRimHash( + supportReferenceManifest.getRimBytes(), swidRes.getHashValue()); + } + } + + if (passed && signingCert == null) { + passed = false; + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: signing cert not found."); + } + + if (passed && supportReferenceManifest == null) { + fwStatus = new AppraisalStatus(FAIL, + "Support Reference Integrity Manifest can not be found"); + passed = false; + } + + if (passed && !referenceManifestValidator.isSignatureValid()) { + passed = false; + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: Signature validation " + + "failed for Base RIM."); + } + + if (passed && !referenceManifestValidator.isSupportRimValid()) { + passed = false; + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: Hash validation " + + "failed for Support RIM."); + } + + if (passed) { + TCGEventLog logProcessor; + try { + logProcessor = new TCGEventLog(supportReferenceManifest.getRimBytes()); + baseline = logProcessor.getExpectedPCRValues(); + } catch (CertificateException cEx) { + log.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } catch (IOException ioEx) { + log.error(ioEx); + } + + // part 1 of firmware validation check: PCR baseline match + pcrValidator = new PcrValidator(baseline); + + if (baseline.length > 0) { + String pcrContent = ""; + pcrContent = new String(device.getDeviceInfo().getTpmInfo().getPcrValues()); + + if (pcrContent.isEmpty()) { + fwStatus = new AppraisalStatus(FAIL, + "Firmware validation failed: Client did not " + + "provide pcr values."); + log.warn(String.format( + "Firmware validation failed: Client (%s) did not " + + "provide pcr values.", device.getName())); + } else { + // we have a full set of PCR values + //int algorithmLength = baseline[0].length(); + //String[] storedPcrs = buildStoredPcrs(pcrContent, algorithmLength); + //pcrPolicy.validatePcrs(storedPcrs); + + // part 2 of firmware validation check: bios measurements + // vs baseline tcg event log + // find the measurement + TCGEventLog tcgMeasurementLog; + LinkedList tpmPcrEvents = new LinkedList<>(); + List eventValue; + HashMap eventValueMap = new HashMap<>(); + try { + if (measurement.getPlatformManufacturer().equals(manufacturer)) { + tcgMeasurementLog = new TCGEventLog(measurement.getRimBytes()); + eventValue = referenceDigestValueRepository + .findValuesByBaseRimId(baseReferenceManifest.getId()); + for (ReferenceDigestValue rdv : eventValue) { + eventValueMap.put(rdv.getDigestValue(), rdv); + } + + tpmPcrEvents.addAll(pcrValidator.validateTpmEvents( + tcgMeasurementLog, eventValueMap, policySettings)); + } + } catch (CertificateException cEx) { + log.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } catch (IOException ioEx) { + log.error(ioEx); + } + + if (!tpmPcrEvents.isEmpty()) { + StringBuilder sb = new StringBuilder(); + validationObject = measurement; + sb.append(String.format("%d digest(s) were not found:%n", + tpmPcrEvents.size())); + for (TpmPcrEvent tpe : tpmPcrEvents) { + sb.append(String.format("PCR Index %d - %s%n", + tpe.getPcrIndex(), + tpe.getEventTypeStr())); + } + if (fwStatus.getAppStatus().equals(FAIL)) { + fwStatus = new AppraisalStatus(FAIL, String.format("%s%n%s", + fwStatus.getMessage(), sb.toString())); + } else { + fwStatus = new AppraisalStatus(FAIL, sb.toString()); + } + } + } + } else { + fwStatus = new AppraisalStatus(FAIL, "The RIM baseline could not be found."); + } + } + + EventLogMeasurements eventLog = measurement; + eventLog.setOverallValidationResult(fwStatus.getAppStatus()); + referenceManifestRepository.save(eventLog); + } else { + fwStatus = new AppraisalStatus(FAIL, String.format("Firmware Validation failed: " + + "%s for %s can not be found", failedString, manufacturer)); + if (measurement != null) { + measurement.setOverallValidationResult(fwStatus.getAppStatus()); + referenceManifestRepository.save(measurement); + } + } + + return fwStatus; + } +}