diff options
author | Bartosz Gardziejewski <bartosz.gardziejewski@nokia.com> | 2020-12-15 09:59:13 +0100 |
---|---|---|
committer | Bartosz Gardziejewski <bartosz.gardziejewski@nokia.com> | 2020-12-29 11:12:18 +0100 |
commit | 25673a3551f2bf15f23afbbfe986947c6a975c91 (patch) | |
tree | 548a4fa35f28280a8f49dbb3e28b7636522f764b /csarvalidation/src/main/java/org/onap | |
parent | d2f0552ea27a481bdbe9099ee52422bca8b6b314 (diff) |
Add individual artifact validation using common cert.
Signed-off-by: Bartosz Gardziejewski <bartosz.gardziejewski@nokia.com>
Change-Id: I2aa4e862f3d343a3f452e1564dc8a97a34960b83
Issue-ID: VNFSDK-714
Diffstat (limited to 'csarvalidation/src/main/java/org/onap')
13 files changed, 1066 insertions, 504 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)); + } + } |