Add support for server-side SSL context factory

Change-Id: I2fa64c71f55f1abfdeb4a2323c5456475d87fdd1
Issue-ID: DCAEGEN2-1069
Signed-off-by: Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
diff --git a/security/ssl/pom.xml b/security/ssl/pom.xml
index ecccd76..0100d65 100644
--- a/security/ssl/pom.xml
+++ b/security/ssl/pom.xml
@@ -6,14 +6,14 @@
   <parent>
     <groupId>org.onap.dcaegen2.services.sdk.security</groupId>
     <artifactId>dcaegen2-services-sdk-security</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.1.2-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
   </parent>
 
   <artifactId>ssl</artifactId>
-  <version>1.1.1-SNAPSHOT</version>
 
-  <name>SSL</name>
-  <description>Common SSL-related Classes Library</description>
+  <name>Security :: SSL</name>
+  <description>Common functionality to handle SSL/TLS in Netty-based applications</description>
   <packaging>jar</packaging>
 
   <dependencies>
diff --git a/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/KeyStoreTypes.java b/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/KeyStoreTypes.java
new file mode 100644
index 0000000..61e551e
--- /dev/null
+++ b/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/KeyStoreTypes.java
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 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.dcaegen2.services.sdk.security.ssl;
+
+import io.vavr.collection.HashSet;
+import io.vavr.collection.Set;
+import io.vavr.control.Option;
+import java.nio.file.Path;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since 1.1.1
+ */
+final class KeyStoreTypes {
+    static final String TYPE_JKS = "jks";
+    static final String TYPE_PKCS12 = "pkcs12";
+    private static final Set<String> JKS_EXTENSIONS = HashSet.of(TYPE_JKS);
+    private static final Set<String> PKCS12_EXTENSIONS = HashSet.of(TYPE_PKCS12, "p12");
+
+    private KeyStoreTypes() {}
+
+    static Option<String> inferTypeFromExtension(Path filePath) {
+        return extension(filePath.toString())
+                .flatMap(KeyStoreTypes::typeForExtension);
+    }
+
+    private static Option<String> extension(String filePath) {
+        final int dotIndex = filePath.lastIndexOf('.');
+        return dotIndex < 0 || dotIndex + 1 >= filePath.length()
+                ? Option.none()
+                : Option.of(filePath.substring(dotIndex + 1).toLowerCase());
+    }
+
+    private static Option<String> typeForExtension(String extension) {
+        if (JKS_EXTENSIONS.contains(extension)) {
+            return Option.of(TYPE_JKS);
+        } else if (PKCS12_EXTENSIONS.contains(extension)) {
+            return Option.of(TYPE_PKCS12);
+        } else {
+            return Option.none();
+        }
+    }
+}
diff --git a/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SecurityKeys.java b/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SecurityKeys.java
index 05c3c47..244e33c 100644
--- a/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SecurityKeys.java
+++ b/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SecurityKeys.java
@@ -20,7 +20,6 @@
 
 package org.onap.dcaegen2.services.sdk.security.ssl;
 
-import java.nio.file.Path;
 import org.immutables.value.Value;
 
 /**
@@ -29,9 +28,10 @@
  */
 @Value.Immutable
 public interface SecurityKeys {
-    Path keyStore();
+
+    SecurityKeysStore keyStore();
     Password keyStorePassword();
 
-    Path trustStore();
+    SecurityKeysStore trustStore();
     Password trustStorePassword();
 }
