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.
Some checks are pending
Dotnet Provisioner Unit Tests / Restore and Run Unit Tests (ubuntu-20.04) (push) Waiting to run
Dotnet Provisioner Unit Tests / Restore and Run Unit Tests (windows-2022) (push) Waiting to run
Dotnet Provisioner Unit Tests / Evaluate Tests (push) Blocked by required conditions
HIRS Build and Unit Test / ACA_Provisioner_Unit_Tests (push) Waiting to run
HIRS System Tests / DockerTests (push) Waiting to run

This commit is contained in:
ThatSilentCoder 2025-04-09 18:49:32 -04:00
parent 800010870a
commit 9d4b8eca31
15 changed files with 1039 additions and 1533 deletions

View File

@ -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 <T> 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<PlatformCredential> sharedCertificates = getPlatformCertificateByBoardSN(
@ -186,6 +197,7 @@ public class CertificateService {
existingCertificate.resetCreateTime();
this.certificateRepository.save(existingCertificate);
//todo
List<ComponentResult> 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<Certificate> 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<Certificate> 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");
};
}

View File

@ -142,7 +142,7 @@ public abstract class PageController<P extends PageParams> {
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<String, ?> e : params.asMap().entrySet()) {

View File

@ -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<NoPageParams> {
/**
* 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<? extends Certificate> 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<String, String> 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<String> findSearchableColumnsNames(List<Column> 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<? extends Certificate> 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<String> searchableColumns = findSearchableColumnsNames(input.getColumns());
int currentPage = input.getStart() / input.getLength();
Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName));
FilteredRecordsList<CertificateAuthorityCredential> records = new FilteredRecordsList<>();
org.springframework.data.domain.Page<CertificateAuthorityCredential> 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<? extends Certificate> 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<String> searchableColumns = findSearchableColumnsNames(input.getColumns());
int currentPage = input.getStart() / input.getLength();
Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName));
FilteredRecordsList<IDevIDCertificate> records = new FilteredRecordsList<>();
org.springframework.data.domain.Page<IDevIDCertificate> 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<? extends Certificate> 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<String> searchableColumns = findSearchableColumnsNames(input.getColumns());
int currentPage = input.getStart() / input.getLength();
Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName));
FilteredRecordsList<IssuedAttestationCertificate> records = new FilteredRecordsList<>();
org.springframework.data.domain.Page<IssuedAttestationCertificate> 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<String, Object> 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<String, Object> 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<PlatformCredential> 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<Certificate> 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<PlatformCredential> 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<ComponentResult> 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);
}
}

View File

@ -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<NoPagePa
private static final String ENDORSEMENT_CREDENTIALS = "endorsement-key-credentials";
private final CertificateRepository certificateRepository;
private final EndorsementCredentialRepository endorsementCredentialRepository;
private final CertificateService certificateService;
@Autowired
public EndorsementCredentialPageController(
final CertificateRepository certificateRepository,
final EndorsementCredentialRepository endorsementCredentialRepository,
final CertificateService certificateService) {
super(Page.TRUST_CHAIN);
this.certificateRepository = certificateRepository;
this.endorsementCredentialRepository = endorsementCredentialRepository;
this.certificateService = certificateService;
}
@ -83,13 +77,20 @@ public class EndorsementCredentialPageController extends PageController<NoPagePa
return getBaseModelAndView(Page.ENDORSEMENT_KEY_CREDENTIALS);
}
/**
* Retrieves the collection of endorsement credentials that will be displayed on the endorsement
* credentials page.
*
* @param input data table input received from the front-end
* @return data table of endorsement credentials
*/
@ResponseBody
@GetMapping(value = "/list",
produces = MediaType.APPLICATION_JSON_VALUE)
public DataTableResponse<EndorsementCredential> 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<NoPagePa
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
@ -156,20 +157,18 @@ public class EndorsementCredentialPageController extends PageController<NoPagePa
log.warn(notFoundMessage);
// send a 404 error when invalid certificate
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
if (certificate instanceof EndorsementCredential uploadedEndorsementCredential) {
String fileName = "filename=\"" + EndorsementCredential.class.getSimpleName()
+ "_"
+ uploadedEndorsementCredential.getSerialNumber()
+ ".cer\"";
} else if (certificate instanceof EndorsementCredential uploadedEndorsementCredential) {
String fileName = "filename=\"" + EndorsementCredential.class.getSimpleName()
+ "_"
+ uploadedEndorsementCredential.getSerialNumber()
+ ".cer\"";
// Set filename for download.
response.setHeader("Content-Disposition", "attachment;" + fileName);
response.setContentType("application/octet-stream");
// 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());
}
// write cert to output stream
response.getOutputStream().write(certificate.getRawBytes());
}
} catch (IllegalArgumentException ex) {
String uuidError = "Failed to parse ID from: " + id;
@ -180,7 +179,7 @@ public class EndorsementCredentialPageController extends PageController<NoPagePa
}
/**
* Handles request to download the certs by writing it to the response stream
* Handles request to download the endorsement credentials by writing it to the response stream
* for download in bulk.
*
* @param response the response object (needed to update the header with the
@ -188,29 +187,20 @@ public class EndorsementCredentialPageController extends PageController<NoPagePa
* @throws IOException when writing to response output stream
*/
@GetMapping("/bulk-download")
public void endorsementCredentialBulkDownload(final HttpServletResponse response)
public void bulkDownloadEndorsementCredentials(final HttpServletResponse response)
throws IOException {
log.info("Handling request to download all endorsement credentials");
final String fileName = "endorsement_certificates.zip";
final String singleFileName = "Endorsement_Certificates";
// Set filename for download.
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentType("application/zip");
try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
// find all the uploaded endorsement credentials
List<Certificate> certificates = this.certificateRepository.findByType("EndorsementCredential");
// convert the list of certificates to a list of endorsement credentials
List<EndorsementCredential> 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<NoPagePa
}
/**
* Upload and processes an endorsement credential.
* Uploads and processes an endorsement credential.
*
* @param files the files to process
* @param attr the redirection attributes
@ -228,7 +218,7 @@ public class EndorsementCredentialPageController extends PageController<NoPagePa
* @throws URISyntaxException if malformed URI
*/
@PostMapping("/upload")
protected RedirectView upload(
protected RedirectView uploadEndorsementCredential(
@RequestParam("file") final MultipartFile[] files,
final RedirectAttributes attr) throws URISyntaxException {
@ -242,14 +232,14 @@ public class EndorsementCredentialPageController extends PageController<NoPagePa
for (MultipartFile file : files) {
//Parse endorsement credential
EndorsementCredential parseEndorsementCredential = parseEndorsementCredential(file, messages);
EndorsementCredential parsedEndorsementCredential = parseEndorsementCredential(file, messages);
//Store only if it was parsed
if (parseEndorsementCredential != null) {
if (parsedEndorsementCredential != null) {
certificateService.storeCertificate(
ENDORSEMENT_CREDENTIALS,
file.getOriginalFilename(),
successMessages, errorMessages, parseEndorsementCredential);
successMessages, errorMessages, parsedEndorsementCredential);
}
}
@ -260,16 +250,16 @@ public class EndorsementCredentialPageController extends PageController<NoPagePa
}
/**
* Archives (soft delete) the endorsement credential.
* Archives (soft deletes) the endorsement credential.
*
* @param id the UUID of the endorsement cert to delete
* @param id the UUID of the endorsement 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 delete(
public RedirectView deleteEndorsementCredential(
@RequestParam final String id,
final RedirectAttributes attr) throws URISyntaxException {
log.info("Handling request to delete endorsement credential id {}", id);
@ -366,33 +356,4 @@ public class EndorsementCredentialPageController extends PageController<NoPagePa
return null;
}
}
/**
* Helper method that packages a collection of endorsement credentials into a zip file.
*
* @param zipOut zip outputs stream
* @param endorsementCredentials collection of endorsement credentials
* @throws IOException if there are any issues packaging or downloading the zip file
*/
private void bulkDownloadEndorsementCredentials(final ZipOutputStream zipOut,
final List<EndorsementCredential> 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();
}
}

