From 657849e70f70f700cc8470af48351f3ae6b47b6f Mon Sep 17 00:00:00 2001 From: Aleksandra Maciaga Date: Wed, 13 May 2020 14:16:06 +0200 Subject: Fix VNF/PNF package integrity issue with CMS signature not containing certificate Signed-off-by: Aleksandra Maciaga Issue-ID: VNFSDK-582 Change-Id: Id3dc6c8e1ead183449fcf903d9b9b886e4796e84 --- .../cvc/csar/cc/sol004/VTPValidateCSARR130206.java | 45 +++++++++++++-------- .../cvc/csar/security/CmsSignatureValidator.java | 12 +++--- .../VTPValidateCSARR130206IntegrationTest.java | 37 +++++++++++++++++ .../csar-with-etsi-cert-without-cert-in-cms.csar | Bin 0 -> 116773 bytes .../pnf/r130206/csar-with-no-certificate.csar | Bin 0 -> 116706 bytes 5 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 csarvalidation/src/test/resources/pnf/r130206/csar-with-etsi-cert-without-cert-in-cms.csar create mode 100644 csarvalidation/src/test/resources/pnf/r130206/csar-with-no-certificate.csar 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 fefe65b..74706c7 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 @@ -148,25 +148,32 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { validateNonManoCohesionWithSources(nonMano, sources); final File manifestMfFile = csar.getManifestMfFile(); + final String absolutePathToEntryCertificate = getAbsolutePathToEntryCertificate(csar, csarRootDirectory); if (manifestMfFile != null) { - validateFileSignature(manifestMfFile); + validateFileSignature(manifestMfFile, absolutePathToEntryCertificate); } } + private String getAbsolutePathToEntryCertificate(CSARArchive csar, Path csarRootDirectory) { + final String entryCertificateFileName = csar.getToscaMeta().getEntryCertificate(); + return String.format("%s/%s", csarRootDirectory.toAbsolutePath(), entryCertificateFileName); + } + + private void validateNonManoCohesionWithSources(final Map>> nonMano, final List sources) { final Collection>> values = nonMano.values(); final List 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 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()); @@ -174,8 +181,8 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } - private void validateFileSignature(File manifestMfFile) { - final boolean isValid = this.manifestFileSignatureValidator.isValid(manifestMfFile); + private void validateFileSignature(File manifestMfFile, String absolutePathToEntryCertificate) { + final boolean isValid = this.manifestFileSignatureValidator.isValid(manifestMfFile, absolutePathToEntryCertificate); if (!isValid) { this.errors.add(new CSARErrorInvalidSignature()); } @@ -205,7 +212,7 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { } private void validateSources(Path csarRootDirectory, CSARArchive.Manifest manifest) - throws NoSuchAlgorithmException, IOException { + throws NoSuchAlgorithmException, IOException { final List sources = manifest.getSources(); for (SourcesParser.Source source : sources) { if (!source.getAlgorithm().isEmpty() || !source.getHash().isEmpty()) { @@ -215,7 +222,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())); @@ -229,7 +236,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())); @@ -237,7 +244,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(); @@ -262,15 +269,19 @@ public class VTPValidateCSARR130206 extends VTPValidateCSARBase { private final ManifestFileSplitter manifestFileSplitter = new ManifestFileSplitter(); private final CmsSignatureValidator cmsSignatureValidator = new CmsSignatureValidator(); - boolean isValid(File manifestFile) { + boolean isValid(File manifestFile, String absolutePathToEntryCertificate) { try { + byte[] entryCertificate = Files.readAllBytes(new File(absolutePathToEntryCertificate).toPath()); ManifestFileModel mf = manifestFileSplitter.split(manifestFile); return cmsSignatureValidator.verifySignedData(toBytes(mf.getCMS(), mf.getNewLine()), - Optional.empty(), - toBytes(mf.getData(), mf.getNewLine())); + Optional.of(entryCertificate), + toBytes(mf.getData(), mf.getNewLine())); } 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; } } 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 b8b3714..47d4bef 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 @@ -57,13 +57,14 @@ public class CmsSignatureValidator { Collection signers = signedData.getSignerInfos().getSigners(); SignerInformation firstSigner = signers.iterator().next(); - Store certificates = signedData.getCertificates(); + Store certificates = signedData.getCertificates(); + Collection firstSignerCertificates = certificates.getMatches(firstSigner.getSID()); X509Certificate cert; - if (!certificate.isPresent()) { - X509CertificateHolder firstSignerFirstCertificate = getX509CertificateHolder(firstSigner, certificates); + if (!firstSignerCertificates.isEmpty()) { + X509CertificateHolder firstSignerFirstCertificate = getX509CertificateHolder(firstSignerCertificates); cert = loadCertificate(firstSignerFirstCertificate.getEncoded()); } else { - cert = loadCertificate(certificate.get()); + cert = loadCertificate(certificate.orElseThrow(() -> new CmsSignatureValidatorException("No certificate found in cms signature and ETSI-Entry-Certificate doesn't exist"))); } return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); @@ -77,8 +78,7 @@ public class CmsSignatureValidator { } } - private X509CertificateHolder getX509CertificateHolder(SignerInformation firstSigner, Store certificates) throws CmsSignatureValidatorException { - Collection firstSignerCertificates = certificates.getMatches(firstSigner.getSID()); + private X509CertificateHolder getX509CertificateHolder(Collection firstSignerCertificates) throws CmsSignatureValidatorException { if(!firstSignerCertificates.iterator().hasNext()){ throw new CmsSignatureValidatorException("No certificate found in cms signature that should contain one!"); } 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 036e169..feabe7f 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 @@ -62,6 +62,25 @@ public class VTPValidateCSARR130206IntegrationTest { assertThat(errors.size()).isEqualTo(0); } + @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 { + + // given + configureTestCase(testCase, "pnf/r130206/csar-with-etsi-cert-without-cert-in-cms.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(0); + } + + @Test public void shouldReportThatOnlySignatureIsInvalid() throws Exception { @@ -122,5 +141,23 @@ public class VTPValidateCSARR130206IntegrationTest { } + @Test + public void shouldReportThanInVnfPackageETSIFileIsMissingAndNoCertificateInCMS() throws Exception { + + // given + configureTestCase(testCase, "pnf/r130206/csar-with-no-certificate.csar", "vtp-validate-csar-r130206.yaml", IS_PNF); + + // when + testCase.execute(); + + // then + List errors = testCase.getErrors(); + assertThat(convertToMessagesList(errors)).contains( + "Unable to find cert file defined by ETSI-Entry-Certificate!", + "Unable to find CMS section in manifest!" + + ); + } + } 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 new file mode 100644 index 0000000..d359994 Binary files /dev/null and b/csarvalidation/src/test/resources/pnf/r130206/csar-with-etsi-cert-without-cert-in-cms.csar differ diff --git a/csarvalidation/src/test/resources/pnf/r130206/csar-with-no-certificate.csar b/csarvalidation/src/test/resources/pnf/r130206/csar-with-no-certificate.csar new file mode 100644 index 0000000..624f8fe Binary files /dev/null and b/csarvalidation/src/test/resources/pnf/r130206/csar-with-no-certificate.csar differ -- cgit 1.2.3-korg