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/cvc/csar/cc/sol004/r130206 | |
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/cvc/csar/cc/sol004/r130206')
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(); + } +} |