diff options
Diffstat (limited to 'catalog-be-plugins/etsi-nfv-nsd-csar-plugin')
15 files changed, 855 insertions, 53 deletions
diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/builder/NsdCsarManifestBuilder.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/builder/NsdCsarManifestBuilder.java index 38f03f12f5..73be4a2100 100644 --- a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/builder/NsdCsarManifestBuilder.java +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/builder/NsdCsarManifestBuilder.java @@ -1,4 +1,3 @@ - /* * ============LICENSE_START======================================================= * Copyright (C) 2020 Nordix Foundation @@ -25,6 +24,7 @@ import java.util.Collection; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; +import org.apache.commons.lang.StringUtils; /** * Builder for the manifest (.mf) file in a NSD CSAR @@ -45,6 +45,7 @@ public class NsdCsarManifestBuilder { private final MetadataHeader metadataHeader; private final Set<String> sources; private final Set<String> compatibleSpecificationVersions; + private String signature; public NsdCsarManifestBuilder() { metadataHeader = new MetadataHeader(); @@ -122,6 +123,13 @@ public class NsdCsarManifestBuilder { return this; } + public NsdCsarManifestBuilder withSignature(final String signature) { + if (signature != null) { + this.signature = signature.trim(); + } + return this; + } + /** * Builds a string representing the manifest content based on provided values. * @@ -142,7 +150,14 @@ public class NsdCsarManifestBuilder { .append(String.join(",", compatibleSpecificationVersions)).append(NEW_LINE); } final StringBuilder builder = new StringBuilder(); - builder.append(metadataBuilder).append(compatibleSpecificationVersionsBuilder).append(NEW_LINE).append(sourceBuilder); + + builder.append(metadataBuilder) + .append(compatibleSpecificationVersionsBuilder) + .append(NEW_LINE) + .append(sourceBuilder); + if (StringUtils.isNotBlank(signature)) { + builder.append(signature); + } return builder.toString(); } diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/factory/EtsiNfvNsdCsarGeneratorFactory.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/factory/EtsiNfvNsdCsarGeneratorFactory.java index fb08f56ac2..c51dc51854 100644 --- a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/factory/EtsiNfvNsdCsarGeneratorFactory.java +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/factory/EtsiNfvNsdCsarGeneratorFactory.java @@ -1,4 +1,3 @@ - /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Nordix Foundation @@ -25,6 +24,7 @@ import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.EtsiNfvNsdCsarGenerat import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.VnfDescriptorGenerator; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.EtsiVersion; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorConfig; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.NsdCsarEtsiOption2Signer; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Component; @@ -35,20 +35,24 @@ public class EtsiNfvNsdCsarGeneratorFactory { private final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory; private final ArtifactCassandraDao artifactCassandraDao; private final ObjectProvider<EtsiNfvNsdCsarGeneratorImpl> etsiNfvNsdCsarGeneratorObjectProvider; + private final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer; public EtsiNfvNsdCsarGeneratorFactory(final VnfDescriptorGenerator vnfDescriptorGenerator, final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory, final ArtifactCassandraDao artifactCassandraDao, - final ObjectProvider<EtsiNfvNsdCsarGeneratorImpl> etsiNfvNsdCsarGeneratorObjectProvider) { + final ObjectProvider<EtsiNfvNsdCsarGeneratorImpl> etsiNfvNsdCsarGeneratorObjectProvider, + final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer) { this.vnfDescriptorGenerator = vnfDescriptorGenerator; this.nsDescriptorGeneratorFactory = nsDescriptorGeneratorFactory; this.artifactCassandraDao = artifactCassandraDao; this.etsiNfvNsdCsarGeneratorObjectProvider = etsiNfvNsdCsarGeneratorObjectProvider; + this.nsdCsarEtsiOption2Signer = nsdCsarEtsiOption2Signer; } public EtsiNfvNsdCsarGenerator create(final EtsiVersion version) { final NsDescriptorConfig nsDescriptorConfig = new NsDescriptorConfig(version); return etsiNfvNsdCsarGeneratorObjectProvider - .getObject(nsDescriptorConfig, vnfDescriptorGenerator, nsDescriptorGeneratorFactory, artifactCassandraDao); + .getObject(nsDescriptorConfig, vnfDescriptorGenerator, nsDescriptorGeneratorFactory, artifactCassandraDao + , nsdCsarEtsiOption2Signer); } } diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsCsarEntryGenerator.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsCsarEntryGenerator.java index 90359a550d..9a7312b0fb 100644 --- a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsCsarEntryGenerator.java +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsCsarEntryGenerator.java @@ -1,4 +1,3 @@ - /* * ============LICENSE_START======================================================= * Copyright (C) 2020 Nordix Foundation @@ -30,6 +29,7 @@ import org.openecomp.sdc.be.plugins.CsarEntryGenerator; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.factory.EtsiNfvNsdCsarGeneratorFactory; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.EtsiVersion; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +40,9 @@ import org.slf4j.LoggerFactory; public class EtsiNfvNsCsarEntryGenerator implements CsarEntryGenerator { static final String ETSI_NS_COMPONENT_CATEGORY = "ETSI NFV Network Service"; - static final String NSD_FILE_PATH_FORMAT = "Artifacts/%s/%s.csar"; + static final String NSD_FILE_PATH_FORMAT = "Artifacts/%s/%s.%s"; + static final String SIGNED_CSAR_EXTENSION = "zip"; + static final String UNSIGNED_CSAR_EXTENSION = "csar"; static final String ETSI_VERSION_METADATA = "ETSI Version"; private static final Logger LOGGER = LoggerFactory.getLogger(EtsiNfvNsCsarEntryGenerator.class); private final EtsiNfvNsdCsarGeneratorFactory etsiNfvNsdCsarGeneratorFactory; @@ -69,7 +71,8 @@ public class EtsiNfvNsCsarEntryGenerator implements CsarEntryGenerator { ETSI_NS_COMPONENT_CATEGORY); return Collections.emptyMap(); } - final byte[] nsdCsar; + + final NsdCsar nsdCsar; try { final EtsiVersion etsiVersion = getComponentEtsiVersion(component); final EtsiNfvNsdCsarGenerator etsiNfvNsdCsarGenerator = etsiNfvNsdCsarGeneratorFactory.create(etsiVersion); @@ -81,7 +84,8 @@ public class EtsiNfvNsCsarEntryGenerator implements CsarEntryGenerator { LOGGER.error("Could not create NSD CSAR entry for component '{}'. An unexpected exception occurred", component.getName(), e); return Collections.emptyMap(); } - return createEntry(component.getNormalizedName(), nsdCsar); + + return createEntry(nsdCsar); } private EtsiVersion getComponentEtsiVersion(Component component) { @@ -89,10 +93,11 @@ public class EtsiNfvNsCsarEntryGenerator implements CsarEntryGenerator { return EtsiVersion.convertOrNull(etsiVersion); } - private Map<String, byte[]> createEntry(final String csarName, final byte[] nsdCsar) { + private Map<String, byte[]> createEntry(final NsdCsar nsdCsar) { final Map<String, byte[]> entryMap = new HashMap<>(); - final String entryKey = String.format(NSD_FILE_PATH_FORMAT, ETSI_PACKAGE, csarName); - entryMap.put(entryKey, nsdCsar); + final String extension = nsdCsar.isSigned() ? SIGNED_CSAR_EXTENSION : UNSIGNED_CSAR_EXTENSION; + final String entryKey = String.format(NSD_FILE_PATH_FORMAT, ETSI_PACKAGE, nsdCsar.getFileName(), extension); + entryMap.put(entryKey, nsdCsar.getCsarPackage()); return entryMap; } } diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGenerator.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGenerator.java index 072c4c5a89..1ad6e82481 100644 --- a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGenerator.java +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGenerator.java @@ -1,4 +1,3 @@ - /* * ============LICENSE_START======================================================= * Copyright (C) 2020 Nordix Foundation @@ -21,6 +20,7 @@ package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator; import org.openecomp.sdc.be.model.Component; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar; /** * Generator for a ETSI NFV NSD CSAR @@ -33,5 +33,5 @@ public interface EtsiNfvNsdCsarGenerator { * @param component the service component * @return the CSAR package content */ - byte[] generateNsdCsar(Component component) throws NsdException; + NsdCsar generateNsdCsar(Component component) throws NsdException; } diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGeneratorImpl.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGeneratorImpl.java index e7d30197d7..f9e0970ba9 100644 --- a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGeneratorImpl.java +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGeneratorImpl.java @@ -1,4 +1,3 @@ - /* * ============LICENSE_START======================================================= * Copyright (C) 2020 Nordix Foundation @@ -23,10 +22,13 @@ import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ETSI_PACKAGE; import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ONBOARDED_PACKAGE; import fj.data.Either; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -36,9 +38,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.lang.StringUtils; +import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo; import org.openecomp.sdc.be.dao.cassandra.ArtifactCassandraDao; import org.openecomp.sdc.be.dao.cassandra.CassandraOperationStatus; import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields; @@ -53,7 +57,9 @@ import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.EtsiVersion; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorConfig; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorVersionComparator; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.NsdCsarEtsiOption2Signer; import org.openecomp.sdc.be.resources.data.DAOArtifactData; import org.openecomp.sdc.be.tosca.utils.OperationArtifactUtil; import org.slf4j.Logger; @@ -74,6 +80,8 @@ public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator { private static final String MANIFEST_EXT = "mf"; private static final String SLASH = "/"; private static final String DOT = "."; + private static final String SIGNATURE_EXTENSION = ".sig.cms"; + private static final String CSAR_EXTENSION = ".csar"; private static final String DOT_YAML = DOT + "yaml"; private static final String DEFINITION = "Definitions"; private static final String TOSCA_META_PATH = "TOSCA-Metadata/TOSCA.meta"; @@ -81,18 +89,21 @@ public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator { private final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory; private final ArtifactCassandraDao artifactCassandraDao; private final NsDescriptorConfig nsDescriptorConfig; + private final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer; public EtsiNfvNsdCsarGeneratorImpl(final NsDescriptorConfig nsDescriptorConfig, final VnfDescriptorGenerator vnfDescriptorGenerator, final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory, - final ArtifactCassandraDao artifactCassandraDao) { + final ArtifactCassandraDao artifactCassandraDao, + final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer) { this.nsDescriptorConfig = nsDescriptorConfig; this.vnfDescriptorGenerator = vnfDescriptorGenerator; this.nsDescriptorGeneratorFactory = nsDescriptorGeneratorFactory; this.artifactCassandraDao = artifactCassandraDao; + this.nsdCsarEtsiOption2Signer = nsdCsarEtsiOption2Signer; } @Override - public byte[] generateNsdCsar(final Component component) throws NsdException { + public NsdCsar generateNsdCsar(final Component component) throws NsdException { if (component == null) { throw new NsdException("Could not generate the NSD CSAR, invalid component argument"); } @@ -101,23 +112,43 @@ public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator { final String componentName = component.getName(); try { LOGGER.debug("Starting NSD CSAR generation for component '{}'", componentName); - final Map<String, byte[]> nsdCsarFiles = new HashMap<>(); - final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component); - vnfDescriptorList.forEach(vnfPackage -> nsdCsarFiles.putAll(vnfPackage.getDefinitionFiles())); final String nsdFileName = getNsdFileName(component); + final NsdCsar nsdCsar = new NsdCsar(nsdFileName); + + final List<VnfDescriptor> vnfDescriptorList = generateVnfPackages(component); + vnfDescriptorList.forEach(vnfPackage -> nsdCsar.addAllFiles(vnfPackage.getDefinitionFiles())); + final EtsiVersion etsiVersion = nsDescriptorConfig.getNsVersion(); final Nsd nsd = generateNsd(component, vnfDescriptorList); - nsdCsarFiles.put(getNsdPath(nsdFileName), nsd.getContents()); - nsdCsarFiles.put(TOSCA_META_PATH, buildToscaMetaContent(nsdFileName).getBytes()); - addEtsiSolNsdTypes(etsiVersion, nsdCsarFiles); + + nsdCsar.addFile(getNsdPath(nsdFileName), nsd.getContents()); + nsdCsar.addFile(TOSCA_META_PATH, buildToscaMetaContent(nsdFileName).getBytes()); + + nsdCsar.addAllFiles(createEtsiSolNsdTypeEntries(etsiVersion)); for (final String referencedFile : nsd.getArtifactReferences()) { getReferencedArtifact(component, referencedFile) - .ifPresent(artifactDefinition -> nsdCsarFiles.put(referencedFile, artifactDefinition.getPayloadData())); + .ifPresent(artifactDefinition -> nsdCsar.addFile(referencedFile, artifactDefinition.getPayloadData()) + ); + } + final boolean isCertificateConfigured = nsdCsarEtsiOption2Signer.isCertificateConfigured(); + final String manifestPath = getManifestPath(nsdFileName); + final NsdCsarManifestBuilder manifestBuilder = + createManifestBuilder(nsd, etsiVersion, manifestPath, nsdCsar.getFileMap().keySet(), isCertificateConfigured); + + nsdCsar.addManifest(manifestBuilder); + + if (isCertificateConfigured) { + nsdCsarEtsiOption2Signer.signArtifacts(nsdCsar); + } + + byte[] package1 = buildCsarPackage(nsdCsar.getFileMap()); + if (isCertificateConfigured) { + package1 = buildZipWithCsarAndSignature(nsdCsar.getFileName(), package1); + nsdCsar.setSigned(true); } - nsdCsarFiles.put(getManifestPath(nsdFileName), getManifestFileContent(nsd, etsiVersion, nsdCsarFiles.keySet()).getBytes()); - final byte[] csar = buildCsarPackage(nsdCsarFiles); + nsdCsar.setCsarPackage(package1); LOGGER.debug("Successfully generated NSD CSAR package"); - return csar; + return nsdCsar; } catch (final Exception exception) { throw new NsdException("Could not generate the NSD CSAR file", exception); } @@ -205,29 +236,41 @@ public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator { }).findFirst(); } - private void addEtsiSolNsdTypes(final EtsiVersion etsiVersion, final Map<String, byte[]> nsdCsarFileMap) { + private Map<String, byte[]> createEtsiSolNsdTypeEntries(final EtsiVersion etsiVersion) { final EtsiVersion currentVersion = etsiVersion == null ? EtsiVersion.getDefaultVersion() : etsiVersion; + final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try { final Resource[] resources = resolver.getResources(String.format("classpath:etsi-nfv-types/%s/*.*", currentVersion.getVersion())); if (resources.length > 0) { + final Map<String, byte[]> entryMap = new HashMap<>(); for (final Resource resource : resources) { - addToCsarFileMap(resource, nsdCsarFileMap); + readResource(resource).ifPresent(resourceBytes -> { + final String entryName = createDefinitionEntryName(resource.getFilename()); + entryMap.put(entryName, resourceBytes); + }); } + return entryMap; } } catch (final IOException e) { LOGGER.error("Could not find types files for the version '{}'", currentVersion.getVersion(), e); } + + return Collections.emptyMap(); + } + + private String createDefinitionEntryName(final String fileName) { + return DEFINITION + "/" + fileName; } - private void addToCsarFileMap(final Resource resource, final Map<String, byte[]> nsdCsarFileMap) { + private Optional<byte[]> readResource(final Resource resource) { try { - nsdCsarFileMap.put(DEFINITION + "/" + resource.getFilename(), - IOUtils.toByteArray(resource.getInputStream())); + return Optional.ofNullable(IOUtils.toByteArray(resource.getInputStream())); } catch (final IOException exception) { LOGGER.error("Error adding '{}' to NSD CSAR", resource.getFilename(), exception); } + return Optional.empty(); } private Nsd generateNsd(final Component component, @@ -294,18 +337,24 @@ public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator { return toscaMetadata; } - private String getManifestFileContent(final Nsd nsd, - final EtsiVersion nsdVersion, - final Set<String> files) { + private NsdCsarManifestBuilder createManifestBuilder(final Nsd nsd, final EtsiVersion nsdVersion, + final String manifestFilePath, final Set<String> filePath, + final boolean addSignatureFiles) { LOGGER.debug("Creating NS manifest file content"); + final Set<String> filesToAdd = new HashSet<>(filePath); + if (addSignatureFiles) { + filePath.forEach(file -> filesToAdd.add(file + SIGNATURE_EXTENSION)); + } + filesToAdd.add(manifestFilePath); + final NsdCsarManifestBuilder nsdCsarManifestBuilder = new NsdCsarManifestBuilder(); nsdCsarManifestBuilder.withDesigner(nsd.getDesigner()) .withInvariantId(nsd.getInvariantId()) .withName(nsd.getName()) .withNowReleaseDateTime() .withFileStructureVersion(nsd.getVersion()) - .withSources(files); + .withSources(filesToAdd); final NsDescriptorVersionComparator nsdVersionComparator = new NsDescriptorVersionComparator(); @@ -313,10 +362,10 @@ public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator { nsdCsarManifestBuilder.withCompatibleSpecificationVersion(nsdVersion.getVersion()); } - final String manifest = nsdCsarManifestBuilder.build(); - LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", manifest); - return manifest; - + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Successfully created NS CSAR manifest file content:\n {}", nsdCsarManifestBuilder.build()); + } + return nsdCsarManifestBuilder; } private String getManifestPath(final String nsdFileName) { @@ -358,6 +407,37 @@ public class EtsiNfvNsdCsarGeneratorImpl implements EtsiNfvNsdCsarGenerator { } } + private byte[] buildZipWithCsarAndSignature(final String csarFileName, final byte[] csarPackageBytes) throws NsdException { + final byte[] signature; + try { + signature = nsdCsarEtsiOption2Signer.sign(csarPackageBytes); + } catch (final Exception e) { + throw new NsdException(String.format("Could not sign the CSAR '%s'", csarFileName), e); + } + final Optional<CertificateInfo> certificateInfoOpt = nsdCsarEtsiOption2Signer.getSigningCertificate(); + if (certificateInfoOpt.isEmpty()) { + throw new NsdException(String.format("Could not sign the CSAR '%s'. No certificate configured.", csarFileName)); + } + final CertificateInfo certificateInfo = certificateInfoOpt.get(); + try (final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final ZipOutputStream zip = new ZipOutputStream(out)) { + zip.putNextEntry(new ZipEntry(csarFileName + CSAR_EXTENSION)); + zip.write(csarPackageBytes); + zip.putNextEntry(new ZipEntry(csarFileName + SIGNATURE_EXTENSION)); + zip.write(signature); + final File certificateFile = certificateInfo.getCertificateFile(); + zip.putNextEntry(new ZipEntry(csarFileName + "." + FilenameUtils.getExtension(certificateFile.getName()))); + zip.write(Files.readAllBytes(certificateFile.toPath())); + zip.flush(); + zip.finish(); + LOGGER.debug("NSD signed CSAR zip file was successfully built"); + + return out.toByteArray(); + } catch (final IOException e) { + throw new NsdException("Could not build the NSD signed CSAR zip file", e); + } + } + } diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/model/NsdCsar.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/model/NsdCsar.java new file mode 100644 index 0000000000..733604529a --- /dev/null +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/model/NsdCsar.java @@ -0,0 +1,110 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdCsarManifestBuilder; + +/** + * Represents a NSD CSAR package + */ +public class NsdCsar { + + private static final String MANIFEST_EXTENSION = "mf"; + private static final String YAML_EXTENSION = "yaml"; + private static final String DOT = "."; + private static final String SLASH = "/"; + private static final String DEFINITIONS = "Definitions"; + + private final Map<String, byte[]> fileMap = new HashMap<>(); + @Getter + private final String fileName; + @Getter + @Setter + private byte[] csarPackage; + @Getter + @Setter + private boolean isSigned; + @Getter + private NsdCsarManifestBuilder manifestBuilder; + + public NsdCsar(final String fileName) { + this.fileName = fileName; + manifestBuilder = new NsdCsarManifestBuilder(); + } + + public void addFile(final String filePath, final byte[] fileBytes) { + fileMap.put(filePath, fileBytes); + } + + public byte[] getFile(final String filePath) { + return fileMap.get(filePath); + } + + public byte[] getManifest() { + return fileMap.get(getManifestPath()); + } + + public byte[] getMainDefinition() { + return fileMap.get(getMainDefinitionPath()); + } + + public Map<String, byte[]> getFileMap() { + return new HashMap<>(fileMap); + } + + public String getManifestPath() { + return fileName + DOT + MANIFEST_EXTENSION; + } + + public boolean isManifest(final String filePath) { + return getManifestPath().equals(filePath); + } + + public String getMainDefinitionPath() { + return DEFINITIONS + SLASH + fileName + YAML_EXTENSION; + } + + public void addAllFiles(final Map<String, byte[]> definitionFiles) { + fileMap.putAll(definitionFiles); + } + + /** + * Sets a manifest builder and build it, adding its content to the to the CSAR files. Ignores {@code null} manifest builders. + * + * @param manifestBuilder the manifest builder + */ + public void addManifest(final NsdCsarManifestBuilder manifestBuilder) { + if (manifestBuilder == null) { + return; + } + this.manifestBuilder = manifestBuilder; + final String manifestContent = manifestBuilder.build(); + addFile(getManifestPath(), manifestContent.getBytes(StandardCharsets.UTF_8)); + } + + public boolean isEmpty() { + return fileMap.isEmpty(); + } +} diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/NsdCsarEtsiOption2Signer.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/NsdCsarEtsiOption2Signer.java new file mode 100644 index 0000000000..abba32ad8d --- /dev/null +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/NsdCsarEtsiOption2Signer.java @@ -0,0 +1,151 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.cert.Certificate; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.stream.Collectors; +import org.openecomp.sdc.be.csar.security.api.CertificateManager; +import org.openecomp.sdc.be.csar.security.api.CmsContentSigner; +import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo; +import org.openecomp.sdc.be.csar.security.exception.CmsSignatureException; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdCsarManifestBuilder; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception.NsdSignatureException; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception.NsdSignatureExceptionSupplier; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +/** + * Handles NSD CSAR file and package signature, following the ETSI SOL007 v3.3.1, section 5.1, signing option 2. + * + * @see <a href="https://www.etsi.org/deliver/etsi_gs/NFV-SOL/001_099/007/03.03.01_60/gs_NFV-SOL007v030301p.pdf">ETSI SOL007 v3.3.1 documentation</a> + */ +@Component +public class NsdCsarEtsiOption2Signer { + + public static final String SDC_NSD_CERT_NAME = "SDC_NSD_CERT_NAME"; + public static final String SIGNATURE_EXTENSION = ".sig.cms"; + + private final CertificateManager certificateManager; + private final CmsContentSigner cmsContentSigner; + private final Environment environment; + + public NsdCsarEtsiOption2Signer(final CertificateManager certificateManager, + final CmsContentSigner cmsContentSigner, + final Environment environment) { + this.certificateManager = certificateManager; + this.cmsContentSigner = cmsContentSigner; + this.environment = environment; + } + + /** + * Sign each NSD CSAR artifact (files), generating a cms file for each. The manifest, though, have its signature added in its body instead of a separate + * CMS file. Modifies the given NSD CSAR by adding the file signatures and the modified manifest. + * + * @param nsdCsar the NSD CSAR + * @throws NsdSignatureException when there was a problem while creating a file signature + */ + public void signArtifacts(final NsdCsar nsdCsar) throws NsdSignatureException { + if (nsdCsar == null) { + return; + } + //ignore the manifest, the signature of the manifest goes inside the manifest itself ETSI 3.3.1 section 5.3 + final Map<String, byte[]> fileMap = nsdCsar.getFileMap().entrySet().stream() + .filter(entry -> !nsdCsar.isManifest(entry.getKey())) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + + for (final Entry<String, byte[]> fileEntry : fileMap.entrySet()) { + final byte[] signatureBytes = sign(fileEntry.getValue()); + final String filePath = fileEntry.getKey(); + nsdCsar.addFile(filePath + SIGNATURE_EXTENSION, signatureBytes); + } + + signManifest(nsdCsar); + } + + private void signManifest(final NsdCsar nsdCsar) throws NsdSignatureException { + final Optional<Entry<String, byte[]>> manifestEntryOpt = nsdCsar.getFileMap().entrySet().stream() + .filter(entry -> nsdCsar.isManifest(entry.getKey())).findFirst(); + if (manifestEntryOpt.isEmpty()) { + return; + } + final CertificateInfo certificateInfo = getValidCertificate(); + + final Entry<String, byte[]> manifestEntry = manifestEntryOpt.get(); + final String pemSignature = createFileSignature(certificateInfo.getCertificate(), + certificateInfo.getPrivateKey(), manifestEntry.getValue()); + final NsdCsarManifestBuilder manifestBuilder = nsdCsar.getManifestBuilder(); + manifestBuilder.withSignature(pemSignature); + nsdCsar.addManifest(manifestBuilder); + } + + /** + * Sign a file, creating the PEM format signature and returning its bytes. + * + * @param fileBytes the file to sign + * @return the bytes of the PEM format signature created from the given file and the NSD certificate + * @throws NsdSignatureException when it was not possible to retrieve the NSD certificate + * @throws NsdSignatureException when the NSD certificate is invalid + * @throws NsdSignatureException it was not possible to sign the file + */ + public byte[] sign(final byte[] fileBytes) throws NsdSignatureException { + final CertificateInfo certificateInfo = getValidCertificate(); + final String pemSignature = + createFileSignature(certificateInfo.getCertificate(), certificateInfo.getPrivateKey(), fileBytes); + return pemSignature.getBytes(StandardCharsets.UTF_8); + } + + public Optional<CertificateInfo> getSigningCertificate() { + final String sdcNsdCertName = environment.getProperty(SDC_NSD_CERT_NAME); + return certificateManager.getCertificate(sdcNsdCertName); + } + + public boolean isCertificateConfigured() { + return getSigningCertificate().isPresent(); + } + + private CertificateInfo getValidCertificate() throws NsdSignatureException { + final Optional<CertificateInfo> certificateInfoOpt = getSigningCertificate(); + if (certificateInfoOpt.isEmpty()) { + throw NsdSignatureExceptionSupplier.certificateNotConfigured(); + } + final CertificateInfo certificateInfo = certificateInfoOpt.get(); + if (!certificateInfo.isValid()) { + throw NsdSignatureExceptionSupplier.invalidCertificate(certificateInfo.getName()); + } + + return certificateInfo; + } + + private String createFileSignature(final Certificate certificate, final Key privateKey, + final byte[] fileBytes) throws NsdSignatureException { + try { + final byte[] dataSignature = cmsContentSigner.signData(fileBytes, certificate, privateKey); + return cmsContentSigner.formatToPemSignature(dataSignature); + } catch (final CmsSignatureException e) { + throw NsdSignatureExceptionSupplier.unableToCreateSignature(e); + } + } +} diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/exception/NsdSignatureException.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/exception/NsdSignatureException.java new file mode 100644 index 0000000000..1591a5e294 --- /dev/null +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/exception/NsdSignatureException.java @@ -0,0 +1,31 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception; + +public class NsdSignatureException extends Exception { + + public NsdSignatureException(final String message) { + super(message); + } + + public NsdSignatureException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/exception/NsdSignatureExceptionSupplier.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/exception/NsdSignatureExceptionSupplier.java new file mode 100644 index 0000000000..696a8d8626 --- /dev/null +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/exception/NsdSignatureExceptionSupplier.java @@ -0,0 +1,39 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception; + +public final class NsdSignatureExceptionSupplier { + + private NsdSignatureExceptionSupplier() { + } + + public static NsdSignatureException invalidCertificate(final String certificateName) { + return new NsdSignatureException(String.format("The certificate '%s' is invalid", certificateName)); + } + + public static NsdSignatureException certificateNotConfigured() { + return new NsdSignatureException("No certificate configured"); + } + + public static NsdSignatureException unableToCreateSignature(final Exception e) { + return new NsdSignatureException("Could create file signature", e); + } + +}
\ No newline at end of file diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/builder/NsdCsarManifestBuilderTest.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/builder/NsdCsarManifestBuilderTest.java index 1ff3a6d52b..caca932929 100644 --- a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/builder/NsdCsarManifestBuilderTest.java +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/builder/NsdCsarManifestBuilderTest.java @@ -1,4 +1,3 @@ - /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Nordix Foundation @@ -43,6 +42,10 @@ class NsdCsarManifestBuilderTest { nsdCsarManifestBuilder.withFileStructureVersion("fileStructureVersion"); nsdCsarManifestBuilder.withCompatibleSpecificationVersion("1.0.0"); nsdCsarManifestBuilder.withCompatibleSpecificationVersion("1.0.1"); + final String signature = "-----BEGIN CMS-----\n" + + "12d08j19d981928129dj129j1\n" + + "-----END CMS-----"; + nsdCsarManifestBuilder.withSignature(signature); final List<String> sourceList = new ArrayList<>(); final String source1 = "Definitions/aSource1.yaml"; sourceList.add(source1); @@ -53,9 +56,16 @@ class NsdCsarManifestBuilderTest { assertSource(manifest, source1); assertSource(manifest, source2); assertCompatibleSpecificationVersions(manifest, "1.0.0,1.0.1"); - final String expectedManifest = "metadata: \n" + "nsd_designer: designer\n" + "nsd_invariant_id: invariantId\n" + "nsd_name: name\n" - + "nsd_file_structure_version: fileStructureVersion\n" + "compatible_specification_versions: 1.0.0,1.0.1\n" + "\n" - + "Source: Definitions/aSource1.yaml\n" + "Source: Definitions/aSource2.yaml\n" + ""; + final String expectedManifest = "metadata: \n" + + "nsd_designer: designer\n" + + "nsd_invariant_id: invariantId\n" + + "nsd_name: name\n" + + "nsd_file_structure_version: fileStructureVersion\n" + + "compatible_specification_versions: 1.0.0,1.0.1\n" + + "\n" + + "Source: Definitions/aSource1.yaml\n" + + "Source: Definitions/aSource2.yaml\n" + + signature; assertEquals(expectedManifest, manifest); } diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsCsarEntryGeneratorTest.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsCsarEntryGeneratorTest.java index 8ebd1df190..837179d50d 100644 --- a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsCsarEntryGeneratorTest.java +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsCsarEntryGeneratorTest.java @@ -1,4 +1,3 @@ - /* * ============LICENSE_START======================================================= * Copyright (C) 2020 Nordix Foundation @@ -27,6 +26,7 @@ import static org.mockito.Mockito.when; import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.EtsiNfvNsCsarEntryGenerator.ETSI_NS_COMPONENT_CATEGORY; import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.EtsiNfvNsCsarEntryGenerator.ETSI_VERSION_METADATA; import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.EtsiNfvNsCsarEntryGenerator.NSD_FILE_PATH_FORMAT; +import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.EtsiNfvNsCsarEntryGenerator.UNSIGNED_CSAR_EXTENSION; import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ETSI_PACKAGE; import java.util.ArrayList; @@ -44,6 +44,7 @@ import org.openecomp.sdc.be.model.category.CategoryDefinition; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.exception.NsdException; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.factory.EtsiNfvNsdCsarGeneratorFactory; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.EtsiVersion; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar; class EtsiNfvNsCsarEntryGeneratorTest { @@ -68,12 +69,14 @@ class EtsiNfvNsCsarEntryGeneratorTest { @Test void successfullyEntryGenerationTest() throws NsdException { mockServiceComponent(); - final byte[] expectedNsdCsar = new byte[5]; - when(etsiNfvNsdCsarGenerator.generateNsdCsar(service)).thenReturn(expectedNsdCsar); + final NsdCsar nsdCsar = new NsdCsar(SERVICE_NORMALIZED_NAME); + nsdCsar.setCsarPackage(new byte[5]); + when(etsiNfvNsdCsarGenerator.generateNsdCsar(service)).thenReturn(nsdCsar); final Map<String, byte[]> entryMap = etsiNfvNsCsarEntryGenerator.generateCsarEntries(service); assertThat("Csar Entries should contain only one entry", entryMap.size(), is(1)); assertThat("Csar Entries should contain the expected entry", entryMap, - hasEntry(String.format(NSD_FILE_PATH_FORMAT, ETSI_PACKAGE, SERVICE_NORMALIZED_NAME), expectedNsdCsar)); + hasEntry(String.format(NSD_FILE_PATH_FORMAT, ETSI_PACKAGE, SERVICE_NORMALIZED_NAME, UNSIGNED_CSAR_EXTENSION), + nsdCsar.getCsarPackage())); } @Test diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGeneratorImplTest.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGeneratorImplTest.java index b498c45d1f..b1b8157534 100644 --- a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGeneratorImplTest.java +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/generator/EtsiNfvNsdCsarGeneratorImplTest.java @@ -1,4 +1,3 @@ - /* * ============LICENSE_START======================================================= * Copyright (C) 2020 Nordix Foundation @@ -23,12 +22,16 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.EtsiNfvNsCsarEntryGenerator.ETSI_NS_COMPONENT_CATEGORY; import static org.openecomp.sdc.common.api.ArtifactTypeEnum.ONBOARDED_PACKAGE; import fj.data.Either; +import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -39,6 +42,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.openecomp.sdc.be.csar.security.model.CertificateInfoImpl; import org.openecomp.sdc.be.dao.cassandra.ArtifactCassandraDao; import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum; import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields; @@ -52,7 +56,10 @@ import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.factory.NsDescriptorGeneratorFa import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.EtsiVersion; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.generator.config.NsDescriptorConfig; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.Nsd; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar; import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.VnfDescriptor; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.NsdCsarEtsiOption2Signer; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception.NsdSignatureException; import org.openecomp.sdc.be.resources.data.DAOArtifactData; class EtsiNfvNsdCsarGeneratorImplTest { @@ -67,6 +74,8 @@ class EtsiNfvNsdCsarGeneratorImplTest { @Mock private ArtifactCassandraDao artifactCassandraDao; @Mock + private NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer; + @Mock private Service service; private EtsiNfvNsdCsarGeneratorImpl etsiNfvNsdCsarGenerator; @@ -74,8 +83,8 @@ class EtsiNfvNsdCsarGeneratorImplTest { void setUp() { MockitoAnnotations.initMocks(this); final EtsiVersion version2_5_1 = EtsiVersion.VERSION_2_5_1; - etsiNfvNsdCsarGenerator = new EtsiNfvNsdCsarGeneratorImpl(new NsDescriptorConfig(version2_5_1), vnfDescriptorGenerator, - nsDescriptorGeneratorFactory, artifactCassandraDao); + etsiNfvNsdCsarGenerator = new EtsiNfvNsdCsarGeneratorImpl(new NsDescriptorConfig(version2_5_1), + vnfDescriptorGenerator, nsDescriptorGeneratorFactory, artifactCassandraDao, nsdCsarEtsiOption2Signer); when(nsDescriptorGeneratorFactory.create()).thenReturn(nsDescriptorGeneratorImpl); } @@ -83,8 +92,29 @@ class EtsiNfvNsdCsarGeneratorImplTest { void generateNsdCsarSuccessfulTest() throws VnfDescriptorException, NsdException { mockServiceComponent(); mockServiceComponentArtifacts(); - final byte[] nsdCsar = etsiNfvNsdCsarGenerator.generateNsdCsar(service); - assertThat("", nsdCsar, is(notNullValue())); + final NsdCsar nsdCsar = etsiNfvNsdCsarGenerator.generateNsdCsar(service); + assertThat("The NSD CSAR should not be null", nsdCsar, is(notNullValue())); + assertThat("The NSD CSAR should not be signed", nsdCsar.isSigned(), is(false)); + assertThat("The NSD CSAR content should not be null", nsdCsar.getCsarPackage(), is(notNullValue())); + } + + @Test + void generateSignedNsdCsarSuccessfulTest() throws VnfDescriptorException, NsdException, NsdSignatureException { + mockServiceComponent(); + mockServiceComponentArtifacts(); + when(nsdCsarEtsiOption2Signer.isCertificateConfigured()).thenReturn(true); + final String path = getClass().getClassLoader().getResource("aFile.txt").getPath(); + System.out.println(path); + final CertificateInfoImpl certificateInfo = new CertificateInfoImpl(new File(path), null); + when(nsdCsarEtsiOption2Signer.getSigningCertificate()).thenReturn(Optional.of(certificateInfo)); + when(nsdCsarEtsiOption2Signer.sign(any(byte[].class))).thenReturn("signedCsar".getBytes(StandardCharsets.UTF_8)); + final NsdCsar nsdCsar = etsiNfvNsdCsarGenerator.generateNsdCsar(service); + verify(nsdCsarEtsiOption2Signer).signArtifacts(any(NsdCsar.class)); + assertThat("The NSD CSAR should not be null", nsdCsar, is(notNullValue())); + assertThat("The NSD CSAR should be signed", nsdCsar.isSigned(), is(true)); + assertThat("The NSD CSAR content should not be null", nsdCsar.getCsarPackage(), is(notNullValue())); + assertThat("The NSD CSAR name should be as expected", nsdCsar.getFileName(), is(SERVICE_NORMALIZED_NAME)); + assertThat("The NSD CSAR name should be as expected", nsdCsar.isEmpty(), is(false)); } @Test() @@ -103,6 +133,7 @@ class EtsiNfvNsdCsarGeneratorImplTest { final Nsd nsd = new Nsd(); when(vnfDescriptorGenerator.generate(componentInstance1Name, instanceArtifact1)).thenReturn(Optional.of(vnfDescriptor1)); when(nsDescriptorGeneratorImpl.generate(service, vnfDescriptorList)).thenReturn(Optional.of(nsd)); + final List<CategoryDefinition> categoryDefinitionList = new ArrayList<>(); final CategoryDefinition nsComponentCategoryDefinition = new CategoryDefinition(); nsComponentCategoryDefinition.setName(ETSI_NS_COMPONENT_CATEGORY); diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/model/NsdCsarTest.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/model/NsdCsarTest.java new file mode 100644 index 0000000000..061a52a2dc --- /dev/null +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/model/NsdCsarTest.java @@ -0,0 +1,140 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.builder.NsdCsarManifestBuilder; + +class NsdCsarTest { + + @Test + void testAddFile() { + final NsdCsar nsdCsar = new NsdCsar(""); + assertTrue(nsdCsar.getFileMap().isEmpty()); + final String aFilePath = "aFile"; + final byte[] fileContent = aFilePath.getBytes(StandardCharsets.UTF_8); + nsdCsar.addFile(aFilePath, fileContent); + assertEquals(1, nsdCsar.getFileMap().size()); + assertEquals(fileContent, nsdCsar.getFile(aFilePath)); + } + + @Test + void testGetFile() { + final NsdCsar nsdCsar = new NsdCsar(""); + final String aFilePath = "aFile"; + assertNull(nsdCsar.getFile(aFilePath)); + final byte[] fileContent = aFilePath.getBytes(StandardCharsets.UTF_8); + nsdCsar.addFile(aFilePath, fileContent); + assertEquals(1, nsdCsar.getFileMap().size()); + assertEquals(fileContent, nsdCsar.getFile(aFilePath)); + } + + @Test + void testIsManifest() { + final NsdCsar nsdCsar = new NsdCsar(""); + assertTrue(nsdCsar.isManifest(nsdCsar.getManifestPath())); + assertFalse(nsdCsar.isManifest("")); + } + + @Test + void testGetManifest() { + final NsdCsar nsdCsar = new NsdCsar(""); + assertNull(nsdCsar.getManifest()); + final byte[] expectedManifest = "".getBytes(StandardCharsets.UTF_8); + nsdCsar.addFile(nsdCsar.getManifestPath(), expectedManifest); + final byte[] actualManifest = nsdCsar.getManifest(); + assertNotNull(actualManifest); + assertEquals(actualManifest, expectedManifest); + } + + @Test + void testGetMainDefinition() { + final String csarFileName = "csarFileName"; + final NsdCsar nsdCsar = new NsdCsar(csarFileName); + assertNull(nsdCsar.getMainDefinition()); + assertTrue(nsdCsar.getMainDefinitionPath().contains(csarFileName)); + final byte[] expectedMainDefinition = "".getBytes(StandardCharsets.UTF_8); + nsdCsar.addFile(nsdCsar.getMainDefinitionPath(), expectedMainDefinition); + final byte[] actualMainDefinition = nsdCsar.getMainDefinition(); + assertNotNull(actualMainDefinition); + assertEquals(actualMainDefinition, expectedMainDefinition); + } + + @Test + void testFileMapEncapsulation() { + final NsdCsar nsdCsar = new NsdCsar(""); + final Map<String, byte[]> fileMap = nsdCsar.getFileMap(); + fileMap.put("", new byte[]{}); + assertTrue(nsdCsar.getFileMap().isEmpty()); + } + + @Test + void addAllFiles() { + final NsdCsar nsdCsar = new NsdCsar(""); + assertTrue(nsdCsar.isEmpty()); + Map<String, byte[]> fileMap = new HashMap(); + fileMap.put("1", new byte[]{}); + fileMap.put("2", new byte[]{}); + fileMap.put("3", new byte[]{}); + fileMap.put("4", new byte[]{}); + nsdCsar.addAllFiles(fileMap); + assertFalse(nsdCsar.isEmpty()); + assertEquals(nsdCsar.getFileMap().size(), fileMap.size()); + } + + @Test + void testIfStartsEmpty() { + final NsdCsar nsdCsar = new NsdCsar("test"); + assertTrue(nsdCsar.getFileMap().isEmpty(), "Csar should starts empty"); + assertTrue(nsdCsar.isEmpty(), "Csar should starts empty"); + } + + @Test + void testIsEmpty() { + final NsdCsar nsdCsar = new NsdCsar("test"); + assertTrue(nsdCsar.isEmpty()); + nsdCsar.addFile("", new byte[]{}); + assertFalse(nsdCsar.isEmpty()); + } + + @Test + void testAddManifest() { + final NsdCsar nsdCsar = new NsdCsar("test"); + nsdCsar.addManifest(null); + assertTrue(nsdCsar.isEmpty()); + final NsdCsarManifestBuilder manifestBuilder = new NsdCsarManifestBuilder(); + nsdCsar.addManifest(manifestBuilder); + assertFalse(nsdCsar.isEmpty()); + assertEquals(1, nsdCsar.getFileMap().size()); + final byte[] expectedManifestContent = manifestBuilder.build().getBytes(StandardCharsets.UTF_8); + assertThat("Manifest content should be the same", nsdCsar.getManifest(), is(expectedManifestContent)); + } +}
\ No newline at end of file diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/NsdCsarEtsiOption2SignerTest.java b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/NsdCsarEtsiOption2SignerTest.java new file mode 100644 index 0000000000..e6e89574f9 --- /dev/null +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/NsdCsarEtsiOption2SignerTest.java @@ -0,0 +1,183 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.NsdCsarEtsiOption2Signer.SDC_NSD_CERT_NAME; +import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.NsdCsarEtsiOption2Signer.SIGNATURE_EXTENSION; +import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception.NsdSignatureExceptionSupplier.certificateNotConfigured; +import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception.NsdSignatureExceptionSupplier.invalidCertificate; +import static org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception.NsdSignatureExceptionSupplier.unableToCreateSignature; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openecomp.sdc.be.csar.security.api.CertificateManager; +import org.openecomp.sdc.be.csar.security.api.CmsContentSigner; +import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo; +import org.openecomp.sdc.be.csar.security.exception.CmsSignatureException; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.model.NsdCsar; +import org.openecomp.sdc.be.plugins.etsi.nfv.nsd.security.exception.NsdSignatureException; +import org.springframework.core.env.Environment; + +class NsdCsarEtsiOption2SignerTest { + + private static final String CERT_NAME = "nsdCert"; + + @Mock + private CertificateManager certificateManager; + @Mock + private CmsContentSigner cmsContentSigner; + @Mock + private Environment environment; + @Mock + private CertificateInfo certificateInfo; + @InjectMocks + private NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer; + + @BeforeEach + void setUp() { + MockitoAnnotations.initMocks(this); + when(environment.getProperty(SDC_NSD_CERT_NAME)).thenReturn(CERT_NAME); + when(certificateManager.getCertificate(CERT_NAME)).thenReturn(Optional.of(certificateInfo)); + when(certificateInfo.isValid()).thenReturn(true); + } + + @Test + void signNsdTest() throws NsdSignatureException, CmsSignatureException { + final NsdCsar nsdCsar = new NsdCsar(""); + nsdCsar.addFile("aFile", "aFile".getBytes(StandardCharsets.UTF_8)); + final byte[] aFileSigned = "aFileSigned".getBytes(StandardCharsets.UTF_8); + when(cmsContentSigner.signData(eq("aFile".getBytes(StandardCharsets.UTF_8)), any(), any())).thenReturn( + aFileSigned); + final String aFileSignedPemString = "aFileSignedPemString"; + when(cmsContentSigner.formatToPemSignature(aFileSigned)).thenReturn(aFileSignedPemString); + nsdCsarEtsiOption2Signer.signArtifacts(nsdCsar); + assertThat("The NSD CSAR should contain the original file and its signature", + nsdCsar.getFileMap().keySet(), hasSize(2)); + assertThat("The signed file should be as expected", + nsdCsar.getFile("aFile" + SIGNATURE_EXTENSION), is(aFileSignedPemString.getBytes(StandardCharsets.UTF_8))); + } + + @Test + void dontCreateNsdManifestSignatureFileTest() throws NsdSignatureException { + final NsdCsar nsdCsar = new NsdCsar("nsdCsar"); + nsdCsar.addFile(nsdCsar.getManifestPath(), "manifest".getBytes(StandardCharsets.UTF_8)); + nsdCsarEtsiOption2Signer.signArtifacts(nsdCsar); + assertThat("The NSD CSAR should contain only the original file", + nsdCsar.getFileMap().keySet(), hasSize(1)); + assertThat("The NSD CSAR should not contain the manifest signature file", + nsdCsar.getFile(nsdCsar.getManifestPath() + SIGNATURE_EXTENSION), is(nullValue())); + } + + @Test + void signEmptyNsdTest() throws NsdSignatureException { + final NsdCsar nsdCsar = new NsdCsar(""); + nsdCsarEtsiOption2Signer.signArtifacts(nsdCsar); + assertThat("The NSD CSAR should continue empty", nsdCsar.isEmpty(), is(true)); + } + + @Test + void signNsdNoCertificateTest() { + when(certificateManager.getCertificate(CERT_NAME)).thenReturn(Optional.empty()); + final NsdCsar nsdCsar = new NsdCsar(""); + nsdCsar.addFile("anyFile", "anyFile".getBytes()); + final NsdSignatureException actualException = assertThrows(NsdSignatureException.class, + () -> nsdCsarEtsiOption2Signer.signArtifacts(nsdCsar)); + assertThat(actualException.getMessage(), is(certificateNotConfigured().getMessage())); + } + + @Test + void signWholeNoCertificateTest() { + when(certificateManager.getCertificate(CERT_NAME)).thenReturn(Optional.empty()); + final NsdSignatureException actualException = + Assertions.assertThrows(NsdSignatureException.class, + () -> nsdCsarEtsiOption2Signer.sign(new byte[]{})); + assertThat(actualException.getMessage(), is(certificateNotConfigured().getMessage())); + } + + @Test + void signWithInvalidCertificateTest() { + when(certificateInfo.isValid()).thenReturn(false); + final NsdSignatureException actualException = + Assertions.assertThrows(NsdSignatureException.class, + () -> nsdCsarEtsiOption2Signer.sign(new byte[]{})); + assertThat(actualException.getMessage(), is(invalidCertificate(null).getMessage())); + } + + @Test + void signWholeFileTest() throws NsdSignatureException, CmsSignatureException { + final byte[] nsdCsarBytes = "nsdCsarBytes".getBytes(StandardCharsets.UTF_8); + final NsdCsar nsdCsar = new NsdCsar(""); + nsdCsar.addFile("aFile", "aFile".getBytes(StandardCharsets.UTF_8)); + final byte[] nsdCsarBytesSigned = "nsdCsarBytesSigned".getBytes(StandardCharsets.UTF_8); + when(cmsContentSigner.signData(eq(nsdCsarBytes), any(), any())).thenReturn(nsdCsarBytesSigned); + final String nsdCsarBytesSignedPemString = "nsdCsarBytesSignedPemString"; + when(cmsContentSigner.formatToPemSignature(nsdCsarBytesSigned)).thenReturn(nsdCsarBytesSignedPemString); + final byte[] actualNsdSignedCsar = nsdCsarEtsiOption2Signer.sign(nsdCsarBytes); + assertThat("Signature should be as expected", + actualNsdSignedCsar, is(nsdCsarBytesSignedPemString.getBytes(StandardCharsets.UTF_8))); + } + + @Test + void signatureCreationErrorTest() throws CmsSignatureException { + final byte[] nsdCsarBytes = "nsdCsarBytes".getBytes(StandardCharsets.UTF_8); + final NsdCsar nsdCsar = new NsdCsar(""); + nsdCsar.addFile("aFile", "aFile".getBytes(StandardCharsets.UTF_8)); + when(cmsContentSigner.signData(eq(nsdCsarBytes), any(), any())) + .thenThrow(new CmsSignatureException(null, null)); + final NsdSignatureException actualException = assertThrows(NsdSignatureException.class, + () -> nsdCsarEtsiOption2Signer.sign(nsdCsarBytes)); + assertThat(actualException.getMessage(), is(unableToCreateSignature(null).getMessage())); + } + + @Test + void getSigningCertificateTest() { + when(certificateManager.getCertificate(CERT_NAME)).thenReturn(Optional.empty()); + Optional<CertificateInfo> signingCertificate = nsdCsarEtsiOption2Signer.getSigningCertificate(); + assertThat("Certificate should not be present", signingCertificate.isEmpty(), is(true)); + when(certificateManager.getCertificate(CERT_NAME)).thenReturn(Optional.of(certificateInfo)); + signingCertificate = nsdCsarEtsiOption2Signer.getSigningCertificate(); + assertThat("Certificate should be present", signingCertificate.isPresent(), is(true)); + assertThat("Certificate should be as expected", signingCertificate.get(), is(certificateInfo)); + } + + @Test + void isCertificateConfiguredTest() { + when(certificateManager.getCertificate(CERT_NAME)).thenReturn(Optional.empty()); + boolean isCertificateConfigured = nsdCsarEtsiOption2Signer.isCertificateConfigured(); + assertThat("Certificate should not be configured", isCertificateConfigured, is(false)); + when(certificateManager.getCertificate(CERT_NAME)).thenReturn(Optional.of(certificateInfo)); + isCertificateConfigured = nsdCsarEtsiOption2Signer.isCertificateConfigured(); + assertThat("Certificate should be configured", isCertificateConfigured, is(true)); + } +}
\ No newline at end of file diff --git a/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/resources/aFile.txt b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/resources/aFile.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/test/resources/aFile.txt |