diff options
author | Bartosz Gardziejewski <bartosz.gardziejewski@nokia.com> | 2020-09-17 14:46:47 +0200 |
---|---|---|
committer | Bartosz Gardziejewski <bartosz.gardziejewski@nokia.com> | 2020-09-24 14:07:06 +0200 |
commit | 6767596c5b15b75a3f1ae43e169aa88e0de56c3a (patch) | |
tree | 2b64ad7df6a076fb8315208ef3f32e355ace4536 /csarvalidation | |
parent | e88eed4a3e6d7b9bf299d95fe2534d9f3bdbafb4 (diff) |
Fixing R130206 certificate searching mechanism
Issue-ID: VNFSDK-595
Signed-off-by: Bartosz Gardziejewski <bartosz.gardziejewski@nokia.com>
Change-Id: I8dacd924b16812378356b05291229f2097dfcbe1
Diffstat (limited to 'csarvalidation')
38 files changed, 764 insertions, 201 deletions
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 3a0f76b..822ddde 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 @@ -26,6 +26,10 @@ 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; @@ -57,17 +61,24 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { public static class CSARErrorUnableToFindCertificate extends CSARArchive.CSARError { - CSARErrorUnableToFindCertificate(String paramName) { + CSARErrorUnableToFindCertificate() { super("0x4001"); - this.message = String.format("Unable to find cert file defined by %s!", paramName); + this.message = "Unable to find cert file!"; } } - public static class CSARErrorUnableToFindCmsSection extends CSARArchive.CSARError { + public static class CSARErrorUnableToFindCms extends CSARArchive.CSARError { - CSARErrorUnableToFindCmsSection() { + CSARErrorUnableToFindCms() { super("0x4002"); - this.message = "Unable to find CMS section in manifest!"; + this.message = "Unable to find cms signature!"; + } + } + + public static class CSARErrorUnableToLoadCms extends CSARArchive.CSARError { + CSARErrorUnableToLoadCms() { + super("0x4002"); + this.message = "Unable to load cms signature!"; } } @@ -107,7 +118,7 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { CSARErrorInvalidSignature() { super("0x4007"); - this.message = "File has invalid CMS signature!"; + this.message = "File has invalid signature!"; } } @@ -119,9 +130,49 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } } - public static class CSARWarningNoSecurity extends CSARArchive.CSARErrorWarning{ - CSARWarningNoSecurity(){ - super(EMPTY_STRING, EMPTY_STRING,-1, EMPTY_STRING); + 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 certificate is included in ETSI-Entry-Certificate"; + } + } + + 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"; } } @@ -145,62 +196,153 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } private void validate(CSARArchive csar, Path csarRootDirectory) throws IOException, NoSuchAlgorithmException { - final CSARArchive.Manifest manifest = csar.getManifest(); - validateEntryCertificate(csar, csarRootDirectory); - if(verifyThatCsarIsSecure(manifest)){ - - validateManifestCms(manifest); - validateSources(csarRootDirectory, manifest); - - final Map<String, Map<String, List<String>>> nonMano = manifest.getNonMano(); - final List<SourcesParser.Source> sources = manifest.getSources(); - - validateNonManoCohesionWithSources(nonMano, sources); + if (containsCms(csar.getManifest())) { + validateCmsSignature(csar, csarRootDirectory); + } else if ( + containsCertificateInTosca(csar.getToscaMeta()) || + containsCertificateInRootCatalog(csar) || + containsHashOrAlgorithm(csar.getManifest())) { + this.errors.add(new CSARErrorUnableToFindCms()); + } else { + this.errors.add(new CSARWarningNoSecurity()); + } + } - final File manifestMfFile = csar.getManifestMfFile(); - final String absolutePathToEntryCertificate = getAbsolutePathToEntryCertificate(csar, csarRootDirectory); - if (manifestMfFile != null) { - validateFileSignature(manifestMfFile, absolutePathToEntryCertificate); + private void validateCmsSignature(CSARArchive csar, Path csarRootDirectory) throws NoSuchAlgorithmException, IOException { + try { + CmsSignatureData signatureData = this.manifestFileSignatureValidator.createSignatureData(csar.getManifestMfFile()); + if (signatureData.getCertificate().isPresent()) { + validateCertificationUsingCmsCertificate(signatureData, csar, csarRootDirectory); + } else if (containsCertificateInTosca(csar.getToscaMeta())) { + validateCertificationUsingTosca(signatureData, csar, csarRootDirectory); + } else if (containsCertificateInRootCatalog(csar)) { + validateCertificationUsingCertificateFromRootDirectory(signatureData, csar, csarRootDirectory); + } else { + this.errors.add(new CSARErrorUnableToFindCertificate()); } - }else{ - this.errors.add(new CSARWarningNoSecurity()); + } 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 verifyThatCsarIsSecure(CSARArchive.Manifest manifest) { - final List<SourcesParser.Source> sources = manifest.getSources(); - final String cms = manifest.getCms(); - final boolean containsHashOrAlgorithm = (sources.stream().anyMatch( + 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 containsHashOrAlgorithm(CSARArchive.Manifest manifest) { + return manifest.getSources().stream().anyMatch( source -> !source.getAlgorithm().equals(EMPTY_STRING) || - !source.getHash().equals(EMPTY_STRING) - ) + !source.getHash().equals(EMPTY_STRING) ); - final boolean containsCms = cms != null && !cms.equals(EMPTY_STRING); - return containsCms || containsHashOrAlgorithm; } - private String getAbsolutePathToEntryCertificate(CSARArchive csar, Path csarRootDirectory) { - final String entryCertificateFileName = csar.getToscaMeta().getEntryCertificate(); - return String.format("%s/%s", csarRootDirectory.toAbsolutePath(), entryCertificateFileName); + 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) { + 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; + } + } + + 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()); + .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()); + .map(SourcesParser.Source::getValue) + .collect(Collectors.toList()); if (!sourcePaths.containsAll(nonManoSourcePaths)) { this.errors.add(new CSARErrorContentMismatch()); @@ -208,39 +350,15 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } - private void validateFileSignature(File manifestMfFile, String absolutePathToEntryCertificate) { - final boolean isValid = this.manifestFileSignatureValidator.isValid(manifestMfFile, absolutePathToEntryCertificate); + private void validateFileSignature(CmsSignatureData signatureData) { + final boolean isValid = this.manifestFileSignatureValidator.isValid(signatureData); if (!isValid) { this.errors.add(new CSARErrorInvalidSignature()); } } - private void validateEntryCertificate(CSARArchive csar, Path csarRootDirectory) { - final CSARArchive.TOSCAMeta toscaMeta = csar.getToscaMeta(); - final String entryCertificateParamName = csar.getEntryCertificateParamName(); - final Optional<File> entryCertificate = resolveCertificateFilePath(toscaMeta, csarRootDirectory); - if (!entryCertificate.isPresent() || !entryCertificate.get().exists()) { - this.errors.add(new CSARErrorUnableToFindCertificate(entryCertificateParamName)); - } - } - - private void validateManifestCms(CSARArchive.Manifest manifest) { - if (manifest.getCms() == null || manifest.getCms().isEmpty()) { - this.errors.add(new CSARErrorUnableToFindCmsSection()); - } - } - - private Optional<File> resolveCertificateFilePath(CSARArchive.TOSCAMeta toscaMeta, Path csarRootDirectory) { - final String certificatePath = toscaMeta.getEntryCertificate(); - if (certificatePath == null) { - return Optional.empty(); - } else { - return Optional.of(csarRootDirectory.resolve(certificatePath).toFile()); - } - } - private void validateSources(Path csarRootDirectory, CSARArchive.Manifest manifest) - throws NoSuchAlgorithmException, IOException { + throws NoSuchAlgorithmException, IOException { final List<SourcesParser.Source> sources = manifest.getSources(); for (SourcesParser.Source source : sources) { if (!source.getAlgorithm().isEmpty() || !source.getHash().isEmpty()) { @@ -250,7 +368,7 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } private void validateSource(Path csarRootDirectory, SourcesParser.Source source) - throws NoSuchAlgorithmException, IOException { + throws NoSuchAlgorithmException, IOException { final Path sourcePath = csarRootDirectory.resolve(source.getValue()); if (!sourcePath.toFile().exists()) { this.errors.add(new CSARErrorUnableToFindSource(source.getValue())); @@ -264,7 +382,7 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } private void validateSourceHashCode(Path csarRootDirectory, SourcesParser.Source source) - throws NoSuchAlgorithmException, IOException { + throws NoSuchAlgorithmException, IOException { String hashCode = generateHashCode(csarRootDirectory, source); if (!hashCode.equals(source.getHash())) { this.errors.add(new CSARErrorWrongHashCode(source.getValue())); @@ -272,7 +390,7 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } private String generateHashCode(Path csarRootDirectory, SourcesParser.Source source) - throws NoSuchAlgorithmException, IOException { + throws NoSuchAlgorithmException, IOException { final byte[] sourceData = Files.readAllBytes(csarRootDirectory.resolve(source.getValue())); final String algorithm = source.getAlgorithm(); @@ -291,25 +409,26 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } - class ManifestFileSignatureValidator { + static class ManifestFileSignatureValidator { - private final Logger log = LoggerFactory.getLogger(ManifestFileSignatureValidator.class); private final ManifestFileSplitter manifestFileSplitter = new ManifestFileSplitter(); private final CmsSignatureValidator cmsSignatureValidator = new CmsSignatureValidator(); + private final CmsSignatureDataFactory cmsSignatureDataFactory = new CmsSignatureDataFactory(); + + CmsSignatureData createSignatureData(File manifestFile) throws CmsSignatureLoadingException { + ManifestFileModel mf = manifestFileSplitter.split(manifestFile); + return cmsSignatureDataFactory.createForFirstSigner( + toBytes(mf.getCMS(), mf.getNewLine()), + toBytes(mf.getData(), mf.getNewLine()) + ); + } - boolean isValid(File manifestFile, String absolutePathToEntryCertificate) { + boolean isValid(CmsSignatureData signatureData) { try { - byte[] entryCertificate = Files.readAllBytes(new File(absolutePathToEntryCertificate).toPath()); - ManifestFileModel mf = manifestFileSplitter.split(manifestFile); - return cmsSignatureValidator.verifySignedData(toBytes(mf.getCMS(), mf.getNewLine()), - Optional.of(entryCertificate), - toBytes(mf.getData(), mf.getNewLine())); + return cmsSignatureValidator.verifySignedData(signatureData); } catch (CmsSignatureValidatorException e) { LOG.error("Unable to verify signed data!", e); return false; - } catch (IOException e) { - LOG.error("Unable to read ETSI entry certificate file!", e); - return false; } } @@ -318,4 +437,5 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { return updatedData.getBytes(Charset.defaultCharset()); } } + } diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/security/CertificateLoadingException.java b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CertificateLoadingException.java new file mode 100644 index 0000000..2be5be2 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CertificateLoadingException.java @@ -0,0 +1,25 @@ +/* + * 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.security; + +public class CertificateLoadingException extends RuntimeException { + + public CertificateLoadingException(String s, Throwable t) { + super(s, t); + } +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureData.java b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureData.java new file mode 100644 index 0000000..456f365 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureData.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.security; + +import org.bouncycastle.cms.SignerInformation; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Optional; + +public class CmsSignatureData { + + private X509Certificate certificate; + private final SignerInformation signerInformation; + + public CmsSignatureData(X509Certificate certificate, SignerInformation signerInformation) { + this.certificate = certificate; + this.signerInformation = signerInformation; + } + + public CmsSignatureData(SignerInformation signerInformation) { + this.signerInformation = signerInformation; + } + + public Optional<X509Certificate> getCertificate() { + return Optional.ofNullable(certificate); + } + + public SignerInformation getSignerInformation() { + return signerInformation; + } + + public void loadCertificate(Path pathToCertificate) throws CertificateLoadingException { + try { + loadCertificate(Files.readAllBytes(pathToCertificate)); + } catch (IOException e) { + final String errorMessage = String.format( + "Error during loading Certificate from given path: %s !" + ,pathToCertificate + ); + throw new CertificateLoadingException(errorMessage, e); + } + } + + public void loadCertificate(final byte[] certificate) throws CertificateLoadingException { + try (InputStream in = new ByteArrayInputStream(certificate)) { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + this.certificate = (X509Certificate) factory.generateCertificate(in); + } catch (IOException | CertificateException e) { + throw new CertificateLoadingException("Error during loading Certificate from bytes!", e); + } + } + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureDataFactory.java b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureDataFactory.java new file mode 100644 index 0000000..2744bc6 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureDataFactory.java @@ -0,0 +1,91 @@ +/* + * 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.security; + +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSTypedData; +import org.bouncycastle.cms.SignerInformation; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.util.Store; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Optional; + +public class CmsSignatureDataFactory { + + public CmsSignatureData createForFirstSigner(final byte[] cmsSignature, final byte[] fileContent) + throws CmsSignatureLoadingException{ + + try (ByteArrayInputStream cmsSignatureStream = new ByteArrayInputStream(cmsSignature)) { + CMSSignedData signedData = getCMSSignedData(fileContent, cmsSignatureStream); + Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners(); + Store<X509CertificateHolder> certificates = signedData.getCertificates(); + SignerInformation firstSigner = getFirstSigner(signers); + CmsSignatureData signatureData = new CmsSignatureData(firstSigner); + getFirstSignerCertificate(certificates, firstSigner).ifPresent( + signatureData::loadCertificate + ); + return signatureData; + } catch (CertificateLoadingException | IOException | CMSException e) { + throw new CmsSignatureLoadingException("Unexpected error occurred during signature validation!", e); + } + } + + private SignerInformation getFirstSigner(Collection<SignerInformation> signers) { + return signers.iterator().next(); + } + + private Optional<byte[]> getFirstSignerCertificate( + Store<X509CertificateHolder> certificates, + SignerInformation firstSigner) + throws IOException { + Collection<X509CertificateHolder> firstSignerCertificates = certificates.getMatches(firstSigner.getSID()); + Optional<byte[]> cert; + if (!firstSignerCertificates.isEmpty()) { + X509CertificateHolder firstSignerFirstCertificate = firstSignerCertificates.iterator().next(); + cert = Optional.of(firstSignerFirstCertificate.getEncoded()); + } else { + cert = Optional.empty(); + } + return cert; + } + + + private CMSSignedData getCMSSignedData(byte[] innerPackageFileCSAR, ByteArrayInputStream signatureStream) throws IOException, CmsSignatureLoadingException, CMSException { + ContentInfo signature = produceSignature(signatureStream); + CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFileCSAR); + return new CMSSignedData(signedContent, signature); + } + + private ContentInfo produceSignature(ByteArrayInputStream signatureStream) throws IOException, CmsSignatureLoadingException { + Object parsedObject = new PEMParser(new InputStreamReader(signatureStream, Charset.defaultCharset())).readObject(); + if (!(parsedObject instanceof ContentInfo)) { + throw new CmsSignatureLoadingException("Signature is not recognized!"); + } + return ContentInfo.getInstance(parsedObject); + } + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureLoadingException.java b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureLoadingException.java new file mode 100644 index 0000000..0e203b2 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureLoadingException.java @@ -0,0 +1,29 @@ +/* + * 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.security; + +public class CmsSignatureLoadingException extends Exception { + + public CmsSignatureLoadingException(String s) { + super(s); + } + + public CmsSignatureLoadingException(String s, Throwable t) { + super(s, t); + } +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureValidator.java index 47d4bef..5d7b879 100644 --- a/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureValidator.java +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureValidator.java @@ -17,30 +17,14 @@ package org.onap.cvc.csar.security; -import org.bouncycastle.asn1.cms.ContentInfo; -import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.CMSException; -import org.bouncycastle.cms.CMSProcessableByteArray; -import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignerDigestMismatchException; -import org.bouncycastle.cms.CMSTypedData; -import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; -import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.util.Store; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.util.Collection; import java.util.Optional; public class CmsSignatureValidator { @@ -52,63 +36,30 @@ public class CmsSignatureValidator { final Optional<byte[]> certificate, final byte[] fileContent) throws CmsSignatureValidatorException { - try (ByteArrayInputStream cmsSignatureStream = new ByteArrayInputStream(cmsSignature)) { - CMSSignedData signedData = getCMSSignedData(fileContent, cmsSignatureStream); - Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners(); - SignerInformation firstSigner = signers.iterator().next(); - - Store<X509CertificateHolder> certificates = signedData.getCertificates(); - Collection<X509CertificateHolder> firstSignerCertificates = certificates.getMatches(firstSigner.getSID()); - X509Certificate cert; - if (!firstSignerCertificates.isEmpty()) { - X509CertificateHolder firstSignerFirstCertificate = getX509CertificateHolder(firstSignerCertificates); - cert = loadCertificate(firstSignerFirstCertificate.getEncoded()); - } else { - cert = loadCertificate(certificate.orElseThrow(() -> new CmsSignatureValidatorException("No certificate found in cms signature and ETSI-Entry-Certificate doesn't exist"))); + try { + CmsSignatureData signatureData = new CmsSignatureDataFactory().createForFirstSigner(cmsSignature, fileContent); + if( signatureData.getCertificate().isEmpty() ) { + signatureData.loadCertificate(certificate.orElseThrow(() -> new CmsSignatureValidatorException("No certificate found in cms signature and ETSI-Entry-Certificate doesn't exist"))); } + return verifySignedData(signatureData); + } catch ( CmsSignatureLoadingException e) { + throw new CmsSignatureValidatorException("Unexpected error occurred during signature validation!", e); + } + } - return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); + public boolean verifySignedData(final CmsSignatureData signatureData) throws CmsSignatureValidatorException { + try { + X509Certificate certificate = signatureData.getCertificate().orElseThrow(() -> new CMSException("No certificate found in signature data!")); + return signatureData.getSignerInformation().verify(new JcaSimpleSignerInfoVerifierBuilder().build(certificate)); } catch (CMSSignerDigestMismatchException e){ //message-digest attribute value does not match calculated value LOG.warn("CMS signer digest mismatch.", e); return false; } - catch (OperatorCreationException | IOException | CMSException e) { + catch (OperatorCreationException | CMSException e) { throw new CmsSignatureValidatorException("Unexpected error occurred during signature validation!", e); } } - private X509CertificateHolder getX509CertificateHolder(Collection<X509CertificateHolder> firstSignerCertificates) throws CmsSignatureValidatorException { - if(!firstSignerCertificates.iterator().hasNext()){ - throw new CmsSignatureValidatorException("No certificate found in cms signature that should contain one!"); - } - return firstSignerCertificates.iterator().next(); - } - - private CMSSignedData getCMSSignedData(byte[] innerPackageFileCSAR, ByteArrayInputStream signatureStream) throws IOException, CmsSignatureValidatorException, CMSException { - ContentInfo signature = produceSignature(signatureStream); - CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFileCSAR); - return new CMSSignedData(signedContent, signature); - } - - private ContentInfo produceSignature(ByteArrayInputStream signatureStream) throws IOException, CmsSignatureValidatorException { - Object parsedObject = new PEMParser(new InputStreamReader(signatureStream, Charset.defaultCharset())).readObject(); - if (!(parsedObject instanceof ContentInfo)) { - throw new CmsSignatureValidatorException("Signature is not recognized!"); - } - return ContentInfo.getInstance(parsedObject); - } - - - private X509Certificate loadCertificate(byte[] certFile) throws CmsSignatureValidatorException { - try (InputStream in = new ByteArrayInputStream(certFile)) { - CertificateFactory factory = CertificateFactory.getInstance("X.509"); - return (X509Certificate) factory.generateCertificate(in); - } catch (CertificateException | IOException e) { - throw new CmsSignatureValidatorException("Error during loading Certificate from bytes!", e); - } - } - - } diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureValidatorException.java b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureValidatorException.java index 75cd8de..1f708fd 100644 --- a/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureValidatorException.java +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/security/CmsSignatureValidatorException.java @@ -25,4 +25,5 @@ public class CmsSignatureValidatorException extends Exception { public CmsSignatureValidatorException(String s, Throwable t) { super(s, t); } + } diff --git a/csarvalidation/src/test/java/org/onap/cvc/csar/CsarValidatorTest.java b/csarvalidation/src/test/java/org/onap/cvc/csar/CsarValidatorTest.java index 299aff2..491b20a 100644 --- a/csarvalidation/src/test/java/org/onap/cvc/csar/CsarValidatorTest.java +++ b/csarvalidation/src/test/java/org/onap/cvc/csar/CsarValidatorTest.java @@ -35,79 +35,83 @@ import static org.onap.cvc.csar.cc.sol004.IntegrationTestUtils.absoluteFilePath; public class CsarValidatorTest { - private static final String NO_CERTIFICATE_RULE = "r130206"; + private static final String CERTIFICATION_RULE = "r130206"; private static final String OPERATION_STATUS_FAILED = "FAILED"; + private static final String OPERATION_STATUS_PASS = "PASS"; @Test - public void shouldReportErrorAsWarningWhenErrorIsIgnored() throws URISyntaxException { + public void shouldReportThanVnfValidationFailed() throws URISyntaxException { // given OnapCliWrapper cli = new OnapCliWrapper(new String[]{ "--product", "onap-dublin", "csar-validate", "--format", "json", - "--pnf", - "--csar", absoluteFilePath("pnf/r130206/csar-option1-warning-2.csar")}); + "--csar", absoluteFilePath("VoLTE.csar")}); // when cli.handle(); // then final OnapCommandResult onapCommandResult = cli.getCommandResult(); - assertTrue(onapCommandResult.getOutput().toString().contains( - "\"warnings\":[{\"vnfreqNo\":\"R130206\",\"code\":\"0x1006\",\"message\":\"Warning. Consider adding package " - + "integrity and authenticity assurance according to ETSI NFV-SOL 004 Security Option 1\",\"file\":\"\",\"lineNumber\":-1}]}")); + verifyThatOperation(onapCommandResult, OPERATION_STATUS_FAILED); + verifyThatXRulesFails(onapCommandResult, 7); + verifyThatOperationFinishedWithoutAnyError(cli); } + @Test - public void shouldReportThanVnfValidationFailed() throws URISyntaxException { + public void shouldReportOnlyWarningWhenCsarDoNotHaveCertificateAndHashesInManifest() throws URISyntaxException { // given OnapCliWrapper cli = new OnapCliWrapper(new String[]{ "--product", "onap-dublin", "csar-validate", "--format", "json", - "--csar", absoluteFilePath("VoLTE.csar")}); - + "--pnf", + "--csar", absoluteFilePath("pnf/validFile.csar")}); // when cli.handle(); // then final OnapCommandResult onapCommandResult = cli.getCommandResult(); - verifyThatOperation(onapCommandResult, OPERATION_STATUS_FAILED); - verifyThatXRulesFails(onapCommandResult, 7); + verifyThatOperation(onapCommandResult, OPERATION_STATUS_PASS); + assertTrue(onapCommandResult.getOutput().toString().contains( + "\"warnings\":[{" + + "\"vnfreqNo\":\"R130206\"," + + "\"code\":\"0x1006\"," + + "\"message\":\"Warning. Consider adding package integrity and authenticity assurance according to ETSI NFV-SOL 004 Security Option 1\"," + + "\"file\":\"\"," + + "\"lineNumber\":-1}]")); verifyThatOperationFinishedWithoutAnyError(cli); } - @Test - public void shouldReportThatPnfValidationFailedWhenCsarDoNotHaveCertificate_allOtherRulesShouldPass() throws URISyntaxException { + public void shouldNotReportThatPnfValidationFailedWhenZipDoNotHaveCertificatesAndHashesInManifest() throws URISyntaxException { // given OnapCliWrapper cli = new OnapCliWrapper(new String[]{ "--product", "onap-dublin", "csar-validate", "--format", "json", "--pnf", - "--csar", absoluteFilePath("pnf/r972082/validFile.csar")}); + "--csar", absoluteFilePath("pnf/signed-package-valid-signature.zip")}); + // when cli.handle(); // then final OnapCommandResult onapCommandResult = cli.getCommandResult(); - verifyThatOperation(onapCommandResult, OPERATION_STATUS_FAILED); - verifyThatXRulesFails(onapCommandResult, 1); - verifyThatRuleFails(onapCommandResult, NO_CERTIFICATE_RULE); + verifyThatOperation(onapCommandResult, OPERATION_STATUS_PASS); verifyThatOperationFinishedWithoutAnyError(cli); } @Test - public void shouldReportThatPnfValidationFailedWhenZipDoNotHaveCertificate_allOtherRulesShouldPass() throws URISyntaxException { + public void shouldReportThatPnfValidationFailedWhenCsarContainsCertificateInCmsAndInToscaAndInRootAndHashIsIncorrect_allOtherRulesShouldPass() throws URISyntaxException { // given OnapCliWrapper cli = new OnapCliWrapper(new String[]{ "--product", "onap-dublin", "csar-validate", "--format", "json", "--pnf", - "--csar", absoluteFilePath("pnf/signed-package-valid-signature.zip")}); - + "--csar", absoluteFilePath("pnf/r130206/cert-in-cms-and-root-and-tosca-incorrect-hash.csar")}); // when cli.handle(); @@ -115,7 +119,7 @@ public class CsarValidatorTest { final OnapCommandResult onapCommandResult = cli.getCommandResult(); verifyThatOperation(onapCommandResult, OPERATION_STATUS_FAILED); verifyThatXRulesFails(onapCommandResult, 1); - verifyThatRuleFails(onapCommandResult, NO_CERTIFICATE_RULE); + verifyThatRuleFails(onapCommandResult, CERTIFICATION_RULE); verifyThatOperationFinishedWithoutAnyError(cli); } diff --git a/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206IntegrationTest.java b/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206IntegrationTest.java index 3eed6c6..cdaef79 100644 --- a/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206IntegrationTest.java +++ b/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR130206IntegrationTest.java @@ -49,10 +49,10 @@ public class VTPValidateCSARR130206IntegrationTest { "To verify signed package please please follow instructions from test/resources/README.txt file and comment @Ignore tag. " + "Use instructions for option 1. Test was created for manual verification." ) - public void manual_shouldValidateProperCsar() throws Exception { + public void manual_shouldValidateProperCsarWithCms() throws Exception { // given - configureTestCase(testCase, "pnf/r130206/csar-option1-valid.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms-valid.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); // when testCase.execute(); @@ -64,13 +64,31 @@ public class VTPValidateCSARR130206IntegrationTest { @Test @Ignore("It is impossible to write test which will always pass, because certificate used to sign the file has time validity." + - "To verify signed package please please follow instructions from test/resources/README.txt file and comment @Ignore tag. " + - "Use instructions for option 1. Test was created for manual verification." + "To verify signed package please please follow instructions from test/resources/README.txt file and comment @Ignore tag. " + + "Use instructions for option 1. Test was created for manual verification." + ) + public void manual_shouldValidateCsarWithCertificateInToscaEtsiWithValidSignature() throws Exception { + + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-tosca-valid.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isZero(); + } + + @Test + @Ignore("It is impossible to write test which will always pass, because certificate used to sign the file has time validity." + + "To verify signed package please please follow instructions from test/resources/README.txt file and comment @Ignore tag. " + + "Use instructions for option 1. Test was created for manual verification." ) - public void manual_shouldValidateCsarWithCertificateInEtsiAndMissingInCMS() throws Exception { + public void manual_shouldValidateCsarWithCertificateInRootWithValidSignature() throws Exception { // given - configureTestCase(testCase, "pnf/r130206/csar-with-etsi-cert-without-cert-in-cms.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + configureTestCase(testCase, "pnf/r130206/csar-cert-in-root-valid.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); // when testCase.execute(); @@ -81,9 +99,10 @@ public class VTPValidateCSARR130206IntegrationTest { } @Test - public void shouldReportWarningForMissingCMSAndHashCodes() throws Exception{ + public void shouldReportWarningForMissingCertInCmsToscaMetaAndRootCatalogAndMissingHashCodesInManifest() + throws Exception{ // given - configureTestCase(testCase, "pnf/r130206/csar-option1-warning.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + configureTestCase(testCase, "pnf/r130206/csar-not-secure-warning.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); // when testCase.execute(); @@ -92,16 +111,102 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(errors.size()).isEqualTo(1); assertThat(convertToMessagesList(errors)).contains( - "Warning. Consider adding package integrity and authenticity assurance according to ETSI NFV-SOL 004 Security Option 1" + "Warning. Consider adding package integrity and authenticity assurance according to ETSI NFV-SOL 004 Security Option 1" ); } + @Test + public void shouldReturnNoErrorWhenCertIsOnlyInCmsAndAlgorithmAndHashesAreCorrect() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(1); + assertThat(convertToMessagesList(errors)).contains( + "File has invalid signature!" + ); + } @Test - public void shouldReportThatOnlySignatureIsInvalid() throws Exception { + public void shouldReturnNoErrorWhenCertIsOnlyInToscaAndAlgorithmAndHashesAreCorrect() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-tosca.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(1); + assertThat(convertToMessagesList(errors)).contains( + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnNoErrorWhenCertIsOnlyInRootDirectoryAndAlgorithmAndHashesAreCorrect() + throws Exception{ // given - configureTestCase(testCase, "pnf/r130206/csar-option1-validSection.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + configureTestCase(testCase, "pnf/r130206/csar-cert-in-root.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(1); + assertThat(convertToMessagesList(errors)).contains( + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertIsOnlyInCmsHoweverHashesAreIncorrect() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms-incorrect-hash.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(2); + assertThat(convertToMessagesList(errors)).contains( + "Source 'Artifacts/Other/my_script.csh' has wrong hash!", + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertIsOnlyInToscaHoweverHashesAreIncorrect() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-tosca-incorrect-hash.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(2); + assertThat(convertToMessagesList(errors)).contains( + "Source 'Artifacts/Deployment/Measurements/PM_Dictionary.yml' has wrong hash!", + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertIsOnlyInRootDirectoryHoweverHashesAreIncorrect() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-root-incorrect-hash.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); // when testCase.execute(); @@ -110,70 +215,232 @@ public class VTPValidateCSARR130206IntegrationTest { List<CSARArchive.CSARError> errors = testCase.getErrors(); assertThat(errors.size()).isEqualTo(2); assertThat(convertToMessagesList(errors)).contains( - "File has invalid CMS signature!", - "Mismatch between contents of non-mano-artifact-sets and source files of the package" + "Source 'Artifacts/Deployment/Events/RadioNode_Pnf_v1.yaml' has wrong hash!", + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenToscaEtsiEntryCertificatePointToNotExistingFile() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-with-tosca-cert-pointing-non-existing-cert.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(2); + assertThat(convertToMessagesList(errors)).contains( + "Unable to find cert file defined by ETSI-Entry-Certificate!", + "Invalid value. Entry [Entry-Certificate]. Artifacts/sample-pnf.cert does not exist" + ); + } + + @Test + public void shouldReturnErrorWhenCertificateIsLocatedInCmsAndInTosca() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms-and-tosca.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(3); + assertThat(convertToMessagesList(errors)).contains( + "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container", + "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container", + "File has invalid signature!" ); } @Test - public void shouldReportErrorsForInvalidCsar() throws Exception { + public void shouldReturnErrorWhenCertificateIsLocatedInCmsAndInToscaAndHashIsIncorrect() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms-and-tosca-incorrect-hash.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(4); + assertThat(convertToMessagesList(errors)).contains( + "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container", + "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container", + "Source 'Artifacts/Informational/user_guide.txt' has wrong hash!", + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertificateIsLocatedInCmsAndInToscaAndInRootDirectory() + throws Exception{ // given - configureTestCase(testCase, "pnf/r130206/csar-option1-invalid.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms-and-root-and-tosca.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); // when testCase.execute(); // then List<CSARArchive.CSARError> errors = testCase.getErrors(); - assertThat(errors.size()).isEqualTo(6); + assertThat(errors.size()).isEqualTo(4); assertThat(convertToMessagesList(errors)).contains( - "Unable to find CMS section in manifest!", - "Source 'Definitions/MainServiceTemplate.yaml' has wrong hash!", - "Source 'Artifacts/Other/my_script.csh' has hash, but unable to find algorithm tag!", - "Unable to calculate digest - file missing: Artifacts/NonExisting2.txt", - "Mismatch between contents of non-mano-artifact-sets and source files of the package", - "File has invalid CMS signature!" + "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container", + "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container", + "Certificate present in root catalog despite the certificate is included in the signature container", + "File has invalid signature!" ); } @Test - public void shouldReportThanInVnfPackageCertFileWasNotDefined() throws Exception { + public void shouldReturnErrorWhenCertificateIsLocatedInCmsAndInToscaAndInRootDirectoryAndHashIsIncorrect() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms-and-root-and-tosca-incorrect-hash.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(5); + assertThat(convertToMessagesList(errors)).contains( + "ETSI-Entry-Certificate entry in Tosca.meta is defined despite the certificate is included in the signature container", + "ETSI-Entry-Certificate certificate present despite the certificate is included in the signature container", + "Certificate present in root catalog despite the certificate is included in the signature container", + "Source 'Artifacts/Informational/user_guide.txt' has wrong hash!", + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertificateIsLocatedInCmsAndInRootDirectory() + throws Exception{ // given - configureTestCase(testCase, "sample2.csar", "vtp-validate-csar-r130206.yaml", false); + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms-and-root.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); // when testCase.execute(); // then List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(2); assertThat(convertToMessagesList(errors)).contains( - "Unable to find cert file defined by Entry-Certificate!", - "Warning. Consider adding package integrity and authenticity assurance according to ETSI NFV-SOL 004 Security Option 1", - "Missing. Entry [tosca_definitions_version]" + "Certificate present in root catalog despite the certificate is included in the signature container", + "File has invalid signature!" ); } + @Test + public void shouldReturnErrorWhenCertificateIsLocatedInCmsAndInRootDirectoryAndHashIsIncorrect() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-cms-and-root-incorrect-hash.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(3); + assertThat(convertToMessagesList(errors)).contains( + "Certificate present in root catalog despite the certificate is included in the signature container", + "Source 'Artifacts/Informational/user_guide.txt' has wrong hash!", + "File has invalid signature!" + ); + } @Test - public void shouldReportThanInVnfPackageETSIFileIsMissing() throws Exception { + public void shouldReturnErrorWhenCertificateIsLocatedInToscaAndInRootDirectory() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-root-and-tosca.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(2); + assertThat(convertToMessagesList(errors)).contains( + "Certificate present in root catalog despite the certificate is included in ETSI-Entry-Certificate", + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertificateIsLocatedInToscaAndInRootDirectoryAdnHashIsIncorrect() + throws Exception{ // given - configureTestCase(testCase, "pnf/r130206/csar-with-no-certificate.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + configureTestCase(testCase, "pnf/r130206/csar-cert-in-root-and-tosca-incorrect-hash.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); // when testCase.execute(); // then List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(3); + assertThat(convertToMessagesList(errors)).contains( + "Certificate present in root catalog despite the certificate is included in ETSI-Entry-Certificate", + "Source 'Artifacts/Deployment/Yang_module/yang-module1.yang' has wrong hash!", + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnNoErrorWhenCertificateIsLocatedInToscaAndInRootDirectoryHoweverEtsiEntryIsPointingCertificateInRoot() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-root-pointed-by-tosca.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(1); assertThat(convertToMessagesList(errors)).contains( - "Unable to find cert file defined by ETSI-Entry-Certificate!", - "Warning. Consider adding package integrity and authenticity assurance according to ETSI NFV-SOL 004 Security Option 1" + "File has invalid signature!" + ); + } + + @Test + public void shouldReturnErrorWhenCertificateIsLocatedInToscaHoweverManifestDoesNotContainsCms() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-cert-in-tosca-no-cms.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(1); + assertThat(convertToMessagesList(errors)).contains( + "Unable to find cms signature!" ); } + @Test + public void shouldReturnErrorWhenCsarDoesNotContainsCmsAndCertsHoweverManifestContainsHash() + throws Exception{ + // given + configureTestCase(testCase, "pnf/r130206/csar-no-cms-no-cert-with-hash.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(1); + assertThat(convertToMessagesList(errors)).contains( + "Unable to find cms signature!" + ); + } } diff --git a/csarvalidation/src/test/resources/pnf/r130206/cert-in-cms-and-root-and-tosca-incorrect-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/cert-in-cms-and-root-and-tosca-incorrect-hash.csar Binary files differnew file mode 100644 index 0000000..bf19010 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/cert-in-cms-and-root-and-tosca-incorrect-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-and-tosca-incorrect-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-and-tosca-incorrect-hash.csar Binary files differnew file mode 100644 index 0000000..c8a4c39 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-and-tosca-incorrect-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-and-tosca.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-and-tosca.csar Binary files differnew file mode 100644 index 0000000..b47f565 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-and-tosca.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-incorrect-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-incorrect-hash.csar Binary files differnew file mode 100644 index 0000000..392d41e --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root-incorrect-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root.csar Binary files differnew file mode 100644 index 0000000..f9112c7 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-root.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-tosca-incorrect-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-tosca-incorrect-hash.csar Binary files differnew file mode 100644 index 0000000..f331233 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-tosca-incorrect-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-tosca.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-tosca.csar Binary files differnew file mode 100644 index 0000000..0854291 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-and-tosca.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-incorrect-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-incorrect-hash.csar Binary files differnew file mode 100644 index 0000000..12c90a2 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-incorrect-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid.csar Binary files differnew file mode 100644 index 0000000..ece4064 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms-valid.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms.csar Binary files differnew file mode 100644 index 0000000..5ddbe1a --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-cms.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-and-tosca-incorrect-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-and-tosca-incorrect-hash.csar Binary files differnew file mode 100644 index 0000000..be19521 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-and-tosca-incorrect-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-and-tosca.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-and-tosca.csar Binary files differnew file mode 100644 index 0000000..e4dbef9 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-and-tosca.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-incorrect-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-incorrect-hash.csar Binary files differnew file mode 100644 index 0000000..b926aac --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-incorrect-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-pointed-by-tosca.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-pointed-by-tosca.csar Binary files differnew file mode 100644 index 0000000..0d9c3f3 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-pointed-by-tosca.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-valid.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-valid.csar Binary files differnew file mode 100644 index 0000000..70885d8 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root-valid.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root.csar Binary files differnew file mode 100644 index 0000000..d5d8f94 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-root.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-incorrect-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-incorrect-hash.csar Binary files differnew file mode 100644 index 0000000..9b651d0 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-incorrect-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-warning.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-no-cms.csar Binary files differindex d50d74a..fe34a61 100644 --- a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-warning.csar +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-no-cms.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-valid.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-valid.csar Binary files differnew file mode 100644 index 0000000..3446aaf --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca-valid.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca.csar Binary files differnew file mode 100644 index 0000000..c4168dc --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-cert-in-tosca.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-no-cms-no-cert-with-hash.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-no-cms-no-cert-with-hash.csar Binary files differnew file mode 100644 index 0000000..826425e --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-no-cms-no-cert-with-hash.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-not-secure-warning.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-not-secure-warning.csar Binary files differnew file mode 100644 index 0000000..6520a61 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-not-secure-warning.csar diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-invalid.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-option1-invalid.csar Binary files differdeleted file mode 100644 index 187c008..0000000 --- a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-invalid.csar +++ /dev/null diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-valid.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-option1-valid.csar Binary files differdeleted file mode 100644 index 08c3605..0000000 --- a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-valid.csar +++ /dev/null diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-validSection.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-option1-validSection.csar Binary files differdeleted file mode 100644 index bc90a75..0000000 --- a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-validSection.csar +++ /dev/null diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-warning-2.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-option1-warning-2.csar Binary files differdeleted file mode 100644 index 748efbb..0000000 --- a/csarvalidation/src/test/resources/pnf/r130206/csar-option1-warning-2.csar +++ /dev/null diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-with-etsi-cert-without-cert-in-cms.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-with-etsi-cert-without-cert-in-cms.csar Binary files differdeleted file mode 100644 index d359994..0000000 --- a/csarvalidation/src/test/resources/pnf/r130206/csar-with-etsi-cert-without-cert-in-cms.csar +++ /dev/null diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-with-no-certificate.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-with-tosca-cert-pointing-non-existing-cert.csar Binary files differindex 998619a..b392fac 100644 --- a/csarvalidation/src/test/resources/pnf/r130206/csar-with-no-certificate.csar +++ b/csarvalidation/src/test/resources/pnf/r130206/csar-with-tosca-cert-pointing-non-existing-cert.csar diff --git a/csarvalidation/src/test/resources/pnf/validFile.csar b/csarvalidation/src/test/resources/pnf/validFile.csar Binary files differnew file mode 100644 index 0000000..11d1945 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/validFile.csar |