summaryrefslogtreecommitdiffstats
path: root/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206
diff options
context:
space:
mode:
authorBartosz Gardziejewski <bartosz.gardziejewski@nokia.com>2020-12-15 09:59:13 +0100
committerBartosz Gardziejewski <bartosz.gardziejewski@nokia.com>2020-12-29 11:12:18 +0100
commit25673a3551f2bf15f23afbbfe986947c6a975c91 (patch)
tree548a4fa35f28280a8f49dbb3e28b7636522f764b /csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206
parentd2f0552ea27a481bdbe9099ee52422bca8b6b314 (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/cvc/csar/cc/sol004/r130206')
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSecurityValidator.java220
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSourceSecurityValidator.java154
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/CsarSourcesSecurityValidator.java99
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/Error.java226
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/FileHashValidator.java52
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/FileSignatureValidator.java76
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidator.java75
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidatorFactory.java40
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ArtifactSecurityFileValidatorWithCommonFile.java40
-rw-r--r--csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/r130206/artifact/ValidatedSecurityFile.java46
10 files changed, 1028 insertions, 0 deletions
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();
+ }
+}