From 7d9f3d72f4618228846b87edbcc9de7d53913c0e Mon Sep 17 00:00:00 2001 From: Cyrus <24922493+cyrus-dev@users.noreply.github.com> Date: Fri, 21 Apr 2023 10:30:51 -0400 Subject: [PATCH] Updating the branch with additional code. The ACA loads, however trust-chain does not. I backed out x509 Certificate changes. [no ci] --- .../persist/CriteriaModifier.java | 14 + .../persist/DBManagerException.java | 46 + .../persist/DBServiceException.java | 40 + .../persist/FilteredRecordsList.java | 21 + .../persist/OrderedListQuerier.java | 65 + .../persist/entity/AbstractEntity.java | 31 +- .../persist/entity/ArchivableEntity.java | 3 + .../ReferenceDigestValueRepository.java | 11 + .../SupplyChainValidationRepository.java | 9 + .../entity/userdefined/Certificate.java | 20 +- .../userdefined/ReferenceDigestValue.java | 72 - .../entity/userdefined/ReferenceManifest.java | 1 - .../userdefined/SupplyChainValidation.java | 124 ++ .../SupplyChainValidationSummary.java | 267 +++ .../CertificateAuthorityCredential.java | 39 + .../certificate/ComponentResult.java | 41 + .../certificate/PlatformCredential.java | 177 +- .../attributes/ComponentClass.java | 1 + .../userdefined/info/ComponentInfo.java | 221 +++ .../rim/BaseReferenceManifest.java | 112 ++ .../userdefined/rim/EventLogMeasurements.java | 126 ++ .../userdefined/rim/ReferenceDigestValue.java | 135 ++ .../rim/SupportReferenceManifest.java | 93 + .../persist/enums/AppraisalStatus.java | 52 +- .../persist/service/CertificateService.java | 23 + .../service/CertificateServiceImpl.java | 71 +- .../persist/service/DbServiceImpl.java | 15 - .../persist/service/DefaultDbService.java | 199 ++ .../persist/service/HibernateDbService.java | 132 ++ .../service/ReferenceDigestValueService.java | 20 + .../ReferenceDigestValueServiceImpl.java | 64 + .../service/ReferenceManifestService.java | 23 + .../service/ReferenceManifestServiceImpl.java | 76 +- .../service/SupplyChainValidationService.java | 16 + .../SupplyChainValidationServiceImpl.java | 190 ++ .../service/selector/CertificateSelector.java | 473 +++++ .../selector/ReferenceManifestSelector.java | 233 +++ .../validation/CredentialValidator.java | 68 + .../ReferenceManifestValidator.java | 445 +++++ .../SupplyChainValidatorException.java | 45 + .../portal/HIRSDbInitializer.java | 43 - .../portal/PersistenceJPAConfig.java | 127 +- .../portal/datatables/Column.java | 77 + .../portal/datatables/DataTableInput.java | 224 +++ .../portal/datatables/DataTableResponse.java | 11 +- .../portal/datatables/Order.java | 72 + .../OrderedListQueryDataTableAdapter.java | 72 + .../portal/datatables/Search.java | 50 + .../CertificatePageController.java | 569 +++++- ...eferenceManifestDetailsPageController.java | 601 ++++++ .../ReferenceManifestPageController.java | 120 ++ .../RimDatabasePageController.java | 122 ++ .../utils/SupplyChainCredentialValidator.java | 1765 +++++++++++++++++ .../src/main/resources/portal.properties | 37 + .../src/main/resources/swid_schema.xsd | 1345 ------------- .../WEB-INF/jsp/certificate-details.jsp | 964 +++++++++ .../WEB-INF/jsp/issued-certificates.jsp | 131 ++ .../WEB-INF/jsp/platform-credentials.jsp | 136 ++ .../WEB-INF/jsp/reference-manifests.jsp | 78 + .../main/webapp/WEB-INF/jsp/rim-database.jsp | 75 + .../main/webapp/WEB-INF/jsp/rim-details.jsp | 653 ++++++ .../main/webapp/WEB-INF/jsp/trust-chain.jsp | 155 ++ .../webapp/WEB-INF/jsp/validation-reports.jsp | 256 +++ HIRS_Utils/src/main/resources/swid_schema.xsd | 1345 +++++++++++++ 64 files changed, 11167 insertions(+), 1675 deletions(-) create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/CriteriaModifier.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/DBManagerException.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/DBServiceException.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/FilteredRecordsList.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/OrderedListQuerier.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/SupplyChainValidationRepository.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceDigestValue.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/SupplyChainValidation.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/SupplyChainValidationSummary.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/ComponentResult.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/info/ComponentInfo.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/ReferenceDigestValue.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java delete mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DbServiceImpl.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DefaultDbService.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/HibernateDbService.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueService.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueServiceImpl.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestService.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/CertificateSelector.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/ReferenceManifestSelector.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CredentialValidator.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/ReferenceManifestValidator.java create mode 100644 HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainValidatorException.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Column.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/DataTableInput.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Order.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/OrderedListQueryDataTableAdapter.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Search.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestPageController.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/RimDatabasePageController.java create mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/SupplyChainCredentialValidator.java create mode 100644 HIRS_AttestationCAPortal/src/main/resources/portal.properties delete mode 100644 HIRS_AttestationCAPortal/src/main/resources/swid_schema.xsd create mode 100644 HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp create mode 100644 HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/issued-certificates.jsp create mode 100644 HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/platform-credentials.jsp create mode 100644 HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/reference-manifests.jsp create mode 100644 HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-database.jsp create mode 100644 HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-details.jsp create mode 100644 HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/trust-chain.jsp create mode 100644 HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/validation-reports.jsp create mode 100644 HIRS_Utils/src/main/resources/swid_schema.xsd diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/CriteriaModifier.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/CriteriaModifier.java new file mode 100644 index 00000000..452138f4 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/CriteriaModifier.java @@ -0,0 +1,14 @@ +package hirs.attestationca.persist; + +import jakarta.persistence.criteria.CriteriaQuery; + +/** + * Allows a user of the DBManager to modify the criteria object before processing. + */ +public interface CriteriaModifier { + /** + * Allows a client to modify the criteria object by reference. + * @param criteria The hibernate criteria builder object + */ + void modify(CriteriaQuery criteria); +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/DBManagerException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/DBManagerException.java new file mode 100644 index 00000000..e68d1b2e --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/DBManagerException.java @@ -0,0 +1,46 @@ +package hirs.attestationca.persist; + +/** + * This class represents an Exception generated by a + * DBManageer. + */ +public class DBManagerException extends RuntimeException { + + private static final long serialVersionUID = 3081536085161873284L; + + /** + * Creates a new DBManagerException that has the message + * msg. + * + * @param msg + * exception message + */ + public DBManagerException(final String msg) { + super(msg); + } + + /** + * Creates a new DBManagerException that wraps the given + * Throwable. + * + * @param t + * root cause + */ + public DBManagerException(final Throwable t) { + super(t); + } + + /** + * Creates a new DBManagerException that has the message + * msg and wraps the root cause. + * + * @param msg + * exception message + * @param t + * root cause + */ + public DBManagerException(final String msg, final Throwable t) { + super(msg, t); + } + +} \ No newline at end of file diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/DBServiceException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/DBServiceException.java new file mode 100644 index 00000000..35bddbbb --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/DBServiceException.java @@ -0,0 +1,40 @@ +package hirs.attestationca.persist; + +/** + * This class represents an Exception generated by a + * DBManageer. + */ +public class DBServiceException extends RuntimeException { + private static final long serialVersionUID = 3081536085161873284L; + + /** + * Creates a new DBManagerException that has the message + * msg. + * + * @param msg exception message + */ + public DBServiceException(final String msg) { + super(msg); + } + + /** + * Creates a new DBManagerException that wraps the given + * Throwable. + * + * @param t root cause + */ + public DBServiceException(final Throwable t) { + super(t); + } + + /** + * Creates a new DBManagerException that has the message + * msg and wraps the root cause. + * + * @param msg exception message + * @param t root cause + */ + DBServiceException(final String msg, final Throwable t) { + super(msg, t); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/FilteredRecordsList.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/FilteredRecordsList.java new file mode 100644 index 00000000..07c5d5d7 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/FilteredRecordsList.java @@ -0,0 +1,21 @@ +package hirs.attestationca.persist; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.ArrayList; + +/** + * FilteredRecordsList is an object designed to hold the results from multiple + * queries necessary to populate the JQuery Datatables. The members include + * the total number of records in the entity, the number of records returned + * after filtering through the search bar, and the records themselves. + * + * @param Class accepts generic for the list of data records. + */ +@Data +@EqualsAndHashCode(callSuper=false) +public class FilteredRecordsList extends ArrayList { + + private long recordsTotal, recordsFiltered; +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/OrderedListQuerier.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/OrderedListQuerier.java new file mode 100644 index 00000000..f303e0e4 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/OrderedListQuerier.java @@ -0,0 +1,65 @@ +package hirs.attestationca.persist; + +import java.util.Map; + +/** + * Interface defining methods for getting ordered lists from a data source. Includes + * properties for sorting, paging, and searching. + * @param the record type, T. + */ +public interface OrderedListQuerier { + + /** + * Returns a list of all Ts that are ordered by a column and + * direction (ASC, DESC) that is provided by the user. This method helps + * support the server-side processing in the JQuery DataTables. + * + * @param clazz class type of Ts to search for (may be null to + * use Class<T>) + * @param columnToOrder Column to be ordered + * @param ascending direction of sort + * @param firstResult starting point of first result in set + * @param maxResults total number we want returned for display in table + * @param search string of criteria to be matched to visible columns + * @param searchableColumns Map of String and boolean values with column + * headers and whether they should be searched. Boolean is true if field provides a + * typical String that can be searched by Hibernate without transformation. + * @return FilteredRecordsList object with query data + * @throws DBManagerException if unable to create the list + */ + FilteredRecordsList getOrderedList( + Class clazz, String columnToOrder, + boolean ascending, int firstResult, + int maxResults, String search, + Map searchableColumns) + throws DBManagerException; + + + /** + * Returns a list of all Ts that are ordered by a column and + * direction (ASC, DESC) that is provided by the user. This method helps + * support the server-side processing in the JQuery DataTables. For entities that support + * soft-deletes, the returned list does not contain Ts that have been soft-deleted. + * + * @param clazz class type of Ts to search for (may be null to + * use Class<T>) + * @param columnToOrder Column to be ordered + * @param ascending direction of sort + * @param firstResult starting point of first result in set + * @param maxResults total number we want returned for display in table + * @param search string of criteria to be matched to visible columns + * @param searchableColumns Map of String and boolean values with column + * headers and whether they should be searched. Boolean is true if field provides a + * typical String that can be searched by Hibernate without transformation. + * @param criteriaModifier a way to modify the criteria used in the query + * @return FilteredRecordsList object with query data + * @throws DBManagerException if unable to create the list + */ + @SuppressWarnings("checkstyle:parameternumber") + FilteredRecordsList getOrderedList( + Class clazz, String columnToOrder, + boolean ascending, int firstResult, + int maxResults, String search, + Map searchableColumns, CriteriaModifier criteriaModifier) + throws DBManagerException; +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/AbstractEntity.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/AbstractEntity.java index 9f6b2a52..669fb900 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/AbstractEntity.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/AbstractEntity.java @@ -2,15 +2,15 @@ package hirs.attestationca.persist.entity; import jakarta.persistence.Column; import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.Generated; import org.hibernate.annotations.GenerationTime; -import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.UuidGenerator; import java.io.Serializable; import java.util.Date; @@ -19,6 +19,7 @@ import java.util.UUID; /** * An abstract database entity. */ +@EqualsAndHashCode @ToString @MappedSuperclass public abstract class AbstractEntity implements Serializable { @@ -31,8 +32,8 @@ public abstract class AbstractEntity implements Serializable { @Id @Column(name = "id") - @GeneratedValue(generator = "uuid2", strategy=GenerationType.AUTO) - @JdbcTypeCode(java.sql.Types.VARCHAR) + @UuidGenerator(style = UuidGenerator.Style.AUTO) + @GeneratedValue @Getter private UUID id; @@ -75,26 +76,4 @@ public abstract class AbstractEntity implements Serializable { public void resetCreateTime() { createTime.setTime(new Date().getTime()); } - - @Override - public int hashCode() { - if (id != null) { - return id.hashCode(); - } - return super.hashCode(); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(this.getClass().equals(obj.getClass()))) { - return false; - } - return this.hashCode() == obj.hashCode(); - } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/ArchivableEntity.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/ArchivableEntity.java index b063fcaa..a39ec842 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/ArchivableEntity.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/ArchivableEntity.java @@ -4,6 +4,8 @@ import jakarta.persistence.Column; import jakarta.persistence.MappedSuperclass; import lombok.Getter; import lombok.ToString; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; import java.util.Date; @@ -23,6 +25,7 @@ public abstract class ArchivableEntity extends AbstractEntity { @Column(name = "archived_time") private Date archivedTime; + @JdbcTypeCode(SqlTypes.LONGVARCHAR) @Column(name = "archived_description") private String archivedDescription; 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 new file mode 100644 index 00000000..7117a66d --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/ReferenceDigestValueRepository.java @@ -0,0 +1,11 @@ +package hirs.attestationca.persist.entity.manager; + +import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface ReferenceDigestValueRepository extends JpaRepository { +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/SupplyChainValidationRepository.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/SupplyChainValidationRepository.java new file mode 100644 index 00000000..7111c3a9 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/manager/SupplyChainValidationRepository.java @@ -0,0 +1,9 @@ +package hirs.attestationca.persist.entity.manager; + +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface SupplyChainValidationRepository extends JpaRepository { +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java index 387165f6..bddaa21f 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/Certificate.java @@ -9,8 +9,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Transient; import lombok.Getter; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import lombok.extern.log4j.Log4j2; import org.bouncycastle.asn1.ASN1BitString; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1GeneralizedTime; @@ -76,11 +75,10 @@ import java.util.Objects; * It stores certain attributes separately from the serialized certificate to enable querying on * those attributes. */ +@Log4j2 @Entity public abstract class Certificate extends ArchivableEntity { - private static final Logger LOGGER = LogManager.getLogger(Certificate.class); - /** * Holds the different certificate types. */ @@ -561,8 +559,8 @@ public abstract class Certificate extends ArchivableEntity { try { return getX509Certificate().getVersion() - 1; } catch (IOException ex) { - LOGGER.warn("X509 Credential Version not found."); - LOGGER.error(ex); + log.warn("X509 Credential Version not found."); + log.error(ex); return Integer.MAX_VALUE; } } @@ -589,7 +587,7 @@ public abstract class Certificate extends ArchivableEntity { isIssuer = ""; } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) { - LOGGER.error(e); + log.error(e); } break; case ATTRIBUTE_CERTIFICATE: @@ -604,7 +602,7 @@ public abstract class Certificate extends ArchivableEntity { } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException sigEx) { - LOGGER.error(sigEx); + log.error(sigEx); } break; default: @@ -769,7 +767,7 @@ public abstract class Certificate extends ArchivableEntity { asn1InputStream = new ASN1InputStream(oct.getOctets()); asn1Primitive = asn1InputStream.readObject(); } catch (IOException ioEx) { - LOGGER.error(ioEx); + log.error(ioEx); } finally { if (asn1InputStream != null) { asn1InputStream.close(); @@ -794,7 +792,7 @@ public abstract class Certificate extends ArchivableEntity { .getInstance(JcaX509ExtensionUtils.parseExtensionValue(authInfoAccess)))); } } catch (IOException ioEx) { - LOGGER.error(ioEx); + log.error(ioEx); } return sb.toString(); @@ -993,7 +991,7 @@ public abstract class Certificate extends ArchivableEntity { certificateHolder.getSubjectPublicKeyInfo().parsePublicKey().toASN1Primitive() ); } catch (IOException e) { - LOGGER.info("No RSA Key Detected in certificate"); + log.info("No RSA Key Detected in certificate"); return null; } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceDigestValue.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceDigestValue.java deleted file mode 100644 index 4d7949c9..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceDigestValue.java +++ /dev/null @@ -1,72 +0,0 @@ -package hirs.attestationca.persist.entity.userdefined; - -import hirs.attestationca.persist.entity.ArchivableEntity; -import jakarta.persistence.Access; -import jakarta.persistence.AccessType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import org.hibernate.annotations.JdbcTypeCode; - -import java.util.UUID; - -/** - * This class represents that actual entry in the Support RIM. - * Digest Value, Event Type, index, RIM Tagid - */ -@ToString @EqualsAndHashCode(callSuper = false) -@Setter @Getter -@Entity -@Table(name = "ReferenceDigestValue") -@Access(AccessType.FIELD) -public class ReferenceDigestValue extends ArchivableEntity { - -// @Type(type = "uuid-char") - @JdbcTypeCode(java.sql.Types.VARCHAR) - @Column - private UUID baseRimId; -// @Type(type = "uuid-char") - @JdbcTypeCode(java.sql.Types.VARCHAR) - @Column - private UUID supportRimId; - @Column(nullable = false) - private String manufacturer; - @Column(nullable = false) - private String model; - @Column(nullable = false) - private int pcrIndex; - @Column(nullable = false) - private String digestValue; - @Column(nullable = false) - private String eventType; - @Column(columnDefinition = "blob", nullable = true) - private byte[] contentBlob; - @Column(nullable = false) - private boolean matchFail; - @Column(nullable = false) - private boolean patched = false; - @Column(nullable = false) - private boolean updated = false; - - /** - * Default constructor necessary for Hibernate. - */ - protected ReferenceDigestValue() { - super(); - this.baseRimId = null; - this.supportRimId = null; - this.manufacturer = ""; - this.model = ""; - this.pcrIndex = -1; - this.digestValue = ""; - this.eventType = ""; - this.matchFail = false; - this.patched = false; - this.updated = false; - this.contentBlob = null; - } -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceManifest.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceManifest.java index 0cdb64ca..0fda758e 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceManifest.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/ReferenceManifest.java @@ -92,7 +92,6 @@ public class ReferenceManifest extends ArchivableEntity { private String platformModel = null; @Column(nullable = false) private String fileName = null; -// @Type(type="uuid-char") @JdbcTypeCode(java.sql.Types.VARCHAR) @Column private UUID associatedRim; diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/SupplyChainValidation.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/SupplyChainValidation.java new file mode 100644 index 00000000..97c892e5 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/SupplyChainValidation.java @@ -0,0 +1,124 @@ +package hirs.attestationca.persist.entity.userdefined; + +import com.google.common.base.Preconditions; +import hirs.attestationca.persist.entity.ArchivableEntity; +import hirs.attestationca.persist.enums.AppraisalStatus; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Stores results of a single element of the supply chain validation process. + */ +@Entity +public class SupplyChainValidation extends ArchivableEntity { + /** + * Used to indicate which type of validation a result is related to. + */ + public enum ValidationType { + /** + * Validation of an endorsement credential. + */ + ENDORSEMENT_CREDENTIAL, + + /** + * Validation of a platform credential and also delta platform credentials from spec 1.1. + */ + PLATFORM_CREDENTIAL, + + /** + * Validation of a platform credential's attributes. + */ + PLATFORM_CREDENTIAL_ATTRIBUTES, + + /** + * Validation of the device firmware. + */ + FIRMWARE + } + + @Getter + @Column + private final ValidationType validationType; + + @Getter + @Column + private final AppraisalStatus.Status validationResult; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "CertificatesUsedToValidate", + joinColumns = { @JoinColumn(name = "validation_id", nullable = false) }) + private final List certificatesUsed; + + @Getter + @Column(length = MAX_MESSAGE_LENGTH) + private final String message; + + @Getter + @Column + private String rimId; + + /** + * Default constructor necessary for Hibernate. + */ + public SupplyChainValidation() { + this.validationType = null; + this.validationResult = AppraisalStatus.Status.ERROR; + this.certificatesUsed = Collections.emptyList(); + this.message = null; + this.rimId = ""; + } + + /** + * Construct a new SupplyChainValidation instance. + * + * @param validationType the type of validation this instance will represent; not null + * @param validationResult whether the validation was successful or not + * @param certificatesUsed certificates used, if any, in the validation process; not null + * @param message a related information or error message; may be null + */ + public SupplyChainValidation(final ValidationType validationType, + final AppraisalStatus.Status validationResult, + final List certificatesUsed, + final String message) { + Preconditions.checkArgument( + validationType != null, + "Cannot construct a SupplyChainValidation with a null ValidationType" + ); + + Preconditions.checkArgument( + certificatesUsed != null, + "Cannot construct a SupplyChainValidation with a null certificatesUsed" + ); + + this.validationType = validationType; + this.validationResult = validationResult; + this.certificatesUsed = new ArrayList<>(); + this.rimId = ""; + for (ArchivableEntity ae : certificatesUsed) { + if (ae instanceof ReferenceManifest) { + this.rimId = ae.getId().toString(); + break; + } else { + this.certificatesUsed.add((Certificate) ae); + } + } + + this.message = message; + } + + /** + * @return certificates used in the process of performing the validation; may be empty + */ + public List getCertificatesUsed() { + return Collections.unmodifiableList(certificatesUsed); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/SupplyChainValidationSummary.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/SupplyChainValidationSummary.java new file mode 100644 index 00000000..72b5dcd1 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/SupplyChainValidationSummary.java @@ -0,0 +1,267 @@ +package hirs.attestationca.persist.entity.userdefined; + +import com.google.common.base.Preconditions; +import hirs.attestationca.persist.entity.ArchivableEntity; +import hirs.attestationca.persist.enums.AppraisalStatus; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; +import org.springframework.data.repository.CrudRepository; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + + + +/** + * A container class to group multiple related {@link SupplyChainValidation} instances + * together. + */ +@Entity +public class SupplyChainValidationSummary extends ArchivableEntity { + + @ManyToOne + @JoinColumn(name = "device_id") + private final Device device; + + private static final String DEVICE_ID_FIELD = "device.id"; + + @Column + @Enumerated(EnumType.STRING) + private final AppraisalStatus.Status overallValidationResult; + + @Column(length = RESULT_MESSAGE_LENGTH) + private final String message; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, + targetEntity = SupplyChainValidation.class, orphanRemoval = true) + private final Set validations; + + /** + * Default constructor necessary for Hibernate. + */ + protected SupplyChainValidationSummary() { + this.device = null; + overallValidationResult = AppraisalStatus.Status.FAIL; + validations = Collections.emptySet(); + this.message = Strings.EMPTY; + } + + /** + * This class enables the retrieval of SupplyChainValidationSummaries by their attributes. + */ + public static class Selector { + private final CrudRepository + supplyChainValidationSummaryCrudManager; + + private final Map fieldValueSelections; + + /** + * Construct a new Selector that will use the given {@link CrudRepository} to + * retrieve SupplyChainValidationSummaries. + * + * @param supplyChainValidationSummaryCrudManager the summary manager to be used to retrieve + * supply chain validation summaries + */ + public Selector( + final CrudRepository + supplyChainValidationSummaryCrudManager) { + Preconditions.checkArgument( + supplyChainValidationSummaryCrudManager != null, + "supply chain validation summary manager cannot be null" + ); + + this.supplyChainValidationSummaryCrudManager = supplyChainValidationSummaryCrudManager; + this.fieldValueSelections = new HashMap<>(); + } + + /** + * Construct the criterion that can be used to query for supply chain validation summaries + * matching the configuration of this Selector. + * + * @return a Criterion that can be used to query for supply chain validation summaries + * matching the configuration of this instance + */ + public Predicate[] getCriterion(final CriteriaBuilder criteriaBuilder) { + Predicate[] predicates = new Predicate[fieldValueSelections.size()]; + CriteriaQuery query = criteriaBuilder.createQuery(SupplyChainValidationSummary.class); + Root root = query.from(SupplyChainValidationSummary.class); + + int i = 0; + for (Map.Entry fieldValueEntry : fieldValueSelections.entrySet()) { + predicates[i++] = criteriaBuilder.equal(root.get(fieldValueEntry.getKey()), fieldValueEntry.getValue()); + } + + return predicates; + } + + /** + * Set a field name and value to match. + * + * @param name the field name to query + * @param value the value to query + */ + protected void setFieldValue(final String name, final Object value) { + Object valueToAssign = value; + + Preconditions.checkArgument( + value != null, + "field value cannot be null." + ); + + if (value instanceof String) { + Preconditions.checkArgument( + StringUtils.isNotEmpty((String) value), + "field value cannot be empty." + ); + } + + if (value instanceof byte[]) { + byte[] valueBytes = (byte[]) value; + + Preconditions.checkArgument( + ArrayUtils.isNotEmpty(valueBytes), + "field value cannot be empty." + ); + + valueToAssign = Arrays.copyOf(valueBytes, valueBytes.length); + } + + fieldValueSelections.put(name, valueToAssign); + } + + + /** + * Specify a device id that supply chain validation summaries must have to be considered + * as matching. + * + * @param device the device id to query + * @return this instance (for chaining further calls) + */ + public Selector byDeviceId(final UUID device) { + setFieldValue(DEVICE_ID_FIELD, device); + return this; + } + } + + /** + * Get a Selector for use in retrieving SupplyChainValidationSummary. + * + * @param certMan the CrudManager to be used to retrieve persisted supply chain validation + * summaries + * @return a SupplyChainValidationSummary.Selector instance to use for retrieving certificates + */ + public static SupplyChainValidationSummary.Selector select( + final CrudRepository certMan) { + return new SupplyChainValidationSummary.Selector(certMan); + } + + /** + * Construct a new SupplyChainValidationSummary. + * + * @param device device that underwent supply chain validation + * @param validations a Collection of Validations that should comprise this summary; not null + */ + public SupplyChainValidationSummary(final Device device, + final Collection validations) { + + Preconditions.checkArgument( + device != null, + "Cannot construct a SupplyChainValidationSummary with a null device" + ); + + Preconditions.checkArgument( + validations != null, + "Cannot construct a SupplyChainValidationSummary with a null validations list" + ); + + this.device = device; + AppraisalStatus status = calculateValidationResult(validations); + this.overallValidationResult = status.getAppStatus(); + this.validations = new HashSet<>(validations); + this.message = status.getMessage(); + } + + /** + * This retrieves the device associated with the supply chain validation summaries. + * + * @return the validated device + */ + public Device getDevice() { + return device; + } + + /** + * @return the overall appraisal result + */ + public AppraisalStatus.Status getOverallValidationResult() { + return overallValidationResult; + } + + /** + * @return the fail message if there is a failure. + */ + public String getMessage() { + return message; + } + + /** + * @return the validations that this summary contains + */ + public Set getValidations() { + return Collections.unmodifiableSet(validations); + } + + /** + * A utility method that helps determine the overall appraisal result. + * + * @param validations the validations to evaluate + * @return the overall appraisal result + */ + private AppraisalStatus calculateValidationResult( + final Collection validations) { + boolean hasAnyFailures = false; + StringBuilder failureMsg = new StringBuilder(); + + for (SupplyChainValidation validation : validations) { + switch (validation.getValidationResult()) { + // if any error, then process overall as error immediately. + case ERROR: + return new AppraisalStatus(AppraisalStatus.Status.ERROR, + validation.getMessage()); + case FAIL: + hasAnyFailures = true; + failureMsg.append(String.format("%s%n", validation.getValidationType())); + break; + default: + break; + } + } + // if failures, but no error, indicate failure result. + if (hasAnyFailures) { + return new AppraisalStatus(AppraisalStatus.Status.FAIL, + failureMsg.toString()); + } + return new AppraisalStatus(AppraisalStatus.Status.PASS, + Strings.EMPTY); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateAuthorityCredential.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateAuthorityCredential.java index 72047d42..006d7a47 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateAuthorityCredential.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/CertificateAuthorityCredential.java @@ -1,6 +1,8 @@ package hirs.attestationca.persist.entity.userdefined.certificate; import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.service.CertificateService; +import hirs.attestationca.persist.service.selector.CertificateSelector; import jakarta.persistence.Column; import jakarta.persistence.Entity; import lombok.Getter; @@ -44,6 +46,43 @@ public class CertificateAuthorityCredential extends Certificate { @Column private final String credentialType = "TCPA Trusted Platform Module Endorsement"; + /** + * This class enables the retrieval of CertificateAuthorityCredentials by their attributes. + */ + public static class Selector extends CertificateSelector { + /** + * Construct a new CertificateSelector that will use the given {@link CertificateService} to + * retrieve one or many CertificateAuthorityCredentials. + * + * @param certificateManager the certificate manager to be used to retrieve certificates + */ + public Selector(final CertificateService certificateManager) { + super(certificateManager, CertificateAuthorityCredential.class); + } + + /** + * Specify a subject key identifier that certificates must have to be considered + * as matching. + * + * @param subjectKeyIdentifier a subject key identifier buffer to query, not empty or null + * @return this instance (for chaining further calls) + */ + public Selector bySubjectKeyIdentifier(final byte[] subjectKeyIdentifier) { + setFieldValue(SUBJECT_KEY_IDENTIFIER_FIELD, subjectKeyIdentifier); + return this; + } + } + + /** + * Get a Selector for use in retrieving CertificateAuthorityCredentials. + * + * @param certMan the CertificateService to be used to retrieve persisted certificates + * @return a CertificateAuthorityCredential.Selector instance to use for retrieving certificates + */ + public static Selector select(final CertificateService certMan) { + return new Selector(certMan); + } + /** * Construct a new CertificateAuthorityCredential given its binary contents. The given * certificate should represent either an X509 certificate or X509 attribute certificate. diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/ComponentResult.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/ComponentResult.java new file mode 100644 index 00000000..d54bea4a --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/ComponentResult.java @@ -0,0 +1,41 @@ +package hirs.attestationca.persist.entity.userdefined.certificate; + +import hirs.attestationca.persist.entity.AbstractEntity; +import jakarta.persistence.Entity; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.Objects; +import java.util.UUID; + +@EqualsAndHashCode(callSuper=false) +@Getter +@Entity +public class ComponentResult extends AbstractEntity { + + private UUID certificateId; + private int componentHash; + private String expected; + private String actual; + private boolean mismatched; + + /** + * Hibernate default constructor + */ + protected ComponentResult() { + } + + public ComponentResult(final UUID certificateId, final int componentHash, + final String expected, final String actual) { + this.certificateId = certificateId; + this.componentHash = componentHash; + this.expected = expected; + this.actual = actual; + this.mismatched = Objects.equals(expected, actual); + } + + public String toString() { + return String.format("ComponentResult[%d]: expected=[%s] actual=[%s]", + componentHash, expected, actual); + } +} 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 0c134b04..30babffc 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 @@ -7,6 +7,8 @@ import hirs.attestationca.persist.entity.userdefined.certificate.attributes.Plat import hirs.attestationca.persist.entity.userdefined.certificate.attributes.TBBSecurityAssertion; import hirs.attestationca.persist.entity.userdefined.certificate.attributes.URIReference; import hirs.attestationca.persist.entity.userdefined.certificate.attributes.V2.PlatformConfigurationV2; +import hirs.attestationca.persist.service.CertificateService; +import hirs.attestationca.persist.service.selector.CertificateSelector; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Transient; @@ -46,6 +48,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; /** * This class persists Platform credentials by extending the base Certificate @@ -94,7 +97,7 @@ public class PlatformCredential extends DeviceAssociatedCertificate { * TCG Platform Specification values * At this time these are placeholder values. */ - private static final Map TCG_PLATFORM_MAP = new HashMap() {{ + private static final Map TCG_PLATFORM_MAP = new HashMap<>() {{ put("#00000000", "Unclassified"); put("#00000001", "PC Client"); put("#00000002", "PDA"); @@ -128,89 +131,89 @@ public class PlatformCredential extends DeviceAssociatedCertificate { /** * This class enables the retrieval of PlatformCredentials by their attributes. */ -// public static class Selector extends CertificateSelector { -// /** -// * Construct a new CertificateSelector that will use the given {@link CertificateManager} to -// * retrieve one or many PlatformCredentials. -// * -// * @param certificateManager the certificate manager to be used to retrieve certificates -// */ -// public Selector(final CertificateManager certificateManager) { -// super(certificateManager, PlatformCredential.class); -// } -// -// /** -// * Specify a manufacturer that certificates must have to be considered as matching. -// * @param manufacturer the manufacturer to query, not empty or null -// * @return this instance (for chaining further calls) -// */ -// public Selector byManufacturer(final String manufacturer) { -// setFieldValue(MANUFACTURER_FIELD, manufacturer); -// return this; -// } -// -// /** -// * Specify a model that certificates must have to be considered as matching. -// * @param model the model to query, not empty or null -// * @return this instance (for chaining further calls) -// */ -// public Selector byModel(final String model) { -// setFieldValue(MODEL_FIELD, model); -// return this; -// } -// -// /** -// * Specify a version that certificates must have to be considered as matching. -// * @param version the version to query, not empty or null -// * @return this instance (for chaining further calls) -// */ -// public Selector byVersion(final String version) { -// setFieldValue(VERSION_FIELD, version); -// return this; -// } -// -// /** -// * Specify a serial number that certificates must have to be considered as matching. -// * @param serialNumber the serial number to query, not empty or null -// * @return this instance (for chaining further calls) -// */ -// public Selector bySerialNumber(final String serialNumber) { -// setFieldValue(SERIAL_NUMBER_FIELD, serialNumber); -// return this; -// } -// -// /** -// * Specify a board serial number that certificates must have to be considered as matching. -// * @param boardSerialNumber the board serial number to query, not empty or null -// * @return this instance (for chaining further calls) -// */ -// public Selector byBoardSerialNumber(final String boardSerialNumber) { -// setFieldValue(PLATFORM_SERIAL_FIELD, boardSerialNumber); -// return this; -// } -// -// /** -// * Specify a chassis serial number that certificates must have to be considered as matching. -// * @param chassisSerialNumber the board serial number to query, not empty or null -// * @return this instance (for chaining further calls) -// */ -// public Selector byChassisSerialNumber(final String chassisSerialNumber) { -// setFieldValue(CHASSIS_SERIAL_NUMBER_FIELD, chassisSerialNumber); -// return this; -// } -// -// /** -// * Specify a device id that certificates must have to be considered -// * as matching. -// * -// * @param device the device id to query -// * @return this instance (for chaining further calls) -// */ -// public Selector byDeviceId(final UUID device) { -// setFieldValue(DEVICE_ID_FIELD, device); -// return this; -// } -// } + public static class Selector extends CertificateSelector { + /** + * Construct a new CertificateSelector that will use the given {@link CertificateService} to + * retrieve one or many PlatformCredentials. + * + * @param certificateManager the certificate manager to be used to retrieve certificates + */ + public Selector(final CertificateService certificateManager) { + super(certificateManager, PlatformCredential.class); + } + + /** + * Specify a manufacturer that certificates must have to be considered as matching. + * @param manufacturer the manufacturer to query, not empty or null + * @return this instance (for chaining further calls) + */ + public Selector byManufacturer(final String manufacturer) { + setFieldValue(MANUFACTURER_FIELD, manufacturer); + return this; + } + + /** + * Specify a model that certificates must have to be considered as matching. + * @param model the model to query, not empty or null + * @return this instance (for chaining further calls) + */ + public Selector byModel(final String model) { + setFieldValue(MODEL_FIELD, model); + return this; + } + + /** + * Specify a version that certificates must have to be considered as matching. + * @param version the version to query, not empty or null + * @return this instance (for chaining further calls) + */ + public Selector byVersion(final String version) { + setFieldValue(VERSION_FIELD, version); + return this; + } + + /** + * Specify a serial number that certificates must have to be considered as matching. + * @param serialNumber the serial number to query, not empty or null + * @return this instance (for chaining further calls) + */ + public Selector bySerialNumber(final String serialNumber) { + setFieldValue(SERIAL_NUMBER_FIELD, serialNumber); + return this; + } + + /** + * Specify a board serial number that certificates must have to be considered as matching. + * @param boardSerialNumber the board serial number to query, not empty or null + * @return this instance (for chaining further calls) + */ + public Selector byBoardSerialNumber(final String boardSerialNumber) { + setFieldValue(PLATFORM_SERIAL_FIELD, boardSerialNumber); + return this; + } + + /** + * Specify a chassis serial number that certificates must have to be considered as matching. + * @param chassisSerialNumber the board serial number to query, not empty or null + * @return this instance (for chaining further calls) + */ + public Selector byChassisSerialNumber(final String chassisSerialNumber) { + setFieldValue(CHASSIS_SERIAL_NUMBER_FIELD, chassisSerialNumber); + return this; + } + + /** + * Specify a device id that certificates must have to be considered + * as matching. + * + * @param device the device id to query + * @return this instance (for chaining further calls) + */ + public Selector byDeviceId(final UUID device) { + setFieldValue(DEVICE_ID_FIELD, device); + return this; + } + } @Column private String credentialType = null; @@ -275,9 +278,9 @@ public class PlatformCredential extends DeviceAssociatedCertificate { * @param certMan the CertificateManager to be used to retrieve persisted certificates * @return a PlatformCredential.Selector instance to use for retrieving certificates */ -// public static Selector select(final CertificateManager certMan) { -// return new Selector(certMan); -// } + public static Selector select(final CertificateService certMan) { + return new Selector(certMan); + } /** * Construct a new PlatformCredential given its binary contents. ParseFields is diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/attributes/ComponentClass.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/attributes/ComponentClass.java index 40ae1763..a57b6007 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/attributes/ComponentClass.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/certificate/attributes/ComponentClass.java @@ -52,6 +52,7 @@ public class ComponentClass { @Getter private String component, componentStr; private String registryType; + @Getter private String componentIdentifier; /** diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/info/ComponentInfo.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/info/ComponentInfo.java new file mode 100644 index 00000000..1b6887d8 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/info/ComponentInfo.java @@ -0,0 +1,221 @@ +package hirs.attestationca.persist.entity.userdefined.info; + +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.xml.bind.annotation.XmlElement; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.annotations.DiscriminatorOptions; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.Objects; + +/** + * ComponentInfo is a class to hold Hardware component information + * such as manufacturer, model, serial number and version. + */ +@NoArgsConstructor +@Entity +@DiscriminatorColumn(name = "componentTypeEnum", discriminatorType = DiscriminatorType.STRING) +@DiscriminatorOptions(force = true) +public class ComponentInfo implements Serializable { + + @Id + @Column(name = "componentInfo_id") + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @XmlElement + @Column(nullable = false) + private String componentManufacturer; + + @XmlElement + @Column(nullable = false) + private String componentModel; + + @XmlElement + @Column + private String componentSerial; + + @XmlElement + @Column + private String componentRevision; + + @XmlElement + @Column + private String componentClass; + + /** + * Get the Component's Manufacturer. + * @return the Component's Manufacturer + */ + public String getComponentManufacturer() { + return componentManufacturer; + } + + /** + * Get the Component's Model. + * @return the Component's Model + */ + public String getComponentModel() { + return componentModel; + } + + /** + * Get the Component's Serial Number. + * @return the Component's Serial Number + */ + public String getComponentSerial() { + return componentSerial; + } + + /** + * Get the Component's Revision. + * @return the Component's Revision + */ + public String getComponentRevision() { + return componentRevision; + } + + /** + * Get the Component's Class Registry. + * @return the Component's Class + */ + public String getComponentClass() { + return componentClass; + } + + /** + * Constructor. + * @param componentManufacturer Component Manufacturer (must not be null) + * @param componentModel Component Model (must not be null) + * @param componentSerial Component Serial Number (can be null) + * @param componentRevision Component Revision or Version (can be null) + */ + public ComponentInfo(final String componentManufacturer, + final String componentModel, + final String componentSerial, + final String componentRevision) { + Assert.state(isComplete( + componentManufacturer, + componentModel, + componentSerial, + componentRevision), + "ComponentInfo: manufacturer and/or " + + "model can not be null"); + this.componentManufacturer = componentManufacturer.trim(); + this.componentModel = componentModel.trim(); + if (componentSerial != null) { + this.componentSerial = componentSerial.trim(); + } else { + this.componentSerial = StringUtils.EMPTY; + } + if (componentRevision != null) { + this.componentRevision = componentRevision.trim(); + } else { + this.componentRevision = StringUtils.EMPTY; + } + } + + /** + * Constructor. + * @param componentManufacturer Component Manufacturer (must not be null) + * @param componentModel Component Model (must not be null) + * @param componentSerial Component Serial Number (can be null) + * @param componentRevision Component Revision or Version (can be null) + * @param componentClass Component Class (can be null) + */ + public ComponentInfo(final String componentManufacturer, + final String componentModel, + final String componentSerial, + final String componentRevision, + final String componentClass) { + Assert.state(isComplete( + componentManufacturer, + componentModel, + componentSerial, + componentRevision), + "ComponentInfo: manufacturer and/or " + + "model can not be null"); + this.componentManufacturer = componentManufacturer.trim(); + this.componentModel = componentModel.trim(); + if (componentSerial != null) { + this.componentSerial = componentSerial.trim(); + } else { + this.componentSerial = StringUtils.EMPTY; + } + if (componentRevision != null) { + this.componentRevision = componentRevision.trim(); + } else { + this.componentRevision = StringUtils.EMPTY; + } + + if (componentClass != null) { + this.componentClass = componentClass; + } else { + this.componentClass = StringUtils.EMPTY; + } + } + + /** + * Determines whether the given properties represent a + * ComponentInfo that will be useful in validation. + * Currently, only components which have a non-null + * manufacturer and model are considered valid. + * + * @param componentManufacturer a String containing a component's manufacturer + * @param componentModel a String representing a component's model + * @param componentSerial a String representing a component's serial number + * @param componentRevision a String representing a component's revision + * @return true if the component is valid, false if not + */ + public static boolean isComplete(final String componentManufacturer, + final String componentModel, + final String componentSerial, + final String componentRevision) { + return !(StringUtils.isEmpty(componentManufacturer) + || StringUtils.isEmpty(componentModel)); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ComponentInfo that = (ComponentInfo) o; + return Objects.equals(id, that.id) + && Objects.equals(componentManufacturer, that.componentManufacturer) + && Objects.equals(componentModel, that.componentModel) + && Objects.equals(componentSerial, that.componentSerial) + && Objects.equals(componentRevision, that.componentRevision) + && Objects.equals(componentClass, that.componentClass); + } + + @Override + public int hashCode() { + return Objects.hash(id, componentManufacturer, componentModel, + componentSerial, componentRevision, componentClass); + } + + @Override + public String toString() { + return String.format("ComponentInfo{" + + "componentManufacturer='%s'" + + ", componentModel='%s'" + + ", componentSerial='%s'" + + ", componentRevision='%s'" + + ", componentClass='%s'}", + componentManufacturer, + componentModel, componentSerial, + componentRevision, componentClass); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/BaseReferenceManifest.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/BaseReferenceManifest.java index 81b67edf..14f4714c 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/BaseReferenceManifest.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/BaseReferenceManifest.java @@ -2,7 +2,9 @@ package hirs.attestationca.persist.entity.userdefined.rim; import com.fasterxml.jackson.annotation.JsonIgnore; import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.service.ReferenceManifestService; import hirs.attestationca.persist.service.ReferenceManifestServiceImpl; +import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; import hirs.utils.SwidResource; import hirs.utils.xjc.BaseElement; import hirs.utils.xjc.Directory; @@ -93,6 +95,105 @@ public class BaseReferenceManifest extends ReferenceManifest { private String linkHref = null; private String linkRel = null; + /** + * This class enables the retrieval of BaseReferenceManifest by their attributes. + */ + public static class Selector extends ReferenceManifestSelector { + /** + * Construct a new ReferenceManifestSelector that will use + * the given (@link ReferenceManifestService} + * to retrieve one or may BaseReferenceManifest. + * + * @param referenceManifestManager the reference manifest manager to be used to retrieve + * reference manifests. + */ + public Selector(final ReferenceManifestService referenceManifestManager) { + super(referenceManifestManager, BaseReferenceManifest.class); + } + + /** + * Specify the platform manufacturer that rims must have to be considered + * as matching. + * @param manufacturer string for the manufacturer + * @return this instance + */ + public Selector byManufacturer(final String manufacturer) { + setFieldValue(PLATFORM_MANUFACTURER, manufacturer); + return this; + } + + /** + * Specify the platform model that rims must have to be considered + * as matching. + * @param model string for the model + * @return this instance + */ + public Selector byModel(final String model) { + setFieldValue(PLATFORM_MODEL, model); + return this; + } + + /** + * Specify the platform manufacturer/model that rims must have to be considered + * as matching. + * @param manufacturer string for the manufacturer + * @param model string for the model + * @return this instance + */ + public Selector byManufacturerModel(final String manufacturer, final String model) { + setFieldValue(PLATFORM_MANUFACTURER, manufacturer); + setFieldValue(PLATFORM_MODEL, model); + return this; + } + + /** + * Specify the platform manufacturer/model/base flag that rims must have to be considered + * as matching. + * @param manufacturer string for the manufacturer + * @param model string for the model + * @return this instance + */ + public Selector byManufacturerModelBase(final String manufacturer, final String model) { + setFieldValue(PLATFORM_MANUFACTURER, manufacturer); + setFieldValue(PLATFORM_MODEL, model); + setFieldValue("swidPatch", false); + setFieldValue("swidSupplemental", false); + //setFieldValue("", false); //corpus? + return this; + } + + /** + * Specify the device name that rims must have to be considered + * as matching. + * @param deviceName string for the deviceName + * @return this instance + */ + public Selector byDeviceName(final String deviceName) { + setFieldValue("deviceName", deviceName); + return this; + } + + /** + * Specify the RIM hash associated with the base RIM. + * @param base64Hash the hash of the file associated with the rim + * @return this instance + */ + public Selector byBase64Hash(final String base64Hash) { + setFieldValue(BASE_64_HASH_FIELD, base64Hash); + return this; + } + + /** + * Specify the RIM hash associated with the base RIM. + * @param hexDecHash the hash of the file associated with the rim + * @return this instance + */ + public Selector byHexDecHash(final String hexDecHash) { + setFieldValue(HEX_DEC_HASH_FIELD, hexDecHash); + return this; + } + } + /** * Support constructor for the RIM object. * @@ -242,6 +343,17 @@ public class BaseReferenceManifest extends ReferenceManifest { } } + /** + * Get a Selector for use in retrieving ReferenceManifest. + * + * @param rimMan the ReferenceManifestService to be used to retrieve + * persisted RIMs + * @return a Selector instance to use for retrieving RIMs + */ + public static Selector select(final ReferenceManifestService rimMan) { + return new Selector(rimMan); + } + /** * This method and code is pulled and adopted from the TCG Tool. Since this * is taking in an file stored in memory through http, this was changed from diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/EventLogMeasurements.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/EventLogMeasurements.java index f2ee5bb1..d62a89e6 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/EventLogMeasurements.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/EventLogMeasurements.java @@ -3,20 +3,33 @@ package hirs.attestationca.persist.entity.userdefined.rim; import com.fasterxml.jackson.annotation.JsonIgnore; import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.service.ReferenceManifestService; +import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; +import hirs.utils.tpm.eventlog.TCGEventLog; +import hirs.utils.tpm.eventlog.TpmPcrEvent; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import lombok.extern.log4j.Log4j2; import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; /** * Sub class that will just focus on PCR Values and Events. * Similar to {@link SupportReferenceManifest} * however this is the live log from the client. */ +@Log4j2 +@EqualsAndHashCode(callSuper=false) @Entity public class EventLogMeasurements extends ReferenceManifest { @@ -28,6 +41,66 @@ public class EventLogMeasurements extends ReferenceManifest { @Getter @Setter private AppraisalStatus.Status overallValidationResult = AppraisalStatus.Status.FAIL; + /** + * This class enables the retrieval of SupportReferenceManifest by their attributes. + */ + public static class Selector extends ReferenceManifestSelector { + /** + * Construct a new ReferenceManifestSelector that + * will use the given (@link ReferenceManifestService} + * to retrieve one or may SupportReferenceManifest. + * + * @param referenceManifestManager the reference manifest manager to be used to retrieve + * reference manifests. + */ + public Selector(final ReferenceManifestService referenceManifestManager) { + super(referenceManifestManager, EventLogMeasurements.class, false); + } + + /** + * Specify the platform manufacturer that rims must have to be considered + * as matching. + * @param manufacturer string for the manufacturer + * @return this instance + */ + public Selector byManufacturer(final String manufacturer) { + setFieldValue(PLATFORM_MANUFACTURER, manufacturer); + return this; + } + + /** + * Specify the platform model that rims must have to be considered + * as matching. + * @param model string for the model + * @return this instance + */ + public Selector byModel(final String model) { + setFieldValue(PLATFORM_MODEL, model); + return this; + } + + /** + * Specify the device name that rims must have to be considered + * as matching. + * @param deviceName string for the deviceName + * @return this instance + */ + public Selector byDeviceName(final String deviceName) { + setFieldValue("deviceName", deviceName); + return this; + } + + /** + * Specify the RIM hash associated with the Event Log. + * @param hexDecHash the hash of the file associated with the rim + * @return this instance + */ + public Selector byHexDecHash(final String hexDecHash) { + setFieldValue(HEX_DEC_HASH_FIELD, hexDecHash); + return this; + } + } + /** * Support constructor for the RIM object. * @@ -61,4 +134,57 @@ public class EventLogMeasurements extends ReferenceManifest { super(); this.pcrHash = 0; } + + /** + * Get a Selector for use in retrieving ReferenceManifest. + * + * @param rimMan the ReferenceManifestService to be used to retrieve + * persisted RIMs + * @return a Selector instance to use for retrieving RIMs + */ + public static Selector select(final ReferenceManifestService rimMan) { + return new Selector(rimMan); + } + + /** + * Getter method for the expected PCR values contained within the support + * RIM. + * @return a string array of the pcr values. + */ + public String[] getExpectedPCRList() { + try { + TCGEventLog logProcessor = new TCGEventLog(this.getRimBytes()); + this.pcrHash = Arrays.hashCode(logProcessor.getExpectedPCRValues()); + return logProcessor.getExpectedPCRValues(); + } catch (CertificateException cEx) { + log.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } catch (IOException ioEx) { + log.error(ioEx); + } + + return new String[0]; + } + + /** + * Getter method for the event log that should be present in the support RIM. + * + * @return list of TPM PCR Events for display + */ + public Collection getEventLog() { + TCGEventLog logProcessor = null; + try { + logProcessor = new TCGEventLog(this.getRimBytes()); + return logProcessor.getEventList(); + } catch (CertificateException cEx) { + log.error(cEx); + } catch (NoSuchAlgorithmException noSaEx) { + log.error(noSaEx); + } catch (IOException ioEx) { + log.error(ioEx); + } + + return new ArrayList<>(); + } } \ No newline at end of file diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/ReferenceDigestValue.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/ReferenceDigestValue.java new file mode 100644 index 00000000..3d1129d7 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/ReferenceDigestValue.java @@ -0,0 +1,135 @@ +package hirs.attestationca.persist.entity.userdefined.rim; + +import hirs.attestationca.persist.entity.ArchivableEntity; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.bouncycastle.util.Arrays; +import org.hibernate.annotations.JdbcTypeCode; + +import java.util.UUID; + +/** + * This class represents that actual entry in the Support RIM. + * Digest Value, Event Type, index, RIM Tagid + */ +@Data +@Builder +@AllArgsConstructor +@Entity +@EqualsAndHashCode(callSuper=false) +@Table(name = "ReferenceDigestValue") +@Access(AccessType.FIELD) +public class ReferenceDigestValue extends ArchivableEntity { + + @JdbcTypeCode(java.sql.Types.VARCHAR) + @Column + private UUID baseRimId; + @JdbcTypeCode(java.sql.Types.VARCHAR) + @Column + private UUID supportRimId; + @Column(nullable = false) + private String manufacturer; + @Column(nullable = false) + private String model; + @Column(nullable = false) + private int pcrIndex; + @Column(nullable = false) + private String digestValue; + @Column(nullable = false) + private String eventType; + @Column(columnDefinition = "blob", nullable = true) + private byte[] contentBlob; + @Column(nullable = false) + private boolean matchFail; + @Column(nullable = false) + private boolean patched; + @Column(nullable = false) + private boolean updated; + + /** + * Default constructor necessary for Hibernate. + */ + protected ReferenceDigestValue() { + super(); + this.baseRimId = null; + this.supportRimId = null; + this.manufacturer = ""; + this.model = ""; + this.pcrIndex = -1; + this.digestValue = ""; + this.eventType = ""; + this.matchFail = false; + this.patched = false; + this.updated = false; + this.contentBlob = null; + } + + /** + * Default Constructor with parameters for all associated data. + * @param baseRimId the UUID of the associated record + * @param supportRimId the UUID of the associated record + * @param manufacturer associated creator for this information + * @param model the specific device type + * @param pcrIndex the event number + * @param digestValue the key digest value + * @param eventType the event type to store + * @param matchFail the status of the baseline check + * @param patched the status of the value being updated to patch + * @param updated the status of the value being updated with info + * @param contentBlob the data value of the content + */ + public ReferenceDigestValue(final UUID baseRimId, final UUID supportRimId, + final String manufacturer, final String model, + final int pcrIndex, final String digestValue, + final String eventType, final boolean matchFail, + final boolean patched, final boolean updated, + final byte[] contentBlob) { + this.baseRimId = baseRimId; + this.supportRimId = supportRimId; + this.manufacturer = manufacturer; + this.model = model; + this.pcrIndex = pcrIndex; + this.digestValue = digestValue; + this.eventType = eventType; + this.matchFail = matchFail; + this.patched = patched; + this.updated = updated; + this.contentBlob = Arrays.clone(contentBlob); + } + + /** + * Helper method to update the attributes of this object. + * @param support the associated RIM. + * @param baseRimId the main id to update + */ + public void updateInfo(final SupportReferenceManifest support, final UUID baseRimId) { + if (support != null) { + setBaseRimId(baseRimId); + setManufacturer(support.getPlatformManufacturer()); + setModel(support.getPlatformModel()); + setUpdated(true); + if (support.isSwidPatch()) { + // come back to this later, how does this get + // identified to be patched + setPatched(true); + } + } + } + + /** + * Returns a string of the classes fields. + * @return a string + */ + public String toString() { + return String.format("ReferenceDigestValue: {%s, %d, %s, %s, " + + "matchFail - %b, updated - %b, patched - %b}", + model, pcrIndex, digestValue, eventType, matchFail, updated, patched); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/SupportReferenceManifest.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/SupportReferenceManifest.java index eed38dd8..a5ada73f 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/SupportReferenceManifest.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/entity/userdefined/rim/SupportReferenceManifest.java @@ -2,6 +2,8 @@ package hirs.attestationca.persist.entity.userdefined.rim; import com.fasterxml.jackson.annotation.JsonIgnore; import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.service.ReferenceManifestService; +import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; import hirs.utils.tpm.eventlog.TCGEventLog; import hirs.utils.tpm.eventlog.TpmPcrEvent; import jakarta.persistence.Column; @@ -34,6 +36,78 @@ public class SupportReferenceManifest extends ReferenceManifest { @Column private boolean processed = false; + /** + * This class enables the retrieval of SupportReferenceManifest by their attributes. + */ + public static class Selector extends ReferenceManifestSelector { + /** + * Construct a new ReferenceManifestSelector that will + * use the given (@link ReferenceManifestService} + * to retrieve one or may SupportReferenceManifest. + * + * @param referenceManifestManager the reference manifest manager to be used to retrieve + * reference manifests. + */ + public Selector(final ReferenceManifestService referenceManifestManager) { + super(referenceManifestManager, SupportReferenceManifest.class); + } + + /** + * Specify the platform manufacturer that rims must have to be considered + * as matching. + * @param manufacturer string for the manufacturer + * @return this instance + */ + public Selector byManufacturer(final String manufacturer) { + setFieldValue(PLATFORM_MANUFACTURER, manufacturer); + return this; + } + + /** + * Specify the platform model that rims must have to be considered + * as matching. + * @param manufacturer string for the manufacturer + * @param model string for the model + * @return this instance + */ + public Selector byManufacturerModel(final String manufacturer, final String model) { + setFieldValue(PLATFORM_MANUFACTURER, manufacturer); + setFieldValue(PLATFORM_MODEL, model); + return this; + } + + /** + * Specify the device name that rims must have to be considered + * as matching. + * @param deviceName string for the deviceName + * @return this instance + */ + public Selector byDeviceName(final String deviceName) { + setFieldValue("deviceName", deviceName); + return this; + } + + /** + * Specify the file name that rims should have. + * @param fileName the name of the file associated with the rim + * @return this instance + */ + public Selector byFileName(final String fileName) { + setFieldValue(RIM_FILENAME_FIELD, fileName); + return this; + } + + /** + * Specify the RIM hash associated with the support RIM. + * @param hexDecHash the hash of the file associated with the rim + * @return this instance + */ + public Selector byHexDecHash(final String hexDecHash) { + setFieldValue(HEX_DEC_HASH_FIELD, hexDecHash); + return this; + } + } + /** * Main constructor for the RIM object. This takes in a byte array of a * valid swidtag file and parses the information. @@ -69,6 +143,16 @@ public class SupportReferenceManifest extends ReferenceManifest { this.pcrHash = 0; } + /** + * Get a Selector for use in retrieving ReferenceManifest. + * + * @param rimMan the ReferenceManifestService to be used to retrieve + * persisted RIMs + * @return a Selector instance to use for retrieving RIMs + */ + public static Selector select(final ReferenceManifestService rimMan) { + return new Selector(rimMan); + } /** * Getter method for the expected PCR values contained within the support @@ -111,4 +195,13 @@ public class SupportReferenceManifest extends ReferenceManifest { return new ArrayList<>(); } + + /** + * This is a method to indicate whether or not this support + * rim is a base log file. + * @return flag for base. + */ + public boolean isBaseSupport() { + return !this.isSwidSupplemental() && !this.isSwidPatch(); + } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/enums/AppraisalStatus.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/enums/AppraisalStatus.java index 0319d31a..37431f5f 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/enums/AppraisalStatus.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/enums/AppraisalStatus.java @@ -1,8 +1,13 @@ package hirs.attestationca.persist.enums; +import lombok.Getter; +import lombok.Setter; + /** * Class to capture appraisal results and corresponding messages. */ +@Getter +@Setter public class AppraisalStatus { /** * Enum used to represent appraisal status. @@ -56,51 +61,4 @@ public class AppraisalStatus { this.additionalInfo = additionalInfo; } - /** - * Get appraisal status. - * @return appraisal status - */ - public Status getAppStatus() { - return appStatus; - } - - /** - * Set appraisal status. - * @param appStatus new status - */ - public void setAppStatus(final Status appStatus) { - this.appStatus = appStatus; - } - - /** - * Get appraisal description message. - * @return appraisal description message - */ - public String getMessage() { - return message; - } - - /** - * Set appraisal description message. - * @param message appraisal description message - */ - public void setMessage(final String message) { - this.message = message; - } - - /** - * Getter for additional information during validation. - * @return string of additional information - */ - public String getAdditionalInfo() { - return additionalInfo; - } - - /** - * Setter for any additional information. - * @param additionalInfo the string of additional information - */ - public void setAdditionalInfo(final String additionalInfo) { - this.additionalInfo = additionalInfo; - } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java new file mode 100644 index 00000000..7cd7f4b6 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java @@ -0,0 +1,23 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.service.selector.CertificateSelector; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public interface CertificateService { + + Certificate saveCertificate(Certificate certificate); + + List fetchCertificates(Class classType); + + Certificate updateCertificate(Certificate certificate, UUID certificateId); + + Certificate updateCertificate(Certificate certificate); + + void deleteCertificate(Certificate certificate); + + Set get(CertificateSelector certificateSelector); +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateServiceImpl.java index 31f0ac5e..7320ab3e 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateServiceImpl.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateServiceImpl.java @@ -2,19 +2,84 @@ package hirs.attestationca.persist.service; import hirs.attestationca.persist.entity.manager.CertificateRepository; import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.service.selector.CertificateSelector; import jakarta.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Set; +import java.util.UUID; + @Service -public class CertificateServiceImpl { +public class CertificateServiceImpl extends DefaultDbService implements CertificateService { + @Autowired(required = false) private EntityManager entityManager; @Autowired private CertificateRepository repository; - private void saveCertificate(Certificate certificate) { - repository.save(certificate); + @Override + public Certificate saveCertificate(Certificate certificate) { + return repository.save(certificate); + } + + @Override + @SuppressWarnings("unchecked") + public List fetchCertificates(Class classType) { + return (List) repository.findAll(Sort.sort(classType)); + } + + @Override + public Certificate updateCertificate(Certificate certificate, UUID certificateId) { + return saveCertificate(certificate); + } + + @Override + public Certificate updateCertificate(Certificate certificate) { + return saveCertificate(certificate); + } + + /** + * This method does not need to be used directly as it is used by {@link CertificateSelector}'s + * get* methods. Regardless, it may be used to retrieve certificates by other code in this + * package, given a configured CertificateSelector. + * + * Example: + * + *
+     * {@code
+     * CertificateSelector certSelector =
+     *      new CertificateSelector(Certificate.Type.CERTIFICATE_AUTHORITY)
+     *      .byIssuer("CN=Some certain issuer");
+     *
+     * Set certificates = certificateManager.get(certSelector);}
+     * 
+ * + * @param the type of certificate that will be retrieved + * @param certificateSelector a configured {@link CertificateSelector} to use for querying + * @return the resulting set of Certificates, possibly empty + */ + @SuppressWarnings("unchecked") + public Set get(final CertificateSelector certificateSelector) { +// return new HashSet<>( +// (List) getWithCriteria( +// certificateSelector.getCertificateClass(), +// Collections.singleton(certificateSelector.getCriterion()) +// ) +// ); + return null; + } + + /** + * Remove a certificate from the database. + * + * @param certificate the certificate to delete + * @return true if deletion was successful, false otherwise + */ + public void deleteCertificate(final Certificate certificate) { + repository.delete(certificate); } } diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DbServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DbServiceImpl.java deleted file mode 100644 index 871c7403..00000000 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DbServiceImpl.java +++ /dev/null @@ -1,15 +0,0 @@ -package hirs.attestationca.persist.service; - -public class DbServiceImpl { - /** - * The default maximum number of retries to attempt a database transaction. - */ - public static final int DEFAULT_MAX_RETRY_ATTEMPTS = 10; - /* - * The default number of milliseconds to wait before retrying a database transaction. - */ - private static final long DEFAULT_RETRY_WAIT_TIME_MS = 3000; - - // structure for retrying methods in the database -// private RetryTemplate retryTemplate; -} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DefaultDbService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DefaultDbService.java new file mode 100644 index 00000000..577928fc --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/DefaultDbService.java @@ -0,0 +1,199 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.DBManagerException; +import hirs.attestationca.persist.entity.ArchivableEntity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.hibernate.StaleObjectStateException; +import org.hibernate.exception.LockAcquisitionException; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryListener; +import org.springframework.retry.backoff.FixedBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.stereotype.Service; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +@Log4j2 +@Service +@NoArgsConstructor +public class DefaultDbService extends HibernateDbService { + /** + * The default maximum number of retries to attempt a database transaction. + */ + public static final int DEFAULT_MAX_RETRY_ATTEMPTS = 10; + /* + * The default number of milliseconds to wait before retrying a database transaction. + */ + private static final long DEFAULT_RETRY_WAIT_TIME_MS = 3000; + private static final int MAX_CLASS_CACHE_ENTRIES = 500; + + private Class clazz; + @PersistenceContext + private EntityManager entityManager; + private JpaRepository repository; + // structure for retrying methods in the database + private RetryTemplate retryTemplate; + + /** + * Creates a new DefaultDbService. + * + * @param clazz Class to search for when doing Hibernate queries, + * unfortunately class type of T cannot be determined using only T + */ + public DefaultDbService(final Class clazz) { + super(clazz, null); + setRetryTemplate(); + } + + /** + * Set the parameters used to retry database transactions. The retry template will + * retry transactions that throw a LockAcquisitionException or StaleObjectStateException. + */ + public final void setRetryTemplate() { + Map, Boolean> exceptionsToRetry = new HashMap<>(); + exceptionsToRetry.put(LockAcquisitionException.class, true); + exceptionsToRetry.put(StaleObjectStateException.class, true); + + SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy( + DEFAULT_MAX_RETRY_ATTEMPTS, + exceptionsToRetry, + true, + false + ); + + FixedBackOffPolicy backoffPolicy = new FixedBackOffPolicy(); + backoffPolicy.setBackOffPeriod(DEFAULT_RETRY_WAIT_TIME_MS); + this.retryTemplate = new RetryTemplate(); + this.retryTemplate.setRetryPolicy(retryPolicy); + this.retryTemplate.setBackOffPolicy(backoffPolicy); + } + + /** + * Registers a retry listener to be notified of retry activity. + * @param retryListener the retry listener + */ + public void addRetryListener(final RetryListener retryListener) { + retryTemplate.registerListener(retryListener); + } + + /** + * Retrieves the Object from the database. This searches the + * database for an entry whose name matches name. It then + * reconstructs the Object from the database entry. + * + * @param name name of the object + * @return object if found, otherwise null. + * @throws DBManagerException if unable to search the database or recreate + * the Object + */ + public final T get(final String name) throws DBManagerException { + return retryTemplate.execute(new RetryCallback() { + @Override + public T doWithRetry(final RetryContext context) throws DBManagerException { + return doGet(name); + } + }); + } + + /** + * Retrieves the Object from the database. This searches the + * database for an entry whose id matches id. It then + * reconstructs the Object from the database entry. + * + * @param id id of the object + * @return object if found, otherwise null. + * @throws DBManagerException if unable to search the database or recreate + * the Object + */ + public final T get(final Serializable id) throws DBManagerException { + return retryTemplate.execute(new RetryCallback() { + @Override + public T doWithRetry(final RetryContext context) throws DBManagerException { + return doGet(id); + } + }); + } + + /** + * Retrieves the Object from the database. This searches the + * database for an entry whose name matches name. It then + * reconstructs the Object from the database entry. + * + * @param name name of the object + * @return object if found, otherwise null. + * @throws DBManagerException if unable to search the database or recreate + * the Object + */ + protected T doGet(final String name) throws DBManagerException { + log.debug("getting object: {}", name); + if (name == null) { + log.debug("null name argument"); + return null; + } + + Object entity = entityManager.find(clazz, name); + entityManager.detach(entity); + + return clazz.cast(entity); + } + + /** + * Retrieves the Object from the database. This searches the + * database for an entry whose id matches id. It then + * reconstructs the Object from the database entry. + * + * @param id id of the object + * @return object if found, otherwise null. + * @throws DBManagerException if unable to search the database or recreate + * the Object + */ + protected T doGet(final Serializable id) throws DBManagerException { + log.debug("getting object: {}", id); + if (id == null) { + log.debug("null id argument"); + return null; + } + + Object entity = entityManager.find(clazz, id); + entityManager.detach(entity); + + return clazz.cast(entity); + } + + /** + * Archives the named object and updates it in the database. + * + * @param name name of the object to archive + * @return true if the object was successfully found and archived, false if the object was not + * found + * @throws DBManagerException if the object is not an instance of ArchivableEntity + */ +// @Override +// public final boolean archive(final String name) throws DBManagerException { +// log.debug("archiving object: {}", name); +// if (name == null) { +// log.debug("null name argument"); +// return false; +// } +// +// T target = get(name); +// if (target == null) { +// return false; +// } +// if (!(target instanceof ArchivableEntity)) { +// throw new DBManagerException("unable to archive non-archivable object"); +// } +// +// ((ArchivableEntity) target).archive(); +// repository.save(target); +// return true; +// } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/HibernateDbService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/HibernateDbService.java new file mode 100644 index 00000000..e949f46d --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/HibernateDbService.java @@ -0,0 +1,132 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.DBManagerException; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import lombok.extern.log4j.Log4j2; + +import java.util.ArrayList; +import java.util.List; + +/** + * Abstract class that has the underlying Hibernate commands used by other DB Managers. + * This class exists primarily to reduce code in {@link hirs.attestationca.persist.service.DefaultDbService} which retries these methods + * using a RetryTemplate. + * + * @param type of objects to manage by this manager + */ +@Log4j2 +public abstract class HibernateDbService { + + private static final int MAX_CLASS_CACHE_ENTRIES = 500; + + private final Class clazz; + @PersistenceContext + private EntityManager entityManager; + private CriteriaBuilder criteriaBuilder; + private CriteriaQuery criteriaQuery; + + /** + * Creates a new AbstractDbManager. + * + * @param clazz Class to search for when doing Hibernate queries, + * unfortunately class type of T cannot be determined using only T + * @param entityManager the session factory to use to interact with the database + */ + public HibernateDbService(final Class clazz, final EntityManager entityManager) { + if (clazz == null) { + log.error("HibernateDbService cannot be instantiated with a null class"); + throw new IllegalArgumentException( + "HibernateDbService cannot be instantiated with a null class" + ); + } +// if (entityManager == null) { +// log.error("HibernateDbService cannot be instantiated with a null SessionFactory"); +// throw new IllegalArgumentException( +// "HibernateDbService cannot be instantiated with a null SessionFactory" +// ); +// } + this.clazz = clazz; + this.entityManager = entityManager; + } + + public HibernateDbService() { + clazz = null; + } + + /** + * Returns a list of all Ts of type clazz in the database, with an + * additional restriction also specified in the query. + *

+ * This would be useful if T has several subclasses being + * managed. This class argument allows the caller to limit which types of + * T should be returned. + * + * @param clazz class type of Ts to search for (may be null to + * use Class<T>) + * @param additionalRestriction - an added Criterion to use in the query, null for none + * @return list of T names + * @throws DBManagerException if unable to search the database + */ + protected List doGetList(final Class clazz) + throws DBManagerException { + log.debug("Getting object list"); + Class searchClass = clazz; + if (clazz == null) { + log.debug("clazz is null"); + searchClass = this.clazz; + } + + List objects = new ArrayList<>(); + + return objects; + } + + /** + * Deletes the object from the database. This removes all of the database + * entries that stored information with regards to the this object. + *

+ * If the object is referenced by any other tables then this will throw a + * DBManagerException. + * + * @param name name of the object to delete + * @return true if successfully found and deleted the object + * @throws DBManagerException if unable to find the baseline or delete it + * from the database + */ +// protected boolean doDelete(final String name) throws DBManagerException { +// log.debug("deleting object: {}", name); +// if (name == null) { +// log.debug("null name argument"); +// return false; +// } +// +// boolean deleted = false; +// Session session = entityManager.unwrap(Session.class); +// try { +// log.debug("retrieving object from db"); +// criteriaBuilder = session.getCriteriaBuilder(); +// criteriaQuery = criteriaBuilder.createQuery(clazz); +// Root root = criteriaQuery.from(clazz); +// criteriaQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name)); +// +// Object object = session.createQuery(criteriaQuery).getSingleResult(); +// +// if (clazz.isInstance(object)) { +// T objectOfTypeT = clazz.cast(object); +// log.debug("found object, deleting it"); +// session.delete(objectOfTypeT); +// deleted = true; +// } +// } catch (Exception e) { +// final String msg = "unable to retrieve object"; +// log.error(msg, e); +// throw new DBManagerException(msg, e); +// } +// return deleted; +// } + + +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueService.java new file mode 100644 index 00000000..cef27536 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueService.java @@ -0,0 +1,20 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; + +import java.util.List; +import java.util.UUID; + +public interface ReferenceDigestValueService { + + ReferenceDigestValue saveReferenceDigestValue(ReferenceDigestValue referenceDigestValue); + + List fetchDigestValues(); + + ReferenceDigestValue updateRefDigestValue(ReferenceDigestValue referenceDigestValue, UUID rdvId); + + List getValuesByRimId(ReferenceManifest baseRim); + + void deleteRefDigestValueById(UUID rdvId); +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueServiceImpl.java new file mode 100644 index 00000000..52fa1502 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceDigestValueServiceImpl.java @@ -0,0 +1,64 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.entity.manager.ReferenceDigestValueRepository; +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.datatables.mapping.DataTablesInput; +import org.springframework.data.jpa.datatables.mapping.DataTablesOutput; +import org.springframework.stereotype.Service; + +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +@Service +public class ReferenceDigestValueServiceImpl extends DefaultDbService implements ReferenceDigestValueService { + + @Autowired + private ReferenceDigestValueRepository repository; + + @Override + public ReferenceDigestValue saveReferenceDigestValue(ReferenceDigestValue referenceDigestValue) { + return repository.save(referenceDigestValue); + } + + public List findAll() { + return repository.findAll(); + } + + @Override + public List fetchDigestValues() { + return repository.findAll(); + } + + @Override + public ReferenceDigestValue updateRefDigestValue(ReferenceDigestValue referenceDigestValue, UUID rdvId) { + return saveReferenceDigestValue(referenceDigestValue); + } + + public ReferenceDigestValue updateRefDigestValue(ReferenceDigestValue referenceDigestValue) { + if (referenceDigestValue.getId() != null) { + return updateRefDigestValue(referenceDigestValue, referenceDigestValue.getId()); + } + return null; + } + + public List getValuesByRimId(ReferenceManifest baseRim) { + List results = new LinkedList<>(); + if (baseRim != null) { + for (ReferenceDigestValue rdv : repository.findAll()) { + if (rdv.getBaseRimId() == baseRim.getId()) { + results.add(rdv); + } + } + } + + return results; + } + + @Override + public void deleteRefDigestValueById(UUID rdvId) { + repository.getReferenceById(rdvId).archive(); + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestService.java new file mode 100644 index 00000000..49b2e7e6 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestService.java @@ -0,0 +1,23 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.OrderedListQuerier; +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public interface ReferenceManifestService extends OrderedListQuerier { + + ReferenceManifest saveReferenceManifest(ReferenceManifest referenceManifest); + + List fetchReferenceManifests(); +// DataTablesOutput fetchReferenceManifests(DataTablesInput input); + + ReferenceManifest updateReferenceManifest(ReferenceManifest referenceManifest, UUID rimId); + + void deleteReferenceManifestById(UUID rimId); + + Set get(ReferenceManifestSelector referenceManifestSelector); +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestServiceImpl.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestServiceImpl.java index 4ee3ad3a..6baf680e 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestServiceImpl.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/ReferenceManifestServiceImpl.java @@ -1,10 +1,15 @@ package hirs.attestationca.persist.service; +import hirs.attestationca.persist.CriteriaModifier; +import hirs.attestationca.persist.DBManagerException; +import hirs.attestationca.persist.FilteredRecordsList; import hirs.attestationca.persist.entity.manager.ReferenceManifestRepository; import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.service.selector.ReferenceManifestSelector; import jakarta.persistence.EntityManager; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.xml.sax.SAXException; @@ -13,10 +18,14 @@ import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.io.IOException; import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; @Log4j2 @Service -public class ReferenceManifestServiceImpl { +public class ReferenceManifestServiceImpl extends DefaultDbService implements ReferenceManifestService { /** * The variable that establishes a schema factory for xml processing. @@ -67,4 +76,69 @@ public class ReferenceManifestServiceImpl { } return schema; } + + @Override + public ReferenceManifest saveReferenceManifest(ReferenceManifest referenceManifest) { + return repository.save(referenceManifest); + } + + @Override + public List fetchReferenceManifests() { + return repository.findAll(); + } + + /** + * This method does not need to be used directly as it is used by + * {@link ReferenceManifestSelector}'s get* methods. Regardless, it may be + * used to retrieve ReferenceManifest by other code in this package, given a + * configured ReferenceManifestSelector. + * + * @param referenceManifestSelector a configured + * {@link ReferenceManifestSelector} to use for querying + * @return the resulting set of ReferenceManifest, possibly empty + */ + @SuppressWarnings("unchecked") + public List get( + Class classType) { + log.info("Getting the full set of Reference Manifest files."); +// return new HashSet<>( +// (List) getWithCriteria( +// referenceManifestSelector.getReferenceManifestClass(), +// Collections.singleton(referenceManifestSelector.getCriterion()) +// ) +// ); + return (List) repository.findAll(Sort.sort(classType)); + } + + @Override + public ReferenceManifest updateReferenceManifest(ReferenceManifest referenceManifest, UUID rimId) { + return null; + } + + @Override + public void deleteReferenceManifestById(UUID rimId) { + repository.deleteById(rimId); + } + + @Override + public Set get(ReferenceManifestSelector referenceManifestSelector) { + return null; + } + + @Override + public FilteredRecordsList getOrderedList(Class clazz, + String columnToOrder, boolean ascending, int firstResult, + int maxResults, String search, + Map searchableColumns) throws DBManagerException { + return null; + } + + @Override + public FilteredRecordsList getOrderedList(Class clazz, + String columnToOrder, boolean ascending, + int firstResult, int maxResults, String search, + Map searchableColumns, + CriteriaModifier criteriaModifier) throws DBManagerException { + return null; + } } 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 new file mode 100644 index 00000000..b8ce4c56 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationService.java @@ -0,0 +1,16 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; + +import java.util.List; +import java.util.UUID; + +public interface SupplyChainValidationService { + SupplyChainValidation saveSupplyChainValidation(SupplyChainValidation supplyChainValidation); + + List fetchSupplyChainValidations(); + + SupplyChainValidation updateSupplyChainValidation(SupplyChainValidation supplyChainValidation, UUID scvId); + + void deleteSupplyChainValidation(UUID scvId); +} 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 new file mode 100644 index 00000000..0bb287e0 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/SupplyChainValidationServiceImpl.java @@ -0,0 +1,190 @@ +package hirs.attestationca.persist.service; + +import hirs.attestationca.persist.entity.manager.SupplyChainValidationRepository; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.SupplyChainValidation; +import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +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.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@Log4j2 +@Service +public class SupplyChainValidationServiceImpl extends DefaultDbService implements SupplyChainValidationService { + + @Autowired + SupplyChainValidationRepository repository; + @Autowired + private CertificateService certificateService; + + public SupplyChainValidationServiceImpl(final CertificateService certificateService) { + super(); + this.certificateService = certificateService; + } + + @Override + public SupplyChainValidation saveSupplyChainValidation(SupplyChainValidation supplyChainValidation) { + return repository.save(supplyChainValidation); + } + + @Override + public List fetchSupplyChainValidations() { + return repository.findAll(); + } + + @Override + public SupplyChainValidation updateSupplyChainValidation(SupplyChainValidation supplyChainValidation, UUID scvId) { + return null; + } + + @Override + public void deleteSupplyChainValidation(UUID scvId) { + repository.deleteById(scvId); + } + + /** + * 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; + Set certAuthsWithMatchingIssuer = new HashSet<>(); + if (credential.getAuthorityKeyIdentifier() != null + && !credential.getAuthorityKeyIdentifier().isEmpty()) { + byte[] bytes = Hex.decode(credential.getAuthorityKeyIdentifier()); + skiCA = CertificateAuthorityCredential + .select(certificateService) + .bySubjectKeyIdentifier(bytes).getCertificate(); + } + + if (skiCA == null) { + if (credential.getIssuerSorted() == null + || credential.getIssuerSorted().isEmpty()) { + certAuthsWithMatchingIssuer = CertificateAuthorityCredential + .select(certificateService) + .bySubject(credential.getHolderIssuer()) + .getCertificates(); + } else { + //Get certificates by subject organization + certAuthsWithMatchingIssuer = CertificateAuthorityCredential + .select(certificateService) + .bySubjectSorted(credential.getIssuerSorted()) + .getCertificates(); + } + } else { + certAuthsWithMatchingIssuer.add(skiCA); + } + Set queriedOrganizations = new HashSet<>(previouslyQueriedSubjects); + queriedOrganizations.add(credential.getHolderIssuer()); + + HashSet caCreds = new HashSet<>(); + for (CertificateAuthorityCredential cred : certAuthsWithMatchingIssuer) { + caCreds.add(cred); + if (!BouncyCastleUtils.x500NameCompare(cred.getHolderIssuer(), + 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 boolean checkForMultipleBaseCredentials(final String platformSerialNumber) { + boolean multiple = false; + PlatformCredential baseCredential = null; + + if (platformSerialNumber != null) { + List chainCertificates = PlatformCredential + .select(certificateService) + .byBoardSerialNumber(platformSerialNumber) + .getCertificates().stream().collect(Collectors.toList()); + + for (PlatformCredential pc : chainCertificates) { + if (baseCredential != null && pc.isPlatformBase()) { + multiple = true; + } else if (pc.isPlatformBase()) { + baseCredential = pc; + } + } + } + + return multiple; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/CertificateSelector.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/CertificateSelector.java new file mode 100644 index 00000000..2c5dac16 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/CertificateSelector.java @@ -0,0 +1,473 @@ +package hirs.attestationca.persist.service.selector; + +import com.google.common.base.Preconditions; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.service.CertificateService; +import hirs.attestationca.persist.service.CertificateServiceImpl; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.util.Arrays; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * This class is used to select one or many certificates in conjunction + * with a {@link CertificateServiceImpl}. To make use of this object, + * use (some CertificateImpl).select(CertificateManager). + * + * This class loosely follows the builder pattern. It is instantiated with + * the type of certificate that should be retrieved. It is possible to + * further specify which certificate(s) should be retrieved by using an + * instance's by* methods; each call to a by* method will further + * restrict the result set. At any time, the results may be retrieved + * by using one of the get* methods according to the form the + * results should be in. + * + * If no matching certificates were found for the query, the returned + * value may empty or null, depending on the return type. + * + * For example, to retrieve all platform certificates: + * + *

+ * {@code
+ * Set certificates =
+ *      certificateManager.select(Certificate.Type.PLATFORM)
+ *      .getCertificates();
+ * }
+ * 
+ * + * To retrieve all CA certificates in a KeyStore: + * + *
+ * {@code
+ * KeyStore trustStore =
+ *      certificateManager.select(Certificate.Type.CERTIFICATE_AUTHORITY)
+ *      .getKeyStore();
+ * }
+ * 
+ * + * To retrieve all CA certificates matching a certain issuer in X509 format: + * + *
+ * {@code
+ * Set certificates =
+ *      certificateManager.select(Certificate.Type.CERTIFICATE_AUTHORITY)
+ *      .byIssuer("CN=Some certain issuer")
+ *      .getX509Certificates();
+ * }
+ * 
+ * + * @param the type of certificate that will be retrieved + */ +public abstract class CertificateSelector { + + private final CertificateService certificateManager; + private final Class certificateClass; + + private final Map fieldValueSelections; + private boolean excludeArchivedCertificates; + + /** + * Construct a new CertificateSelector that will use the given {@link CertificateServiceImpl} to + * retrieve certificates of the given type. + * + * @param certificateManager the certificate manager to be used to retrieve certificates + * @param certificateClass the class of certificate to be retrieved + */ + public CertificateSelector( + final CertificateService certificateManager, + final Class certificateClass) { + this(certificateManager, certificateClass, true); + } + + /** + * Construct a new CertificateSelector that will use the given {@link CertificateService} to + * retrieve certificates of the given type. + * + * @param certificateManager the certificate manager to be used to retrieve certificates + * @param certificateClass the class of certificate to be retrieved + * @param excludeArchivedCertificates true if excluding archived certificates + */ + public CertificateSelector( + final CertificateService certificateManager, + final Class certificateClass, final boolean excludeArchivedCertificates) { + Preconditions.checkArgument( + certificateManager != null, + "certificate manager cannot be null" + ); + + Preconditions.checkArgument( + certificateClass != null, + "type cannot be null" + ); + + this.certificateManager = certificateManager; + this.certificateClass = certificateClass; + this.fieldValueSelections = new HashMap<>(); + this.excludeArchivedCertificates = excludeArchivedCertificates; + } + /** + * Specify the entity id that certificates must have to be considered + * as matching. + * + * @param uuid the UUID to query + * @return this instance (for chaining further calls) + */ + public CertificateSelector byEntityId(final UUID uuid) { + setFieldValue(Certificate.ID_FIELD, uuid); + return this; + } + + + /** + * Specify the hash code of the bytes that certificates must match. + * + * @param certificateHash the hash code of the bytes to query for + * @return this instance (for chaining further calls) + */ + public CertificateSelector byHashCode(final int certificateHash) { + setFieldValue(Certificate.CERTIFICATE_HASH_FIELD, certificateHash); + return this; + } + + /** + * Specify a serial number that certificates must have to be considered + * as matching. + * + * @param serialNumber the serial number to query + * @return this instance (for chaining further calls) + */ + public CertificateSelector bySerialNumber(final BigInteger serialNumber) { + setFieldValue(Certificate.SERIAL_NUMBER_FIELD, serialNumber); + return this; + } + + /** + * Specify a holder serial number that certificates must have to be considered + * as matching. + * + * @param holderSerialNumber the holder serial number to query + * @return this instance (for chaining further calls) + */ + public CertificateSelector byHolderSerialNumber(final BigInteger holderSerialNumber) { + setFieldValue(Certificate.HOLDER_SERIAL_NUMBER_FIELD, holderSerialNumber); + return this; + } + + /** + * Specify an issuer string that certificates must have to be considered + * as matching. + * + * @param issuer certificate issuer string to query, not empty or null + * @return this instance (for chaining further calls) + */ + public CertificateSelector byIssuer(final String issuer) { + Preconditions.checkArgument( + StringUtils.isNotEmpty(issuer), + String.format("%s: issuer cannot be null or empty.", + this.certificateClass.toString()) + ); + + setFieldValue(Certificate.ISSUER_FIELD, issuer); + return this; + } + + /** + * Specify a subject string that certificates must have to be considered + * as matching. + * + * @param subject certificate subject string to query, not empty or null + * @return this instance (for chaining further calls) + */ + public CertificateSelector bySubject(final String subject) { + Preconditions.checkArgument( + StringUtils.isNotEmpty(subject), + String.format("%s: subject cannot be null or empty.", + this.certificateClass.toString()) + ); + + setFieldValue(Certificate.SUBJECT_FIELD, subject); + return this; + } + + /** + * Specify the sorted issuer string that certificates must have to be considered + * as matching. + * + * @param issuerSorted certificate issuer organization string to query, not empty or null + * @return this instance (for chaining further calls) + */ + public CertificateSelector byIssuerSorted(final String issuerSorted) { + Preconditions.checkArgument( + StringUtils.isNotEmpty(issuerSorted), + String.format("%s: issuerSorted cannot be null or empty.", + this.certificateClass.toString()) + ); + + setFieldValue(Certificate.ISSUER_SORTED_FIELD, issuerSorted); + return this; + } + + /** + * Specify the sorted subject string that certificates must have to be considered + * as matching. + * + * @param subjectSorted certificate subject organization string to query, not empty or null + * @return this instance (for chaining further calls) + */ + public CertificateSelector bySubjectSorted(final String subjectSorted) { + Preconditions.checkArgument( + StringUtils.isNotEmpty(subjectSorted), + String.format("%s: subjectSorted cannot be null or empty.", + this.certificateClass.toString()) + ); + + setFieldValue(Certificate.SUBJECT_SORTED_FIELD, subjectSorted); + return this; + } + + /** + * Specify a public key that certificates must have to be considered + * as matching. + * + * @param encodedPublicKey the binary-encoded public key to query, not empty or null + * @return this instance (for chaining further calls) + */ + public CertificateSelector byEncodedPublicKey(final byte[] encodedPublicKey) { + Preconditions.checkArgument( + ArrayUtils.isNotEmpty(encodedPublicKey), + String.format("%s: publicKey cannot be null or empty.", + this.certificateClass.toString()) + ); + + setFieldValue( + Certificate.ENCODED_PUBLIC_KEY_FIELD, + Arrays.copyOf(encodedPublicKey, encodedPublicKey.length) + ); + + return this; + } + + /** + * Specify the authority key identifier to find certificate(s). + * @param authorityKeyIdentifier the string of the AKI associated with the certificate. + * @return this instance + */ + public CertificateSelector byAuthorityKeyIdentifier(final String authorityKeyIdentifier) { + Preconditions.checkArgument( + StringUtils.isNotEmpty(authorityKeyIdentifier), + String.format("%s: authorityKeyIdentifier cannot be null or empty.", + this.certificateClass.toString()) + ); + + setFieldValue(Certificate.AUTHORITY_KEY_ID_FIELD, authorityKeyIdentifier); + + return this; + } + + /** + * Specify a public key modulus that certificates must have to be considered + * as matching. + * + * @param publicKeyModulus a BigInteger representing a public key's modulus to query not null + * @return this instance (for chaining further calls) + */ + public CertificateSelector byPublicKeyModulus(final BigInteger publicKeyModulus) { + Preconditions.checkArgument( + publicKeyModulus != null, + String.format("%s: Public key modulus cannot be null", + this.certificateClass.toString()) + ); + + setFieldValue( + Certificate.PUBLIC_KEY_MODULUS_FIELD, + publicKeyModulus.toString(Certificate.HEX_BASE) + ); + + return this; + } + + /** + * Set a field name and value to match. + * + * @param name the field name to query + * @param value the value to query + */ + protected void setFieldValue(final String name, final Object value) { + Object valueToAssign = value; + + Preconditions.checkArgument( + value != null, + String.format("field value (%s) cannot be null.", name) + ); + + if (value instanceof String) { + Preconditions.checkArgument( + StringUtils.isNotEmpty((String) value), + "field value cannot be empty." + ); + } + + if (value instanceof byte[]) { + byte[] valueBytes = (byte[]) value; + + Preconditions.checkArgument( + ArrayUtils.isNotEmpty(valueBytes), + String.format("field value (%s) cannot be empty.", name) + ); + + valueToAssign = Arrays.copyOf(valueBytes, valueBytes.length); + } + + fieldValueSelections.put(name, valueToAssign); + } + + /** + * Retrieve the result set as a single {@link Certificate}. + * This method is best used when selecting on a unique attribute. + * If the result set contains more than one certificate, one is chosen + * arbitrarily and returned. If no matching certificates are found, + * this method returns null. + * + * @return a matching certificate or null if none is found + */ + public T getCertificate() { + Set certs = execute(); + if (certs.size() == 0) { + return null; + } + return certs.iterator().next(); + } + + /** + * Retrieve the result set as a set of {@link Certificate}s. + * This method is best used when selecting on non-unique attributes. + * Certificates are populated into the set in no specific order. + * If no matching certificates are found, the returned Set will be empty. + * + * @return a Set of matching Certificates, possibly empty + */ + public Set getCertificates() { + return Collections.unmodifiableSet(new HashSet<>(execute())); + } + + /** + * Retrieve the result set as a single {@link X509Certificate}. + * This method is best used when selecting on a unique attribute. + * If the result set contains more than one certificate, one is chosen + * arbitrarily and returned. If no matching certificates are found, + * this method returns null. + * + * @return a matching certificate or null if none is found + * @throws IOException if there is a problem reconstructing the X509Certificate + */ + public X509Certificate getX509Certificate() throws IOException { + Certificate cert = getCertificate(); + if (cert == null) { + return null; + } + return cert.getX509Certificate(); + } + + /** + * Retrieve the result set as a set of {@link X509Certificate}s. + * This method is best used when selecting on non-unique attributes. + * Certificates are populated into the set in no specific order. + * If no matching certificates are found, the returned Set will be empty. + * + * @return a Set of matching Certificates, possibly empty + * @throws IOException if there is a problem reconstructing the X509Certificates + */ + public Set getX509Certificates() throws IOException { + Set certs = new HashSet<>(); + for (Certificate cert : getCertificates()) { + certs.add(cert.getX509Certificate()); + } + return Collections.unmodifiableSet(certs); + } + + /** + * Retrieve the result set populated into a {@link KeyStore}. + * Certificates are populated into a JKS-formatted KeyStore, with their aliases + * set to their unique identifiers. + * If no matching certificates are found, the returned KeyStore will be empty. + * + * @return a KeyStore populated with the matching certificates, if any + * @throws KeyStoreException if there is a problem instantiating a JKS-formatted KeyStore + * @throws IOException if there is a problem populating the keystore + */ + public KeyStore getKeyStore() throws KeyStoreException, IOException { + KeyStore keyStore = KeyStore.getInstance("JKS"); + try { + keyStore.load(null, "".toCharArray()); + for (Certificate cert : getCertificates()) { + keyStore.setCertificateEntry(cert.getId().toString(), cert.getX509Certificate()); + } + } catch (IOException | CertificateException | NoSuchAlgorithmException e) { + throw new IOException("Could not create and populate keystore", e); + } + + return keyStore; + } + + /** + * Construct the criterion that can be used to query for certificates matching the configuration + * of this {@link CertificateSelector}. + * + * @return a Criterion that can be used to query for certificates matching the configuration of + * this instance + */ + Predicate[] getCriterion(final CriteriaBuilder criteriaBuilder) { + Predicate[] predicates = new Predicate[fieldValueSelections.size()]; + CriteriaQuery query = criteriaBuilder.createQuery(getCertificateClass()); + Root root = query.from(getCertificateClass()); + + int i = 0; + for (Map.Entry fieldValueEntry : fieldValueSelections.entrySet()) { + predicates[i++] = criteriaBuilder.equal(root.get(fieldValueEntry.getKey()), fieldValueEntry.getValue()); + } + + if (this.excludeArchivedCertificates) { + predicates[i] = criteriaBuilder.isNull(root.get(Certificate.ARCHIVE_FIELD)); + } + + return predicates; + } + + /** + * @return the certificate class that this instance will query + */ + public Class getCertificateClass() { + return certificateClass; + } + + // construct and execute query + private Set execute() { + return certificateManager.get(this); + } + + /** + * Configures the selector to query for archived and unarchived certificates. + * @return the selector + */ + public CertificateSelector includeArchived() { + excludeArchivedCertificates = false; + return this; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/ReferenceManifestSelector.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/ReferenceManifestSelector.java new file mode 100644 index 00000000..b110c923 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/selector/ReferenceManifestSelector.java @@ -0,0 +1,233 @@ +package hirs.attestationca.persist.service.selector; + +import com.google.common.base.Preconditions; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.service.ReferenceManifestService; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * This class is used to select one or many RIMs in conjunction + * with a {@link ReferenceManifestService}. To make use of this object, + * use (some ReferenceManifest).select(ReferenceManifestManager). + * + * @param the type of Reference Integrity Manifest that will be retrieved. + */ +public abstract class ReferenceManifestSelector { + /** + * String representing the database field for the manufacturer. + */ + public static final String PLATFORM_MANUFACTURER = "platformManufacturer"; + /** + * String representing the database field for the manufacturer id. + */ + public static final String PLATFORM_MANUFACTURER_ID = "platformManufacturerId"; + /** + * String representing the database field for the model. + */ + public static final String PLATFORM_MODEL = "platformModel"; + /** + * String representing the database field for the filename. + */ + public static final String RIM_FILENAME_FIELD = "fileName"; + private static final String RIM_TYPE_FIELD = "rimType"; + + private final ReferenceManifestService referenceManifestManager; + private final Class referenceTypeClass; + + private final Map fieldValueSelections; + private boolean excludeArchivedRims; + + /** + * Default Constructor. + * + * @param referenceManifestManager the RIM manager to be used to retrieve RIMs + * @param referenceTypeClass the type of Reference Manifest to process. + */ + public ReferenceManifestSelector(final ReferenceManifestService referenceManifestManager, + final Class referenceTypeClass) { + this(referenceManifestManager, referenceTypeClass, true); + } + + /** + * Standard Constructor for the Selector. + * + * @param referenceManifestManager the RIM manager to be used to retrieve RIMs + * @param referenceTypeClass the type of Reference Manifest to process. + * @param excludeArchivedRims true if excluding archived RIMs + */ + public ReferenceManifestSelector(final ReferenceManifestService referenceManifestManager, + final Class referenceTypeClass, + final boolean excludeArchivedRims) { + Preconditions.checkArgument( + referenceManifestManager != null, + "reference manifest manager cannot be null" + ); + + Preconditions.checkArgument( + referenceTypeClass != null, + "type cannot be null" + ); + + this.referenceManifestManager = referenceManifestManager; + this.referenceTypeClass = referenceTypeClass; + this.excludeArchivedRims = excludeArchivedRims; + this.fieldValueSelections = new HashMap<>(); + } + + /** + * Specify the entity id that rims must have to be considered as matching. + * + * @param uuid the UUID to query + * @return this instance (for chaining further calls) + */ + public ReferenceManifestSelector byEntityId(final UUID uuid) { + setFieldValue(Certificate.ID_FIELD, uuid); + return this; + } + + /** + * Specify the file name of the object to grab. + * @param fileName the name of the file associated with the rim + * @return instance of the manifest in relation to the filename. + */ + public ReferenceManifestSelector byFileName(final String fileName) { + setFieldValue(RIM_FILENAME_FIELD, fileName); + return this; + } + + /** + * Specify the RIM Type to match. + * @param rimType the type of rim + * @return this instance + */ + public ReferenceManifestSelector byRimType(final String rimType) { + setFieldValue(RIM_TYPE_FIELD, rimType); + return this; + } + + /** + * Set a field name and value to match. + * + * @param name the field name to query + * @param value the value to query + */ + protected void setFieldValue(final String name, final Object value) { + Object valueToAssign = value; + + Preconditions.checkArgument( + value != null, + String.format("field value (%s) cannot be null.", name) + ); + + if (value instanceof String) { + Preconditions.checkArgument( + StringUtils.isNotEmpty((String) value), + "field value cannot be empty." + ); + } + + if (value instanceof byte[]) { + byte[] valueBytes = (byte[]) value; + + Preconditions.checkArgument( + ArrayUtils.isNotEmpty(valueBytes), + String.format("field value (%s) cannot be empty.", name) + ); + + valueToAssign = Arrays.copyOf(valueBytes, valueBytes.length); + } + + fieldValueSelections.put(name, valueToAssign); + } + + /** + * Retrieve the result set as a single + * {@link ReferenceManifest}. This method is best used + * when selecting on a unique attribute. If the result set contains more + * than one RIM, one is chosen arbitrarily and returned. If no matching RIMs + * are found, this method returns null. + * + * @return a matching RIM or null if none is found + */ + public T getRIM() { + Set rims = execute(); + if (rims.isEmpty()) { + return null; + } + return rims.iterator().next(); + } + + /** + * Retrieve the result set as a set of + * {@link ReferenceManifest}s. This method is best used + * when selecting on non-unique attributes. ReferenceManifests are populated + * into the set in no specific order. If no matching certificates are found, + * the returned Set will be empty. + * + * @return a Set of matching RIMs, possibly empty + */ + public Set getRIMs() { + return Set.copyOf(execute()); + } + + /** + * Construct the criterion that can be used to query for rims matching the + * configuration of this {@link ReferenceManifestSelector}. + * + * @return a Criterion that can be used to query for rims matching the + * configuration of this instance + */ + Predicate[] getCriterion(final CriteriaBuilder criteriaBuilder) { + Predicate[] predicates = new Predicate[fieldValueSelections.size()]; + CriteriaQuery query = criteriaBuilder.createQuery(getReferenceManifestClass()); + Root root = query.from(getReferenceManifestClass()); + + int i = 0; + for (Map.Entry fieldValueEntry : fieldValueSelections.entrySet()) { + predicates[i++] = criteriaBuilder.equal(root.get(fieldValueEntry.getKey()), fieldValueEntry.getValue()); + } + + if (this.excludeArchivedRims) { + predicates[i] = criteriaBuilder.isNull(root.get(Certificate.ARCHIVE_FIELD)); + } + + return predicates; + } + + /** + * @return the rim class that this instance will query + */ + public Class getReferenceManifestClass() { + return this.referenceTypeClass; + } + + // construct and execute query + private Set execute() { + Set results = this.referenceManifestManager.get(this); + return results; + } + + /** + * Configures the selector to query for archived and unarchived rims. + * + * @return the selector + */ + public ReferenceManifestSelector includeArchived() { + this.excludeArchivedRims = false; + return this; + } +} 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 new file mode 100644 index 00000000..7e4638bf --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/CredentialValidator.java @@ -0,0 +1,68 @@ +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 java.security.KeyStore; +import java.util.Map; + +/** + * 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. + * + * @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. + */ + AppraisalStatus validatePlatformCredential(PlatformCredential pc, + KeyStore trustStore, + boolean acceptExpired); + + /** + * 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 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. + */ + 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); +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/ReferenceManifestValidator.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/ReferenceManifestValidator.java new file mode 100644 index 00000000..0f0adb8d --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/ReferenceManifestValidator.java @@ -0,0 +1,445 @@ +package hirs.attestationca.persist.validation; + +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.UnmarshalException; +import jakarta.xml.bind.Unmarshaller; +import lombok.extern.log4j.Log4j2; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.crypto.AlgorithmMethod; +import javax.xml.crypto.KeySelector; +import javax.xml.crypto.KeySelectorException; +import javax.xml.crypto.KeySelectorResult; +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.XMLCryptoContext; +import javax.xml.crypto.XMLStructure; +import javax.xml.crypto.dsig.XMLSignature; +import javax.xml.crypto.dsig.XMLSignatureException; +import javax.xml.crypto.dsig.XMLSignatureFactory; +import javax.xml.crypto.dsig.dom.DOMValidateContext; +import javax.xml.crypto.dsig.keyinfo.KeyInfo; +import javax.xml.crypto.dsig.keyinfo.X509Data; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Iterator; + +/** + * This class handles validation functions of RIM files. + * Currently supports validation of support RIM hashes and + * base RIM signatures. + */ +@Log4j2 +public class ReferenceManifestValidator { + private static final String SIGNATURE_ALGORITHM_RSA_SHA256 = + "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; + private static final String SCHEMA_PACKAGE = "hirs.utils.xjc"; + private static final String SCHEMA_URL = "swid_schema.xsd"; + private static final String SCHEMA_LANGUAGE = XMLConstants.W3C_XML_SCHEMA_NS_URI; + private static final String IDENTITY_TRANSFORM = "identity_transform.xslt"; + private static final String SHA256 = "SHA-256"; + private static final int EIGHT_BIT_MASK = 0xff; + private static final int LEFT_SHIFT = 0x100; + private static final int RADIX = 16; + + private Document rim; + private Unmarshaller unmarshaller; + private PublicKey publicKey; + private Schema schema; + private String subjectKeyIdentifier; + private boolean signatureValid, supportRimValid; + + /** + * Setter for the RIM to be validated. The ReferenceManifest object is converted into a + * Document for processing. + * + * @param rim ReferenceManifest object + */ + public void setRim(final ReferenceManifest rim) { + try { + Document doc = validateSwidtagSchema(removeXMLWhitespace(new StreamSource( + new ByteArrayInputStream(rim.getRimBytes())))); + this.rim = doc; + } catch (IOException e) { + log.error("Error while unmarshalling rim bytes: " + e.getMessage()); + } + } + + /** + * Getter for signatureValid. + * + * @return true if valid, false if not. + */ + public boolean isSignatureValid() { + return signatureValid; + } + + /** + * Getter for supportRimValid. + * + * @return true if valid, false if not. + */ + public boolean isSupportRimValid() { + return supportRimValid; + } + + /** + * Getter for certificate PublicKey. + * + * @return PublicKey + */ + public PublicKey getPublicKey() { + return publicKey; + } + + /** + * Getter for subjectKeyIdentifier. + * + * @return subjectKeyIdentifier + */ + public String getSubjectKeyIdentifier() { + return subjectKeyIdentifier; + } + + /** + * This default constructor creates the Schema object from SCHEMA_URL immediately to save + * time during validation calls later. + */ + public ReferenceManifestValidator() { + try { + InputStream is = ReferenceManifestValidator.class + .getClassLoader().getResourceAsStream(SCHEMA_URL); + SchemaFactory schemaFactory = SchemaFactory.newInstance(SCHEMA_LANGUAGE); + schema = schemaFactory.newSchema(new StreamSource(is)); + rim = null; + signatureValid = false; + supportRimValid = false; + publicKey = null; + subjectKeyIdentifier = "(not found)"; + } catch (SAXException e) { + log.warn("Error setting schema for validation!"); + } + } + + /** + * This method attempts to validate the signature element of the instance's RIM + * using a given cert. The cert is compared to either the RIM's embedded certificate + * or the RIM's subject key identifier. If the cert is matched then validation proceeds, + * otherwise validation ends. + * + * @param cert the cert to be checked against the RIM + * @return true if the signature element is validated, false otherwise + */ + @SuppressWarnings("magicnumber") + public boolean validateXmlSignature(final CertificateAuthorityCredential cert) { + DOMValidateContext context = null; + try { + NodeList nodes = rim.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + if (nodes.getLength() == 0) { + log.error("Cannot validate RIM, signature element not found!"); + return false; + } + NodeList certElement = rim.getElementsByTagName("X509Certificate"); + if (certElement.getLength() > 0) { + X509Certificate embeddedCert = parseCertFromPEMString( + certElement.item(0).getTextContent()); + if (embeddedCert != null) { + subjectKeyIdentifier = getCertificateSubjectKeyIdentifier(embeddedCert); + if (Arrays.equals(embeddedCert.getPublicKey().getEncoded(), + cert.getEncodedPublicKey())) { + context = new DOMValidateContext(new X509KeySelector(), nodes.item(0)); + } + } + } else { + subjectKeyIdentifier = getKeyName(rim); + if (subjectKeyIdentifier.equals(cert.getSubjectKeyIdString())) { + context = new DOMValidateContext(cert.getX509Certificate().getPublicKey(), + nodes.item(0)); + } + } + if (context != null) { + publicKey = cert.getX509Certificate().getPublicKey(); + signatureValid = validateSignedXMLDocument(context); + return signatureValid; + } + } catch (IOException e) { + log.warn("Error while parsing certificate data: " + e.getMessage()); + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + /** + * This method calculates the SHA256 hash of the input byte array and compares it against + * the value passed in. + * + * @param input byte array to hash. + * @param expected value to compare against. + */ + public void validateSupportRimHash(final byte[] input, final String expected) { + String calculatedHash = getHashValue(input, SHA256); + supportRimValid = calculatedHash.equals(expected); + if (!supportRimValid) { + log.info("Unmatched support RIM hash! Expected: " + expected + + ", actual: " + calculatedHash); + } + } + + /** + * This method calculates the digest of a byte array based on the hashing algorithm passed in. + * + * @param input byte array. + * @param sha hash algorithm. + * @return String digest. + */ + private String getHashValue(final byte[] input, final String sha) { + String resultString = null; + try { + MessageDigest md = MessageDigest.getInstance(sha); + byte[] bytes = md.digest(input); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < bytes.length; i++) { + sb.append(Integer.toString((bytes[i] & EIGHT_BIT_MASK) + + LEFT_SHIFT, RADIX).substring(1)); + } + resultString = sb.toString(); + } catch (NoSuchAlgorithmException grex) { + log.warn(grex.getMessage()); + } + + return resultString; + } + + private boolean validateSignedXMLDocument(final DOMValidateContext context) { + try { + XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM"); + XMLSignature signature = sigFactory.unmarshalXMLSignature(context); + return signature.validate(context); + } catch (MarshalException e) { + log.warn("Error while unmarshalling XML signature: " + e.getMessage()); + } catch (XMLSignatureException e) { + log.warn("Error while validating XML signature: " + e.getMessage()); + } + + return false; + } + + /** + * This internal class handles selecting an X509 certificate embedded in a KeyInfo element. + * It is passed as a parameter to a DOMValidateContext that uses it to validate + * an XML signature. + */ + public static class X509KeySelector extends KeySelector { + /** + * This method selects a public key for validation. + * PKs are parsed preferentially from the following elements: + * - X509Data + * - KeyValue + * The parsed PK is then verified based on the provided algorithm before + * being returned in a KeySelectorResult. + * + * @param keyinfo object containing the cert. + * @param purpose purpose. + * @param algorithm algorithm. + * @param context XMLCryptoContext. + * @return KeySelectorResult holding the PublicKey. + * @throws KeySelectorException exception. + */ + public KeySelectorResult select(final KeyInfo keyinfo, + final KeySelector.Purpose purpose, + final AlgorithmMethod algorithm, + final XMLCryptoContext context) + throws KeySelectorException { + Iterator keyinfoItr = keyinfo.getContent().iterator(); + while (keyinfoItr.hasNext()) { + XMLStructure element = (XMLStructure) keyinfoItr.next(); + if (element instanceof X509Data) { + X509Data data = (X509Data) element; + Iterator dataItr = data.getContent().iterator(); + while (dataItr.hasNext()) { + Object object = dataItr.next(); + if (object instanceof X509Certificate) { + final PublicKey publicKey = ((X509Certificate) object).getPublicKey(); + if (areAlgorithmsEqual(algorithm.getAlgorithm(), + publicKey.getAlgorithm())) { + return new ReferenceManifestValidator.X509KeySelector + .RIMKeySelectorResult(publicKey); + } + } + } + } + } + throw new KeySelectorException("No key found!"); + } + + /** + * This method checks if two strings refer to the same algorithm. + * + * @param uri string 1 + * @param name string 2 + * @return true if equal, false if not + */ + public boolean areAlgorithmsEqual(final String uri, final String name) { + return uri.equals(SIGNATURE_ALGORITHM_RSA_SHA256) && name.equalsIgnoreCase("RSA"); + } + + /** + * This internal class creates a KeySelectorResult from the public key. + */ + private static class RIMKeySelectorResult implements KeySelectorResult { + private Key key; + + RIMKeySelectorResult(final Key key) { + this.key = key; + } + + public Key getKey() { + return key; + } + } + } + + /** + * This method extracts certificate bytes from a string. The bytes are assumed to be + * PEM format, and a header and footer are concatenated with the input string to + * facilitate proper parsing. + * + * @param pemString the input string + * @return an X509Certificate created from the string, or null + * @throws Exception if certificate cannot be successfully parsed + */ + private X509Certificate parseCertFromPEMString(final String pemString) throws Exception { + String certificateHeader = "-----BEGIN CERTIFICATE-----"; + String certificateFooter = "-----END CERTIFICATE-----"; + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + InputStream inputStream = new ByteArrayInputStream((certificateHeader + + System.lineSeparator() + + pemString + + System.lineSeparator() + + certificateFooter).getBytes("UTF-8")); + return (X509Certificate) factory.generateCertificate(inputStream); + } catch (CertificateException e) { + log.warn("Error creating CertificateFactory instance: " + e.getMessage()); + } catch (UnsupportedEncodingException e) { + log.warn("Error while parsing cert from PEM string: " + e.getMessage()); + } + + return null; + } + + /** + * This method returns the subjectKeyIdentifier from a given X509Certificate. + * + * @param certificate the cert to pull the subjectKeyIdentifier from + * @return the String representation of the subjectKeyIdentifier + * @throws IOException + */ + private String getCertificateSubjectKeyIdentifier(final X509Certificate certificate) + throws IOException { + String decodedValue; + byte[] extension = certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId()); + if (extension != null && extension.length > 0) { + decodedValue = JcaX509ExtensionUtils.parseExtensionValue(extension).toString(); + } else { + decodedValue = " "; //Unlikely that a proper X509Certificate does not have a skid + } + return decodedValue.substring(1); //Drop the # at the beginning of the string + } + + /** + * This method parses the subject key identifier from the KeyName element of a signature. + * + * @param doc + * @return SKID if found, or an empty string. + */ + private String getKeyName(final Document doc) { + NodeList keyName = doc.getElementsByTagName("KeyName"); + if (keyName.getLength() > 0) { + return keyName.item(0).getTextContent(); + } else { + return null; + } + } + + /** + * This method validates the Document against the schema. + * + * @param doc of the input swidtag. + * @return document validated against the schema. + */ + private Document validateSwidtagSchema(final Document doc) { + try { + JAXBContext jaxbContext = JAXBContext.newInstance(SCHEMA_PACKAGE); + unmarshaller = jaxbContext.createUnmarshaller(); + unmarshaller.setSchema(schema); + unmarshaller.unmarshal(doc); + } catch (UnmarshalException e) { + log.warn("Error validating swidtag file!"); + } catch (IllegalArgumentException e) { + log.warn("Input file empty."); + } catch (JAXBException e) { + e.printStackTrace(); + } + + return doc; + } + + /** + * This method strips all whitespace from an xml file, including indents and spaces + * added for human-readability. + * + * @param source of the input xml. + * @return Document representation of the xml. + */ + private Document removeXMLWhitespace(final StreamSource source) throws IOException { + TransformerFactory tf = TransformerFactory.newInstance(); + Source identitySource = new StreamSource( + ReferenceManifestValidator.class.getClassLoader() + .getResourceAsStream(IDENTITY_TRANSFORM)); + Document doc = null; + try { + Transformer transformer = tf.newTransformer(identitySource); + DOMResult result = new DOMResult(); + transformer.transform(source, result); + doc = (Document) result.getNode(); + } catch (TransformerConfigurationException e) { + log.warn("Error configuring transformer!"); + e.printStackTrace(); + } catch (TransformerException e) { + log.warn("Error transforming input!"); + e.printStackTrace(); + } + + return doc; + } +} diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainValidatorException.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainValidatorException.java new file mode 100644 index 00000000..82a300e8 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/validation/SupplyChainValidatorException.java @@ -0,0 +1,45 @@ +package hirs.attestationca.persist.validation; + +/** + * This class represents exceptions thrown by the SupplyChainValidator class. + */ +public class SupplyChainValidatorException extends Exception { + + private static final long serialVersionUID = 8563981058518865230L; + + /** + * Creates a new SupplyChainValidatorException that has the message + * message and Throwable cause cause. + * + * @param message + * exception message + * @param cause + * root cause + */ + public SupplyChainValidatorException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Creates a new SupplyChainValidatorException that has the String + * message message. + * + * @param message + * exception message + */ + public SupplyChainValidatorException(final String message) { + super(message); + } + + /** + * Creates a new SupplyChainValidatorException that has the Throwable + * cause cause. + * + * @param cause + * root cause + */ + public SupplyChainValidatorException(final Throwable cause) { + super(cause); + } + +} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/HIRSDbInitializer.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/HIRSDbInitializer.java index 2d4a8277..01cdaf94 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/HIRSDbInitializer.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/HIRSDbInitializer.java @@ -13,47 +13,4 @@ public class HIRSDbInitializer implements ServletContextListener { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @Autowired static SettingsServiceImpl settingsService = new SettingsServiceImpl(); -// -// public void contextInitialized(final ServletContextEvent servletContextEvent) { -//// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); -// context.getEnvironment().addActiveProfile("server"); -// context.register(PersistenceJPAConfig.class); -// context.refresh(); -// -// // obtain reference to hibernate session factory -// EntityManager entityManager = context.getBean(EntityManagerFactory.class) -// .createEntityManager(); -// /** -// * This fails if there is an entry already. -// */ -//// entityManager.getTransaction().begin(); -//// entityManager.persist(context.getBean("default-settings")); -//// entityManager.getTransaction().commit(); -// -// insertDefaultEntries(); -// } -// -// /** -// * Insert the ACA's default entries into the DB. This class is invoked after successful -// * install of the HIRS_AttestationCA RPM. -// * -// */ -// public static synchronized void insertDefaultEntries() { -// LOGGER.error("Ensuring default ACA database entries are present."); -// -// // If the SupplyChainAppraiser exists, do not attempt to re-save the supply chain appraiser -// // or SupplyChainSettings -// -// // Create the SupplyChainAppraiser -// LOGGER.error("Saving supply chain appraiser..."); -// -// -// // Create the SupplyChainSettings -// LOGGER.error("Saving default supply chain policy..."); -//// SupplyChainSettings supplyChainPolicy = new SupplyChainSettings( -//// SupplyChainSettings.DEFAULT_POLICY); -// settingsService.saveSettings(new SupplyChainSettings("Default", "Settings are configured for no validation flags set.")); -// -// LOGGER.error("ACA database initialization complete."); -// } } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java index fa8cad80..c9c14302 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/PersistenceJPAConfig.java @@ -1,5 +1,7 @@ package hirs.attestationca.portal; +import hirs.attestationca.persist.entity.userdefined.SupplyChainSettings; +import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -17,15 +19,29 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; +import java.security.cert.X509Certificate; import java.util.Properties; +@Log4j2 @Configuration @EnableTransactionManagement -@PropertySource({ "classpath:hibernate.properties" }) -@ComponentScan({ "hirs.attestationca.portal.page" }) -@EnableJpaRepositories(basePackages = "hirs.attestationca.persist") +@PropertySource({ "classpath:hibernate.properties", "classpath:portal.properties" }) +@ComponentScan({ "hirs.attestationca.portal.page.controllers", "hirs.attestationca.persist.entity" }) +@EnableJpaRepositories(basePackages = "hirs.attestationca.persist.entity.manager") public class PersistenceJPAConfig { + @Value("${aca.directories.certificates}") + private String certificatesLocation; + + @Value("${aca.keyStore.location}") + private String keyStoreLocation; + + @Value("${aca.keyStore.password:''}") + private String keyStorePassword; + + @Value("${aca.keyStore.alias}") + private String keyAlias; + @Autowired private Environment environment; @@ -53,6 +69,97 @@ public class PersistenceJPAConfig { return dataSource; } + /** + * Initialization of the ACA. Detects environment and runs configuration + * methods as required. This method is intended to be invoked by the Spring + * application context. + */ +// @PostConstruct +// void initialize() { +// // ensure that Bouncy Castle is registered as a security provider +// Security.addProvider(new BouncyCastleProvider()); +// +// // obtain path to ACA configuration +// Path certificatesPath = Paths.get(certificatesLocation); +// +// // create base directories if they do not exist +// try { +// Files.createDirectories(certificatesPath); +// } catch (IOException ioEx) { +// throw new BeanInitializationException( +// "Encountered error while initializing ACA directories: " + ioEx.getMessage(), ioEx); +// } +// +// // create the ACA key store if it doesn't exist +// Path keyStorePath = Paths.get(keyStoreLocation); +//// if (!Files.exists(keyStorePath)) { +//// throw new IllegalStateException( +//// String.format("ACA Key Store not found at %s. Consult the HIRS User " +//// + "Guide for ACA installation instructions.", keyStoreLocation)); +//// } +// } + + /** + * @return the {@link X509Certificate} of the ACA + */ +// @Bean +// public X509Certificate acaCertificate() { +// KeyStore keyStore = keyStore(); +// +// try { +// X509Certificate acaCertificate = (X509Certificate) keyStore.getCertificate(keyAlias); +// +// // break early if the certificate is not available. +// if (acaCertificate == null) { +// throw new BeanInitializationException(String.format("Certificate with alias " +// + "%s was not in KeyStore %s. Ensure that the KeyStore has the " +// + "specified certificate. ", keyAlias, keyStoreLocation)); +// } +// +// return acaCertificate; +// } catch (KeyStoreException ksEx) { +// throw new BeanInitializationException("Encountered error loading ACA certificate " +// + "from key store: " + ksEx.getMessage(), ksEx); +// } +// } + + /** + * @return the {@link java.security.KeyStore} that contains the certificates + * for the ACA. + */ +// @Bean +// public KeyStore keyStore() { +// Path keyStorePath = Paths.get(keyStoreLocation); +// +// // creating empty store +// String storePassword = "storePassword"; +// String storeName = "emptyStore.jks"; +// String storeType = "jks"; +// +// // attempt to open the key store. if that fails, log a meaningful message before failing. +//// try { +//// KeyStore keyStore = KeyStore.getInstance("JKS"); +//// keyStore.load(Files.newInputStream(keyStorePath), keyStorePassword.toCharArray()); +// +// // empty +// try (FileOutputStream fileOutputStream = new FileOutputStream(storeName)) { +// KeyStore keyStore = KeyStore.getInstance(storeType); +// keyStore.load(null, storePassword.toCharArray()); +//// keyStore.setCertificateEntry(keyAlias,); +// keyStore.store(fileOutputStream, storePassword.toCharArray()); +// +// +// return keyStore; +// } catch (Exception e) { +// log.error(String.format( +// "Encountered error while loading ACA key store. The most common issue is " +// + "that configured password does not work on the configured key" +// + " store %s.", keyStorePath)); +// log.error(String.format("Exception message: %s", e.getMessage())); +// throw new BeanInitializationException(e.getMessage(), e); +// } +// } + @Bean public PlatformTransactionManager transactionManager() { final JpaTransactionManager transactionManager = new JpaTransactionManager(); @@ -76,11 +183,11 @@ public class PersistenceJPAConfig { return hibernateProperties; } -// -// @Bean(name="default-settings") -// public SupplyChainSettings supplyChainSettings() { -// SupplyChainSettings scSettings = new SupplyChainSettings("Default", "Settings are configured for no validation flags set."); -// -// return scSettings; -// } + + @Bean(name="default-settings") + public SupplyChainSettings supplyChainSettings() { + SupplyChainSettings scSettings = new SupplyChainSettings("Default", "Settings are configured for no validation flags set."); + + return scSettings; + } } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Column.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Column.java new file mode 100644 index 00000000..147fa274 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Column.java @@ -0,0 +1,77 @@ +package hirs.attestationca.portal.datatables; + + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Java representation of a jQuery DataTables Column. + */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PUBLIC) +public class Column { + + /** + * Column's data source. + * + * @see http://datatables.net/reference/option/columns.data + */ + @NotBlank + private String data; + + /** + * Column's name. + * + * @see http://datatables.net/reference/option/columns.name + */ + private String name; + + /** + * Flag to indicate if this column is searchable (true) or not (false). + * + * @see http://datatables.net/reference/option/columns.searchable + */ + @NotNull + private boolean searchable; + + /** + * Flag to indicate if this column is orderable (true) or not (false). + * + * @see http://datatables.net/reference/option/columns.orderable + */ + @NotNull + private boolean orderable; + + /** + * Search value to apply to this specific column. + */ + @NotNull + private Search search; + + /** + * Set the search value to apply to this column. + * + * @param searchValue if any, the search value to apply + */ + public void setSearchValue(final String searchValue) { + this.search.setValue(searchValue); + } + + @Override + public String toString() { + return "Column{" + + "data='" + data + '\'' + + ", name='" + name + '\'' + + ", searchable=" + searchable + + ", orderable=" + orderable + + ", search=" + search + + '}'; + } +} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/DataTableInput.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/DataTableInput.java new file mode 100644 index 00000000..ee6256c7 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/DataTableInput.java @@ -0,0 +1,224 @@ +package hirs.attestationca.portal.datatables; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents a data table input in a jQuery DataTable. + */ +@NoArgsConstructor(access = AccessLevel.PUBLIC) +public class DataTableInput { + + private static final int DEFAULT_LENGTH = 10; + + /** + * Constructor. + * @param draw the draw counter + * @param start the paging start indicator + * @param length the number of records in current draw + * @param search the search parameter + * @param order the orderings + * @param columns the columns of the input + */ + public DataTableInput(final Integer draw, final Integer start, final Integer length, + final Search search, final List order, + final List columns) { + this.draw = draw; + this.start = start; + this.length = length; + this.search = search; + this.order.addAll(order); + this.columns.addAll(columns); + } + + /** + * Draw counter. This is used by DataTables to ensure that the Ajax returns from server-side + * processing requests are drawn in sequence by DataTables (Ajax requests are asynchronous and + * thus can return out of sequence). This is used as part of the draw return parameter (see + * below). + */ + @NotNull + @Min(0) + @Getter + @Setter + private int draw = 1; + + /** + * Paging first record indicator. This is the start point in the current data set + * (0 index based - i.e. 0 is the first record). + */ + @NotNull + @Min(0) + @Getter + @Setter + private int start = 0; + + /** + * Number of records that the table can display in the current draw. It is expected that the + * number of records returned will be equal to this number, + * unless the server has fewer records to return. Note that this can be -1 to indicate that + * all records should be returned (although that + * negates any benefits of server-side processing!) + */ + @NotNull + @Min(-1) + @Getter + @Setter + private int length = DEFAULT_LENGTH; + + /** + * Global search parameter. + */ + @Getter + @Setter + @NotNull + private Search search = new Search(); + + /** + * Order parameter. + */ + @Getter + @NotEmpty + private List order = new ArrayList<>(); + + /** + * Per-column search parameter. + */ + @Getter + @NotEmpty + private List columns = new ArrayList<>(); + + /** + * Sets the orders. + * @param order the orders + */ + public void setOrder(final List order) { + this.order.clear(); + this.order.addAll(order); + } + + /** + * Sets the table columns. + * @param columns the columns + */ + public void setColumns(final List columns) { + this.columns.clear(); + this.columns.addAll(columns); + } + + /** + * + * @return a {@link Map} of {@link Column} indexed by name + */ + public Map getColumnsAsMap() { + Map map = new HashMap(); + for (Column column : columns) { + map.put(column.getData(), column); + } + return map; + } + + /** + * Find a column by its name. + * + * @param columnName the name of the column + * @return the given Column, or null if not found + */ + public Column getColumn(final String columnName) { + if (columnName == null) { + return null; + } + for (Column column : columns) { + if (columnName.equals(column.getData())) { + return column; + } + } + return null; + } + + /** + * Add a new column. + * + * @param columnName the name of the column + * @param searchable whether the column is searchable or not + * @param orderable whether the column is orderable or not + * @param searchValue if any, the search value to apply + */ + public void addColumn(final String columnName, final boolean searchable, + final boolean orderable, final String searchValue) { + this.columns.add(new Column(columnName, "", searchable, orderable, + new Search(searchValue, false))); + } + + /** + * Add an order on the given column. + * + * @param columnName the name of the column + * @param ascending whether the sorting is ascending or descending + */ + public void addOrder(final String columnName, final boolean ascending) { + if (columnName == null) { + return; + } + for (int i = 0; i < columns.size(); i++) { + if (!columnName.equals(columns.get(i).getData())) { + continue; + } + order.add(new Order(i, ascending)); + } + } + + /** + * Gets the order column name, given the order ordinal value. + * @return the order column name + */ + public String getOrderColumnName() { + // attempt to get the column property based on the order index. + String orderColumnName = "id"; + List orders = getOrder(); + if (!CollectionUtils.isEmpty(orders)) { + int orderColumnIndex = orders.get(0).getColumn(); + + final Column column = getColumns().get(orderColumnIndex); + + // use the column's name as the order field for hibernate if set, + // otherwise, use the columns' data field + if (StringUtils.isNotEmpty(column.getName())) { + orderColumnName = column.getName(); + } else { + orderColumnName = column.getData(); + } + } + return orderColumnName; + } + + + /** + * Generates a string for this object. + * @return the string + */ + @Override + public String toString() { + return "DataTableInput{" + + "draw=" + draw + + ", start=" + start + + ", length=" + length + + ", search=" + search + + ", order=" + order + + ", columns=" + columns + + '}'; + } +} + diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/DataTableResponse.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/DataTableResponse.java index a1a486e9..d366ea37 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/DataTableResponse.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/DataTableResponse.java @@ -1,5 +1,6 @@ package hirs.attestationca.portal.datatables; +import hirs.attestationca.persist.FilteredRecordsList; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -30,11 +31,11 @@ public final class DataTableResponse { * @param recordList the filtered record list * @param inputQuery the data table input (used for draw) */ -// public DataTableResponse(final FilteredRecordsList recordList, -// final DataTableInput inputQuery) { -// this(recordList, inputQuery.getDraw(), -// recordList.getRecordsTotal(), recordList.getRecordsFiltered()); -// } + public DataTableResponse(final FilteredRecordsList recordList, + final DataTableInput inputQuery) { + this(recordList, inputQuery.getDraw(), + recordList.getRecordsTotal(), recordList.getRecordsFiltered()); + } /** * Constructs a data table response using the specified data with the data table specific diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Order.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Order.java new file mode 100644 index 00000000..80ecd80b --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Order.java @@ -0,0 +1,72 @@ +package hirs.attestationca.portal.datatables; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Represents a column ordering with regards to a jQuery DataTable. + */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PUBLIC) +public class Order { + + + /** + * Constructor. + * @param column the column index + * @param isAscending true if ascending order + */ + public Order(final int column, final boolean isAscending) { + this.column = column; + if (isAscending) { + this.dir = "asc"; + } else { + this.dir = "desc"; + } + } + + + /** + * Column to which ordering should be applied. This is an index reference + * to the columns array of information that is also submitted to the server. + */ + @NotNull + @Min(0) + private int column; + + /** + * Ordering direction for this column. It will be asc or desc to indicate ascending ordering or + * descending ordering, respectively. + */ + @NotNull + @Pattern(regexp = "(desc|asc)") + private String dir; + + /** + * + * @return true if ascending order, false otherwise. + */ + public boolean isAscending() { + if (dir.equalsIgnoreCase("asc")) { + return true; + } + return false; + } + + @Override + public String toString() { + return "Order{" + + "column=" + column + + ", dir='" + dir + '\'' + + '}'; + } +} + diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/OrderedListQueryDataTableAdapter.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/OrderedListQueryDataTableAdapter.java new file mode 100644 index 00000000..29be00da --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/OrderedListQueryDataTableAdapter.java @@ -0,0 +1,72 @@ +package hirs.attestationca.portal.datatables; + +import hirs.attestationca.persist.CriteriaModifier; +import hirs.attestationca.persist.FilteredRecordsList; +import hirs.attestationca.persist.OrderedListQuerier; +import org.springframework.util.CollectionUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A class to adapt the Javascript DataTable java class abstractions to the DBManager's getting + * of ordered lists. + * @param The type of object to query + */ +public final class OrderedListQueryDataTableAdapter { + + private OrderedListQueryDataTableAdapter() { + // do not construct + } + + /** + * Gets the ordered list of records using a default, no-op criteria modifier. + * @param clazz the type of objects to query for + * @param dbManager the db manager to execute the actual query + * @param dataTableInput the JS DataTable query abstraction + * @param orderColumnName the name of the column (java object field name) to query on + * @param the parameter type + * @return the filtered record list + */ + public static FilteredRecordsList getOrderedList(final Class clazz, + final OrderedListQuerier dbManager, + final DataTableInput dataTableInput, + final String orderColumnName) { + return getOrderedList(clazz, dbManager, dataTableInput, orderColumnName, null); + } + + /** + * Gets the ordered list of records. + * @param clazz the type of objects to query for + * @param dbManager the db manager to execute the actual query + * @param dataTableInput the JS DataTable query abstraction + * @param orderColumnName the name of the column (java object field name) to query on + * @param criteriaModifier the criteria modifier + * @param the parameter type + * @return the filtered record list + */ + public static FilteredRecordsList getOrderedList(final Class clazz, + final OrderedListQuerier dbManager, + final DataTableInput dataTableInput, + final String orderColumnName, + final CriteriaModifier criteriaModifier) { + + Map searchableColumnMap = new HashMap<>(); + for (Column column : dataTableInput.getColumns()) { + searchableColumnMap.put(column.getData(), column.isSearchable()); + } + + List orders = dataTableInput.getOrder(); + boolean isAscending = true; + if (!CollectionUtils.isEmpty(orders)) { + isAscending = orders.get(0).isAscending(); + } + + return dbManager.getOrderedList(clazz, orderColumnName, isAscending, + dataTableInput.getStart(), dataTableInput.getLength(), + dataTableInput.getSearch().getValue(), + searchableColumnMap, criteriaModifier); + } +} + diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Search.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Search.java new file mode 100644 index 00000000..c8d206d5 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Search.java @@ -0,0 +1,50 @@ +package hirs.attestationca.portal.datatables; + +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Represents a jQuery DataTables search parameter. + */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PUBLIC) +public class Search { + + /** + * Constructor for a non-regex search. + * @param value the search value + */ + public Search(final String value) { + this(value, false); + } + + /** + * Global search value. To be applied to all columns which have searchable as true. + */ + @NotNull + private String value = ""; + + /** + * true if the global filter should be treated as a regular expression for advanced searching, + * false otherwise. Note that normally server-side processing scripts will not perform regular + * expression searching for performance reasons on large data sets, + * but it is technically possible and at the discretion of your script. + */ + @NotNull + private boolean regex; + + @Override + public String toString() { + return "Search{" + + "value='" + value + '\'' + + ", regex=" + regex + + '}'; + } +} + diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java index 78190fb1..e94ff8b6 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java @@ -1,16 +1,47 @@ package hirs.attestationca.portal.page.controllers; +import hirs.attestationca.persist.DBServiceException; +import hirs.attestationca.persist.entity.userdefined.Certificate; import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; +import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate; +import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; +import hirs.attestationca.persist.service.CertificateService; import hirs.attestationca.persist.service.CertificateServiceImpl; import hirs.attestationca.portal.page.Page; import hirs.attestationca.portal.page.PageController; +import hirs.attestationca.portal.page.PageMessages; import hirs.attestationca.portal.page.params.NoPageParams; +import hirs.attestationca.portal.page.utils.CertificateStringMapBuilder; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.log4j.Log4j2; +import org.bouncycastle.util.encoders.DecoderException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import org.springframework.web.servlet.view.RedirectView; + +import java.io.IOException; +import java.net.URISyntaxException; +//import java.security.cert.CertificateEncodingException; +//import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +// note uploading base64 certs, old or new having decode issues check ACA channel @Log4j2 @Controller @@ -34,12 +65,12 @@ public class CertificatePageController extends PageController { * Constructor providing the Page's display and routing specification. * * @param certificateServiceImpl the certificate manager - * @param crudManager the CRUD manager for certificates - * @param acaCertificate the ACA's X509 certificate + // * @param crudManager the CRUD manager for certificates + // * @param acaCertificate the ACA's X509 certificate */ @Autowired public CertificatePageController( - final CertificateServiceImpl certificateServiceImpl + final CertificateServiceImpl certificateServiceImpl//, // final CrudManager crudManager, // final X509Certificate acaCertificate ) { @@ -48,12 +79,12 @@ public class CertificatePageController extends PageController { // this.dataTableQuerier = crudManager; // try { -//// certificateAuthorityCredential -//// = new CertificateAuthorityCredential(acaCertificate.getEncoded()); -// } catch (IOException e) { -// log.error("Failed to read ACA certificate", e); -// } catch (CertificateEncodingException e) { -// log.error("Error getting encoded ACA certificate", e); +// certificateAuthorityCredential +// = new CertificateAuthorityCredential(acaCertificate.getEncoded()); +// } catch (IOException ioEx) { +// log.error("Failed to read ACA certificate", ioEx); +// } catch (CertificateEncodingException ceEx) { +// log.error("Error getting encoded ACA certificate", ceEx); // } } @@ -70,4 +101,524 @@ public class CertificatePageController extends PageController { public ModelAndView initPage(final NoPageParams params, final Model model) { return getBaseModelAndView(); } + + /** + * Returns the path for the view and the data model for the page. + * + * @param certificateType String containing the certificate type + * @param params The object to map url parameters into. + * @param model The data model for the request. Can contain data from + * redirect. + * @return the path for the view and data model for the page. + */ + @RequestMapping("/{certificateType}") + public ModelAndView initPage(@PathVariable("certificateType") final String certificateType, + final NoPageParams params, final Model model) { + + ModelAndView mav = null; + HashMap data = new HashMap<>(); + // add page information + switch (certificateType) { + case PLATFORMCREDENTIAL: + mav = getBaseModelAndView(Page.PLATFORM_CREDENTIALS); + break; + case ENDORSEMENTCREDENTIAL: + mav = getBaseModelAndView(Page.ENDORSEMENT_KEY_CREDENTIALS); + break; + case ISSUEDCERTIFICATES: + mav = getBaseModelAndView(Page.ISSUED_CERTIFICATES); + break; + case TRUSTCHAIN: + mav = getBaseModelAndView(Page.TRUST_CHAIN); + // Map with the ACA certificate information + data.putAll(CertificateStringMapBuilder.getCertificateAuthorityInformation( + certificateAuthorityCredential, this.certificateServiceImpl)); + mav.addObject(ACA_CERT_DATA, data); + break; + default: + // send to an error page + break; + } + + return mav; + } + + /** + * Upload and processes a credential. + * + * @param certificateType String containing the certificate type + * @param files the files to process + * @param attr the redirection attributes + * @return the redirection view + * @throws URISyntaxException if malformed URI + */ + @RequestMapping(value = "/{certificateType}/upload", method = RequestMethod.POST) + protected RedirectView upload( + @PathVariable("certificateType") final String certificateType, + @RequestParam("file") final MultipartFile[] files, + final RedirectAttributes attr) throws URISyntaxException { + + Map model = new HashMap<>(); + PageMessages messages = new PageMessages(); + + for (MultipartFile file : files) { + //Parse certificate + Certificate certificate = parseCertificate(certificateType, file, messages); + + //Store only if it was parsed + if (certificate != null) { + storeCertificate( + certificateType, + file.getOriginalFilename(), + messages, certificate, + certificateServiceImpl); + } + } + + //Add messages to the model + model.put(MESSAGES_ATTRIBUTE, messages); + + return redirectTo(getCertificatePage(certificateType), new NoPageParams(), model, attr); + } + + /** + * Handles request to download the ACA cert by writing it to the response + * stream for download. + * + * @param response the response object (needed to update the header with the + * file name) + * + * @throws java.io.IOException when writing to response output stream + */ + @ResponseBody + @RequestMapping(value = "/trust-chain/download-aca-cert", method = RequestMethod.GET) + public void downloadAcaCertificate(final HttpServletResponse response) + throws IOException { + + // Set filename for download. + response.setHeader("Content-Disposition", "attachment; filename=\"hirs-aca-cert.cer\""); + response.setContentType("application/octet-stream"); + + // write cert to output stream + response.getOutputStream().write(certificateAuthorityCredential.getRawBytes()); + } + + /** + * Handles request to download the certs by writing it to the response stream + * for download in bulk. + * + * @param response the response object (needed to update the header with the + * file name) + * @throws java.io.IOException when writing to response output stream + */ + @RequestMapping(value = "/trust-chain/bulk", method = RequestMethod.GET) + public void caBulkDownload(final HttpServletResponse response) + throws IOException { + log.info("Handling request to download all trust chain certificates"); + String fileName = "trust-chain.zip"; + final String singleFileName = "ca-certificates"; + + // Set filename for download. + response.setHeader("Content-Disposition", "attachment; filename=" + fileName); + response.setContentType("application/zip"); + + try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) { + // get all files + bulkDownload(zipOut, this.certificateServiceImpl.fetchCertificates(CertificateAuthorityCredential.class), singleFileName); + // write cert to output stream + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from: "; + log.error(uuidError, ex); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + + /** + * Handles request to download the certs by writing it to the response stream + * for download in bulk. + * + * @param response the response object (needed to update the header with the + * file name) + * @throws java.io.IOException when writing to response output stream + */ + @RequestMapping(value = "/platform-credentials/bulk", method = RequestMethod.GET) + public void pcBulkDownload(final HttpServletResponse response) + throws IOException { + log.info("Handling request to download all platform certificates"); + String fileName = "platform_certificates.zip"; + final String singleFileName = "Platform_Certificate"; + String zipFileName; + + // Set filename for download. + response.setHeader("Content-Disposition", "attachment; filename=" + fileName); + response.setContentType("application/zip"); + + try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) { + // get all files + bulkDownload(zipOut, this.certificateServiceImpl.fetchCertificates(PlatformCredential.class), singleFileName); + // write cert to output stream + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from: "; + log.error(uuidError, ex); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + + /** + * Handles request to download the certs by writing it to the response stream + * for download in bulk. + * + * @param response the response object (needed to update the header with the + * file name) + * @throws java.io.IOException when writing to response output stream + */ + @RequestMapping(value = "/issued-certificates/bulk", method = RequestMethod.GET) + public void icBulkDownload(final HttpServletResponse response) + throws IOException { + log.info("Handling request to download all issued certificates"); + String fileName = "issued_certificates.zip"; + final String singleFileName = "Issued_Certificate"; + String zipFileName; + + // Set filename for download. + response.setHeader("Content-Disposition", "attachment; filename=" + fileName); + response.setContentType("application/zip"); + + try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) { + // get all files + bulkDownload(zipOut, this.certificateServiceImpl.fetchCertificates(IssuedAttestationCertificate.class), singleFileName); + // write cert to output stream + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from: "; + log.error(uuidError, ex); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + + /** + * Handles request to download the certs by writing it to the response stream + * for download in bulk. + * + * @param response the response object (needed to update the header with the + * file name) + * @throws java.io.IOException when writing to response output stream + */ + @RequestMapping(value = "/endorsement-key-credentials/bulk", method = RequestMethod.GET) + public void ekBulkDownload(final HttpServletResponse response) + throws IOException { + log.info("Handling request to download all endorsement certificates"); + String fileName = "endorsement_certificates.zip"; + final String singleFileName = "Endorsement_Certificates"; + + // Set filename for download. + response.setHeader("Content-Disposition", "attachment; filename=" + fileName); + response.setContentType("application/zip"); + + try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) { + // get all files + bulkDownload(zipOut, this.certificateServiceImpl.fetchCertificates(EndorsementCredential.class), singleFileName); + // write cert to output stream + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from: "; + log.error(uuidError, ex); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + + private ZipOutputStream bulkDownload(final ZipOutputStream zipOut, + final List certificates, + final String singleFileName) throws IOException { + String zipFileName; + // get all files + for (Certificate certificate : certificates) { + zipFileName = String.format("%s[%s].cer", singleFileName, + Integer.toHexString(certificate.getCertificateHash())); + // configure the zip entry, the properties of the 'file' + ZipEntry zipEntry = new ZipEntry(zipFileName); + zipEntry.setSize((long) certificate.getRawBytes().length * Byte.SIZE); + zipEntry.setTime(System.currentTimeMillis()); + zipOut.putNextEntry(zipEntry); + // the content of the resource + StreamUtils.copy(certificate.getRawBytes(), zipOut); + zipOut.closeEntry(); + } + zipOut.finish(); + return zipOut; + } + + /** + * Get the page based on the certificate type. + * + * @param certificateType String containing the certificate type + * @return the page for the certificate type. + */ + private static Page getCertificatePage(final String certificateType) { + // get page information (default to TRUST_CHAIN) + return switch (certificateType) { + case PLATFORMCREDENTIAL -> Page.PLATFORM_CREDENTIALS; + case ENDORSEMENTCREDENTIAL -> Page.ENDORSEMENT_KEY_CREDENTIALS; + case ISSUEDCERTIFICATES -> Page.ISSUED_CERTIFICATES; + default -> Page.TRUST_CHAIN; + }; + } + + /** + * Gets the certificate by the hash code of its bytes. Looks for both + * archived and unarchived certificates. + * + * @param certificateType String containing the certificate type + * @param certificateHash the hash of the certificate's bytes + * @param certificateManager the certificate manager to query + * @return the certificate or null if none is found + */ + private Certificate getCertificateByHash( + final String certificateType, + final int certificateHash, + final CertificateService certificateManager) { + + switch (certificateType) { + case PLATFORMCREDENTIAL: + return PlatformCredential + .select(certificateManager) + .includeArchived() + .byHashCode(certificateHash) + .getCertificate(); + case ENDORSEMENTCREDENTIAL: +// return EndorsementCredential +// .select(certificateManager) +// .includeArchived() +// .byHashCode(certificateHash) +// .getCertificate(); + case TRUSTCHAIN: + return CertificateAuthorityCredential + .select(certificateManager) + .includeArchived() + .byHashCode(certificateHash) + .getCertificate(); + default: + return null; + } + } + + /** + * Gets the certificate by the platform serial number. + * + * @param certificateType String containing the certificate type + * @param serialNumber the platform serial number + * @param certificateManager the certificate manager to query + * @return the certificate or null if none is found + */ + private List getCertificateByBoardSN( + final String certificateType, + final String serialNumber, + final CertificateService certificateManager) { + + if (serialNumber == null) { + return null; + } + + switch (certificateType) { + case PLATFORMCREDENTIAL: + return PlatformCredential + .select(certificateManager) + .byBoardSerialNumber(serialNumber) + .getCertificates().stream().collect(Collectors.toList()); + default: + return null; + } + } + + /** + * Parses an uploaded file into a certificate and populates the given model + * with error messages if parsing fails. + * + * @param certificateType String containing the certificate type + * @param file the file being uploaded from the portal + * @param messages contains any messages that will be display on the page + * @return the parsed certificate or null if parsing failed. + */ + private Certificate parseCertificate( + final String certificateType, + final MultipartFile file, + final PageMessages messages) { + log.info("Received File of Size: " + file.getSize()); + + byte[] fileBytes; + String fileName = file.getOriginalFilename(); + + // build the certificate from the uploaded bytes + try { + fileBytes = file.getBytes(); + } catch (IOException e) { + final String failMessage = String.format( + "Failed to read uploaded file (%s): ", fileName); + log.error(failMessage, e); + messages.addError(failMessage + e.getMessage()); + return null; + } + try { + switch (certificateType) { + case PLATFORMCREDENTIAL: + return new PlatformCredential(fileBytes); + case ENDORSEMENTCREDENTIAL: + return new EndorsementCredential(fileBytes); + case TRUSTCHAIN: + return new CertificateAuthorityCredential(fileBytes); + default: + final String failMessage = String.format("Failed to parse uploaded file " + + "(%s). Invalid certificate type: %s", fileName, certificateType); + log.error(failMessage); + messages.addError(failMessage); + return null; + } + } catch (IOException e) { + final String failMessage = String.format( + "Failed to parse uploaded file (%s): ", fileName); + log.error(failMessage, e); + messages.addError(failMessage + e.getMessage()); + return null; + } catch (DecoderException dEx) { + final String failMessage = String.format( + "Failed to parse uploaded pem file (%s): ", fileName); + log.error(failMessage, dEx); + messages.addError(failMessage + dEx.getMessage()); + return null; + } catch (IllegalArgumentException e) { + final String failMessage = String.format( + "Certificate format not recognized(%s): ", fileName); + log.error(failMessage, e); + messages.addError(failMessage + e.getMessage()); + return null; + } + } + + /** + * Store the given certificate in the database. + * + * @param certificateType String containing the certificate type + * @param fileName contain the name of the file of the certificate to + * be stored + * @param messages contains any messages that will be display on the page + * @param certificate the certificate to store + * @param certificateManager the DB manager to use + * @return the messages for the page + */ + private void storeCertificate( + final String certificateType, + final String fileName, + final PageMessages messages, + final Certificate certificate, + final CertificateService certificateManager) { + + Certificate existingCertificate; + + // look for an identical certificate in the database + try { + existingCertificate = getCertificateByHash( + certificateType, + certificate.getCertificateHash(), + certificateManager); + } catch (DBServiceException e) { + final String failMessage = "Querying for existing certificate failed (" + + fileName + "): "; + messages.addError(failMessage + e.getMessage()); + log.error(failMessage, e); + return; + } + + try { + // save the new certificate if no match is found + if (existingCertificate == null) { + if (certificateType.equals(PLATFORMCREDENTIAL)) { + PlatformCredential platformCertificate = (PlatformCredential) certificate; + if (platformCertificate.isPlatformBase()) { + List sharedCertificates = getCertificateByBoardSN( + certificateType, + platformCertificate.getPlatformSerial(), + certificateManager); + + if (sharedCertificates != null) { + for (PlatformCredential pc : sharedCertificates) { + if (pc.isPlatformBase()) { + final String failMessage = "Storing certificate failed: " + + "platform credential " + + "chain (" + pc.getPlatformSerial() + + ") base already exists in this chain (" + + fileName + ")"; + messages.addError(failMessage); + log.error(failMessage); + return; + } + } + } + } /**else { + // this is a delta, check if the holder exists. + PlatformCredential holderPC = PlatformCredential + .select(certificateManager) + .bySerialNumber(platformCertificate.getHolderSerialNumber()) + .getCertificate(); + if (holderPC == null) { + final String failMessage = "Storing certificate failed: " + + "delta credential" + + " must have an existing holder stored. " + + "Credential serial " + + platformCertificate.getHolderSerialNumber() + + " doesn't exist."; + messages.addError(failMessage); + LOGGER.error(failMessage); + return; + } + }**/ + } + + certificateManager.saveCertificate(certificate); + + final String successMsg + = String.format("New certificate successfully uploaded (%s): ", fileName); + messages.addSuccess(successMsg); + log.info(successMsg); + return; + } + } catch (DBServiceException e) { + final String failMessage = String.format("Storing new certificate failed (%s): ", + fileName); + messages.addError(failMessage + e.getMessage()); + log.error(failMessage, e); + return; + } + + try { + // if an identical certificate is archived, update the existing certificate to + // unarchive it and change the creation date + if (existingCertificate.isArchived()) { + existingCertificate.restore(); + existingCertificate.resetCreateTime(); + certificateManager.updateCertificate(existingCertificate); + + final String successMsg = String.format("Pre-existing certificate " + + "found and unarchived (%s): ", fileName); + messages.addSuccess(successMsg); + log.info(successMsg); + return; + } + } catch (DBServiceException e) { + final String failMessage = String.format("Found an identical" + + " pre-existing certificate in the " + + "archive, but failed to unarchive it (%s): ", fileName); + messages.addError(failMessage + e.getMessage()); + log.error(failMessage, e); + return; + } + + // if an identical certificate is already unarchived, do nothing and show a fail message + final String failMessage + = String.format("Storing certificate failed: an identical" + + " certificate already exists (%s): ", fileName); + messages.addError(failMessage); + log.error(failMessage); + } } 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 new file mode 100644 index 00000000..98554883 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestDetailsPageController.java @@ -0,0 +1,601 @@ +package hirs.attestationca.portal.page.controllers; + +import hirs.attestationca.persist.DBServiceException; +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; +import hirs.attestationca.persist.entity.userdefined.rim.BaseReferenceManifest; +import hirs.attestationca.persist.entity.userdefined.rim.EventLogMeasurements; +import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; +import hirs.attestationca.persist.entity.userdefined.rim.SupportReferenceManifest; +import hirs.attestationca.persist.service.CertificateService; +import hirs.attestationca.persist.service.ReferenceDigestValueService; +import hirs.attestationca.persist.service.ReferenceDigestValueServiceImpl; +import hirs.attestationca.persist.service.ReferenceManifestService; +import hirs.attestationca.persist.service.ReferenceManifestServiceImpl; +import hirs.attestationca.persist.service.SupplyChainValidationServiceImpl; +import hirs.attestationca.persist.validation.ReferenceManifestValidator; +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; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * Controller for the Reference Manifest Details page. + */ +@Log4j2 +@Controller +@RequestMapping("/rim-details") +public class ReferenceManifestDetailsPageController extends PageController { + + private final ReferenceManifestService referenceManifestManager; + private final ReferenceDigestValueService referenceEventManager; + private final CertificateService certificateService; + private static final ReferenceManifestValidator RIM_VALIDATOR + = new ReferenceManifestValidator(); + + /** + * Constructor providing the Page's display and routing specification. + * + * @param referenceManifestManager the reference manifest manager. + * @param referenceEventManager the reference event manager. + * @param certificateService the certificate manager. + */ + @Autowired + public ReferenceManifestDetailsPageController( + final ReferenceManifestServiceImpl referenceManifestManager, + final ReferenceDigestValueServiceImpl referenceEventManager, + final CertificateService certificateService) { + super(Page.RIM_DETAILS); + this.referenceManifestManager = referenceManifestManager; + this.referenceEventManager = referenceEventManager; + this.certificateService = certificateService; + } + + /** + * Returns the filePath for the view and the data model for the page. + * + * @param params The object to map url parameters into. + * @param model The data model for the request. Can contain data from + * redirect. + * @return the path for the view and data model for the page. + */ + @Override + public ModelAndView initPage(final ReferenceManifestDetailsPageParams params, + final Model model) { + // get the basic information to render the page + ModelAndView mav = getBaseModelAndView(); + PageMessages messages = new PageMessages(); + + // Map with the rim information + HashMap data = new HashMap<>(); + + // Check if parameters were set + if (params.getId() == null) { + String typeError = "ID was not provided"; + messages.addError(typeError); + log.debug(typeError); + mav.addObject(MESSAGES_ATTRIBUTE, messages); + } else { + try { + UUID uuid = UUID.fromString(params.getId()); + data.putAll(getRimDetailInfo(uuid, referenceManifestManager, + referenceEventManager, certificateService)); + } catch (IllegalArgumentException iaEx) { + String uuidError = "Failed to parse ID from: " + params.getId(); + messages.addError(uuidError); + log.error(uuidError, iaEx); + } catch (Exception ioEx) { + log.error(ioEx); + } + if (data.isEmpty()) { + String notFoundMessage = "Unable to find RIM with ID: " + params.getId(); + messages.addError(notFoundMessage); + log.warn(notFoundMessage); + mav.addObject(MESSAGES_ATTRIBUTE, messages); + } else { + mav.addObject(INITIAL_DATA, data); + } + } + + // return the model and view + return mav; + } + + /** + * This method takes the place of an entire class for a string builder. + * Gathers all information and returns it for displays. + * + * @param uuid database reference for the requested RIM. + * @param referenceManifestManager the reference manifest manager. + * @param referenceEventManager the reference event manager. + * @param certificateManager the certificate manager. + * @return mapping of the RIM information from the database. + * @throws java.io.IOException error for reading file bytes. + * @throws NoSuchAlgorithmException If an unknown Algorithm is encountered. + * @throws CertificateException if a certificate doesn't parse. + */ + public static HashMap getRimDetailInfo(final UUID uuid, + final ReferenceManifestService referenceManifestManager, + final ReferenceDigestValueService referenceEventManager, + final CertificateService certificateManager) + throws IOException, + CertificateException, NoSuchAlgorithmException { + HashMap data = new HashMap<>(); + + BaseReferenceManifest bRim = BaseReferenceManifest.select(referenceManifestManager) + .byEntityId(uuid).getRIM(); + + if (bRim != null) { + data.putAll(getBaseRimInfo(bRim, referenceManifestManager, certificateManager)); + } + + SupportReferenceManifest sRim = SupportReferenceManifest.select(referenceManifestManager) + .byEntityId(uuid).getRIM(); + + if (sRim != null) { + data.putAll(getSupportRimInfo(sRim, referenceManifestManager)); + } + + EventLogMeasurements bios = EventLogMeasurements.select(referenceManifestManager) + .byEntityId(uuid).getRIM(); + + if (bios != null) { + data.putAll(getMeasurementsRimInfo(bios, referenceManifestManager, + referenceEventManager)); + } + + return data; + } + + /** + * This method takes the place of an entire class for a string builder. + * Gathers all information and returns it for displays. + * + * @param baseRim established ReferenceManifest Type. + * @param referenceManifestManager the reference manifest manager. + * @param certificateManager the certificate manager. + * @return mapping of the RIM information from the database. + * @throws java.io.IOException error for reading file bytes. + * @throws NoSuchAlgorithmException If an unknown Algorithm is encountered. + * @throws CertificateException if a certificate doesn't parse. + */ + private static HashMap getBaseRimInfo( + final BaseReferenceManifest baseRim, + final ReferenceManifestService referenceManifestManager, + final CertificateService certificateManager) + throws IOException, CertificateException, NoSuchAlgorithmException { + HashMap data = new HashMap<>(); + + // Software Identity + data.put("swidName", baseRim.getSwidName()); + data.put("swidVersion", baseRim.getSwidVersion()); + data.put("swidTagVersion", baseRim.getSwidTagVersion()); + if (baseRim.getSwidCorpus() == 1) { + data.put("swidCorpus", "True"); + } else { + data.put("swidCorpus", "False"); + } + if (baseRim.isSwidPatch()) { + data.put("swidPatch", "True"); + } else { + data.put("swidPatch", "False"); + } + if (baseRim.isSwidSupplemental()) { + data.put("swidSupplemental", "True"); + } else { + data.put("swidSupplemental", "False"); + } + data.put("swidTagId", baseRim.getTagId()); + // Entity + data.put("entityName", baseRim.getEntityName()); + data.put("entityRegId", baseRim.getEntityRegId()); + data.put("entityRole", baseRim.getEntityRole()); + data.put("entityThumbprint", baseRim.getEntityThumbprint()); + // Link + data.put("linkHref", baseRim.getLinkHref()); + data.put("linkHrefLink", ""); + for (BaseReferenceManifest bRim : BaseReferenceManifest + .select(referenceManifestManager).getRIMs()) { + if (baseRim.getLinkHref().contains(bRim.getTagId())) { + data.put("linkHrefLink", bRim.getId()); + } + } + data.put("linkRel", baseRim.getLinkRel()); + data.put("platformManufacturer", baseRim.getPlatformManufacturer()); + data.put("platformManufacturerId", baseRim.getPlatformManufacturerId()); + data.put("platformModel", baseRim.getPlatformModel()); + data.put("platformVersion", baseRim.getPlatformVersion()); + data.put("payloadType", baseRim.getPayloadType()); + data.put("colloquialVersion", baseRim.getColloquialVersion()); + data.put("edition", baseRim.getEdition()); + data.put("product", baseRim.getProduct()); + data.put("revision", baseRim.getRevision()); + data.put("bindingSpec", baseRim.getBindingSpec()); + data.put("bindingSpecVersion", baseRim.getBindingSpecVersion()); + data.put("pcUriGlobal", baseRim.getPcURIGlobal()); + data.put("pcUriLocal", baseRim.getPcURILocal()); + data.put("rimLinkHash", baseRim.getRimLinkHash()); + if (baseRim.getRimLinkHash() != null) { + ReferenceManifest rim = BaseReferenceManifest.select(referenceManifestManager) + .byHexDecHash(baseRim.getRimLinkHash()).getRIM(); + if (rim != null) { + data.put("rimLinkId", rim.getId()); + data.put("linkHashValid", true); + } else { + data.put("linkHashValid", false); + } + } + data.put("rimType", baseRim.getRimType()); + + List resources = baseRim.parseResource(); + TCGEventLog logProcessor = null; + SupportReferenceManifest support = null; + + if (baseRim.getAssociatedRim() == null) { + support = SupportReferenceManifest.select(referenceManifestManager) + .byManufacturer(baseRim.getPlatformManufacturer()) + .getRIM(); + if (support != null) { + baseRim.setAssociatedRim(support.getId()); + } + } else { + support = SupportReferenceManifest.select(referenceManifestManager) + .byEntityId(baseRim.getAssociatedRim()).getRIM(); + } + // going to have to pull the filename and grab that from the DB + // to get the id to make the link + RIM_VALIDATOR.setRim(baseRim); + for (SwidResource swidRes : resources) { + if (support != null && swidRes.getHashValue() + .equalsIgnoreCase(support.getHexDecHash())) { + RIM_VALIDATOR.validateSupportRimHash(support.getRimBytes(), + swidRes.getHashValue()); + if (RIM_VALIDATOR.isSupportRimValid()) { + data.put("supportRimHashValid", true); + } else { + data.put("supportRimHashValid", false); + } + break; + } + } + + data.put("associatedRim", baseRim.getAssociatedRim()); + data.put("swidFiles", resources); + if (support != null && (!baseRim.isSwidSupplemental() + && !baseRim.isSwidPatch())) { + data.put("pcrList", support.getExpectedPCRList()); + } + + Set certificates = + CertificateAuthorityCredential.select(certificateManager) + .getCertificates(); + //Report invalid signature unless RIM_VALIDATOR validates it and cert path is valid + data.put("signatureValid", false); + for (CertificateAuthorityCredential cert : certificates) { + SupplyChainValidationServiceImpl scvsImpl = + new SupplyChainValidationServiceImpl(certificateManager); + KeyStore keystore = scvsImpl.getCaChain(cert); + if (RIM_VALIDATOR.validateXmlSignature(cert)) { + try { + if (SupplyChainCredentialValidator.verifyCertificate( + cert.getX509Certificate(), keystore)) { + data.replace("signatureValid", true); + break; + } + } catch (SupplyChainValidatorException e) { + log.error("Error verifying cert chain: " + e.getMessage()); + } + } + } + data.put("skID", RIM_VALIDATOR.getSubjectKeyIdentifier()); + try { + for (CertificateAuthorityCredential cert : certificates) { + if (Arrays.equals(cert.getEncodedPublicKey(), + RIM_VALIDATOR.getPublicKey().getEncoded())) { + data.put("issuerID", cert.getId().toString()); + } + } + } catch (NullPointerException e) { + log.error("Unable to link signing certificate: " + e.getMessage()); + } + return data; + } + + /** + * This method takes the place of an entire class for a string builder. + * Gathers all information and returns it for displays. + * + * @param support established ReferenceManifest Type. + * @param referenceManifestManager the reference manifest manager. + * @return mapping of the RIM information from the database. + * @throws java.io.IOException error for reading file bytes. + * @throws NoSuchAlgorithmException If an unknown Algorithm is encountered. + * @throws CertificateException if a certificate doesn't parse. + */ + private static HashMap getSupportRimInfo( + final SupportReferenceManifest support, + final ReferenceManifestService referenceManifestManager) + throws IOException, CertificateException, NoSuchAlgorithmException { + HashMap data = new HashMap<>(); + EventLogMeasurements measurements = null; + + if (support.getAssociatedRim() == null) { + Set baseRims = BaseReferenceManifest + .select(referenceManifestManager) + .byRimType(ReferenceManifest.BASE_RIM).getRIMs(); + for (BaseReferenceManifest baseRim : baseRims) { + if (baseRim != null && baseRim.getAssociatedRim() != null + && baseRim.getAssociatedRim().equals(support.getId())) { + support.setAssociatedRim(baseRim.getId()); + try { + referenceManifestManager.updateReferenceManifest(support, support.getId()); + } catch (DBServiceException ex) { + log.error("Failed to update Support RIM", ex); + } + break; + } + } + } + + // testing this independent of the above if statement because the above + // starts off checking if associated rim is null; that is irrelevant for + // this statement. + measurements = EventLogMeasurements.select(referenceManifestManager) + .byHexDecHash(support.getHexDecHash()).getRIM(); + + if (support.isSwidPatch()) { + data.put("swidPatch", "True"); + } else { + data.put("swidPatch", "False"); + } + if (support.isSwidSupplemental()) { + data.put("swidSupplemental", "True"); + } else { + data.put("swidSupplemental", "False"); + } + data.put("swidBase", (!support.isSwidPatch() + && !support.isSwidSupplemental())); + data.put("baseRim", support.getTagId()); + data.put("associatedRim", support.getAssociatedRim()); + data.put("rimType", support.getRimType()); + data.put("tagId", support.getTagId()); + + TCGEventLog logProcessor = new TCGEventLog(support.getRimBytes()); + LinkedList tpmPcrEvents = new LinkedList<>(); + TCGEventLog measurementsProcess; + if (measurements != null) { + measurementsProcess = new TCGEventLog((measurements.getRimBytes())); + HashMap digestMap = new HashMap<>(); + for (TpmPcrEvent tpe : logProcessor.getEventList()) { + digestMap.put(tpe.getEventDigestStr(), tpe); + if (!support.isSwidSupplemental() + && !tpe.eventCompare( + measurementsProcess.getEventByNumber( + tpe.getEventNumber()))) { + tpe.setError(true); + } + tpmPcrEvents.add(tpe); + } + for (TpmPcrEvent tpe : logProcessor.getEventList()) { + tpe.setError(!digestMap.containsKey(tpe.getEventDigestStr())); + } + data.put("events", tpmPcrEvents); + } else { + data.put("events", logProcessor.getEventList()); + } + + getEventSummary(data, logProcessor.getEventList()); + return data; + } + + private static void getEventSummary(final HashMap data, + final Collection eventList) { + boolean crtm = false; + boolean bootManager = false; + boolean osLoader = false; + boolean osKernel = false; + boolean acpiTables = false; + boolean smbiosTables = false; + boolean gptTable = false; + boolean bootOrder = false; + boolean defaultBootDevice = false; + boolean secureBoot = false; + boolean pk = false; + boolean kek = false; + boolean sigDb = false; + boolean forbiddenDbx = false; + + String contentStr; + for (TpmPcrEvent tpe : eventList) { + contentStr = tpe.getEventContentStr(); + // check for specific events + if (contentStr.contains("CRTM")) { + crtm = true; + } else if (contentStr.contains("shimx64.efi") + || contentStr.contains("bootmgfw.efi")) { + bootManager = true; + } else if (contentStr.contains("grubx64.efi") + || contentStr.contains("winload.efi")) { + osLoader = true; + } else if (contentStr.contains("vmlinuz") + || contentStr.contains("ntoskrnl.exe")) { + osKernel = true; + } else if (contentStr.contains("ACPI")) { + acpiTables = true; + } else if (contentStr.contains("SMBIOS")) { + smbiosTables = true; + } else if (contentStr.contains("GPT")) { + gptTable = true; + } else if (contentStr.contains("BootOrder")) { + bootOrder = true; + } else if (contentStr.contains("Boot0000")) { + defaultBootDevice = true; + } else if (contentStr.contains("variable named PK")) { + pk = true; + } else if (contentStr.contains("variable named KEK")) { + kek = true; + } else if (contentStr.contains("variable named db")) { + if (contentStr.contains("dbx")) { + forbiddenDbx = true; + } else { + sigDb = true; + } + } else if (contentStr.contains("Secure Boot is enabled")) { + secureBoot = true; + } + } + + data.put("crtm", crtm); + data.put("bootManager", bootManager); + data.put("osLoader", osLoader); + data.put("osKernel", osKernel); + data.put("acpiTables", acpiTables); + data.put("smbiosTables", smbiosTables); + data.put("gptTable", gptTable); + data.put("bootOrder", bootOrder); + data.put("defaultBootDevice", defaultBootDevice); + data.put("secureBoot", secureBoot); + data.put("pk", pk); + data.put("kek", kek); + data.put("sigDb", sigDb); + data.put("forbiddenDbx", forbiddenDbx); + } + + /** + * This method takes the place of an entire class for a string builder. + * Gathers all information and returns it for displays. + * + * @param measurements established ReferenceManifest Type. + * @param referenceManifestManager the reference manifest manager. + * @param referenceEventManager the reference event manager. + * @return mapping of the RIM information from the database. + * @throws java.io.IOException error for reading file bytes. + * @throws NoSuchAlgorithmException If an unknown Algorithm is encountered. + * @throws CertificateException if a certificate doesn't parse. + */ + private static HashMap getMeasurementsRimInfo( + final EventLogMeasurements measurements, + final ReferenceManifestService referenceManifestManager, + final ReferenceDigestValueService referenceEventManager) + throws IOException, CertificateException, NoSuchAlgorithmException { + HashMap data = new HashMap<>(); + LinkedList livelogEvents = new LinkedList<>(); + BaseReferenceManifest base = null; + List supports = new ArrayList<>(); + SupportReferenceManifest baseSupport = null; + + data.put("supportFilename", "Blank"); + data.put("supportId", ""); + data.put("associatedRim", ""); + data.put("rimType", measurements.getRimType()); + data.put("hostName", measurements.getDeviceName()); + data.put("validationResult", measurements.getOverallValidationResult()); + data.put("swidBase", true); + + List eventValues = new ArrayList<>(); + if (measurements.getDeviceName() != null) { + supports.addAll(SupportReferenceManifest + .select(referenceManifestManager) + .byDeviceName(measurements + .getDeviceName()).getRIMs()); + for (SupportReferenceManifest support : supports) { + if (support.isBaseSupport()) { + baseSupport = support; + } + } + + if (baseSupport != null) { + data.put("supportFilename", baseSupport.getFileName()); + data.put("supportId", baseSupport.getId()); + + base = BaseReferenceManifest + .select(referenceManifestManager) + .byEntityId(baseSupport.getAssociatedRim()) + .getRIM(); + data.put("tagId", baseSupport.getTagId()); + + if (base != null) { + data.put("associatedRim", base.getId()); + } + + eventValues.addAll(referenceEventManager.getValuesByRimId(base)); + } + } + + TCGEventLog measurementLog = new TCGEventLog(measurements.getRimBytes()); + Map eventValueMap = new HashMap<>(); + + for (ReferenceDigestValue rdv : eventValues) { + eventValueMap.put(rdv.getDigestValue(), rdv); + } + for (TpmPcrEvent measurementEvent : measurementLog.getEventList()) { + if (!eventValueMap.containsKey(measurementEvent.getEventDigestStr())) { + livelogEvents.add(measurementEvent); + } + } + + if (!supports.isEmpty()) { + Map> baselineLogEvents = new HashMap<>(); + List matchedEvents = null; + List combinedBaselines = new LinkedList<>(); + for (SupportReferenceManifest support : supports) { + combinedBaselines.addAll(support.getEventLog()); + } + String bootVariable; + String variablePrefix = "Variable Name:"; + String variableSuffix = "UEFI_GUID"; + for (TpmPcrEvent tpe : livelogEvents) { + matchedEvents = new ArrayList<>(); + for (TpmPcrEvent tpmPcrEvent : combinedBaselines) { + if (tpmPcrEvent.getEventType() == tpe.getEventType()) { + if (tpe.getEventContentStr().contains(variablePrefix)) { + bootVariable = tpe.getEventContentStr().substring(( + tpe.getEventContentStr().indexOf(variablePrefix) + + variablePrefix.length()), + tpe.getEventContentStr().indexOf(variableSuffix)); + if (tpmPcrEvent.getEventContentStr().contains(bootVariable)) { + matchedEvents.add(tpmPcrEvent); + } + } else { + matchedEvents.add(tpmPcrEvent); + } + } + } + baselineLogEvents.put(tpe.getEventDigestStr(), matchedEvents); + } + data.put("eventTypeMap", baselineLogEvents); + } + + TCGEventLog logProcessor = new TCGEventLog(measurements.getRimBytes()); + data.put("livelogEvents", livelogEvents); + data.put("events", logProcessor.getEventList()); + getEventSummary(data, logProcessor.getEventList()); + + return data; + } +} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestPageController.java new file mode 100644 index 00000000..2de66b24 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/ReferenceManifestPageController.java @@ -0,0 +1,120 @@ +package hirs.attestationca.portal.page.controllers; + +import hirs.attestationca.persist.CriteriaModifier; +import hirs.attestationca.persist.FilteredRecordsList; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.ReferenceManifest; +import hirs.attestationca.persist.service.ReferenceDigestValueService; +import hirs.attestationca.persist.service.ReferenceDigestValueServiceImpl; +import hirs.attestationca.persist.service.ReferenceManifestService; +import hirs.attestationca.persist.service.ReferenceManifestServiceImpl; +import hirs.attestationca.portal.datatables.DataTableInput; +import hirs.attestationca.portal.datatables.DataTableResponse; +import hirs.attestationca.portal.datatables.OrderedListQueryDataTableAdapter; +import hirs.attestationca.portal.page.Page; +import hirs.attestationca.portal.page.PageController; +import hirs.attestationca.portal.page.params.NoPageParams; +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import jakarta.validation.Valid; +import lombok.extern.log4j.Log4j2; +import org.hibernate.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +import java.lang.ref.Reference; + +/** + * Controller for the Reference Manifest page. + */ +@Log4j2 +@Controller +@RequestMapping("/reference-manifests") +public class ReferenceManifestPageController extends PageController { + + @Autowired(required = false) + private EntityManager entityManager; + + private final ReferenceManifestService referenceManifestManager; + private final ReferenceDigestValueService referenceEventManager; + + /** + * Constructor providing the Page's display and routing specification. + * + * @param referenceManifestManager the reference manifest manager + * @param referenceEventManager this is the reference event manager + */ + @Autowired + public ReferenceManifestPageController( + final ReferenceManifestServiceImpl referenceManifestManager, + final ReferenceDigestValueServiceImpl referenceEventManager) { + super(Page.REFERENCE_MANIFESTS); + this.referenceManifestManager = referenceManifestManager; + this.referenceEventManager = referenceEventManager; + } + + /** + * Returns the filePath for the view and the data model for the page. + * + * @param params The object to map url parameters into. + * @param model The data model for the request. Can contain data from + * redirect. + * @return the filePath for the view and data model for the page. + */ + @Override + public ModelAndView initPage(final NoPageParams params, + final Model model) { + return getBaseModelAndView(); + } + + /** + * Returns the list of RIMs using the data table input for paging, ordering, + * and filtering. + * + * @param input the data tables input + * @return the data tables response, including the result set and paging + * information + */ + @ResponseBody + @RequestMapping(value = "/list", + produces = MediaType.APPLICATION_JSON_VALUE, + method = RequestMethod.GET) + public DataTableResponse getTableData( + @Valid final DataTableInput input) { + log.info("Handling request for summary list: " + input); + +// return this.referenceManifestManager.fetchReferenceManifests(input); + + String orderColumnName = input.getOrderColumnName(); + log.debug("Ordering on column: " + orderColumnName); + + // check that the alert is not archived and that it is in the specified report + CriteriaModifier criteriaModifier = new CriteriaModifier() { + @Override + public void modify(final CriteriaQuery criteriaQuery) { + Session session = entityManager.unwrap(Session.class); + CriteriaBuilder cb = session.getCriteriaBuilder(); + Root rimRoot = criteriaQuery.from(Reference.class); + + criteriaQuery.select(rimRoot).distinct(true).where(cb.isNull(rimRoot.get(Certificate.ARCHIVE_FIELD))); +// criteria.add(Restrictions.isNull(Certificate.ARCHIVE_FIELD)); + } + }; + FilteredRecordsList records + = OrderedListQueryDataTableAdapter.getOrderedList( + ReferenceManifest.class, + referenceManifestManager, + input, orderColumnName, criteriaModifier); + + log.debug("Returning list of size: " + records.size()); + return new DataTableResponse<>(records, input); + } +} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/RimDatabasePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/RimDatabasePageController.java new file mode 100644 index 00000000..a8f15e9f --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/RimDatabasePageController.java @@ -0,0 +1,122 @@ +package hirs.attestationca.portal.page.controllers; + +import hirs.attestationca.persist.entity.userdefined.rim.ReferenceDigestValue; +import hirs.attestationca.persist.service.ReferenceDigestValueService; +import hirs.attestationca.persist.service.ReferenceDigestValueServiceImpl; +import hirs.attestationca.persist.service.ReferenceManifestService; +import hirs.attestationca.persist.service.ReferenceManifestServiceImpl; +import hirs.attestationca.portal.page.Page; +import hirs.attestationca.portal.page.PageController; +import hirs.attestationca.portal.page.params.NoPageParams; +import jakarta.validation.Valid; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.datatables.mapping.DataTablesInput; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +import java.util.List; + +/** + * Controller for the TPM Events page. + */ +@Log4j2 +@Controller +@RequestMapping("/rim-database") +public class RimDatabasePageController extends PageController { + + private final ReferenceManifestService referenceManifestManager; + private final ReferenceDigestValueService referenceEventManager; + + /** + * Constructor providing the Page's display and routing specification. + * + * @param referenceManifestManager the ReferenceManifestManager object + * @param referenceEventManager the referenceEventManager object + */ + @Autowired + public RimDatabasePageController(final ReferenceManifestServiceImpl referenceManifestManager, + final ReferenceDigestValueServiceImpl referenceEventManager) { + super(Page.RIM_DATABASE); + this.referenceManifestManager = referenceManifestManager; + this.referenceEventManager = referenceEventManager; + } + + /** + * Returns the filePath for the view and the data model for the page. + * + * @param params The object to map url parameters into. + * @param model The data model for the request. Can contain data from + * redirect. + * @return the filePath for the view and data model for the page. + */ + @Override + public ModelAndView initPage(final NoPageParams params, + final Model model) { + return getBaseModelAndView(); + } + + /** + * Returns the list of TPM Events using the data table input for paging, ordering, + * and filtering. + * + * @param input the data tables input + * @return the data tables response, including the result set and paging + * information + */ + @ResponseBody + @RequestMapping(value = "/list", + produces = MediaType.APPLICATION_JSON_VALUE, + method = RequestMethod.GET) + public List getTableData( + @Valid final DataTablesInput input) { + log.info("Handling request for summary list: " + input); + + return this.referenceEventManager.fetchDigestValues(); + + +// String orderColumnName = input.getOrderColumnName(); +// log.info("Ordering on column: " + orderColumnName); +// +// // check that the alert is not archived and that it is in the specified report +// CriteriaModifier criteriaModifier = new CriteriaModifier() { +// @Override +// public void modify(final Criteria criteria) { +// criteria.add(Restrictions.isNull(Certificate.ARCHIVE_FIELD)); +// } +// }; +// +// log.info("Querying with the following datatableinput: " + input.toString()); +// +// FilteredRecordsList referenceDigestValues = +// OrderedListQueryDataTableAdapter.getOrderedList( +// ReferenceDigestValue.class, +// referenceEventManager, +// input, orderColumnName, criteriaModifier); +// +// SupportReferenceManifest support; +// for (ReferenceDigestValue rdv : referenceDigestValues) { +// // We are updating the base rim ID field if necessary and +// if (rdv.getBaseRimId() == null) { +// support = SupportReferenceManifest.select(referenceManifestManager) +// .byEntityId(rdv.getSupportRimId()).getRIM(); +// if (support != null) { +// rdv.setBaseRimId(support.getAssociatedRim()); +// try { +// referenceEventManager.updateRefDigestValue(rdv); +// } catch (DBManagerException e) { +// log.error("Failed to update TPM Event with Base RIM ID"); +// log.error(rdv); +// } +// } +// } +// } +// +// return new DataTableResponse<>(referenceDigestValues, input); + } +} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/SupplyChainCredentialValidator.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/SupplyChainCredentialValidator.java new file mode 100644 index 00000000..b37c306d --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/utils/SupplyChainCredentialValidator.java @@ -0,0 +1,1765 @@ +package hirs.attestationca.portal.page.utils; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import hirs.attestationca.persist.entity.ArchivableEntity; +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.certificate.attributes.ComponentIdentifier; +import hirs.attestationca.persist.entity.userdefined.certificate.attributes.V2.ComponentIdentifierV2; +import hirs.attestationca.persist.entity.userdefined.info.ComponentInfo; +import hirs.attestationca.persist.entity.userdefined.info.HardwareInfo; +import hirs.attestationca.persist.entity.userdefined.report.DeviceInfoReport; +import hirs.attestationca.persist.enums.AppraisalStatus; +import hirs.attestationca.persist.validation.CredentialValidator; +import hirs.attestationca.persist.validation.SupplyChainValidatorException; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; +import org.bouncycastle.asn1.DERUTF8String; +import 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 org.springframework.stereotype.Service; + +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +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.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.ERROR; +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.FAIL; +import static hirs.attestationca.persist.enums.AppraisalStatus.Status.PASS; + +/** + * Validates elements of the supply chain. + */ +@Log4j2 +@NoArgsConstructor +@Service +public final class SupplyChainCredentialValidator implements CredentialValidator { + private static final int NUC_VARIABLE_BIT = 159; + + /** + * AppraisalStatus message for a valid endorsement credential appraisal. + */ + public static final String ENDORSEMENT_VALID = "Endorsement credential validated"; + + /** + * AppraisalStatus message for a valid platform credential appraisal. + */ + public static final String PLATFORM_VALID = "Platform credential validated"; + + /** + * AppraisalStatus message for a valid platform credential attributes appraisal. + */ + public static final String PLATFORM_ATTRIBUTES_VALID = + "Platform credential attributes validated"; + + /** + * AppraisalStatus message for a valid firmware appraisal. + */ + public static final String FIRMWARE_VALID = "Firmware validated"; + + /** + * Ensure that BouncyCastle is configured as a javax.security.Security provider, as this + * class expects it to be available. + */ + static { + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * 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 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) + || DeviceInfoReport.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) + || DeviceInfoReport.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) + || DeviceInfoReport.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_AttestationCAPortal/src/main/resources/portal.properties b/HIRS_AttestationCAPortal/src/main/resources/portal.properties new file mode 100644 index 00000000..3ec20bbe --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/resources/portal.properties @@ -0,0 +1,37 @@ +# General notes: +# Properties are processed using an expression processor. That said, properties can inherit the +# values of other properties. +# +# In example the processor will resolve the value of aca.both as 'hello world!'. +# aca.hello = hello +# aca.world = world! +# aca.both = ${aca.hello} ${aca.world} +# +# ACA Directories +# root: the root directory of ACA related files +# certificates: the directory for ACA certificate files +aca.directories.root = /etc/hirs/aca +aca.directories.certificates = ${aca.directories.root}/certificates + +# ACA certificate related properties. These are generic properties that apply to the creation of +# any certificate that the ACA is responsible for creating. +# validity: the number of days that credentials generated by the ACA are valid. +aca.certificates.validity = 3652 + +# ACA key store properties +# alias: the alias to reference the ACA key and certificate by +# location: the absolute path to the ACA key store. +# password: key store password +aca.keyStore.alias = HIRS_ACA_KEY +aca.keyStore.location = ${aca.directories.certificates}/keyStore.jks +aca.keyStore.password = + +# ACA setup/initialization properties. These properties are used exclusively by the ACA +# initialization process. Generally these properties do not need to be modified +# +# keySize: the default key size of the ACA key pair stored within the trust store +# subjectName: the CN of the generate X509 certificate +# expiration: the number of days that the generated X509 certificate will expire +aca.setup.keyStore.keySize = 2048 +aca.setup.keyStore.subjectName = HIRS_AttestationCA_Endorsement +aca.setup.keyStore.expiration = ${aca.certificates.validity} \ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/resources/swid_schema.xsd b/HIRS_AttestationCAPortal/src/main/resources/swid_schema.xsd deleted file mode 100644 index fd7e4b19..00000000 --- a/HIRS_AttestationCAPortal/src/main/resources/swid_schema.xsd +++ /dev/null @@ -1,1345 +0,0 @@ - - - - - This is the XML Schema for the Jakarta XML Binding binding customization descriptor. All binding customization descriptors must indicate the descriptor schema by using the Jakarta XML Binding namespace: https://jakarta.ee/xml/ns/jaxb and by indicating the version of the schema by using the version element as shown below: - ... - The instance documents may indicate the published version of the schema using the xsi:schemaLocation attribute for Jakarta XML Binding namespace with the following location: https://jakarta.ee/xml/ns/jaxb/bindingschema_3_0.xsd - - - - - - - Schema for ISO-IEC 19770-2 Software Identification Tags - http://standards.iso.org/iso/19770/-2/2015/schema.xsd - - Copyright 2015 ISO/IEC, all rights reserved - - Copyright notice: ISO and IEC grant the users of this Standard the right - to use this XSD file free of charge for the purpose of implementing the - present Standard. - - Disclaimer: In no event shall ISO and/or IEC be liable for any damages - whatsoever (including, but not limited to, damages for loss of profits, - business interruption, loss of information, or any other pecuniary - loss) arising out of or related to the use of or inability to use the - XSD file. ISO and IEC disclaim all warranties, express or implied, - including but not limited to warranties of merchantability and fitness - for a particular purpose. - - - 3.0 - - - - - - - Represents the root element specifying data about a software component - - - - - - - - Attributes common to all Elements in this schema - - - - - - - Allow xml:lang attribute on any element. - - - - - - - - Allows any undeclared attributes on any element as long as the - attribute is placed in a different namespace. - - - - - - - - - - - - Specifies the organizations related to the software component - referenced by this SWID tag. - - - - - - - - An open-ended collection of elements that can be used to attach - arbitrary metadata to an Entity. - - - - - - - - - The name of the organization claiming a particular role in the - SWID tag. - - - - - - - - The regid of the organization. If the regid is unknown, the - value "invalid.unavailable" is provided by default (see - RFC 6761 for more details on the default value). - - - - - - - - The relationship between this organization and this tag e.g. tag, - softwareCreator, licensor, tagCreator, etc. The role of - tagCreator is required for every SWID tag. - - EntityRole may include any role value, but the pre-defined roles - include: aggregator, distributor, licensor, softwareCreator, - tagCreator - - Other roles will be defined as the market uses the SWID tags. - - - - - - - - this value provides a hexadecimal string that contains a hash - (or thumbprint) of the signing entities certificate. - - - - - - - - - - - - - - The element is used to provide results from a scan of a system - where software that does not have a SWID tag is discovered. - This information is not provided by the software creator, and - is instead created when a system is being scanned and the - evidence for why software is believed to be installed on the - device is provided in the Evidence element. - - - - - - Date and time the evidence was gathered. - - - - - - - - Identifier for the device the evidence was gathered from. - - - - - - - - - - - - - - Represents an individual file - - - - - - - Files that are considered important or required for the use of - a software component. Typical key files would be those which, - if not available on a system, would cause the software not to - execute. - - Key files will typically be used to validate that software - referenced by the SWID tag is actually installed on a specific - computing device - - - - - - - - The directory or location where a file was found or can expected - to be located. does not include the filename itself. This can - be relative path from the 'root' attribute. - - - - - - - - The filename without any path characters - - - - - - - - A system-specific root folder that the 'location' - attribute is an offset from. If this is not specified - the assumption is the 'root' is the same folder as - the location of the SWIDTAG. - - - - - - - - Permits any user-defined attributes in file tags - - - - - - - - - - - - - Provides the ability to apply a directory structure to the files - defined in a Payload or Evidence element. - - - - - - - - A Directory element allows one or more directories to be - defined in the file structure. - - - - - - - - A File element that allows one or more files to be specified - for a given location. - - - - - - - - - - - - - - - Represents an individual file - - - - - - - The file size in bytes of the file - - - - - - - - The file version - - - - - - - - - - - - - - Provides process information for data that will show up in a - devices process table. - - - - - - - The process name as it will be found in the devices process - table. - - - - - - - - The process ID for the executing process - note that this will - typically only be provided when the Process element is included as part - of Evidence. - - - - - - - - - - - - - - A container that can be used to provide arbitrary resource - information about an application installed on a device, or - evidence collected from a device. - - - - - - - The type of resource (ie, registrykey, port, rootUrl) - - - - - - - - - - - - - - This type is used by Payload to provide details on what may rbe - installed on a device, and by Evidence to indicate what an - inventory process discovered on a device. - - - - - - - - One or more directory elements - - - - - - - - One or more File elements - - - - - - - - One or more Process elements - - - - - - - - One or more generic resource elements - - - - - - - - - - - - - - - A reference to any another item (can include details that are - related to the SWID tag such as details on where software - downloads can be found, vulnerability database associations, - use rights, etc). - - This is modeled directly to match the HTML [LINK] element; it is - critical for streamlining software discovery scenarios that - these are kept consistent. - - - - - - - For installation media (rel="installationmedia") - dictates the - canonical name for the file. - - Items with the same artifact name should be considered mirrors - of each other (so download from wherever works). - - - - - - - - The link to the item being referenced. - - The href can point to several different things, and can be any - of the following: - - - a RELATIVE URI (no scheme) - which is interpreted depending on - context (ie, "./folder/supplemental.swidtag" ) - - - a physical file location with any system-acceptable - URI scheme (ie, file:// http:// https:// ftp:// ... etc ) - - - an URI with "swid:" as the scheme, which refers to another - swid by tagId. This URI would need to be resolved in the - context of the system by software that can lookup other - swidtags.( ie, "swid:2df9de35-0aff-4a86-ace6-f7dddd1ade4c" ) - - - an URI with "swidpath:" as the scheme, which refers to another - swid by an XPATH query. This URI would need to be resolved in - the context of the system by software that can lookup other - swidtags, and select the appropriate one based on an XPATH - query. Examples: - - swidpath://SoftwareIdentity[Entity/@regid='http://contoso.com'] - would retrieve all swidtags that had an entity where the - regid was Contoso - - swidpath://SoftwareIdentity[Meta/@persistentId='b0c55172-38e9-4e36-be86-92206ad8eddb'] - would retrieve swidtags that matched the persistentId - - See XPATH query standard : http://www.w3.org/TR/xpath20/ - - - - - - - - An attribute defined by the W3C Media Queries Recommendation - (see http://www.w3.org/TR/css3-mediaqueries/). - - A hint to the consumer of the link to what the target item is - applicable for. - - - - - - - - Determines the relative strength of ownership of the target - piece of software. - - - - - - - - The relationship between this SWID and the target file. - - Relationships can be identified by referencing the IANA - registration library - - https://www.iana.org/assignments/link-relations/link-relations.xhtml. - - - - - - - - The IANA MediaType for the target file; this provides the - consumer with intelligence of what to expect. - - See http://www.iana.org/assignments/media-types/media-types.xhtml - for more details on link type. - - - - - - - - Determines if the target software is a hard requirement or not - - - - - - - - - - - - - - An open-ended collection of key/value data related to this SWID. - - - - - - - Permits any user-defined attributes in Meta tags - - - - - - - - - - - - - - - - Specifies the organizations related to the software component - referenced by this SWID tag. - - This has a minOccurs of 1 because the spec declares that - you must have at least a Entity with role='tagCreator' - - - - - - - - This element is used to provide results from a scan of a - system where software that does not have a SWID tag is - discovered. This information is not provided by the - software creator, but is instead created when a system - is being scanned and the evidence for why software is - believed to be installed on the device is provided in the - Evidence element. - - - - - - - - A reference to any another item (can include details that - are related to the SWID tag such as details on where software - downloads can be found, vulnerability database associations, - use rights, etc). - - Note: This is modelled directly to match the HTML [LINK] - element; it is critical for streamlining software discovery - scenarios that these are kept consistent. - - - - - - - - An open-ended collection of key/value data related to this SWID. - - - - - - - - The items that may be installed on a device when the software is - installed. Note that Payload may be a superset of the items - installed and, depending on optimization systems for a device, - may or may not include every item that could be created or - executed on a device when software is installed. - - In general, payload will be used to indicate the files that - may be installed with a software product and will often be a - superset of those files (i.e. if a particular optional - component is not installed, the files associated with that - component may be included in payload, but not installed on - the device). - - - - - - - - Allows any undeclared elements in the SoftwareIdentity element - as long as the element is placed in a different namespace. - - As xs:any supercedes an xs:element declaration, this continues - to support digital signatures using the ds:Signature element: - - Signatures are not a mandatory part of the software - identification tag standard, and can be used as required - by any tag producer to ensure that sections of a tag are not - modified and/or to provide authentication of the signer. If - signatures are included in the software identification tag, - they shall follow the W3C recommendation defining the XML - signature syntax which provides message integrity - authentication as well as signer authentication services for - data of any type. - - - - - - - - - - Set to true, if this attribute specifies that this SWID tag is a - collection of information that describes the pre-installation - data of software component. - - - - - - - - Set to true if this SWID describes a product patch or - modification to a different software element. - - - - - - - - media is a hint to the tag consumer to understand what this - SWID tag applies to (see the [Link] tags media attribute). - - - - - - - - This attribute provides the software component name as it would - typically be referenced. For example, what would be seen in the - add/remove dialog on a Windows device, or what is specified as - the name of a packaged software product or a patch identifier - name on a Linux device. - - - - - - - - Specifies that this tag provides supplemental tag data that can - be merged with primary tag data to create a complete record of - the software information. - - Supplemental tags will often be provided at install time and may - be provided by different entities (such as the tag consumer, or - a Value Added Reseller). - - - - - - - - tagId shall be a globally unique identifier and should be - assigned a GUID reference (see ISO/IEC 19770-5 definition - for GUID). - - The tagID provides a unique reference for the specific product, - version, edition, revision, etc (essentially, the same binary - distribution). If two tagIDs match and the tagCreator is the - same, the underlying products they represent are expected to be - exactly the same. - - This allows IT systems to identify if a software item (for - example, a patch) is installed simply by referencing the - specific tagID value which is likely to be readily available - in a software inventory. - - It is recommended, when possible, that a 16 byte GUID - be used for this field as this provides global uniqueness without - a significant amount of overhead for space. - - If use of a 16 byte GUID is not possible, a text based globally - unique ID may be constructed, this ID should include a unique - naming authority for the tagCreator and sufficient additional - details that the tagId is unique for the software product, - version, edition, revision, etc. This would likely look as - follows (+ is used as a string concatenation symbol): - - regid + productName + version + edition + revision + ... - - - - - - - - The tagVersion indicates if a specific release of a software - product has more than one tag that can represent that specific - release. This may be the case if a software tag producer creates - and releases an incorrect tag that they subsequently want to fix, - but with no underlying changes to the product the SWID tag - represents. This could happen if, for example, a patch is - distributed that has a Link reference that does not cover all the - various software releases it can patch. A newer SWID tag for that - patch can be generated and the tagVersion value incremented to - indicate that the data is updated. - - - - - - - - Underlying development version for the software component. - - - - - - - - Scheme used for the version number. Some possible common values are: - - value="alphanumeric" - Strictly a string, sorting alphanumericaly - - value="decimal" - A floating point number : ( ie, 1.25 is less than 1.3 ) - - value="multipartnumeric" - Numbers seperated by dots, where the numbers are interpreted as - integers (ie, 1.2.3 , 1.4.5.6 , 1.2.3.4.5.6.7 ) - - value="multipartnumeric+suffix" - Numbers seperated by dots, where the numbers are interpreted as - integers with an additional string suffix: (ie, 1.2.3a) - - value="semver" - Follows the semver.org spec - - value="unknown" - Unknown, no attempt should be made to order these - - - - - - - - - - - - - - An open-ended collection of key/value data related to this SWID. - - The attributes included in this Element are predefined attributes - to ensure common usage across the industry. The schema allows for - any additional attribute to be included in a SWID tag, though it is - recommended that industry norms for new attributes are defined and - followed to the degree possible. - - - - - - - Identification of the activation status of this software title - (e.g. Trial, Serialized, Licensed, Unlicensed, etc). Typically, - this is used in supplemental tags. - - - - - - - - Provides information on which channel this particular - software was targeted for (e.g. Volume, Retail, OEM, - Academic, etc). Typically used in supplemental tags. - - - - - - - - The informal or colloquial version of the product (i.e. 2013). - Note that this version may be the same through multiple releases - of a software product where the version specified in - SoftwareEntity is much more specific and will change for each - software release. - - Note that this representation of version is typically used to - identify a group of specific software releases that are part of - the same release/support infrastructure - (i.e. Fabrikam Office 2013). This version is used for string - comparisons only and is not compared to be an earlier or later - release (that is done via the SoftwareEntity version). - - - - - - - - A longer, detailed description of the software. This description - can be multiple sentences (differentiated from summary which is - a very short, one-sentence description). - - - - - - - - The variation of the product (Extended, Enterprise, Professional, - Standard etc) - - - - - - - - An indicator to determine if there should be accompanying proof - of entitlement when a software license reconciliation is - completed. - - - - - - - - A vendor-specific textual key that can be used to reconcile the - validity of an entitlement. (e.g. serial number, product or - license key). - - - - - - - - The name of the software tool that created a SWID tag. This - element is typically used if tags are created on the fly, or - based on a catalogue based analysis for data found on a - computing device. - - - - - - - - A GUID used to represent products installed where the products - are related, but may be different versions. See one - representation of this value through the use of what, in a - windows installation process is referred to as an upgradeCode - - http://msdn.microsoft.com/en-us/library/aa372375(v=vs.85).aspx - as one example of the use of this value. - - - - - - - - The base name of the product (e.g. Office, Creative Suites, - Websphere, etc). - - - - - - - - The overall product family this software belongs to. Product - family is not used to identify that a product is part of a - suite, but is instead used when a set of products that are all - related may be installed on multiple different devices. - - For example, an Enterprise backup system may consist of a backup - server, multiple different backup systems that support mail - servers, databases and ERP systems as well as individual software - items that backup client devices. In this case all software - titles that are part of the backup system would have the same - productFamily name so they can be grouped together in reporting - systems. - - - - - - - - The informal or colloquial representation of the sub-version of - the given product (ie, SP1, R2, RC1, Beta 2, etc). Note that the - SoftwareIdentity.version will provide very exact version details, - the revision is intended for use in environments where reporting - on the informal or colloquial representation of the software is - important (for example, if for a certain business process, an - organization recognizes that it must have ServicePack 1 or later - of a specific product installed on all devices, they can use the - revision data value to quickly identify any devices that do not - meet this requirement). - - Depending on how a software organizations distributes revisions, - this value could be specified in a primary (if distributed as an - upgrade) or supplemental (if distributed as a patch) SWID tag. - - - - - - - - A short (one-sentence) description of the software. - - - - - - - - An 8 digit code that provides UNSPSC classification of the - software product this SWID tag identifies. For more - information see, http://www.unspsc.org/ - - - - - - - - The version of the UNSPSC code used to define the UNSPSC code - value. For more information see, http://www.unspsc.org/. - - - - - - - - - - - An expression that the document evaluator can use to determine if the - target of the link is applicable to the current platform (the host - environment) - - Used as an optimization hint to notify a system that it can - ignore something when it's not likely to be used. - - The format of this string is modeled upon the MediaQuery definition at - http://www.w3.org/TR/css3-mediaqueries/ - - This is one or more EXPRESSIONs where the items are connected - with an OPERATOR: - - media="EXPRESSION [[OPERATOR] [EXPRESSION]...]" - - EXPRESSION is processed case-insensitive and defined either : - (ENVIRONMENT) - indicates the presence of the environment - or - ([PREFIX-]ENVIRONMENT.ATTRIBUTE:VALUE) - indicates a comparison of an attribute of the environment. - - ENVIRONMENT is a text identifier that specifies any software,hardware - feature or aspect of the system the software is intended to run in. - - Common ENVIRONMENTs include (but not limited to): - linux - windows - java - powershell - ios - chipset - peripheral - - ATTRIBUTE is a property of an ENVIRONMENT with a specific value. - Common attributes include (but not limited to): - version - vendor - architecture - - PREFIX is defined as one of: - MIN # property has a minimum value of VALUE - MAX # property has a maximum value of VALUE - - if a PREFIX is not provided, then the property should equal VALUE - - OPERATOR is defined of one of: - AND - NOT - - Examples: - media="(windows)" - # applies to only systems that identify themselves as 'Windows' - - media="(windows) not (windows.architecture:x64)" - # applies to only systems that identify - # themselves as windows and are not for an x64 cpu - - media="(windows) and (min-windows.version:6.1)" - # applies to systems that identify themselves as - # windows and at least version 6.1 - - media="(linux) and (linux.vendor:redhat) and (min-linux.kernelversion:3.0)" - # applies to systems that identify themselves as - # linux, made by redhat and with a kernel version of at least 3.0 - - media="(freebsd) and (min-freebsd.kernelversion:6.6)" - # applies to systems that identify themselves as - # freebsd, with a kernel version of at least 6.6 - - media="(powershell) and (min-powershell.version:3.0)" - # applies to systems that have powershell 3.0 or greater - - Properties are expected to be able to be resolved by the host - environment without having to do significant computation. - - - - - - - - - The IANA MediaType for the target href; this provides the SWID tag - consumer with intelligence of what to expect. - - See http://www.iana.org/assignments/media-types/media-types.xhtml - for more details on link type. - - - - - - - - - - - - Determines the relative strength of ownership of the target - piece of software. - - - - - - - - If this is uninstalled, then the [Link]'d software should be removed - too. - - - - - - - - If this is uninstalled, then the [Link]'d software should be removed - if nobody else is sharing it - - - - - - - - - - - Determines if the target software is a hard requirement. - - - - - - - - The [Link]'d software is absolutely required for installation - - - - - - - - Not absolutely required, but install unless directed not to - - - - - - - - Not absolutely required, install only when asked - - - - - - - - \ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp new file mode 100644 index 00000000..27d728f0 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/certificate-details.jsp @@ -0,0 +1,964 @@ +<%@ page contentType="text/html" pageEncoding="UTF-8"%> +<%-- JSP TAGS--%> +<%@taglib prefix="c" uri="jakarta.tags.core" %> +<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> + +<%@taglib prefix="my" tagdir="/WEB-INF/tags"%> + +<%--CONTENT--%> + + + + + + + + Certificate Authority + + + + + + Endorsement Certificate + + + + + + Platform Certificate + + + + + + Issued Attestation Certificates + + + + + + Unknown Certificate + + + + + +
+
+
Issuer
+
+ +
Distinguished Name:  + + + + ${initialData.issuer} + + + + ${initialData.issuer} + + + +
+
Authority Key Identifier:  + +
+ +
Authority Info Access:  + ${initialData.authInfoAccess} + +
+
+ +
Authority Serial Number:  + +
+
+ + + + + + + + + + + + + + + + +
+
+ +
+
Subject
+
${initialData.subject}
+
+
+ +
+
Serial Number
+
+
+
+ +
+
Validity
+
+
Not Before: ${initialData.beginValidity}
+
Not After: ${initialData.endValidity}
+
+
+
+
+
Signature
+
+
+ + +
+
+ + +
+
+
+
+ +
+
Public Key
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
X509 Credential Version
+
${initialData.x509Version} (v${initialData.x509Version + 1})
+
+
+
Credential Type
+
${initialData.credentialType}
+
+ + + + + +
+
Subject Key Identifier
+
+
+
+
+ +
+
Revocation Locator
+ +
+
+
+
Key Usage
+ + +
${initialData.keyUsage}
+
+ +
Not Specified
+
+
+
+ + +
+
Extended Key Usage
+
${initialData.extendedKeyUsage}
+
+
+
+
+ +
+
System Information
+
+
Manufacturer: ${initialData.manufacturer}
+
Model: ${initialData.model}
+
Version: ${initialData.version}
+
+
+
+
Policy Reference
+
+ + + ${initialData.policyReference} + + + Not Specified + + +
+
+ +
+ +
+
Key Usage
+ + +
${initialData.keyUsage}
+
+ +
Not Specified
+
+
+
+ + +
+
Extended Key Usage
+
${initialData.extendedKeyUsage}
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + + +
+
Platform Type
+
${initialData.platformType}
+
+
+
Platform Chain
+
+ + + + + ${loop.index}  + + + ${loop.index}  + + + + +
+
+
+ +
+
Certification Practice Statement URI
+ +
+
+
+
Holder
+
+ +
Holder Certificate: ${initialData.holderIssuer}
+
+
+
Holder Identifier:  + + + + + + + ${initialData.holderSerialNumber} + + + + + ${initialData.holderSerialNumber} + + + + + + + ${initialData.holderSerialNumber} + + +
+
+
+
+
+
System Platform Information
+
+
Manufacturer: ${initialData.manufacturer}
+
Model: ${initialData.model}
+
Version: ${initialData.version}
+
Serial Number: ${initialData.platformSerial}
+
+
+
+
TCG Platform Specification Version
+
${initialData.majorVersion}.${initialData.minorVersion}.${initialData.revisionLevel}
+
+
+
Platform Class
+
${initialData.platformClass}
+
+ + +
+
TBB Security Assertion
+
+
+ Version: + ${initialData.tbbSecurityAssertion.getVersion()} (v${initialData.tbbSecurityAssertion.getVersion().getValue() + 1}) +
+
+ RTM (Root of Trust of Measurement): + ${fn:toUpperCase(initialData.tbbSecurityAssertion.getRtmType().getValue())} +
+ + + +
+ +
+
+
+
+ Version: + ${ccinfo.getVersion()} +
+
+ Assurance Level: + ${fn:toUpperCase(ccinfo.getAssurancelevel().getValue())} +
+
+ Evaluation Status: + ${fn:toUpperCase(ccinfo.getEvaluationStatus().getValue())} +
+
+ + + Plus + + + Not Plus + + +
+
+ Strength of Function: + ${fn:toUpperCase(ccinfo.getStrengthOfFunction().getValue())} +
+ +
+ Profile OID: + ${ccinfo.getProfileOid()} +
+
+ + + +
+ Profile Hash Algorithm: + ${ccinfo.getProfileUri().getHashAlgorithm()} +
+
+ +
+ Profile Hash Value: + ${ccinfo.getProfileUri().getHashValue()} +
+
+
+ +
+ Target OID: + ${ccinfo.getTargetOid()} +
+
+ + + +
+ Target Hash Algorithm: + ${ccinfo.getTargetUri().getHashAlgorithm()} +
+
+ +
+ Target Hash Value: + ${ccinfo.getTargetUri().getHashValue()} +
+
+
+
+
+
+
+
+ + + +
+ +
+
+
+
+ Version: + ${fipslevel.getVersion()} +
+
+ Level: + ${fn:toUpperCase(fipslevel.getLevel().getValue())} +
+
+ + + Plus + + + Not Plus + + +
+
+
+
+
+
+ +
+ +
+
+
+
+ + + ISO 9000 Certified + + + ISO 9000 Not Certified + + +
+ + + +
+
+
+
+
+
+
+ + +
+
TCG Platform Configuration
+
+ + +
+ +
+
+
+ + +
+
+ + +
+ + +
+ + + + + ${component.getComponentClass()} + + + Platform Components + + +
+
+ Manufacturer: + ${component.getComponentManufacturer()}
+ Model: + ${component.getComponentModel()}
+ + Serial Number: + ${component.getComponentSerial()}
+
+ + Revision: + ${component.getComponentRevision()}
+
+ + ${address.getAddressTypeValue()} address: + ${address.getAddressValue()}
+
+ + + Replaceable
+
+ + Irreplaceable
+
+
+ + + Platform Certificate Issuer: + ${component.getCertificateIdentifier().getIssuerDN()}
+ Platform Certificate Serial Number: + ${component.getCertificateIdentifier().getCertificateSerialNumber()}
+ Platform Certificate URI: +
+ + + ${component.getComponentPlatformUri().getUniformResourceIdentifier()} + +
+ Status: + ${component.getAttributeStatus()}
+
+
+
+
+ +
+
+
+
+ + + +
+ +
+
+
+ URI: + + ${initialData.componentsIdentifierURI.getUniformResourceIdentifier()} + + + Hash Algorithm: + ${initialData.componentsIdentifierURI.getHashAlgorithm()} + + + Hash Value: + ${initialData.componentsIdentifierURI.getHashValue()} + +
+
+
+
+
+ + +
+ +
+
+
+ +
+
+
+ Name: + ${property.getPropertyName()}
+ Value: + ${property.getPropertyValue()}
+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+
+ URI: + + ${initialData.platformPropertiesURI.getUniformResourceIdentifier()} + + + Hash Algorithm: + ${initialData.platformPropertiesURI.getHashAlgorithm()} + + + Hash Value: + ${initialData.platformPropertiesURI.getHashValue()} + +
+
+
+
+
+
+
+ + + +
+
System Information
+
+
Manufacturer: ${initialData.manufacturer}
+
Model: ${initialData.model}
+
Version: ${initialData.version}
+
Serial Number: ${initialData.platformSerial}
+
+
+
+
Policy Reference
+
+ + + ${initialData.policyReference} + + + Not Specified + + +
+
+ +
+
Revocation Locator
+ +
+
+ +
+
Platform Credentials
+
+ + + + + + + +
+
+
+
TCG Platform Specification Version
+
${initialData.majorVersion}.${initialData.minorVersion}.${initialData.revisionLevel}
+
+
+
TCG Credential Specification Version
+
${initialData.tcgMajorVersion}.${initialData.tcgMinorVersion}.${initialData.tcgRevisionLevel}
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/issued-certificates.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/issued-certificates.jsp new file mode 100644 index 00000000..bd8052e2 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/issued-certificates.jsp @@ -0,0 +1,131 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + +<%-- JSP TAGS --%> +<%@taglib prefix="c" uri="jakarta.tags.core" %> +<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@taglib prefix="my" tagdir="/WEB-INF/tags"%> + +<%-- CONTENT --%> + + + + + + Issued Certificates + +
+ Issued Credentials + + + +
+
+
+ + + + + + + + + + + + + + + +
HostnameIssuerValid (begin)Valid (end)CredentialsOptions
EndorsementPlatform
+
+ +
+ +
\ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/platform-credentials.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/platform-credentials.jsp new file mode 100644 index 00000000..88dbdffc --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/platform-credentials.jsp @@ -0,0 +1,136 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + +<%-- JSP TAGS --%> +<%@taglib prefix="c" uri="jakarta.tags.core" %> +<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@taglib prefix="my" tagdir="/WEB-INF/tags"%> + +<%-- CONTENT --%> + + + + + + Platform Certificates + + + + +
+ + Platform Credentials + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
DeviceIssuerTypeManufacturerModelVersionBoard SNValid (begin)Valid (end)EndorsementOptions
+
+ + +
+
\ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/reference-manifests.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/reference-manifests.jsp new file mode 100644 index 00000000..0bc1d0fb --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/reference-manifests.jsp @@ -0,0 +1,78 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + +<%-- JSP TAGS --%> +<%@taglib prefix="c" uri="jakarta.tags.core" %> +<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@taglib prefix="my" tagdir="/WEB-INF/tags"%> + +<%-- CONTENT --%> + + + + + + Reference Integrity Manifests + + + +
+ + Reference Integrity Manifests + + + + + + + +
+
+
+ + + + + + + + + + + +
Tag IDTypeManufacturerModelVersionOptions
+
+ + +
+
\ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-database.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-database.jsp new file mode 100644 index 00000000..36e3a10f --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-database.jsp @@ -0,0 +1,75 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + +<%-- JSP TAGS --%> +<%@taglib prefix="c" uri="jakarta.tags.core" %> +<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@taglib prefix="my" tagdir="/WEB-INF/tags"%> + +<%-- CONTENT --%> + + + + + + RIM Database + + +
+
+ + + + + + + + + + + + +
ManufacturerModelEvent TypePCR IndexDigest ValueBase RIMSupport RIM
+
+ + +
+
\ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-details.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-details.jsp new file mode 100644 index 00000000..f55dc3d7 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/rim-details.jsp @@ -0,0 +1,653 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + +<%-- JSP TAGS--%> +<%@taglib prefix="c" uri="jakarta.tags.core" %> +<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> + +<%@taglib prefix="my" tagdir="/WEB-INF/tags"%> + +<%--CONTENT--%> + + + + + + + + + + TCG Log events + + + TCG Log event(s) not found in the RIM DB + + + + ${initialData.rimType} Reference Integrity Manifest + + + + + + + + + + + + + +
+ + +
+
Additional
RIM Info
+
+ + + + ${initialData.tagId} + + +
Device: ${initialData.hostName}
+
+ + + +
+ +
RIM not uploaded from the ACA RIM Page
+
+
+
+
+ +
+
+ RIM Type +
+
+ +
SWID Corpus
+
+ +
SWID Patch
+
+ +
SWID Supplemental
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Event #PCR IndexEvent TypeDigestEvent Content
${count}PCR${event.getPcrIndex()}${event.getEventTypeStr()}${event.getEventDigestStr()}
${event.getEventContentStr()}
+
+
${initialData.events.size()} entries
+
+ +
+
+
Base/Support
+
+
Download Measurement:  + + + BIOS Measurements + + +
+ +
Device: ${initialData.hostName} +
+
+ + + + + + +
+
+
+
+
+
Client Log
+ + + +
+
+
Failed Event Digest:
+
+
+ PCR Index: ${lEvent.getPcrIndex()}
+ Digest: ${lEvent.getEventDigestStr()}
+ Event Content: ${lEvent.getEventContentStr()} +
+
+
+
+
+ Expected Events from RIM DB:
+ ${lEvent.getEventTypeString()} +
+
+ + + + +
+
PCR Index: ${event.getPcrIndex()}
+
Digest: ${event.getEventDigestStr()}
+
Event Content: ${event.getEventContentStr()}
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
Software Identity
+
+
SWID Name: ${initialData.swidName}
+
SWID Version: ${initialData.swidVersion}
+
SWID Tag ID: ${initialData.swidTagId}
+
SWID Tag Version: ${initialData.swidTagVersion}
+ +
SWID Corpus:  +
+
+ +
SWID Patch:  +
+
+ +
SWID Supplemental:  +
+
+
+
+
+
Entity
+
+
Entity Name: ${initialData.entityName}
+ +
Entity Reg ID: ${initialData.entityRegId}
+
+
Entity Role: ${initialData.entityRole}
+
Entity Thumbprint: ${initialData.entityThumbprint}
+
+
+
+
Link
+ +
+
+
Meta
+ +
+
Payload/Support RIM(s)
+
+
+ +
+
+ +
+ +
+ +
+
+
+ + + + ${resource.getName()} + + + + + + + + + + + ${resource.getName()} + + + +
+
+ File Size: + ${resource.getSize()}
+ Hash: + ${resource.getHashValue()}
+ + RIM Format: + ${resource.getRimFormat()}
+
+ + RIM Type: + ${resource.getRimType()}
+
+ + URI Global: + ${resource.getRimUriGlobal()}
+
+
+ + +
+
+ +
+
+ + +
+
+ PCR ${count} - + ${pcrValue} +
+
+ +
+
+
+
+
+
+ + +
Support RIM file named ${resource.getName()} was not imported via the Reference Integrity Manifest page.
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+
Signature
+
+
Validity:  + + + + + + + + + +
+
+ + + + + +
+
+ + +
Subject Key Identifier: ${initialData.skID}
+
+
+
+
+
+
+ + +
+
+ + + +
+
\ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/trust-chain.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/trust-chain.jsp new file mode 100644 index 00000000..4ae7a3e1 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/trust-chain.jsp @@ -0,0 +1,155 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + +<%-- JSP TAGS --%> +<%@taglib prefix="c" uri="jakarta.tags.core" %> +<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@taglib prefix="my" tagdir="/WEB-INF/tags"%> + +<%-- CONTENT --%> + + + + + Trust Chain Management + + + + HIRS Attestation CA Certificate + + +
+
+
Issuer
+
+ + + + + ${acaCertData.issuer} + + + + ${acaCertData.issuer} + + +
+
+ +
+
Subject
+
${acaCertData.subject}
+
+
+
+
Serial Number
+
${acaCertData.serialNumber}
+
+
+
Validity
+
+
Not Before: ${acaCertData.beginValidity}
+
Not After: ${acaCertData.endValidity}
+
+
+
+
Signature
+
+
+ +
+
Public Key
+
+
+
+
+
+
+ + + + +
+ + Trust Chain CA Certificates + + + + + + + +
+
+
+ + + + + + + + + + +
IssuerSubjectValid (begin)Valid (end)Options
+
+ +
+ +
\ No newline at end of file diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/validation-reports.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/validation-reports.jsp new file mode 100644 index 00000000..a42b1336 --- /dev/null +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/validation-reports.jsp @@ -0,0 +1,256 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + +<%-- JSP TAGS --%> +<%@taglib prefix="c" uri="jakarta.tags.core" %> +<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@taglib prefix="my" tagdir="/WEB-INF/tags"%> + +<%-- CONTENT --%> + + + + + + Validation Reports + + + + + + + + + + + + + + Download Validation Reports + + + +
+ + +
+
+ +
+ + + + + + + + + + + + + + +
ResultTimestampDeviceCredential Validations
EndorsementPlatformFirmware
+
+ +
+ +
\ No newline at end of file diff --git a/HIRS_Utils/src/main/resources/swid_schema.xsd b/HIRS_Utils/src/main/resources/swid_schema.xsd new file mode 100644 index 00000000..92ef54b2 --- /dev/null +++ b/HIRS_Utils/src/main/resources/swid_schema.xsd @@ -0,0 +1,1345 @@ + + + + + This is the XML Schema for the Jakarta XML Binding binding customization descriptor. All binding customization descriptors must indicate the descriptor schema by using the Jakarta XML Binding namespace: https://jakarta.ee/xml/ns/jaxb and by indicating the version of the schema by using the version element as shown below: + ... + The instance documents may indicate the published version of the schema using the xsi:schemaLocation attribute for Jakarta XML Binding namespace with the following location: https://jakarta.ee/xml/ns/jaxb/bindingschema_3_0.xsd + + + + + + + Schema for ISO-IEC 19770-2 Software Identification Tags + http://standards.iso.org/iso/19770/-2/2015/schema.xsd + + Copyright 2015 ISO/IEC, all rights reserved + + Copyright notice: ISO and IEC grant the users of this Standard the right + to use this XSD file free of charge for the purpose of implementing the + present Standard. + + Disclaimer: In no event shall ISO and/or IEC be liable for any damages + whatsoever (including, but not limited to, damages for loss of profits, + business interruption, loss of information, or any other pecuniary + loss) arising out of or related to the use of or inability to use the + XSD file. ISO and IEC disclaim all warranties, express or implied, + including but not limited to warranties of merchantability and fitness + for a particular purpose. + + + 3.0 + + + + + + + Represents the root element specifying data about a software component + + + + + + + + Attributes common to all Elements in this schema + + + + + + + Allow xml:lang attribute on any element. + + + + + + + + Allows any undeclared attributes on any element as long as the + attribute is placed in a different namespace. + + + + + + + + + + + + Specifies the organizations related to the software component + referenced by this SWID tag. + + + + + + + + An open-ended collection of elements that can be used to attach + arbitrary metadata to an Entity. + + + + + + + + + The name of the organization claiming a particular role in the + SWID tag. + + + + + + + + The regid of the organization. If the regid is unknown, the + value "invalid.unavailable" is provided by default (see + RFC 6761 for more details on the default value). + + + + + + + + The relationship between this organization and this tag e.g. tag, + softwareCreator, licensor, tagCreator, etc. The role of + tagCreator is required for every SWID tag. + + EntityRole may include any role value, but the pre-defined roles + include: aggregator, distributor, licensor, softwareCreator, + tagCreator + + Other roles will be defined as the market uses the SWID tags. + + + + + + + + this value provides a hexadecimal string that contains a hash + (or thumbprint) of the signing entities certificate. + + + + + + + + + + + + + + The element is used to provide results from a scan of a system + where software that does not have a SWID tag is discovered. + This information is not provided by the software creator, and + is instead created when a system is being scanned and the + evidence for why software is believed to be installed on the + device is provided in the Evidence element. + + + + + + Date and time the evidence was gathered. + + + + + + + + Identifier for the device the evidence was gathered from. + + + + + + + + + + + + + + Represents an individual file + + + + + + + Files that are considered important or required for the use of + a software component. Typical key files would be those which, + if not available on a system, would cause the software not to + execute. + + Key files will typically be used to validate that software + referenced by the SWID tag is actually installed on a specific + computing device + + + + + + + + The directory or location where a file was found or can expected + to be located. does not include the filename itself. This can + be relative path from the 'root' attribute. + + + + + + + + The filename without any path characters + + + + + + + + A system-specific root folder that the 'location' + attribute is an offset from. If this is not specified + the assumption is the 'root' is the same folder as + the location of the SWIDTAG. + + + + + + + + Permits any user-defined attributes in file tags + + + + + + + + + + + + + Provides the ability to apply a directory structure to the files + defined in a Payload or Evidence element. + + + + + + + + A Directory element allows one or more directories to be + defined in the file structure. + + + + + + + + A File element that allows one or more files to be specified + for a given location. + + + + + + + + + + + + + + + Represents an individual file + + + + + + + The file size in bytes of the file + + + + + + + + The file version + + + + + + + + + + + + + + Provides process information for data that will show up in a + devices process table. + + + + + + + The process name as it will be found in the devices process + table. + + + + + + + + The process ID for the executing process - note that this will + typically only be provided when the Process element is included as part + of Evidence. + + + + + + + + + + + + + + A container that can be used to provide arbitrary resource + information about an application installed on a device, or + evidence collected from a device. + + + + + + + The type of resource (ie, registrykey, port, rootUrl) + + + + + + + + + + + + + + This type is used by Payload to provide details on what may rbe + installed on a device, and by Evidence to indicate what an + inventory process discovered on a device. + + + + + + + + One or more directory elements + + + + + + + + One or more File elements + + + + + + + + One or more Process elements + + + + + + + + One or more generic resource elements + + + + + + + + + + + + + + + A reference to any another item (can include details that are + related to the SWID tag such as details on where software + downloads can be found, vulnerability database associations, + use rights, etc). + + This is modeled directly to match the HTML [LINK] element; it is + critical for streamlining software discovery scenarios that + these are kept consistent. + + + + + + + For installation media (rel="installationmedia") - dictates the + canonical name for the file. + + Items with the same artifact name should be considered mirrors + of each other (so download from wherever works). + + + + + + + + The link to the item being referenced. + + The href can point to several different things, and can be any + of the following: + + - a RELATIVE URI (no scheme) - which is interpreted depending on + context (ie, "./folder/supplemental.swidtag" ) + + - a physical file location with any system-acceptable + URI scheme (ie, file:// http:// https:// ftp:// ... etc ) + + - an URI with "swid:" as the scheme, which refers to another + swid by tagId. This URI would need to be resolved in the + context of the system by software that can lookup other + swidtags.( ie, "swid:2df9de35-0aff-4a86-ace6-f7dddd1ade4c" ) + + - an URI with "swidpath:" as the scheme, which refers to another + swid by an XPATH query. This URI would need to be resolved in + the context of the system by software that can lookup other + swidtags, and select the appropriate one based on an XPATH + query. Examples: + + swidpath://SoftwareIdentity[Entity/@regid='http://contoso.com'] + would retrieve all swidtags that had an entity where the + regid was Contoso + + swidpath://SoftwareIdentity[Meta/@persistentId='b0c55172-38e9-4e36-be86-92206ad8eddb'] + would retrieve swidtags that matched the persistentId + + See XPATH query standard : http://www.w3.org/TR/xpath20/ + + + + + + + + An attribute defined by the W3C Media Queries Recommendation + (see http://www.w3.org/TR/css3-mediaqueries/). + + A hint to the consumer of the link to what the target item is + applicable for. + + + + + + + + Determines the relative strength of ownership of the target + piece of software. + + + + + + + + The relationship between this SWID and the target file. + + Relationships can be identified by referencing the IANA + registration library - + https://www.iana.org/assignments/link-relations/link-relations.xhtml. + + + + + + + + The IANA MediaType for the target file; this provides the + consumer with intelligence of what to expect. + + See http://www.iana.org/assignments/media-types/media-types.xhtml + for more details on link type. + + + + + + + + Determines if the target software is a hard requirement or not + + + + + + + + + + + + + + An open-ended collection of key/value data related to this SWID. + + + + + + + Permits any user-defined attributes in Meta tags + + + + + + + + + + + + + + + + Specifies the organizations related to the software component + referenced by this SWID tag. + + This has a minOccurs of 1 because the spec declares that + you must have at least a Entity with role='tagCreator' + + + + + + + + This element is used to provide results from a scan of a + system where software that does not have a SWID tag is + discovered. This information is not provided by the + software creator, but is instead created when a system + is being scanned and the evidence for why software is + believed to be installed on the device is provided in the + Evidence element. + + + + + + + + A reference to any another item (can include details that + are related to the SWID tag such as details on where software + downloads can be found, vulnerability database associations, + use rights, etc). + + Note: This is modelled directly to match the HTML [LINK] + element; it is critical for streamlining software discovery + scenarios that these are kept consistent. + + + + + + + + An open-ended collection of key/value data related to this SWID. + + + + + + + + The items that may be installed on a device when the software is + installed. Note that Payload may be a superset of the items + installed and, depending on optimization systems for a device, + may or may not include every item that could be created or + executed on a device when software is installed. + + In general, payload will be used to indicate the files that + may be installed with a software product and will often be a + superset of those files (i.e. if a particular optional + component is not installed, the files associated with that + component may be included in payload, but not installed on + the device). + + + + + + + + Allows any undeclared elements in the SoftwareIdentity element + as long as the element is placed in a different namespace. + + As xs:any supercedes an xs:element declaration, this continues + to support digital signatures using the ds:Signature element: + + Signatures are not a mandatory part of the software + identification tag standard, and can be used as required + by any tag producer to ensure that sections of a tag are not + modified and/or to provide authentication of the signer. If + signatures are included in the software identification tag, + they shall follow the W3C recommendation defining the XML + signature syntax which provides message integrity + authentication as well as signer authentication services for + data of any type. + + + + + + + + + + Set to true, if this attribute specifies that this SWID tag is a + collection of information that describes the pre-installation + data of software component. + + + + + + + + Set to true if this SWID describes a product patch or + modification to a different software element. + + + + + + + + media is a hint to the tag consumer to understand what this + SWID tag applies to (see the [Link] tags media attribute). + + + + + + + + This attribute provides the software component name as it would + typically be referenced. For example, what would be seen in the + add/remove dialog on a Windows device, or what is specified as + the name of a packaged software product or a patch identifier + name on a Linux device. + + + + + + + + Specifies that this tag provides supplemental tag data that can + be merged with primary tag data to create a complete record of + the software information. + + Supplemental tags will often be provided at install time and may + be provided by different entities (such as the tag consumer, or + a Value Added Reseller). + + + + + + + + tagId shall be a globally unique identifier and should be + assigned a GUID reference (see ISO/IEC 19770-5 definition + for GUID). + + The tagID provides a unique reference for the specific product, + version, edition, revision, etc (essentially, the same binary + distribution). If two tagIDs match and the tagCreator is the + same, the underlying products they represent are expected to be + exactly the same. + + This allows IT systems to identify if a software item (for + example, a patch) is installed simply by referencing the + specific tagID value which is likely to be readily available + in a software inventory. + + It is recommended, when possible, that a 16 byte GUID + be used for this field as this provides global uniqueness without + a significant amount of overhead for space. + + If use of a 16 byte GUID is not possible, a text based globally + unique ID may be constructed, this ID should include a unique + naming authority for the tagCreator and sufficient additional + details that the tagId is unique for the software product, + version, edition, revision, etc. This would likely look as + follows (+ is used as a string concatenation symbol): + + regid + productName + version + edition + revision + ... + + + + + + + + The tagVersion indicates if a specific release of a software + product has more than one tag that can represent that specific + release. This may be the case if a software tag producer creates + and releases an incorrect tag that they subsequently want to fix, + but with no underlying changes to the product the SWID tag + represents. This could happen if, for example, a patch is + distributed that has a Link reference that does not cover all the + various software releases it can patch. A newer SWID tag for that + patch can be generated and the tagVersion value incremented to + indicate that the data is updated. + + + + + + + + Underlying development version for the software component. + + + + + + + + Scheme used for the version number. Some possible common values are: + + value="alphanumeric" + Strictly a string, sorting alphanumericaly + + value="decimal" + A floating point number : ( ie, 1.25 is less than 1.3 ) + + value="multipartnumeric" + Numbers seperated by dots, where the numbers are interpreted as + integers (ie, 1.2.3 , 1.4.5.6 , 1.2.3.4.5.6.7 ) + + value="multipartnumeric+suffix" + Numbers seperated by dots, where the numbers are interpreted as + integers with an additional string suffix: (ie, 1.2.3a) + + value="semver" + Follows the semver.org spec + + value="unknown" + Unknown, no attempt should be made to order these + + + + + + + + + + + + + + An open-ended collection of key/value data related to this SWID. + + The attributes included in this Element are predefined attributes + to ensure common usage across the industry. The schema allows for + any additional attribute to be included in a SWID tag, though it is + recommended that industry norms for new attributes are defined and + followed to the degree possible. + + + + + + + Identification of the activation status of this software title + (e.g. Trial, Serialized, Licensed, Unlicensed, etc). Typically, + this is used in supplemental tags. + + + + + + + + Provides information on which channel this particular + software was targeted for (e.g. Volume, Retail, OEM, + Academic, etc). Typically used in supplemental tags. + + + + + + + + The informal or colloquial version of the product (i.e. 2013). + Note that this version may be the same through multiple releases + of a software product where the version specified in + SoftwareEntity is much more specific and will change for each + software release. + + Note that this representation of version is typically used to + identify a group of specific software releases that are part of + the same release/support infrastructure + (i.e. Fabrikam Office 2013). This version is used for string + comparisons only and is not compared to be an earlier or later + release (that is done via the SoftwareEntity version). + + + + + + + + A longer, detailed description of the software. This description + can be multiple sentences (differentiated from summary which is + a very short, one-sentence description). + + + + + + + + The variation of the product (Extended, Enterprise, Professional, + Standard etc) + + + + + + + + An indicator to determine if there should be accompanying proof + of entitlement when a software license reconciliation is + completed. + + + + + + + + A vendor-specific textual key that can be used to reconcile the + validity of an entitlement. (e.g. serial number, product or + license key). + + + + + + + + The name of the software tool that created a SWID tag. This + element is typically used if tags are created on the fly, or + based on a catalogue based analysis for data found on a + computing device. + + + + + + + + A GUID used to represent products installed where the products + are related, but may be different versions. See one + representation of this value through the use of what, in a + windows installation process is referred to as an upgradeCode + - http://msdn.microsoft.com/en-us/library/aa372375(v=vs.85).aspx + as one example of the use of this value. + + + + + + + + The base name of the product (e.g. Office, Creative Suites, + Websphere, etc). + + + + + + + + The overall product family this software belongs to. Product + family is not used to identify that a product is part of a + suite, but is instead used when a set of products that are all + related may be installed on multiple different devices. + + For example, an Enterprise backup system may consist of a backup + server, multiple different backup systems that support mail + servers, databases and ERP systems as well as individual software + items that backup client devices. In this case all software + titles that are part of the backup system would have the same + productFamily name so they can be grouped together in reporting + systems. + + + + + + + + The informal or colloquial representation of the sub-version of + the given product (ie, SP1, R2, RC1, Beta 2, etc). Note that the + SoftwareIdentity.version will provide very exact version details, + the revision is intended for use in environments where reporting + on the informal or colloquial representation of the software is + important (for example, if for a certain business process, an + organization recognizes that it must have ServicePack 1 or later + of a specific product installed on all devices, they can use the + revision data value to quickly identify any devices that do not + meet this requirement). + + Depending on how a software organizations distributes revisions, + this value could be specified in a primary (if distributed as an + upgrade) or supplemental (if distributed as a patch) SWID tag. + + + + + + + + A short (one-sentence) description of the software. + + + + + + + + An 8 digit code that provides UNSPSC classification of the + software product this SWID tag identifies. For more + information see, http://www.unspsc.org/ + + + + + + + + The version of the UNSPSC code used to define the UNSPSC code + value. For more information see, http://www.unspsc.org/. + + + + + + + + + + + An expression that the document evaluator can use to determine if the + target of the link is applicable to the current platform (the host + environment) + + Used as an optimization hint to notify a system that it can + ignore something when it's not likely to be used. + + The format of this string is modeled upon the MediaQuery definition at + http://www.w3.org/TR/css3-mediaqueries/ + + This is one or more EXPRESSIONs where the items are connected + with an OPERATOR: + + media="EXPRESSION [[OPERATOR] [EXPRESSION]...]" + + EXPRESSION is processed case-insensitive and defined either : + (ENVIRONMENT) + indicates the presence of the environment + or + ([PREFIX-]ENVIRONMENT.ATTRIBUTE:VALUE) + indicates a comparison of an attribute of the environment. + + ENVIRONMENT is a text identifier that specifies any software,hardware + feature or aspect of the system the software is intended to run in. + + Common ENVIRONMENTs include (but not limited to): + linux + windows + java + powershell + ios + chipset + peripheral + + ATTRIBUTE is a property of an ENVIRONMENT with a specific value. + Common attributes include (but not limited to): + version + vendor + architecture + + PREFIX is defined as one of: + MIN # property has a minimum value of VALUE + MAX # property has a maximum value of VALUE + + if a PREFIX is not provided, then the property should equal VALUE + + OPERATOR is defined of one of: + AND + NOT + + Examples: + media="(windows)" + # applies to only systems that identify themselves as 'Windows' + + media="(windows) not (windows.architecture:x64)" + # applies to only systems that identify + # themselves as windows and are not for an x64 cpu + + media="(windows) and (min-windows.version:6.1)" + # applies to systems that identify themselves as + # windows and at least version 6.1 + + media="(linux) and (linux.vendor:redhat) and (min-linux.kernelversion:3.0)" + # applies to systems that identify themselves as + # linux, made by redhat and with a kernel version of at least 3.0 + + media="(freebsd) and (min-freebsd.kernelversion:6.6)" + # applies to systems that identify themselves as + # freebsd, with a kernel version of at least 6.6 + + media="(powershell) and (min-powershell.version:3.0)" + # applies to systems that have powershell 3.0 or greater + + Properties are expected to be able to be resolved by the host + environment without having to do significant computation. + + + + + + + + + The IANA MediaType for the target href; this provides the SWID tag + consumer with intelligence of what to expect. + + See http://www.iana.org/assignments/media-types/media-types.xhtml + for more details on link type. + + + + + + + + + + + + Determines the relative strength of ownership of the target + piece of software. + + + + + + + + If this is uninstalled, then the [Link]'d software should be removed + too. + + + + + + + + If this is uninstalled, then the [Link]'d software should be removed + if nobody else is sharing it + + + + + + + + + + + Determines if the target software is a hard requirement. + + + + + + + + The [Link]'d software is absolutely required for installation + + + + + + + + Not absolutely required, but install unless directed not to + + + + + + + + Not absolutely required, install only when asked + + + + + + + + \ No newline at end of file