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 index ad8b8479..8e708eb9 100644 --- a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java @@ -21,11 +21,14 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * Service layer class that handles the storage and retrieval of all types of certificates. @@ -34,11 +37,11 @@ import java.util.UUID; @Service public class CertificateService { - private static final String TRUSTCHAIN = "trust-chain"; - private static final String PLATFORMCREDENTIAL = "platform-credentials"; - private static final String IDEVIDCERTIFICATE = "idevid-certificates"; - private static final String ENDORSEMENTCREDENTIAL = "endorsement-key-credentials"; - private static final String ISSUEDCERTIFICATES = "issued-certificates"; + private static final String TRUST_CHAIN = "trust-chain"; + private static final String PLATFORM_CREDENTIALS = "platform-credentials"; + private static final String IDEVID_CERTIFICATES = "idevid-certificates"; + private static final String ENDORSEMENT_CREDENTIALS = "endorsement-key-credentials"; + private static final String ISSUED_CERTIFICATES = "issued-certificates"; private final CertificateRepository certificateRepository; private final ComponentResultRepository componentResultRepository; @@ -56,7 +59,7 @@ public class CertificateService { /** * @param entityClass generic entity class * @param searchableColumns list of the searchable column name - * @param searchText text that waas input in the search textbox + * @param searchText text that was input in the search textbox * @param archiveFlag archive flag * @param pageable pageable * @param generic entity class @@ -101,6 +104,14 @@ public class CertificateService { return new PageImpl<>(resultList, pageable, totalRows); } + /** + * @param uuid + * @return + */ + public Certificate findCertificate(UUID uuid) { + return this.certificateRepository.getCertificate(uuid); + } + /** * Stored the given certificate in the database. * @@ -125,18 +136,18 @@ public class CertificateService { existingCertificate = getCertificateByHash( certificateType, certificate.getCertificateHash()); - } catch (DBServiceException dbsEx) { + } catch (Exception exception) { final String failMessage = "Querying for existing certificate failed (" + fileName + "): "; - errorMessages.add(failMessage + dbsEx.getMessage()); - log.error(failMessage, dbsEx); + errorMessages.add(failMessage + exception.getMessage()); + log.error(failMessage, exception); return; } try { // save the new certificate if no match is found if (existingCertificate == null) { - if (certificateType.equals(PLATFORMCREDENTIAL)) { + if (certificateType.equals(PLATFORM_CREDENTIALS)) { PlatformCredential platformCertificate = (PlatformCredential) certificate; if (platformCertificate.isPlatformBase()) { List sharedCertificates = getPlatformCertificateByBoardSN( @@ -186,6 +197,7 @@ public class CertificateService { existingCertificate.resetCreateTime(); this.certificateRepository.save(existingCertificate); + //todo List componentResults = componentResultRepository .findByBoardSerialNumber(((PlatformCredential) existingCertificate) .getPlatformSerial()); @@ -238,7 +250,7 @@ public class CertificateService { errorMessages.add(notFoundMessage); log.warn(notFoundMessage); } else { - if (certificateType.equals(PLATFORMCREDENTIAL)) { + if (certificateType.equals(PLATFORM_CREDENTIALS)) { PlatformCredential platformCertificate = (PlatformCredential) certificate; if (platformCertificate.isPlatformBase()) { // only do this if the base is being deleted. @@ -266,7 +278,63 @@ public class CertificateService { } /** - * Gets the certificate by the hash code of its bytes. Looks for both + * Helper method that packages a collection of certificates into a zip file. + * + * @param zipOut zip outputs streams + * @param singleFileName zip file name + * @throws IOException if there are any issues packaging or downloading the zip file + */ + public void bulkDownloadCertificates(final ZipOutputStream zipOut, + final String certificateType, + final String singleFileName) throws IOException { + String zipFileName; + final List certificates = findCertificatesByType(certificateType); + + // 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(); + } + + /** + * Retrieves the list of certificates based on the certificate type. + * + * @param certificateType certificate type + * @return list of certificates + */ + private List findCertificatesByType(String certificateType) { + return switch (certificateType) { + case PLATFORM_CREDENTIALS -> this.certificateRepository + .findByType( + "PlatformCredential"); + case ENDORSEMENT_CREDENTIALS -> this.certificateRepository + .findByType( + "EndorsementCredential"); + case TRUST_CHAIN -> this.certificateRepository + .findByType( + "CertificateAuthorityCredential"); + case IDEVID_CERTIFICATES -> this.certificateRepository + .findByType( + "IDevIDCertificate"); + case ISSUED_CERTIFICATES -> this.certificateRepository. + findByType("IssuedAttestationCertificate"); + default -> throw new IllegalArgumentException("The provided certificate type {" + + certificateType + "} does not exist"); + }; + } + + /** + * Retrieves the certificate by the hash code of its bytes. Looks for both * archived and unarchived certificates. * * @param certificateType String containing the certificate type @@ -277,19 +345,20 @@ public class CertificateService { final String certificateType, final int certificateHash) { return switch (certificateType) { - case PLATFORMCREDENTIAL -> this.certificateRepository + case PLATFORM_CREDENTIALS -> this.certificateRepository .findByCertificateHash(certificateHash, "PlatformCredential"); - case ENDORSEMENTCREDENTIAL -> this.certificateRepository + case ENDORSEMENT_CREDENTIALS -> this.certificateRepository .findByCertificateHash(certificateHash, "EndorsementCredential"); - case TRUSTCHAIN -> this.certificateRepository + case TRUST_CHAIN -> this.certificateRepository .findByCertificateHash(certificateHash, "CertificateAuthorityCredential"); - case IDEVIDCERTIFICATE -> this.certificateRepository + case IDEVID_CERTIFICATES -> this.certificateRepository .findByCertificateHash(certificateHash, "IDevIDCertificate"); - default -> null; + default -> throw new IllegalArgumentException("The provided certificate type {" + + certificateType + "} does not exist"); }; } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/PageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/PageController.java index e4a60827..89dfaed3 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/PageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/PageController.java @@ -142,7 +142,7 @@ public abstract class PageController

{ String defaultUri = "../" + newPage.getViewName(); // create uri with specified parameters URIBuilder uri = new URIBuilder("../" + newPage.getViewName()); - log.debug("Redirection URI = " + uri.toString()); + log.debug("Redirection URI = {}", uri.toString()); if (params != null) { for (Map.Entry e : params.asMap().entrySet()) { 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 deleted file mode 100644 index 4a81f1ed..00000000 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java +++ /dev/null @@ -1,897 +0,0 @@ -package hirs.attestationca.portal.page.controllers; - -import hirs.attestationca.persist.DBManagerException; -import hirs.attestationca.persist.DBServiceException; -import hirs.attestationca.persist.FilteredRecordsList; -import hirs.attestationca.persist.entity.manager.CACredentialRepository; -import hirs.attestationca.persist.entity.manager.CertificateRepository; -import hirs.attestationca.persist.entity.manager.ComponentResultRepository; -import hirs.attestationca.persist.entity.manager.IDevIDCertificateRepository; -import hirs.attestationca.persist.entity.manager.IssuedCertificateRepository; -import hirs.attestationca.persist.entity.userdefined.Certificate; -import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; -import hirs.attestationca.persist.entity.userdefined.certificate.ComponentResult; -import hirs.attestationca.persist.entity.userdefined.certificate.IDevIDCertificate; -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.util.CredentialHelper; -import hirs.attestationca.portal.datatables.Column; -import hirs.attestationca.portal.datatables.DataTableInput; -import hirs.attestationca.portal.datatables.DataTableResponse; -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.apache.commons.lang3.StringUtils; -import org.bouncycastle.util.encoders.DecoderException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.GetMapping; -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.ByteArrayInputStream; -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; -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 - -/** - * Controller for the Certificates list all pages. - */ -@Log4j2 -@Controller -@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-request") -public class CertificatePageController extends PageController { - /** - * Model attribute name used by initPage for the aca cert info. - */ - static final String ACA_CERT_DATA = "acaCertData"; - private static final String TRUSTCHAIN = "trust-chain"; - private static final String IDEVIDCERTIFICATE = "idevid-certificates"; - private static final String ISSUEDCERTIFICATES = "issued-certificates"; - private final CertificateRepository certificateRepository; - private final ComponentResultRepository componentResultRepository; - private final IssuedCertificateRepository issuedCertificateRepository; - private final CACredentialRepository caCredentialRepository; - private final IDevIDCertificateRepository iDevIDCertificateRepository; - private final CertificateService certificateService; - private CertificateAuthorityCredential certificateAuthorityCredential; - - /** - * Constructor providing the Page's display and routing specification. - * - * @param certificateRepository the general certificate manager - * @param componentResultRepository the component result repo - * @param issuedCertificateRepository the issued certificate manager - * @param caCredentialRepository the ca credential manager - * @param iDevIDCertificateRepository the IDevID certificate repository - * @param acaCertificate the ACA's X509 certificate - */ - @Autowired - public CertificatePageController(final CertificateRepository certificateRepository, - final ComponentResultRepository componentResultRepository, - final IssuedCertificateRepository issuedCertificateRepository, - final CACredentialRepository caCredentialRepository, - final IDevIDCertificateRepository iDevIDCertificateRepository, - final CertificateService certificateService, - final X509Certificate acaCertificate) { - super(Page.TRUST_CHAIN); - this.certificateRepository = certificateRepository; - this.componentResultRepository = componentResultRepository; - this.issuedCertificateRepository = issuedCertificateRepository; - this.caCredentialRepository = caCredentialRepository; - this.iDevIDCertificateRepository = iDevIDCertificateRepository; - this.certificateService = certificateService; - - try { - 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); - } - } - - /** - * 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 ISSUEDCERTIFICATES -> Page.ISSUED_CERTIFICATES; - case IDEVIDCERTIFICATE -> Page.IDEVID_CERTIFICATES; - default -> Page.TRUST_CHAIN; - }; - } - - /** - * Gets the concrete certificate class type to query for. - * - * @param certificateType String containing the certificate type - * @return the certificate class type - */ - private static Class getCertificateClass(final String certificateType) { - return switch (certificateType) { - case ISSUEDCERTIFICATES -> IssuedAttestationCertificate.class; - case IDEVIDCERTIFICATE -> IDevIDCertificate.class; - case TRUSTCHAIN -> CertificateAuthorityCredential.class; - default -> throw new IllegalArgumentException( - String.format("Unknown certificate type: %s", certificateType)); - }; - } - - /** - * Returns the path 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 - @RequestMapping - 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 IDEVIDCERTIFICATE: - mav = getBaseModelAndView(Page.IDEVID_CERTIFICATES); - 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.certificateRepository, - this.caCredentialRepository)); - mav.addObject(ACA_CERT_DATA, data); - break; - default: - // send to an error page - break; - } - - return mav; - } - - /** - * Helper method that returns a list of column names that are searchable. - * - * @return searchable column names - */ - private List findSearchableColumnsNames(List columns) { - - // Retrieve all searchable columns and collect their names into a list of strings. - return columns.stream().filter(Column::isSearchable).map(Column::getName) - .collect(Collectors.toList()); - } - - @ResponseBody - @GetMapping(value = "/trust-chain/list", - produces = MediaType.APPLICATION_JSON_VALUE) - public DataTableResponse getTrustChainTableData( - final DataTableInput input) { - log.debug("Handling list request: {}", input); - - // attempt to get the column property based on the order index. - String orderColumnName = input.getOrderColumnName(); - - log.debug("Ordering on column: {}", orderColumnName); - - String searchText = input.getSearch().getValue(); - List searchableColumns = findSearchableColumnsNames(input.getColumns()); - - int currentPage = input.getStart() / input.getLength(); - Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); - - FilteredRecordsList records = new FilteredRecordsList<>(); - - org.springframework.data.domain.Page pagedResult; - - if (StringUtils.isBlank(searchText)) { - pagedResult = - this.caCredentialRepository.findByArchiveFlag(false, pageable); - } else { - pagedResult = - this.certificateService.findBySearchableColumnsAndArchiveFlag( - CertificateAuthorityCredential.class, - searchableColumns, - searchText, - false, pageable); - } - - - if (pagedResult.hasContent()) { - records.addAll(pagedResult.getContent()); - records.setRecordsTotal(pagedResult.getContent().size()); - } else { - records.setRecordsTotal(input.getLength()); - } - - records.setRecordsFiltered(caCredentialRepository.findByArchiveFlag(false).size()); - - log.debug("Returning the size of the list of trust chain certificates: {}", records.size()); - return new DataTableResponse<>(records, input); - } - - - @ResponseBody - @GetMapping(value = "/idevid-certificates/list", - produces = MediaType.APPLICATION_JSON_VALUE) - public DataTableResponse getIDevIdCertificatesTableData( - final DataTableInput input) { - - log.debug("Handling list request: {}", input); - - // attempt to get the column property based on the order index. - String orderColumnName = input.getOrderColumnName(); - - log.debug("Ordering on column: {}", orderColumnName); - - String searchText = input.getSearch().getValue(); - List searchableColumns = findSearchableColumnsNames(input.getColumns()); - - int currentPage = input.getStart() / input.getLength(); - Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); - - FilteredRecordsList records = new FilteredRecordsList<>(); - org.springframework.data.domain.Page pagedResult; - - if (StringUtils.isBlank(searchText)) { - pagedResult = - this.iDevIDCertificateRepository.findByArchiveFlag(false, pageable); - } else { - pagedResult = - this.certificateService.findBySearchableColumnsAndArchiveFlag( - IDevIDCertificate.class, - searchableColumns, - searchText, - false, pageable); - } - - if (pagedResult.hasContent()) { - records.addAll(pagedResult.getContent()); - records.setRecordsTotal(pagedResult.getContent().size()); - } else { - records.setRecordsTotal(input.getLength()); - } - - records.setRecordsFiltered(iDevIDCertificateRepository.findByArchiveFlag(false).size()); - - log.debug("Returning the size of the list of IDEVID certificates: {}", records.size()); - return new DataTableResponse<>(records, input); - } - - @ResponseBody - @GetMapping(value = "/issued-certificates/list", - produces = MediaType.APPLICATION_JSON_VALUE) - public DataTableResponse getIssuedCertificatesTableData( - final DataTableInput input) { - log.debug("Handling list request: {}", input); - - // attempt to get the column property based on the order index. - String orderColumnName = input.getOrderColumnName(); - - log.debug("Ordering on column: {}", orderColumnName); - - String searchText = input.getSearch().getValue(); - List searchableColumns = findSearchableColumnsNames(input.getColumns()); - - int currentPage = input.getStart() / input.getLength(); - Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); - - FilteredRecordsList records = new FilteredRecordsList<>(); - org.springframework.data.domain.Page pagedResult; - - if (StringUtils.isBlank(searchText)) { - pagedResult = - this.issuedCertificateRepository.findByArchiveFlag(false, pageable); - } else { - pagedResult = - this.certificateService.findBySearchableColumnsAndArchiveFlag( - IssuedAttestationCertificate.class, - searchableColumns, - searchText, - false, pageable); - } - - if (pagedResult.hasContent()) { - records.addAll(pagedResult.getContent()); - records.setRecordsTotal(pagedResult.getContent().size()); - } else { - records.setRecordsTotal(input.getLength()); - } - - records.setRecordsFiltered(issuedCertificateRepository.findByArchiveFlag(false).size()); - - log.debug("Returning the size of the list of issued certificates: {}", records.size()); - return new DataTableResponse<>(records, input); - - } - - - /** - * 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); - } - } - - //Add messages to the model - model.put(MESSAGES_ATTRIBUTE, messages); - - return redirectTo(getCertificatePage(certificateType), new NoPageParams(), model, attr); - } - - /** - * Archives (soft delete) the credential. - * - * @param certificateType String containing the certificate type - * @param id the UUID of the cert to delete - * @param attr RedirectAttributes used to forward data back to the original - * page. - * @return redirect to this page - * @throws URISyntaxException if malformed URI - */ - @RequestMapping(value = "/{certificateType}/delete", method = RequestMethod.POST) - public RedirectView delete( - @PathVariable("certificateType") final String certificateType, - @RequestParam final String id, - final RedirectAttributes attr) throws URISyntaxException { - log.info("Handling request to delete {}", id); - - Map model = new HashMap<>(); - PageMessages messages = new PageMessages(); - - try { - UUID uuid = UUID.fromString(id); - Certificate certificate = certificateRepository.getCertificate(uuid); - - if (certificate == null) { - // Use the term "record" here to avoid user confusion b/t cert and cred - String notFoundMessage = "Unable to locate record with ID: " + uuid; - messages.addError(notFoundMessage); - log.warn(notFoundMessage); - } else { - if (certificateType.equals(PLATFORMCREDENTIAL)) { - PlatformCredential platformCertificate = (PlatformCredential) certificate; - if (platformCertificate.isPlatformBase()) { - // only do this if the base is being deleted. - List sharedCertificates = getCertificateByBoardSN( - certificateType, - platformCertificate.getPlatformSerial()); - - for (PlatformCredential pc : sharedCertificates) { - if (!pc.isPlatformBase()) { - pc.archive("User requested deletion via UI of the base certificate"); - certificateRepository.save(pc); - deleteComponentResults(pc.getPlatformSerial()); - } - } - } - deleteComponentResults(platformCertificate.getPlatformSerial()); - } - - certificate.archive("User requested deletion via UI"); - certificateRepository.save(certificate); - - String deleteCompletedMessage = "Certificate successfully deleted"; - messages.addInfo(deleteCompletedMessage); - log.info(deleteCompletedMessage); - } - } catch (IllegalArgumentException ex) { - String uuidError = "Failed to parse ID from: " + id; - messages.addError(uuidError); - log.error(uuidError, ex); - } catch (DBManagerException ex) { - String dbError = "Failed to archive cert: " + id; - messages.addError(dbError); - log.error(dbError, ex); - } - - model.put(MESSAGES_ATTRIBUTE, messages); - return redirectTo(getCertificatePage(certificateType), new NoPageParams(), model, attr); - } - - /** - * Handles request to download the cert by writing it to the response stream - * for download. - * - * @param certificateType String containing the certificate type - * @param id the UUID of the cert to download - * @param response the response object (needed to update the header with the - * file name) - * @throws IOException when writing to response output stream - */ - @RequestMapping(value = "/{certificateType}/download", method = RequestMethod.GET) - public void download( - @PathVariable("certificateType") final String certificateType, - @RequestParam final String id, - final HttpServletResponse response) - throws IOException { - log.info("Handling request to download {}", id); - - try { - UUID uuid = UUID.fromString(id); - Certificate certificate = certificateRepository.getCertificate(uuid); - if (certificate == null) { - // Use the term "record" here to avoid user confusion b/t cert and cred - String notFoundMessage = "Unable to locate record with ID: " + uuid; - log.warn(notFoundMessage); - // send a 404 error when invalid certificate - response.sendError(HttpServletResponse.SC_NOT_FOUND); - } else { - String fileName = "filename=\"" + getCertificateClass(certificateType).getSimpleName() - + "_" - + certificate.getSerialNumber() - + ".cer\""; - - // Set filename for download. - response.setHeader("Content-Disposition", "attachment;" + fileName); - response.setContentType("application/octet-stream"); - - // write cert to output stream - response.getOutputStream().write(certificate.getRawBytes()); - } - } catch (IllegalArgumentException ex) { - String uuidError = "Failed to parse ID from: " + id; - log.error(uuidError, ex); - // send a 404 error when invalid certificate - response.sendError(HttpServletResponse.SC_NOT_FOUND); - } - } - - /** - * 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 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 IOException when writing to response output stream - */ - @RequestMapping(value = "/trust-chain/bulk-download", 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.certificateRepository.findByType("CertificateAuthorityCredential"), - 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 IOException when writing to response output stream - */ - @RequestMapping(value = "/issued-certificates/bulk-download", 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.certificateRepository.findByType("IssuedAttestationCertificate"), - 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); - } - } - - /** - * Helper method that packages a collection of certificates into a zip file. - * - * @param zipOut zip outputs stream - * @param certificates collection of certificates - * @param singleFileName zip file name - * @return zip outputs stream - * @throws IOException if there are any issues packaging or downloading the zip file - */ - 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 flag indicating if a device-name join/alias is required for - * displaying the table data. This will be true if displaying a cert that is - * associated with a device. - * - * @param certificateType String containing the certificate type - * @return true if the list criteria modifier requires aliasing the device - * table, false otherwise. - */ - private boolean hasDeviceTableToJoin(final String certificateType) { - // Trust_Chain Credential do not contain the device table to join. - return !certificateType.equals(TRUSTCHAIN); - } - - /** - * 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 - * @return the certificate or null if none is found - */ - private Certificate getCertificateByHash( - final String certificateType, - final int certificateHash) { - - return switch (certificateType) { - case TRUSTCHAIN -> this.certificateRepository - .findByCertificateHash(certificateHash, - "CertificateAuthorityCredential"); - case IDEVIDCERTIFICATE -> this.certificateRepository - .findByCertificateHash(certificateHash, - "IDevIDCertificate"); - default -> 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 ioEx) { - final String failMessage = String.format( - "Failed to read uploaded file (%s): ", fileName); - log.error(failMessage, ioEx); - messages.addError(failMessage + ioEx.getMessage()); - return null; - } - try { - switch (certificateType) { - case IDEVIDCERTIFICATE: - return new IDevIDCertificate(fileBytes); - case TRUSTCHAIN: - if (CredentialHelper.isMultiPEM(new String(fileBytes, StandardCharsets.UTF_8))) { - try (ByteArrayInputStream certInputStream = new ByteArrayInputStream(fileBytes)) { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Collection c = cf.generateCertificates(certInputStream); - Iterator i = c.iterator(); - while (i.hasNext()) { - storeCertificate( - certificateType, - file.getOriginalFilename(), - messages, new CertificateAuthorityCredential( - ((java.security.cert.Certificate) i.next()).getEncoded())); - } - - // stop the main thread from saving/storing - return null; - } catch (CertificateException e) { - throw new IOException("Cannot construct X509Certificate from the input stream", - e); - } - } - 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 ioEx) { - final String failMessage = String.format( - "Failed to parse uploaded file (%s): ", fileName); - log.error(failMessage, ioEx); - messages.addError(failMessage + ioEx.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 iaEx) { - final String failMessage = String.format( - "Certificate format not recognized(%s): ", fileName); - log.error(failMessage, iaEx); - messages.addError(failMessage + iaEx.getMessage()); - return null; - } catch (IllegalStateException isEx) { - final String failMessage = String.format( - "Unexpected object while parsing %s ", fileName); - log.error(failMessage, isEx); - messages.addError(failMessage + isEx.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 - */ - private void storeCertificate( - final String certificateType, - final String fileName, - final PageMessages messages, - final Certificate certificate) { - - Certificate existingCertificate; - - // look for an identical certificate in the database - try { - existingCertificate = getCertificateByHash( - certificateType, - certificate.getCertificateHash()); - } catch (DBServiceException dbsEx) { - final String failMessage = "Querying for existing certificate failed (" - + fileName + "): "; - messages.addError(failMessage + dbsEx.getMessage()); - log.error(failMessage, dbsEx); - 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()); - 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; - } - }*/ - } - - this.certificateRepository.save(certificate); - parseAndSaveComponentResults(certificate); - - final String successMsg - = String.format("New certificate successfully uploaded (%s): ", fileName); - messages.addSuccess(successMsg); - log.info(successMsg); - return; - } - } catch (DBServiceException dbsEx) { - final String failMessage = String.format("Storing new certificate failed (%s): ", - fileName); - messages.addError(failMessage + dbsEx.getMessage()); - log.error(failMessage, dbsEx); - return; - } catch (IOException ioException) { - final String ioExceptionMessage = "Failed to save component results in the database"; - messages.addError(ioExceptionMessage + ioException.getMessage()); - log.error(ioExceptionMessage, ioException); - 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(); - this.certificateRepository.save(existingCertificate); - - List componentResults = componentResultRepository - .findByBoardSerialNumber(((PlatformCredential) existingCertificate) - .getPlatformSerial()); - for (ComponentResult componentResult : componentResults) { - componentResult.restore(); - componentResult.resetCreateTime(); - this.componentResultRepository.save(componentResult); - } - - final String successMsg = String.format("Pre-existing certificate " - + "found and unarchived (%s): ", fileName); - messages.addSuccess(successMsg); - log.info(successMsg); - return; - } - } catch (DBServiceException dbsEx) { - final String failMessage = String.format("Found an identical" - + " pre-existing certificate in the " - + "archive, but failed to unarchive it (%s): ", fileName); - messages.addError(failMessage + dbsEx.getMessage()); - log.error(failMessage, dbsEx); - 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/EndorsementCredentialPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/EndorsementCredentialPageController.java index 8442eddb..df7ed854 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/EndorsementCredentialPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/EndorsementCredentialPageController.java @@ -2,7 +2,6 @@ package hirs.attestationca.portal.page.controllers; import hirs.attestationca.persist.DBManagerException; import hirs.attestationca.persist.FilteredRecordsList; -import hirs.attestationca.persist.entity.manager.CertificateRepository; import hirs.attestationca.persist.entity.manager.EndorsementCredentialRepository; import hirs.attestationca.persist.entity.userdefined.Certificate; import hirs.attestationca.persist.entity.userdefined.certificate.EndorsementCredential; @@ -25,7 +24,6 @@ import org.springframework.data.domain.Sort; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -44,7 +42,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @Log4j2 @@ -54,17 +51,14 @@ public class EndorsementCredentialPageController extends PageController getEndorsementCredentialsTableData( final DataTableInput input) { - log.debug("Handling list request: {}", input); + log.debug("Handling list request for endorsement credentials: {}", input); // attempt to get the column property based on the order index. String orderColumnName = input.getOrderColumnName(); @@ -148,7 +149,7 @@ public class EndorsementCredentialPageController extends PageController certificates = this.certificateRepository.findByType("EndorsementCredential"); - - // convert the list of certificates to a list of endorsement credentials - List uploadedEKs = certificates.stream() - .filter(eachPC -> eachPC instanceof EndorsementCredential) - .map(eachPC -> (EndorsementCredential) eachPC).toList(); - - // get all files - bulkDownloadEndorsementCredentials(zipOut, uploadedEKs); - // write cert to output stream + // write endorsement credentials to output stream and bulk download them + this.certificateService.bulkDownloadCertificates(zipOut, ENDORSEMENT_CREDENTIALS, singleFileName); } catch (IllegalArgumentException ex) { String uuidError = "Failed to parse ID from: "; log.error(uuidError, ex); @@ -220,7 +210,7 @@ public class EndorsementCredentialPageController extends PageController endorsementCredentials) - throws IOException { - String zipFileName; - final String singleFileName = "Endorsement_Certificates"; - - // get all endorsement credentials - for (EndorsementCredential endorsementCredential : endorsementCredentials) { - zipFileName = String.format("%s[%s].cer", singleFileName, - Integer.toHexString(endorsementCredential.getCertificateHash())); - // configure the zip entry, the properties of the 'file' - ZipEntry zipEntry = new ZipEntry(zipFileName); - zipEntry.setSize((long) endorsementCredential.getRawBytes().length * Byte.SIZE); - zipEntry.setTime(System.currentTimeMillis()); - zipOut.putNextEntry(zipEntry); - // the content of the resource - StreamUtils.copy(endorsementCredential.getRawBytes(), zipOut); - zipOut.closeEntry(); - } - zipOut.finish(); - } } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IDevIdCertificatePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IDevIdCertificatePageController.java index 147ccf37..6a91815c 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IDevIdCertificatePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IDevIdCertificatePageController.java @@ -1,119 +1,359 @@ -//package hirs.attestationca.portal.page.controllers; -// -//import hirs.attestationca.persist.FilteredRecordsList; -//import hirs.attestationca.persist.entity.manager.IDevIDCertificateRepository; -//import hirs.attestationca.persist.entity.userdefined.certificate.IDevIDCertificate; -//import hirs.attestationca.persist.service.CertificateService; -//import hirs.attestationca.portal.datatables.Column; -//import hirs.attestationca.portal.datatables.DataTableInput; -//import hirs.attestationca.portal.datatables.DataTableResponse; -//import hirs.attestationca.portal.page.Page; -//import hirs.attestationca.portal.page.PageController; -//import hirs.attestationca.portal.page.params.NoPageParams; -//import lombok.extern.log4j.Log4j2; -//import org.apache.commons.lang3.StringUtils; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.data.domain.Pageable; -//import org.springframework.data.domain.Sort; -//import org.springframework.http.MediaType; -//import org.springframework.stereotype.Controller; -//import org.springframework.ui.Model; -//import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.ResponseBody; -//import org.springframework.web.servlet.ModelAndView; -// -//import java.util.List; -//import java.util.stream.Collectors; -// -//@Log4j2 -//@Controller -//@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-request/idevid-certificates") -//public class IDevIdCertificatePageController extends PageController { -// -// private final IDevIDCertificateRepository iDevIDCertificateRepository; -// private final CertificateService certificateService; -// -// @Autowired -// public IDevIdCertificatePageController(final IDevIDCertificateRepository iDevIDCertificateRepository, -// final CertificateService certificateService) { -// super(Page.TRUST_CHAIN); -// this.iDevIDCertificateRepository = iDevIDCertificateRepository; -// this.certificateService = certificateService; -// } -// -// /** -// * Returns the path 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. -// */ -// @RequestMapping -// public ModelAndView initPage( -// final NoPageParams params, final Model model) { -// return getBaseModelAndView(Page.IDEVID_CERTIFICATES); -// } -// -// -// @ResponseBody -// @GetMapping(value = "/list", -// produces = MediaType.APPLICATION_JSON_VALUE) -// public DataTableResponse getIDevIdCertificatesTableData( -// final DataTableInput input) { -// -// log.debug("Handling list request: {}", input); -// -// // attempt to get the column property based on the order index. -// String orderColumnName = input.getOrderColumnName(); -// -// log.debug("Ordering on column: {}", orderColumnName); -// -// String searchText = input.getSearch().getValue(); -// List searchableColumns = findSearchableColumnsNames(input.getColumns()); -// -// int currentPage = input.getStart() / input.getLength(); -// Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); -// -// FilteredRecordsList records = new FilteredRecordsList<>(); -// org.springframework.data.domain.Page pagedResult; -// -// if (StringUtils.isBlank(searchText)) { -// pagedResult = -// this.iDevIDCertificateRepository.findByArchiveFlag(false, pageable); -// } else { -// pagedResult = -// this.certificateService.findBySearchableColumnsAndArchiveFlag( -// IDevIDCertificate.class, -// searchableColumns, -// searchText, -// false, pageable); -// } -// -// if (pagedResult.hasContent()) { -// records.addAll(pagedResult.getContent()); -// records.setRecordsTotal(pagedResult.getContent().size()); -// } else { -// records.setRecordsTotal(input.getLength()); -// } -// -// records.setRecordsFiltered(iDevIDCertificateRepository.findByArchiveFlag(false).size()); -// -// log.debug("Returning the size of the list of IDEVID certificates: {}", records.size()); -// return new DataTableResponse<>(records, input); -// } -// -// /** -// * Helper method that returns a list of column names that are searchable. -// * -// * @return searchable column names -// */ -// private List findSearchableColumnsNames(List columns) { -// -// // Retrieve all searchable columns and collect their names into a list of strings. -// return columns.stream().filter(Column::isSearchable).map(Column::getName) -// .collect(Collectors.toList()); -// } -//} +package hirs.attestationca.portal.page.controllers; + +import hirs.attestationca.persist.DBManagerException; +import hirs.attestationca.persist.FilteredRecordsList; +import hirs.attestationca.persist.entity.manager.IDevIDCertificateRepository; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.certificate.IDevIDCertificate; +import hirs.attestationca.persist.service.CertificateService; +import hirs.attestationca.portal.datatables.Column; +import hirs.attestationca.portal.datatables.DataTableInput; +import hirs.attestationca.portal.datatables.DataTableResponse; +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 jakarta.servlet.http.HttpServletResponse; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.util.encoders.DecoderException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +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.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.zip.ZipOutputStream; + +@Log4j2 +@Controller +@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-request/idevid-certificates") +public class IDevIdCertificatePageController extends PageController { + + private static final String IDEVID_CERTIFICATE = "idevid-certificates"; + + private final IDevIDCertificateRepository iDevIDCertificateRepository; + private final CertificateService certificateService; + + @Autowired + public IDevIdCertificatePageController(final IDevIDCertificateRepository iDevIDCertificateRepository, + final CertificateService certificateService) { + super(Page.TRUST_CHAIN); + this.iDevIDCertificateRepository = iDevIDCertificateRepository; + this.certificateService = certificateService; + } + + /** + * Returns the path 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. + */ + @RequestMapping + public ModelAndView initPage( + final NoPageParams params, final Model model) { + return getBaseModelAndView(Page.IDEVID_CERTIFICATES); + } + + /** + * Retrieves the collection of idevid certificates that will be displayed on the idevid certificates + * page. + * + * @param input data table input received from the front-end + * @return data table of idevid certificates + */ + @ResponseBody + @GetMapping(value = "/list", + produces = MediaType.APPLICATION_JSON_VALUE) + public DataTableResponse getIDevIdCertificatesTableData( + final DataTableInput input) { + + log.debug("Handling list request for idevid certificates: {}", input); + + // attempt to get the column property based on the order index. + String orderColumnName = input.getOrderColumnName(); + + log.debug("Ordering on column: {}", orderColumnName); + + String searchText = input.getSearch().getValue(); + List searchableColumns = findSearchableColumnsNames(input.getColumns()); + + int currentPage = input.getStart() / input.getLength(); + Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); + + FilteredRecordsList records = new FilteredRecordsList<>(); + org.springframework.data.domain.Page pagedResult; + + if (StringUtils.isBlank(searchText)) { + pagedResult = + this.iDevIDCertificateRepository.findByArchiveFlag(false, pageable); + } else { + pagedResult = + this.certificateService.findBySearchableColumnsAndArchiveFlag( + IDevIDCertificate.class, + searchableColumns, + searchText, + false, pageable); + } + + if (pagedResult.hasContent()) { + records.addAll(pagedResult.getContent()); + records.setRecordsTotal(pagedResult.getContent().size()); + } else { + records.setRecordsTotal(input.getLength()); + } + + records.setRecordsFiltered(iDevIDCertificateRepository.findByArchiveFlag(false).size()); + + log.debug("Returning the size of the list of IDEVID certificates: {}", records.size()); + return new DataTableResponse<>(records, input); + } + + /** + * Handles request to download the IDevId certificate by writing it to the response stream + * for download. + * + * @param id the UUID of the cert to download + * @param response the response object (needed to update the header with the + * file name) + * @throws IOException when writing to response output stream + */ + @GetMapping("/download") + public void downloadSingleIDevIdCertificate( + @RequestParam final String id, + final HttpServletResponse response) + throws IOException { + log.info("Handling request to download idevid certificate id {}", id); + + try { + UUID uuid = UUID.fromString(id); + Certificate certificate = this.certificateService.findCertificate(uuid); + + if (certificate == null) { + // Use the term "record" here to avoid user confusion b/t cert and cred + String notFoundMessage = "Unable to locate record with ID: " + uuid; + log.warn(notFoundMessage); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } else if (certificate instanceof IDevIDCertificate uploadedIDevIdCertificate) { + String fileName = "filename=\"" + IDevIDCertificate.class.getSimpleName() + + "_" + + uploadedIDevIdCertificate.getSerialNumber() + + ".cer\""; + + // Set filename for download. + response.setHeader("Content-Disposition", "attachment;" + fileName); + response.setContentType("application/octet-stream"); + + // write cert to output stream + response.getOutputStream().write(certificate.getRawBytes()); + } + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from: " + id; + log.error(uuidError, ex); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + + /** + * Handles request to download the IDevID Certificates 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 IOException when writing to response output stream + */ + @GetMapping("/bulk-download") + public void bulkDownloadIDevIdCertificates(final HttpServletResponse response) + throws IOException { + log.info("Handling request to download all idevid certificates"); + + final String fileName = "idevid_certificates.zip"; + final String singleFileName = "IDevID_Certificates"; + + // Set filename for download. + response.setHeader("Content-Disposition", "attachment; filename=" + fileName); + response.setContentType("application/zip"); + + try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) { + // write idevid certificates to output stream and bulk download them + this.certificateService.bulkDownloadCertificates(zipOut, IDEVID_CERTIFICATE, singleFileName); + } 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); + } + } + + /** + * Uploads and processes an idevid certificate. + * + * @param files the files to process + * @param attr the redirection attributes + * @return the redirection view + * @throws URISyntaxException if malformed URI + */ + @PostMapping("/upload") + protected RedirectView uploadIDevIdCertificate( + @RequestParam("file") final MultipartFile[] files, + final RedirectAttributes attr) throws URISyntaxException { + + log.info("Handling request to upload one or more idevid certificates"); + + Map model = new HashMap<>(); + PageMessages messages = new PageMessages(); + + List errorMessages = new ArrayList<>(); + List successMessages = new ArrayList<>(); + + for (MultipartFile file : files) { + //Parse IDevId Certificate + IDevIDCertificate parsedIDevIDCertificate = + parseIDevIDCertificate(file, messages); + + //Store only if it was parsed + if (parsedIDevIDCertificate != null) { + certificateService.storeCertificate( + IDEVID_CERTIFICATE, + file.getOriginalFilename(), + successMessages, errorMessages, parsedIDevIDCertificate); + } + } + + //Add messages to the model + model.put(MESSAGES_ATTRIBUTE, messages); + + return redirectTo(Page.IDEVID_CERTIFICATES, new NoPageParams(), model, attr); + } + + /** + * Archives (soft deletes) the idevid certificate. + * + * @param id the UUID of the idevid certificate to delete + * @param attr RedirectAttributes used to forward data back to the original + * page. + * @return redirect to this page + * @throws URISyntaxException if malformed URI + */ + @PostMapping("/delete") + public RedirectView deleteIdevIdCertificate( + @RequestParam final String id, + final RedirectAttributes attr) throws URISyntaxException { + log.info("Handling request to delete idevid id {}", id); + + Map model = new HashMap<>(); + PageMessages messages = new PageMessages(); + + try { + List successMessages = new ArrayList<>(); + List errorMessages = new ArrayList<>(); + + UUID uuid = UUID.fromString(id); + + this.certificateService.deleteCertificate(uuid, IDEVID_CERTIFICATE, + successMessages, errorMessages); + + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from idevid certificate: " + id; + messages.addError(uuidError); + log.error(uuidError, ex); + } catch (DBManagerException ex) { + String dbError = "Failed to archive idevid certificate: " + id; + messages.addError(dbError); + log.error(dbError, ex); + } + + model.put(MESSAGES_ATTRIBUTE, messages); + return redirectTo(Page.IDEVID_CERTIFICATES, new NoPageParams(), model, attr); + } + + /** + * Helper method that returns a list of column names that are searchable. + * + * @return searchable column names + */ + private List findSearchableColumnsNames(List columns) { + + // Retrieve all searchable columns and collect their names into a list of strings. + return columns.stream().filter(Column::isSearchable).map(Column::getName) + .collect(Collectors.toList()); + } + + /** + * Attempts to parse the provided file in order to create an IDevId Certificate. + * + * @param file file + * @param messages page messages + * @return IDevId certificate + */ + private IDevIDCertificate parseIDevIDCertificate(MultipartFile file, + PageMessages messages) { + log.info("Received IDevId certificate file of size: {}", file.getSize()); + + byte[] fileBytes; + String fileName = file.getOriginalFilename(); + + // attempt to retrieve file bytes from the provided file + try { + fileBytes = file.getBytes(); + } catch (IOException ioEx) { + final String failMessage = String.format( + "Failed to read uploaded IDevId certificate file (%s): ", fileName); + log.error(failMessage, ioEx); + messages.addError(failMessage + ioEx.getMessage()); + return null; + } + + // attempt to build the IDevId certificate from the uploaded bytes + try { + return new IDevIDCertificate(fileBytes); + } catch (IOException ioEx) { + final String failMessage = String.format( + "Failed to parse uploaded IDevId certificate file (%s): ", fileName); + log.error(failMessage, ioEx); + messages.addError(failMessage + ioEx.getMessage()); + return null; + } catch (DecoderException dEx) { + final String failMessage = String.format( + "Failed to parse uploaded IDevId certificate pem file (%s): ", fileName); + log.error(failMessage, dEx); + messages.addError(failMessage + dEx.getMessage()); + return null; + } catch (IllegalArgumentException iaEx) { + final String failMessage = String.format( + "IDevId certificate format not recognized(%s): ", fileName); + log.error(failMessage, iaEx); + messages.addError(failMessage + iaEx.getMessage()); + return null; + } catch (IllegalStateException isEx) { + final String failMessage = String.format( + "Unexpected object while parsing IDevId certificate %s ", fileName); + log.error(failMessage, isEx); + messages.addError(failMessage + isEx.getMessage()); + return null; + } + } +} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IssuedCertificateController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IssuedCertificateController.java index 464cce43..6dcc3b13 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IssuedCertificateController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/IssuedCertificateController.java @@ -1,7 +1,7 @@ package hirs.attestationca.portal.page.controllers; +import hirs.attestationca.persist.DBManagerException; import hirs.attestationca.persist.FilteredRecordsList; -import hirs.attestationca.persist.entity.manager.CertificateRepository; import hirs.attestationca.persist.entity.manager.IssuedCertificateRepository; import hirs.attestationca.persist.entity.userdefined.Certificate; import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestationCertificate; @@ -11,6 +11,7 @@ import hirs.attestationca.portal.datatables.DataTableInput; import hirs.attestationca.portal.datatables.DataTableResponse; 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 jakarta.servlet.http.HttpServletResponse; import lombok.extern.log4j.Log4j2; @@ -22,18 +23,23 @@ import org.springframework.data.domain.Sort; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; 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.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @Log4j2 @@ -41,23 +47,22 @@ import java.util.zip.ZipOutputStream; @RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-request/issued-certificates") public class IssuedCertificateController extends PageController { - private final CertificateRepository certificateRepository; + private static final String ISSUED_CERTIFICATES = "issued-certificates"; + private final IssuedCertificateRepository issuedCertificateRepository; private final CertificateService certificateService; @Autowired public IssuedCertificateController( - final CertificateRepository certificateRepository, final IssuedCertificateRepository issuedCertificateRepository, final CertificateService certificateService) { super(Page.TRUST_CHAIN); - this.certificateRepository = certificateRepository; this.issuedCertificateRepository = issuedCertificateRepository; this.certificateService = certificateService; } /** - * Returns the path for the view and the data model for the page. + * Returns the path for the view and the data model for the Issued Attestation Certificate page. * * @param params The object to map url parameters into. * @param model The data model for the request. Can contain data from @@ -70,6 +75,10 @@ public class IssuedCertificateController extends PageController { return getBaseModelAndView(Page.ISSUED_CERTIFICATES); } + /** + * @param input + * @return + */ @ResponseBody @GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) @@ -117,16 +126,16 @@ public class IssuedCertificateController extends PageController { } /** - * Handles request to download the cert by writing it to the response stream + * Handles request to download the issued attestation certificate by writing it to the response stream * for download. * - * @param id the UUID of the cert to download + * @param id the UUID of the issued attestation certificate to download * @param response the response object (needed to update the header with the * file name) * @throws IOException when writing to response output stream */ @GetMapping("/download") - public void issuedCertificateSingleDownload( + public void downloadSingleIssuedCertificate( @RequestParam final String id, final HttpServletResponse response) throws IOException { @@ -134,17 +143,18 @@ public class IssuedCertificateController extends PageController { try { UUID uuid = UUID.fromString(id); - Certificate certificate = certificateRepository.getCertificate(uuid); + Certificate certificate = this.certificateService.findCertificate(uuid); + if (certificate == null) { // Use the term "record" here to avoid user confusion b/t cert and cred String notFoundMessage = "Unable to locate record with ID: " + uuid; log.warn(notFoundMessage); // send a 404 error when invalid certificate response.sendError(HttpServletResponse.SC_NOT_FOUND); - } else { + } else if (certificate instanceof IssuedAttestationCertificate uploadedIssuedCertificate) { String fileName = "filename=\"" + IssuedAttestationCertificate.class.getSimpleName() + "_" - + certificate.getSerialNumber() + + uploadedIssuedCertificate.getSerialNumber() + ".cer\""; // Set filename for download. @@ -163,7 +173,7 @@ public class IssuedCertificateController extends PageController { } /** - * Handles request to download the certs by writing it to the response stream + * Handles request to download the issued attestation certificates by writing it to the response stream * for download in bulk. * * @param response the response object (needed to update the header with the @@ -171,30 +181,68 @@ public class IssuedCertificateController extends PageController { * @throws IOException when writing to response output stream */ @GetMapping("/bulk-download") - public void icBulkDownload(final HttpServletResponse response) + public void bulkDownloadIssuedCertificates(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; + final String fileName = "issued_certificates.zip"; // 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.certificateRepository.findByType("IssuedAttestationCertificate"), - singleFileName); - // write cert to output stream - } catch (IllegalArgumentException ex) { - String uuidError = "Failed to parse ID from: "; - log.error(uuidError, ex); + // write issued attestation certificates to output stream and bulk download them + this.certificateService.bulkDownloadCertificates(zipOut, ISSUED_CERTIFICATES, singleFileName); + } catch (Exception ex) { + log.error("Failed to bulk download issued certificates:", ex); // send a 404 error when invalid certificate response.sendError(HttpServletResponse.SC_NOT_FOUND); } } + /** + * Archives (soft deletes) the issued attestation certificate. + * + * @param id the UUID of the issued attestation certificate to delete + * @param attr RedirectAttributes used to forward data back to the original + * page. + * @return redirect to this page + * @throws URISyntaxException if malformed URI + */ + @PostMapping("/delete") + public RedirectView deleteIssuedCertificate( + @RequestParam final String id, + final RedirectAttributes attr) throws URISyntaxException { + log.info("Handling request to delete issued attestation certificate id {}", id); + + Map model = new HashMap<>(); + PageMessages messages = new PageMessages(); + + try { + List successMessages = new ArrayList<>(); + List errorMessages = new ArrayList<>(); + + UUID uuid = UUID.fromString(id); + + this.certificateService.deleteCertificate(uuid, ISSUED_CERTIFICATES, + successMessages, errorMessages); + + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from issued attestation certificate: " + id; + messages.addError(uuidError); + log.error(uuidError, ex); + } catch (DBManagerException ex) { + String dbError = "Failed to archive issued attestation certificate: " + id; + messages.addError(dbError); + log.error(dbError, ex); + } + + model.put(MESSAGES_ATTRIBUTE, messages); + return redirectTo(Page.ISSUED_CERTIFICATES, new NoPageParams(), model, attr); + } + /** * Helper method that returns a list of column names that are searchable. * @@ -206,35 +254,4 @@ public class IssuedCertificateController extends PageController { return columns.stream().filter(Column::isSearchable).map(Column::getName) .collect(Collectors.toList()); } - - - /** - * Helper method that packages a collection of certificates into a zip file. - * - * @param zipOut zip outputs stream - * @param certificates collection of certificates - * @param singleFileName zip file name - * @return zip outputs stream - * @throws IOException if there are any issues packaging or downloading the zip file - */ - 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; - } } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/PlatformCredentialPageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/PlatformCredentialPageController.java index 232ee47f..85267c5f 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/PlatformCredentialPageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/PlatformCredentialPageController.java @@ -2,7 +2,6 @@ package hirs.attestationca.portal.page.controllers; import hirs.attestationca.persist.DBManagerException; import hirs.attestationca.persist.FilteredRecordsList; -import hirs.attestationca.persist.entity.manager.CertificateRepository; import hirs.attestationca.persist.entity.manager.EndorsementCredentialRepository; import hirs.attestationca.persist.entity.manager.PlatformCertificateRepository; import hirs.attestationca.persist.entity.userdefined.Certificate; @@ -27,7 +26,6 @@ import org.springframework.data.domain.Sort; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -46,7 +44,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @Log4j2 @@ -56,18 +53,16 @@ public class PlatformCredentialPageController extends PageController certificates = this.certificateRepository.findByType("PlatformCredential"); - - // convert the list of certificates to a list of platform credentials - List uploadedPCs = certificates.stream() - .filter(eachPC -> eachPC instanceof PlatformCredential) - .map(eachPC -> (PlatformCredential) eachPC).toList(); - - // get all files and write certificates to output stream - bulkDownloadPlatformCertificates(zipOut, uploadedPCs); + // write platform credentials to output stream and bulk download them + this.certificateService.bulkDownloadCertificates(zipOut, PLATFORM_CREDENTIALS, singleFileName); } catch (IllegalArgumentException ex) { String uuidError = "Failed to parse platform credential ID from: "; log.error(uuidError, ex); @@ -283,14 +277,14 @@ public class PlatformCredentialPageController extends PageController platformCredentials) - throws IOException { - String zipFileName; - final String singleFileName = "Platform_Certificate"; - - // get all files - for (PlatformCredential platformCredential : platformCredentials) { - zipFileName = String.format("%s[%s].cer", singleFileName, - Integer.toHexString(platformCredential.getCertificateHash())); - // configure the zip entry, the properties of the 'file' - ZipEntry zipEntry = new ZipEntry(zipFileName); - zipEntry.setSize((long) platformCredential.getRawBytes().length * Byte.SIZE); - zipEntry.setTime(System.currentTimeMillis()); - zipOut.putNextEntry(zipEntry); - // the content of the resource - StreamUtils.copy(platformCredential.getRawBytes(), zipOut); - zipOut.closeEntry(); - } - zipOut.finish(); - } } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/TrustChainCertificatePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/TrustChainCertificatePageController.java index 7598059f..59d5a9d8 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/TrustChainCertificatePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/TrustChainCertificatePageController.java @@ -1,286 +1,445 @@ -//package hirs.attestationca.portal.page.controllers; -// -//import hirs.attestationca.persist.FilteredRecordsList; -//import hirs.attestationca.persist.entity.manager.CACredentialRepository; -//import hirs.attestationca.persist.entity.manager.CertificateRepository; -//import hirs.attestationca.persist.entity.userdefined.Certificate; -//import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; -//import hirs.attestationca.persist.service.CertificateService; -//import hirs.attestationca.portal.datatables.Column; -//import hirs.attestationca.portal.datatables.DataTableInput; -//import hirs.attestationca.portal.datatables.DataTableResponse; -//import hirs.attestationca.portal.page.Page; -//import hirs.attestationca.portal.page.PageController; -//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.apache.commons.lang3.StringUtils; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.data.domain.Pageable; -//import org.springframework.data.domain.Sort; -//import org.springframework.http.MediaType; -//import org.springframework.stereotype.Controller; -//import org.springframework.ui.Model; -//import org.springframework.util.StreamUtils; -//import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RequestParam; -//import org.springframework.web.bind.annotation.ResponseBody; -//import org.springframework.web.servlet.ModelAndView; -// -//import java.io.IOException; -//import java.security.cert.CertificateEncodingException; -//import java.security.cert.X509Certificate; -//import java.util.HashMap; -//import java.util.List; -//import java.util.UUID; -//import java.util.stream.Collectors; -//import java.util.zip.ZipEntry; -//import java.util.zip.ZipOutputStream; -// -//import static hirs.attestationca.portal.page.controllers.CertificatePageController.ACA_CERT_DATA; -// -//@Log4j2 -//@Controller -//@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-request/trust-chain") -//public class TrustChainCertificatePageController extends PageController { -// -// private final CertificateRepository certificateRepository; -// private final CACredentialRepository caCredentialRepository; -// private final CertificateService certificateService; -// private CertificateAuthorityCredential certificateAuthorityCredential; -// -// @Autowired -// public TrustChainCertificatePageController(final CertificateRepository certificateRepository, -// final CACredentialRepository caCredentialRepository, -// final CertificateService certificateService, -// final X509Certificate acaCertificate) { -// super(Page.TRUST_CHAIN); -// this.certificateRepository = certificateRepository; -// this.caCredentialRepository = caCredentialRepository; -// this.certificateService = certificateService; -// -// try { -// 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); -// } -// } -// -// /** -// * Returns the path 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. -// */ -// @RequestMapping -// public ModelAndView initPage( -// final NoPageParams params, final Model model) { -// -// ModelAndView mav = getBaseModelAndView(Page.TRUST_CHAIN); -// -// mav.addObject(ACA_CERT_DATA, -// new HashMap(CertificateStringMapBuilder.getCertificateAuthorityInformation( -// certificateAuthorityCredential, this.certificateRepository, -// this.caCredentialRepository))); -// -// return mav; -// } -// -// @ResponseBody -// @GetMapping(value = "/list", -// produces = MediaType.APPLICATION_JSON_VALUE) -// public DataTableResponse getTrustChainTableData( -// final DataTableInput input) { -// log.debug("Handling list request: {}", input); -// -// // attempt to get the column property based on the order index. -// String orderColumnName = input.getOrderColumnName(); -// -// log.debug("Ordering on column: {}", orderColumnName); -// -// String searchText = input.getSearch().getValue(); -// List searchableColumns = findSearchableColumnsNames(input.getColumns()); -// -// int currentPage = input.getStart() / input.getLength(); -// Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); -// -// FilteredRecordsList records = new FilteredRecordsList<>(); -// -// org.springframework.data.domain.Page pagedResult; -// -// if (StringUtils.isBlank(searchText)) { -// pagedResult = -// this.caCredentialRepository.findByArchiveFlag(false, pageable); -// } else { -// pagedResult = -// this.certificateService.findBySearchableColumnsAndArchiveFlag( -// CertificateAuthorityCredential.class, -// searchableColumns, -// searchText, -// false, pageable); -// } -// -// -// if (pagedResult.hasContent()) { -// records.addAll(pagedResult.getContent()); -// records.setRecordsTotal(pagedResult.getContent().size()); -// } else { -// records.setRecordsTotal(input.getLength()); -// } -// -// records.setRecordsFiltered(caCredentialRepository.findByArchiveFlag(false).size()); -// -// log.debug("Returning the size of the list of trust chain certificates: {}", records.size()); -// return new DataTableResponse<>(records, input); -// } -// -// /** -// * Handles request to download the cert by writing it to the response stream -// * for download. -// * -// * @param id the UUID of the cert to download -// * @param response the response object (needed to update the header with the -// * file name) -// * @throws IOException when writing to response output stream -// */ -// @GetMapping("/download") -// public void download( -// @RequestParam final String id, -// final HttpServletResponse response) -// throws IOException { -// log.info("Handling request to download {}", id); -// -// try { -// UUID uuid = UUID.fromString(id); -// Certificate certificate = certificateRepository.getCertificate(uuid); -// if (certificate == null) { -// // Use the term "record" here to avoid user confusion b/t cert and cred -// String notFoundMessage = "Unable to locate record with ID: " + uuid; -// log.warn(notFoundMessage); -// // send a 404 error when invalid certificate -// response.sendError(HttpServletResponse.SC_NOT_FOUND); -// } else { -// String fileName = "filename=\"" + getCertificateClass(certificateType).getSimpleName() -// + "_" -// + certificate.getSerialNumber() -// + ".cer\""; -// -// // Set filename for download. -// response.setHeader("Content-Disposition", "attachment;" + fileName); -// response.setContentType("application/octet-stream"); -// -// // write cert to output stream -// response.getOutputStream().write(certificate.getRawBytes()); -// } -// } catch (IllegalArgumentException ex) { -// String uuidError = "Failed to parse ID from: " + id; -// log.error(uuidError, ex); -// // send a 404 error when invalid certificate -// response.sendError(HttpServletResponse.SC_NOT_FOUND); -// } -// } -// -// /** -// * 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 IOException when writing to response output stream -// */ -// @ResponseBody -// @GetMapping("/download-aca-cert") -// 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 IOException when writing to response output stream -// */ -// @GetMapping("/bulk-download") -// 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.certificateRepository.findByType("CertificateAuthorityCredential"), -// 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); -// } -// } -// -// -// /** -// * Helper method that returns a list of column names that are searchable. -// * -// * @return searchable column names -// */ -// private List findSearchableColumnsNames(List columns) { -// -// // Retrieve all searchable columns and collect their names into a list of strings. -// return columns.stream().filter(Column::isSearchable).map(Column::getName) -// .collect(Collectors.toList()); -// } -// -// /** -// * Helper method that packages a collection of certificates into a zip file. -// * -// * @param zipOut zip outputs stream -// * @param certificates collection of certificates -// * @param singleFileName zip file name -// * @return zip outputs stream -// * @throws IOException if there are any issues packaging or downloading the zip file -// */ -// 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; -// } -//} +package hirs.attestationca.portal.page.controllers; + +import hirs.attestationca.persist.DBManagerException; +import hirs.attestationca.persist.FilteredRecordsList; +import hirs.attestationca.persist.entity.manager.CACredentialRepository; +import hirs.attestationca.persist.entity.manager.CertificateRepository; +import hirs.attestationca.persist.entity.userdefined.Certificate; +import hirs.attestationca.persist.entity.userdefined.certificate.CertificateAuthorityCredential; +import hirs.attestationca.persist.service.CertificateService; +import hirs.attestationca.persist.util.CredentialHelper; +import hirs.attestationca.portal.datatables.Column; +import hirs.attestationca.portal.datatables.DataTableInput; +import hirs.attestationca.portal.datatables.DataTableResponse; +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.apache.commons.lang3.StringUtils; +import org.bouncycastle.util.encoders.DecoderException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +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.ByteArrayInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.zip.ZipOutputStream; + +@Log4j2 +@Controller +@RequestMapping("/HIRS_AttestationCAPortal/portal/certificate-request/trust-chain") +public class TrustChainCertificatePageController extends PageController { + + /** + * Model attribute name used by initPage for the aca cert info. + */ + static final String ACA_CERT_DATA = "acaCertData"; + + private static final String TRUST_CHAIN = "trust-chain"; + + private final CertificateRepository certificateRepository; + private final CACredentialRepository caCredentialRepository; + private final CertificateService certificateService; + private CertificateAuthorityCredential certificateAuthorityCredential; + + @Autowired + public TrustChainCertificatePageController(final CertificateRepository certificateRepository, + final CACredentialRepository caCredentialRepository, + final CertificateService certificateService, + final X509Certificate acaCertificate) { + super(Page.TRUST_CHAIN); + this.certificateRepository = certificateRepository; + this.caCredentialRepository = caCredentialRepository; + this.certificateService = certificateService; + + try { + 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); + } + } + + /** + * Returns the path 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. + */ + @RequestMapping + public ModelAndView initPage( + final NoPageParams params, final Model model) { + + ModelAndView mav = getBaseModelAndView(Page.TRUST_CHAIN); + + mav.addObject(ACA_CERT_DATA, + new HashMap<>(CertificateStringMapBuilder.getCertificateAuthorityInformation( + certificateAuthorityCredential, this.certificateRepository, + this.caCredentialRepository))); + + return mav; + } + + /** + * @param input + * @return + */ + @ResponseBody + @GetMapping(value = "/list", + produces = MediaType.APPLICATION_JSON_VALUE) + public DataTableResponse getTrustChainCertificatesTableData( + final DataTableInput input) { + log.debug("Handling list request for trust chain certificates: {}", input); + + // attempt to get the column property based on the order index. + String orderColumnName = input.getOrderColumnName(); + + log.debug("Ordering on column: {}", orderColumnName); + + String searchText = input.getSearch().getValue(); + List searchableColumns = findSearchableColumnsNames(input.getColumns()); + + int currentPage = input.getStart() / input.getLength(); + Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); + + FilteredRecordsList records = new FilteredRecordsList<>(); + + org.springframework.data.domain.Page pagedResult; + + if (StringUtils.isBlank(searchText)) { + pagedResult = + this.caCredentialRepository.findByArchiveFlag(false, pageable); + } else { + pagedResult = + this.certificateService.findBySearchableColumnsAndArchiveFlag( + CertificateAuthorityCredential.class, + searchableColumns, + searchText, + false, pageable); + } + + + if (pagedResult.hasContent()) { + records.addAll(pagedResult.getContent()); + records.setRecordsTotal(pagedResult.getContent().size()); + } else { + records.setRecordsTotal(input.getLength()); + } + + records.setRecordsFiltered(caCredentialRepository.findByArchiveFlag(false).size()); + + log.debug("Returning the size of the list of trust chain certificates: {}", records.size()); + return new DataTableResponse<>(records, input); + } + + /** + * Handles request to download the trust chain certificate by writing it to the response stream + * for download. + * + * @param id the UUID of the trust chain certificate to download + * @param response the response object (needed to update the header with the + * file name) + * @throws IOException when writing to response output stream + */ + @GetMapping("/download") + public void downloadSingleTrustChainCertificate( + @RequestParam final String id, + final HttpServletResponse response) + throws IOException { + log.info("Handling request to download {}", id); + + try { + UUID uuid = UUID.fromString(id); + Certificate certificate = this.certificateService.findCertificate(uuid); + + if (certificate == null) { + // Use the term "record" here to avoid user confusion b/t cert and cred + String notFoundMessage = "Unable to locate record with ID: " + uuid; + log.warn(notFoundMessage); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } else { + String fileName = "filename=\"" + CertificateAuthorityCredential.class.getSimpleName() + + "_" + + certificate.getSerialNumber() + + ".cer\""; + + // Set filename for download. + response.setHeader("Content-Disposition", "attachment;" + fileName); + response.setContentType("application/octet-stream"); + + // write cert to output stream + response.getOutputStream().write(certificate.getRawBytes()); + } + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from: " + id; + log.error(uuidError, ex); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + + /** + * 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 IOException when writing to response output stream + */ + @ResponseBody + @GetMapping("/download-aca-cert") + 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 IOException when writing to response output stream + */ + @GetMapping("/bulk-download") + public void bulkDownloadTrustChainCertificates(final HttpServletResponse response) + throws IOException { + log.info("Handling request to download all trust chain certificates"); + final 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())) { + // write trust chain certificates to output stream and bulk download them + this.certificateService.bulkDownloadCertificates(zipOut, TRUST_CHAIN, singleFileName); + } catch (Exception ex) { + log.error("Failed to bulk download trust chain certificates: ", ex); + // send a 404 error when invalid certificate + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + + /** + * Uploads and processes a trust chain certificate. + * + * @param files the files to process + * @param attr the redirection attributes + * @return the redirection view + * @throws URISyntaxException if malformed URI + */ + @PostMapping("/upload") + protected RedirectView uploadTrustChainCertificate( + @RequestParam("file") final MultipartFile[] files, + final RedirectAttributes attr) throws URISyntaxException { + + log.info("Handling request to upload one or more trust chain certificates"); + + Map model = new HashMap<>(); + PageMessages messages = new PageMessages(); + + List errorMessages = new ArrayList<>(); + List successMessages = new ArrayList<>(); + + for (MultipartFile file : files) { + //Parse trust chain certificate + CertificateAuthorityCredential parsedTrustChainCertificate = + parseTrustChainCertificate(file, messages); + + //Store only if it was parsed + if (parsedTrustChainCertificate != null) { + certificateService.storeCertificate( + TRUST_CHAIN, + file.getOriginalFilename(), + successMessages, errorMessages, parsedTrustChainCertificate); + } + + var a = successMessages; + var b = errorMessages; + } + + //Add messages to the model + model.put(MESSAGES_ATTRIBUTE, messages); + + return redirectTo(Page.TRUST_CHAIN, new NoPageParams(), model, attr); + } + + /** + * Archives (soft deletes) the trust chain certificate. + * + * @param id the UUID of the trust chain certificate to delete + * @param attr RedirectAttributes used to forward data back to the original + * page. + * @return redirect to this page + * @throws URISyntaxException if malformed URI + */ + @PostMapping("/delete") + public RedirectView deleteTrustChainCertificates( + @RequestParam final String id, + final RedirectAttributes attr) throws URISyntaxException { + log.info("Handling request to delete trust chain certificate id {}", id); + + Map model = new HashMap<>(); + PageMessages messages = new PageMessages(); + + try { + List successMessages = new ArrayList<>(); + List errorMessages = new ArrayList<>(); + + UUID uuid = UUID.fromString(id); + + this.certificateService.deleteCertificate(uuid, TRUST_CHAIN, + successMessages, errorMessages); + + var a = successMessages; + + } catch (IllegalArgumentException ex) { + String uuidError = "Failed to parse ID from: " + id; + messages.addError(uuidError); + log.error(uuidError, ex); + } catch (DBManagerException ex) { + String dbError = "Failed to archive cert: " + id; + messages.addError(dbError); + log.error(dbError, ex); + } + + + model.put(MESSAGES_ATTRIBUTE, messages); + return redirectTo(Page.TRUST_CHAIN, new NoPageParams(), model, attr); + } + + /** + * Helper method that returns a list of column names that are searchable. + * + * @return searchable column names + */ + private List findSearchableColumnsNames(List columns) { + + // Retrieve all searchable columns and collect their names into a list of strings. + return columns.stream().filter(Column::isSearchable).map(Column::getName) + .collect(Collectors.toList()); + } + + /** + * Attempts to parse the provided file in order to create a trust chain certificate. + * + * @param file file + * @param messages page messages + * @return trust chain certificate + */ + private CertificateAuthorityCredential parseTrustChainCertificate(MultipartFile file, + PageMessages messages) { + log.info("Received trust chain certificate file of size: {}", file.getSize()); + + byte[] fileBytes; + String fileName = file.getOriginalFilename(); + + // attempt to retrieve file bytes from the provided file + try { + fileBytes = file.getBytes(); + } catch (IOException ioEx) { + final String failMessage = String.format( + "Failed to read uploaded trust chain certificate file (%s): ", fileName); + log.error(failMessage, ioEx); + messages.addError(failMessage + ioEx.getMessage()); + return null; + } + + // attempt to build the trust chain certificates from the uploaded bytes + try { + if (CredentialHelper.isMultiPEM(new String(fileBytes, StandardCharsets.UTF_8))) { + try (ByteArrayInputStream certInputStream = new ByteArrayInputStream(fileBytes)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Collection c = + cf.generateCertificates(certInputStream); + + for (java.security.cert.Certificate certificate : c) { + List successMessages = new ArrayList<>(); + List errorMessages = new ArrayList<>(); + this.certificateService.storeCertificate( + TRUST_CHAIN, + file.getOriginalFilename(), + successMessages, + errorMessages, + new CertificateAuthorityCredential( + certificate.getEncoded())); + } + + // stop the main thread from saving/storing + return null; + } catch (CertificateException e) { + throw new IOException("Cannot construct X509Certificate from the input stream", + e); + } + } + return new CertificateAuthorityCredential(fileBytes); + } catch (IOException ioEx) { + final String failMessage = String.format( + "Failed to parse uploaded trust chain certificate file (%s): ", fileName); + log.error(failMessage, ioEx); + messages.addError(failMessage + ioEx.getMessage()); + return null; + } catch (DecoderException dEx) { + final String failMessage = String.format( + "Failed to parse uploaded trust chain certificate pem file (%s): ", fileName); + log.error(failMessage, dEx); + messages.addError(failMessage + dEx.getMessage()); + return null; + } catch (IllegalArgumentException iaEx) { + final String failMessage = String.format( + "Trust chain certificate format not recognized(%s): ", fileName); + log.error(failMessage, iaEx); + messages.addError(failMessage + iaEx.getMessage()); + return null; + } catch (IllegalStateException isEx) { + final String failMessage = String.format( + "Unexpected object while parsing trust chain certificate %s ", fileName); + log.error(failMessage, isEx); + messages.addError(failMessage + isEx.getMessage()); + return null; + } + } + +} diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/endorsement-key-credentials.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/endorsement-key-credentials.jsp index 2f5d3284..606d9526 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/endorsement-key-credentials.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/endorsement-key-credentials.jsp @@ -1,4 +1,4 @@ - +<%-- CONTENT --%>