diff options
Diffstat (limited to 'csarvalidation')
22 files changed, 1264 insertions, 536 deletions
diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/CSARArchive.java b/csarvalidation/src/main/java/org/onap/cvc/csar/CSARArchive.java index 05f2070..74b1bac 100644 --- a/csarvalidation/src/main/java/org/onap/cvc/csar/CSARArchive.java +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/CSARArchive.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; /** * Verify the CSAR package by following the SOL004 specifications and ONAP VNFREQS for TOSCA. @@ -782,11 +783,13 @@ public class CSARArchive implements AutoCloseable { } public Map<String, Map<String, List<String>>> getNonMano() { - return nonMano; + return Map.copyOf(nonMano); } public void setNonMano(Map<String, Map<String, List<String>>> nonMano) { - this.nonMano = nonMano; + this.nonMano = nonMano.entrySet().stream() + .filter(mapEntry -> mapEntry.getKey() != null && mapEntry.getValue() != null) + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); this.isNonManoAvailable = true; } diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206.java index 6f1ff1f..b14e798 100644 --- a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206.java +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206.java @@ -21,228 +21,27 @@ package org.onap.cvc.csar.cc.sol004; import org.onap.cli.fw.error.OnapCommandException; import org.onap.cli.fw.schema.OnapCommandSchema; import org.onap.cvc.csar.CSARArchive; -import org.onap.cvc.csar.FileArchive; import org.onap.cvc.csar.cc.VTPValidateCSARBase; -import org.onap.cvc.csar.parser.ManifestFileModel; -import org.onap.cvc.csar.parser.ManifestFileSplitter; -import org.onap.cvc.csar.parser.SourcesParser; -import org.onap.cvc.csar.security.CertificateLoadingException; -import org.onap.cvc.csar.security.CmsSignatureData; -import org.onap.cvc.csar.security.CmsSignatureDataFactory; -import org.onap.cvc.csar.security.CmsSignatureLoadingException; -import org.onap.cvc.csar.security.CmsSignatureValidator; -import org.onap.cvc.csar.security.CmsSignatureValidatorException; -import org.onap.cvc.csar.security.ShaHashCodeGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.onap.cvc.csar.cc.sol004.r130206.CsarSecurityValidator; +import org.onap.cvc.csar.cc.sol004.r130206.Error; -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Files; import java.nio.file.Path; -import java.security.NoSuchAlgorithmException; -import java.util.Collection; -import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; @OnapCommandSchema(schema = "vtp-validate-csar-r130206.yaml") public class VTPValidateCSARR130206 extends VTPValidateCSARBase { - private static final Logger LOG = LoggerFactory.getLogger(VTPValidateCSARR130206.class); - private static final String SHA_256 = "SHA-256"; - private static final String SHA_512 = "SHA-512"; - private static final String EMPTY_STRING = ""; - - private final ShaHashCodeGenerator shaHashCodeGenerator = new ShaHashCodeGenerator(); - private final FileSignatureValidator fileSignatureValidator = new FileSignatureValidator(); - - public static class CSARErrorUnableToFindCertificate extends CSARArchive.CSARError { - - CSARErrorUnableToFindCertificate() { - super("0x4001"); - this.message = "Unable to find cert file!"; - } - } - - public static class CSARErrorUnableToFindCms extends CSARArchive.CSARError { - - CSARErrorUnableToFindCms() { - super("0x4002"); - this.message = "Unable to find cms signature!"; - } - } - - public static class CSARErrorUnableToLoadCms extends CSARArchive.CSARError { - CSARErrorUnableToLoadCms() { - super("0x4002"); - this.message = "Unable to load cms signature!"; - } - } - - public static class CSARErrorUnableToFindCsarContent extends CSARArchive.CSARError { - - CSARErrorUnableToFindCsarContent() { - super("0x4003"); - this.message = "Unable to find csar content!"; - } - } - - public static class CSARErrorWrongHashCode extends CSARArchive.CSARError { - - CSARErrorWrongHashCode(String path) { - super("0x4004"); - this.message = String.format("Source '%s' has wrong hash!", path); - } - } - - public static class CSARErrorUnableToFindAlgorithm extends CSARArchive.CSARError { - - CSARErrorUnableToFindAlgorithm(String path) { - super("0x4005"); - this.message = String.format("Source '%s' has hash, but unable to find algorithm tag!", path); - } - } - - public static class CSARErrorUnableToFindSource extends CSARArchive.CSARError { - - CSARErrorUnableToFindSource(String path) { - super("0x4006"); - this.message = String.format("Unable to calculate digest - file missing: %s", path); - } - } - - public static class CSARErrorInvalidSignature extends CSARArchive.CSARError { - - CSARErrorInvalidSignature() { - super("0x4007"); - this.message = "File has invalid signature!"; - } - } - - public static class CSARErrorContentMismatch extends CSARArchive.CSARError { - - CSARErrorContentMismatch() { - super("0x4008"); - this.message = "Mismatch between contents of non-mano-artifact-sets and source files of the package"; - } - } - - public static class CSARErrorUnableToFindEntryCertificate extends CSARArchive.CSARError { - - CSARErrorUnableToFindEntryCertificate() { - super("0x4009"); - this.message = "Unable to find cert file defined by ETSI-Entry-Certificate!"; - } - } - - public static class CSARErrorEntryCertificateIsDefinedDespiteTheCms extends CSARArchive.CSARError { - - CSARErrorEntryCertificateIsDefinedDespiteTheCms() { - super("0x4011"); - this.message = "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container"; - } - } - - public static class CSARErrorEntryCertificateIsPresentDespiteTheCms extends CSARArchive.CSARError { - - CSARErrorEntryCertificateIsPresentDespiteTheCms() { - super("0x4012"); - this.message = "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container"; - } - } - - public static class CSARErrorRootCertificateIsPresentDespiteTheCms extends CSARArchive.CSARError { - - CSARErrorRootCertificateIsPresentDespiteTheCms() { - super("0x4013"); - this.message = "Certificate present in root catalog despite the certificate is included in the signature container"; - } - } - - public static class CSARErrorRootCertificateIsPresentDespiteTheEtsiEntryCertificate extends CSARArchive.CSARError { - - CSARErrorRootCertificateIsPresentDespiteTheEtsiEntryCertificate() { - super("0x4013"); - this.message = "Certificate present in root catalog despite the TOSCA.meta file"; - } - } - - public static class CSARErrorUnableToFindCertificateEntryInTosca extends CSARArchive.CSARError { - - CSARErrorUnableToFindCertificateEntryInTosca() { - super("0x4014"); - this.message = "Unable to find ETSI-Entry-Certificate in Tosca file"; - } - } - - public static class CSARErrorUnableToFindArtifactCertificate extends CSARArchive.CSARError { - - CSARErrorUnableToFindArtifactCertificate(String path) { - super("0x4015"); - this.message = String.format("Source '%s' has signature tag, but unable to find certificate tag!", path); - } - } - - public static class CSARErrorUnableToFindArtifactCertificateFile extends CSARArchive.CSARError { - - CSARErrorUnableToFindArtifactCertificateFile(String path) { - super("0x4016"); - this.message = String.format("Source '%s' has certificate tag, pointing to non existing file!", path); - } - } - - public static class CSARErrorUnableToFindArtifactSignature extends CSARArchive.CSARError { - - CSARErrorUnableToFindArtifactSignature(String path) { - super("0x4017"); - this.message = String.format("Source '%s' has certificate tag, but unable to find signature tag!", path); - } - } - - public static class CSARErrorUnableToFindArtifactSignatureFile extends CSARArchive.CSARError { - - CSARErrorUnableToFindArtifactSignatureFile(String path) { - super("0x4018"); - this.message = String.format("Source '%s' has signature tag, pointing to non existing file!", path); - } - } - - public static class CSARErrorFailToLoadArtifactSignature extends CSARArchive.CSARError { - - CSARErrorFailToLoadArtifactSignature(String path, String cms) { - super("0x4019"); - this.message = String.format("Fail to load signature '%s', for source '%s'!", path, cms); - } - } - - public static class CSARErrorIncorrectArtifactSignature extends CSARArchive.CSARError { - - CSARErrorIncorrectArtifactSignature(String path) { - super("0x4020"); - this.message = String.format("Source '%s' has incorrect signature!", path); - } - } - - public static class CSARWarningNoSecurity extends CSARArchive.CSARErrorWarning { - CSARWarningNoSecurity() { - super(EMPTY_STRING, EMPTY_STRING, -1, EMPTY_STRING); - this.message = "Warning. Consider adding package integrity and authenticity assurance according to ETSI NFV-SOL 004 Security Option 1"; - } - } @Override protected void validateCSAR(CSARArchive csar) throws OnapCommandException { try { - FileArchive.Workspace workspace = csar.getWorkspace(); - final Optional<Path> pathToCsarFolder = workspace.getPathToCsarFolder(); + final Optional<Path> pathToCsarFolder = getPathToCsar(csar); if (pathToCsarFolder.isPresent()) { - validate(csar, pathToCsarFolder.get()); + final CsarSecurityValidator csarSecurityValidator = new CsarSecurityValidator(csar, pathToCsarFolder.get()); + this.errors.addAll(csarSecurityValidator.validate()); } else { - this.errors.add(new CSARErrorUnableToFindCsarContent()); + this.errors.add(new Error.CSARErrorUnableToFindCsarContent()); } } catch (Exception e) { LOG.error("Internal VTPValidateCSARR130206 command error", e); @@ -251,263 +50,8 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } - private void validate(CSARArchive csar, Path csarRootDirectory) throws IOException, NoSuchAlgorithmException { - if (containsCms(csar.getManifest())) { - validateCmsSignature(csar, csarRootDirectory); - } else if ( - (containsToscaMeta(csar) && containsCertificateInTosca(csar.getToscaMeta())) || - containsCertificateInRootCatalog(csar) || - containsPerArtifactSecurity(csar.getManifest())) { - this.errors.add(new CSARErrorUnableToFindCms()); - } else { - this.errors.add(new CSARWarningNoSecurity()); - } - } - - private void validateCmsSignature(CSARArchive csar, Path csarRootDirectory) throws NoSuchAlgorithmException, IOException { - try { - CmsSignatureData signatureData = this.fileSignatureValidator.createSignatureDataForManifestFile(csar.getManifestMfFile()); - if (signatureData.getCertificate().isPresent()) { - validateCertificationUsingCmsCertificate(signatureData, csar, csarRootDirectory); - } else if (containsToscaMeta(csar)) { - validateCertificationUsingTosca(signatureData, csar, csarRootDirectory); - } else if (containsCertificateInRootCatalog(csar)) { - validateCertificationUsingCertificateFromRootDirectory(signatureData, csar, csarRootDirectory); - } else { - this.errors.add(new CSARErrorUnableToFindCertificate()); - } - } catch (CmsSignatureLoadingException e) { - LOG.error("Unable to load CMS!", e); - this.errors.add(new CSARErrorUnableToLoadCms()); - } - } - - private boolean containsCms(CSARArchive.Manifest manifest) { - String cms = manifest.getCms(); - return cms != null && !cms.equals(EMPTY_STRING); - } - - private boolean containsToscaMeta(CSARArchive archive) { - return archive.getToscaMetaFile() != null; - } - - private boolean containsCertificateInTosca(CSARArchive.TOSCAMeta toscaMeta) { - String certificate = toscaMeta.getEntryCertificate(); - return certificate != null && !certificate.equals(EMPTY_STRING); - } - - private boolean containsCertificateInRootCatalog(CSARArchive csar) { - File potentialCertificateFileInRootDirectory = getCertificateFromRootDirectory(csar); - return potentialCertificateFileInRootDirectory.exists(); - } - - private boolean containsPerArtifactSecurity(CSARArchive.Manifest manifest) { - return manifest.getSources().stream().anyMatch( - source -> - !source.getAlgorithm().equals(EMPTY_STRING) || - !source.getHash().equals(EMPTY_STRING) || - !source.getCertificate().equals(EMPTY_STRING) || - !source.getSignature().equals(EMPTY_STRING) - ); - } - - private void validateCertificationUsingCmsCertificate(CmsSignatureData signatureData, CSARArchive csar, Path csarRootDirectory) - throws NoSuchAlgorithmException, IOException { - validateAllSources(csar, csarRootDirectory); - validateFileSignature(signatureData); - if (containsCertificateInTosca(csar.getToscaMeta())) { - errors.add(new CSARErrorEntryCertificateIsDefinedDespiteTheCms()); - if (csar.getFileFromCsar(csar.getToscaMeta().getEntryCertificate()).exists()) { - errors.add(new CSARErrorEntryCertificateIsPresentDespiteTheCms()); - } - } - if (containsCertificateInRootCatalog(csar)) { - errors.add(new CSARErrorRootCertificateIsPresentDespiteTheCms()); - } - } - - private void validateCertificationUsingTosca(CmsSignatureData signatureData, CSARArchive csar, Path csarRootDirectory) - throws NoSuchAlgorithmException, IOException { - validateAllSources(csar, csarRootDirectory); - if (loadCertificateFromTosca(signatureData, csar)) { - validateFileSignature(signatureData); - } - if (containsCertificateInRootCatalog(csar) && rootCertificateIsNotReferredAsToscaEtsiEntryCertificate(csar)) { - errors.add(new CSARErrorRootCertificateIsPresentDespiteTheEtsiEntryCertificate()); - } - } - - private boolean loadCertificateFromTosca(CmsSignatureData signatureData, CSARArchive csar) { - if (csar.getToscaMeta().getEntryCertificate() != null) { - try { - final Path absolutePathToEntryCertificate = csar.getFileFromCsar(csar.getToscaMeta().getEntryCertificate()).toPath(); - signatureData.loadCertificate(absolutePathToEntryCertificate); - return true; - } catch (CertificateLoadingException e) { - this.errors.add(new CSARErrorUnableToFindEntryCertificate()); - return false; - } - } else { - this.errors.add(new CSARErrorUnableToFindCertificateEntryInTosca()); - return false; - } - } - - private boolean rootCertificateIsNotReferredAsToscaEtsiEntryCertificate(CSARArchive csar) { - String pathToRootCertificate = getCertificateFromRootDirectory(csar).getPath(); - String pathToEntryEtsiCertificate = csar.getFileFromCsar(csar.getToscaMeta().getEntryCertificate()).getPath(); - return !pathToRootCertificate.equals(pathToEntryEtsiCertificate); - } - - private void validateCertificationUsingCertificateFromRootDirectory(CmsSignatureData signatureData, CSARArchive csar, Path csarRootDirectory) - throws NoSuchAlgorithmException, IOException { - validateAllSources(csar, csarRootDirectory); - if (loadCertificateFromRootDirectory(signatureData, csar)) { - validateFileSignature(signatureData); - } - } - - private boolean loadCertificateFromRootDirectory(CmsSignatureData signatureData, CSARArchive csar) { - try { - File certificateFileFromRootDirectory = getCertificateFromRootDirectory(csar); - signatureData.loadCertificate(certificateFileFromRootDirectory.toPath()); - return true; - } catch (CertificateLoadingException e) { - LOG.error("Uable to read ETSI entry certificate file!", e); - return false; - } - } - - private File getCertificateFromRootDirectory(CSARArchive csar) { - String nameOfCertificate = - csar.getManifestMfFile().getName().split("\\.")[0] + - ".cert"; - return csar.getFileFromCsar(nameOfCertificate); - } - - private void validateAllSources(CSARArchive csar, Path csarRootDirectory) - throws NoSuchAlgorithmException, IOException { - final CSARArchive.Manifest manifest = csar.getManifest(); - validateSources(csarRootDirectory, manifest); - - final Map<String, Map<String, List<String>>> nonMano = manifest.getNonMano(); - final List<SourcesParser.Source> sources = manifest.getSources(); - - validateNonManoCohesionWithSources(nonMano, sources); - } - - private void validateNonManoCohesionWithSources(final Map<String, Map<String, List<String>>> nonMano, - final List<SourcesParser.Source> sources) { - - final Collection<Map<String, List<String>>> values = nonMano.values(); - final List<String> nonManoSourcePaths = values.stream() - .map(Map::values) - .flatMap(Collection::stream) - .flatMap(List::stream) - .filter(it -> !it.isEmpty()) - .collect(Collectors.toList()); - - final List<String> sourcePaths = sources.stream() - .map(SourcesParser.Source::getValue) - .collect(Collectors.toList()); - - if (!sourcePaths.containsAll(nonManoSourcePaths)) { - this.errors.add(new CSARErrorContentMismatch()); - } - - } - - private void validateFileSignature(CmsSignatureData signatureData) { - final boolean isValid = this.fileSignatureValidator.isValid(signatureData); - if (!isValid) { - this.errors.add(new CSARErrorInvalidSignature()); - } - } - - private void validateSources(Path csarRootDirectory, CSARArchive.Manifest manifest) - throws NoSuchAlgorithmException, IOException { - final List<SourcesParser.Source> sources = manifest.getSources(); - for (SourcesParser.Source source : sources) { - validateSource(csarRootDirectory, source); - } - } - - private void validateSource(Path csarRootDirectory, SourcesParser.Source source) - throws NoSuchAlgorithmException, IOException { - final Path sourcePath = csarRootDirectory.resolve(source.getValue()); - if (sourcePath.toFile().exists()) { - validateHashIfPresent(csarRootDirectory, source); - validateArtifactSignatureIfPresent(csarRootDirectory, source); - } else { - this.errors.add(new CSARErrorUnableToFindSource(source.getValue())); - } - } - - private void validateHashIfPresent(Path csarRootDirectory, SourcesParser.Source source) - throws NoSuchAlgorithmException, IOException { - if (!source.getAlgorithm().isEmpty()) { - validateSourceHashCode(csarRootDirectory, source); - } else if (source.getAlgorithm().isEmpty() && !source.getHash().isEmpty()) { - this.errors.add(new CSARErrorUnableToFindAlgorithm(source.getValue())); - } - } - - private void validateSourceHashCode(Path csarRootDirectory, SourcesParser.Source source) - throws NoSuchAlgorithmException, IOException { - String hashCode = generateHashCode(csarRootDirectory, source); - if (!hashCode.equals(source.getHash())) { - this.errors.add(new CSARErrorWrongHashCode(source.getValue())); - } - } - - private String generateHashCode(Path csarRootDirectory, SourcesParser.Source source) - throws NoSuchAlgorithmException, IOException { - final byte[] sourceData = Files.readAllBytes(csarRootDirectory.resolve(source.getValue())); - final String algorithm = source.getAlgorithm(); - - if (algorithm.equalsIgnoreCase(SHA_256)) { - return this.shaHashCodeGenerator.generateSha256(sourceData); - } else if (algorithm.equalsIgnoreCase(SHA_512)) { - return this.shaHashCodeGenerator.generateSha512(sourceData); - } - - throw new UnsupportedOperationException(String.format("Algorithm '%s' is not supported!", algorithm)); - } - - private void validateArtifactSignatureIfPresent(Path csarRootDirectory, SourcesParser.Source source) - throws IOException { - if (!source.getSignature().isEmpty() && !source.getCertificate().isEmpty()) { - validateArtifactSignature(csarRootDirectory, source); - } else if (!source.getSignature().isEmpty()) { - this.errors.add(new CSARErrorUnableToFindArtifactCertificate(source.getValue())); - } else if (!source.getCertificate().isEmpty()) { - this.errors.add(new CSARErrorUnableToFindArtifactSignature(source.getValue())); - } - } - - private void validateArtifactSignature(Path csarRootDirectory, SourcesParser.Source source) - throws IOException { - final Path filePath = csarRootDirectory.resolve(source.getValue()); - final Path signaturePath = csarRootDirectory.resolve(source.getSignature()); - final Path certificatePath = csarRootDirectory.resolve(source.getCertificate()); - if (signaturePath.toFile().exists() && certificatePath.toFile().exists()) { - try { - CmsSignatureData signatureData = - fileSignatureValidator.createSignatureData(filePath, signaturePath, certificatePath); - if( !fileSignatureValidator.isValid(signatureData) ) { - this.errors.add(new CSARErrorIncorrectArtifactSignature(source.getValue())); - } - } catch (CmsSignatureLoadingException e) { - this.errors.add(new CSARErrorFailToLoadArtifactSignature(source.getValue(),source.getSignature())); - } - } else { - if (!signaturePath.toFile().exists()) { - this.errors.add(new CSARErrorUnableToFindArtifactSignatureFile(source.getValue())); - } - if (!certificatePath.toFile().exists()) { - this.errors.add(new CSARErrorUnableToFindArtifactCertificateFile(source.getValue())); - } - } + private Optional<Path> getPathToCsar(CSARArchive csar) { + return csar.getWorkspace().getPathToCsarFolder(); } @Override @@ -516,42 +60,4 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } - static class FileSignatureValidator { - - private final ManifestFileSplitter manifestFileSplitter = new ManifestFileSplitter(); - private final CmsSignatureValidator cmsSignatureValidator = new CmsSignatureValidator(); - private final CmsSignatureDataFactory cmsSignatureDataFactory = new CmsSignatureDataFactory(); - - CmsSignatureData createSignatureDataForManifestFile(File manifestFile) throws CmsSignatureLoadingException { - ManifestFileModel mf = manifestFileSplitter.split(manifestFile); - return cmsSignatureDataFactory.createForFirstSigner( - toBytes(mf.getCMS(), mf.getNewLine()), - toBytes(mf.getData(), mf.getNewLine()) - ); - } - - CmsSignatureData createSignatureData(Path filePath, Path cmsFilePath, Path certFilePath) throws CmsSignatureLoadingException, IOException { - CmsSignatureData signatureData = cmsSignatureDataFactory.createForFirstSigner( - Files.readAllBytes(cmsFilePath), - Files.readAllBytes(filePath) - ); - signatureData.loadCertificate(certFilePath); - return signatureData; - } - - boolean isValid(CmsSignatureData signatureData) { - try { - return cmsSignatureValidator.verifySignedData(signatureData); - } catch (CmsSignatureValidatorException e) { - LOG.error("Unable to verify signed data!", e); - return false; - } - } - - private byte[] toBytes(List<String> data, String newLine) { - final String updatedData = data.stream().map(it -> it + newLine).collect(Collectors.joining()); - return updatedData.getBytes(Charset.defaultCharset()); - } - } - } diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSecurityValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSecurityValidator.java new file mode 100644 index 0000000..4196136 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSecurityValidator.java @@ -0,0 +1,220 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206; + +import org.onap.cvc.csar.CSARArchive; +import org.onap.cvc.csar.parser.SourcesParser; +import org.onap.cvc.csar.security.CertificateLoadingException; +import org.onap.cvc.csar.security.CmsSignatureData; +import org.onap.cvc.csar.security.CmsSignatureLoadingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.onap.validation.csar.FileUtil.getFileNameWithoutExtension; + +public class CsarSecurityValidator { + + private static final Logger LOG = LoggerFactory.getLogger(CsarSecurityValidator.class); + + private static final String EMPTY_STRING = ""; + public static final String CERTIFICATE_EXTENSION = ".cert"; + private final FileSignatureValidator fileSignatureValidator = new FileSignatureValidator(); + + private final List<CSARArchive.CSARError> errors = new ArrayList<>(); + + private final CSARArchive csar; + private final Path csarRootDirectory; + + public CsarSecurityValidator(CSARArchive csar, Path csarRootDirectory) { + this.csar = csar; + this.csarRootDirectory = csarRootDirectory; + } + + public List<CSARArchive.CSARError> validate() throws IOException, NoSuchAlgorithmException { + this.errors.clear(); + if (containsCms()) { + validateCsarSecurity(); + } else if (containAnySecurityElements()) { + this.errors.add(new Error.CSARErrorUnableToFindCms()); + } else { + this.errors.add(new Error.CSARWarningNoSecurity()); + } + return errors; + } + + private boolean containAnySecurityElements() { + return (containsToscaMeta() && containsCertificateInTosca()) || + containsCertificateInRootCatalog() || + containsPerArtifactSecurity(); + } + + private void validateCsarSecurity() throws NoSuchAlgorithmException, IOException { + try { + CmsSignatureData signatureData = this.fileSignatureValidator.createSignatureDataForManifestFile(csar.getManifestMfFile()); + if (signatureData.getCertificate().isPresent()) { + validateCertificationUsingCmsCertificate(signatureData); + } else if (containsToscaMeta()) { + validateCertificationUsingTosca(signatureData); + } else if (containsCertificateInRootCatalog()) { + validateCertificationUsingCertificateFromRootDirectory(signatureData); + } else { + this.errors.add(new Error.CSARErrorUnableToFindCertificate()); + } + } catch (CmsSignatureLoadingException e) { + LOG.error("Unable to load CMS!", e); + this.errors.add(new Error.CSARErrorUnableToLoadCms()); + } + } + + private boolean containsCms() { + String cms = csar.getManifest().getCms(); + return cms != null && !cms.equals(EMPTY_STRING); + } + + private boolean containsToscaMeta() { + return csar.getToscaMetaFile() != null; + } + + private boolean containsCertificateInTosca() { + String certificate = csar.getToscaMeta().getEntryCertificate(); + return certificate != null && !certificate.equals(EMPTY_STRING); + } + + private boolean containsCertificateInRootCatalog() { + File potentialCertificateFileInRootDirectory = getCertificateFromRootDirectory(); + return potentialCertificateFileInRootDirectory.exists(); + } + + private boolean containsPerArtifactSecurity() { + return csar.getManifest().getSources().stream().anyMatch( + source -> + !source.getAlgorithm().equals(EMPTY_STRING) || + !source.getHash().equals(EMPTY_STRING) || + !source.getCertificate().equals(EMPTY_STRING) || + !source.getSignature().equals(EMPTY_STRING) + ); + } + + private void validateCertificationUsingCmsCertificate(CmsSignatureData signatureData) + throws NoSuchAlgorithmException, IOException { + validateAllSources(); + validateFileSignature(signatureData); + if (containsCertificateInTosca()) { + this.errors.add(new Error.CSARErrorEntryCertificateIsDefinedDespiteTheCms()); + if (csar.getFileFromCsar(csar.getToscaMeta().getEntryCertificate()).exists()) { + this.errors.add(new Error.CSARErrorEntryCertificateIsPresentDespiteTheCms()); + } + } + if (containsCertificateInRootCatalog()) { + this.errors.add(new Error.CSARErrorRootCertificateIsPresentDespiteTheCms()); + } + } + + private void validateCertificationUsingTosca(CmsSignatureData signatureData) + throws NoSuchAlgorithmException, IOException { + Optional<Path> pathToCert = loadCertificateFromTosca(); + if (pathToCert.isPresent()) { + validateAllSources(pathToCert.get()); + signatureData.loadCertificate(pathToCert.get()); + validateFileSignature(signatureData); + } + if (containsCertificateInRootCatalog() && rootCertificateIsNotReferredAsToscaEtsiEntryCertificate()) { + this.errors.add(new Error.CSARErrorRootCertificateIsPresentDespiteTheEtsiEntryCertificate()); + } + } + + private Optional<Path> loadCertificateFromTosca() { + if (csar.getToscaMeta().getEntryCertificate() != null) { + final Path absolutePathToEntryCertificate = csar.getFileFromCsar(csar.getToscaMeta().getEntryCertificate()).toPath(); + if (absolutePathToEntryCertificate.toFile().exists()) { + return Optional.of(absolutePathToEntryCertificate); + } else { + this.errors.add(new Error.CSARErrorUnableToFindEntryCertificate()); + return Optional.empty(); + } + } else { + this.errors.add(new Error.CSARErrorUnableToFindCertificateEntryInTosca()); + return Optional.empty(); + } + } + + private boolean rootCertificateIsNotReferredAsToscaEtsiEntryCertificate() { + String pathToRootCertificate = getCertificateFromRootDirectory().getPath(); + String pathToEntryEtsiCertificate = csar.getFileFromCsar(csar.getToscaMeta().getEntryCertificate()).getPath(); + return !pathToRootCertificate.equals(pathToEntryEtsiCertificate); + } + + private void validateCertificationUsingCertificateFromRootDirectory(CmsSignatureData signatureData) + throws NoSuchAlgorithmException, IOException { + Optional<Path> pathToCert = loadCertificateFromRootDirectory(); + if (pathToCert.isPresent()) { + validateAllSources(pathToCert.get()); + signatureData.loadCertificate(pathToCert.get()); + validateFileSignature(signatureData); + } + } + + private void validateFileSignature(CmsSignatureData signatureData) { + final boolean isValid = this.fileSignatureValidator.isValid(signatureData); + if (!isValid) { + this.errors.add(new Error.CSARErrorInvalidSignature()); + } + } + + private Optional<Path> loadCertificateFromRootDirectory() { + try { + Path pathToCertificateInRootDirectory = getCertificateFromRootDirectory().toPath(); + return Optional.of(pathToCertificateInRootDirectory); + } catch (CertificateLoadingException e) { + LOG.error("Unable to read ETSI entry certificate file!", e); + return Optional.empty(); + } + } + + private File getCertificateFromRootDirectory() { + String nameOfCertificate = + getFileNameWithoutExtension(csar.getManifestMfFile().getName()) + CERTIFICATE_EXTENSION; + return csar.getFileFromCsar(nameOfCertificate); + } + + private void validateAllSources() + throws NoSuchAlgorithmException, IOException { + this.errors.addAll(createCsarSourcesValidator().validate()); + } + + private void validateAllSources(Path commonCertificate) + throws NoSuchAlgorithmException, IOException { + this.errors.addAll(createCsarSourcesValidator().validate(commonCertificate)); + } + + private CsarSourcesSecurityValidator createCsarSourcesValidator() { + final CSARArchive.Manifest manifest = csar.getManifest(); + final Map<String, Map<String, List<String>>> nonMano = manifest.getNonMano(); + final List<SourcesParser.Source> sources = manifest.getSources(); + return new CsarSourcesSecurityValidator(nonMano, sources, csarRootDirectory); + } + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSourceSecurityValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSourceSecurityValidator.java new file mode 100644 index 0000000..282b37d --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSourceSecurityValidator.java @@ -0,0 +1,154 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206; + +import org.onap.cvc.csar.CSARArchive; +import org.onap.cvc.csar.cc.sol004.r130206.artifact.ArtifactSecurityFileValidator; +import org.onap.cvc.csar.cc.sol004.r130206.artifact.ArtifactSecurityFileValidatorFactory; +import org.onap.cvc.csar.cc.sol004.r130206.artifact.ValidatedSecurityFile; +import org.onap.cvc.csar.parser.SourcesParser; +import org.onap.cvc.csar.security.CmsSignatureData; +import org.onap.cvc.csar.security.CmsSignatureLoadingException; + +import java.io.IOException; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +public class CsarSourceSecurityValidator { + + public static final String SIGNATURE_FILE_TYPE = "signature"; + private static final String[] ARTIFACT_SIGNATURE_EXTENSIONS = {".sig.cms"}; + public static final String CERTIFICATE_FILE_TYPE = "certificate"; + private static final String[] ARTIFACT_CERTIFICATE_EXTENSIONS = {".cert"}; + + private final FileSignatureValidator fileSignatureValidator = new FileSignatureValidator(); + private final FileHashValidator fileHashValidator = new FileHashValidator(); + + private final SourcesParser.Source source; + private final Path csarRootDirectory; + + private final ArtifactSecurityFileValidator signatureValidator; + private final ArtifactSecurityFileValidator certificateValidator; + + public CsarSourceSecurityValidator(SourcesParser.Source source, Path csarRootDirectory) { + this.source = source; + this.csarRootDirectory = csarRootDirectory; + ArtifactSecurityFileValidatorFactory fileValidatorFactory = + new ArtifactSecurityFileValidatorFactory(csarRootDirectory, source.getValue()); + signatureValidator = fileValidatorFactory.create(source.getSignature()); + certificateValidator = fileValidatorFactory.create(source.getCertificate()); + } + + public CsarSourceSecurityValidator(SourcesParser.Source source, Path csarRootDirectory, Path commonCert) { + this.source = source; + this.csarRootDirectory = csarRootDirectory; + ArtifactSecurityFileValidatorFactory fileValidatorFactory = + new ArtifactSecurityFileValidatorFactory(csarRootDirectory, source.getValue()); + signatureValidator = fileValidatorFactory.create(source.getSignature()); + certificateValidator = fileValidatorFactory.create(source.getCertificate(), commonCert); + } + + public List<CSARArchive.CSARError> validate() throws NoSuchAlgorithmException, IOException { + return validateSource(); + } + + private List<CSARArchive.CSARError> validateSource() + throws NoSuchAlgorithmException, IOException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + final Path sourcePath = csarRootDirectory.resolve(source.getValue()); + if (sourcePath.toFile().exists()) { + errors.addAll(validateHashIfPresent()); + errors.addAll(validateArtifactSignatureIfPresent()); + } else { + errors.add(new Error.CSARErrorUnableToFindSource(source.getValue())); + } + return errors; + } + + private List<CSARArchive.CSARError> validateHashIfPresent() + throws NoSuchAlgorithmException, IOException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + if (!source.getAlgorithm().isEmpty()) { + errors.addAll(validateSourceHashCode()); + } else if (source.getAlgorithm().isEmpty() && !source.getHash().isEmpty()) { + errors.add(new Error.CSARErrorUnableToFindAlgorithm(source.getValue())); + } + return errors; + } + + private List<CSARArchive.CSARError> validateSourceHashCode() + throws NoSuchAlgorithmException, IOException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + if (!fileHashValidator.isValid( + source.getHash(), csarRootDirectory.resolve(source.getValue()), source.getAlgorithm()) + ) { + errors.add(new Error.CSARErrorWrongHashCode(source.getValue())); + } + return errors; + } + + private List<CSARArchive.CSARError> validateArtifactSignatureIfPresent() + throws IOException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + final boolean containsSignatureTag = !source.getSignature().isEmpty(); + final boolean containsCertificateTag = !source.getCertificate().isEmpty(); + + ValidatedSecurityFile validatedSignature = signatureValidator.getValidatedSecurityFile( + source.getValue(), source.getSignature(), ARTIFACT_SIGNATURE_EXTENSIONS, SIGNATURE_FILE_TYPE + ); + errors.addAll(validatedSignature.getErrors()); + ValidatedSecurityFile validatedCertificate = certificateValidator.getValidatedSecurityFile( + source.getValue(), source.getCertificate(), ARTIFACT_CERTIFICATE_EXTENSIONS, CERTIFICATE_FILE_TYPE + ); + errors.addAll(validatedCertificate.getErrors()); + + if (containsSignatureTag) { + if (validatedCertificate.isValid() && validatedSignature.isValid()) { + errors.addAll( + validateArtifactSignature( + csarRootDirectory.resolve(source.getValue()), + validatedSignature.getFilePath(), validatedCertificate.getFilePath() + ) + ); + } else if (!validatedCertificate.isValid() && !containsCertificateTag) { + errors.add(new Error.CSARErrorUnableToFindArtifactCertificateTag(source.getValue())); + } + + } else if (containsCertificateTag) { + errors.add(new Error.CSARErrorUnableToFindArtifactSignatureTag(source.getValue())); + } + return errors; + } + + private List<CSARArchive.CSARError> validateArtifactSignature(Path filePath, Path signaturePath, Path certificatePath) + throws IOException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + try { + final CmsSignatureData signatureData = + fileSignatureValidator.createSignatureData(filePath, signaturePath, certificatePath); + if (!fileSignatureValidator.isValid(signatureData)) { + errors.add(new Error.CSARErrorIncorrectArtifactSignature(source.getValue())); + } + } catch (CmsSignatureLoadingException e) { + errors.add(new Error.CSARErrorFailToLoadArtifactSignature(source.getValue(), source.getSignature())); + } + return errors; + } + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSourcesSecurityValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSourcesSecurityValidator.java new file mode 100644 index 0000000..5db596f --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSourcesSecurityValidator.java @@ -0,0 +1,99 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206; + +import org.onap.cvc.csar.CSARArchive; +import org.onap.cvc.csar.parser.SourcesParser; + +import java.io.IOException; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class CsarSourcesSecurityValidator { + + private final Map<String, Map<String, List<String>>> nonMano; + private final List<SourcesParser.Source> sources; + private final Path csarRootDirectory; + + + public CsarSourcesSecurityValidator( + Map<String, Map<String, List<String>>> nonMano, List<SourcesParser.Source> sources, Path csarRootDirectory + ) { + this.nonMano = nonMano; + this.sources = List.copyOf(sources); + this.csarRootDirectory = csarRootDirectory; + } + + public List<CSARArchive.CSARError> validate() throws IOException, NoSuchAlgorithmException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + errors.addAll(validateSources(sources)); + errors.addAll(validateNonManoCohesionWithSources(nonMano, sources)); + return errors; + } + + public List<CSARArchive.CSARError> validate(Path commonCertificate) throws IOException, NoSuchAlgorithmException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + errors.addAll(validateSources(sources, commonCertificate)); + errors.addAll(validateNonManoCohesionWithSources(nonMano, sources)); + return errors; + } + + private List<CSARArchive.CSARError> validateNonManoCohesionWithSources( + final Map<String, Map<String, List<String>>> nonMano, final List<SourcesParser.Source> sources + ) { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + final Collection<Map<String, List<String>>> values = nonMano.values(); + final List<String> nonManoSourcePaths = values.stream() + .map(Map::values) + .flatMap(Collection::stream) + .flatMap(List::stream) + .filter(it -> !it.isEmpty()) + .collect(Collectors.toList()); + + final List<String> sourcePaths = sources.stream() + .map(SourcesParser.Source::getValue) + .collect(Collectors.toList()); + + if (!sourcePaths.containsAll(nonManoSourcePaths)) { + errors.add(new Error.CSARErrorContentMismatch()); + } + return errors; + } + + private List<CSARArchive.CSARError> validateSources(List<SourcesParser.Source> sources) + throws NoSuchAlgorithmException, IOException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + for (SourcesParser.Source source : sources) { + errors.addAll(new CsarSourceSecurityValidator(source, csarRootDirectory).validate()); + } + return errors; + } + + private List<CSARArchive.CSARError> validateSources(List<SourcesParser.Source> sources, Path commonCertificate) + throws NoSuchAlgorithmException, IOException { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + for (SourcesParser.Source source : sources) { + errors.addAll(new CsarSourceSecurityValidator(source, csarRootDirectory, commonCertificate).validate()); + } + return errors; + } +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/Error.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/Error.java new file mode 100644 index 0000000..72b036f --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/Error.java @@ -0,0 +1,226 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206; + +import org.onap.cvc.csar.CSARArchive; + +public class Error { + + private static final String EMPTY_STRING = ""; + + public static class CSARErrorUnableToFindCertificate extends CSARArchive.CSARError { + + public CSARErrorUnableToFindCertificate() { + super("0x4001"); + this.message = "Unable to find cert file!"; + } + } + + public static class CSARErrorUnableToFindCms extends CSARArchive.CSARError { + + public CSARErrorUnableToFindCms() { + super("0x4002"); + this.message = "Unable to find cms signature!"; + } + } + + public static class CSARErrorUnableToLoadCms extends CSARArchive.CSARError { + public CSARErrorUnableToLoadCms() { + super("0x4002"); + this.message = "Unable to load cms signature!"; + } + } + + public static class CSARErrorUnableToFindCsarContent extends CSARArchive.CSARError { + + public CSARErrorUnableToFindCsarContent() { + super("0x4003"); + this.message = "Unable to find csar content!"; + } + } + + public static class CSARErrorWrongHashCode extends CSARArchive.CSARError { + + public CSARErrorWrongHashCode(String path) { + super("0x4004"); + this.message = String.format("Source '%s' has wrong hash!", path); + } + } + + public static class CSARErrorUnableToFindAlgorithm extends CSARArchive.CSARError { + + public CSARErrorUnableToFindAlgorithm(String path) { + super("0x4005"); + this.message = String.format("Source '%s' has hash, but unable to find algorithm tag!", path); + } + } + + public static class CSARErrorUnableToFindSource extends CSARArchive.CSARError { + + public CSARErrorUnableToFindSource(String path) { + super("0x4006"); + this.message = String.format("Unable to calculate digest - file missing: %s", path); + } + } + + public static class CSARErrorInvalidSignature extends CSARArchive.CSARError { + + public CSARErrorInvalidSignature() { + super("0x4007"); + this.message = "Manifest file has invalid signature!"; + this.file = ""; + } + } + + public static class CSARErrorContentMismatch extends CSARArchive.CSARError { + + public CSARErrorContentMismatch() { + super("0x4008"); + this.message = "Mismatch between contents of non-mano-artifact-sets and source files of the package"; + } + } + + public static class CSARErrorUnableToFindEntryCertificate extends CSARArchive.CSARError { + + public CSARErrorUnableToFindEntryCertificate() { + super("0x4009"); + this.message = "Unable to find cert file defined by ETSI-Entry-Certificate!"; + } + } + + public static class CSARErrorEntryCertificateIsDefinedDespiteTheCms extends CSARArchive.CSARError { + + public CSARErrorEntryCertificateIsDefinedDespiteTheCms() { + super("0x4011"); + this.message = "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container"; + } + } + + public static class CSARErrorEntryCertificateIsPresentDespiteTheCms extends CSARArchive.CSARError { + + public CSARErrorEntryCertificateIsPresentDespiteTheCms() { + super("0x4012"); + this.message = "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container"; + } + } + + public static class CSARErrorRootCertificateIsPresentDespiteTheCms extends CSARArchive.CSARError { + + public CSARErrorRootCertificateIsPresentDespiteTheCms() { + super("0x4013"); + this.message = "Certificate present in root catalog despite the certificate is included in the signature container"; + } + } + + public static class CSARErrorRootCertificateIsPresentDespiteTheEtsiEntryCertificate extends CSARArchive.CSARError { + + public CSARErrorRootCertificateIsPresentDespiteTheEtsiEntryCertificate() { + super("0x4013"); + this.message = "Certificate present in root catalog despite the TOSCA.meta file"; + } + } + + public static class CSARErrorUnableToFindCertificateEntryInTosca extends CSARArchive.CSARError { + + public CSARErrorUnableToFindCertificateEntryInTosca() { + super("0x4014"); + this.message = "Unable to find ETSI-Entry-Certificate in Tosca file"; + } + } + + public static class CSARErrorUnableToFindArtifactCertificateTag extends CSARArchive.CSARError { + + public CSARErrorUnableToFindArtifactCertificateTag(String path) { + super("0x4015"); + this.message = String.format("Source '%s' has signature tag, but unable to find certificate tag!", path); + } + } + + public static class CSARErrorUnableToFindArtifactSignatureTag extends CSARArchive.CSARError { + + public CSARErrorUnableToFindArtifactSignatureTag(String path) { + super("0x4017"); + this.message = String.format("Source '%s' has certificate tag, but unable to find signature tag!", path); + } + } + + public static class CSARErrorUnableToFindArtifactSecurityFile extends CSARArchive.CSARError { + public CSARErrorUnableToFindArtifactSecurityFile(String path, String signatureFile, String type) { + super("0x4018"); + this.message = String.format( + "Source '%s' has '%s' tag, pointing to non existing file!. Pointed file '%s'", + path, type, signatureFile); + } + + } + + public static class CSARErrorFailToLoadArtifactSignature extends CSARArchive.CSARError { + + public CSARErrorFailToLoadArtifactSignature(String path, String cms) { + super("0x4019"); + this.message = String.format("Fail to load signature '%s', for source '%s'!", path, cms); + } + } + + public static class CSARErrorIncorrectArtifactSignature extends CSARArchive.CSARError { + + public CSARErrorIncorrectArtifactSignature(String path) { + super("0x4020"); + this.message = String.format("Source '%s' has incorrect signature!", path); + } + } + + public static class CSARErrorWrongSecurityFileExtension extends CSARArchive.CSARError { + + public CSARErrorWrongSecurityFileExtension(String path, String[] expectedExtension, String type) { + super("0x4021"); + this.message = String.format( + "Source '%s' has '%s' file with wrong extension, expected extension: '%s'!", + path, type, String.join(", ", expectedExtension) + ); + } + } + + public static class CSARErrorWrongSecurityFileLocation extends CSARArchive.CSARError { + + public CSARErrorWrongSecurityFileLocation(String path, String filePath, String type) { + super("0x4022"); + this.message = String.format( + "Source '%s' has '%s' file located in wrong directory, directory: '%s'." + + "Signature should be in same directory as source file!", + path, type, filePath); + } + } + + public static class CSARErrorWrongSecurityFileName extends CSARArchive.CSARError { + + public CSARErrorWrongSecurityFileName(String path, String fileName, String type) { + super("0x4023"); + this.message = String.format( + "Source '%s' has '%s' file with wrong name, signature name: '%s'." + + "Signature should have same name as source file!", + path, type, fileName); + } + } + + public static class CSARWarningNoSecurity extends CSARArchive.CSARErrorWarning { + public CSARWarningNoSecurity() { + super(EMPTY_STRING, EMPTY_STRING, -1, EMPTY_STRING); + this.message = "Warning. Consider adding package integrity and authenticity assurance according to ETSI NFV-SOL 004 Security Option 1"; + } + } +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/FileHashValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/FileHashValidator.java new file mode 100644 index 0000000..f1412d7 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/FileHashValidator.java @@ -0,0 +1,52 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206; + +import org.onap.cvc.csar.security.ShaHashCodeGenerator; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; + +public class FileHashValidator { + + private static final String SHA_256 = "SHA-256"; + private static final String SHA_512 = "SHA-512"; + + private final ShaHashCodeGenerator shaHashCodeGenerator = new ShaHashCodeGenerator(); + + public boolean isValid(String expectedHash, Path fileToCheck, String algorithm) + throws NoSuchAlgorithmException, IOException { + String hashCode = generateHashCode(fileToCheck, algorithm); + return hashCode.equals(expectedHash); + } + + private String generateHashCode(Path fileToCheck, String algorithm) + throws NoSuchAlgorithmException, IOException { + final byte[] sourceData = Files.readAllBytes(fileToCheck); + + if (algorithm.equalsIgnoreCase(SHA_256)) { + return this.shaHashCodeGenerator.generateSha256(sourceData); + } else if (algorithm.equalsIgnoreCase(SHA_512)) { + return this.shaHashCodeGenerator.generateSha512(sourceData); + } + + throw new UnsupportedOperationException(String.format("Algorithm '%s' is not supported!", algorithm)); + } + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/FileSignatureValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/FileSignatureValidator.java new file mode 100644 index 0000000..6e2a0c6 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/FileSignatureValidator.java @@ -0,0 +1,76 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206; + +import org.onap.cvc.csar.parser.ManifestFileModel; +import org.onap.cvc.csar.parser.ManifestFileSplitter; +import org.onap.cvc.csar.security.CmsSignatureData; +import org.onap.cvc.csar.security.CmsSignatureDataFactory; +import org.onap.cvc.csar.security.CmsSignatureLoadingException; +import org.onap.cvc.csar.security.CmsSignatureValidator; +import org.onap.cvc.csar.security.CmsSignatureValidatorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +public class FileSignatureValidator { + + private static final Logger LOG = LoggerFactory.getLogger(FileSignatureValidator.class); + + private final ManifestFileSplitter manifestFileSplitter = new ManifestFileSplitter(); + private final CmsSignatureValidator cmsSignatureValidator = new CmsSignatureValidator(); + private final CmsSignatureDataFactory cmsSignatureDataFactory = new CmsSignatureDataFactory(); + + CmsSignatureData createSignatureDataForManifestFile(File manifestFile) throws CmsSignatureLoadingException { + ManifestFileModel mf = manifestFileSplitter.split(manifestFile); + return cmsSignatureDataFactory.createForFirstSigner( + toBytes(mf.getCMS(), mf.getNewLine()), + toBytes(mf.getData(), mf.getNewLine()) + ); + } + + public CmsSignatureData createSignatureData(Path filePath, Path cmsFilePath, Path certFilePath) throws CmsSignatureLoadingException, IOException { + CmsSignatureData signatureData = cmsSignatureDataFactory.createForFirstSigner( + Files.readAllBytes(cmsFilePath), + Files.readAllBytes(filePath) + ); + signatureData.loadCertificate(certFilePath); + return signatureData; + } + + public boolean isValid(CmsSignatureData signatureData) { + try { + return cmsSignatureValidator.verifySignedData(signatureData); + } catch (CmsSignatureValidatorException e) { + LOG.error("Unable to verify signed data!", e); + return false; + } + } + + private byte[] toBytes(List<String> data, String newLine) { + final String updatedData = data.stream().map(it -> it + newLine).collect(Collectors.joining()); + return updatedData.getBytes(Charset.defaultCharset()); + } + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidator.java new file mode 100644 index 0000000..53e697d --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidator.java @@ -0,0 +1,75 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206.artifact; + +import org.onap.cvc.csar.CSARArchive; +import org.onap.cvc.csar.cc.sol004.r130206.Error; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.onap.validation.csar.FileUtil.fileHaveOneOfExtensions; +import static org.onap.validation.csar.FileUtil.filesAreInSameDirectory; +import static org.onap.validation.csar.FileUtil.filesHaveSameNamesIgnoringExtensions; + +public class ArtifactSecurityFileValidator { + + private final Path rootDirectory; + private final String artifactRelativeFilePath; + private final String securityRelativeFilePath; + + ArtifactSecurityFileValidator(Path rootDirectory, String artifactRelativeFilePath, String securityRelativeFilePath) { + this.rootDirectory = rootDirectory; + this.artifactRelativeFilePath = artifactRelativeFilePath; + this.securityRelativeFilePath = securityRelativeFilePath; + } + + public ValidatedSecurityFile getValidatedSecurityFile( + String pathToSourceFile, String pathToSecurityFile, + String[] securityFileExtensions, String securityFileType + ) { + final List<CSARArchive.CSARError> errors = new ArrayList<>(); + Path validatedSecurityFilePath = null; + if(!securityRelativeFilePath.isEmpty()) { + final Path artifactFilePath = rootDirectory.resolve(artifactRelativeFilePath); + final Path securityFilePath = rootDirectory.resolve(securityRelativeFilePath); + if (!filesAreInSameDirectory(artifactFilePath, securityFilePath)) { + errors.add( + new Error.CSARErrorWrongSecurityFileLocation(pathToSourceFile, pathToSecurityFile, securityFileType) + ); + } else if (!filesHaveSameNamesIgnoringExtensions(artifactFilePath, securityFilePath)) { + errors.add( + new Error.CSARErrorWrongSecurityFileName(pathToSourceFile, securityFilePath.getFileName().toString(), securityFileType) + ); + } else if (!fileHaveOneOfExtensions(securityFilePath, securityFileExtensions)) { + errors.add( + new Error.CSARErrorWrongSecurityFileExtension(pathToSourceFile, securityFileExtensions, securityFileType) + ); + } else if (!securityFilePath.toFile().exists()) { + errors.add( + new Error.CSARErrorUnableToFindArtifactSecurityFile(pathToSourceFile, pathToSecurityFile, securityFileType) + ); + } else { + validatedSecurityFilePath = securityFilePath; + } + } + return new ValidatedSecurityFile(validatedSecurityFilePath,errors); + } + + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidatorFactory.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidatorFactory.java new file mode 100644 index 0000000..9355100 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidatorFactory.java @@ -0,0 +1,40 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206.artifact; + +import java.nio.file.Path; + +public class ArtifactSecurityFileValidatorFactory { + + private final Path rootDirectory; + private final String artifactRelativeFilePath; + + public ArtifactSecurityFileValidatorFactory(Path rootDirectory, String artifactRelativeFilePath) { + this.rootDirectory = rootDirectory; + this.artifactRelativeFilePath = artifactRelativeFilePath; + } + + public ArtifactSecurityFileValidator create(String securityRelativeFilePath) { + return new ArtifactSecurityFileValidator + (rootDirectory, artifactRelativeFilePath, securityRelativeFilePath); + } + + public ArtifactSecurityFileValidator create(String securityRelativeFilePath, Path commonCertificate) { + return new ArtifactSecurityFileValidatorWithCommonFile + (rootDirectory, artifactRelativeFilePath, securityRelativeFilePath, commonCertificate); + } +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidatorWithCommonFile.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidatorWithCommonFile.java new file mode 100644 index 0000000..ff09d5c --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidatorWithCommonFile.java @@ -0,0 +1,40 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206.artifact; + +import java.nio.file.Path; + +public class ArtifactSecurityFileValidatorWithCommonFile extends ArtifactSecurityFileValidator { + + private final Path commonSecurityFilePath; + + ArtifactSecurityFileValidatorWithCommonFile(Path rootDirectory, String artifactRelativeFilePath, String securityRelativeFilePath, Path commonSecurityFilePath) { + super(rootDirectory, artifactRelativeFilePath, securityRelativeFilePath); + this.commonSecurityFilePath = commonSecurityFilePath; + } + + @Override + public ValidatedSecurityFile getValidatedSecurityFile(String pathToSourceFile, String pathToSecurityFile, String[] securityFileExtensions, String securityFileType) { + ValidatedSecurityFile baseValidationResult = + super.getValidatedSecurityFile(pathToSourceFile, pathToSecurityFile, securityFileExtensions, securityFileType); + if(baseValidationResult.getFilePath() == null) { + return new ValidatedSecurityFile(commonSecurityFilePath,baseValidationResult.getErrors()); + } else { + return baseValidationResult; + } + } +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ValidatedSecurityFile.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ValidatedSecurityFile.java new file mode 100644 index 0000000..8071cad --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ValidatedSecurityFile.java @@ -0,0 +1,46 @@ +/* + Copyright 2020 Nokia + <p> + 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 + <p> + http://www.apache.org/licenses/LICENSE-2.0 + <p> + 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. + */ + +package org.onap.cvc.csar.cc.sol004.r130206.artifact; + +import org.onap.cvc.csar.CSARArchive; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +public class ValidatedSecurityFile { + + private final Path filePath; + private final List<CSARArchive.CSARError> errors; + + public ValidatedSecurityFile(Path filePath, List<CSARArchive.CSARError> errors) { + this.filePath = filePath; + this.errors = List.copyOf(errors); + } + + public Path getFilePath() { + return filePath; + } + + public List<CSARArchive.CSARError> getErrors() { + return Collections.unmodifiableList(errors); + } + + public boolean isValid() { + return filePath != null && errors.isEmpty(); + } +} diff --git a/csarvalidation/src/main/java/org/onap/validation/csar/FileUtil.java b/csarvalidation/src/main/java/org/onap/validation/csar/FileUtil.java index ccf1c71..45c8db1 100644 --- a/csarvalidation/src/main/java/org/onap/validation/csar/FileUtil.java +++ b/csarvalidation/src/main/java/org/onap/validation/csar/FileUtil.java @@ -22,6 +22,8 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Path; +import java.util.Arrays; import java.util.zip.ZipFile; import java.nio.file.Files; import java.nio.file.Paths; @@ -186,4 +188,27 @@ public final class FileUtil { } return isFileDeleted; } + + public static boolean filesAreInSameDirectory(Path firstFile, Path secondFile) { + return firstFile.getParent().equals(secondFile.getParent()); + } + + public static boolean filesHaveSameNamesIgnoringExtensions(Path firstFile, Path secondFile) { + return getFileNameWithoutExtension(firstFile).equals(getFileNameWithoutExtension(secondFile)); + } + + public static String getFileNameWithoutExtension(Path file){ + String fileName = file.getFileName().toString(); + return getFileNameWithoutExtension(fileName); + } + + public static String getFileNameWithoutExtension(String fileName){ + int firstDotPosition = fileName.indexOf("."); + return fileName.substring(0, firstDotPosition); + } + + public static boolean fileHaveOneOfExtensions(Path file, String[] extensions) { + return Arrays.stream(extensions).anyMatch(extension -> file.toString().endsWith(extension)); + } + } diff --git a/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206IntegrationTest.java b/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206IntegrationTest.java index 7337a29..4d6adc4 100644 --- a/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206IntegrationTest.java +++ b/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206IntegrationTest.java @@ -113,7 +113,7 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(errors.size()).isEqualTo(1); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -130,7 +130,7 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(errors.size()).isEqualTo(1); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -165,7 +165,7 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(errors.size()).isEqualTo(2); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( "Source 'Artifacts/Other/my_script.csh' has wrong hash!", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -183,7 +183,7 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(errors.size()).isEqualTo(2); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( "Source 'Artifacts/Deployment/Measurements/PM_Dictionary.yml' has wrong hash!", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -198,9 +198,7 @@ public class VTPValidateCSARR130206IntegrationTest { // then List<CSARArchive.CSARError> errors = testCase.getErrors(); - assertThat(errors.size()).isEqualTo(3); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( - "Source 'Artifacts/Deployment/Events/RadioNode_Pnf_v1.yaml' has wrong hash!", "Unable to find ETSI-Entry-Certificate in Tosca file", "Certificate present in root catalog despite the TOSCA.meta file" ); @@ -236,7 +234,7 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(errors.size()).isEqualTo(3); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( - "File has invalid signature!", + "Manifest file has invalid signature!", "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container", "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container" ); @@ -256,7 +254,7 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(errors.size()).isEqualTo(4); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( "Source 'Artifacts/Informational/user_guide.txt' has wrong hash!", - "File has invalid signature!", + "Manifest file has invalid signature!", "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container", "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container" ); @@ -278,7 +276,7 @@ public class VTPValidateCSARR130206IntegrationTest { "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container", "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container", "Certificate present in root catalog despite the certificate is included in the signature container", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -300,7 +298,7 @@ public class VTPValidateCSARR130206IntegrationTest { "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container", "Certificate present in root catalog despite the certificate is included in the signature container", "Source 'Artifacts/Informational/user_guide.txt' has wrong hash!", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -318,7 +316,7 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(errors.size()).isEqualTo(2); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( "Certificate present in root catalog despite the certificate is included in the signature container", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -337,7 +335,7 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( "Certificate present in root catalog despite the certificate is included in the signature container", "Source 'Artifacts/Informational/user_guide.txt' has wrong hash!", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -355,7 +353,7 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(errors.size()).isEqualTo(2); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( "Certificate present in root catalog despite the TOSCA.meta file", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -374,7 +372,7 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( "Certificate present in root catalog despite the TOSCA.meta file", "Source 'Artifacts/Deployment/Yang_module/yang-module1.yang' has wrong hash!", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -391,7 +389,7 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(errors.size()).isEqualTo(1); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -444,7 +442,7 @@ public class VTPValidateCSARR130206IntegrationTest { // This test returns other errors that are connected with missing tosca entry, // in order to simplify testing, assertion only checks if certificate in root was found and used to validate CMS assertThat(convertToMessagesList(errors)).contains( - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -480,7 +478,25 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( - "File has invalid signature!" + "Manifest file has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertIsPresentInCmsAndIndividualArtifactHaveIncorrectSignature() throws Exception { + + // given + configureTestCaseForRule130206("pnf/r130206/csar-cert-in-cms-valid-with-incorrect-signature-of-individual-artifact.csar"); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + + assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( + "Manifest file has invalid signature!", + "Source 'Files/Scripts/my_script.sh' has incorrect signature!" ); } @@ -498,8 +514,9 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( "Source 'Files/Scripts/my_script.sh' has certificate tag, but unable to find signature tag!", + "Source 'Files/Scripts/my_script.sh' has 'certificate' tag, pointing to non existing file!. Pointed file 'Files/Scripts/my_script.cert'", "Source 'Files/pnf-sw-information/pnf-sw-information.yaml' has signature tag, but unable to find certificate tag!", - "File has invalid signature!" + "Manifest file has invalid signature!" ); } @@ -516,17 +533,73 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( - "Source 'Files/Yang_module/mynetconf.yang' has signature tag, pointing to non existing file!", - "Source 'Files/Yang_module/mynetconf.yang' has certificate tag, pointing to non existing file!", - "File has invalid signature!" + "Source 'Files/Yang_module/mynetconf.yang' has 'signature' tag, pointing to non existing file!. Pointed file 'Files/Yang_module/mynetconf.sig.cms'", + "Source 'Files/Yang_module/mynetconf.yang' has 'certificate' tag, pointing to non existing file!. Pointed file 'Files/Yang_module/mynetconf.cert'", + "Manifest file has invalid signature!" ); } @Test - public void shouldReturnErrorWhenCertIsPresentInCmsAndIndividualArtifactHaveIncorrectSignature() throws Exception { + public void shouldReturnErrorWhenCertIsPresentInCmsAndIndividualArtifactHaveSignatureInWrongDirectory() throws Exception { // given - configureTestCaseForRule130206("pnf/r130206/csar-cert-in-cms-valid-with-incorrect-signature-of-individual-artifact.csar"); + configureTestCaseForRule130206("pnf/r130206/csar-cert-in-cms-valid-with-signature-for-individual-artifact-in-wrong-directory.csar"); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + + assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( + "Source 'Files/ChangeLog.txt' has 'signature' file located in wrong directory, directory: 'Files/pnf-sw-information/pnf-sw-information.sig.cms'.Signature should be in same directory as source file!", + "Source 'Files/ChangeLog.txt' has 'certificate' file located in wrong directory, directory: 'Files/pnf-sw-information/pnf-sw-information.cert'.Signature should be in same directory as source file!", + "Manifest file has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertIsPresentInCmsAndIndividualArtifactHaveSignatureWithIncorrectName() throws Exception { + + // given + configureTestCaseForRule130206("pnf/r130206/csar-cert-in-cms-valid-with-signature-with-wrong-name-for-individual-artifact.csar"); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + + assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( + "Source 'Files/ChangeLog.txt' has 'signature' file with wrong name, signature name: 'pnf-sw-information.sig.cms'.Signature should have same name as source file!", + "Source 'Files/ChangeLog.txt' has 'certificate' file with wrong name, signature name: 'pnf-sw-information.cert'.Signature should have same name as source file!", + "Manifest file has invalid signature!" + ); + } + + @Test + public void shouldUseCommonCertWhenCertIsPresentInToscaAndIndividualArtifactHaveOnlySignature() throws Exception { + + // given + configureTestCaseForRule130206("pnf/r130206/csar-cert-in-tosca-individual-signature.csar"); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + + assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( + "Manifest file has invalid signature!", + "Source 'Artifacts/Other/my_script.csh' has incorrect signature!" + ); + } + + @Test + public void shouldReportErrorWhenCertIsPresentInToscaAndIndividualArtifactHaveSignatureAndIncorrectCert() throws Exception { + + // given + configureTestCaseForRule130206("pnf/r130206/csar-cert-in-tosca-individual-signature-nonexistent-cert.csar"); // when testCase.execute(); @@ -535,8 +608,8 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(convertToMessagesList(errors)).containsExactlyInAnyOrder( - "Source 'Files/ChangeLog.txt' has incorrect signature!", - "File has invalid signature!" + "Manifest file has invalid signature!", + "Source 'Artifacts/Other/my_script.csh' has 'certificate' tag, pointing to non existing file!. Pointed file 'Artifacts/Other/my_script.cert'" ); } diff --git a/csarvalidation/src/test/java/org/onap/functional/CsarValidationUtility.java b/csarvalidation/src/test/java/org/onap/functional/CsarValidationUtility.java index 11292d8..d9cc50b 100644 --- a/csarvalidation/src/test/java/org/onap/functional/CsarValidationUtility.java +++ b/csarvalidation/src/test/java/org/onap/functional/CsarValidationUtility.java @@ -36,6 +36,7 @@ public final class CsarValidationUtility { public static final String CERTIFICATION_RULE = "r130206"; public static final String PM_DICTIONARY_YAML_RULE = "r816745"; public static final String MANIFEST_FILE_RULE = "r01123"; + public static final String NON_MANO_FILES_RULE = "r146092"; public static final String OPERATION_STATUS_FAILED = "FAILED"; public static final String OPERATION_STATUS_PASS = "PASS"; diff --git a/csarvalidation/src/test/java/org/onap/functional/PnfValidationFunctionalTest.java b/csarvalidation/src/test/java/org/onap/functional/PnfValidationFunctionalTest.java index 576f763..2038eca 100644 --- a/csarvalidation/src/test/java/org/onap/functional/PnfValidationFunctionalTest.java +++ b/csarvalidation/src/test/java/org/onap/functional/PnfValidationFunctionalTest.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2020 Nokia * <p> * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.onap.cvc.csar.cc.sol004.IntegrationTestUtils.absoluteFilePath; import static org.onap.functional.CsarValidationUtility.CERTIFICATION_RULE; import static org.onap.functional.CsarValidationUtility.MANIFEST_FILE_RULE; +import static org.onap.functional.CsarValidationUtility.NON_MANO_FILES_RULE; import static org.onap.functional.CsarValidationUtility.OPERATION_STATUS_FAILED; import static org.onap.functional.CsarValidationUtility.OPERATION_STATUS_PASS; import static org.onap.functional.CsarValidationUtility.PM_DICTIONARY_YAML_RULE; @@ -86,7 +87,7 @@ public class PnfValidationFunctionalTest { List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedErrors = List.of( createExpectedError(CERTIFICATION_RULE, "0x4007", - "File has invalid signature!"), + "Manifest file has invalid signature!"), createExpectedError(CERTIFICATION_RULE, "0x4004", "Source 'Files/pnf-sw-information/pnf-sw-information.yaml' has wrong hash!"), createExpectedError(CERTIFICATION_RULE, "0x4011", @@ -123,7 +124,7 @@ public class PnfValidationFunctionalTest { List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedErrors = List.of( createExpectedError(CERTIFICATION_RULE, "0x4007", - "File has invalid signature!"), + "Manifest file has invalid signature!"), createExpectedError(CERTIFICATION_RULE, "0x4004", "Source 'Files/pnf-sw-information/pnf-sw-information.yaml' has wrong hash!"), createExpectedError(CERTIFICATION_RULE, "0x4011", @@ -189,7 +190,7 @@ public class PnfValidationFunctionalTest { List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedErrors = List.of( createExpectedError(CERTIFICATION_RULE, "0x4007", - "File has invalid signature!"), + "Manifest file has invalid signature!"), createExpectedError(CERTIFICATION_RULE, "0x4004", "Source 'Files/pnf-sw-information/pnf-sw-information.yaml' has wrong hash!"), createExpectedError(CERTIFICATION_RULE, "0x4011", @@ -231,14 +232,14 @@ public class PnfValidationFunctionalTest { List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedCertificationErrors = List.of( createExpectedError(CERTIFICATION_RULE, "0x4020", - "Source 'Files/ChangeLog.txt' has incorrect signature!"), + "Source 'Files/Scripts/my_script.sh' has incorrect signature!"), createExpectedError(CERTIFICATION_RULE, "0x4007", - "File has invalid signature!") + "Manifest file has invalid signature!") ); List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedManifestErrors = List.of( createExpectedError(MANIFEST_FILE_RULE, "0x1001", - "file(s): [Files/pnf-sw-information/pnf-sw-information.cert, Files/pnf-sw-information/pnf-sw-information.sig.cms] available in CSAR, but cannot be found in Manifest as Source", + "file(s): [Files/Scripts/my_script.cert, Files/Scripts/my_script.sig.cms] available in CSAR, but cannot be found in Manifest as Source", "TOSCA-Metadata" ) ); @@ -268,6 +269,97 @@ public class PnfValidationFunctionalTest { verifyThatOperationFinishedWithoutAnyError(cli); } + @Test + public void shouldReportThatIndividualArtifactsHaveMultipleIncorrectCertificatesAndSignatures() throws URISyntaxException { + // given + List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedCertificationErrors = + List.of( + createExpectedError(CERTIFICATION_RULE, "0x4018", + "Source 'Artifacts/Deployment/Measurements/PM_Dictionary.yml' has 'signature' tag, pointing to non existing file!. Pointed file 'Artifacts/Deployment/Measurements/PM_Dictionary.sig.cms'"), + createExpectedError(CERTIFICATION_RULE, "0x4023", + "Source 'Artifacts/Deployment/Yang_module/yang-module1.yang' has 'signature' file with wrong name, signature name: 'yang-module.sig.cms'.Signature should have same name as source file!"), + createExpectedError(CERTIFICATION_RULE, "0x4023", + "Source 'Artifacts/Deployment/Yang_module/yang-module1.yang' has 'certificate' file with wrong name, signature name: 'yang-module.cert'.Signature should have same name as source file!"), + createExpectedError(CERTIFICATION_RULE, "0x4020", + "Source 'Artifacts/Other/my_script.csh' has incorrect signature!"), + createExpectedError(CERTIFICATION_RULE, "0x4022", + "Source 'Artifacts/Informational/user_guide.txt' has 'signature' file located in wrong directory, directory: 'Artifacts/user_guide.sig.cms'.Signature should be in same directory as source file!"), + createExpectedError(CERTIFICATION_RULE, "0x4022", + "Source 'Artifacts/Informational/user_guide.txt' has 'certificate' file located in wrong directory, directory: 'Artifacts/user_guide.cert'.Signature should be in same directory as source file!"), + createExpectedError(CERTIFICATION_RULE, "0x4007", + "Manifest file has invalid signature!") + ); + List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedManifestErrors = + List.of( + createExpectedError(MANIFEST_FILE_RULE, "0x1001", + "file(s): [TOSCA-Metadata/TOSCA.meta, Definitions/MainServiceTemplate.yaml, Artifacts/Deployment/Yang_module/yang-module2.yang, Artifacts/Deployment/Yang_module/yang-module.cert, Artifacts/Deployment/Yang_module/yang-module.sig.cms, Artifacts/ChangeLog.txt, Artifacts/sample-pnf.cert] available in CSAR, but cannot be found in Manifest as Source", + "TOSCA-Metadata" + ) + ); + List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedPnfDictionaryErrors = + List.of( + createExpectedError(PM_DICTIONARY_YAML_RULE, "0x2000", + "Fail to load PM_Dictionary With error: PM_Dictionary YAML file is empty", + "Artifacts/Deployment/Measurements/PM_Dictionary.yml" + ) + ); + List<OnapCliValidationResponseWrapper.ValidationResultWrapper.ValidationErrorWrapper> expectedNonManoFilesErrors = + List.of( + createExpectedError(NON_MANO_FILES_RULE, "0x2002", + "Missing. Entry [Source under onap_ves_events]", + "MainServiceTemplate.mf" + ), + createExpectedError(NON_MANO_FILES_RULE, "0x2002", + "Missing. Entry [onap_yang_module]", + "MainServiceTemplate.mf" + ), + createExpectedError(NON_MANO_FILES_RULE, "0x2002", + "Missing. Entry [Source under onap_others]]", + "MainServiceTemplate.mf" + ), + createExpectedError(NON_MANO_FILES_RULE, "0x2002", + "Missing. Entry [Source under onap_pm_dictionary]", + "MainServiceTemplate.mf" + ) + ); + OnapCliWrapper cli = new OnapCliWrapper(createPnfValidationRequestInfo( + "pnf/r130206/csar-cert-in-tosca-multiple-individual-signature.csar" + )); + + // when + cli.handle(); + + // then + final OnapCliValidationResponseWrapper result = getCliCommandValidationResult(cli); + + assertThat(result.criteria).isEqualTo(OPERATION_STATUS_FAILED); + result.results.forEach((ruleValidationResult)->{ + assertThat(ruleValidationResult.warnings).isEmpty(); + switch (ruleValidationResult.vnfreqName) { + case CERTIFICATION_RULE: + assertThat(ruleValidationResult.errors) + .containsAll(expectedCertificationErrors); + break; + case MANIFEST_FILE_RULE: + assertThat(ruleValidationResult.errors) + .containsAll(expectedManifestErrors); + break; + case PM_DICTIONARY_YAML_RULE: + assertThat(ruleValidationResult.errors) + .containsAll(expectedPnfDictionaryErrors); + break; + case NON_MANO_FILES_RULE: + assertThat(ruleValidationResult.errors) + .containsAll(expectedNonManoFilesErrors); + break; + default: + assertThat(ruleValidationResult.errors).isEmpty(); + break; + } + }); + verifyThatOperationFinishedWithoutAnyError(cli); + } + private String[] createPnfValidationRequestInfo(String csarPath) throws URISyntaxException { return new String[]{ "--product", "onap-dublin", diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-incorrect-signature-of-individual-artifact.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-incorrect-signature-of-individual-artifact.csar Binary files differindex 8fd6159..e86609e 100644 --- a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-incorrect-signature-of-individual-artifact.csar +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-incorrect-signature-of-individual-artifact.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-signature-for-individual-artifact-in-wrong-directory.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-signature-for-individual-artifact-in-wrong-directory.csar Binary files differnew file mode 100644 index 0000000..8fd6159 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-signature-for-individual-artifact-in-wrong-directory.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-signature-with-wrong-name-for-individual-artifact.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-signature-with-wrong-name-for-individual-artifact.csar Binary files differnew file mode 100644 index 0000000..151472a --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid-with-signature-with-wrong-name-for-individual-artifact.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-individual-signature-nonexistent-cert.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-individual-signature-nonexistent-cert.csar Binary files differnew file mode 100644 index 0000000..c423593 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-individual-signature-nonexistent-cert.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-individual-signature.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-individual-signature.csar Binary files differnew file mode 100644 index 0000000..d50e4f7 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-individual-signature.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-multiple-individual-signature.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-multiple-individual-signature.csar Binary files differnew file mode 100644 index 0000000..c2560bd --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-multiple-individual-signature.csar |