From 404743ddd8e0177a4d3db6831630640f1a4f0207 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Fri, 18 Sep 2026 14:16:47 +0300 Subject: [PATCH] Verify the connector server certificate against the host over the WebSocket connection The Grizzly client wraps its wss:// connections in an SSLEngine, which validates the certificate chain but never checks the certificate against the host, so anyone holding a certificate the client trusts could sit in the middle of the connection. Have JSSE do "HTTPS" endpoint identification during the handshake, against the host of the remote URI also when the socket goes through a proxy. ConnectionManagerConfig gets a hostnameVerification switch that defaults to the system property the legacy client honours, org.identityconnectors.framework.remote.hostnameVerification. A failed handshake is logged at WARN and surfaces as the cause of the "before WebSocket is established" failure instead of a bare connection close. The test certificates expired in January 2025 and are regenerated; the server certificate now names localhost, 127.0.0.1 and ::1 as the client verifies them, and serverKeystore-cn-only.jks keeps one without subjectAltName for the negative test. --- .../ClientRemoteConnectorInfoManager.java | 11 +- .../framework/client/ConnectionManager.java | 67 ++++++- .../client/ConnectionManagerConfig.java | 28 +++ .../ClientHostnameVerificationTest.java | 185 ++++++++++++++++++ .../src/test/resources/README.txt | 36 ++-- .../src/test/resources/clientKeystore.jks | Bin 2154 -> 2157 bytes .../test/resources/serverKeystore-cn-only.jks | Bin 0 -> 2170 bytes .../src/test/resources/serverKeystore.jks | Bin 2159 -> 2208 bytes .../src/test/resources/truststore.jks | Bin 1702 -> 2612 bytes 9 files changed, 309 insertions(+), 18 deletions(-) create mode 100644 OpenICF-java-framework/connector-server-grizzly/src/test/java/org/forgerock/openicf/framework/server/ClientHostnameVerificationTest.java create mode 100644 OpenICF-java-framework/connector-server-grizzly/src/test/resources/serverKeystore-cn-only.jks diff --git a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ClientRemoteConnectorInfoManager.java b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ClientRemoteConnectorInfoManager.java index 286ab681..3e26ee15 100644 --- a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ClientRemoteConnectorInfoManager.java +++ b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ClientRemoteConnectorInfoManager.java @@ -184,8 +184,15 @@ public void onClosed(Closeable closeable, ICloseType type) throws IOException { tryReleaseConnectionPermit(); if (!socket.connectPromise.isDone()) { - socket.connectPromise.handleException(new ConnectorIOException( - "Connection is closed before WebSocket is established")); + final Throwable handshakeFailure = + ConnectionManager.getHandshakeFailure(conn); + socket.connectPromise.handleException(handshakeFailure != null + ? new ConnectorIOException( + "TLS handshake failed before WebSocket is established: " + + handshakeFailure.getMessage(), + handshakeFailure) + : new ConnectorIOException( + "Connection is closed before WebSocket is established")); } // Immediately try to reconnect if (System.currentTimeMillis() - lastConnectithenOnException.get() > 30000) { diff --git a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ConnectionManager.java b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ConnectionManager.java index 9737418a..0f68d194 100644 --- a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ConnectionManager.java +++ b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ConnectionManager.java @@ -11,6 +11,7 @@ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. * ==================== * Portions Copyrighted 2015 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems, LLC */ /** @@ -32,6 +33,8 @@ import java.util.logging.Logger; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; import org.forgerock.openicf.framework.remote.ReferenceCountedObject; import org.forgerock.openicf.framework.remote.rpc.OperationMessageListener; @@ -64,6 +67,7 @@ import org.glassfish.grizzly.http.util.MimeHeaders; import org.glassfish.grizzly.nio.transport.TCPNIOTransport; import org.glassfish.grizzly.nio.transport.TCPNIOTransportBuilder; +import org.glassfish.grizzly.ssl.SSLConnectionContext; import org.glassfish.grizzly.ssl.SSLContextConfigurator; import org.glassfish.grizzly.ssl.SSLEngineConfigurator; import org.glassfish.grizzly.ssl.SSLFilter; @@ -308,7 +312,9 @@ public void onTimeout(Connection connection) { } final SSLEngineConfigurator configurator = new SSLEngineConfigurator(context, true, false, false); - final SwitchingSSLFilter filter = new SwitchingSSLFilter(configurator, defaultSecState); + final SwitchingSSLFilter filter = + new SwitchingSSLFilter(configurator, defaultSecState, clientConfig + .isHostnameVerification()); fcb.add(filter); final AsyncHttpClientEventFilter eventFilter = @@ -476,6 +482,15 @@ public String getAuthority() { return connectionInfo.getRemoteURI().getAuthority(); } + /** The host of the remote URI, without the brackets of an IPv6 literal. */ + String getHost() { + String host = connectionInfo.getRemoteURI().getHost(); + if (host != null && host.startsWith("[") && host.endsWith("]")) { + host = host.substring(1, host.length() - 1); + } + return host; + } + boolean isGracefullyFinishResponseOnClose() { final HttpResponsePacket response = responsePacket; return !response.getProcessingState().isKeepAlive() && !response.isChunked() @@ -860,23 +875,71 @@ public Object type() { } } // END GracefulCloseEvent + /** + * The TLS handshake failure recorded on a connection by + * {@link SwitchingSSLFilter}, so that the reason (an untrusted or + * mismatching server certificate, say) can be reported to whoever waits + * for that connection instead of a bare "connection closed". + */ + static final Attribute HANDSHAKE_FAILURE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER + .createAttribute(SwitchingSSLFilter.class.getName() + ".handshakeFailure"); + + static Throwable getHandshakeFailure(final Connection connection) { + return HANDSHAKE_FAILURE.get(connection); + } + static final class SwitchingSSLFilter extends SSLFilter { private final boolean secureByDefault; + private final boolean hostnameVerification; final Attribute CONNECTION_IS_SECURE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER .createAttribute(SwitchingSSLFilter.class.getName()); // -------------------------------------------------------- Constructors - SwitchingSSLFilter(final SSLEngineConfigurator clientConfig, final boolean secureByDefault) { + SwitchingSSLFilter(final SSLEngineConfigurator clientConfig, final boolean secureByDefault, + final boolean hostnameVerification) { super(null, clientConfig); this.secureByDefault = secureByDefault; + this.hostnameVerification = hostnameVerification; } // ---------------------------------------------- Methods from SSLFilter + /** + * Creates the engine for the server named in the remote URI (not for + * the proxy the socket may be connected to) and, unless switched off, + * has JSSE check the server certificate against that host during the + * handshake: an {@code SSLEngine} does not do this on its own. + */ + @Override + protected SSLEngine createClientSSLEngine(final SSLConnectionContext sslCtx, + final SSLEngineConfigurator sslEngineConfigurator) { + final RemoteConnectionContext context = + RemoteConnectionContext.get(sslCtx.getConnection()); + final String host = context != null ? context.getHost() : null; + final SSLEngine sslEngine = + host != null ? sslEngineConfigurator.createSSLEngine(host, -1) : super + .createClientSSLEngine(sslCtx, sslEngineConfigurator); + if (hostnameVerification) { + final SSLParameters parameters = sslEngine.getSSLParameters(); + parameters.setEndpointIdentificationAlgorithm("HTTPS"); + sslEngine.setSSLParameters(parameters); + } + return sslEngine; + } + + @Override + protected void notifyHandshakeFailed(final Connection connection, final Throwable t) { + HANDSHAKE_FAILURE.set(connection, t); + final RemoteConnectionContext context = RemoteConnectionContext.get(connection); + logger.warn("TLS handshake with connector server {0} failed: {1}", + context != null ? context.getAuthority() : connection.getPeerAddress(), t); + super.notifyHandshakeFailed(connection, t); + } + @Override public NextAction handleEvent(FilterChainContext ctx, FilterChainEvent event) throws IOException { diff --git a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ConnectionManagerConfig.java b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ConnectionManagerConfig.java index d6e26709..b32683bc 100644 --- a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ConnectionManagerConfig.java +++ b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ConnectionManagerConfig.java @@ -20,6 +20,7 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" + * Portions Copyrighted 2026 3A Systems, LLC */ package org.forgerock.openicf.framework.client; @@ -32,6 +33,15 @@ public class ConnectionManagerConfig { + /** + * System property that turns off TLS hostname verification for every + * client unless a {@link ConnectionManagerConfig} says otherwise. Shared + * with the legacy connector server client. Only the literal {@code false} + * disables verification, so a typo in the value can not weaken it. + */ + public static final String HOSTNAME_VERIFICATION_PROPERTY = + "org.identityconnectors.framework.remote.hostnameVerification"; + // SSL Config private String trustStoreProvider; @@ -72,6 +82,9 @@ public class ConnectionManagerConfig { protected int maxConnectionLifeTimeInMs; + protected boolean hostnameVerification = + !"false".equalsIgnoreCase(System.getProperty(HOSTNAME_VERIFICATION_PROPERTY)); + public int getScheduledThreadPoolSize() { return 5; } @@ -175,6 +188,21 @@ public static Builder newBuilder() { return new Builder(); } + /** + * Whether the connector server certificate is checked against the host of + * the remote URI during the TLS handshake (RFC 2818 / RFC 6125 "HTTPS" + * endpoint identification). On by default; switching it off leaves the + * connection open to man-in-the-middle attacks by anyone holding a + * certificate the client trusts. + */ + public boolean isHostnameVerification() { + return hostnameVerification; + } + + public void setHostnameVerification(boolean hostnameVerification) { + this.hostnameVerification = hostnameVerification; + } + public static class Builder { } diff --git a/OpenICF-java-framework/connector-server-grizzly/src/test/java/org/forgerock/openicf/framework/server/ClientHostnameVerificationTest.java b/OpenICF-java-framework/connector-server-grizzly/src/test/java/org/forgerock/openicf/framework/server/ClientHostnameVerificationTest.java new file mode 100644 index 00000000..c52204f9 --- /dev/null +++ b/OpenICF-java-framework/connector-server-grizzly/src/test/java/org/forgerock/openicf/framework/server/ClientHostnameVerificationTest.java @@ -0,0 +1,185 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems, LLC. + */ +package org.forgerock.openicf.framework.server; + +import static org.forgerock.openicf.framework.AsyncConnectorInfoManagerTestBase.JSK_PASSWORD; +import static org.forgerock.openicf.framework.AsyncConnectorInfoManagerTestBase.KEY_HASH; +import static org.forgerock.openicf.framework.AsyncConnectorInfoManagerTestBase.TEST_CONNECTOR_KEY; +import static org.forgerock.openicf.framework.AsyncConnectorInfoManagerTestBase.buildRemoteWSFrameworkConnectionInfo; +import static org.forgerock.openicf.framework.AsyncConnectorInfoManagerTestBase.findFreePort; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.net.URL; +import java.net.URLDecoder; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLHandshakeException; + +import org.forgerock.openicf.framework.ConnectorFramework; +import org.forgerock.openicf.framework.ConnectorFrameworkFactory; +import org.forgerock.openicf.framework.client.ClientRemoteConnectorInfoManager; +import org.forgerock.openicf.framework.client.ConnectionManager; +import org.forgerock.openicf.framework.client.ConnectionManagerConfig; +import org.forgerock.openicf.framework.client.RemoteWSFrameworkConnectionInfo; +import org.forgerock.openicf.framework.remote.ReferenceCountedObject; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.ssl.SSLContextConfigurator; +import org.identityconnectors.framework.api.ConnectorInfo; +import org.identityconnectors.framework.common.exceptions.ConnectorException; +import org.identityconnectors.testconnector.TstConnector; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +/** + * The WebSocket client must check the connector server certificate against + * the host of the remote URI. {@code serverKeystore.jks} names + * {@code localhost}, {@code 127.0.0.1} and {@code ::1} as subjectAltName; + * {@code serverKeystore-cn-only.jks} only has {@code CN=localhost}. Both are + * trusted by {@code truststore.jks}, which the client picks up through the + * {@code javax.net.ssl.trustStore} system properties. + */ +public class ClientHostnameVerificationTest { + + private static final long TIMEOUT_SECONDS = 30; + + private final int sanPort = findFreePort(); + private final int cnOnlyPort = findFreePort(); + + private ConnectorServer connectorServer; + + /** + * Like the other tests of this module, the server lives for the whole + * run: every {@link ConnectorServer} registers with the JVM-wide Grizzly + * {@code WebSocketEngine}, and unregistering one while other test + * servers are still in use leaves their WebSocket upgrades unanswered. + */ + @BeforeTest + public void startServer() throws Exception { + String truststore = resourcePath("truststore.jks"); + System.setProperty(SSLContextConfigurator.TRUST_STORE_FILE, truststore); + System.setProperty(SSLContextConfigurator.TRUST_STORE_PASSWORD, JSK_PASSWORD); + + connectorServer = new ConnectorServer(); + connectorServer.setConnectorFrameworkFactory(new ConnectorFrameworkFactory()); + connectorServer.setConnectorBundleURLs(Arrays.asList(TstConnector.class + .getProtectionDomain().getCodeSource().getLocation())); + connectorServer.init(); + connectorServer.addListener("san", NetworkListener.DEFAULT_NETWORK_HOST, sanPort, + serverSSLContext("serverKeystore.jks", truststore)); + connectorServer.addListener("cn-only", NetworkListener.DEFAULT_NETWORK_HOST, cnOnlyPort, + serverSSLContext("serverKeystore-cn-only.jks", truststore)); + connectorServer.setKeyHash(KEY_HASH); + connectorServer.start(); + } + + @AfterTest + public void stopServer() throws Exception { + connectorServer.stop(); + connectorServer.destroy(); + } + + @Test + public void rejectsServerCertificateWithoutMatchingName() throws Exception { + try (Client client = new Client(new ConnectionManagerConfig())) { + try { + client.connect(cnOnlyPort); + fail("Expected the handshake to fail: certificate has no name matching 127.0.0.1"); + } catch (ConnectorException e) { + assertHostnameVerificationFailure(e); + } + } + } + + @Test + public void acceptsServerCertificateWithMatchingSubjectAltName() throws Exception { + try (Client client = new Client(new ConnectionManagerConfig())) { + assertNotNull(client.connect(sanPort)); + } + } + + @Test + public void connectsToMismatchedCertificateWhenVerificationIsDisabled() throws Exception { + ConnectionManagerConfig config = new ConnectionManagerConfig(); + config.setHostnameVerification(false); + try (Client client = new Client(config)) { + assertNotNull(client.connect(cnOnlyPort)); + } + } + + // ---- helpers --------------------------------------------------------- + + private static void assertHostnameVerificationFailure(ConnectorException e) { + Throwable cause = e; + while (cause != null && !(cause instanceof SSLHandshakeException)) { + cause = cause.getCause(); + } + assertNotNull(cause, "Expected an SSLHandshakeException in the cause chain of: " + e); + String message = String.valueOf(cause.getMessage()); + assertTrue(message.contains("subject alternative") || message.contains("No name matching"), + "Expected a hostname verification failure, got: " + message); + } + + private static SSLContextConfigurator serverSSLContext(String keystore, String truststore) + throws Exception { + SSLContextConfigurator configurator = new SSLContextConfigurator(false); + configurator.setKeyStoreFile(resourcePath(keystore)); + configurator.setKeyStorePass(JSK_PASSWORD); + configurator.setTrustStoreFile(truststore); + configurator.setTrustStorePass(JSK_PASSWORD); + configurator.setSecurityProtocol("TLS"); + return configurator; + } + + private static String resourcePath(String name) throws Exception { + URL url = ClientHostnameVerificationTest.class.getClassLoader().getResource(name); + assertNotNull(url, "missing test resource " + name); + return URLDecoder.decode(url.getFile(), "UTF-8"); + } + + /** A client-side framework with its own connection manager configuration. */ + private static final class Client implements AutoCloseable { + private final ReferenceCountedObject.Reference framework; + + Client(ConnectionManagerConfig config) { + framework = new ConnectorFrameworkFactory().acquire(); + framework.get().setConnectionManagerConfig(config); + } + + /** + * Opens the WebSocket to the server on {@code port} and, once it is + * up, looks up the test connector over it, so that the initial + * exchange has completed by the time the client is closed. + */ + ConnectorInfo connect(int port) throws Exception { + RemoteWSFrameworkConnectionInfo info = + buildRemoteWSFrameworkConnectionInfo(true, port, null); + ConnectionManager connectionManager = + (ConnectionManager) framework.get().getRemoteConnectionInfoManagerFactory(); + ClientRemoteConnectorInfoManager manager = connectionManager.connect(info); + manager.connect().getOrThrow(TIMEOUT_SECONDS, TimeUnit.SECONDS); + return manager.getAsyncConnectorInfoManager().findConnectorInfoAsync( + TEST_CONNECTOR_KEY).getOrThrow(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + public void close() { + framework.release(); + } + } +} diff --git a/OpenICF-java-framework/connector-server-grizzly/src/test/resources/README.txt b/OpenICF-java-framework/connector-server-grizzly/src/test/resources/README.txt index 2a683881..8d29ca70 100644 --- a/OpenICF-java-framework/connector-server-grizzly/src/test/resources/README.txt +++ b/OpenICF-java-framework/connector-server-grizzly/src/test/resources/README.txt @@ -1,17 +1,25 @@ -#Sample keytool commands to generate two self-signed certificate and export them into a trust store. +# keytool commands used to generate the self-signed test certificates and the trust store. +# +# The client verifies the server certificate against the host it connects to (the tests use +# wss://127.0.0.1), so the server certificate carries the loopback names as subjectAltName. +# serverKeystore-cn-only.jks has no subjectAltName and is used to test that such a certificate +# is rejected. -echo changeit > keystore.pin +keytool -genkeypair -alias openicf-server -keyalg RSA -keysize 2048 -sigalg SHA256withRSA \ + -dname "CN=localhost, O=OpenICF Self-Signed Certificate" \ + -ext "SAN=dns:localhost,ip:127.0.0.1,ip:::1" -validity 10950 \ + -storetype JKS -keystore serverKeystore.jks -storepass Passw0rd -keypass Passw0rd +keytool -genkeypair -alias openicf-server-cn-only -keyalg RSA -keysize 2048 -sigalg SHA256withRSA \ + -dname "CN=localhost, O=OpenICF Self-Signed Certificate" -validity 10950 \ + -storetype JKS -keystore serverKeystore-cn-only.jks -storepass Passw0rd -keypass Passw0rd +keytool -genkeypair -alias openicf-client -keyalg RSA -keysize 2048 -sigalg SHA256withRSA \ + -dname "CN=client, O=OpenICF Self-Signed Certificate" -validity 10950 \ + -storetype JKS -keystore clientKeystore.jks -storepass Passw0rd -keypass Passw0rd +keytool -exportcert -rfc -alias openicf-server -keystore serverKeystore.jks -storepass Passw0rd > openicf-server.pem +keytool -exportcert -rfc -alias openicf-server-cn-only -keystore serverKeystore-cn-only.jks -storepass Passw0rd > openicf-server-cn-only.pem +keytool -exportcert -rfc -alias openicf-client -keystore clientKeystore.jks -storepass Passw0rd > openicf-client.pem -keytool -genkey -alias openicf-client -keyalg rsa -dname "CN=client, O=OpenICF Self-Signed Certificate" -keystore clientKeystore.jks -keytool -genkey -alias openicf-server -keyalg rsa -dname "CN=localhost, O=OpenICF Self-Signed Certificate" -keystore serverKeystore.jks - -keytool -selfcert -alias openicf-client -validity 3653 -keystore clientKeystore.jks -keytool -selfcert -alias openicf-server -validity 3653 -keystore serverKeystore.jks - -keytool -export -alias openicf-client -file openicf-client-cert.txt -rfc -keystore clientKeystore.jks -keytool -export -alias openicf-server -file openicf-localhost-cert.txt -rfc -keystore serverKeystore.jks - - -keytool -import -alias openicf-client -file openicf-client-cert.txt -trustcacerts -keystore truststore.jks -storetype JKS -keytool -import -alias openicf-server -file openicf-localhost-cert.txt -trustcacerts -keystore truststore.jks -storetype JKS +keytool -importcert -noprompt -alias openicf-client -file openicf-client.pem -storetype JKS -keystore truststore.jks -storepass Passw0rd +keytool -importcert -noprompt -alias openicf-server -file openicf-server.pem -storetype JKS -keystore truststore.jks -storepass Passw0rd +keytool -importcert -noprompt -alias openicf-server-cn-only -file openicf-server-cn-only.pem -storetype JKS -keystore truststore.jks -storepass Passw0rd diff --git a/OpenICF-java-framework/connector-server-grizzly/src/test/resources/clientKeystore.jks b/OpenICF-java-framework/connector-server-grizzly/src/test/resources/clientKeystore.jks index 5d6a49116eba13b1321612b66b1aad6aaf5b0983..9e19ebd3b02571af5bc55671c4e2398d582c18a2 100644 GIT binary patch delta 1986 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 Us{<43Uo|BUh~DR_<*TiQPMj~ZtN;K2 delta 1983 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 JiZ%nJOu}|Yz3Tt~ delta 1977 zcmV;q2S)gy5$_O?BY#UUiUbe<00jXsf&~3A4h9M<1_1;CDgqG!0R;dAf&}TNZFc&W z?g|!OfSM7`IBRnWiK$Gd+T-@fP<`l2Jjtabia}6G>vgcTZ1@3~Jjff9-pqm}y@rf~ zl<6;Q-UB**&WueLE4v07cU4BFUeV>E0s%1kdp&>~-cn2NGk*?5@eo6RHW7hhIS~sz z!&FZK$?;anEGBaR$|-?*1++Q@XZqC5NW0E+Nn7zo=4sry^Lh^g8Xcyv@F4x58!WR$ zFZ0v3DU+)wq^(Rv!yn$P|LTNhm@-4q&-Q&+wak~$8MLj1X_J1r)&02<%y3TIst=tS zfzai={V_I{w}0cBv50}aldfeX2tB`w=Le7 z)n*GP`#sL~hiGsm zh6m@V3^bBU*PEXP?qOT;L_i_x9!%U{`J$>KRSZsBkk;ugcmQXp;Pqrjv#M$? z$$Iqb{C}iwF|j&@Ot>=IwAKyGvPgpP z)pUr#(W*!1X8*2U$Qt^m+Y*2f=|L!UqvD~8X@B_+#D)x&X)V~oUT6S5oqAVd6|rK@ z;+uQU_11F2JFav2>hVe1N6V{Gqg^#Q$}ZGYIGJCLt$N11;b|U)O+uw5AD3KyJ3yj_ zTaS%|!|`(D6BOihm_Qb2(S;8asb8|?TNBRxx9;yrp4SwkD!~6j_>-L+cDh1>U=G1~ z@_+4onu5d>O?=L8xZws3H!G@H6lc)swtIdgX1`VY=y>#)B#%naW z<|$DUKux(@-I8oW_pfepFqr{14*NRxfLi#*r?Xw`H zJoF=0BEijQEsK`7>6D2VF|CNlg(+CFMm5u zgMb?(D_u`*{QK}T!hmOoHJuM3YUX*i%uxXBB_Og25O!QB3~8i{O@~!UyUlCqZu35g zuTXFjq-diB?S;4Dx@JP2Jva7B*K;?xzS)&d zia{OIGwrHv$s{j3mHyHfJ~KN(Rd8h-hZ6M7v`O%AL+dT($=;iF#OL$Br){fG5YFLq zf|x~vrHulM{nTo-w0kF_2+tXtpfGvU>DkBphQ_GPKxvU>GCDKhOwMJ-ur?B{!KSs_ z#+cN+1u8^wpz8ffo5TPB00966SS~d%IRF3yA25Og8!&DU1w-$?NtRzC^5$@mjJ9|l16s|yjr~{_DJrq^KO#9wuT+O`V3ma$L7hF3wAd3|~Cjs^6O{|6?>AU`FT%5Yrj>?uwH zww&Cm$0`1mp@dJD`RZfuHPN?$Bs4gBdMK8E$r}O#0RRD`Aut~>9R>qc9S#H* z1QdK48$?{mlbOg7p=tJ3#pZ&jVS_LY1_>&LNQU#HLAH9MAf*=yDW_y5j%gT#SbI!~k8kU$)2WuokMQ%vPrE)VF8o!6?bjRq|8t+Ev&+^aEt6izpLy@DEhr>pOq~ctf-TA# zQ@Of*q$f}Xtg!*A25uZnj`7-UKc1~KPH?BOB?`{Hs0ai%OpQ`Q1>24>wF{{uJhBMo z8MWgFEMMhk@c^KS`=xJ!k6;NEMlw{6YApCvzeFK+IAu=uy<>F{+ER34(>6x%cZ{Pa zmLJI}@G}ry>uyN)D~51T){u~IvS86s3sl5wdY|0b$tKA8e&0ZUI0m2Q{ks{>&0wFd LoSA!7@ujfq`!2LP diff --git a/OpenICF-java-framework/connector-server-grizzly/src/test/resources/truststore.jks b/OpenICF-java-framework/connector-server-grizzly/src/test/resources/truststore.jks index 0466fd4b030fcd17516c91dd45e50c713179cb2e..e8706e7e0e894115d7d5e6cf80c19cab073ab7d8 100644 GIT binary patch literal 2612 zcmc)Lc{G&!9|!PRNlar2Eo3Z%G{a+=ERnHfsgNy8h!`4SG-SHBDVZz#Hb_#!9m-Ow zOBX3i#FS>D6fI;)Sz{Pk#&W0S{OafY&iS3+>5t!Y&U2n~zUTXVzt8!6Kd8~XJrMG7T^5PkizVFYpnfsFMH!IDCPeglEP`4tno5D?r|9S7+1XQKfcR34x~ zgz}*fFa)wrAi3E+$LH96(7Uv~~jentFgfP7?s! zHzBnE+?N61`@k>nKm#lM{Lx@I2ucGZK>!WB9zp|yK~D<$JTBx8D7`Igc(1MRu{SYw zJ<|)B(4p@rk!lW-s!j?E9bn{M$c%Sx1&(gf$^xM?Z5A@QQf)SMmg;76S{`F**~m)y zg&1oi4kmRW!#42RezMieB$h#>6Z3BAcy3$jgS1V!a!Y+@9|7n0eh>aE74Fh{9&}qh zHJP+C7qiL~o^lC6HmO|9484So-sLcz?pJ%o?0n1NsAv6JbWh92Xr7-)qZ?(ceIJfi zI=K=Ou43(IKOMCr9>(mHM7SH~$=oU{bJ+pOspZ z1(93eUhsQ;ix!6rg)Pi){#-=<$@7Z{1}T?-n{a^=0SlZ2bB*KHI_;fXhOGzpvR|ef zC)SR<+ttQYCn8&Sx=@XK)kV>eAr2Qhs#LN zVr3awnn@RwksK3NzW%Xf$s}@H>tF{XE<*oAfON$6n%YA{uQ-wYZ?-qiN{ya4Q&mIe z64kB8n>`+Q`cJ(zScs8L#}2JeotfH=U>vyDGrAz>T5(1rA!zJQFZ7)0;`tnAZi%eL zq@OOQ&I#|mI6R*i)$1|j5nWZ;)h+$i)P{dI_07^wr5~0Y`IhYXmUO>cI{E2VQ*A@* z&NUVNJPW3Bk?up( z#>8sJyo(0{6J-ZICr9hNiea%;L_ZPb*Eh6?em>0)&V7uJw5=5RWB-i+Bi`K$W`Uy%h#-!7G`(IV|Wz`gM3v!<#u zSa)8QOzdwaA{{3}m)Mfe8Ef#fAmR30%#&^%=c(PGLFz}okX$`ywTl+Pz# z6ScWAV|~fNY%lC2tUEda>o=~Z*vZ07jE|>I*@Fcz=FF+HpSHSW1O_l(HuVZlgvBvY zr9ms3jL^)a>|2T!{-kyNuw#REf=$RQbpVYapR)acfqP_TbIm`LNvgK6v8D<$kf%K)=tqhLEGk8ud z$l+?^Uh8Qt{hao@q)=r9EBO$%KV3PqKrA+#gz4gn%+~*vz^=|1p666#|2fPpnh;DZ zyy}PXNOd82@(D^WwtHMNv3o`1sX;|LE zBghaHIdtdZeZypjmF|ICBZ1e-ff);ePfJR&SwNZ=BjV9jvG}F@kAjXcaxg3BVcK~U zIe$67f|eMm`xQF1d9< z5lI<`Caluq;kZ)~$^BA7E~nFTUexpS;`ieF_V;|skooZ6Rwq^`00030H<1?vrL%8a zKqLu}b2FxrwgUh}lf;LpllWjr76c3egQ2!Yk^Q0!h?C}K6r4;Ykxfj=CKRd(?Fb5P z@?$1>TZx>YIG&Jk?X&k>nP@h-R{vP~&C7?>672jzQ-Z0HTX8p@4(jL{cJAb(Gjap| zW=3sYki8$lBx%*vH>(u3pWZ&lEU1WPPz2E-+Jxqi-|uxudr$Oka*KQ0$fVa^0>{8F zRy)Tk?xIt>65po(J}}k0*enPWcqT$#>RRP*jaVNLGs1&8REkhm97?tEhb@IHTBxVr zi4zkpa(nUQ2BQ?9kbTvL3$8jM(Vw^de7G%#rp{Cr73Ft$4N6&|7rP%RR*6d-rGi6N z$zhRU(+P5)gln)ZjW`Rv>oShV=(vFc_|c8&frE@Fbc4Zaa(0Yk%Hk+{irP|fL3f*J zcsO3nYf=Y8KmaI5i=;`y%0pbR%1|{Z@(+x|xy}Uh5rkqi}jo>iBdg!Fbu_(nN>)%G%k?yoWE^ zb%>&)jhSIn#YDWWkNr&r2A?Ry9ISj2nXB(4S6BkND<7n>FJY!T&?2LV;=m~=(Nk=e z+*W|n?f*KkTdpU8T&{FSXGf{R5W0L2X2crun*p-ozUBqk zw54$|>WwAuyG`X_j&G1Z`#6z(%HKCA1OPzalg@r&EJd zPrdUS_eskXh9i-^Z;|Q^BW$rn!m8yi)AIzcN#Tq=<3)VihlN)an9H=AcRvwMrtNvn zse!CyEi@L}L`}Q`g9om{%xrTF>xSKzxQ~JL`K4S6RtIw|1U+4}_dbub%q#X_S=rm! zS*(VhKHL%A$tXsWS*k~$nYMDwpziNwMeL>O^f6hTJgLt$hSI?@!fB7C(|hi3HbX02 zd8PEo`T-W-z4V?s96H3Xcn}*gdQhrR@9RoMezCjo@o^FVb`(X5;kHEF(|ea<+Ienq zyrjj$e#x1qQ4J>kiQbUA=`Ky(RI9U zICR|p+FEhi8gl2%ie*@mH%%?$<6@#g9^^p4WeCPZw_98#%vIK+V(J0ae!;H>XgMY|xBqE2pyB zYJ8eDH4q*Qmo>NRKM!6YB9imyPOJIYB9Kq&u54zBJi3zTr`oFBUzwejgm^BI;IFvX zJn6?)sfiplW?z>6qld`T)90JRJGP?+DxC=v{Scxw9xfYFMBm*_@bBEygPm~AXwuMr zHZ=^zuir7XK_d=3>4uJzbnQd5*W}g4=AZYLpv>$R-6Km}j&k2QXJqAUOTFc|P*66R KBh^^KiT?%GJX&P{