From 36ff777984fbd728737b264d7aa3933794716519 Mon Sep 17 00:00:00 2001 From: vasraz Date: Thu, 29 Jul 2021 14:41:18 +0100 Subject: Implement 'Signed Large CSAR' support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I33cc381b86c6a10e20d521c0d3dcc76c28344b8f Signed-off-by: Vasyl Razinkov Issue-ID: SDC-3652 Issue-ID: SDC-3653 Signed-off-by: André Schmid --- .../sdc/be/csar/storage/CsarSizeReducer.java | 153 ++++++++++++++--- .../sdc/be/csar/storage/CsarSizeReducerTest.java | 55 +++++- .../src/test/resources/csarSizeReducer/dummy.csar | Bin 25876 -> 0 bytes .../csarSizeReducer/dummyToNotReduce.csar | Bin 0 -> 24301 bytes .../resources/csarSizeReducer/dummyToReduce.csar | Bin 0 -> 25876 bytes .../resources/csarSizeReducer/dummyToReduce.zip | Bin 0 -> 23905 bytes .../OrchestrationTemplateCandidateImpl.java | 24 ++- .../OrchestrationTemplateCandidateImplTest.java | 12 +- .../OrchestrationTemplateCSARHandler.java | 10 +- .../csar/validation/CsarSecurityValidator.java | 34 ++-- .../security/SecurityManager.java | 188 +++++++++++++++------ .../csar/validation/CsarSecurityValidatorTest.java | 112 +++++++++--- .../security/SecurityManagerTest.java | 66 ++++++-- .../2-file-signed-package.zip | Bin 0 -> 3154 bytes .../3-file-signed-package.zip | Bin 0 -> 3446 bytes 15 files changed, 509 insertions(+), 145 deletions(-) delete mode 100644 common-be/src/test/resources/csarSizeReducer/dummy.csar create mode 100644 common-be/src/test/resources/csarSizeReducer/dummyToNotReduce.csar create mode 100644 common-be/src/test/resources/csarSizeReducer/dummyToReduce.csar create mode 100644 common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip create mode 100644 openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip create mode 100644 openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java index cf35c8c4d7..1fef373362 100644 --- a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java @@ -20,14 +20,24 @@ package org.openecomp.sdc.be.csar.storage; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + import java.io.BufferedOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; +import lombok.Getter; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.io.FilenameUtils; import org.openecomp.sdc.be.csar.storage.exception.CsarSizeReducerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +45,12 @@ import org.slf4j.LoggerFactory; public class CsarSizeReducer implements PackageSizeReducer { private static final Logger LOGGER = LoggerFactory.getLogger(CsarSizeReducer.class); + private static final Set ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms"); + private static final Set ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt"); + private static final String CSAR_EXTENSION = "csar"; + private static final String UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR = "An unexpected problem happened while reading the CSAR '%s'"; + @Getter + private final AtomicBoolean reduced = new AtomicBoolean(false); private final CsarPackageReducerConfiguration configuration; @@ -44,38 +60,31 @@ public class CsarSizeReducer implements PackageSizeReducer { @Override public byte[] reduce(final Path csarPackagePath) { + if (hasSignedPackageStructure(csarPackagePath)) { + return reduce(csarPackagePath, this::signedZipProcessingConsumer); + } else { + return reduce(csarPackagePath, this::unsignedZipProcessingConsumer); + } + } + + private byte[] reduce(final Path csarPackagePath, final ZipProcessFunction zipProcessingFunction) { final var reducedCsarPath = Path.of(csarPackagePath + "." + UUID.randomUUID()); try (final var zf = new ZipFile(csarPackagePath.toString()); final var zos = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(reducedCsarPath)))) { - - zf.entries().asIterator().forEachRemaining(entry -> { - final var entryName = entry.getName(); - try { - if (!entry.isDirectory()) { - zos.putNextEntry(new ZipEntry(entryName)); - if (isCandidateToRemove(entry)) { - // replace with EMPTY string to avoid package description inconsistency/validation errors - zos.write("".getBytes()); - } else { - zos.write(zf.getInputStream(entry).readAllBytes()); - } - } - zos.closeEntry(); - } catch (final IOException ei) { - final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath); - throw new CsarSizeReducerException(errorMsg, ei); - } - }); - + zf.entries().asIterator().forEachRemaining(zipProcessingFunction.getProcessZipConsumer(csarPackagePath, zf, zos)); } catch (final IOException ex1) { rollback(reducedCsarPath); - final var errorMsg = String.format("An unexpected problem happened while reading the CSAR '%s'", csarPackagePath); + final var errorMsg = String.format(UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR, csarPackagePath); throw new CsarSizeReducerException(errorMsg, ex1); } final byte[] reducedCsarBytes; try { - reducedCsarBytes = Files.readAllBytes(reducedCsarPath); + if (reduced.get()) { + reducedCsarBytes = Files.readAllBytes(reducedCsarPath); + } else { + reducedCsarBytes = Files.readAllBytes(csarPackagePath); + } } catch (final IOException e) { final var errorMsg = String.format("Could not read bytes of file '%s'", csarPackagePath); throw new CsarSizeReducerException(errorMsg, e); @@ -90,6 +99,51 @@ public class CsarSizeReducer implements PackageSizeReducer { return reducedCsarBytes; } + private Consumer signedZipProcessingConsumer(final Path csarPackagePath, final ZipFile zf, final ZipOutputStream zos) { + return zipEntry -> { + final var entryName = zipEntry.getName(); + try { + zos.putNextEntry(new ZipEntry(entryName)); + if (!zipEntry.isDirectory()) { + if (entryName.toLowerCase().endsWith(CSAR_EXTENSION)) { + final var internalCsarExtractPath = Path.of(csarPackagePath + "." + UUID.randomUUID()); + Files.copy(zf.getInputStream(zipEntry), internalCsarExtractPath, REPLACE_EXISTING); + zos.write(reduce(internalCsarExtractPath, this::unsignedZipProcessingConsumer)); + Files.delete(internalCsarExtractPath); + } else { + zos.write(zf.getInputStream(zipEntry).readAllBytes()); + } + } + zos.closeEntry(); + } catch (final IOException ei) { + final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath); + throw new CsarSizeReducerException(errorMsg, ei); + } + }; + } + + private Consumer unsignedZipProcessingConsumer(final Path csarPackagePath, final ZipFile zf, final ZipOutputStream zos) { + return zipEntry -> { + final var entryName = zipEntry.getName(); + try { + zos.putNextEntry(new ZipEntry(entryName)); + if (!zipEntry.isDirectory()) { + if (isCandidateToRemove(zipEntry)) { + // replace with EMPTY string to avoid package description inconsistency/validation errors + zos.write("".getBytes()); + reduced.set(true); + } else { + zos.write(zf.getInputStream(zipEntry).readAllBytes()); + } + } + zos.closeEntry(); + } catch (final IOException ei) { + final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath); + throw new CsarSizeReducerException(errorMsg, ei); + } + }; + } + private void rollback(final Path reducedCsarPath) { if (Files.exists(reducedCsarPath)) { try { @@ -106,4 +160,59 @@ public class CsarSizeReducer implements PackageSizeReducer { || zipEntry.getSize() > configuration.getSizeLimit(); } + private boolean hasSignedPackageStructure(final Path csarPackagePath) { + final List packagePathList; + try (final var zf = new ZipFile(csarPackagePath.toString())) { + packagePathList = zf.stream() + .filter(zipEntry -> !zipEntry.isDirectory()) + .map(ZipEntry::getName).map(Path::of) + .collect(Collectors.toList()); + } catch (final IOException e) { + final var errorMsg = String.format(UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR, csarPackagePath); + throw new CsarSizeReducerException(errorMsg, e); + } + + if (CollectionUtils.isEmpty(packagePathList)) { + return false; + } + final int numberOfFiles = packagePathList.size(); + if (numberOfFiles == 2) { + return hasOneInternalPackageFile(packagePathList) && hasOneSignatureFile(packagePathList); + } + if (numberOfFiles == 3) { + return hasOneInternalPackageFile(packagePathList) && hasOneSignatureFile(packagePathList) && hasOneCertificateFile(packagePathList); + } + return false; + } + + private boolean hasOneInternalPackageFile(final List packagePathList) { + return packagePathList.parallelStream() + .map(Path::toString) + .map(FilenameUtils::getExtension) + .map(String::toLowerCase) + .filter(extension -> extension.endsWith(CSAR_EXTENSION)).count() == 1; + } + + private boolean hasOneSignatureFile(final List packagePathList) { + return packagePathList.parallelStream() + .map(Path::toString) + .map(FilenameUtils::getExtension) + .map(String::toLowerCase) + .filter(ALLOWED_SIGNATURE_EXTENSIONS::contains).count() == 1; + } + + private boolean hasOneCertificateFile(final List packagePathList) { + return packagePathList.parallelStream() + .map(Path::toString) + .map(FilenameUtils::getExtension) + .map(String::toLowerCase) + .filter(ALLOWED_CERTIFICATE_EXTENSIONS::contains).count() == 1; + } + + @FunctionalInterface + private interface ZipProcessFunction { + + Consumer getProcessZipConsumer(Path csarPackagePath, ZipFile zf, ZipOutputStream zos); + } + } diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java index c7586446f7..eaa5ffeda2 100644 --- a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java @@ -23,7 +23,9 @@ package org.openecomp.sdc.be.csar.storage; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.when; import java.nio.charset.StandardCharsets; @@ -32,7 +34,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -51,15 +54,16 @@ class CsarSizeReducerTest { MockitoAnnotations.openMocks(this); } - @Test - void reduceByPathAndSizeTest() throws ZipException { + @ParameterizedTest + @ValueSource(strings = {"dummyToReduce.zip", "dummyToReduce.csar", "dummyToNotReduce.csar"}) + void reduceByPathAndSizeTest(String fileName) throws ZipException { final var pathToReduce1 = Path.of("Files/images"); final var pathToReduce2 = Path.of("Files/Scripts/my_script.sh"); final var sizeLimit = 150000L; when(csarPackageReducerConfiguration.getSizeLimit()).thenReturn(sizeLimit); when(csarPackageReducerConfiguration.getFoldersToStrip()).thenReturn(Set.of(pathToReduce1, pathToReduce2)); - final var csarPath = Path.of("src/test/resources/csarSizeReducer/dummy.csar"); + final var csarPath = Path.of("src/test/resources/csarSizeReducer/" + fileName); final Map originalCsar = ZipUtils.readZip(csarPath.toFile(), false); @@ -74,14 +78,47 @@ class CsarSizeReducerTest { assertTrue(reducedCsar.containsKey(originalFilePath), String.format("No file should be removed, but it is missing original file '%s'", originalFilePath)); - if (originalFilePath.startsWith(pathToReduce1.toString()) || originalFilePath.startsWith(pathToReduce2.toString()) - || originalBytes.length > sizeLimit) { - assertArrayEquals("".getBytes(StandardCharsets.UTF_8), reducedCsar.get(originalFilePath), - String.format("File '%s' expected to be reduced to empty string", originalFilePath)); + final String extention = fileName.substring(fileName.lastIndexOf('.') + 1); + switch (extention.toLowerCase()) { + case "zip": + verifyZIP(pathToReduce1, pathToReduce2, sizeLimit, reducedCsar, originalFilePath, originalBytes); + break; + case "csar": + verifyCSAR(pathToReduce1, pathToReduce2, sizeLimit, reducedCsar, originalFilePath, originalBytes); + break; + default: + fail("Unexpected file extention"); + break; + } + } + } + + private void verifyCSAR(final Path pathToReduce1, final Path pathToReduce2, final long sizeLimit, final Map reducedCsar, + final String originalFilePath, final byte[] originalBytes) { + if (originalFilePath.startsWith(pathToReduce1.toString()) || originalFilePath.startsWith(pathToReduce2.toString()) + || originalBytes.length > sizeLimit) { + assertArrayEquals("".getBytes(StandardCharsets.UTF_8), reducedCsar.get(originalFilePath), + String.format("File '%s' expected to be reduced to empty string", originalFilePath)); + } else { + assertArrayEquals(originalBytes, reducedCsar.get(originalFilePath), + String.format("File '%s' expected to be equal", originalFilePath)); + } + } + + private void verifyZIP(final Path pathToReduce1, final Path pathToReduce2, final long sizeLimit, final Map reducedCsar, + final String originalFilePath, final byte[] originalBytes) { + if (originalFilePath.startsWith(pathToReduce1.toString()) || originalFilePath.startsWith(pathToReduce2.toString()) + || originalBytes.length > sizeLimit) { + assertArrayEquals("".getBytes(StandardCharsets.UTF_8), reducedCsar.get(originalFilePath), + String.format("File '%s' expected to be reduced to empty string", originalFilePath)); + } else { + if (originalFilePath.endsWith(".csar") && csarSizeReducer.getReduced().get()) { + assertNotEquals(originalBytes.length, reducedCsar.get(originalFilePath).length, + String.format("File '%s' expected to be NOT equal", originalFilePath)); } else { assertArrayEquals(originalBytes, reducedCsar.get(originalFilePath), String.format("File '%s' expected to be equal", originalFilePath)); } } } -} \ No newline at end of file +} diff --git a/common-be/src/test/resources/csarSizeReducer/dummy.csar b/common-be/src/test/resources/csarSizeReducer/dummy.csar deleted file mode 100644 index 73b28f52fd..0000000000 Binary files a/common-be/src/test/resources/csarSizeReducer/dummy.csar and /dev/null differ diff --git a/common-be/src/test/resources/csarSizeReducer/dummyToNotReduce.csar b/common-be/src/test/resources/csarSizeReducer/dummyToNotReduce.csar new file mode 100644 index 0000000000..d44041382e Binary files /dev/null and b/common-be/src/test/resources/csarSizeReducer/dummyToNotReduce.csar differ diff --git a/common-be/src/test/resources/csarSizeReducer/dummyToReduce.csar b/common-be/src/test/resources/csarSizeReducer/dummyToReduce.csar new file mode 100644 index 0000000000..73b28f52fd Binary files /dev/null and b/common-be/src/test/resources/csarSizeReducer/dummyToReduce.csar differ diff --git a/common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip b/common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip new file mode 100644 index 0000000000..fecb45aaaf Binary files /dev/null and b/common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip differ diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImpl.java b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImpl.java index 10f6012a76..19f2c5df87 100644 --- a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImpl.java +++ b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/main/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImpl.java @@ -160,17 +160,21 @@ public class OrchestrationTemplateCandidateImpl implements OrchestrationTemplate try { packageInputStream = fileToUpload.getDataHandler().getInputStream(); } catch (final IOException e) { - return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError(new ErrorMessage(ErrorLevel.ERROR, UNEXPECTED_PROBLEM_HAPPENED_WHILE_GETTING.formatMessage(filename)))).build(); + return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError( + new ErrorMessage(ErrorLevel.ERROR, UNEXPECTED_PROBLEM_HAPPENED_WHILE_GETTING.formatMessage(filename)))).build(); } try { artifactInfo = artifactStorageManager.upload(vspId, versionId, packageInputStream); } catch (final BusinessException e) { - return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError(new ErrorMessage(ErrorLevel.ERROR, ERROR_HAS_OCCURRED_WHILE_PERSISTING_THE_ARTIFACT.formatMessage(filename)))).build(); + return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError( + new ErrorMessage(ErrorLevel.ERROR, ERROR_HAS_OCCURRED_WHILE_PERSISTING_THE_ARTIFACT.formatMessage(filename)))).build(); } try { fileToUploadBytes = packageSizeReducer.reduce(artifactInfo.getPath()); } catch (final BusinessException e) { - return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError(new ErrorMessage(ErrorLevel.ERROR, ERROR_HAS_OCCURRED_WHILE_REDUCING_THE_ARTIFACT_SIZE.formatMessage(artifactInfo.getPath())))).build(); + return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError( + new ErrorMessage(ErrorLevel.ERROR, ERROR_HAS_OCCURRED_WHILE_REDUCING_THE_ARTIFACT_SIZE.formatMessage(artifactInfo.getPath())))) + .build(); } } else { fileToUploadBytes = fileToUpload.getObject(byte[].class); @@ -183,10 +187,12 @@ public class OrchestrationTemplateCandidateImpl implements OrchestrationTemplate } final var onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); if (onboardPackageInfo == null) { - return Response.ok(buildUploadResponseWithError(new ErrorMessage(ErrorLevel.ERROR, PACKAGE_PROCESS_ERROR.formatMessage(filename)))).build(); + final UploadFileResponseDto uploadFileResponseDto = buildUploadResponseWithError( + new ErrorMessage(ErrorLevel.ERROR, PACKAGE_PROCESS_ERROR.formatMessage(filename))); + return Response.ok(uploadFileResponseDto).build(); } - final var vspDetails = new VspDetails(ValidationUtils.sanitizeInputString(vspId), - new Version(ValidationUtils.sanitizeInputString(versionId))); + final var version = new Version(ValidationUtils.sanitizeInputString(versionId)); + final var vspDetails = new VspDetails(ValidationUtils.sanitizeInputString(vspId), version); return processOnboardPackage(onboardPackageInfo, vspDetails, errorMessages); } @@ -224,7 +230,8 @@ public class OrchestrationTemplateCandidateImpl implements OrchestrationTemplate } else { zipFile = vendorSoftwareProductManager.get(vspId, new Version((versionId))); if (!zipFile.isPresent()) { - ErrorMessage errorMessage = new ErrorMessage(ErrorLevel.ERROR, getErrorWithParameters(NO_FILE_WAS_UPLOADED_OR_FILE_NOT_EXIST.getErrorMessage(), "")); + ErrorMessage errorMessage = new ErrorMessage(ErrorLevel.ERROR, + getErrorWithParameters(NO_FILE_WAS_UPLOADED_OR_FILE_NOT_EXIST.getErrorMessage(), "")); LOGGER.error(errorMessage.getMessage()); return Response.status(NOT_FOUND).build(); } @@ -255,7 +262,8 @@ public class OrchestrationTemplateCandidateImpl implements OrchestrationTemplate FilesDataStructure fileDataStructure = copyFilesDataStructureDtoToFilesDataStructure(fileDataStructureDto); ValidationResponse response = candidateManager.updateFilesDataStructure(vspId, new Version(versionId), fileDataStructure); if (!response.isValid()) { - return Response.status(EXPECTATION_FAILED).entity(new MapValidationResponseToDto().applyMapping(response, ValidationResponseDto.class)).build(); + return Response.status(EXPECTATION_FAILED).entity(new MapValidationResponseToDto().applyMapping(response, ValidationResponseDto.class)) + .build(); } return Response.ok(fileDataStructureDto).build(); } diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java index b89756e501..edf29b75c5 100644 --- a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java +++ b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java @@ -74,7 +74,10 @@ import org.openecomp.sdcrests.vendorsoftwareproducts.types.UploadFileResponseDto class OrchestrationTemplateCandidateImplTest { private final Logger logger = LoggerFactory.getLogger(OrchestrationTemplateCandidateImplTest.class); - + private final String candidateId = UUID.randomUUID().toString(); + private final String softwareProductId = UUID.randomUUID().toString(); + private final String versionId = UUID.randomUUID().toString(); + private final String user = "cs0008"; @Mock private OrchestrationTemplateCandidateManager candidateManager; @Mock @@ -85,15 +88,8 @@ class OrchestrationTemplateCandidateImplTest { private ArtifactStorageManager artifactStorageManager; @Mock private PackageSizeReducer packageSizeReducer; - private OrchestrationTemplateCandidateImpl orchestrationTemplateCandidate; - private final String candidateId = UUID.randomUUID().toString(); - private final String softwareProductId = UUID.randomUUID().toString(); - private final String versionId = UUID.randomUUID().toString(); - - private final String user = "cs0008"; - @BeforeEach public void setUp() { try { diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/OrchestrationTemplateCSARHandler.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/OrchestrationTemplateCSARHandler.java index f1062395af..f1cab482f2 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/OrchestrationTemplateCSARHandler.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/OrchestrationTemplateCSARHandler.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.util.Optional; import org.openecomp.core.utilities.file.FileContentHandler; import org.openecomp.core.utilities.orchestration.OnboardingTypesEnum; +import org.openecomp.sdc.be.csar.storage.ArtifactInfo; import org.openecomp.sdc.common.errors.CoreException; import org.openecomp.sdc.common.errors.Messages; import org.openecomp.sdc.common.utils.SdcCommon; @@ -49,7 +50,8 @@ public class OrchestrationTemplateCSARHandler extends BaseOrchestrationTemplateH final UploadFileResponse uploadFileResponse = new UploadFileResponse(); if (onboardPackageInfo.getPackageType() == OnboardingTypesEnum.SIGNED_CSAR) { final OnboardSignedPackage originalOnboardPackage = (OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(); - validatePackageSecurity(originalOnboardPackage).ifPresent(packageSignatureResponse -> { + final ArtifactInfo artifactInfo = onboardPackageInfo.getArtifactInfo(); + validatePackageSecurity(originalOnboardPackage, artifactInfo).ifPresent(packageSignatureResponse -> { if (packageSignatureResponse.hasErrors()) { uploadFileResponse.addStructureErrors(packageSignatureResponse.getErrors()); } @@ -74,11 +76,11 @@ public class OrchestrationTemplateCSARHandler extends BaseOrchestrationTemplateH return uploadFileResponse; } - private Optional validatePackageSecurity(final OnboardSignedPackage originalOnboardPackage) { + private Optional validatePackageSecurity(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo) { final UploadFileResponse uploadFileResponseDto = new UploadFileResponse(); try { final CsarSecurityValidator csarSecurityValidator = new CsarSecurityValidator(); - if (!csarSecurityValidator.verifyPackageSignature(originalOnboardPackage)) { + if (!csarSecurityValidator.verifyPackageSignature(signedPackage, artifactInfo)) { final ErrorMessage errorMessage = new ErrorMessage(ErrorLevel.ERROR, Messages.FAILED_TO_VERIFY_SIGNATURE.getErrorMessage()); logger.error(errorMessage.getMessage()); uploadFileResponseDto.addStructureError(SdcCommon.UPLOAD_FILE, errorMessage); @@ -86,7 +88,7 @@ public class OrchestrationTemplateCSARHandler extends BaseOrchestrationTemplateH } } catch (final SecurityManagerException e) { final ErrorMessage errorMessage = new ErrorMessage(ErrorLevel.ERROR, e.getMessage()); - logger.error("Could not validate package signature {}", originalOnboardPackage.getFilename(), e); + logger.error("Could not validate package signature {}", signedPackage.getFilename(), e); uploadFileResponseDto.addStructureError(SdcCommon.UPLOAD_FILE, errorMessage); return Optional.of(uploadFileResponseDto); } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java index 81a17f333b..bf5abe3737 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java @@ -19,7 +19,8 @@ package org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation; import java.util.Optional; -import org.openecomp.core.utilities.file.FileContentHandler; +import lombok.NoArgsConstructor; +import org.openecomp.sdc.be.csar.storage.ArtifactInfo; import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManager; import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManagerException; import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; @@ -27,13 +28,11 @@ import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; /** * Validates the package security */ +@NoArgsConstructor public class CsarSecurityValidator { private SecurityManager securityManager = SecurityManager.getInstance(); - public CsarSecurityValidator() { - } - //for tests purpose CsarSecurityValidator(final SecurityManager securityManager) { this.securityManager = securityManager; @@ -45,15 +44,24 @@ public class CsarSecurityValidator { * @return true if signature verified * @throws SecurityManagerException when a certificate error occurs. */ - public boolean verifyPackageSignature(final OnboardSignedPackage signedPackage) throws SecurityManagerException { - final FileContentHandler fileContentHandler = signedPackage.getFileContentHandler(); - final byte[] signatureBytes = fileContentHandler.getFileContent(signedPackage.getSignatureFilePath()); - final byte[] archiveBytes = fileContentHandler.getFileContent(signedPackage.getInternalPackageFilePath()); - byte[] certificateBytes = null; - final Optional certificateFilePath = signedPackage.getCertificateFilePath(); - if (certificateFilePath.isPresent()) { - certificateBytes = fileContentHandler.getFileContent(certificateFilePath.get()); + public boolean verifyPackageSignature(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo) throws SecurityManagerException { + if (isArtifactInfoPresent(artifactInfo)) { + return securityManager.verifyPackageSignedData(signedPackage, artifactInfo); + } else { + final var fileContentHandler = signedPackage.getFileContentHandler(); + final byte[] signatureBytes = fileContentHandler.getFileContent(signedPackage.getSignatureFilePath()); + final byte[] archiveBytes = fileContentHandler.getFileContent(signedPackage.getInternalPackageFilePath()); + byte[] certificateBytes = null; + final Optional certificateFilePath = signedPackage.getCertificateFilePath(); + if (certificateFilePath.isPresent()) { + certificateBytes = fileContentHandler.getFileContent(certificateFilePath.get()); + } + return securityManager.verifySignedData(signatureBytes, certificateBytes, archiveBytes); } - return securityManager.verifySignedData(signatureBytes, certificateBytes, archiveBytes); } + + private boolean isArtifactInfoPresent(final ArtifactInfo artifactInfo) { + return artifactInfo != null && artifactInfo.getPath() != null; + } + } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java index d60b54b5e1..fec15b5fcc 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java @@ -19,14 +19,17 @@ */ package org.openecomp.sdc.vendorsoftwareproduct.security; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + import com.google.common.collect.ImmutableSet; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; @@ -48,22 +51,28 @@ import java.util.Collection; import java.util.HashSet; import java.util.Optional; import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.ZipFile; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSProcessableFile; import org.bouncycastle.cms.CMSSignedData; -import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.operator.OperatorCreationException; +import org.openecomp.sdc.be.csar.storage.ArtifactInfo; +import org.openecomp.sdc.common.errors.SdcRuntimeException; import org.openecomp.sdc.logging.api.Logger; import org.openecomp.sdc.logging.api.LoggerFactory; +import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; /** * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be reviewed Class is responsible @@ -71,13 +80,12 @@ import org.openecomp.sdc.logging.api.LoggerFactory; */ public class SecurityManager { + public static final Set ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms"); + public static final Set ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt"); private static final String CERTIFICATE_DEFAULT_LOCATION = "cert"; - public static final Set ALLOWED_SIGNATURE_EXTENSIONS = ImmutableSet.of("cms"); - public static final Set ALLOWED_CERTIFICATE_EXTENSIONS = ImmutableSet.of("cert", "crt"); - private Logger logger = LoggerFactory.getLogger(SecurityManager.class); - private Set trustedCertificates = new HashSet<>(); - private Set trustedCertificatesFromPackage = new HashSet<>(); - private File certificateDirectory; + private static final Logger logger = LoggerFactory.getLogger(SecurityManager.class); + private static final String UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION = "Unexpected error occurred during signature validation!"; + private static final String COULD_NOT_VERIFY_SIGNATURE = "Could not verify signature!"; static { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { @@ -85,6 +93,10 @@ public class SecurityManager { } } + private Set trustedCertificates = new HashSet<>(); + private Set trustedCertificatesFromPackage = new HashSet<>(); + private File certificateDirectory; + private SecurityManager() { certificateDirectory = this.getcertDirectory(System.getenv("SDC_CERT_DIR")); } @@ -98,21 +110,13 @@ public class SecurityManager { return SecurityManagerInstanceHolder.instance; } - /** - * Initialization on demand class / synchronized singleton pattern. - */ - private static class SecurityManagerInstanceHolder { - - private static final SecurityManager instance = new SecurityManager(); - } - /** * Checks the configured location for available trustedCertificates * * @return set of trustedCertificates * @throws SecurityManagerException */ - public Set getTrustedCertificates() throws SecurityManagerException, FileNotFoundException { + public Set getTrustedCertificates() throws SecurityManagerException { //if file number in certificate directory changed reload certs String[] certFiles = certificateDirectory.list(); if (certFiles == null) { @@ -145,43 +149,121 @@ public class SecurityManager { * @return true if signature verified * @throws SecurityManagerException */ - public boolean verifySignedData(final byte[] messageSyntaxSignature, final byte[] packageCert, final byte[] innerPackageFile) - throws SecurityManagerException { - try (ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature); final PEMParser pemParser = new PEMParser( - new InputStreamReader(signatureStream))) { + public boolean verifySignedData(final byte[] messageSyntaxSignature, + final byte[] packageCert, + final byte[] innerPackageFile) throws SecurityManagerException { + try (final ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature); + final PEMParser pemParser = new PEMParser(new InputStreamReader(signatureStream))) { final Object parsedObject = pemParser.readObject(); if (!(parsedObject instanceof ContentInfo)) { throw new SecurityManagerException("Signature is not recognized"); } - final ContentInfo signature = ContentInfo.getInstance(parsedObject); - final CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile); - final CMSSignedData signedData = new CMSSignedData(signedContent, signature); - final Collection signers = signedData.getSignerInfos().getSigners(); - final SignerInformation firstSigner = signers.iterator().next(); - final X509Certificate cert; - Collection certs; - if (packageCert == null) { - certs = signedData.getCertificates().getMatches(null); - cert = readSignCert(certs, firstSigner) - .orElseThrow(() -> new SecurityManagerException("No certificate found in cms signature that should contain one!")); - } else { - certs = parseCertsFromPem(packageCert); - cert = readSignCert(certs, firstSigner) - .orElseThrow(() -> new SecurityManagerException("No matching certificate found in certificate file that should contain one!")); + return verify(packageCert, new CMSSignedData(new CMSProcessableByteArray(innerPackageFile), ContentInfo.getInstance(parsedObject))); + } catch (final IOException | CMSException e) { + logger.error(e.getMessage(), e); + throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); + } + } + + public boolean verifyPackageSignedData(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo) + throws SecurityManagerException { + boolean fail = false; + final var fileContentHandler = signedPackage.getFileContentHandler(); + byte[] packageCert = null; + final Optional certificateFilePath = signedPackage.getCertificateFilePath(); + if (certificateFilePath.isPresent()) { + packageCert = fileContentHandler.getFileContent(certificateFilePath.get()); + } + final var path = artifactInfo.getPath(); + final var target = Path.of(path.toString() + "." + UUID.randomUUID()); + + try (final var signatureStream = new ByteArrayInputStream(fileContentHandler.getFileContent(signedPackage.getSignatureFilePath())); + final var pemParser = new PEMParser(new InputStreamReader(signatureStream))) { + final var parsedObject = pemParser.readObject(); + if (!(parsedObject instanceof ContentInfo)) { + fail = true; + throw new SecurityManagerException("Signature is not recognized"); } - trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner); - if (verifyCertificate(cert, getTrustedCertificates()) == null) { + + if (!findCSARandExtract(path, target)) { + fail = true; return false; } - return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); - } catch (OperatorCreationException | IOException | CMSException e) { + final var verify = verify(packageCert, new CMSSignedData(new CMSProcessableFile(target.toFile()), ContentInfo.getInstance(parsedObject))); + fail = !verify; + return verify; + } catch (final IOException e) { + fail = true; logger.error(e.getMessage(), e); - throw new SecurityManagerException("Unexpected error occurred during signature validation!", e); - } catch (GeneralSecurityException e) { - throw new SecurityManagerException("Could not verify signature!", e); + throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); + } catch (final CMSException e) { + fail = true; + throw new SecurityManagerException(COULD_NOT_VERIFY_SIGNATURE, e); + } catch (final SecurityManagerException e) { + fail = true; + throw e; + } finally { + deleteFile(target); + if (fail) { + deleteFile(path); + } + } + } + + private void deleteFile(final Path filePath) { + try { + Files.delete(filePath); + } catch (final IOException e) { + logger.warn("Failed to delete '{}' after verifying package signed data", filePath, e); } } + private boolean verify(final byte[] packageCert, final CMSSignedData signedData) throws SecurityManagerException { + final SignerInformation firstSigner = signedData.getSignerInfos().getSigners().iterator().next(); + final X509Certificate cert; + Collection certs; + if (packageCert == null) { + certs = signedData.getCertificates().getMatches(null); + cert = readSignCert(certs, firstSigner) + .orElseThrow(() -> new SecurityManagerException("No certificate found in cms signature that should contain one!")); + } else { + try { + certs = parseCertsFromPem(packageCert); + } catch (final IOException e) { + throw new SecurityManagerException("Failed to parse certificate from PEM", e); + } + cert = readSignCert(certs, firstSigner) + .orElseThrow(() -> new SecurityManagerException("No matching certificate found in certificate file that should contain one!")); + } + trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner); + if (verifyCertificate(cert, getTrustedCertificates()) == null) { + return false; + } + try { + return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); + } catch (CMSException | OperatorCreationException e) { + throw new SecurityManagerException("Failed to verify package signed data", e); + } + } + + private boolean findCSARandExtract(final Path path, final Path target) throws IOException { + final AtomicBoolean found = new AtomicBoolean(false); + try (final var zf = new ZipFile(path.toString())) { + zf.entries().asIterator().forEachRemaining(entry -> { + final var entryName = entry.getName(); + if (!entry.isDirectory() && entryName.toLowerCase().endsWith(".csar")) { + try { + Files.copy(zf.getInputStream(entry), target, REPLACE_EXISTING); + } catch (final IOException e) { + throw new SdcRuntimeException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); + } + found.set(true); + } + }); + } + return found.get(); + } + private Optional readSignCert(final Collection certs, final SignerInformation firstSigner) { return certs.stream().filter(crt -> firstSigner.getSID().match(crt)).findAny().map(this::loadCertificate); } @@ -205,7 +287,7 @@ public class SecurityManager { return allCerts; } - private void processCertificateDir() throws SecurityManagerException, FileNotFoundException { + private void processCertificateDir() throws SecurityManagerException { if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) { logger.error("Issue with certificate directory, check if exists!"); return; @@ -253,8 +335,8 @@ public class SecurityManager { } } - private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, Set additionalCerts) - throws GeneralSecurityException, SecurityManagerException { + private PKIXCertPathBuilderResult verifyCertificate(final X509Certificate cert, + final Set additionalCerts) throws SecurityManagerException { if (null == cert) { throw new SecurityManagerException("The certificate is empty!"); } @@ -273,7 +355,11 @@ public class SecurityManager { intermediateCerts.add(additionalCert); } } - return verifyCertificate(cert, trustedRootCerts, intermediateCerts); + try { + return verifyCertificate(cert, trustedRootCerts, intermediateCerts); + } catch (final GeneralSecurityException e) { + throw new SecurityManagerException("Failed to verify certificate", e); + } } private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, Set allTrustedRootCerts, @@ -325,4 +411,12 @@ public class SecurityManager { private boolean isSelfSigned(X509Certificate cert) { return cert.getIssuerDN().equals(cert.getSubjectDN()); } + + /** + * Initialization on demand class / synchronized singleton pattern. + */ + private static class SecurityManagerInstanceHolder { + + private static final SecurityManager instance = new SecurityManager(); + } } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java index 5f5f9eb7dc..96d11eb148 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java @@ -19,6 +19,7 @@ package org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -27,12 +28,21 @@ import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; -import org.junit.Before; -import org.junit.Test; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; +import org.openecomp.sdc.be.csar.storage.ArtifactInfo; +import org.openecomp.sdc.be.csar.storage.PersistentStorageArtifactInfo; import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.OnboardingPackageProcessor; import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.validation.CnfPackageValidator; import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManager; @@ -40,37 +50,88 @@ import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManagerException import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardPackageInfo; import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; -public class CsarSecurityValidatorTest { +class CsarSecurityValidatorTest { - private static final String BASE_DIR = "/vspmanager.csar/"; + private static final String BASE_DIR = "/vspmanager.csar/signing/"; + private static final String DELIMITER = "---"; private CsarSecurityValidator csarSecurityValidator; @Mock - SecurityManager securityManager; + private SecurityManager securityManager; - @Before - public void setUp() { + @AfterEach + void tearDown() throws Exception { + restore(); + } + + private void restore() throws Exception { + final URI uri = CsarSecurityValidatorTest.class.getResource(BASE_DIR).toURI(); + final List list = Files.list(Path.of(uri.getPath())).filter(path -> path.toString().contains(DELIMITER)).collect(Collectors.toList()); + for (final Path path : list) { + final String[] split = path.toString().split(DELIMITER); + Files.move(path, Path.of(split[0]), REPLACE_EXISTING); + } + } + + @BeforeEach + public void setUp() throws Exception { initMocks(this); csarSecurityValidator = new CsarSecurityValidator(securityManager); + backup(); + } + + private void backup() throws Exception { + final URI uri = CsarSecurityValidatorTest.class.getResource(BASE_DIR).toURI(); + final List list = Files.list(Path.of(uri.getPath())).collect(Collectors.toList()); + for (final Path path : list) { + Files.copy(path, Path.of(path.toString() + DELIMITER + UUID.randomUUID()), REPLACE_EXISTING); + } } @Test - public void isSignatureValidTestCorrectStructureAndValidSignatureExists() throws SecurityManagerException { - final byte[] packageBytes = getFileBytesOrFail("signing/signed-package.zip"); - final OnboardSignedPackage onboardSignedPackage = loadSignedPackage("signed-package.zip", + void isSignatureValidTestCorrectStructureAndValidSignatureExists() throws SecurityManagerException, IOException { + final byte[] packageBytes = getFileBytesOrFail("signed-package.zip"); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithArtifactInfo("signed-package.zip", packageBytes, null); + when(securityManager.verifyPackageSignedData(any(OnboardSignedPackage.class), any(ArtifactInfo.class))).thenReturn(true); + final boolean isSignatureValid = csarSecurityValidator + .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); + assertThat("Signature should be valid", isSignatureValid, is(true)); + } + + @Test + void isSignatureValidTestCorrectStructureAndNotValidSignatureExists() throws SecurityManagerException { + final byte[] packageBytes = getFileBytesOrFail("signed-package-tampered-data.zip"); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithArtifactInfo("signed-package-tampered-data.zip", packageBytes, null); + //no mocked securityManager + csarSecurityValidator = new CsarSecurityValidator(); + Assertions.assertThrows(SecurityManagerException.class, () -> { + csarSecurityValidator + .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); + }); + } + + @Test + void isSignatureValidTestCorrectStructureAndValidSignatureExistsArtifactStorageManagerIsEnabled() throws SecurityManagerException { + final byte[] packageBytes = getFileBytesOrFail("signed-package.zip"); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithoutArtifactInfo("signed-package.zip", packageBytes, null); when(securityManager.verifySignedData(any(), any(), any())).thenReturn(true); - final boolean isSignatureValid = csarSecurityValidator.verifyPackageSignature(onboardSignedPackage); + final boolean isSignatureValid = csarSecurityValidator + .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); + assertThat("Signature should be valid", isSignatureValid, is(true)); } - @Test(expected = SecurityManagerException.class) - public void isSignatureValidTestCorrectStructureAndNotValidSignatureExists() throws SecurityManagerException { - final byte[] packageBytes = getFileBytesOrFail("signing/signed-package-tampered-data.zip"); - final OnboardSignedPackage onboardSignedPackage = loadSignedPackage("signed-package-tampered-data.zip", + @Test + void isSignatureValidTestCorrectStructureAndNotValidSignatureExistsArtifactStorageManagerIsEnabled() throws SecurityManagerException { + final byte[] packageBytes = getFileBytesOrFail("signed-package-tampered-data.zip"); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithoutArtifactInfo("signed-package-tampered-data.zip", packageBytes, null); //no mocked securityManager csarSecurityValidator = new CsarSecurityValidator(); - csarSecurityValidator.verifyPackageSignature(onboardSignedPackage); + Assertions.assertThrows(SecurityManagerException.class, () -> { + csarSecurityValidator + .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); + }); } private byte[] getFileBytesOrFail(final String path) { @@ -87,8 +148,21 @@ public class CsarSecurityValidatorTest { CsarSecurityValidatorTest.class.getResource(BASE_DIR + path).toURI())); } - private OnboardSignedPackage loadSignedPackage(final String packageName, final byte[] packageBytes, - CnfPackageValidator cnfPackageValidator) { + private OnboardPackageInfo loadSignedPackageWithArtifactInfo(final String packageName, final byte[] packageBytes, + final CnfPackageValidator cnfPackageValidator) { + final OnboardingPackageProcessor onboardingPackageProcessor = + new OnboardingPackageProcessor(packageName, packageBytes, cnfPackageValidator, + new PersistentStorageArtifactInfo(Path.of("src/test/resources/vspmanager.csar/signing/signed-package.zip"))); + final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); + if (onboardPackageInfo == null) { + fail("Unexpected error. Could not load original package"); + } + + return onboardPackageInfo; + } + + private OnboardPackageInfo loadSignedPackageWithoutArtifactInfo(final String packageName, final byte[] packageBytes, + final CnfPackageValidator cnfPackageValidator) { final OnboardingPackageProcessor onboardingPackageProcessor = new OnboardingPackageProcessor(packageName, packageBytes, cnfPackageValidator, null); final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); @@ -96,6 +170,6 @@ public class CsarSecurityValidatorTest { fail("Unexpected error. Could not load original package"); } - return (OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(); + return onboardPackageInfo; } } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java index b5479e0868..6dc5517c45 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java @@ -27,14 +27,20 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openecomp.sdc.be.csar.storage.PersistentStorageArtifactInfo; +import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.OnboardingPackageProcessor; +import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.validation.CnfPackageValidator; +import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardPackageInfo; +import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; -public class SecurityManagerTest { +class SecurityManagerTest { private File certDir; private String cerDirPath = "/tmp/cert/"; @@ -71,7 +77,7 @@ public class SecurityManagerTest { } @Test - public void testGetCertificates() throws IOException, SecurityManagerException, URISyntaxException { + void testGetCertificates() throws IOException, SecurityManagerException, URISyntaxException { File newFile = prepareCertFiles("/cert/root-certificate.pem", cerDirPath + "/root-certificate.pem"); assertEquals(1, securityManager.getTrustedCertificates().size()); newFile.delete(); @@ -79,13 +85,13 @@ public class SecurityManagerTest { } @Test - public void testGetCertificatesNoDirectory() throws IOException, SecurityManagerException { + void testGetCertificatesNoDirectory() throws IOException, SecurityManagerException { certDir.delete(); assertEquals(0, securityManager.getTrustedCertificates().size()); } @Test - public void testGetCertificatesException() throws IOException, SecurityManagerException { + void testGetCertificatesException() throws IOException, SecurityManagerException { File newFile = new File(cerDirPath + "root-certificate.pem"); newFile.createNewFile(); Assertions.assertThrows(SecurityManagerException.class, () -> { @@ -97,9 +103,9 @@ public class SecurityManagerTest { } @Test - public void testGetCertificatesUpdated() throws IOException, SecurityManagerException, URISyntaxException { + void testGetCertificatesUpdated() throws IOException, SecurityManagerException, URISyntaxException { File newFile = prepareCertFiles("/cert/root-certificate.pem", cerDirPath + "root-certificate.pem"); - assertTrue(securityManager.getTrustedCertificates().size() == 1); + assertEquals(1, securityManager.getTrustedCertificates().size()); File otherNewFile = prepareCertFiles("/cert/package-certificate.pem", cerDirPath + "package-certificate.pem"); assertEquals(2, securityManager.getTrustedCertificates().size()); otherNewFile.delete(); @@ -109,7 +115,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); byte[] signature = readAllBytes("/cert/2-file-signed-package/dummyPnfv4.cms"); byte[] archive = readAllBytes("/cert/2-file-signed-package/dummyPnfv4.csar"); @@ -117,7 +123,22 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertNotIncludedIntoSignatureButExpected() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertIncludedIntoSignatureArtifactStorageManagerIsEnabled() + throws IOException, URISyntaxException, SecurityManagerException { + prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); + byte[] fileToUploadBytes = readAllBytes("/cert/2-file-signed-package/2-file-signed-package.zip"); + + final var onboardingPackageProcessor = new OnboardingPackageProcessor("2-file-signed-package.zip", fileToUploadBytes, + new CnfPackageValidator(), + new PersistentStorageArtifactInfo(Path.of("src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip"))); + final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); + + assertTrue(securityManager + .verifyPackageSignedData((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo())); + } + + @Test + void verifySignedDataTestCertNotIncludedIntoSignatureButExpected() throws IOException, URISyntaxException, SecurityManagerException { Assertions.assertThrows(SecurityManagerException.class, () -> { prepareCertFiles("/cert/root.cert", cerDirPath + "root.cert"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); @@ -128,7 +149,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertNotIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertNotIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); byte[] archive = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.csar"); @@ -137,7 +158,22 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertIntermediateNotIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertNotIncludedIntoSignatureArtifactStorageManagerIsEnabled() + throws IOException, URISyntaxException, SecurityManagerException { + prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); + byte[] fileToUploadBytes = readAllBytes("/cert/3-file-signed-package/3-file-signed-package.zip"); + + final var onboardingPackageProcessor = new OnboardingPackageProcessor("3-file-signed-package.zip", fileToUploadBytes, + new CnfPackageValidator(), + new PersistentStorageArtifactInfo(Path.of("src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip"))); + final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); + + assertTrue(securityManager + .verifyPackageSignedData((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo())); + } + + @Test + void verifySignedDataTestCertIntermediateNotIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); prepareCertFiles("/cert/package2.cert", cerDirPath + "signing-ca2.crt"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); @@ -147,7 +183,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertWrongIntermediate() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertWrongIntermediate() throws IOException, URISyntaxException, SecurityManagerException { Assertions.assertThrows(SecurityManagerException.class, () -> { prepareCertFiles("/cert/root.cert", cerDirPath + "root.cert"); prepareCertFiles("/cert/signing-ca1.crt", cerDirPath + "signing-ca1.crt"); @@ -160,7 +196,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertIncludedIntoSignatureWithWrongIntermediateInDirectory() + void verifySignedDataTestCertIncludedIntoSignatureWithWrongIntermediateInDirectory() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); prepareCertFiles("/cert/signing-ca1.crt", cerDirPath + "signing-ca1.crt"); @@ -170,7 +206,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertWrongIntermediateInDirectory() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertWrongIntermediateInDirectory() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); prepareCertFiles("/cert/signing-ca1.crt", cerDirPath + "signing-ca1.crt"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); @@ -180,7 +216,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestWrongCertificate() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestWrongCertificate() throws IOException, URISyntaxException, SecurityManagerException { Assertions.assertThrows(SecurityManagerException.class, () -> { prepareCertFiles("/cert/root-certificate.pem", cerDirPath + "root-certificate.cert"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); @@ -192,7 +228,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestChangedArchive() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestChangedArchive() throws IOException, URISyntaxException, SecurityManagerException { Assertions.assertThrows(SecurityManagerException.class, () -> { prepareCertFiles("/cert/root.cert", cerDirPath + "root.cert"); byte[] signature = readAllBytes("/cert/tampered-signed-package/dummyPnfv4.cms"); diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip new file mode 100644 index 0000000000..be48e8a674 Binary files /dev/null and b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip differ diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip new file mode 100644 index 0000000000..7f2eacbe10 Binary files /dev/null and b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip differ -- cgit 1.2.3-korg