From 13b39127c1c91d7c05c67ea2c14220c8f992cba5 Mon Sep 17 00:00:00 2001 From: "andre.schmid" Date: Thu, 28 Jan 2021 17:53:22 +0000 Subject: ETSI SOL007 3.3.1 package security option 2 Change-Id: I4e021c517449e6ddf11571c02d0b4bdbc93e7c1e Issue-ID: SDC-2614 Signed-off-by: andre.schmid --- common-be/pom.xml | 7 + .../be/csar/security/CertificateManagerImpl.java | 167 +++++++++++++++++++++ .../sdc/be/csar/security/PrivateKeyReaderImpl.java | 53 +++++++ .../security/Sha256WithRsaCmsContentSigner.java | 98 ++++++++++++ .../be/csar/security/X509CertificateReader.java | 57 +++++++ .../be/csar/security/api/CertificateManager.java | 29 ++++ .../be/csar/security/api/CertificateReader.java | 34 +++++ .../sdc/be/csar/security/api/CmsContentSigner.java | 32 ++++ .../sdc/be/csar/security/api/PrivateKeyReader.java | 38 +++++ .../csar/security/api/model/CertificateInfo.java | 46 ++++++ .../exception/CertificateNotFoundException.java | 27 ++++ .../security/exception/CmsSignatureException.java | 27 ++++ .../exception/LoadCertificateException.java | 27 ++++ .../exception/LoadPrivateKeyException.java | 27 ++++ .../exception/UnsupportedKeyFormatException.java | 27 ++++ .../csar/security/model/CertificateInfoImpl.java | 70 +++++++++ .../csar/security/CertificateManagerImplTest.java | 141 +++++++++++++++++ .../be/csar/security/PrivateKeyReaderImplTest.java | 95 ++++++++++++ .../Sha256WithRsaCmsContentSignerTest.java | 119 +++++++++++++++ .../csar/security/X509CertificateReaderTest.java | 81 ++++++++++ .../security/model/CertificateInfoImplTest.java | 69 +++++++++ .../resources/certificateManager/fakeCert1.cert | 0 .../resources/certificateManager/fakeCert1.key | 0 .../resources/certificateManager/fakeCert2.cert | 0 .../resources/certificateManager/fakeCert3.key | 0 .../certificateManager/realCert/realCert1.cert | 24 +++ .../certificateManager/realCert/realCert1.key | 28 ++++ .../certificateManager/signerTest/fileToSign.txt | 1 + .../org.mockito.plugins.MockMaker | 1 + 29 files changed, 1325 insertions(+) create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/CertificateManagerImpl.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/PrivateKeyReaderImpl.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/Sha256WithRsaCmsContentSigner.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/X509CertificateReader.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CertificateManager.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CertificateReader.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CmsContentSigner.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/PrivateKeyReader.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/model/CertificateInfo.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/CertificateNotFoundException.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/CmsSignatureException.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/LoadCertificateException.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/LoadPrivateKeyException.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/UnsupportedKeyFormatException.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/security/model/CertificateInfoImpl.java create mode 100644 common-be/src/test/java/org/openecomp/sdc/be/csar/security/CertificateManagerImplTest.java create mode 100644 common-be/src/test/java/org/openecomp/sdc/be/csar/security/PrivateKeyReaderImplTest.java create mode 100644 common-be/src/test/java/org/openecomp/sdc/be/csar/security/Sha256WithRsaCmsContentSignerTest.java create mode 100644 common-be/src/test/java/org/openecomp/sdc/be/csar/security/X509CertificateReaderTest.java create mode 100644 common-be/src/test/java/org/openecomp/sdc/be/csar/security/model/CertificateInfoImplTest.java create mode 100644 common-be/src/test/resources/certificateManager/fakeCert1.cert create mode 100644 common-be/src/test/resources/certificateManager/fakeCert1.key create mode 100644 common-be/src/test/resources/certificateManager/fakeCert2.cert create mode 100644 common-be/src/test/resources/certificateManager/fakeCert3.key create mode 100644 common-be/src/test/resources/certificateManager/realCert/realCert1.cert create mode 100644 common-be/src/test/resources/certificateManager/realCert/realCert1.key create mode 100644 common-be/src/test/resources/certificateManager/signerTest/fileToSign.txt create mode 100644 common-be/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker (limited to 'common-be') diff --git a/common-be/pom.xml b/common-be/pom.xml index 94da6cf7c2..c4489b0e17 100644 --- a/common-be/pom.xml +++ b/common-be/pom.xml @@ -73,6 +73,13 @@ + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + compile + + org.hamcrest hamcrest diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/CertificateManagerImpl.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/CertificateManagerImpl.java new file mode 100644 index 0000000000..9ec8ea864e --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/CertificateManagerImpl.java @@ -0,0 +1,167 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.Key; +import java.security.Security; +import java.security.cert.Certificate; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.io.FilenameUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.openecomp.sdc.be.csar.security.api.CertificateManager; +import org.openecomp.sdc.be.csar.security.api.CertificateReader; +import org.openecomp.sdc.be.csar.security.api.PrivateKeyReader; +import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo; +import org.openecomp.sdc.be.csar.security.exception.CertificateNotFoundException; +import org.openecomp.sdc.be.csar.security.model.CertificateInfoImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +public class CertificateManagerImpl implements CertificateManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(CertificateManagerImpl.class); + + private final PrivateKeyReader privateKeyReader; + private final CertificateReader certificateReader; + private final Environment environment; + + private Path certificateDirectoryPath; + private File certificateDirectory; + private final Map certificateMap = new HashMap<>(); + + public static final String CERT_DIR_ENV_VARIABLE = "SDC_CERT_DIR"; + + public CertificateManagerImpl(final PrivateKeyReader privateKeyReader, + final CertificateReader certificateReader, + final Environment environment) { + this.certificateReader = certificateReader; + this.privateKeyReader = privateKeyReader; + this.environment = environment; + init(); + } + + private void init() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + + final String certificateDir = environment.getProperty(CERT_DIR_ENV_VARIABLE); + if (certificateDir == null) { + LOGGER.warn("Environment variable '{}' was not provided. Could not load certificates.", CERT_DIR_ENV_VARIABLE); + return; + } + try { + this.certificateDirectoryPath = Paths.get(certificateDir); + } catch (final Exception e) { + LOGGER.error("Invalid path '{}' provided in the environment variable '{}'. Could not load certificates.", + certificateDir, CERT_DIR_ENV_VARIABLE, e); + return; + } + try { + loadCertificateDirectory(); + } catch (final Exception e) { + LOGGER.error("Could not load certificate directory", e); + return; + } + try { + loadCertificates(); + } catch (final Exception e) { + LOGGER.error("Could not load certificates", e); + } + } + + private void loadCertificates() { + final File[] files = certificateDirectory.listFiles(); + if (files == null || files.length == 0) { + LOGGER.warn("Certificate directory is empty. No trusted certificate found."); + return; + } + + final List certFileList = Arrays.stream(files) + .filter(file -> "cert".equals(FilenameUtils.getExtension(file.getName()))) + .collect(Collectors.toList()); + final List keyFileList = Arrays.stream(files) + .filter(file -> "key".equals(FilenameUtils.getExtension(file.getName()))) + .collect(Collectors.toList()); + + if (certFileList.isEmpty()) { + LOGGER.error("Certificate directory is empty. No trusted certificate found."); + return; + } + + certFileList.forEach(certFile -> { + final String baseFileName = FilenameUtils.getBaseName(certFile.getName()); + final Certificate certificate = loadCertificate(certFile); + final Optional keyFileOptional = keyFileList.stream().filter( + keyFile1 -> FilenameUtils.getBaseName(keyFile1.getName()) + .equals(baseFileName)).findFirst(); + keyFileOptional.ifPresentOrElse( + keyFile -> { + final CertificateInfoImpl certificateInfo = + new CertificateInfoImpl(certFile, certificate, keyFile, loadPrivateKey(keyFile)); + if (certificateInfo.isValid()) { + certificateMap.put(baseFileName, certificateInfo); + } + }, + () -> { + final CertificateInfoImpl certificateInfo = new CertificateInfoImpl(certFile, certificate); + if (certificateInfo.isValid()) { + certificateMap.put(baseFileName, new CertificateInfoImpl(certFile, certificate)); + } + } + ); + }); + } + + private void loadCertificateDirectory() { + final File file = certificateDirectoryPath.toFile(); + if (!file.exists() || !file.isDirectory()) { + final String errorMsg = + String.format("Provided certificate path '%s' is not a directory or does not exist", + certificateDirectoryPath); + throw new CertificateNotFoundException(errorMsg); + } + this.certificateDirectory = file; + } + + private Certificate loadCertificate(final File certFile) { + return certificateReader.loadCertificate(certFile); + } + + private Key loadPrivateKey(final File privateKeyFile) { + return privateKeyReader.loadPrivateKey(privateKeyFile); + } + + @Override + public Optional getCertificate(final String certName) { + return Optional.ofNullable(certificateMap.get(certName)); + } +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/PrivateKeyReaderImpl.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/PrivateKeyReaderImpl.java new file mode 100644 index 0000000000..a6ee61d680 --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/PrivateKeyReaderImpl.java @@ -0,0 +1,53 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security; + +import java.io.File; +import java.io.FileReader; +import java.security.Key; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.openecomp.sdc.be.csar.security.api.PrivateKeyReader; +import org.openecomp.sdc.be.csar.security.exception.LoadPrivateKeyException; +import org.openecomp.sdc.be.csar.security.exception.UnsupportedKeyFormatException; +import org.springframework.stereotype.Component; + +@Component +public class PrivateKeyReaderImpl implements PrivateKeyReader { + + @Override + public Key loadPrivateKey(final File privateKeyFile) { + try (final PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile))) { + final Object pemObject = pemParser.readObject(); + final JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME); + if (pemObject instanceof PrivateKeyInfo) { + return converter.getPrivateKey((PrivateKeyInfo) pemObject); + } + } catch (final Exception e) { + final String errorMsg = "Could not load the private key from given file '%s'"; + throw new LoadPrivateKeyException(String.format(errorMsg, privateKeyFile), e); + } + final String errorMsg = "Could not load the private key from given file '%s'. Unsupported format."; + throw new UnsupportedKeyFormatException(String.format(errorMsg, privateKeyFile)); + } + +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/Sha256WithRsaCmsContentSigner.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/Sha256WithRsaCmsContentSigner.java new file mode 100644 index 0000000000..7b7273e810 --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/Sha256WithRsaCmsContentSigner.java @@ -0,0 +1,98 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security; + +import java.io.IOException; +import java.io.StringWriter; +import java.security.Key; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.cert.jcajce.JcaCertStore; +import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSSignedDataGenerator; +import org.bouncycastle.cms.CMSTypedData; +import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; +import org.openecomp.sdc.be.csar.security.api.CmsContentSigner; +import org.openecomp.sdc.be.csar.security.exception.CmsSignatureException; +import org.springframework.stereotype.Component; + +@Component +public class Sha256WithRsaCmsContentSigner implements CmsContentSigner { + + @Override + public byte[] signData(final byte[] data, final Certificate signingCertificate, final Key signingKey) + throws CmsSignatureException { + + final CMSTypedData cmsData = new CMSProcessableByteArray(data); + final JcaCertStore certStore = createCertificateStore(signingCertificate); + try { + final ContentSigner contentSigner + = new JcaContentSignerBuilder("SHA256withRSA") + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build((PrivateKey) signingKey); + + final CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator(); + cmsGenerator.addSignerInfoGenerator( + new JcaSignerInfoGeneratorBuilder( + new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build() + ).build(contentSigner, (X509Certificate) signingCertificate) + ); + cmsGenerator.addCertificates(certStore); + + final CMSSignedData cms = cmsGenerator.generate(cmsData, false); + return cms.getEncoded(); + } catch (final Exception e) { + throw new CmsSignatureException("Could not sign the given data", e); + } + } + + @Override + public String formatToPemSignature(final byte[] signedData) throws CmsSignatureException { + final StringWriter sw = new StringWriter(); + try (final JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(sw)) { + final ContentInfo ci = ContentInfo.getInstance(ASN1Primitive.fromByteArray(signedData)); + jcaPEMWriter.writeObject(ci); + } catch (final IOException e) { + throw new CmsSignatureException("Could not convert signed data to PEM format", e); + } + return sw.toString(); + } + + private JcaCertStore createCertificateStore(final Certificate signingCertificate) throws CmsSignatureException { + try { + return new JcaCertStore(Collections.singletonList(signingCertificate)); + } catch (final CertificateEncodingException e) { + final String errorMsg = String + .format("Could not create certificate store from certificate '%s'", signingCertificate); + throw new CmsSignatureException(errorMsg, e); + } + } + +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/X509CertificateReader.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/X509CertificateReader.java new file mode 100644 index 0000000000..b8e95e7b18 --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/X509CertificateReader.java @@ -0,0 +1,57 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import org.openecomp.sdc.be.csar.security.api.CertificateReader; +import org.openecomp.sdc.be.csar.security.exception.LoadCertificateException; +import org.springframework.stereotype.Component; + +@Component +public class X509CertificateReader implements CertificateReader { + + /** + * Reads X.509 certificate file. + * + * @param certFile the certificate file + * @return the read certificate + * @throws LoadCertificateException when an error has occurred while reading the certificate + */ + @Override + public Certificate loadCertificate(final File certFile) { + try (final FileInputStream fi = new FileInputStream(certFile)) { + return buildCertificate(fi); + } catch (final Exception e) { + final String errorMsg = "Could not load the certificate from given file '%s'"; + throw new LoadCertificateException(String.format(errorMsg, certFile), e); + } + } + + private Certificate buildCertificate(final InputStream certificateInputStream) throws CertificateException { + final CertificateFactory factory = CertificateFactory.getInstance("X.509"); + return factory.generateCertificate(certificateInputStream); + } + +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CertificateManager.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CertificateManager.java new file mode 100644 index 0000000000..53437f399f --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CertificateManager.java @@ -0,0 +1,29 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.api; + +import java.util.Optional; +import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo; + +public interface CertificateManager { + + Optional getCertificate(String certName); + +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CertificateReader.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CertificateReader.java new file mode 100644 index 0000000000..4c32fa1cee --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CertificateReader.java @@ -0,0 +1,34 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.api; + +import java.io.File; +import java.security.cert.Certificate; + +public interface CertificateReader { + + /** + * Reads a certificate file. + * + * @param certFile the certificate file + * @return the read certificate + */ + Certificate loadCertificate(File certFile); +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CmsContentSigner.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CmsContentSigner.java new file mode 100644 index 0000000000..37bd988e50 --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/CmsContentSigner.java @@ -0,0 +1,32 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.api; + +import java.security.Key; +import java.security.cert.Certificate; +import org.openecomp.sdc.be.csar.security.exception.CmsSignatureException; + +public interface CmsContentSigner { + + byte[] signData(byte[] data, Certificate signingCertificate, Key signingKey) + throws CmsSignatureException; + + String formatToPemSignature(byte[] signedData) throws CmsSignatureException; +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/PrivateKeyReader.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/PrivateKeyReader.java new file mode 100644 index 0000000000..3e8c406b74 --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/PrivateKeyReader.java @@ -0,0 +1,38 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.api; + +import java.io.File; +import java.security.Key; +import org.openecomp.sdc.be.csar.security.exception.LoadPrivateKeyException; +import org.openecomp.sdc.be.csar.security.exception.UnsupportedKeyFormatException; + +public interface PrivateKeyReader { + + /** + * Reads a given private file. + * + * @param privateKeyFile the private key file + * @return the read private key + * @throws LoadPrivateKeyException when an error has occurred while reading the private key + * @throws UnsupportedKeyFormatException when the private key is not supported + */ + Key loadPrivateKey(final File privateKeyFile); +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/model/CertificateInfo.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/model/CertificateInfo.java new file mode 100644 index 0000000000..5b234cc661 --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/api/model/CertificateInfo.java @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.api.model; + +import java.io.File; +import java.security.Key; +import java.security.cert.Certificate; + +public interface CertificateInfo { + + String getName(); + + File getCertificateFile(); + + Certificate getCertificate(); + + File getPrivateKeyFile(); + + Key getPrivateKey(); + + /** + * Check if the certificate is valid. + * + * @return {@code true} if the certificate is valid. {@code false} otherwise. + * @throws UnsupportedOperationException when the certificate is not supported + */ + boolean isValid(); + +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/CertificateNotFoundException.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/CertificateNotFoundException.java new file mode 100644 index 0000000000..a2175f379c --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/CertificateNotFoundException.java @@ -0,0 +1,27 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.exception; + +public class CertificateNotFoundException extends RuntimeException { + + public CertificateNotFoundException(final String message) { + super(message); + } +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/CmsSignatureException.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/CmsSignatureException.java new file mode 100644 index 0000000000..6bc49d6e4d --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/CmsSignatureException.java @@ -0,0 +1,27 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.exception; + +public class CmsSignatureException extends Exception { + + public CmsSignatureException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/LoadCertificateException.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/LoadCertificateException.java new file mode 100644 index 0000000000..3cd10628f5 --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/LoadCertificateException.java @@ -0,0 +1,27 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.exception; + +public class LoadCertificateException extends RuntimeException { + + public LoadCertificateException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/LoadPrivateKeyException.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/LoadPrivateKeyException.java new file mode 100644 index 0000000000..00681bc842 --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/LoadPrivateKeyException.java @@ -0,0 +1,27 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.exception; + +public class LoadPrivateKeyException extends RuntimeException { + + public LoadPrivateKeyException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/UnsupportedKeyFormatException.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/UnsupportedKeyFormatException.java new file mode 100644 index 0000000000..d30f6f274a --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/exception/UnsupportedKeyFormatException.java @@ -0,0 +1,27 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.exception; + +public class UnsupportedKeyFormatException extends RuntimeException { + + public UnsupportedKeyFormatException(final String message) { + super(message); + } +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/security/model/CertificateInfoImpl.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/model/CertificateInfoImpl.java new file mode 100644 index 0000000000..f7b2fafb3c --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/security/model/CertificateInfoImpl.java @@ -0,0 +1,70 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.model; + +import java.io.File; +import java.security.Key; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import lombok.Getter; +import org.apache.commons.io.FilenameUtils; +import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Getter +public class CertificateInfoImpl implements CertificateInfo { + + private static final Logger LOGGER = LoggerFactory.getLogger(CertificateInfoImpl.class); + + private final String name; + private final File certificateFile; + private final Certificate certificate; + private File privateKeyFile; + private Key privateKey; + + public CertificateInfoImpl(final File certificateFile, final Certificate certificate) { + this.certificateFile = certificateFile; + this.certificate = certificate; + this.name = FilenameUtils.getBaseName(certificateFile.getName()); + } + + public CertificateInfoImpl(final File certificateFile, final Certificate certificate, + final File privateKeyFile, final Key privateKey) { + this(certificateFile, certificate); + this.privateKeyFile = privateKeyFile; + this.privateKey = privateKey; + } + + @Override + public boolean isValid() { + if("X.509".equals(certificate.getType())) { + try { + ((X509Certificate) certificate).checkValidity(); + return true; + } catch (final Exception e) { + LOGGER.warn("Invalid certificate '{}'", certificateFile.getAbsolutePath(), e); + return false; + } + } + throw new UnsupportedOperationException(String.format("Certificate type '%s' not supported", certificate.getType())); + } + +} diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/security/CertificateManagerImplTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/CertificateManagerImplTest.java new file mode 100644 index 0000000000..6287c0e85b --- /dev/null +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/CertificateManagerImplTest.java @@ -0,0 +1,141 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.openecomp.sdc.be.csar.security.CertificateManagerImpl.CERT_DIR_ENV_VARIABLE; + +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Optional; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openecomp.sdc.be.csar.security.api.CertificateReader; +import org.openecomp.sdc.be.csar.security.api.PrivateKeyReader; +import org.openecomp.sdc.be.csar.security.api.model.CertificateInfo; +import org.springframework.core.env.Environment; + +class CertificateManagerImplTest { + + @Mock + private Environment environment; + @Mock + private PrivateKeyReader privateKeyReader; + @Mock + private CertificateReader certificateReader; + @Mock + private X509Certificate certificateMock; + private CertificateManagerImpl certificateManager; + + static Path certificateFolderPath; + + @BeforeAll + static void beforeAll() { + final String resourceFolder = "certificateManager"; + final URL certificateManager = CertificateManagerImplTest.class.getClassLoader().getResource(resourceFolder); + if (certificateManager == null) { + fail("Could not find resource folder " + resourceFolder); + } + certificateFolderPath = Paths.get(certificateManager.getPath()); + } + + @BeforeEach + void setUp() throws CertificateNotYetValidException, CertificateExpiredException { + MockitoAnnotations.initMocks(this); + when(environment.getProperty(CERT_DIR_ENV_VARIABLE)).thenReturn(certificateFolderPath.toString()); + when(certificateMock.getType()).thenReturn("X.509"); + doNothing().when(certificateMock).checkValidity(); + when(certificateReader.loadCertificate(ArgumentMatchers.any())).thenReturn(certificateMock); + certificateManager = new CertificateManagerImpl(privateKeyReader, certificateReader, environment); + } + + @Test + void getCertificateSuccessTest() { + final String certificateName = "fakeCert1"; + final Optional certificateOpt = certificateManager.getCertificate(certificateName); + assertThat(certificateOpt.isPresent(), is(true)); + final CertificateInfo certificateInfo = certificateOpt.get(); + assertThat(certificateInfo.getName(), is(certificateName)); + assertThat(certificateInfo.getPrivateKeyFile(), is(notNullValue())); + assertThat(certificateInfo.getPrivateKeyFile().getAbsolutePath(), + is(certificateFolderPath.resolve(certificateName + ".key").toString())); + assertThat(certificateInfo.getCertificateFile(), is(notNullValue())); + assertThat(certificateInfo.getCertificateFile().getAbsolutePath(), + is(certificateFolderPath.resolve(certificateName + ".cert").toString())); + } + + @Test + void initCertificateSuccessTest() { + final String certificateName1 = "fakeCert1"; + final String certificateName2 = "fakeCert2"; + final String certificateName3 = "fakeCert3"; + assertThat("Certificate " + certificateName1 + " should be present", + certificateManager.getCertificate(certificateName1).isPresent(), is(true)); + assertThat("Certificate " + certificateName2 + " should be present", + certificateManager.getCertificate(certificateName2).isPresent(), is(true)); + assertThat("Certificate " + certificateName3 + " should not be present", + certificateManager.getCertificate(certificateName3).isEmpty(), is(true)); + } + + @Test + void invalidCertificateFolderTest() { + final String certificateName1 = "fakeCert1"; + when(environment.getProperty(CERT_DIR_ENV_VARIABLE)).thenReturn("/an/invalid/folder"); + final CertificateManagerImpl certificateManager = + new CertificateManagerImpl(privateKeyReader, certificateReader, environment); + assertThat("Certificate " + certificateName1 + " should be present", + certificateManager.getCertificate(certificateName1).isPresent(), is(false)); + } + + @Test + void noEnvironmentVariableConfiguredTest() { + final String certificateName1 = "fakeCert1"; + when(environment.getProperty(CERT_DIR_ENV_VARIABLE)).thenReturn(null); + final CertificateManagerImpl certificateManager = + new CertificateManagerImpl(privateKeyReader, certificateReader, environment); + assertThat("Certificate " + certificateName1 + " should be present", + certificateManager.getCertificate(certificateName1).isPresent(), is(false)); + } + + @Test + void loadCertificateExceptionTest() { + final String certificateName1 = "fakeCert1"; + when(certificateReader.loadCertificate(any())).thenThrow(new RuntimeException()); + final CertificateManagerImpl certificateManager = + new CertificateManagerImpl(privateKeyReader, certificateReader, environment); + assertThat("Certificate " + certificateName1 + " should be present", + certificateManager.getCertificate(certificateName1).isPresent(), is(false)); + } + +} \ No newline at end of file diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/security/PrivateKeyReaderImplTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/PrivateKeyReaderImplTest.java new file mode 100644 index 0000000000..7bd44cf9c1 --- /dev/null +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/PrivateKeyReaderImplTest.java @@ -0,0 +1,95 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.Key; +import java.security.Security; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openecomp.sdc.be.csar.security.exception.LoadPrivateKeyException; +import org.openecomp.sdc.be.csar.security.exception.UnsupportedKeyFormatException; + +class PrivateKeyReaderImplTest { + + private PrivateKeyReaderImpl privateKeyReader; + + @BeforeEach + void setUp() { + privateKeyReader = new PrivateKeyReaderImpl(); + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + @AfterEach + void tearDown() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) != null) { + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); + } + } + + @Test + void loadPrivateKeySuccessTest() { + final Path certPath = Paths.get("certificateManager", "realCert", "realCert1.key"); + final URL resource = getClass().getClassLoader().getResource(certPath.toString()); + if (resource == null) { + fail("Could not find resource " + certPath.toString()); + } + final Key privateKey = privateKeyReader.loadPrivateKey(new File(resource.getPath())); + assertNotNull(privateKey); + } + + @Test + void loadInvalidKeyFilePathTest() { + final String invalidFilePath = "aaaa"; + final File keyFile = new File(invalidFilePath); + final LoadPrivateKeyException actualException = assertThrows(LoadPrivateKeyException.class, + () -> privateKeyReader.loadPrivateKey(keyFile)); + assertThat(actualException.getMessage(), + is(String.format("Could not load the private key from given file '%s'", invalidFilePath))); + } + + @Test + void loadInvalidKeyFileTest() { + final Path certPath = Paths.get("certificateManager", "fakeCert1.key"); + final URL resource = getClass().getClassLoader().getResource(certPath.toString()); + if (resource == null) { + fail("Could not find resource " + certPath.toString()); + } + final File keyFile = new File(resource.getPath()); + final UnsupportedKeyFormatException actualException = assertThrows(UnsupportedKeyFormatException.class, + () -> privateKeyReader.loadPrivateKey(keyFile)); + assertThat(actualException.getMessage(), + is(String.format("Could not load the private key from given file '%s'. Unsupported format.", + resource.getPath()))); + } +} \ No newline at end of file diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/security/Sha256WithRsaCmsContentSignerTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/Sha256WithRsaCmsContentSignerTest.java new file mode 100644 index 0000000000..2f0031d6e1 --- /dev/null +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/Sha256WithRsaCmsContentSignerTest.java @@ -0,0 +1,119 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.SignerInformation; +import org.bouncycastle.cms.SignerInformationStore; +import org.bouncycastle.cms.SignerInformationVerifier; +import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openecomp.sdc.be.csar.security.api.CertificateReader; +import org.openecomp.sdc.be.csar.security.api.PrivateKeyReader; +import org.openecomp.sdc.be.csar.security.exception.CmsSignatureException; + +class Sha256WithRsaCmsContentSignerTest { + + private Sha256WithRsaCmsContentSigner cmsContentSigner; + private PrivateKeyReader privateKeyReader; + private CertificateReader certificateReader; + + private static final Path testFilesPath = Path.of("certificateManager", "signerTest"); + private static final Path certFilesPath = Path.of("certificateManager", "realCert"); + + @BeforeEach + void setUp() { + Security.addProvider(new BouncyCastleProvider()); + cmsContentSigner = new Sha256WithRsaCmsContentSigner(); + privateKeyReader = new PrivateKeyReaderImpl(); + certificateReader = new X509CertificateReader(); + } + + @AfterEach + void tearDown() { + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); + } + + @Test + void signDataSuccessTest() throws OperatorCreationException, CMSException, IOException, CmsSignatureException { + final File certFile = getResourceFile(certFilesPath.resolve("realCert1.cert")); + final File keyFile = getResourceFile(certFilesPath.resolve("realCert1.key")); + final File fileToSign = getResourceFile(testFilesPath.resolve("fileToSign.txt")); + final Key privateKey = privateKeyReader.loadPrivateKey(keyFile); + final Certificate certificate = certificateReader.loadCertificate(certFile); + final byte[] actualSignatureBytes = cmsContentSigner + .signData(Files.readAllBytes(fileToSign.toPath()), certificate, privateKey); + + assertTrue(verifySignature(Files.readAllBytes(fileToSign.toPath()), actualSignatureBytes, + (X509Certificate) certificate)); + } + + @Test + void signDataInvalidCertAndKeyTest() { + assertThrows(CmsSignatureException.class, + () -> cmsContentSigner.signData(null, null, null)); + } + + private boolean verifySignature(byte[] contentBytes, byte[] signatureBytes, X509Certificate certificate) + throws CMSException, OperatorCreationException { + + final CMSSignedData cms = new CMSSignedData(new CMSProcessableByteArray(contentBytes), signatureBytes); + final SignerInformationStore signers = cms.getSignerInfos(); + final SignerInformationVerifier signerInformationVerifier = + new JcaSimpleSignerInfoVerifierBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(certificate); + for (final SignerInformation signer : signers.getSigners()) { + if (!signer.verify(signerInformationVerifier)) { + return false; + } + } + + return true; + } + + private File getResourceFile(final Path testResourcePath) { + final URL resource = getClass().getClassLoader().getResource(testResourcePath.toString()); + if (resource == null) { + fail("Could not load the file " + testResourcePath.toString()); + } + + return new File(resource.getPath()); + } + +} \ No newline at end of file diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/security/X509CertificateReaderTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/X509CertificateReaderTest.java new file mode 100644 index 0000000000..3235739780 --- /dev/null +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/X509CertificateReaderTest.java @@ -0,0 +1,81 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.cert.Certificate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openecomp.sdc.be.csar.security.exception.LoadCertificateException; + +class X509CertificateReaderTest { + + private X509CertificateReader certificateReader; + + @BeforeEach + void setUp() { + certificateReader = new X509CertificateReader(); + } + + @Test + void loadCertificateSuccessTest() { + final Path certPath = Paths.get("certificateManager", "realCert", "realCert1.cert"); + final URL resource = getClass().getClassLoader().getResource(certPath.toString()); + if (resource == null) { + fail("Could not find resource " + certPath.toString()); + } + final Certificate certificate = certificateReader.loadCertificate(new File(resource.getPath())); + assertNotNull(certificate); + } + + @Test + void loadInvalidCertificateFilePathTest() { + final String invalidFilePath = "aaaa"; + final File certFile = new File(invalidFilePath); + final LoadCertificateException actualException = assertThrows(LoadCertificateException.class, + () -> certificateReader.loadCertificate(certFile)); + assertThat(actualException.getMessage(), + is(String.format("Could not load the certificate from given file '%s'", invalidFilePath))); + } + + @Test + void loadInvalidCertificateFileTest() { + final Path certPath = Paths.get("certificateManager", "fakeCert1.cert"); + System.out.println(certPath.toString()); + final URL resource = getClass().getClassLoader().getResource(certPath.toString()); + if (resource == null) { + fail("Could not find resource " + certPath.toString()); + } + final File certFile = new File(resource.getPath()); + final LoadCertificateException actualException = assertThrows(LoadCertificateException.class, + () -> certificateReader.loadCertificate(certFile)); + assertThat(actualException.getMessage(), + is(String.format("Could not load the certificate from given file '%s'", resource.getPath()))); + } +} \ No newline at end of file diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/security/model/CertificateInfoImplTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/model/CertificateInfoImplTest.java new file mode 100644 index 0000000000..6b094130b0 --- /dev/null +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/security/model/CertificateInfoImplTest.java @@ -0,0 +1,69 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.security.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class CertificateInfoImplTest { + + @Mock + private X509Certificate certificate; + + @BeforeEach + void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + void isValidTest() throws CertificateNotYetValidException, CertificateExpiredException { + when(certificate.getType()).thenReturn("X.509"); + doNothing().when(certificate).checkValidity(); + final CertificateInfoImpl certificateInfo = new CertificateInfoImpl(new File(""), certificate); + assertTrue(certificateInfo.isValid()); + doThrow(CertificateExpiredException.class).when(certificate).checkValidity(); + assertFalse(certificateInfo.isValid()); + } + + @Test + void unsupportedCertificateTypeTest() { + final String certificateType = "unknown"; + when(certificate.getType()).thenReturn(certificateType); + final CertificateInfoImpl certificateInfo = new CertificateInfoImpl(new File(""), certificate); + final UnsupportedOperationException actualException = + assertThrows(UnsupportedOperationException.class, certificateInfo::isValid); + assertEquals(actualException.getMessage(), + String.format("Certificate type '%s' not supported", certificateType)); + } +} diff --git a/common-be/src/test/resources/certificateManager/fakeCert1.cert b/common-be/src/test/resources/certificateManager/fakeCert1.cert new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common-be/src/test/resources/certificateManager/fakeCert1.key b/common-be/src/test/resources/certificateManager/fakeCert1.key new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common-be/src/test/resources/certificateManager/fakeCert2.cert b/common-be/src/test/resources/certificateManager/fakeCert2.cert new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common-be/src/test/resources/certificateManager/fakeCert3.key b/common-be/src/test/resources/certificateManager/fakeCert3.key new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common-be/src/test/resources/certificateManager/realCert/realCert1.cert b/common-be/src/test/resources/certificateManager/realCert/realCert1.cert new file mode 100644 index 0000000000..ae7d518ab0 --- /dev/null +++ b/common-be/src/test/resources/certificateManager/realCert/realCert1.cert @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIUZqdsy8mtirp4AGnTopg9rb3NXcEwDQYJKoZIhvcNAQEL +BQAwgY8xCzAJBgNVBAYTAkVVMRAwDgYDVQQIDAdOb3doZXJlMRIwEAYDVQQHDAlT +b21ld2hlcmUxDTALBgNVBAoMBEFDTUUxDDAKBgNVBAsMA1NEQzEVMBMGA1UEAwwM +c2RjLm9uYXAub3JnMSYwJAYJKoZIhvcNAQkBFhdvbmFwLXNkY0BsaXN0cy5vbmFw +Lm9yZzAgFw0yMTAyMTYxNDQ2NThaGA8yMDcxMDIwNDE0NDY1OFowgY8xCzAJBgNV +BAYTAkVVMRAwDgYDVQQIDAdOb3doZXJlMRIwEAYDVQQHDAlTb21ld2hlcmUxDTAL +BgNVBAoMBEFDTUUxDDAKBgNVBAsMA1NEQzEVMBMGA1UEAwwMc2RjLm9uYXAub3Jn +MSYwJAYJKoZIhvcNAQkBFhdvbmFwLXNkY0BsaXN0cy5vbmFwLm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBANAWK6fjuOq6Wgxwh6QqK0YB9ahjPRj0 +ik/l86AokwyawpgY/iC2xXw+9Etq91C6plYy8BlbYIcv/+KoiteL4YgME9Hk1lwK +GEceNGwvLKzU5/fTX1BP4qpVi3aB5zCAWa9MhCk+UI/aIeERKFP9XHAx/wuAUq/x +OP4HLxgLuX/A7Trld3RjjaMYOLjbfJjPFN3lrvfQ02fy5DlJfWG3ASTOOEfdHt1g +wfOLuzl2l5R34p+zwQKid+2cyKXYs0owZu7Dz9OTWLVxZcLiYEGz8Y3gmmHVhXy+ +TPpvl5wPEajOQBUMksdOric0dIfKjJCMJqFzILv1dfZCr3843MYN3oMCAwEAAaNT +MFEwHQYDVR0OBBYEFJ6mFarA/5ISm7nZimLshzCH/0F8MB8GA1UdIwQYMBaAFJ6m +FarA/5ISm7nZimLshzCH/0F8MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAGUzF4W1vHfksW9qSYqtc8xuM+lV1WrdOW7pX7JkIihUv3Z4jMqysrU4 +nj2iVBG5dPNm9v5GtaeK1ESnVilcdMSYTj/F34/MXJ0iZy1UR0j0IBSrR7JjbTAD +aYH1sx00hH13U8GMZInnmenmwziOQgBwhhCjLMblUY2vQ4O5MGwZG0VW2e3mUcq1 +HBC69yRMx9jQ+Iof6+rHnCLZXTjcl+65IxXSbKhofvpPJMVXFlmV3TdDdONuvKpS +051z/ISD6/SEWEvF9ZwAnZvJ/5yPqGzKgyC7rp8zJL+N3VLfr+la1D1w7qgoyFTi +drptEMCzLOk4OuM8PX8l2kY0G+gQD1o= +-----END CERTIFICATE----- diff --git a/common-be/src/test/resources/certificateManager/realCert/realCert1.key b/common-be/src/test/resources/certificateManager/realCert/realCert1.key new file mode 100644 index 0000000000..a31579a407 --- /dev/null +++ b/common-be/src/test/resources/certificateManager/realCert/realCert1.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQFiun47jquloM +cIekKitGAfWoYz0Y9IpP5fOgKJMMmsKYGP4gtsV8PvRLavdQuqZWMvAZW2CHL//i +qIrXi+GIDBPR5NZcChhHHjRsLyys1Of3019QT+KqVYt2gecwgFmvTIQpPlCP2iHh +EShT/VxwMf8LgFKv8Tj+By8YC7l/wO065Xd0Y42jGDi423yYzxTd5a730NNn8uQ5 +SX1htwEkzjhH3R7dYMHzi7s5dpeUd+Kfs8EConftnMil2LNKMGbuw8/Tk1i1cWXC +4mBBs/GN4Jph1YV8vkz6b5ecDxGozkAVDJLHTq4nNHSHyoyQjCahcyC79XX2Qq9/ +ONzGDd6DAgMBAAECggEANLj5VK+JIcgXmsFETN72WeWTNZf3WgRTqwzLXpAJOg9Y +MKtccDN+9A0LXrR6dzTjgkGjvfj+CyKpReeITja97PeKagr+GRHhtts7UxHc0uma +4JrosnObLadBD8S4K/zJPHY5oi2MwfX10Y3EVwuByVeRlHtt9/A5jXuKfiAyXuCw +42AT6GUFxntwcKhJSQEcy2B9FwvnF5BDDQKyRVD1McrIj/g7+fiyOpqBXGxk+VYX +6GjYb0kWR+lVexPBP4fgSs6Yx9EroDqzXAolH94NQfb93sOJuawtNVhkEgjE9hec +NumdpHMXhXGP0WI/jhicz4gaBDGyEwuSqUxMnfqNyQKBgQDyFx715F3iz2yfnsUQ +xU7KheUjN0g/vhrcir31L9NuhRpWoBJyRUDeXekuCaDF2ElUXJAdUTMGWDwXxlLx +aa5b8LPBL4pGvNRakfT1TJrViUm3A9wC4EO8l0IIpoYAEt8nstaw4pMEWLOSSKH/ +O2wG+v1h1/Xfd+ruTbmcBn6yLwKBgQDcCuSQoheMZrb6Hq+i+99LF3ZHVqJKUUCI +XRnH3fhwhnMrsR9EshOVRtex0aAdl1GUWCAoVt4ZNom5m7b2qRji0gx8npwA0jxD +CSDUw68Jy3MzQxDMPWNRI38P2r46CslNqCkEGlebbgHEJ0qzp2tIfN25pO6EXfjx +wk9afY5n7QKBgQCG0Wtzgn7qfZs/dTrHsSnEzTYjG3lHzkXFRhqtfbngVY2qajB7 +pKeQbnoaIlB/fYiwy2+SdBLXWLH2h4LPYIwyNWTVk/UMmcIkwh3JsaSUgIUlv6d5 +jo0KbK3ghWQgjGHsCMNY9ITtKbyvHXXh3qS1andLUuphTbXuiihwhIlwDwKBgD9P +QwP6HxxeUTcVrSMPpOdOENHlszv+tLqHTuuaieiWRnzDsWNqeQfyIg0faxoYd3hf +AqGYnL5UWrv0eWfuryJTnRQd7nSuCHihH7kXtDz1NGgDW8nnv7OQqvY80Y6Rm+mk +AGkVyy8FL6zoQS3/dXadto27ToT3JLEqXvqCNX7hAoGAPfNcJtXJXrXh9P3JZqkM +pa8nRqUW0MeRt3v8rLqi4D7J6kW1Rkz5AhjiKkp1EwtE0IbGRI1EPhxT5DCMKlIy +UX7VfP8nf+wb6dLHQ0s4oB20y5J6T7FAkD+50dE6Q/6fAl/2MkE+/R0npKRVhOlc +eXIeBGzSc7p92K1p/vRMOZg= +-----END PRIVATE KEY----- diff --git a/common-be/src/test/resources/certificateManager/signerTest/fileToSign.txt b/common-be/src/test/resources/certificateManager/signerTest/fileToSign.txt new file mode 100644 index 0000000000..cf3472103d --- /dev/null +++ b/common-be/src/test/resources/certificateManager/signerTest/fileToSign.txt @@ -0,0 +1 @@ +This is a file to be signed. diff --git a/common-be/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/common-be/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..ca6ee9cea8 --- /dev/null +++ b/common-be/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file -- cgit 1.2.3-korg