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/src/main/java/org | |
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/src/main/java/org')
7 files changed, 440 insertions, 148 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); } + } |