From 9e692d47d60d47c3668ba3281fdca61921a92fcf Mon Sep 17 00:00:00 2001 From: pwielebs Date: Wed, 26 Feb 2020 10:15:19 +0100 Subject: Implementation of CSR procedure Issue-ID: AAF-996 Signed-off-by: pwielebs Change-Id: Id294259b292e2d355be9c70ab4f7eb7017c8c150 --- .../aaf/certservice/client/CertServiceClient.java | 4 +- .../onap/aaf/certservice/client/api/ExitCode.java | 3 +- .../client/certification/CsrFactory.java | 158 +++++++++++++++++++++ .../EncryptionAlgorithmConstants.java | 15 +- .../exception/CsrGenerationException.java | 35 +++++ .../configuration/model/CsrConfiguration.java | 8 +- .../client/certification/CsrFactoryTest.java | 58 ++++++++ .../model/CsrConfigurationFactoryTest.java | 2 +- 8 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/CsrFactory.java create mode 100644 certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/exception/CsrGenerationException.java create mode 100644 certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/CsrFactoryTest.java (limited to 'certServiceClient') diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java index f8867846..3e8f73eb 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java @@ -20,6 +20,7 @@ package org.onap.aaf.certservice.client; import org.onap.aaf.certservice.client.api.ExitableException; +import org.onap.aaf.certservice.client.certification.CsrFactory; import org.onap.aaf.certservice.client.certification.KeyPairFactory; import org.onap.aaf.certservice.client.configuration.EnvsForClient; import org.onap.aaf.certservice.client.configuration.EnvsForCsr; @@ -47,10 +48,11 @@ public class CertServiceClient { ClientConfiguration clientConfiguration = new ClientConfigurationFactory(new EnvsForClient()).create(); CsrConfiguration csrConfiguration = new CsrConfigurationFactory(new EnvsForCsr()).create(); KeyPair keyPair = keyPairFactory.create(); + CsrFactory csrFactory = new CsrFactory(csrConfiguration); + String csr = csrFactory.createEncodedCsr(keyPair); } catch (ExitableException e) { appExitHandler.exit(e.applicationExitCode()); } appExitHandler.exit(SUCCESS_EXIT_CODE.getValue()); } - } diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java index 295738f4..45f2c400 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java @@ -22,7 +22,8 @@ public enum ExitCode { SUCCESS_EXIT_CODE(0), CLIENT_CONFIGURATION_EXCEPTION(1), CSR_CONFIGURATION_EXCEPTION(2), - KEY_PAIR_GENERATION_EXCEPTION(3); + KEY_PAIR_GENERATION_EXCEPTION(3), + CSR_GENERATION_EXCEPTION(4); private final int value; diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/CsrFactory.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/CsrFactory.java new file mode 100644 index 00000000..f936636a --- /dev/null +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/CsrFactory.java @@ -0,0 +1,158 @@ +/*============LICENSE_START======================================================= + * aaf-certservice-client + * ================================================================================ + * Copyright (C) 2020 Nokia. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.aaf.certservice.client.certification; + +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.ExtensionsGenerator; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; + +import org.onap.aaf.certservice.client.certification.exception.CsrGenerationException; +import org.onap.aaf.certservice.client.configuration.model.CsrConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.util.Base64; +import java.util.Optional; + +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.COMMON_NAME; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.COUNTRY; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.LOCATION; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.ORGANIZATION; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.ORGANIZATION_UNIT; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.SIGN_ALGORITHM; +import static org.onap.aaf.certservice.client.certification.EncryptionAlgorithmConstants.STATE; + + +public class CsrFactory { + + private final Logger LOGGER = LoggerFactory.getLogger(CsrFactory.class); + private static final String SANS_DELIMITER = ":"; + private final CsrConfiguration configuration; + + + public CsrFactory(CsrConfiguration configuration) { + this.configuration = configuration; + } + + + public String createEncodedCsr(KeyPair keyPair) throws CsrGenerationException { + PKCS10CertificationRequest request; + String csrParameters = getMandatoryParameters().append(getOptionalParameters()).toString(); + X500Principal subject = new X500Principal(csrParameters); + request = createPKCS10Csr(subject, keyPair); + return encodeToBase64(convertPKC10CsrToPem(request)); + } + + + private StringBuilder getMandatoryParameters() { + return new StringBuilder(String.format("%s=%s, %s=%s, %s=%s, %s=%s", + COMMON_NAME, configuration.getCommonName(), + COUNTRY, configuration.getCountry(), + STATE, configuration.getState(), + ORGANIZATION, configuration.getOrganization())); + } + + private String getOptionalParameters() { + StringBuilder optionalParameters = new StringBuilder(); + Optional.ofNullable(configuration.getOrganizationUnit()) + .filter(CsrFactory::isParameterPresent) + .map(unit -> optionalParameters.append(String.format(", %s=%s", ORGANIZATION_UNIT, unit))); + Optional.ofNullable(configuration.getLocation()) + .filter(CsrFactory::isParameterPresent) + .map(location -> optionalParameters.append(String.format(", %s=%s", LOCATION, location))); + return optionalParameters.toString(); + } + + private PKCS10CertificationRequest createPKCS10Csr(X500Principal subject, KeyPair keyPair) throws CsrGenerationException { + JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic()); + + if (isParameterPresent(configuration.getSans())) { + builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, generateSansExtension()); + } + + return builder.build(getContentSigner(keyPair)); + } + + private ContentSigner getContentSigner(KeyPair keyPair) throws CsrGenerationException { + ContentSigner contentSigner; + try { + contentSigner = new JcaContentSignerBuilder(SIGN_ALGORITHM).build(keyPair.getPrivate()); + } catch (OperatorCreationException e) { + LOGGER.error("Creation of PKCS10Csr failed, exception message: {}", e.getMessage()); + throw new CsrGenerationException(e); + + } + return contentSigner; + } + + private String convertPKC10CsrToPem(PKCS10CertificationRequest request) throws CsrGenerationException { + final StringWriter stringWriter = new StringWriter(); + try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(request); + } catch (IOException e) { + LOGGER.error("Conversion to PEM failed, exception message: {}", e.getMessage()); + throw new CsrGenerationException(e); + } + return stringWriter.toString(); + } + + private Extensions generateSansExtension() throws CsrGenerationException { + ExtensionsGenerator generator = new ExtensionsGenerator(); + try { + generator.addExtension(Extension.subjectAlternativeName, false, createGeneralNames()); + } catch (IOException e) { + LOGGER.error("Generation of SANs parameter failed, exception message: {}", e.getMessage()); + throw new CsrGenerationException(e); + } + return generator.generate(); + } + + private GeneralNames createGeneralNames() { + String[] sansTable = this.configuration.getSans().split(SANS_DELIMITER); + int length = sansTable.length; + GeneralName[] generalNames = new GeneralName[length]; + for (int i = 0; i < length; i++) { + generalNames[i] = new GeneralName(GeneralName.dNSName, sansTable[i]); + } + return new GeneralNames(generalNames); + } + + private static Boolean isParameterPresent(String parameter) { + return parameter != null && !"".equals(parameter); + } + + private static String encodeToBase64(String csrInPem) { + return Base64.getEncoder().encodeToString(csrInPem.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java index 2afdbee0..96b3650c 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java @@ -16,9 +16,22 @@ * limitations under the License. * ============LICENSE_END========================================================= */ + package org.onap.aaf.certservice.client.certification; -public class EncryptionAlgorithmConstants { +public final class EncryptionAlgorithmConstants { + + private EncryptionAlgorithmConstants() {} + public static final String RSA_ENCRYPTION_ALGORITHM = "RSA"; + public static final String SIGN_ALGORITHM = "SHA1withRSA"; public static final int KEY_SIZE = 2048; + + public static final String COMMON_NAME = "CN"; + public static final String ORGANIZATION = "O"; + public static final String ORGANIZATION_UNIT = "OU"; + public static final String LOCATION = "L"; + public static final String STATE = "ST"; + public static final String COUNTRY = "C"; + } diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/exception/CsrGenerationException.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/exception/CsrGenerationException.java new file mode 100644 index 00000000..c1d4afd2 --- /dev/null +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/exception/CsrGenerationException.java @@ -0,0 +1,35 @@ +/*============LICENSE_START======================================================= + * aaf-certservice-client + * ================================================================================ + * Copyright (C) 2020 Nokia. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.aaf.certservice.client.certification.exception; + +import org.onap.aaf.certservice.client.api.ExitCode; +import org.onap.aaf.certservice.client.api.ExitableException; + +public class CsrGenerationException extends ExitableException { + private static final ExitCode EXIT_CODE = ExitCode.CSR_GENERATION_EXCEPTION; + + public CsrGenerationException(Throwable e) { + super(e); + } + + public int applicationExitCode() { + return EXIT_CODE.getValue(); + } +} diff --git a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java index 30caf42a..aaaf10fa 100644 --- a/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java +++ b/certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java @@ -29,7 +29,7 @@ public class CsrConfiguration implements ConfigurationModel { private String country; private String organizationUnit; private String location; - private String subjectAlternativeNames; + private String sans; public String getCommonName() { @@ -86,12 +86,12 @@ public class CsrConfiguration implements ConfigurationModel { return this; } - public String getSubjectAlternativeNames() { - return subjectAlternativeNames; + public String getSans() { + return sans; } public CsrConfiguration setSubjectAlternativeNames(String subjectAlternativeNames) { - this.subjectAlternativeNames = subjectAlternativeNames; + this.sans = subjectAlternativeNames; return this; } } diff --git a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/CsrFactoryTest.java b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/CsrFactoryTest.java new file mode 100644 index 00000000..16b5e03b --- /dev/null +++ b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/CsrFactoryTest.java @@ -0,0 +1,58 @@ +/*============LICENSE_START======================================================= + * aaf-certservice-client + * ================================================================================ + * Copyright (C) 2020 Nokia. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.aaf.certservice.client.certification; + + +import org.junit.jupiter.api.Test; +import org.onap.aaf.certservice.client.certification.exception.CsrGenerationException; +import org.onap.aaf.certservice.client.certification.exception.KeyPairGenerationException; +import org.onap.aaf.certservice.client.configuration.model.CsrConfiguration; + +import java.security.KeyPair; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class CsrFactoryTest { + + CsrConfiguration config = mock(CsrConfiguration.class); + + + + @Test + void createEncodedCsr_shouldSucceedWhenAllFieldsAreSetCorrectly() throws KeyPairGenerationException, CsrGenerationException { + + KeyPair keyPair = + new KeyPairFactory(EncryptionAlgorithmConstants.RSA_ENCRYPTION_ALGORITHM, EncryptionAlgorithmConstants.KEY_SIZE).create(); + + when(config.getCommonName()).thenReturn("onap.org"); + when(config.getSans()).thenReturn("onapexample.com:onapexample.com.pl:onapexample.pl"); + when(config.getCountry()).thenReturn("US"); + when(config.getLocation()).thenReturn("San-Francisco"); + when(config.getOrganization()).thenReturn("Linux-Foundation"); + when(config.getOrganizationUnit()).thenReturn("ONAP"); + when(config.getState()).thenReturn("California"); + + assertThat(new CsrFactory(config).createEncodedCsr(keyPair)).isNotEmpty(); + } +} + diff --git a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java index d6bf431b..695a9e4b 100644 --- a/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java +++ b/certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java @@ -61,7 +61,7 @@ public class CsrConfigurationFactoryTest { // then assertThat(configuration.getCommonName()).isEqualTo(COMMON_NAME_VALID); - assertThat(configuration.getSubjectAlternativeNames()).isEqualTo(SANS_VALID); + assertThat(configuration.getSans()).isEqualTo(SANS_VALID); assertThat(configuration.getCountry()).isEqualTo(COUNTRY_VALID); assertThat(configuration.getLocation()).isEqualTo(LOCATION_VALID); assertThat(configuration.getOrganization()).isEqualTo(ORGANIZATION_VALID); -- cgit 1.2.3-korg