From 9d4b8eca310a85e7461929953bc33e8905a436ef Mon Sep 17 00:00:00 2001 From: ThatSilentCoder <184309164+ThatSilentCoder@users.noreply.github.com> Date: Wed, 9 Apr 2025 18:49:32 -0400 Subject: [PATCH] v3_issue_811: successfully placed rest endpoints in their respective controllers. Search feature works fine. Found bug that exists in main branch as well (will fix it in here). Will now need to worry about searching just on individual comments, adding more javadocs/comments, and lastly fixing error/info messages display. --- .../persist/service/CertificateService.java | 103 +- .../portal/page/PageController.java | 2 +- .../CertificatePageController.java | 897 ------------------ .../EndorsementCredentialPageController.java | 103 +- .../IDevIdCertificatePageController.java | 478 +++++++--- .../IssuedCertificateController.java | 127 +-- .../PlatformCredentialPageController.java | 81 +- .../TrustChainCertificatePageController.java | 731 ++++++++------ .../jsp/endorsement-key-credentials.jsp | 6 +- .../WEB-INF/jsp/issued-certificates.jsp | 1 - .../main/webapp/WEB-INF/jsp/trust-chain.jsp | 5 +- .../portal/page/PageControllerTest.java | 25 +- ...ementKeyCredentialsPageControllerTest.java | 9 +- ...rustChainManagementPageControllerTest.java | 4 +- b.txt | 0 15 files changed, 1039 insertions(+), 1533 deletions(-) delete mode 100644 HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java delete mode 100644 b.txt 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 --%>