View File

@ -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<NoPageParams> {
//
// 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<IDevIDCertificate> 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<String> searchableColumns = findSearchableColumnsNames(input.getColumns());
//
// int currentPage = input.getStart() / input.getLength();
// Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName));
//
// FilteredRecordsList<IDevIDCertificate> records = new FilteredRecordsList<>();
// org.springframework.data.domain.Page<IDevIDCertificate> 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<String> findSearchableColumnsNames(List<Column> 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<NoPageParams> {
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<IDevIDCertificate> 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<String> searchableColumns = findSearchableColumnsNames(input.getColumns());
int currentPage = input.getStart() / input.getLength();
Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName));
FilteredRecordsList<IDevIDCertificate> records = new FilteredRecordsList<>();
org.springframework.data.domain.Page<IDevIDCertificate> 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<String, Object> model = new HashMap<>();
PageMessages messages = new PageMessages();
List<String> errorMessages = new ArrayList<>();
List<String> 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<String, Object> model = new HashMap<>();
PageMessages messages = new PageMessages();
try {
List<String> successMessages = new ArrayList<>();
List<String> 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<String> findSearchableColumnsNames(List<Column> 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;
}
}
}

View File

@ -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<NoPageParams> {
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<NoPageParams> {
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<NoPageParams> {
}
/**
* 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<NoPageParams> {
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<NoPageParams> {
}
/**
* 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<NoPageParams> {
* @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<String, Object> model = new HashMap<>();
PageMessages messages = new PageMessages();
try {
List<String> successMessages = new ArrayList<>();
List<String> 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<NoPageParams> {
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<Certificate> 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;
}
}

View File

@ -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<NoPageParam
private static final String PLATFORM_CREDENTIALS = "platform-credentials";
private final CertificateRepository certificateRepository;
private final PlatformCertificateRepository platformCertificateRepository;
private final EndorsementCredentialRepository endorsementCredentialRepository;
private final CertificateService certificateService;
@Autowired
public PlatformCredentialPageController(final CertificateRepository certificateRepository,
final PlatformCertificateRepository platformCertificateRepository,
final EndorsementCredentialRepository endorsementCredentialRepository,
final CertificateService certificateService) {
public PlatformCredentialPageController(
final PlatformCertificateRepository platformCertificateRepository,
final EndorsementCredentialRepository endorsementCredentialRepository,
final CertificateService certificateService) {
super(Page.TRUST_CHAIN);
this.certificateRepository = certificateRepository;
this.platformCertificateRepository = platformCertificateRepository;
this.endorsementCredentialRepository = endorsementCredentialRepository;
this.certificateService = certificateService;
@ -87,6 +82,13 @@ public class PlatformCredentialPageController extends PageController<NoPageParam
return getBaseModelAndView(Page.PLATFORM_CREDENTIALS);
}
/**
* Retrieves the collection of platform credentials that will be displayed on the platform
* credentials page.
*
* @param input data table input received from the front-end
* @return data table of platform credentials
*/
@ResponseBody
@GetMapping(value = "/list",
produces = MediaType.APPLICATION_JSON_VALUE)
@ -154,10 +156,10 @@ public class PlatformCredentialPageController extends PageController<NoPageParam
}
/**
* Handles request to download the cert by writing it to the response stream
* Handles request to download the platform credential by writing it to the response stream
* for download.
*
* @param id the UUID of the cert to download
* @param id the UUID of the platform credential to download
* @param response the response object (needed to update the header with the
* file name)
* @throws IOException when writing to response output stream
@ -171,7 +173,7 @@ public class PlatformCredentialPageController extends PageController<NoPageParam
try {
UUID uuid = UUID.fromString(id);
Certificate certificate = certificateRepository.getCertificate(uuid);
Certificate certificate = this.certificateService.findCertificate(uuid);
if (certificate == null) {
log.warn("Unable to locate platform credential record with ID: {}", uuid);
@ -189,7 +191,7 @@ public class PlatformCredentialPageController extends PageController<NoPageParam
response.setHeader("Content-Disposition", "attachment;" + fileName);
response.setContentType("application/octet-stream");
// write cert to output stream
// write platform credential to output stream
response.getOutputStream().write(certificate.getRawBytes());
}
}
@ -201,7 +203,7 @@ public class PlatformCredentialPageController extends PageController<NoPageParam
}
/**
* Handles request to download the platform crednetials by writing it to the response stream
* Handles request to download the platform credentials by writing it to the response stream
* for download in bulk.
*
* @param response the response object (needed to update the header with the
@ -214,23 +216,15 @@ public class PlatformCredentialPageController extends PageController<NoPageParam
log.info("Handling request to download all platform credentials");
final String fileName = "platform_certificates.zip";
final String singleFileName = "Platform_Certificate";
// Set filename for download.
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentType("application/zip");
try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
// find all the uploaded platform credentials
List<Certificate> certificates = this.certificateRepository.findByType("PlatformCredential");
// convert the list of certificates to a list of platform credentials
List<PlatformCredential> 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<NoPageParam
/**
* Archives (soft delete) the platform credential.
*
* @param id the UUID of the platform cert to delete
* @param id the UUID of the platform credential 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 delete(
public RedirectView deletePlatformCredential(
@RequestParam final String id,
final RedirectAttributes attr) throws URISyntaxException {
log.info("Handling request to delete platform credential id {}", id);
@ -308,11 +302,11 @@ public class PlatformCredentialPageController extends PageController<NoPageParam
successMessages, errorMessages);
} catch (IllegalArgumentException ex) {
String uuidError = "Failed to parse ID from: " + id;
String uuidError = "Failed to parse platform credential ID from: " + id;
messages.addError(uuidError);
log.error(uuidError, ex);
} catch (DBManagerException ex) {
String dbError = "Failed to archive cert: " + id;
String dbError = "Failed to archive platform credential: " + id;
messages.addError(dbError);
log.error(dbError, ex);
}
@ -385,33 +379,4 @@ public class PlatformCredentialPageController extends PageController<NoPageParam
return null;
}
}
/**
* Helper method that packages a collection of platform credentials into a zip file.
*
* @param zipOut zip outputs stream
* @param platformCredentials collection of platform credentials
* @throws IOException if there are any issues packaging or downloading the zip file
*/
private void bulkDownloadPlatformCertificates(final ZipOutputStream zipOut,
final List<PlatformCredential> 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();
}
}

