Implementation of CSR procedure
authorpwielebs <piotr.wielebski@nokia.com>
Wed, 26 Feb 2020 09:15:19 +0000 (10:15 +0100)
committerpwielebs <piotr.wielebski@nokia.com>
Thu, 27 Feb 2020 14:36:18 +0000 (15:36 +0100)
Issue-ID: AAF-996

Signed-off-by: pwielebs <piotr.wielebski@nokia.com>
Change-Id: Id294259b292e2d355be9c70ab4f7eb7017c8c150

certServiceClient/src/main/java/org/onap/aaf/certservice/client/CertServiceClient.java
certServiceClient/src/main/java/org/onap/aaf/certservice/client/api/ExitCode.java
certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/CsrFactory.java [new file with mode: 0644]
certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/EncryptionAlgorithmConstants.java
certServiceClient/src/main/java/org/onap/aaf/certservice/client/certification/exception/CsrGenerationException.java [new file with mode: 0644]
certServiceClient/src/main/java/org/onap/aaf/certservice/client/configuration/model/CsrConfiguration.java
certServiceClient/src/test/java/org/onap/aaf/certservice/client/certification/CsrFactoryTest.java [new file with mode: 0644]
certServiceClient/src/test/java/org/onap/aaf/certservice/client/configuration/model/CsrConfigurationFactoryTest.java

index f886784..3e8f73e 100644 (file)
@@ -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());
     }
-
 }
index 295738f..45f2c40 100644 (file)
@@ -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 (file)
index 0000000..f936636
--- /dev/null
@@ -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));
+    }
+}
index 2afdbee..96b3650 100644 (file)
  * 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 (file)
index 0000000..c1d4afd
--- /dev/null
@@ -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();
+    }
+}
index 30caf42..aaaf10f 100644 (file)
@@ -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 (file)
index 0000000..16b5e03
--- /dev/null
@@ -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();
+     }
+}
+
index d6bf431..695a9e4 100644 (file)
@@ -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);