diff --git a/pom.xml b/pom.xml index 494510f..8662932 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ UTF-8 + 1.0.2.5 @@ -89,6 +90,13 @@ 5.11.0 test + + + org.bouncycastle + bc-fips + ${bc-fips.version} + test + diff --git a/src/main/java/org/folio/edge/core/Constants.java b/src/main/java/org/folio/edge/core/Constants.java index ca092fc..cf03dab 100644 --- a/src/main/java/org/folio/edge/core/Constants.java +++ b/src/main/java/org/folio/edge/core/Constants.java @@ -118,9 +118,33 @@ private Constants() { defaultMap.put(SYS_HTTP_SERVER_SSL_ENABLED, Boolean.parseBoolean(System.getProperty(SYS_HTTP_SERVER_SSL_ENABLED, Boolean.toString(DEFAULT_SSL_ENABLED)))); + defaultMap.put(SYS_HTTP_SERVER_KEYSTORE_TYPE, + System.getProperty(SYS_HTTP_SERVER_KEYSTORE_TYPE)); + defaultMap.put(SYS_HTTP_SERVER_KEYSTORE_PROVIDER, + System.getProperty(SYS_HTTP_SERVER_KEYSTORE_PROVIDER)); + defaultMap.put(SYS_HTTP_SERVER_KEYSTORE_PATH, + System.getProperty(SYS_HTTP_SERVER_KEYSTORE_PATH)); + defaultMap.put(SYS_HTTP_SERVER_KEYSTORE_PASSWORD, + System.getProperty(SYS_HTTP_SERVER_KEYSTORE_PASSWORD)); + defaultMap.put(SYS_HTTP_SERVER_KEY_ALIAS, + System.getProperty(SYS_HTTP_SERVER_KEY_ALIAS)); + defaultMap.put(SYS_HTTP_SERVER_KEY_ALIAS_PASSWORD, + System.getProperty(SYS_HTTP_SERVER_KEY_ALIAS_PASSWORD)); defaultMap.put(SYS_WEB_CLIENT_SSL_ENABLED, Boolean.parseBoolean(System.getProperty(SYS_WEB_CLIENT_SSL_ENABLED, Boolean.toString(DEFAULT_SSL_ENABLED)))); + defaultMap.put(SYS_WEB_CLIENT_TRUSTSTORE_TYPE, + System.getProperty(SYS_WEB_CLIENT_TRUSTSTORE_TYPE)); + defaultMap.put(SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER, + System.getProperty(SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER)); + defaultMap.put(SYS_WEB_CLIENT_TRUSTSTORE_PATH, + System.getProperty(SYS_WEB_CLIENT_TRUSTSTORE_PATH)); + defaultMap.put(SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD, + System.getProperty(SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD)); + defaultMap.put(SYS_WEB_CLIENT_KEY_ALIAS, + System.getProperty(SYS_WEB_CLIENT_KEY_ALIAS)); + defaultMap.put(SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD, + System.getProperty(SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD)); defaultMap.put(SYS_SECURE_STORE_PROP_FILE, System.getProperty(SYS_SECURE_STORE_PROP_FILE)); defaultMap.put(SYS_OKAPI_URL, diff --git a/src/test/java/org/folio/edge/core/EdgeVerticleTlsIntegrationTest.java b/src/test/java/org/folio/edge/core/EdgeVerticleTlsIntegrationTest.java new file mode 100644 index 0000000..c6cc471 --- /dev/null +++ b/src/test/java/org/folio/edge/core/EdgeVerticleTlsIntegrationTest.java @@ -0,0 +1,147 @@ +package org.folio.edge.core; + +import io.netty.handler.codec.http.HttpResponseStatus; +import io.vertx.core.AsyncResult; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpServer; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.folio.edge.core.utils.OkapiClient; +import org.folio.edge.core.utils.OkapiClientFactory; +import org.folio.edge.core.utils.OkapiClientFactoryInitializer; +import org.folio.edge.core.utils.SslConfigurationUtil; +import org.folio.edge.core.utils.test.TestUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.security.Security; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@RunWith(VertxUnitRunner.class) +public class EdgeVerticleTlsIntegrationTest { + + private static final Logger logger = LogManager.getLogger(EdgeVerticleTlsIntegrationTest.class); + private static Vertx vertx; + + private static final String KEYSTORE_TYPE = "BCFKS"; + private static final String KEYSTORE_PATH = "test.keystore 1.bcfks"; + private static final String TRUST_STORE_PATH = "test.truststore 1.bcfks"; + private static final String KEYSTORE_PASSWORD = "SecretPassword"; + private static final String RESPONSE_MESSAGE = ""; + private static final String OKAPI_URL = "http://localhost:" + TestUtils.getPort(); + private static final String TENANT = "diku"; + + @Before + public void setUpOnce() { + Security.addProvider(new BouncyCastleFipsProvider()); + vertx = Vertx.vertx(); + } + + @After + public void tearDown(TestContext context) { + vertx.close(context.asyncAssertSuccess()); + } + + @Test + public void testServerClientTlsCommunication(TestContext context) throws IllegalAccessException { + final JsonObject config = getCommonConfig(true); + + final HttpServerOptions serverOptions = new HttpServerOptions(); + serverOptions.setPort(config.getInteger(Constants.SYS_PORT)); + + SslConfigurationUtil.configureSslServerOptionsIfEnabled(config, serverOptions); + + final HttpServer httpServer = vertx.createHttpServer(serverOptions); + httpServer + .requestHandler(req -> req.response().putHeader(HttpHeaders.CONTENT_TYPE, Constants.TEXT_PLAIN).end(RESPONSE_MESSAGE)) + .listen(config.getInteger(Constants.SYS_PORT), getCommonServerHandler(config)); + + final OkapiClientFactory okapiClientFactory = OkapiClientFactoryInitializer.createInstance(vertx, config); + final OkapiClient okapiClient = okapiClientFactory.getOkapiClient(TENANT); + final WebClientOptions webClientOptions = (WebClientOptions) FieldUtils.readDeclaredField(okapiClient.client, "options", true); + + assertTrue(webClientOptions.isSsl()); + assertNotNull(webClientOptions.getTrustOptions()); + + createClientRequest(context, webClientOptions, config); + } + + @Test(expected = java.net.ConnectException.class) + public void testFailingServerClientTlsCommunication(TestContext context) throws IllegalAccessException { + final JsonObject config = getCommonConfig(false); + + final HttpServerOptions serverOptions = new HttpServerOptions(); + serverOptions.setPort(config.getInteger(Constants.SYS_PORT)); + + SslConfigurationUtil.configureSslServerOptionsIfEnabled(config, serverOptions); + + final HttpServer httpServer = vertx.createHttpServer(serverOptions); + httpServer + .requestHandler(req -> req.response().putHeader(HttpHeaders.CONTENT_TYPE, Constants.TEXT_PLAIN).end(RESPONSE_MESSAGE)) + .listen(config.getInteger(Constants.SYS_PORT), getCommonServerHandler(config)); + + final OkapiClientFactory okapiClientFactory = OkapiClientFactoryInitializer.createInstance(vertx, config); + final OkapiClient okapiClient = okapiClientFactory.getOkapiClient(TENANT); + final WebClientOptions webClientOptions = (WebClientOptions) FieldUtils.readDeclaredField(okapiClient.client, "options", true); + + assertFalse(webClientOptions.isSsl()); + assertNull(webClientOptions.getTrustOptions()); + + createClientRequest(context, webClientOptions, config); + } + + private static io.vertx.core.@NotNull Handler> getCommonServerHandler(JsonObject config) { + return http -> logger.info("Server started on port {}", config.getInteger(Constants.SYS_PORT)); + } + + private static void createClientRequest(TestContext context, WebClientOptions webClientOptions, JsonObject config) { + final WebClient webClient = WebClient.create(vertx, webClientOptions); + webClient.get(config.getInteger(Constants.SYS_PORT), "localhost", "/") + .send() + .onComplete(context.asyncAssertSuccess(response -> { + String message = response.body().toString(); + logger.info("WebClient sent message to server port {}, response message: {}", config.getInteger(Constants.SYS_PORT), message); + context.assertEquals(HttpResponseStatus.OK.code(), response.statusCode()); + context.assertEquals(RESPONSE_MESSAGE, message); + })); + } + + private JsonObject getCommonConfig(boolean enableWebClientSsl) { + int serverPort = TestUtils.getPort(); + JsonObject config = new JsonObject().put(Constants.SYS_PORT, serverPort) + .put(Constants.SYS_OKAPI_URL, OKAPI_URL) + .put(Constants.SYS_SECURE_STORE_PROP_FILE, "src/main/resources/ephemeral.properties") + .put(Constants.SYS_LOG_LEVEL, "TRACE") + .put(Constants.SYS_REQUEST_TIMEOUT_MS, 5000) + .put(Constants.SYS_HTTP_SERVER_SSL_ENABLED, true) + .put(Constants.SYS_HTTP_SERVER_KEYSTORE_TYPE, KEYSTORE_TYPE) + .put(Constants.SYS_HTTP_SERVER_KEYSTORE_PATH, KEYSTORE_PATH) + .put(Constants.SYS_HTTP_SERVER_KEYSTORE_PASSWORD, KEYSTORE_PASSWORD); + if (enableWebClientSsl) { + return config + .put(Constants.SYS_WEB_CLIENT_SSL_ENABLED, true) + .put(Constants.SYS_WEB_CLIENT_TRUSTSTORE_TYPE, KEYSTORE_TYPE) + .put(Constants.SYS_WEB_CLIENT_TRUSTSTORE_PATH, TRUST_STORE_PATH) + .put(Constants.SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD, KEYSTORE_PASSWORD); + } else { + return config + .put(Constants.SYS_WEB_CLIENT_SSL_ENABLED, false); + } + } +} diff --git a/src/test/resources/test.keystore 1.bcfks b/src/test/resources/test.keystore 1.bcfks new file mode 100644 index 0000000..af4eb23 Binary files /dev/null and b/src/test/resources/test.keystore 1.bcfks differ diff --git a/src/test/resources/test.truststore 1.bcfks b/src/test/resources/test.truststore 1.bcfks new file mode 100644 index 0000000..4612e7e Binary files /dev/null and b/src/test/resources/test.truststore 1.bcfks differ