View File

@ -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<NoPageParams> {
//
// 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<String, String>(CertificateStringMapBuilder.getCertificateAuthorityInformation(
// certificateAuthorityCredential, this.certificateRepository,
// this.caCredentialRepository)));
//
// return mav;
// }
//
// @ResponseBody
// @GetMapping(value = "/list",
// produces = MediaType.APPLICATION_JSON_VALUE)
// public DataTableResponse<CertificateAuthorityCredential> 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<String> searchableColumns = findSearchableColumnsNames(input.getColumns());
//
// int currentPage = input.getStart() / input.getLength();
// Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName));
//
// FilteredRecordsList<CertificateAuthorityCredential> records = new FilteredRecordsList<>();
//
// org.springframework.data.domain.Page<CertificateAuthorityCredential> 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<String> findSearchableColumnsNames(List<Column> 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<Certificate> 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<NoPageParams> {
/**
* 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<CertificateAuthorityCredential> 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<String> searchableColumns = findSearchableColumnsNames(input.getColumns());
int currentPage = input.getStart() / input.getLength();
Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName));
FilteredRecordsList<CertificateAuthorityCredential> records = new FilteredRecordsList<>();
org.springframework.data.domain.Page<CertificateAuthorityCredential> 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<String, Object> model = new HashMap<>();
PageMessages messages = new PageMessages();
List<String> errorMessages = new ArrayList<>();
List<String> 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<String, Object> model = new HashMap<>();
PageMessages messages = new PageMessages();
try {
List<String> successMessages = new ArrayList<>();
List<String> 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<String> findSearchableColumnsNames(List<Column> 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<? extends java.security.cert.Certificate> c =
cf.generateCertificates(certInputStream);
for (java.security.cert.Certificate certificate : c) {
List<String> successMessages = new ArrayList<>();
List<String> 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;
}
}
}

View File

@ -1,4 +1,4 @@
<!-- <%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%-- JSP TAGS --%>
<%@taglib prefix="c" uri="jakarta.tags.core" %>
@ -6,7 +6,7 @@
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@taglib prefix="my" tagdir="/WEB-INF/tags"%>
<%-- CONTENT --%> -->
<%-- CONTENT --%>
<my:page>
<jsp:attribute name="script">
<script
@ -35,7 +35,7 @@
<a
href="${portal}/certificate-request/endorsement-key-credentials/bulk-download"
>
<!-- <img src="${icons}/ic_file_download_black_24dp.png" title="Download All Endorsement Certificates"> -->
<img src="${icons}/ic_file_download_black_24dp.png" title="Download All Endorsement Certificates">
</a>
</form:form>
</div>

View File

@ -7,7 +7,6 @@
<%@taglib prefix="my" tagdir="/WEB-INF/tags"%>
<%-- CONTENT --%>
<my:page>
<jsp:attribute name="script">
<script

View File

@ -5,7 +5,6 @@
<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@taglib prefix="my" tagdir="/WEB-INF/tags"%>
<%-- CONTENT --%>
<my:page>
<jsp:attribute name="script">
@ -87,7 +86,7 @@
</my:details-viewer>
<a href="${portal}/certificate-request/trust-chain/download-aca-cert">
<!-- <img src="${baseURL}/images/icons/ic_file_download_black_24dp.png" title="Download ACA Certificate"> -->
<img src="${baseURL}/images/icons/ic_file_download_black_24dp.png" title="Download ACA Certificate">
</a>
<div class="aca-input-box-header">
<form:form
@ -100,7 +99,7 @@
<input id="importFile" type="file" name="file" multiple="multiple" />
</my:file-chooser>
<a href="${portal}/certificate-request/trust-chain/bulk-download">
<!-- <img src="${icons}/ic_file_download_black_24dp.png" title="Download All Trust Chain Certificates"> -->
<img src="${icons}/ic_file_download_black_24dp.png" title="Download All Trust Chain Certificates">
</a>
</form:form>
</div>

View File

@ -162,21 +162,16 @@ public abstract class PageControllerTest {
throw new IOException("Could not resolve path URI", e);
}
switch (certificateClass.getSimpleName()) {
case "EndorsementCredential":
return new EndorsementCredential(fPath);
case "PlatformCredential":
return new PlatformCredential(fPath);
case "CertificateAuthorityCredential":
return new CertificateAuthorityCredential(fPath);
case "IssuedAttestationCertificate":
return new IssuedAttestationCertificate(fPath,
endorsementCredential, platformCredentials, false);
default:
throw new IllegalArgumentException(
String.format("Unknown certificate class %s", certificateClass.getName())
);
}
return switch (certificateClass.getSimpleName()) {
case "EndorsementCredential" -> new EndorsementCredential(fPath);
case "PlatformCredential" -> new PlatformCredential(fPath);
case "CertificateAuthorityCredential" -> new CertificateAuthorityCredential(fPath);
case "IssuedAttestationCertificate" -> new IssuedAttestationCertificate(fPath,
endorsementCredential, platformCredentials, false);
default -> throw new IllegalArgumentException(
String.format("Unknown certificate class %s", certificateClass.getName())
);
};
}
/**

View File

@ -1,6 +1,5 @@
package hirs.attestationca.portal.page.controllers;
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;
@ -37,16 +36,16 @@ public class EndorsementKeyCredentialsPageControllerTest extends PageControllerT
private static final String EKCERT = "certificates/fakeIntelIntermediateCA.pem";
private static final String BADEKCERT = "certificates/badCert.pem";
// Base path for the page
private String pagePath;
// Repository manager to handle data access between certificate entity and data storage in db
@Autowired
private CertificateRepository certificateRepository;
private final String pagePath;
// Repository manager to handle data access between endorsement certificate entity and data storage in db
@Autowired
private EndorsementCredentialRepository endorsementCredentialRepository;
// A file that contains a cert that is not an EK Cert. Should be parsable as a general cert,
// but should (eventually) not be stored as an EK because it isn't one.
private MockMultipartFile nonEkCertFile;
// A file that is not a cert at all, and just contains garbage text.
private MockMultipartFile badCertFile;

View File

@ -91,9 +91,9 @@ public class TrustChainManagementPageControllerTest extends PageControllerTest {
getMockMvc()
.perform(MockMvcRequestBuilders.get(pagePath))
.andExpect(status().isOk())
.andExpect(model().attributeExists(CertificatePageController.ACA_CERT_DATA))
.andExpect(model().attributeExists(TrustChainCertificatePageController.ACA_CERT_DATA))
.andExpect(model().attribute(
CertificatePageController.ACA_CERT_DATA,
TrustChainCertificatePageController.ACA_CERT_DATA,
hasEntry("issuer", "CN=Fake Root CA"))
);
}

0
b.txt
View File