diff --git a/HIRS_AttestationCA/build.gradle b/HIRS_AttestationCA/build.gradle index 31806ddd..e9427645 100644 --- a/HIRS_AttestationCA/build.gradle +++ b/HIRS_AttestationCA/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation libs.jakarta.api implementation libs.jakarta.xml implementation libs.hibernate.core + implementation libs.pci implementation libs.guava implementation libs.jackson.core implementation libs.jackson.databind diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/PCRQuoteValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/PCRQuoteValidator.java deleted file mode 100644 index 431dacf6..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/PCRQuoteValidator.java +++ /dev/null @@ -1,223 +0,0 @@ -package hirs.attestationca.persist; - -import hirs.attestationca.persist.entity.userdefined.PolicySettings; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.extern.log4j.Log4j2; - -/** - * The class handles the flags that ignore certain PCRs for validation. - */ -@Log4j2 -@NoArgsConstructor -public class PCRQuoteValidator { - - /** - * Minimum possible value for a PCR ID. This is 0. - */ - public static final int MIN_PCR_ID = 0; - - /** - * Maximum possible value for a PCR ID. This is 23. - */ - public static final int MAX_PCR_ID = 23; - - private static final int NUM_TO_SKIP = 1; - private static final int NUM_OF_TBOOT_PCR = 3; - // PCR 5-16 - private static final int PXE_PCR_START = 5; - private static final int PXE_PCR_END = 16; - // PCR 10 - private static final int IMA_PCR = 10; - // PCR 17-19 - private static final int TBOOT_PCR_START = 17; - private static final int TBOOT_PCR_END = 19; - // PCR 5 - private static final int GPT_PCR = 5; - private static final int IMA_MASK = 0xfffbff; - - // Event Log Event Types - private static final String EVT_EFI_BOOT = "EV_EFI_BOOT_SERVICES_APPLICATION"; - private static final String EVT_EFI_VAR = "EV_EFI_VARIABLE_BOOT"; - private static final String EVT_EFI_GPT = "EV_EFI_GPT_EVENT"; - private static final String EVT_EFI_CFG = "EV_EFI_VARIABLE_DRIVER_CONFIG"; - - private String[] baselinePCRS = new String[MAX_PCR_ID + 1]; - @Getter - @Setter - private PolicySettings settings; - - /** - * Constructor to parse PCR values. - * @param pcrValues pcrValues RIM provided baseline PCRs - * @param settings settings for the supply chain portal settings for provisioning - */ - public PCRQuoteValidator(final String[] pcrValues, - final PolicySettings settings) { - if (pcrValues != null) { - baselinePCRS = new String[MAX_PCR_ID + 1]; - for (int i = 0; i <= MAX_PCR_ID; i++) { - baselinePCRS[i] = pcrValues[i]; - } - } - - this.settings = settings; - } - - /** - * Getter for the array of baseline PCRs. - * @return instance of the PCRs. - */ - public String[] getBaselinePCRS() { - return baselinePCRS.clone(); - } - - /** - * Setter for the array of baseline PCRs. - * @param baselinePCRS instance of the PCRs. - */ - public void setBaselinePCRS(final String[] baselinePCRS) { - this.baselinePCRS = baselinePCRS.clone(); - } - - /** - * Compares the baseline pcr list and the quote pcr list. If the - * ignore flags are set, 10 and 17-19 will be skipped for comparison. - * - * @param storedPCRS non-baseline pcr list - * @return a StringBuilder that is empty if everything passes. - */ - public StringBuilder validatePCRS(final String[] storedPCRS) { - StringBuilder sb = new StringBuilder(); - String failureMsg = "PCR %d does not match%n"; - if (storedPCRS[0] == null || storedPCRS[0].isEmpty()) { - sb.append("failureMsg"); - } else { - for (int i = 0; i <= MAX_PCR_ID; i++) { - if (settings.isIgnoreImaEnabled() && i == IMA_PCR) { - log.info("PCR Policy IMA Ignore enabled."); - i += NUM_TO_SKIP; - } - - if (settings.isIgnoretBootEnabled() && i == TBOOT_PCR_START) { - log.info("PCR Policy TBoot Ignore enabled."); - i += NUM_OF_TBOOT_PCR; - } - - if (settings.isIgnoreGptEnabled() && i == GPT_PCR) { - log.info("PCR Policy GPT Ignore enabled."); - i += NUM_TO_SKIP; - } - - if (!baselinePCRS[i].equals(storedPCRS[i])) { - //error - log.error(String.format("%s =/= %s", baselinePCRS[i], storedPCRS[i])); - sb.append(String.format(failureMsg, i)); - } - } - } - - return sb; - } - - /** - * Checks that the expected FM events occurring. There are policy options that - * will ignore certain PCRs, Event Types and Event Variables present. - * @param tcgMeasurementLog Measurement log from the client - * @param eventValueMap The events stored as baseline to compare - * @return the events that didn't pass - */ -// public List validateTpmEvents(final TCGEventLog tcgMeasurementLog, -// final Map eventValueMap) { -// List tpmPcrEvents = new LinkedList<>(); -// for (TpmPcrEvent tpe : tcgMeasurementLog.getEventList()) { -// if (enableIgnoreIma && tpe.getPcrIndex() == IMA_PCR) { -// log.info(String.format("IMA Ignored -> %s", tpe)); -// } else if (enableIgnoretBoot && (tpe.getPcrIndex() >= TBOOT_PCR_START -// && tpe.getPcrIndex() <= TBOOT_PCR_END)) { -// log.info(String.format("TBOOT Ignored -> %s", tpe)); -// } else if (enableIgnoreOsEvt && (tpe.getPcrIndex() >= PXE_PCR_START -// && tpe.getPcrIndex() <= PXE_PCR_END)) { -// log.info(String.format("OS Evt Ignored -> %s", tpe)); -// } else { -// if (enableIgnoreGpt && tpe.getEventTypeStr().contains(EVT_EFI_GPT)) { -// log.info(String.format("GPT Ignored -> %s", tpe)); -// } else if (enableIgnoreOsEvt && (tpe.getEventTypeStr().contains(EVT_EFI_BOOT) -// || tpe.getEventTypeStr().contains(EVT_EFI_VAR))) { -// log.info(String.format("OS Evt Ignored -> %s", tpe)); -// } else if (enableIgnoreOsEvt && (tpe.getEventTypeStr().contains(EVT_EFI_CFG) -// && tpe.getEventContentStr().contains("SecureBoot"))) { -// log.info(String.format("OS Evt Config Ignored -> %s", tpe)); -// } else { -// if (!eventValueMap.containsKey(tpe.getEventDigestStr())) { -// tpmPcrEvents.add(tpe); -// } -// } -// } -// } -// -// return tpmPcrEvents; -// } - - /** - * Compares hashes to validate the quote from the client. - * - * @param tpmQuote the provided quote - * @param storedPCRS values from the RIM file - * @return true if validated, false if not - */ -// public boolean validateQuote(final byte[] tpmQuote, final String[] storedPCRS) { -// System.out.println("Validating quote from associated device."); -// boolean validated = false; -// short localityAtRelease = 0; -// String quoteString = new String(tpmQuote, StandardCharsets.UTF_8); -// int pcrMaskSelection = PcrSelection.ALL_PCRS_ON; -// -// if (enableIgnoreIma) { -// pcrMaskSelection = IMA_MASK; -// } -// -// ArrayList measurements = new ArrayList<>(); -// -// try { -// for (int i = 0; i < storedPcrs.length; i++) { -// if (i == IMA_PCR && enableIgnoreIma) { -// log.info("Ignore IMA PCR policy is enabled."); -// } else { -// measurements.add(new TPMMeasurementRecord(i, storedPcrs[i])); -// } -// } -// } catch (DecoderException deEx) { -// //error -// System.out.println(deEx); -// } -// -// PcrSelection pcrSelection = new PcrSelection(pcrMaskSelection); -// PcrComposite pcrComposite = new PcrComposite(pcrSelection); -// PcrInfoShort pcrInfoShort = new PcrInfoShort(pcrSelection, -// localityAtRelease, -// tpmQuote, pcrComposite); -// -// try { -// /** -// * The calculated string is being used in the contains method -// * because the TPM Quote's hash isn't just for PCR values, -// * it contains the calculated digest of the PCRs, along with -// * other information. -// */ -// String calculatedString = Hex.encodeHexString( -// pcrInfoShort.getCalculatedDigest()); -// validated = quoteString.contains(calculatedString); -// if (!validated) { -// // warn -// System.out.println(calculatedString + " not found in " + quoteString); -// } -// } catch (NoSuchAlgorithmException naEx) { -// // error -// System.out.println(naEx); -// } -// -// return validated; -// } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java index 24be3dd5..c228a587 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java @@ -11,12 +11,9 @@ import java.util.UUID; @Repository public interface ReferenceDigestValueRepository extends JpaRepository { - @Query(value = "SELECT * FROM ReferenceDigestValue", nativeQuery = true) - List listAll(); List findByModel(String model); List findByManufacturer(String manufacturer); - @Query(value = "SELECT * FROM ReferenceDigestValue WHERE baseRimId = '?1' OR supportRimId = '?1'", nativeQuery = true) - List getValuesByRimId(UUID associatedRimId); + List findValuesByBaseRimId(UUID associatedRimId); List findBySupportRimId(UUID supportRimId); List findBySupportRimHash(String supportRimHash); List findByManufacturerAndModel(String manufacturer, String model); diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceManifestRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceManifestRepository.java index b74516bf..eb0892b2 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceManifestRepository.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceManifestRepository.java @@ -39,4 +39,6 @@ public interface ReferenceManifestRepository extends JpaRepository getSupportByManufacturerModel(String manufacturer, String model); + @Query(value = "SELECT * FROM ReferenceManifest WHERE platformModel = ?1 AND DTYPE = 'EventLogMeasurements'", nativeQuery = true) + EventLogMeasurements getLogByModel(String model); } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/PlatformCredential.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/PlatformCredential.java index 52b6769e..4956723f 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/PlatformCredential.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/PlatformCredential.java @@ -176,6 +176,8 @@ public class PlatformCredential extends DeviceAssociatedCertificate { @Column(length = MAX_MESSAGE_LENGTH) private String componentFailures = Strings.EMPTY; + @Column(length = MAX_MESSAGE_LENGTH) + private String componentFailureMessage = Strings.EMPTY; @Transient private EndorsementCredential endorsementCredential = null; diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java index 2ca0611c..5eb42b76 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/CertificateRequestHandler.java @@ -198,6 +198,7 @@ public class CertificateRequestHandler extends AbstractRequestHandler { * @return the {@link AppraisalStatus} of the supply chain validation */ private AppraisalStatus.Status doQuoteValidation(final Device device) { + log.info("Beginning Quote Validation..."); // perform supply chain validation SupplyChainValidationSummary scvs = supplyChainValidationService.validateQuote( device); diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java index a2d6dfc8..4dc3458c 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/provision/IdentityClaimHandler.java @@ -124,6 +124,7 @@ public class IdentityClaimHandler extends AbstractRequestHandler { try { validationResult = doSupplyChainValidation(claim, ekPub); } catch (Exception ex) { + log.error(ex.getMessage()); for (StackTraceElement ste : ex.getStackTrace()) { log.error(ste.toString()); } @@ -191,12 +192,15 @@ public class IdentityClaimHandler extends AbstractRequestHandler { // this is to check what is in the platform object and pull // additional information from the DB if information exists if (platformCredentials.size() == 1) { + List tempList = new LinkedList<>(); for (PlatformCredential pc : platformCredentials) { if (pc != null && pc.getPlatformSerial() != null) { - platformCredentials.addAll(certificateRepository + tempList.addAll(certificateRepository .byBoardSerialNumber(pc.getPlatformSerial())); } } + + platformCredentials.addAll(tempList); } // perform supply chain validation SupplyChainValidationSummary summary = supplyChainValidationService.validateSupplyChain( @@ -227,6 +231,9 @@ public class IdentityClaimHandler extends AbstractRequestHandler { log.info("Processing Device Info Report"); // store device and device info report. Device device = this.deviceRepository.findByName(deviceInfoReport.getNetworkInfo().getHostname()); + if (device == null) { + device = new Device(deviceInfoReport); + } device.setDeviceInfo(deviceInfoReport); return this.deviceRepository.save(device); } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java index 55e01b09..1c6e9ec1 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java @@ -1,30 +1,259 @@ package hirs.attestationca.persist.service; +import hirs.attestationca.persist.DBManagerException; +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.PolicyRepository; +import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; +import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; +import hirs.attestationca.persist.entity.manager.SupplyChainValidationSummaryRepository; 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.SupplyChainValidationSummary; import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.entity.userdefined.rim.EventLogMeasurements; +import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.validation.PcrValidator; +import hirs.attestationca.persist.validation.SupplyChainCredentialValidator; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.Level; + +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.FAIL; +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.PASS; + +@Log4j2 +@Service +public class SupplyChainValidationService { + + private CACredentialRepository caCredentialRepository; + private PolicyRepository policyRepository; + private ReferenceManifestRepository referenceManifestRepository; + private ReferenceDigestValueRepository referenceDigestValueRepository; + private ComponentResultRepository componentResultRepository; + private CertificateRepository certificateRepository; + private SupplyChainValidationSummaryRepository supplyChainValidationSummaryRepository; -/** - * Interface defining a component that will perform supply chain validations, which yields a - * {@link SupplyChainValidationSummary}. - */ -public interface SupplyChainValidationService { /** - * The "main" method of supply chain validation. Takes the credentials from an identity - * request and validates the supply chain in accordance to the current supply chain - * policy. + * Constructor. * - * @param ec The endorsement credential from the identity request. - * @param pc The set of platform credentials from the identity request. - * @param device The device to be validated. - * @return True if validation is successful, false otherwise. + * @param caCredentialRepository ca credential repository + * @param policyRepository the policy manager + * @param certificateRepository the cert manager + * @param componentResultRepository the comp result manager + * @param referenceManifestRepository the RIM manager + * @param supplyChainValidationSummaryRepository the summary manager + * @param referenceDigestValueRepository the even manager */ - SupplyChainValidationSummary validateSupplyChain(EndorsementCredential ec, - List pc, - Device device); + @Autowired + @SuppressWarnings("ParameterNumberCheck") + public SupplyChainValidationService( + final CACredentialRepository caCredentialRepository, + final PolicyRepository policyRepository, + final CertificateRepository certificateRepository, + final ComponentResultRepository componentResultRepository, + final ReferenceManifestRepository referenceManifestRepository, + final SupplyChainValidationSummaryRepository supplyChainValidationSummaryRepository, + final ReferenceDigestValueRepository referenceDigestValueRepository) { + this.caCredentialRepository = caCredentialRepository; + this.policyRepository = policyRepository; + this.certificateRepository = certificateRepository; + this.componentResultRepository = componentResultRepository; + this.referenceManifestRepository = referenceManifestRepository; + this.supplyChainValidationSummaryRepository = supplyChainValidationSummaryRepository; + this.referenceDigestValueRepository = referenceDigestValueRepository; + } + + /** + * The "main" method of supply chain validation. Takes the credentials from + * an identity request and validates the supply chain in accordance to the + * current supply chain policy. + * + * @param ec The endorsement credential from the identity request. + * @param pcs The platform credentials from the identity request. + * @param device The device to be validated. + * @return A summary of the validation results. + */ + @SuppressWarnings("methodlength") + public SupplyChainValidationSummary validateSupplyChain(final EndorsementCredential ec, + final List pcs, + final Device device) { + boolean acceptExpiredCerts = getPolicySettings().isExpiredCertificateValidationEnabled(); + PlatformCredential baseCredential = null; + SupplyChainValidation platformScv = null; + SupplyChainValidation basePlatformScv = null; + boolean chkDeltas = false; + String pcErrorMessage = ""; + List validations = new LinkedList<>(); + Map deltaMapping = new HashMap<>(); + SupplyChainValidation.ValidationType platformType = SupplyChainValidation + .ValidationType.PLATFORM_CREDENTIAL; + log.info("Beginning Supply Chain Validation..."); + + log.info("Beginning Endorsement Credential Validation..."); + // Validate the Endorsement Credential + if (getPolicySettings().isEcValidationEnabled()) { + validations.add(ValidationManager.evaluateEndorsementCredentialStatus(ec, this.caCredentialRepository, acceptExpiredCerts)); + // store the device with the credential + if (ec != null) { + ec.setDeviceId(device.getId()); + this.certificateRepository.save(ec); + } + } + + log.info("Beginning Platform Credential Validation..."); + // Validate Platform Credential signatures + if (getPolicySettings().isPcValidationEnabled()) { + // Ensure there are platform credentials to validate + if (pcs == null || pcs.isEmpty()) { + log.error("There were no Platform Credentials to validate."); + pcErrorMessage = "Platform credential(s) missing\n"; + } else { + for (PlatformCredential pc : pcs) { + KeyStore trustedCa = ValidationManager.getCaChain(pc, caCredentialRepository); + platformScv = ValidationManager.evaluatePlatformCredentialStatus( + pc, trustedCa, acceptExpiredCerts); + + if (platformScv.getValidationResult() == AppraisalStatus.Status.FAIL) { + pcErrorMessage = String.format("%s%s%n", pcErrorMessage, + platformScv.getMessage()); + } + // set the base credential + if (pc.isPlatformBase()) { + baseCredential = pc; + basePlatformScv = platformScv; + } else { + chkDeltas = true; + deltaMapping.put(pc, null); + } + pc.setDeviceId(device.getId()); + this.certificateRepository.save(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.isPlatformBase() && (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 { + if (pcs == null) { + validations.add(new SupplyChainValidation(platformType, + AppraisalStatus.Status.FAIL, new ArrayList<>(), pcErrorMessage)); + } else { + validations.add(new SupplyChainValidation(platformType, + AppraisalStatus.Status.FAIL, new ArrayList<>(pcs), pcErrorMessage)); + } + } + } + + log.info("Beginning Platform Attributes Validation..."); + // Validate Platform Credential attributes + if (getPolicySettings().isPcAttributeValidationEnabled() + && pcErrorMessage.isEmpty()) { + // Ensure there are platform credentials to validate + 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(ValidationManager.buildValidationRecord( + SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL, + AppraisalStatus.Status.FAIL, + "Base Platform credential missing." + + " Cannot validate attributes", + null, Level.ERROR)); + } else { + if (chkDeltas) { + aes.addAll(basePlatformScv.getCertificatesUsed()); + Iterator it = pcs.iterator(); + while (it.hasNext()) { + PlatformCredential pc = it.next(); + if (pc != null && !pc.isPlatformBase()) { + attributeScv = ValidationManager.evaluateDeltaAttributesStatus( + pc, device.getDeviceInfo(), + baseCredential, deltaMapping, certificateRepository); + if (attributeScv.getValidationResult() == AppraisalStatus.Status.FAIL) { + attrErrorMessage = String.format("%s%s%n", attrErrorMessage, + attributeScv.getMessage()); + } + } + } + } else { + aes.add(baseCredential); + validations.remove(platformScv); + // if there are no deltas, just check base credential + platformScv = ValidationManager.evaluatePCAttributesStatus( + baseCredential, device.getDeviceInfo(), ec, + certificateRepository, componentResultRepository); + validations.add(new SupplyChainValidation( + SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL, + platformScv.getValidationResult(), aes, platformScv.getMessage())); + } + } + if (!attrErrorMessage.isEmpty()) { + //combine platform and platform attributes + validations.remove(platformScv); + validations.add(new SupplyChainValidation( + SupplyChainValidation.ValidationType.PLATFORM_CREDENTIAL, + attributeScv.getValidationResult(), aes, attributeScv.getMessage())); + } + } + + log.info("Beginning Firmware Validation..."); + if (getPolicySettings().isFirmwareValidationEnabled()) { + // may need to associated with device to pull the correct info + // compare tpm quote with what is pulled from RIM associated file + validations.add(ValidationManager.evaluateFirmwareStatus(device, getPolicySettings(), + referenceManifestRepository, referenceDigestValueRepository, + caCredentialRepository)); + } + + log.info("The validation finished, summarizing..."); + // Generate validation summary, save it, and return it. + SupplyChainValidationSummary summary + = new SupplyChainValidationSummary(device, validations); + try { + supplyChainValidationSummaryRepository.save(summary); + } catch (DBManagerException dbMEx) { + log.error("Failed to save Supply Chain Summary"); + } + + return summary; + } /** * A supplemental method that handles validating just the quote post main validation. @@ -32,11 +261,110 @@ public interface SupplyChainValidationService { * @param device the associated device. * @return True if validation is successful, false otherwise. */ - SupplyChainValidationSummary validateQuote(Device device); + public SupplyChainValidationSummary validateQuote(final Device device) { + SupplyChainValidation quoteScv = null; + SupplyChainValidationSummary summary = null; + Level level = Level.ERROR; + AppraisalStatus fwStatus = new AppraisalStatus(FAIL, + "Unknown exception caught during quote validation."); + SupportReferenceManifest sRim = null; + EventLogMeasurements eventLog = null; + + // check if the policy is enabled + if (getPolicySettings().isFirmwareValidationEnabled()) { + String[] baseline = new String[Integer.SIZE]; + String deviceName = device.getDeviceInfo() + .getNetworkInfo().getHostname(); + + try { + List supportRims = referenceManifestRepository + .getSupportByManufacturerModel( + device.getDeviceInfo().getHardwareInfo().getManufacturer(), + device.getDeviceInfo().getHardwareInfo().getProductName()); + for (SupportReferenceManifest support : supportRims) { + if (support.isBaseSupport()) { + sRim = support; + } + } + eventLog = (EventLogMeasurements) referenceManifestRepository + .findByHexDecHash(sRim.getEventLogHash()); + + if (sRim == null) { + fwStatus = new AppraisalStatus(FAIL, + String.format("Firmware Quote validation failed: " + + "No associated Support RIM file " + + "could be found for %s", + deviceName)); + } else if (eventLog == null) { + fwStatus = new AppraisalStatus(FAIL, + String.format("Firmware Quote validation failed: " + + "No associated Client Log file " + + "could be found for %s", + deviceName)); + } else { + baseline = sRim.getExpectedPCRList(); + String[] storedPcrs = eventLog.getExpectedPCRList(); + PcrValidator pcrValidator = new PcrValidator(baseline); + // grab the quote + byte[] hash = device.getDeviceInfo().getTpmInfo().getTpmQuoteHash(); + if (pcrValidator.validateQuote(hash, storedPcrs, getPolicySettings())) { + level = Level.INFO; + fwStatus = new AppraisalStatus(PASS, + SupplyChainCredentialValidator.FIRMWARE_VALID); + fwStatus.setMessage("Firmware validation of TPM Quote successful."); + } else { + fwStatus.setMessage("Firmware validation of TPM Quote failed." + + "\nPCR hash and Quote hash do not match."); + } + eventLog.setOverallValidationResult(fwStatus.getAppStatus()); + this.referenceManifestRepository.save(eventLog); + } + } catch (Exception ex) { + log.error(ex); + } + + quoteScv = ValidationManager.buildValidationRecord(SupplyChainValidation + .ValidationType.FIRMWARE, + fwStatus.getAppStatus(), fwStatus.getMessage(), eventLog, level); + + // Generate validation summary, save it, and return it. + List validations = new ArrayList<>(); + SupplyChainValidationSummary previous + = this.supplyChainValidationSummaryRepository.findByDevice(deviceName); + for (SupplyChainValidation scv : previous.getValidations()) { + if (scv.getValidationType() != SupplyChainValidation.ValidationType.FIRMWARE) { + validations.add(ValidationManager.buildValidationRecord(scv.getValidationType(), + scv.getValidationResult(), scv.getMessage(), + scv.getCertificatesUsed().get(0), Level.INFO)); + } + } + validations.add(quoteScv); + previous.archive(); + supplyChainValidationSummaryRepository.save(previous); + summary = new SupplyChainValidationSummary(device, validations); + + // try removing the supply chain validation as well and resaving that + try { + supplyChainValidationSummaryRepository.save(summary); + } catch (DBManagerException dbEx) { + log.error("Failed to save Supply Chain Summary", dbEx); + } + } + + return summary; + } /** - * Allows other service access to the policy information. - * @return supply chain policy + * Helper function to get a fresh load of the default policy from the DB. + * + * @return The default Supply Chain Policy */ -// SupplyChainPolicy getPolicy(); + private PolicySettings getPolicySettings() { + PolicySettings defaultSettings = this.policyRepository.findByName("Default"); + + if (defaultSettings == null) { + defaultSettings = new PolicySettings("Default", "Settings are configured for no validation flags set."); + } + return defaultSettings; + } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java deleted file mode 100644 index 02c7c7ab..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java +++ /dev/null @@ -1,377 +0,0 @@ -package hirs.attestationca.persist.service; - -import hirs.attestationca.persist.entity.ArchivableEntity; -import hirs.attestationca.persist.DBManagerException; -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.PolicyRepository; -import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; -import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; -import hirs.attestationca.persist.entity.manager.SupplyChainValidationSummaryRepository; -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.SupplyChainValidationSummary; -import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; -import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; -import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; -import hirs.attestationca.persist.entity.userdefined.record.TPMMeasurementRecord; -import hirs.attestationca.persist.entity.userdefined.rim.EventLogMeasurements; -import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest; -import hirs.attestationca.persist.enums.AppraisalStatus; -import hirs.attestationca.persist.validation.CredentialValidator; -import hirs.attestationca.persist.validation.PcrValidator; -import hirs.attestationca.persist.validation.SupplyChainCredentialValidator; -import hirs.utils.BouncyCastleUtils; -import lombok.extern.log4j.Log4j2; -import org.bouncycastle.util.encoders.Hex; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -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.Set; -import org.apache.logging.log4j.Level; - -import static hirs.attestationca.persist.enums.AppraisalStatus.Status.FAIL; -import static hirs.attestationca.persist.enums.AppraisalStatus.Status.PASS; - -@Log4j2 -@Service -public class SupplyChainValidationServiceImpl implements SupplyChainValidationService { - - private CACredentialRepository caCredentialRepository; - private PolicyRepository policyRepository; - private ReferenceManifestRepository referenceManifestRepository; - private ReferenceDigestValueRepository referenceDigestValueRepository; - private ComponentResultRepository componentResultRepository; - private CertificateRepository certificateRepository; - private CredentialValidator supplyChainCredentialValidator; - private SupplyChainValidationSummaryRepository supplyChainValidationSummaryRepository; - - /** - * Constructor to set just the CertificateRepository, so that cert chain validating - * methods can be called from outside classes. - * - * @param certificateRepository the cert repository - */ - public SupplyChainValidationServiceImpl(final CertificateRepository certificateRepository) { - this.certificateRepository = certificateRepository; - } - - /** - * Constructor. - * - * @param caCredentialRepository ca credential repository - * @param policyRepository the policy manager - * @param certificateRepository the cert manager - * @param componentResultRepository the comp result manager - * @param referenceManifestRepository the RIM manager - * @param supplyChainValidationSummaryRepository the summary manager - * @param supplyChainCredentialValidator the credential validator - * @param referenceDigestValueRepository the even manager - */ - @Autowired - @SuppressWarnings("ParameterNumberCheck") - public SupplyChainValidationServiceImpl( - final CACredentialRepository caCredentialRepository, - final PolicyRepository policyRepository, - final CertificateRepository certificateRepository, - final ComponentResultRepository componentResultRepository, - final ReferenceManifestRepository referenceManifestRepository, - final SupplyChainValidationSummaryRepository supplyChainValidationSummaryRepository, - final CredentialValidator supplyChainCredentialValidator, - final ReferenceDigestValueRepository referenceDigestValueRepository) { - this.caCredentialRepository = caCredentialRepository; - this.policyRepository = policyRepository; - this.certificateRepository = certificateRepository; - this.componentResultRepository = componentResultRepository; - this.referenceManifestRepository = referenceManifestRepository; - this.supplyChainValidationSummaryRepository = supplyChainValidationSummaryRepository; - this.supplyChainCredentialValidator = supplyChainCredentialValidator; - this.referenceDigestValueRepository = referenceDigestValueRepository; - } - - @Override - public SupplyChainValidationSummary validateSupplyChain(final EndorsementCredential ec, - final List pc, - final Device device) { - return null; - } - - /** - * A supplemental method that handles validating just the quote post main validation. - * - * @param device the associated device. - * @return True if validation is successful, false otherwise. - */ - @Override - public SupplyChainValidationSummary validateQuote(final Device device) { - SupplyChainValidation quoteScv = null; - SupplyChainValidationSummary summary = null; - Level level = Level.ERROR; - AppraisalStatus fwStatus = new AppraisalStatus(FAIL, - "Unknown exception caught during quote validation."); - SupportReferenceManifest sRim = null; - EventLogMeasurements eventLog = null; - - // check if the policy is enabled - if (getPolicySettings().isFirmwareValidationEnabled()) { - String[] baseline = new String[Integer.SIZE]; - String deviceName = device.getDeviceInfo() - .getNetworkInfo().getHostname(); - - try { - List supportRims = referenceManifestRepository.getSupportByManufacturerModel( - device.getDeviceInfo().getHardwareInfo().getManufacturer(), - device.getDeviceInfo().getHardwareInfo().getProductName()); - for (SupportReferenceManifest support : supportRims) { - if (support.isBaseSupport()) { - sRim = support; - } - } - eventLog = (EventLogMeasurements) referenceManifestRepository - .findByHexDecHash(sRim.getEventLogHash()); - - if (sRim == null) { - fwStatus = new AppraisalStatus(FAIL, - String.format("Firmware Quote validation failed: " - + "No associated Support RIM file " - + "could be found for %s", - deviceName)); - } else if (eventLog == null) { - fwStatus = new AppraisalStatus(FAIL, - String.format("Firmware Quote validation failed: " - + "No associated Client Log file " - + "could be found for %s", - deviceName)); - } else { - baseline = sRim.getExpectedPCRList(); - String[] storedPcrs = eventLog.getExpectedPCRList(); - PcrValidator pcrValidator = new PcrValidator(baseline); - // grab the quote - byte[] hash = device.getDeviceInfo().getTpmInfo().getTpmQuoteHash(); - if (pcrValidator.validateQuote(hash, storedPcrs, getPolicySettings())) { - level = Level.INFO; - fwStatus = new AppraisalStatus(PASS, - SupplyChainCredentialValidator.FIRMWARE_VALID); - fwStatus.setMessage("Firmware validation of TPM Quote successful."); - } else { - fwStatus.setMessage("Firmware validation of TPM Quote failed." - + "\nPCR hash and Quote hash do not match."); - } - eventLog.setOverallValidationResult(fwStatus.getAppStatus()); - this.referenceManifestRepository.save(eventLog); - } - } catch (Exception ex) { - log.error(ex); - } - - quoteScv = buildValidationRecord(SupplyChainValidation - .ValidationType.FIRMWARE, - fwStatus.getAppStatus(), fwStatus.getMessage(), eventLog, level); - - // Generate validation summary, save it, and return it. - List validations = new ArrayList<>(); - SupplyChainValidationSummary previous - = this.supplyChainValidationSummaryRepository.findByDevice(deviceName); - for (SupplyChainValidation scv : previous.getValidations()) { - if (scv.getValidationType() != SupplyChainValidation.ValidationType.FIRMWARE) { - validations.add(buildValidationRecord(scv.getValidationType(), - scv.getValidationResult(), scv.getMessage(), - scv.getCertificatesUsed().get(0), Level.INFO)); - } - } - validations.add(quoteScv); - previous.archive(); - supplyChainValidationSummaryRepository.save(previous); - summary = new SupplyChainValidationSummary(device, validations); - - // try removing the supply chain validation as well and resaving that - try { - supplyChainValidationSummaryRepository.save(summary); - } catch (DBManagerException dbEx) { - log.error("Failed to save Supply Chain Summary", dbEx); - } - } - - return summary; - } - - /** - * 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 - */ - private 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 credential the credential whose CA chain should be retrieved - * @return A keystore containing all relevant CA credentials to the given - * certificate's organization or null if the keystore can't be assembled - */ - public KeyStore getCaChain(final Certificate credential) { - KeyStore caKeyStore = null; - try { - caKeyStore = caCertSetToKeystore(getCaChainRec(credential, Collections.emptySet())); - } 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 - */ - private Set getCaChainRec( - final Certificate credential, - final Set previouslyQueriedSubjects) { - CertificateAuthorityCredential skiCA = null; - List certAuthsWithMatchingIssuer = new LinkedList<>(); - if (credential.getAuthorityKeyIdentifier() != null - && !credential.getAuthorityKeyIdentifier().isEmpty()) { - byte[] bytes = Hex.decode(credential.getAuthorityKeyIdentifier()); - // CYRUS is SKI unique? - 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)); - } - } - return caCreds; - } - - private 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; - } - - private String[] buildStoredPcrs(final String pcrContent, final int algorithmLength) { - // we have a full set of PCR values - String[] pcrSet = pcrContent.split("\\n"); - String[] storedPcrs = new String[TPMMeasurementRecord.MAX_PCR_ID + 1]; - - // we need to scroll through the entire list until we find - // a matching hash length - int offset = 1; - - for (int i = 0; i < pcrSet.length; i++) { - if (pcrSet[i].contains("sha")) { - // entered a new set, check size - if (pcrSet[i + offset].split(":")[1].trim().length() - == algorithmLength) { - // found the matching set - for (int j = 0; j <= TPMMeasurementRecord.MAX_PCR_ID; j++) { - storedPcrs[j] = pcrSet[++i].split(":")[1].trim(); - } - break; - } - } - } - - return storedPcrs; - } - - /** - * Helper function to get a fresh load of the default policy from the DB. - * - * @return The default Supply Chain Policy - */ - private PolicySettings getPolicySettings() { - PolicySettings defaultSettings = this.policyRepository.findByName("Default"); - - if (defaultSettings == null) { - defaultSettings = new PolicySettings("Default", "Settings are configured for no validation flags set."); - } - return defaultSettings; - } -} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/PciIds.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/util/PciIds.java similarity index 99% rename from HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/PciIds.java rename to HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/util/PciIds.java index 3623e922..e17fd1e8 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/PciIds.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/util/PciIds.java @@ -1,4 +1,4 @@ -package hirs.attestationca.portal.page.utils; +package hirs.attestationca.persist.util; import com.github.marandus.pciid.model.Device; import com.github.marandus.pciid.model.Vendor; diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CredentialValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CredentialValidator.java index 7e4638bf..2084cfe9 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CredentialValidator.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CredentialValidator.java @@ -1,19 +1,86 @@ package hirs.attestationca.persist.validation; -import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; 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 lombok.extern.log4j.Log4j2; +import org.bouncycastle.cert.X509AttributeCertificateHolder; +import java.io.IOException; import java.security.KeyStore; -import java.util.Map; +import java.security.KeyStoreException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Date; + +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 CredentialValidator extends SupplyChainCredentialValidator { + + /** + * Checks if the endorsement credential is valid. + * + * @param ec the endorsement credential to verify. + * @param trustStore trust store holding trusted trusted certificates. + * @param acceptExpired whether or not to accept expired and not yet valid certificates + * as valid. + * @return the result of the validation. + */ + public static AppraisalStatus validateEndorsementCredential(final EndorsementCredential ec, + final KeyStore trustStore, + final boolean acceptExpired) { + final String baseErrorMessage = "Can't validate endorsement credential attributes without "; + String message; + if (ec == null) { + message = baseErrorMessage + "an endorsement credential"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + if (trustStore == null) { + message = baseErrorMessage + "a trust store"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + + try { + X509Certificate verifiableCert = ec.getX509Certificate(); + + // check validity period, currently acceptExpired will also accept not yet + // valid certificates + if (!acceptExpired) { + verifiableCert.checkValidity(); + } + + if (verifyCertificate(verifiableCert, trustStore)) { + return new AppraisalStatus(PASS, ENDORSEMENT_VALID); + } else { + return new AppraisalStatus(FAIL, "Endorsement credential does not have a valid " + + "signature chain in the trust store"); + } + } catch (IOException e) { + message = "Couldn't retrieve X509 certificate from endorsement credential"; + log.error(message, e); + return new AppraisalStatus(ERROR, message + " " + e.getMessage()); + } catch (SupplyChainValidatorException e) { + message = "An error occurred indicating the credential is not valid"; + log.warn(message, e); + return new AppraisalStatus(ERROR, message + " " + e.getMessage()); + } catch (CertificateExpiredException e) { + message = "The endorsement credential is expired"; + log.warn(message, e); + return new AppraisalStatus(FAIL, message + " " + e.getMessage()); + } catch (CertificateNotYetValidException e) { + message = "The endorsement credential is not yet valid"; + log.warn(message, e); + return new AppraisalStatus(FAIL, message + " " + e.getMessage()); + } + } -/** - * A class used to support supply chain validation by performing the actual - * validation of credentials. - */ -public interface CredentialValidator { /** * Checks if the platform credential is valid. * @@ -22,47 +89,113 @@ public interface CredentialValidator { * @param acceptExpired whether or not to accept expired certificates as valid. * @return The result of the validation. */ - AppraisalStatus validatePlatformCredential(PlatformCredential pc, - KeyStore trustStore, - boolean acceptExpired); + public static AppraisalStatus validatePlatformCredential(final PlatformCredential pc, + final KeyStore trustStore, + final boolean acceptExpired) { + final String baseErrorMessage = "Can't validate platform credential without "; + String message; + String certVerifyMsg; + if (pc == null) { + message = baseErrorMessage + "a platform credential\n"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + try { + if (trustStore == null || trustStore.size() == 0) { + message = baseErrorMessage + "an Issuer Cert in the Trust Store\n"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + } catch (KeyStoreException e) { + message = baseErrorMessage + "an initialized trust store"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + + X509AttributeCertificateHolder attributeCert = null; + try { + attributeCert = pc.getX509AttributeCertificateHolder(); + } catch (IOException e) { + message = "Could not retrieve X509 Attribute certificate"; + log.error(message, e); + return new AppraisalStatus(FAIL, message + " " + e.getMessage()); + } + + // check validity period, currently acceptExpired will also accept not yet + // valid certificates + if (!acceptExpired && !pc.isValidOn(new Date())) { + message = "Platform credential has expired"; + // if not valid at the current time + log.warn(message); + return new AppraisalStatus(FAIL, message); + } + + // verify cert against truststore + try { + certVerifyMsg = verifyCertificate(attributeCert, trustStore); + if (certVerifyMsg.isEmpty()) { + message = PLATFORM_VALID; + log.info(message); + return new AppraisalStatus(PASS, message); + } else { + message = String.format("Platform credential failed verification%n%s", + certVerifyMsg); + log.error(message); + return new AppraisalStatus(FAIL, message); + } + } catch (SupplyChainValidatorException scvEx) { + message = "An error occurred indicating the credential is not valid"; + log.warn(message, scvEx); + return new AppraisalStatus(FAIL, message + " " + scvEx.getMessage()); + } + } /** * Checks if the platform credential's attributes are valid. - * @param pc The platform credential to verify. - * @param deviceInfoReport Report containing the serial numbers of the platform to be validated. - * @param ec The endorsement credential supplied from the same identity request as - * the platform credential. - * @return The result of the validation. - */ - AppraisalStatus validatePlatformCredentialAttributes(PlatformCredential pc, - DeviceInfoReport deviceInfoReport, - EndorsementCredential ec); - - /** - * Checks if the delta credential's attributes are valid. - * @param delta the delta credential to verify + * @param platformCredential The platform credential to verify. * @param deviceInfoReport The device info report containing * serial number of the platform to be validated. - * @param base 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. + * @param endorsementCredential The endorsement credential supplied from the same + * identity request as the platform credential. + * @return The result of the validation. */ - AppraisalStatus validateDeltaPlatformCredentialAttributes(PlatformCredential delta, - DeviceInfoReport deviceInfoReport, - PlatformCredential base, - Map deltaMapping); - /** - * Checks if the endorsement credential is valid. - * - * @param ec the endorsement credential to verify. - * @param trustStore trust store holding trusted trusted certificates. - * @param acceptExpired whether or not to accept expired certificates as valid. - * @return the result of the validation. - */ - AppraisalStatus validateEndorsementCredential(EndorsementCredential ec, - KeyStore trustStore, - boolean acceptExpired); -} + public static AppraisalStatus validatePlatformCredentialAttributes( + final PlatformCredential platformCredential, + final DeviceInfoReport deviceInfoReport, + final EndorsementCredential endorsementCredential) { + final String baseErrorMessage = "Can't validate platform credential attributes without "; + String message; + if (platformCredential == null) { + message = baseErrorMessage + "a platform credential"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + if (deviceInfoReport == null) { + message = baseErrorMessage + "a device info report"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + if (endorsementCredential == null) { + message = baseErrorMessage + "an endorsement credential"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + + // Quick, early check if the platform credential references the endorsement credential + if (!endorsementCredential.getSerialNumber() + .equals(platformCredential.getHolderSerialNumber())) { + message = "Platform Credential holder serial number does not match " + + "the Endorsement Credential's serial number"; + log.error(message); + return new AppraisalStatus(FAIL, message); + } + + String credentialType = platformCredential.getCredentialType(); + if (PlatformCredential.CERTIFICATE_TYPE_2_0.equals(credentialType)) { + return CertificateAttributeScvValidator.validatePlatformCredentialAttributesV2p0( + platformCredential, deviceInfoReport); + } + return CertificateAttributeScvValidator.validatePlatformCredentialAttributesV1p2( + platformCredential, deviceInfoReport); + } +} \ No newline at end of file diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/PcrValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/PcrValidator.java index 9e8b738c..568ebe72 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/PcrValidator.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/PcrValidator.java @@ -226,4 +226,30 @@ public class PcrValidator { return validated; } + + public static String[] buildStoredPcrs(final String pcrContent, final int algorithmLength) { + // we have a full set of PCR values + String[] pcrSet = pcrContent.split("\\n"); + String[] storedPcrs = new String[TPMMeasurementRecord.MAX_PCR_ID + 1]; + + // we need to scroll through the entire list until we find + // a matching hash length + int offset = 1; + + for (int i = 0; i < pcrSet.length; i++) { + if (pcrSet[i].contains("sha")) { + // entered a new set, check size + if (pcrSet[i + offset].split(":")[1].trim().length() + == algorithmLength) { + // found the matching set + for (int j = 0; j <= TPMMeasurementRecord.MAX_PCR_ID; j++) { + storedPcrs[j] = pcrSet[++i].split(":")[1].trim(); + } + break; + } + } + } + + return storedPcrs; + } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainCredentialValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainCredentialValidator.java index 70aaec2c..bcdc9d92 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainCredentialValidator.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainCredentialValidator.java @@ -1,23 +1,50 @@ package hirs.attestationca.persist.validation; -import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; 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.entity.userdefined.certificate.attributes.ComponentIdentifier; +import hirs.attestationca.persist.entity.userdefined.certificate.attributes.V2.ComponentIdentifierV2; +import hirs.attestationca.persist.entity.userdefined.info.ComponentInfo; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; +import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.CertException; +import org.bouncycastle.cert.X509AttributeCertificateHolder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentVerifierProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.security.InvalidKeyException; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Security; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; +import java.util.Set; @Log4j2 @NoArgsConstructor -public class SupplyChainCredentialValidator implements CredentialValidator { +public class SupplyChainCredentialValidator { + public static final int NUC_VARIABLE_BIT = 159; /** * AppraisalStatus message for a valid endorsement credential appraisal. */ @@ -39,34 +66,447 @@ public class SupplyChainCredentialValidator implements CredentialValidator { */ public static final String FIRMWARE_VALID = "Firmware validated"; - private static List componentResultList = new LinkedList<>(); + /** + * Ensure that BouncyCastle is configured as a javax.security.Security provider, as this + * class expects it to be available. + */ + static { + Security.addProvider(new BouncyCastleProvider()); + } - @Override - public AppraisalStatus validatePlatformCredential(final PlatformCredential pc, - final KeyStore trustStore, - final boolean acceptExpired) { + /** + * Attempts to check if the certificate is validated by certificates in a cert chain. The cert + * chain is expected to be stored in a non-ordered KeyStore (trust store). If the signing + * certificate for the target cert is found, but it is an intermediate cert, the validation will + * continue to try to find the signing cert of the intermediate cert. It will continue searching + * until it follows the chain up to a root (self-signed) cert. + * + * @param cert + * certificate to validate + * @param trustStore + * trust store holding trusted root certificates and intermediate certificates + * @return the certificate chain if validation is successful + * @throws SupplyChainValidatorException + * if the verification is not successful + */ + public static String verifyCertificate(final X509AttributeCertificateHolder cert, + final KeyStore trustStore) throws SupplyChainValidatorException { + try { + if (cert == null || trustStore == null) { + throw new SupplyChainValidatorException("Certificate or trust store is null"); + } else if (trustStore.size() == 0) { + throw new SupplyChainValidatorException("Truststore is empty"); + } + } catch (KeyStoreException e) { + log.error("Error accessing trust store: " + e.getMessage()); + } + + try { + Set trustedCerts = new HashSet<>(); + + Enumeration alias = trustStore.aliases(); + + while (alias.hasMoreElements()) { + trustedCerts.add((X509Certificate) trustStore.getCertificate(alias.nextElement())); + } + + String certChainValidated = validateCertChain(cert, trustedCerts); + if (!certChainValidated.isEmpty()) { + log.error("Cert chain could not be validated"); + } + return certChainValidated; + } catch (KeyStoreException e) { + throw new SupplyChainValidatorException("Error with the trust store", e); + } + } + + /** + * Attempts to check if the certificate is validated by certificates in a cert chain. The cert + * chain is expected to be stored in a non-ordered KeyStore (trust store). If the signing + * certificate for the target cert is found, but it is an intermediate cert, the validation will + * continue to try to find the signing cert of the intermediate cert. It will continue searching + * until it follows the chain up to a root (self-signed) cert. + * + * @param cert + * certificate to validate + * @param trustStore + * trust store holding trusted root certificates and intermediate certificates + * @return the certificate chain if validation is successful + * @throws SupplyChainValidatorException + * if the verification is not successful + */ + public static boolean verifyCertificate(final X509Certificate cert, + final KeyStore trustStore) throws SupplyChainValidatorException { + try { + if (cert == null || trustStore == null) { + throw new SupplyChainValidatorException("Certificate or trust store is null"); + } else if (trustStore.size() == 0) { + throw new SupplyChainValidatorException("Truststore is empty"); + } + } catch (KeyStoreException e) { + log.error("Error accessing trust store: " + e.getMessage()); + } + + try { + Set trustedCerts = new HashSet<>(); + Enumeration alias = trustStore.aliases(); + + while (alias.hasMoreElements()) { + trustedCerts.add((X509Certificate) trustStore.getCertificate(alias.nextElement())); + } + + return validateCertChain(cert, trustedCerts).isEmpty(); + } catch (KeyStoreException e) { + log.error("Error accessing keystore", e); + throw new SupplyChainValidatorException("Error with the trust store", e); + } + } + + /** + * Attempts to check if an attribute certificate is validated by certificates in a cert chain. + * The cert chain is represented as a Set of X509Certificates. If the signing certificate for + * the target cert is found, but it is an intermediate cert, the validation will continue to try + * to find the signing cert of the intermediate cert. It will continue searching until it + * follows the chain up to a root (self-signed) cert. + * + * @param cert + * certificate to validate + * @param additionalCerts + * Set of certs to validate against + * @return String status of the cert chain validation - + * blank if successful, error message otherwise + * @throws SupplyChainValidatorException tried to validate using null certificates + */ + public static String validateCertChain(final X509AttributeCertificateHolder cert, + final Set additionalCerts) + throws SupplyChainValidatorException { + if (cert == null || additionalCerts == null) { + throw new SupplyChainValidatorException( + "Certificate or validation certificates are null"); + } + final String intCAError = "Intermediate signing cert found, check for CA cert"; + String foundRootOfCertChain = ""; + X509Certificate nextInChain = null; + + do { + for (X509Certificate trustedCert : additionalCerts) { + boolean issuerMatchesSubject = false; + boolean signatureMatchesPublicKey = false; + if (nextInChain != null) { + issuerMatchesSubject = issuerMatchesSubjectDN(nextInChain, trustedCert); + signatureMatchesPublicKey = signatureMatchesPublicKey(nextInChain, + trustedCert); + } else { + issuerMatchesSubject = issuerMatchesSubjectDN(cert, trustedCert); + signatureMatchesPublicKey = signatureMatchesPublicKey(cert, trustedCert); + } + + if (issuerMatchesSubject && signatureMatchesPublicKey) { + if (isSelfSigned(trustedCert)) { + log.info("CA Root found."); + return ""; + } else { + foundRootOfCertChain = intCAError; + nextInChain = trustedCert; + break; + } + } else { + if (!issuerMatchesSubject) { + foundRootOfCertChain = "Issuer DN does not match Subject DN"; + } + if (!signatureMatchesPublicKey) { + foundRootOfCertChain = "Certificate signature failed to verify"; + } + } + } + } while (foundRootOfCertChain.equals(intCAError)); + + log.error(foundRootOfCertChain); + return foundRootOfCertChain; + } + + /** + * Attempts to check if a public-key certificate is validated by certificates in a cert chain. + * The cert chain is represented as a Set of X509Certificates. If the signing certificate for + * the target cert is found, but it is an intermediate cert, the validation will continue to try + * to find the signing cert of the intermediate cert. It will continue searching until it + * follows the chain up to a root (self-signed) cert. + * + * @param cert + * certificate to validate + * @param additionalCerts + * Set of certs to validate against + * @return String status of the cert chain validation - + * blank if successful, error message otherwise + * @throws SupplyChainValidatorException tried to validate using null certificates + */ + public static String validateCertChain(final X509Certificate cert, + final Set additionalCerts) throws SupplyChainValidatorException { + if (cert == null || additionalCerts == null) { + throw new SupplyChainValidatorException( + "Certificate or validation certificates are null"); + } + final String intCAError = "Intermediate signing cert found, check for CA cert"; + String foundRootOfCertChain = ""; + X509Certificate startOfChain = cert; + + do { + for (X509Certificate trustedCert : additionalCerts) { + boolean issuerMatchesSubject = issuerMatchesSubjectDN(startOfChain, trustedCert); + boolean signatureMatchesPublicKey = signatureMatchesPublicKey(startOfChain, + trustedCert); + if (issuerMatchesSubject && signatureMatchesPublicKey) { + if (isSelfSigned(trustedCert)) { + log.info("CA Root found."); + return ""; + } else { + foundRootOfCertChain = intCAError; + startOfChain = trustedCert; + break; + } + } else { + if (!issuerMatchesSubject) { + foundRootOfCertChain = "Issuer DN does not match Subject DN"; + } + if (!signatureMatchesPublicKey) { + foundRootOfCertChain = "Certificate signature failed to verify"; + } + } + } + } while (foundRootOfCertChain.equals(intCAError)); + + log.warn(foundRootOfCertChain); + return foundRootOfCertChain; + } + + /** + * 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 java.io.IOException if something goes wrong parsing the JSON + */ + public static List getComponentInfoFromPaccorOutput(final String paccorOutput) + throws IOException { + List componentInfoList = new ArrayList<>(); + + 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(); + componentInfoList.add(new ComponentInfo( + getJSONNodeValueAsText(next, "MANUFACTURER"), + getJSONNodeValueAsText(next, "MODEL"), + getJSONNodeValueAsText(next, "SERIAL"), + getJSONNodeValueAsText(next, "REVISION"))); + } + } + + 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(); + } return null; } - @Override - public AppraisalStatus validatePlatformCredentialAttributes(final PlatformCredential pc, - final DeviceInfoReport deviceInfoReport, - final EndorsementCredential ec) { - return null; + /** + * Checks if the issuer info of an attribute cert matches the supposed signing cert's + * distinguished name. + * + * @param cert + * the attribute certificate with the signature to validate + * @param signingCert + * the certificate with the public key to validate + * @return boolean indicating if the names + * @throws SupplyChainValidatorException tried to validate using null certificates + */ + public static boolean issuerMatchesSubjectDN(final X509AttributeCertificateHolder cert, + final X509Certificate signingCert) throws SupplyChainValidatorException { + if (cert == null || signingCert == null) { + throw new SupplyChainValidatorException("Certificate or signing certificate is null"); + } + String signingCertSubjectDN = signingCert.getSubjectX500Principal().getName(); + X500Name namedSubjectDN = new X500Name(signingCertSubjectDN); + + X500Name issuerDN = cert.getIssuer().getNames()[0]; + + // equality check ignore DN component ordering + return issuerDN.equals(namedSubjectDN); } - @Override - public AppraisalStatus validateDeltaPlatformCredentialAttributes(final PlatformCredential delta, - final DeviceInfoReport deviceInfoReport, - final PlatformCredential base, - final Map deltaMapping) { - return null; + /** + * Checks if the issuer info of a public-key cert matches the supposed signing cert's + * distinguished name. + * + * @param cert + * the public-key certificate with the signature to validate + * @param signingCert + * the certificate with the public key to validate + * @return boolean indicating if the names + * @throws SupplyChainValidatorException tried to validate using null certificates + */ + public static boolean issuerMatchesSubjectDN(final X509Certificate cert, + final X509Certificate signingCert) throws SupplyChainValidatorException { + if (cert == null || signingCert == null) { + throw new SupplyChainValidatorException("Certificate or signing certificate is null"); + } + String signingCertSubjectDN = signingCert.getSubjectX500Principal(). + getName(X500Principal.RFC1779); + X500Name namedSubjectDN = new X500Name(signingCertSubjectDN); + + String certIssuerDN = cert.getIssuerX500Principal().getName(); + X500Name namedIssuerDN = new X500Name(certIssuerDN); + + // equality check ignore DN component ordering + return namedIssuerDN.equals(namedSubjectDN); } - @Override - public AppraisalStatus validateEndorsementCredential(final EndorsementCredential ec, - final KeyStore trustStore, - final boolean acceptExpired) { - return null; + /** + * Checks if the signature of an attribute cert is validated against the signing cert's public + * key. + * + * @param cert + * the public-key certificate with the signature to validate + * @param signingCert + * the certificate with the public key to validate + * @return boolean indicating if the validation passed + * @throws SupplyChainValidatorException tried to validate using null certificates + */ + public static boolean signatureMatchesPublicKey(final X509Certificate cert, + final X509Certificate signingCert) throws SupplyChainValidatorException { + if (cert == null || signingCert == null) { + throw new SupplyChainValidatorException("Certificate or signing certificate is null"); + } + try { + cert.verify(signingCert.getPublicKey(), BouncyCastleProvider.PROVIDER_NAME); + return true; + } catch (InvalidKeyException e) { + log.info("Incorrect key given to validate this cert's signature"); + } catch (CertificateException e) { + log.info("Encoding error while validating this cert's signature"); + } catch (NoSuchAlgorithmException e) { + log.info("Unsupported signature algorithm found during validation"); + } catch (NoSuchProviderException e) { + log.info("Incorrect provider for cert signature validation"); + } catch (SignatureException e) { + log.info(String.format("%s.verify(%s)", cert.getSubjectX500Principal(), + signingCert.getSubjectX500Principal())); + } + return false; + + } + + /** + * Checks if the signature of a public-key cert is validated against the signing cert's public + * key. + * + * @param cert + * the attribute certificate with the signature to validate + * @param signingCert + * the certificate with the public key to validate + * @return boolean indicating if the validation passed + * @throws SupplyChainValidatorException tried to validate using null certificates + */ + public static boolean signatureMatchesPublicKey(final X509AttributeCertificateHolder cert, + final X509Certificate signingCert) throws SupplyChainValidatorException { + if (signingCert == null) { + throw new SupplyChainValidatorException("Signing certificate is null"); + } + return signatureMatchesPublicKey(cert, signingCert.getPublicKey()); + } + + /** + * Checks if an X509 Attribute Certificate is valid directly against a public key. + * + * @param cert + * the attribute certificate with the signature to validate + * @param signingKey + * the key to use to check the attribute cert + * @return boolean indicating if the validation passed + * @throws SupplyChainValidatorException tried to validate using null certificates + */ + public static boolean signatureMatchesPublicKey(final X509AttributeCertificateHolder cert, + final PublicKey signingKey) throws SupplyChainValidatorException { + if (cert == null || signingKey == null) { + throw new SupplyChainValidatorException("Certificate or signing certificate is null"); + } + ContentVerifierProvider contentVerifierProvider; + try { + contentVerifierProvider = + new JcaContentVerifierProviderBuilder().setProvider("BC").build(signingKey); + return cert.isSignatureValid(contentVerifierProvider); + } catch (OperatorCreationException | CertException e) { + log.info("Exception thrown while verifying certificate", e); + log.info(String.format("%s.isSignatureValid(%s)", cert.getSerialNumber(), + signingKey.getFormat())); + return false; + } + } + + /** + * Checks whether given X.509 public-key certificate is self-signed. If the cert can be + * verified using its own public key, that means it was self-signed. + * + * @param cert + * X.509 Certificate + * @return boolean indicating if the cert was self-signed + */ + private static boolean isSelfSigned(final X509Certificate cert) + throws SupplyChainValidatorException { + if (cert == null) { + throw new SupplyChainValidatorException("Certificate is null"); + } + try { + PublicKey key = cert.getPublicKey(); + cert.verify(key); + return true; + } catch (SignatureException | InvalidKeyException e) { + return false; + } catch (CertificateException | NoSuchAlgorithmException | NoSuchProviderException e) { + log.error("Exception occurred while checking if cert is self-signed", e); + return false; + } } } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java index c2947c68..110bbd00 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java @@ -1,6 +1,7 @@ package hirs.attestationca.portal.page.controllers; import hirs.attestationca.persist.DBServiceException; +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; @@ -10,14 +11,14 @@ 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.entity.userdefined.rim.SupportReferenceManifest; -import hirs.attestationca.persist.service.SupplyChainValidationServiceImpl; +import hirs.attestationca.persist.service.ValidationManager; import hirs.attestationca.persist.validation.ReferenceManifestValidator; +import hirs.attestationca.persist.validation.SupplyChainCredentialValidator; import hirs.attestationca.persist.validation.SupplyChainValidatorException; import hirs.attestationca.portal.page.Page; import hirs.attestationca.portal.page.PageController; import hirs.attestationca.portal.page.PageMessages; import hirs.attestationca.portal.page.params.ReferenceManifestDetailsPageParams; -import hirs.attestationca.portal.page.utils.SupplyChainCredentialValidator; import hirs.utils.SwidResource; import hirs.utils.tpm.eventlog.TCGEventLog; import hirs.utils.tpm.eventlog.TpmPcrEvent; @@ -52,6 +53,7 @@ public class ReferenceManifestDetailsPageController extends PageController getRimDetailInfo(final UUID uuid, final ReferenceManifestRepository referenceManifestRepository, final ReferenceDigestValueRepository referenceDigestValueRepository, - final CertificateRepository certificateRepository) + final CertificateRepository certificateRepository, + final CACredentialRepository caCertificateRepository) throws IOException, CertificateException, NoSuchAlgorithmException { HashMap data = new HashMap<>(); @@ -146,7 +154,7 @@ public class ReferenceManifestDetailsPageController extends PageController getBaseRimInfo( final BaseReferenceManifest baseRim, final ReferenceManifestRepository referenceManifestRepository, - final CertificateRepository certificateRepository) + final CertificateRepository certificateRepository, + final CACredentialRepository caCertificateRepository) throws IOException, CertificateException, NoSuchAlgorithmException { HashMap data = new HashMap<>(); @@ -288,9 +298,7 @@ public class ReferenceManifestDetailsPageController extends PageController getComponentInfoFromPaccorOutput(final String paccorOutput) - throws IOException { - List componentInfoList = new ArrayList<>(); - - 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(); - componentInfoList.add(new ComponentInfo( - getJSONNodeValueAsText(next, "MANUFACTURER"), - getJSONNodeValueAsText(next, "MODEL"), - getJSONNodeValueAsText(next, "SERIAL"), - getJSONNodeValueAsText(next, "REVISION"))); - } - } - - 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(); - } - return null; - } - - /** - * Checks if the platform credential is valid. - * - * @param pc The platform credential to verify. - * @param trustStore trust store holding trusted certificates. - * @param acceptExpired whether or not to accept expired certificates as valid. - * @return The result of the validation. - */ - @Override - public AppraisalStatus validatePlatformCredential(final PlatformCredential pc, - final KeyStore trustStore, - final boolean acceptExpired) { - final String baseErrorMessage = "Can't validate platform credential without "; - String message; - String certVerifyMsg; - if (pc == null) { - message = baseErrorMessage + "a platform credential\n"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - try { - if (trustStore == null || trustStore.size() == 0) { - message = baseErrorMessage + "an Issuer Cert in the Trust Store\n"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - } catch (KeyStoreException e) { - message = baseErrorMessage + "an initialized trust store"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - - X509AttributeCertificateHolder attributeCert = null; - try { - attributeCert = pc.getX509AttributeCertificateHolder(); - } catch (IOException e) { - message = "Could not retrieve X509 Attribute certificate"; - log.error(message, e); - return new AppraisalStatus(FAIL, message + " " + e.getMessage()); - } - - // check validity period, currently acceptExpired will also accept not yet - // valid certificates - if (!acceptExpired && !pc.isValidOn(new Date())) { - message = "Platform credential has expired"; - // if not valid at the current time - log.warn(message); - return new AppraisalStatus(FAIL, message); - } - - // verify cert against truststore - try { - certVerifyMsg = verifyCertificate(attributeCert, trustStore); - if (certVerifyMsg.isEmpty()) { - message = PLATFORM_VALID; - log.info(message); - return new AppraisalStatus(PASS, message); - } else { - message = String.format("Platform credential failed verification%n%s", - certVerifyMsg); - log.error(message); - return new AppraisalStatus(FAIL, message); - } - } catch (SupplyChainValidatorException scvEx) { - message = "An error occurred indicating the credential is not valid"; - log.warn(message, scvEx); - return new AppraisalStatus(FAIL, message + " " + scvEx.getMessage()); - } - } - - /** - * Checks if the platform credential's attributes are valid. - * @param platformCredential The platform credential to verify. - * @param deviceInfoReport The device info report containing - * serial number of the platform to be validated. - * @param endorsementCredential The endorsement credential supplied from the same - * identity request as the platform credential. - * @return The result of the validation. - */ - @Override - public AppraisalStatus validatePlatformCredentialAttributes( - final PlatformCredential platformCredential, - final DeviceInfoReport deviceInfoReport, - final EndorsementCredential endorsementCredential) { - final String baseErrorMessage = "Can't validate platform credential attributes without "; - String message; - if (platformCredential == null) { - message = baseErrorMessage + "a platform credential"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - if (deviceInfoReport == null) { - message = baseErrorMessage + "a device info report"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - if (endorsementCredential == null) { - message = baseErrorMessage + "an endorsement credential"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - - // Quick, early check if the platform credential references the endorsement credential - if (!endorsementCredential.getSerialNumber() - .equals(platformCredential.getHolderSerialNumber())) { - message = "Platform Credential holder serial number does not match " - + "the Endorsement Credential's serial number"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - - String credentialType = platformCredential.getCredentialType(); - if (PlatformCredential.CERTIFICATE_TYPE_2_0.equals(credentialType)) { - return validatePlatformCredentialAttributesV2p0(platformCredential, deviceInfoReport); - } - return validatePlatformCredentialAttributesV1p2(platformCredential, deviceInfoReport); - } - - /** - * 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. - */ - @Override - public 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(deviceInfoReport, - deltaMapping, origPcComponents); - } - - private 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 - */ - 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( - 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 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( - 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 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(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(); - } - - /** - * 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 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(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(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(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; - } - - /** - * 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; - } - - /** - * 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; - } - - /** - * 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); - } - - 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; - } - - /** - * 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 ComponentIdentifier pcComponent, - final ComponentInfo potentialMatch) { - boolean matchesSoFar = true; - - matchesSoFar &= isMatchOrEmptyInPlatformCert( - potentialMatch.getComponentManufacturer(), - pcComponent.getComponentManufacturer() - ); - - matchesSoFar &= isMatchOrEmptyInPlatformCert( - potentialMatch.getComponentModel(), - pcComponent.getComponentModel() - ); - - matchesSoFar &= isMatchOrEmptyInPlatformCert( - potentialMatch.getComponentSerial(), - pcComponent.getComponentSerial() - ); - - matchesSoFar &= isMatchOrEmptyInPlatformCert( - potentialMatch.getComponentRevision(), - pcComponent.getComponentRevision() - ); - - 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) { - 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; - } - - /** - * Checks if the endorsement credential is valid. - * - * @param ec the endorsement credential to verify. - * @param trustStore trust store holding trusted trusted certificates. - * @param acceptExpired whether or not to accept expired and not yet valid certificates - * as valid. - * @return the result of the validation. - */ - @Override - public AppraisalStatus validateEndorsementCredential(final EndorsementCredential ec, - final KeyStore trustStore, - final boolean acceptExpired) { - final String baseErrorMessage = "Can't validate endorsement credential attributes without "; - String message; - if (ec == null) { - message = baseErrorMessage + "an endorsement credential"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - if (trustStore == null) { - message = baseErrorMessage + "a trust store"; - log.error(message); - return new AppraisalStatus(FAIL, message); - } - - try { - X509Certificate verifiableCert = ec.getX509Certificate(); - - // check validity period, currently acceptExpired will also accept not yet - // valid certificates - if (!acceptExpired) { - verifiableCert.checkValidity(); - } - - if (verifyCertificate(verifiableCert, trustStore)) { - return new AppraisalStatus(PASS, ENDORSEMENT_VALID); - } else { - return new AppraisalStatus(FAIL, "Endorsement credential does not have a valid " - + "signature chain in the trust store"); - } - } catch (IOException e) { - message = "Couldn't retrieve X509 certificate from endorsement credential"; - log.error(message, e); - return new AppraisalStatus(ERROR, message + " " + e.getMessage()); - } catch (SupplyChainValidatorException e) { - message = "An error occurred indicating the credential is not valid"; - log.warn(message, e); - return new AppraisalStatus(ERROR, message + " " + e.getMessage()); - } catch (CertificateExpiredException e) { - message = "The endorsement credential is expired"; - log.warn(message, e); - return new AppraisalStatus(FAIL, message + " " + e.getMessage()); - } catch (CertificateNotYetValidException e) { - message = "The endorsement credential is not yet valid"; - log.warn(message, e); - return new AppraisalStatus(FAIL, message + " " + e.getMessage()); - } - } - - /** - * Attempts to check if the certificate is validated by certificates in a cert chain. The cert - * chain is expected to be stored in a non-ordered KeyStore (trust store). If the signing - * certificate for the target cert is found, but it is an intermediate cert, the validation will - * continue to try to find the signing cert of the intermediate cert. It will continue searching - * until it follows the chain up to a root (self-signed) cert. - * - * @param cert - * certificate to validate - * @param trustStore - * trust store holding trusted root certificates and intermediate certificates - * @return the certificate chain if validation is successful - * @throws SupplyChainValidatorException - * if the verification is not successful - */ - public static String verifyCertificate(final X509AttributeCertificateHolder cert, - final KeyStore trustStore) throws SupplyChainValidatorException { - try { - if (cert == null || trustStore == null) { - throw new SupplyChainValidatorException("Certificate or trust store is null"); - } else if (trustStore.size() == 0) { - throw new SupplyChainValidatorException("Truststore is empty"); - } - } catch (KeyStoreException e) { - log.error("Error accessing trust store: " + e.getMessage()); - } - - try { - Set trustedCerts = new HashSet<>(); - - Enumeration alias = trustStore.aliases(); - - while (alias.hasMoreElements()) { - trustedCerts.add((X509Certificate) trustStore.getCertificate(alias.nextElement())); - } - - String certChainValidated = validateCertChain(cert, trustedCerts); - if (!certChainValidated.isEmpty()) { - log.error("Cert chain could not be validated"); - } - return certChainValidated; - } catch (KeyStoreException e) { - throw new SupplyChainValidatorException("Error with the trust store", e); - } - } - - /** - * Attempts to check if the certificate is validated by certificates in a cert chain. The cert - * chain is expected to be stored in a non-ordered KeyStore (trust store). If the signing - * certificate for the target cert is found, but it is an intermediate cert, the validation will - * continue to try to find the signing cert of the intermediate cert. It will continue searching - * until it follows the chain up to a root (self-signed) cert. - * - * @param cert - * certificate to validate - * @param trustStore - * trust store holding trusted root certificates and intermediate certificates - * @return the certificate chain if validation is successful - * @throws SupplyChainValidatorException - * if the verification is not successful - */ - public static boolean verifyCertificate(final X509Certificate cert, - final KeyStore trustStore) throws SupplyChainValidatorException { - try { - if (cert == null || trustStore == null) { - throw new SupplyChainValidatorException("Certificate or trust store is null"); - } else if (trustStore.size() == 0) { - throw new SupplyChainValidatorException("Truststore is empty"); - } - } catch (KeyStoreException e) { - log.error("Error accessing trust store: " + e.getMessage()); - } - - try { - Set trustedCerts = new HashSet<>(); - - Enumeration alias = trustStore.aliases(); - - while (alias.hasMoreElements()) { - trustedCerts.add((X509Certificate) trustStore.getCertificate(alias.nextElement())); - } - - return validateCertChain(cert, trustedCerts).isEmpty(); - } catch (KeyStoreException e) { - log.error("Error accessing keystore", e); - throw new SupplyChainValidatorException("Error with the trust store", e); - } - - } - - /** - * Attempts to check if an attribute certificate is validated by certificates in a cert chain. - * The cert chain is represented as a Set of X509Certificates. If the signing certificate for - * the target cert is found, but it is an intermediate cert, the validation will continue to try - * to find the signing cert of the intermediate cert. It will continue searching until it - * follows the chain up to a root (self-signed) cert. - * - * @param cert - * certificate to validate - * @param additionalCerts - * Set of certs to validate against - * @return String status of the cert chain validation - - * blank if successful, error message otherwise - * @throws SupplyChainValidatorException tried to validate using null certificates - */ - public static String validateCertChain(final X509AttributeCertificateHolder cert, - final Set additionalCerts) - throws SupplyChainValidatorException { - if (cert == null || additionalCerts == null) { - throw new SupplyChainValidatorException( - "Certificate or validation certificates are null"); - } - final String intCAError = "Intermediate signing cert found, check for CA cert"; - String foundRootOfCertChain = ""; - X509Certificate nextInChain = null; - - do { - for (X509Certificate trustedCert : additionalCerts) { - boolean issuerMatchesSubject = false; - boolean signatureMatchesPublicKey = false; - if (nextInChain != null) { - issuerMatchesSubject = issuerMatchesSubjectDN(nextInChain, trustedCert); - signatureMatchesPublicKey = signatureMatchesPublicKey(nextInChain, - trustedCert); - } else { - issuerMatchesSubject = issuerMatchesSubjectDN(cert, trustedCert); - signatureMatchesPublicKey = signatureMatchesPublicKey(cert, trustedCert); - } - - if (issuerMatchesSubject && signatureMatchesPublicKey) { - if (isSelfSigned(trustedCert)) { - log.info("CA Root found."); - return ""; - } else { - foundRootOfCertChain = intCAError; - nextInChain = trustedCert; - break; - } - } else { - if (!issuerMatchesSubject) { - foundRootOfCertChain = "Issuer DN does not match Subject DN"; - } - if (!signatureMatchesPublicKey) { - foundRootOfCertChain = "Certificate signature failed to verify"; - } - } - } - } while (foundRootOfCertChain.equals(intCAError)); - - log.error(foundRootOfCertChain); - return foundRootOfCertChain; - } - - /** - * Attempts to check if a public-key certificate is validated by certificates in a cert chain. - * The cert chain is represented as a Set of X509Certificates. If the signing certificate for - * the target cert is found, but it is an intermediate cert, the validation will continue to try - * to find the signing cert of the intermediate cert. It will continue searching until it - * follows the chain up to a root (self-signed) cert. - * - * @param cert - * certificate to validate - * @param additionalCerts - * Set of certs to validate against - * @return String status of the cert chain validation - - * blank if successful, error message otherwise - * @throws SupplyChainValidatorException tried to validate using null certificates - */ - public static String validateCertChain(final X509Certificate cert, - final Set additionalCerts) throws SupplyChainValidatorException { - if (cert == null || additionalCerts == null) { - throw new SupplyChainValidatorException( - "Certificate or validation certificates are null"); - } - final String intCAError = "Intermediate signing cert found, check for CA cert"; - String foundRootOfCertChain = ""; - X509Certificate startOfChain = cert; - - do { - for (X509Certificate trustedCert : additionalCerts) { - boolean issuerMatchesSubject = issuerMatchesSubjectDN(startOfChain, trustedCert); - boolean signatureMatchesPublicKey = signatureMatchesPublicKey(startOfChain, - trustedCert); - if (issuerMatchesSubject && signatureMatchesPublicKey) { - if (isSelfSigned(trustedCert)) { - log.info("CA Root found."); - return ""; - } else { - foundRootOfCertChain = intCAError; - startOfChain = trustedCert; - break; - } - } else { - if (!issuerMatchesSubject) { - foundRootOfCertChain = "Issuer DN does not match Subject DN"; - } - if (!signatureMatchesPublicKey) { - foundRootOfCertChain = "Certificate signature failed to verify"; - } - } - } - } while (foundRootOfCertChain.equals(intCAError)); - - log.warn(foundRootOfCertChain); - 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.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(); - } - - /** - * Checks if the issuer info of an attribute cert matches the supposed signing cert's - * distinguished name. - * - * @param cert - * the attribute certificate with the signature to validate - * @param signingCert - * the certificate with the public key to validate - * @return boolean indicating if the names - * @throws SupplyChainValidatorException tried to validate using null certificates - */ - public static boolean issuerMatchesSubjectDN(final X509AttributeCertificateHolder cert, - final X509Certificate signingCert) throws SupplyChainValidatorException { - if (cert == null || signingCert == null) { - throw new SupplyChainValidatorException("Certificate or signing certificate is null"); - } - String signingCertSubjectDN = signingCert.getSubjectX500Principal().getName(); - X500Name namedSubjectDN = new X500Name(signingCertSubjectDN); - - X500Name issuerDN = cert.getIssuer().getNames()[0]; - - // equality check ignore DN component ordering - return issuerDN.equals(namedSubjectDN); - } - - /** - * Checks if the issuer info of a public-key cert matches the supposed signing cert's - * distinguished name. - * - * @param cert - * the public-key certificate with the signature to validate - * @param signingCert - * the certificate with the public key to validate - * @return boolean indicating if the names - * @throws SupplyChainValidatorException tried to validate using null certificates - */ - public static boolean issuerMatchesSubjectDN(final X509Certificate cert, - final X509Certificate signingCert) throws SupplyChainValidatorException { - if (cert == null || signingCert == null) { - throw new SupplyChainValidatorException("Certificate or signing certificate is null"); - } - String signingCertSubjectDN = signingCert.getSubjectX500Principal(). - getName(X500Principal.RFC1779); - X500Name namedSubjectDN = new X500Name(signingCertSubjectDN); - - String certIssuerDN = cert.getIssuerX500Principal().getName(); - X500Name namedIssuerDN = new X500Name(certIssuerDN); - - // equality check ignore DN component ordering - return namedIssuerDN.equals(namedSubjectDN); - } - - /** - * Checks if the signature of an attribute cert is validated against the signing cert's public - * key. - * - * @param cert - * the public-key certificate with the signature to validate - * @param signingCert - * the certificate with the public key to validate - * @return boolean indicating if the validation passed - * @throws SupplyChainValidatorException tried to validate using null certificates - */ - public static boolean signatureMatchesPublicKey(final X509Certificate cert, - final X509Certificate signingCert) throws SupplyChainValidatorException { - if (cert == null || signingCert == null) { - throw new SupplyChainValidatorException("Certificate or signing certificate is null"); - } - try { - cert.verify(signingCert.getPublicKey(), BouncyCastleProvider.PROVIDER_NAME); - return true; - } catch (InvalidKeyException e) { - log.info("Incorrect key given to validate this cert's signature"); - } catch (CertificateException e) { - log.info("Encoding error while validating this cert's signature"); - } catch (NoSuchAlgorithmException e) { - log.info("Unsupported signature algorithm found during validation"); - } catch (NoSuchProviderException e) { - log.info("Incorrect provider for cert signature validation"); - } catch (SignatureException e) { - log.info(String.format("%s.verify(%s)", cert.getSubjectX500Principal(), - signingCert.getSubjectX500Principal())); - } - return false; - - } - - /** - * Checks if the signature of a public-key cert is validated against the signing cert's public - * key. - * - * @param cert - * the attribute certificate with the signature to validate - * @param signingCert - * the certificate with the public key to validate - * @return boolean indicating if the validation passed - * @throws SupplyChainValidatorException tried to validate using null certificates - */ - public static boolean signatureMatchesPublicKey(final X509AttributeCertificateHolder cert, - final X509Certificate signingCert) throws SupplyChainValidatorException { - if (signingCert == null) { - throw new SupplyChainValidatorException("Signing certificate is null"); - } - return signatureMatchesPublicKey(cert, signingCert.getPublicKey()); - } - - /** - * Checks if an X509 Attribute Certificate is valid directly against a public key. - * - * @param cert - * the attribute certificate with the signature to validate - * @param signingKey - * the key to use to check the attribute cert - * @return boolean indicating if the validation passed - * @throws SupplyChainValidatorException tried to validate using null certificates - */ - public static boolean signatureMatchesPublicKey(final X509AttributeCertificateHolder cert, - final PublicKey signingKey) throws SupplyChainValidatorException { - if (cert == null || signingKey == null) { - throw new SupplyChainValidatorException("Certificate or signing certificate is null"); - } - ContentVerifierProvider contentVerifierProvider; - try { - contentVerifierProvider = - new JcaContentVerifierProviderBuilder().setProvider("BC").build(signingKey); - return cert.isSignatureValid(contentVerifierProvider); - } catch (OperatorCreationException | CertException e) { - log.info("Exception thrown while verifying certificate", e); - log.info(String.format("%s.isSignatureValid(%s)", cert.getSerialNumber(), - signingKey.getFormat())); - return false; - } - } - - /** - * Checks whether given X.509 public-key certificate is self-signed. If the cert can be - * verified using its own public key, that means it was self-signed. - * - * @param cert - * X.509 Certificate - * @return boolean indicating if the cert was self-signed - */ - private static boolean isSelfSigned(final X509Certificate cert) - throws SupplyChainValidatorException { - if (cert == null) { - throw new SupplyChainValidatorException("Certificate is null"); - } - try { - PublicKey key = cert.getPublicKey(); - cert.verify(key); - return true; - } catch (SignatureException | InvalidKeyException e) { - return false; - } catch (CertificateException | NoSuchAlgorithmException | NoSuchProviderException e) { - log.error("Exception occurred while checking if cert is self-signed", e); - return false; - } - } -} diff --git a/HIRS_Utils/src/main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java b/HIRS_Utils/src/main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java index 7ceecdc0..aa83f937 100644 --- a/HIRS_Utils/src/main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java +++ b/HIRS_Utils/src/main/java/hirs/utils/tpm/eventlog/uefi/UefiGuid.java @@ -23,7 +23,7 @@ public class UefiGuid { */ private static final int UUID_EPOCH_DIVISOR = 10000; - private static final Path JSON_PATH = FileSystems.getDefault().getPath("/opt", + private static final Path JSON_PATH = FileSystems.getDefault().getPath("/etc", "hirs/aca", "default-properties", "vendor-table.json"); private JsonObject uefiVendorRef; /**