From 6b50f21a75f76ebad011188c42b6406d7c097537 Mon Sep 17 00:00:00 2001 From: Piotr Jaszczyk Date: Mon, 4 Feb 2019 14:53:57 +0100 Subject: Add support for server-side SSL context factory Change-Id: I2fa64c71f55f1abfdeb4a2323c5456475d87fdd1 Issue-ID: DCAEGEN2-1069 Signed-off-by: Piotr Jaszczyk --- security/ssl/pom.xml | 8 +-- .../services/sdk/security/ssl/KeyStoreTypes.java | 61 +++++++++++++++++ .../services/sdk/security/ssl/SecurityKeys.java | 6 +- .../sdk/security/ssl/SecurityKeysStore.java | 53 +++++++++++++++ .../services/sdk/security/ssl/SslFactory.java | 70 ++++++++++++-------- .../sdk/security/ssl/KeyStoreTypesTest.java | 76 ++++++++++++++++++++++ 6 files changed, 241 insertions(+), 33 deletions(-) create mode 100644 security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/KeyStoreTypes.java create mode 100644 security/ssl/src/main/java/org/onap/dcaegen2/services/sdk/security/ssl/SecurityKeysStore.java create mode 100644 security/ssl/src/test/java/org/onap/dcaegen2/services/sdk/security/ssl/KeyStoreTypesTest.java (limited to 'security/ssl') diff --git a/security/ssl/pom.xml b/security/ssl/pom.xml index ecccd767..0100d65b 100644 --- a/security/ssl/pom.xml +++ b/security/ssl/pom.xml @@ -6,14 +6,14 @@ org.onap.dcaegen2.services.sdk.security dcaegen2-services-sdk-security - 1.1.0-SNAPSHOT + 1.1.2-SNAPSHOT + ../pom.xml ssl - 1.1.1-SNAPSHOT - SSL - Common SSL-related Classes Library + Security :: SSL + Common functionality to handle SSL/TLS in Netty-based applications jar 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 00000000..61e551e6 --- /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 Piotr Jaszczyk + * @since 1.1.1 + */ +final class KeyStoreTypes { + static final String TYPE_JKS = "jks"; + static final String TYPE_PKCS12 = "pkcs12"; + private static final Set JKS_EXTENSIONS = HashSet.of(TYPE_JKS); + private static final Set PKCS12_EXTENSIONS = HashSet.of(TYPE_PKCS12, "p12"); + + private KeyStoreTypes() {} + + static Option inferTypeFromExtension(Path filePath) { + return extension(filePath.toString()) + .flatMap(KeyStoreTypes::typeForExtension); + } + + private static Option 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 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 05c3c470..244e33c8 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 @@ import org.immutables.value.Value; */ @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 00000000..0ebfc451 --- /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 Piotr Jaszczyk + * @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 15739eb6..1f3f4cfd 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 @@ import javax.net.ssl.TrustManagerFactory; public class SslFactory { /** - * Function for creating secure ssl context. + * Creates Netty SSL client context using provided security keys. * * @param keys - Security keys to be used * @return configured SSL context */ - public Try createSecureContext(final SecurityKeys keys) { - final Try keyManagerFactory = - keyManagerFactory(keys.keyStore(), keys.keyStorePassword()); - final Try trustManagerFactory = - trustManagerFactory(keys.trustStore(), keys.trustStorePassword()); + public Try createSecureClientContext(final SecurityKeys keys) { + return Try.success(SslContextBuilder.forClient()) + .flatMap(ctx -> keyManagerFactory(keys).map(ctx::keyManager)) + .flatMap(ctx -> trustManagerFactory(keys).map(ctx::trustManager)) + .mapTry(SslContextBuilder::build); + } + /** + * Creates Netty SSL server context using provided security keys. + * + * @param keys - Security keys to be used + * @return configured SSL context + */ + public Try 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 createInsecureClientContext() { return Try.success(SslContextBuilder.forClient()) - .flatMap(ctx -> keyManagerFactory.map(ctx::keyManager)) - .flatMap(ctx -> trustManagerFactory.map(ctx::trustManager)) + .map(ctx -> ctx.trustManager(InsecureTrustManagerFactory.INSTANCE)) .mapTry(SslContextBuilder::build); } - private Try keyManagerFactory(Path path, Password password) { + private Try trustManagerFactory(SecurityKeys keys) { + return trustManagerFactory(keys.trustStore(), keys.trustStorePassword()); + } + + private Try keyManagerFactory(SecurityKeys keys) { + return keyManagerFactory(keys.keyStore(), keys.keyStorePassword()); + } + + private Try 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(Path path, Password password) { + private Try 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 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 00000000..ab2aa773 --- /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 Piotr Jaszczyk + * @since February 2019 + */ +class KeyStoreTypesTest { + + @Test + void guessType_shouldReturnExtension_forP12() { + final Option result = callGuessTypeWithFileName("file.p12"); + assertThat(result.get()).isEqualTo(KeyStoreTypes.TYPE_PKCS12); + } + + @Test + void guessType_shouldReturnExtension_forPkcs12() { + final Option result = callGuessTypeWithFileName("file.pkcs12"); + assertThat(result.get()).isEqualTo(KeyStoreTypes.TYPE_PKCS12); + } + + @Test + void guessType_shouldReturnExtension_forJks() { + final Option result = callGuessTypeWithFileName("file.jks"); + assertThat(result.get()).isEqualTo(KeyStoreTypes.TYPE_JKS); + } + + @Test + void guessType_shouldReturnExtension_ignoringCase() { + final Option result = callGuessTypeWithFileName("file.PKCS12"); + assertThat(result.get()).isEqualTo(KeyStoreTypes.TYPE_PKCS12); + } + + @Test + void guessType_shouldReturnNone_whenFileDoesNotHaveExtension() { + final Option result = callGuessTypeWithFileName("file"); + assertThat(result.isEmpty()).isTrue(); + } + + @Test + void guessType_shouldReturnNone_whenFileEndsWithDot() { + final Option result = callGuessTypeWithFileName("file."); + assertThat(result.isEmpty()).isTrue(); + } + + private Option callGuessTypeWithFileName(String fileName) { + final Path path = Paths.get("/", "tmp", fileName); + return KeyStoreTypes.inferTypeFromExtension(path); + } +} \ No newline at end of file -- cgit 1.2.3-korg