diff --git a/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SecurityKeysStore.java b/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SecurityKeysStore.java
new file mode 100644
index 0000000..0ebfc45
--- /dev/null
+++ b/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SecurityKeysStore.java
@@ -0,0 +1,53 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 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.dcaegen2.services.sdk.security.ssl;
+
+import java.nio.file.Path;
+import org.immutables.value.Value;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since 1.1.1
+ */
+@Value.Immutable
+public interface SecurityKeysStore {
+    /**
+     * Stores the file path of the key store. It should contain data in format specified by {@link #type()}.
+     *
+     * @return key store path
+     */
+    @Value.Parameter
+    Path path();
+
+    /**
+     * Type of the key store. Can be anything supported by the JVM, eg. {@code jks} or {@code pkcs12}.
+     *
+     * If not set it will be guessed from the {@link #path()}. {@link IllegalStateException} will be thrown if it will
+     * not be possible.
+     *
+     * @return key store type
+     */
+    @Value.Default
+    default String type() {
+        return KeyStoreTypes.inferTypeFromExtension(path())
+                .getOrElseThrow(() -> new IllegalStateException("Could not determine key store type by file name"));
+    }
+}
diff --git a/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SslFactory.java b/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SslFactory.java
index 15739eb..1f3f4cf 100644
--- a/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SslFactory.java
+++ b/security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SslFactory.java
@@ -36,54 +36,72 @@
 public class SslFactory {
 
     /**
-     * Function for creating secure ssl context.
+     * Creates Netty SSL <em>client</em> context using provided security keys.
      *
      * @param keys - Security keys to be used
      * @return configured SSL context
      */
-    public Try<SslContext> createSecureContext(final SecurityKeys keys) {
-        final Try<KeyManagerFactory> keyManagerFactory =
-                keyManagerFactory(keys.keyStore(), keys.keyStorePassword());
-        final Try<TrustManagerFactory> trustManagerFactory =
-                trustManagerFactory(keys.trustStore(), keys.trustStorePassword());
-
+    public Try<SslContext> createSecureClientContext(final SecurityKeys keys) {
         return Try.success(SslContextBuilder.forClient())
-                .flatMap(ctx -> keyManagerFactory.map(ctx::keyManager))
-                .flatMap(ctx -> trustManagerFactory.map(ctx::trustManager))
+                .flatMap(ctx -> keyManagerFactory(keys).map(ctx::keyManager))
+                .flatMap(ctx -> trustManagerFactory(keys).map(ctx::trustManager))
                 .mapTry(SslContextBuilder::build);
     }
 
-    private Try<KeyManagerFactory> keyManagerFactory(Path path, Password password) {
+    /**
+     * Creates Netty SSL <em>server</em> context using provided security keys.
+     *
+     * @param keys - Security keys to be used
+     * @return configured SSL context
+     */
+    public Try<SslContext> createSecureServerContext(final SecurityKeys keys) {
+        return keyManagerFactory(keys)
+                .map(SslContextBuilder::forServer)
+                .flatMap(ctx -> trustManagerFactory(keys).map(ctx::trustManager))
+                .mapTry(SslContextBuilder::build);
+    }
+
+    /**
+     * Function for creating insecure SSL context.
+     *
+     * @return configured insecure ssl context
+     * @deprecated Do not use in production. Will trust anyone.
+     */
+    @Deprecated
+    public Try<SslContext> createInsecureClientContext() {
+        return Try.success(SslContextBuilder.forClient())
+                .map(ctx -> ctx.trustManager(InsecureTrustManagerFactory.INSTANCE))
+                .mapTry(SslContextBuilder::build);
+    }
+
+    private Try<TrustManagerFactory> trustManagerFactory(SecurityKeys keys) {
+        return trustManagerFactory(keys.trustStore(), keys.trustStorePassword());
+    }
+
+    private Try<KeyManagerFactory> keyManagerFactory(SecurityKeys keys) {
+        return keyManagerFactory(keys.keyStore(), keys.keyStorePassword());
+    }
+
+    private Try<KeyManagerFactory> keyManagerFactory(SecurityKeysStore store, Password password) {
         return password.useChecked(passwordChars -> {
             KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
-            kmf.init(loadKeyStoreFromFile(path, passwordChars), passwordChars);
+            kmf.init(loadKeyStoreFromFile(store, passwordChars), passwordChars);
             return kmf;
         });
     }
 
-    private Try<TrustManagerFactory> trustManagerFactory(Path path, Password password) {
+    private Try<TrustManagerFactory> trustManagerFactory(SecurityKeysStore store, Password password) {
         return password.useChecked(passwordChars -> {
             TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-            tmf.init(loadKeyStoreFromFile(path, passwordChars));
+            tmf.init(loadKeyStoreFromFile(store, passwordChars));
             return tmf;
         });
     }
 
-    private KeyStore loadKeyStoreFromFile(Path path, char[] keyStorePassword)
+    private KeyStore loadKeyStoreFromFile(SecurityKeysStore store, char[] keyStorePassword)
             throws GeneralSecurityException, IOException {
-        KeyStore ks = KeyStore.getInstance("pkcs12");
-        ks.load(Files.newInputStream(path, StandardOpenOption.READ), keyStorePassword);
+        KeyStore ks = KeyStore.getInstance(store.type());
+        ks.load(Files.newInputStream(store.path(), StandardOpenOption.READ), keyStorePassword);
         return ks;
     }
-
-    /**
-     * Function for creating insecure ssl context.
-     *
-     * @return configured insecure ssl context
-     */
-    public Try<SslContext> createInsecureContext() {
-        return Try.success(SslContextBuilder.forClient())
-            .map(ctx -> ctx.trustManager(InsecureTrustManagerFactory.INSTANCE))
-            .mapTry(SslContextBuilder::build);
-    }
 }
diff --git a/security/ssl/src/test/java/org/onap/dcaegen2/services/sdk/security/ssl/KeyStoreTypesTest.java b/security/ssl/src/test/java/org/onap/dcaegen2/services/sdk/security/ssl/KeyStoreTypesTest.java
new file mode 100644
index 0000000..ab2aa77
--- /dev/null
+++ b/security/ssl/src/test/java/org/onap/dcaegen2/services/sdk/security/ssl/KeyStoreTypesTest.java
@@ -0,0 +1,76 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 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.dcaegen2.services.sdk.security.ssl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.vavr.control.Option;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since February 2019
+ */
+class KeyStoreTypesTest {
+
+    @Test
+    void guessType_shouldReturnExtension_forP12() {
+        final Option<String> result = callGuessTypeWithFileName("file.p12");
+        assertThat(result.get()).isEqualTo(KeyStoreTypes.TYPE_PKCS12);
+    }
+
+    @Test
+    void guessType_shouldReturnExtension_forPkcs12() {
+        final Option<String> result = callGuessTypeWithFileName("file.pkcs12");
+        assertThat(result.get()).isEqualTo(KeyStoreTypes.TYPE_PKCS12);
+    }
+
+    @Test
+    void guessType_shouldReturnExtension_forJks() {
+        final Option<String> result = callGuessTypeWithFileName("file.jks");
+        assertThat(result.get()).isEqualTo(KeyStoreTypes.TYPE_JKS);
+    }
+
+    @Test
+    void guessType_shouldReturnExtension_ignoringCase() {
+        final Option<String> result = callGuessTypeWithFileName("file.PKCS12");
+        assertThat(result.get()).isEqualTo(KeyStoreTypes.TYPE_PKCS12);
+    }
+
+    @Test
+    void guessType_shouldReturnNone_whenFileDoesNotHaveExtension() {
+        final Option<String> result = callGuessTypeWithFileName("file");
+        assertThat(result.isEmpty()).isTrue();
+    }
+
+    @Test
+    void guessType_shouldReturnNone_whenFileEndsWithDot() {
+        final Option<String> result = callGuessTypeWithFileName("file.");
+        assertThat(result.isEmpty()).isTrue();
+    }
+
+    private Option<String> callGuessTypeWithFileName(String fileName) {
+        final Path path = Paths.get("/", "tmp", fileName);
+        return KeyStoreTypes.inferTypeFromExtension(path);
+    }
+}
\ No newline at end of file