From 13b39127c1c91d7c05c67ea2c14220c8f992cba5 Mon Sep 17 00:00:00 2001 From: "andre.schmid" Date: Thu, 28 Jan 2021 17:53:22 +0000 Subject: ETSI SOL007 3.3.1 package security option 2 Change-Id: I4e021c517449e6ddf11571c02d0b4bdbc93e7c1e Issue-ID: SDC-2614 Signed-off-by: andre.schmid --- .../nfv/nsd/builder/NsdCsarManifestBuilder.java | 19 ++- .../factory/EtsiNfvNsdCsarGeneratorFactory.java | 10 +- .../nsd/generator/EtsiNfvNsCsarEntryGenerator.java | 19 ++- .../nfv/nsd/generator/EtsiNfvNsdCsarGenerator.java | 4 +- .../nsd/generator/EtsiNfvNsdCsarGeneratorImpl.java | 132 ++++++++++++++---- .../sdc/be/plugins/etsi/nfv/nsd/model/NsdCsar.java | 110 +++++++++++++++ .../nfv/nsd/security/NsdCsarEtsiOption2Signer.java | 151 +++++++++++++++++++++ .../security/exception/NsdSignatureException.java | 31 +++++ .../exception/NsdSignatureExceptionSupplier.java | 39 ++++++ 9 files changed, 475 insertions(+), 40 deletions(-) create mode 100644 catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/model/NsdCsar.java create mode 100644 catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/NsdCsarEtsiOption2Signer.java create mode 100644 catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/exception/NsdSignatureException.java create mode 100644 catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp/sdc/be/plugins/etsi/nfv/nsd/security/exception/NsdSignatureExceptionSupplier.java (limited to 'catalog-be-plugins/etsi-nfv-nsd-csar-plugin/src/main/java/org/openecomp') 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 sources; private final Set 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 etsiNfvNsdCsarGeneratorObjectProvider; + private final NsdCsarEtsiOption2Signer nsdCsarEtsiOption2Signer; public EtsiNfvNsdCsarGeneratorFactory(final VnfDescriptorGenerator vnfDescriptorGenerator, final NsDescriptorGeneratorFactory nsDescriptorGeneratorFactory, final ArtifactCassandraDao artifactCassandraDao, - final ObjectProvider etsiNfvNsdCsarGeneratorObjectProvider) { + final ObjectProvider 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 createEntry(final String csarName, final byte[] nsdCsar) { + private Map createEntry(final NsdCsar nsdCsar) { final Map 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 nsdCsarFiles = new HashMap<>(); - final List vnfDescriptorList = generateVnfPackages(component); - vnfDescriptorList.forEach(vnfPackage -> nsdCsarFiles.putAll(vnfPackage.getDefinitionFiles())); final String nsdFileName = getNsdFileName(component); + final NsdCsar nsdCsar = new NsdCsar(nsdFileName); + + final List 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 nsdCsarFileMap) { + private Map 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 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 nsdCsarFileMap) { + private Optional 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 files) { + private NsdCsarManifestBuilder createManifestBuilder(final Nsd nsd, final EtsiVersion nsdVersion, + final String manifestFilePath, final Set filePath, + final boolean addSignatureFiles) { LOGGER.debug("Creating NS manifest file content"); + final Set 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 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 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 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 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 ETSI SOL007 v3.3.1 documentation + */ +@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 fileMap = nsdCsar.getFileMap().entrySet().stream() + .filter(entry -> !nsdCsar.isManifest(entry.getKey())) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + + for (final Entry 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> manifestEntryOpt = nsdCsar.getFileMap().entrySet().stream() + .filter(entry -> nsdCsar.isManifest(entry.getKey())).findFirst(); + if (manifestEntryOpt.isEmpty()) { + return; + } + final CertificateInfo certificateInfo = getValidCertificate(); + + final Entry 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 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 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 -- cgit 1.2.3-korg