Repository: ignite Updated Branches: refs/heads/master a83f3038a -> 42161cdd5
IGNITE-6625: JDBC thin: support SSL connection to Ignite node This closes #3233 Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/42161cdd Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/42161cdd Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/42161cdd Branch: refs/heads/master Commit: 42161cdd5eafb6403aa6ed4beb75d2645f16f944 Parents: a83f303 Author: tledkov-gridgain <[email protected]> Authored: Thu Feb 8 15:29:42 2018 +0300 Committer: Igor Sapego <[email protected]> Committed: Thu Feb 8 15:31:29 2018 +0300 ---------------------------------------------------------------------- .../jdbc/suite/IgniteJdbcDriverTestSuite.java | 2 + .../jdbc/thin/JdbcThinConnectionSSLTest.java | 479 +++++++++++++++++++ .../jdbc/thin/JdbcThinConnectionSelfTest.java | 36 +- .../jdbc/thin/JdbcThinErrorsSelfTest.java | 2 +- .../jdbc/thin/ConnectionProperties.java | 206 ++++++++ .../jdbc/thin/ConnectionPropertiesImpl.java | 232 ++++++++- .../internal/jdbc/thin/JdbcThinConnection.java | 5 + .../internal/jdbc/thin/JdbcThinSSLUtil.java | 332 +++++++++++++ .../internal/jdbc/thin/JdbcThinTcpIo.java | 43 +- 9 files changed, 1297 insertions(+), 40 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java index 656e218..c004817 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java @@ -43,6 +43,7 @@ import org.apache.ignite.jdbc.thin.JdbcThinBulkLoadTransactionalPartitionedSelfT import org.apache.ignite.jdbc.thin.JdbcThinBulkLoadTransactionalReplicatedSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinComplexDmlDdlSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinComplexQuerySelfTest; +import org.apache.ignite.jdbc.thin.JdbcThinConnectionSSLTest; import org.apache.ignite.jdbc.thin.JdbcThinConnectionSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinDeleteStatementSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinDynamicIndexAtomicPartitionedNearSelfTest; @@ -133,6 +134,7 @@ public class IgniteJdbcDriverTestSuite extends TestSuite { // New thin JDBC suite.addTest(new TestSuite(JdbcThinConnectionSelfTest.class)); + suite.addTest(new TestSuite(JdbcThinConnectionSSLTest.class)); suite.addTest(new TestSuite(JdbcThinPreparedStatementSelfTest.class)); suite.addTest(new TestSuite(JdbcThinResultSetSelfTest.class)); http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSSLTest.java ---------------------------------------------------------------------- diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSSLTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSSLTest.java new file mode 100644 index 0000000..cc71f51 --- /dev/null +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSSLTest.java @@ -0,0 +1,479 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.ignite.jdbc.thin; + +import java.security.NoSuchAlgorithmException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.Callable; +import javax.cache.configuration.Factory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import org.apache.ignite.IgniteException; +import org.apache.ignite.configuration.ClientConnectorConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.binary.BinaryMarshaller; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; +import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; +import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; +import org.apache.ignite.ssl.SslContextFactory; +import org.apache.ignite.testframework.GridTestUtils; + +/** + * SSL connection test. + */ +@SuppressWarnings("ThrowableNotThrown") +public class JdbcThinConnectionSSLTest extends JdbcThinAbstractSelfTest { + /** IP finder. */ + private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true); + + /** Client key store path. */ + private static final String CLI_KEY_STORE_PATH = U.getIgniteHome() + + "/modules/clients/src/test/keystore/client.jks"; + + /** Server key store path. */ + private static final String SRV_KEY_STORE_PATH = U.getIgniteHome() + + "/modules/clients/src/test/keystore/server.jks"; + + /** Trust key store path. */ + private static final String TRUST_KEY_STORE_PATH = U.getIgniteHome() + + "/modules/clients/src/test/keystore/trust.jks"; + + /** SSL context factory. */ + private static Factory<SSLContext> sslCtxFactory; + + /** Set SSL context factory to client listener. */ + private static boolean setSslCtxFactoryToCli; + + /** Set SSL context factory to ignite. */ + private static boolean setSslCtxFactoryToIgnite; + + /** {@inheritDoc} */ + @SuppressWarnings("deprecation") + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + TcpDiscoverySpi disco = new TcpDiscoverySpi(); + + disco.setIpFinder(IP_FINDER); + + cfg.setDiscoverySpi(disco); + + cfg.setMarshaller(new BinaryMarshaller()); + + cfg.setClientConnectorConfiguration( + new ClientConnectorConfiguration() + .setSslEnabled(true) + .setUseIgniteSslContextFactory(setSslCtxFactoryToIgnite) + .setSslClientAuth(true) + .setSslContextFactory(setSslCtxFactoryToCli ? sslCtxFactory : null)); + + cfg.setSslContextFactory(setSslCtxFactoryToIgnite ? sslCtxFactory : null); + + return cfg; + } + + /** + * @throws Exception If failed. + */ + public void testConnection() throws Exception { + setSslCtxFactoryToCli = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try { + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456")) { + checkConnection(conn); + } + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testConnectionTrustAll() throws Exception { + setSslCtxFactoryToCli = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try { + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustAll=true")) { + checkConnection(conn); + } + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testConnectionUseIgniteFactory() throws Exception { + setSslCtxFactoryToIgnite = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try { + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456")) { + checkConnection(conn); + } + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testDefaultContext() throws Exception { + setSslCtxFactoryToCli = true; + // Factory return default SSL context + sslCtxFactory = new Factory<SSLContext>() { + @Override public SSLContext create() { + try { + return SSLContext.getDefault(); + } + catch (NoSuchAlgorithmException e) { + throw new IgniteException(e); + } + } + }; + + System.setProperty("javax.net.ssl.keyStore", CLI_KEY_STORE_PATH); + System.setProperty("javax.net.ssl.keyStorePassword", "123456"); + System.setProperty("javax.net.ssl.trustStore", TRUST_KEY_STORE_PATH); + System.setProperty("javax.net.ssl.trustStorePassword", "123456"); + + startGrids(1); + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require")) { + checkConnection(conn); + } + finally { + System.getProperties().remove("javax.net.ssl.keyStore"); + System.getProperties().remove("javax.net.ssl.keyStorePassword"); + System.getProperties().remove("javax.net.ssl.trustStore"); + System.getProperties().remove("javax.net.ssl.trustStorePassword"); + + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testContextFactory() throws Exception { + setSslCtxFactoryToCli = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslFactory=" + TestSSLFactory.class.getName())) { + checkConnection(conn); + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testSslServerAndPlainClient() throws Exception { + setSslCtxFactoryToCli = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try { + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1"); + + return null; + } + }, SQLException.class, "Failed to connect to Ignite cluster"); + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testInvalidKeystoreConfig() throws Exception { + setSslCtxFactoryToCli = true; + + startGrids(1); + + try { + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=invalid_client_keystore_path" + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Could not open client key store"); + + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=invalid_cli_passwd" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Could not open client key store"); + + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=invalid_trust_keystore_path" + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Could not open trusted key store"); + + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=invalid_trust_passwd"); + + return null; + } + }, SQLException.class, "Could not open trusted key store"); + + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslClientCertificateKeyStoreType=INVALID" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Could not create client KeyStore instance"); + + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStoreType=INVALID" + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Could not create trust KeyStore instance"); + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testUnknownClientCertificate() throws Exception { + setSslCtxFactoryToCli = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try { + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + Connection c = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Failed to SSL connect to server"); + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testUnsupportedSslProtocol() throws Exception { + setSslCtxFactoryToCli = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try { + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + Connection c = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslProtocol=TLSv1.3" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "TLSv1.3 is not a valid SSL protocol"); + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testInvalidKeyAlgorithm() throws Exception { + setSslCtxFactoryToCli = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try { + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + Connection c = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslKeyAlgorithm=INVALID" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Default algorithm definitions for TrustManager and/or KeyManager are invalid"); + } + finally { + stopAllGrids(); + } + } + + /** + * @throws Exception If failed. + */ + public void testInvalidKeyStoreType() throws Exception { + setSslCtxFactoryToCli = true; + sslCtxFactory = getTestSslContextFactory(); + + startGrids(1); + + try { + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + Connection c = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreType=PKCS12" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + TRUST_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Could not open client key store"); + } + finally { + stopAllGrids(); + } + } + + /** + * @param conn Connection to check. + * @throws SQLException On failed. + */ + public void checkConnection(Connection conn) throws SQLException { + assertEquals("PUBLIC", conn.getSchema()); + + try (Statement stmt = conn.createStatement()) { + ResultSet rs = stmt.executeQuery("SELECT 1"); + + assertTrue(rs.next()); + + assertEquals(1, rs.getInt(1)); + } + } + + /** + * @return Test SSL context factory. + */ + private static Factory<SSLContext> getTestSslContextFactory() { + SslContextFactory factory = new SslContextFactory(); + + factory.setKeyStoreFilePath(SRV_KEY_STORE_PATH); + factory.setKeyStorePassword("123456".toCharArray()); + factory.setTrustStoreFilePath(TRUST_KEY_STORE_PATH); + factory.setTrustStorePassword("123456".toCharArray()); + + return factory; + } + + /** + * + */ + public static class TestSSLFactory implements Factory<SSLSocketFactory> { + /** {@inheritDoc} */ + @Override public SSLSocketFactory create() { + return getTestSslContextFactory().create().getSocketFactory(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java index 0cf6ab6..ad98683 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java @@ -38,7 +38,7 @@ import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.binary.BinaryMarshaller; import org.apache.ignite.internal.jdbc.thin.JdbcThinConnection; import org.apache.ignite.internal.jdbc.thin.JdbcThinTcpIo; -import org.apache.ignite.internal.jdbc.thin.JdbcThinUtils; +import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; @@ -68,6 +68,14 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { /** */ private static final String URL = "jdbc:ignite:thin://127.0.0.1"; + /** Client key store path. */ + private static final String CLI_KEY_STORE_PATH = U.getIgniteHome() + + "/modules/clients/src/test/keystore/client.jks"; + + /** Server key store path. */ + private static final String SRV_KEY_STORE_PATH = U.getIgniteHome() + + "/modules/clients/src/test/keystore/server.jks"; + /** {@inheritDoc} */ @SuppressWarnings("deprecation") @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { @@ -263,16 +271,16 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { */ public void testTcpNoDelay() throws Exception { assertInvalid("jdbc:ignite:thin://127.0.0.1?tcpNoDelay=0", - "Failed to parse boolean property [name=tcpNoDelay, value=0]"); + "Invalid property value. [name=tcpNoDelay, val=0, choices=[true, false]]"); assertInvalid("jdbc:ignite:thin://127.0.0.1?tcpNoDelay=1", - "Failed to parse boolean property [name=tcpNoDelay, value=1]"); + "Invalid property value. [name=tcpNoDelay, val=1, choices=[true, false]]"); assertInvalid("jdbc:ignite:thin://127.0.0.1?tcpNoDelay=false1", - "Failed to parse boolean property [name=tcpNoDelay, value=false1]"); + "Invalid property value. [name=tcpNoDelay, val=false1, choices=[true, false]]"); assertInvalid("jdbc:ignite:thin://127.0.0.1?tcpNoDelay=true1", - "Failed to parse boolean property [name=tcpNoDelay, value=true1]"); + "Invalid property value. [name=tcpNoDelay, val=true1, choices=[true, false]]"); try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1")) { assertTrue(io(conn).connectionProperties().isTcpNoDelay()); @@ -303,7 +311,7 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { public void testAutoCloseServerCursorProperty() throws Exception { String url = "jdbc:ignite:thin://127.0.0.1?autoCloseServerCursor"; - String err = "Failed to parse boolean property [name=autoCloseServerCursor"; + String err = "Invalid property value. [name=autoCloseServerCursor"; assertInvalid(url + "=0", err); assertInvalid(url + "=1", err); @@ -1757,6 +1765,22 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { } /** + */ + public void testSslClientAndPlainServer() { + GridTestUtils.assertThrows(log, new Callable<Object>() { + @Override public Object call() throws Exception { + DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" + + "&sslClientCertificateKeyStoreUrl=" + CLI_KEY_STORE_PATH + + "&sslClientCertificateKeyStorePassword=123456" + + "&sslTrustCertificateKeyStoreUrl=" + SRV_KEY_STORE_PATH + + "&sslTrustCertificateKeyStorePassword=123456"); + + return null; + } + }, SQLException.class, "Failed to SSL connect to server"); + } + + /** * @return Savepoint. */ private Savepoint getFakeSavepoint() { http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinErrorsSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinErrorsSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinErrorsSelfTest.java index 90588c4..c01764c 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinErrorsSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinErrorsSelfTest.java @@ -48,7 +48,7 @@ public class JdbcThinErrorsSelfTest extends JdbcErrorsAbstractSelfTest { return null; } - }, "08001", "Failed to connect to Ignite cluster [host=unknown.host"); + }, "08001", "Failed to connect to server [host=unknown.host"); } /** http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java index d793484..169f85b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java @@ -23,6 +23,12 @@ import java.sql.SQLException; * Provide access and manipulations with connection JDBC properties. */ public interface ConnectionProperties { + /** SSL mode: DISABLE. */ + public static final String SSL_MODE_DISABLE = "disable"; + + /** SSL mode: REQUIRE. */ + public static final String SSL_MODE_REQUIRE = "require"; + /** * @return Host name or host's IP to connect. */ @@ -145,4 +151,204 @@ public interface ConnectionProperties { * @param skipReducerOnUpdate Skip reducer on update flag. */ public void setSkipReducerOnUpdate(boolean skipReducerOnUpdate); + + /** + * Gets SSL connection mode. + * + * @return Use SSL flag. + * @see #setSslMode(String). + */ + public String getSslMode(); + + /** + * Use SSL connection to Ignite node. In case set to {@code "require"} SSL context must be configured. + * {@link #setSslClientCertificateKeyStoreUrl} property and related properties must be set up + * or JSSE properties must be set up (see {@code javax.net.ssl.keyStore} and other {@code javax.net.ssl.*} + * properties) + * + * In case set to {@code "disable"} plain connection is used. + * Available modes: {@code "disable", "require"}. Default value is {@code "disable"} + * + * @param mode SSL mode. + */ + public void setSslMode(String mode); + + /** + * Gets protocol for secure transport. + * + * @return SSL protocol name. + */ + public String getSslProtocol(); + + /** + * Sets protocol for secure transport. If not specified, TLS protocol will be used. + * Protocols implementations supplied by JSEE: SSLv3 (SSL), TLSv1 (TLS), TLSv1.1, TLSv1.2 + * + * <p>See more at JSSE Reference Guide. + * + * @param sslProtocol SSL protocol name. + */ + public void setSslProtocol(String sslProtocol); + + /** + * Gets algorithm that will be used to create a key manager. + * + * @return Key manager algorithm. + */ + public String getSslKeyAlgorithm(); + + /** + * Sets key manager algorithm that will be used to create a key manager. Notice that in most cased default value + * suites well, however, on Android platform this value need to be set to <tt>X509<tt/>. + * Algorithms implementations supplied by JSEE: PKIX (X509 or SunPKIX), SunX509 + * + * <p>See more at JSSE Reference Guide. + * + * @param keyAlgorithm Key algorithm name. + */ + public void setSslKeyAlgorithm(String keyAlgorithm); + + /** + * Gets the key store URL. + * + * @return Client certificate KeyStore URL. + */ + public String getSslClientCertificateKeyStoreUrl(); + + /** + * Sets path to the key store file. This is a mandatory parameter since + * ssl context could not be initialized without key manager. + * + * In case {@link #getSslMode()} is {@code required} and key store URL isn't specified by Ignite properties + * (e.g. at JDBC URL) the JSSE property {@code javax.net.ssl.keyStore} will be used. + * + * @param url Client certificate KeyStore URL. + */ + public void setSslClientCertificateKeyStoreUrl(String url); + + /** + * Gets key store password. + * + * @return Client certificate KeyStore password. + */ + public String getSslClientCertificateKeyStorePassword(); + + /** + * Sets key store password. + * + * In case {@link #getSslMode()} is {@code required} and key store password isn't specified by Ignite properties + * (e.g. at JDBC URL) the JSSE property {@code javax.net.ssl.keyStorePassword} will be used. + * + * @param passwd Client certificate KeyStore password. + */ + public void setSslClientCertificateKeyStorePassword(String passwd); + + /** + * Gets key store type used for context creation. + * + * @return Client certificate KeyStore type. + */ + public String getSslClientCertificateKeyStoreType(); + + /** + * Sets key store type used in context initialization. + * + * In case {@link #getSslMode()} is {@code required} and key store type isn't specified by Ignite properties + * (e.g. at JDBC URL)the JSSE property {@code javax.net.ssl.keyStoreType} will be used. + * In case both Ignite properties and JSSE properties are not set the default 'JKS' type is used. + * + * <p>See more at JSSE Reference Guide. + * + * @param ksType Client certificate KeyStore type. + */ + public void setSslClientCertificateKeyStoreType(String ksType); + + /** + * Gets the trust store URL. + * + * @return Trusted certificate KeyStore URL. + */ + public String getSslTrustCertificateKeyStoreUrl(); + + /** + * Sets path to the trust store file. This is an optional parameter, + * however one of the {@code setSslTrustCertificateKeyStoreUrl(String)}, {@link #setSslTrustAll(boolean)} + * properties must be set. + * + * In case {@link #getSslMode()} is {@code required} and trust store URL isn't specified by Ignite properties + * (e.g. at JDBC URL) the JSSE property {@code javax.net.ssl.trustStore} will be used. + * + * @param url Trusted certificate KeyStore URL. + */ + public void setSslTrustCertificateKeyStoreUrl(String url); + + /** + * Gets trust store password. + * + * @return Trusted certificate KeyStore password. + */ + public String getSslTrustCertificateKeyStorePassword(); + + /** + * Sets trust store password. + * + * In case {@link #getSslMode()} is {@code required} and trust store password isn't specified by Ignite properties + * (e.g. at JDBC URL) the JSSE property {@code javax.net.ssl.trustStorePassword} will be used. + * + * @param passwd Trusted certificate KeyStore password. + */ + public void setSslTrustCertificateKeyStorePassword(String passwd); + + /** + * Gets trust store type. + * + * @return Trusted certificate KeyStore type. + */ + public String getSslTrustCertificateKeyStoreType(); + + /** + * Sets trust store type. + * + * In case {@link #getSslMode()} is {@code required} and trust store type isn't specified by Ignite properties + * (e.g. at JDBC URL) the JSSE property {@code javax.net.ssl.trustStoreType} will be used. + * In case both Ignite properties and JSSE properties are not set the default 'JKS' type is used. + * + * @param ksType Trusted certificate KeyStore type. + */ + public void setSslTrustCertificateKeyStoreType(String ksType); + + /** + * Gets trust any server certificate flag. + * + * @return Trust all certificates flag. + */ + public boolean isSslTrustAll(); + + /** + * Sets to {@code true} to trust any server certificate (revoked, expired or self-signed SSL certificates). + * + * <p> Defaults is {@code false}. + * + * Note: Do not enable this option in production you are ever going to use + * on a network you do not entirely trust. Especially anything going over the public internet. + * + * @param trustAll Trust all certificates flag. + */ + public void setSslTrustAll(boolean trustAll); + + /** + * Gets the class name of the custom implementation of the Factory<SSLSocketFactory>. + * + * @return Custom class name that implements Factory<SSLSocketFactory>. + */ + public String getSslFactory(); + + /** + * Sets the class name of the custom implementation of the Factory<SSLSocketFactory>. + * If {@link #getSslMode()} is {@code required} and factory is specified the custom factory will be used + * instead of JSSE socket factory. So, other SSL properties will be ignored. + * + * @param sslFactory Custom class name that implements Factory<SSLSocketFactory>. + */ + public void setSslFactory(String sslFactory); } http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java index 86ba2fa..471381b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java @@ -20,6 +20,7 @@ package org.apache.ignite.internal.jdbc.thin; import java.io.Serializable; import java.sql.DriverPropertyInfo; import java.sql.SQLException; +import java.util.Arrays; import java.util.Properties; import javax.naming.RefAddr; import javax.naming.Reference; @@ -39,14 +40,8 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa /** Host name property. */ private StringProperty host = new StringProperty( - "host", "Ignite node IP to connect", null, null, true, new PropertyValidator() { - private static final long serialVersionUID = 0L; - - @Override public void validate(String host) throws SQLException { - if (F.isEmpty(host)) - throw new SQLException("Host name is empty", SqlStateCode.CLIENT_CONNECTION_FAILED); - } - }); + "host", "Ignite node IP to connect", null, null, true, + new EmptyStringValidator("Host name is empty")); /** Connection port property. */ private IntegerProperty port = new IntegerProperty( @@ -96,11 +91,70 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa private BooleanProperty skipReducerOnUpdate = new BooleanProperty( "skipReducerOnUpdate", "Enable execution update queries on ignite server nodes", false, false); + /** SSL: Use SSL connection to Ignite node. */ + private StringProperty sslMode = new StringProperty("sslMode", + "The SSL mode of the connection", SSL_MODE_DISABLE, + new String[] {SSL_MODE_DISABLE, SSL_MODE_REQUIRE}, false, null); + + /** SSL: Client certificate key store url. */ + private StringProperty sslProtocol = new StringProperty("sslProtocol", + "SSL protocol name", null, null, false, null); + + /** SSL: Key algorithm name. */ + private StringProperty sslKeyAlgorithm = new StringProperty("sslKeyAlgorithm", + "SSL key algorithm name", "SunX509", null, false, null); + + /** SSL: Client certificate key store url. */ + private StringProperty sslClientCertificateKeyStoreUrl = + new StringProperty("sslClientCertificateKeyStoreUrl", + "Client certificate key store URL", + null, null, false, null); + + /** SSL: Client certificate key store password. */ + private StringProperty sslClientCertificateKeyStorePassword = + new StringProperty("sslClientCertificateKeyStorePassword", + "Client certificate key store password", + null, null, false, null); + + /** SSL: Client certificate key store type. */ + private StringProperty sslClientCertificateKeyStoreType = + new StringProperty("sslClientCertificateKeyStoreType", + "Client certificate key store type", + null, null, false, null); + + /** SSL: Trusted certificate key store url. */ + private StringProperty sslTrustCertificateKeyStoreUrl = + new StringProperty("sslTrustCertificateKeyStoreUrl", + "Trusted certificate key store URL", null, null, false, null); + + /** SSL Trusted certificate key store password. */ + private StringProperty sslTrustCertificateKeyStorePassword = + new StringProperty("sslTrustCertificateKeyStorePassword", + "Trusted certificate key store password", null, null, false, null); + + /** SSL: Trusted certificate key store type. */ + private StringProperty sslTrustCertificateKeyStoreType = + new StringProperty("sslTrustCertificateKeyStoreType", + "Trusted certificate key store type", + null, null, false, null); + + /** SSL: Trust all certificates. */ + private BooleanProperty sslTrustAll = new BooleanProperty("sslTrustAll", + "Trust all certificates",false, false); + + /** SSL: Custom class name that implements Factory<SSLSocketFactory>. */ + private StringProperty sslFactory = new StringProperty("sslFactory", + "Custom class name that implements Factory<SSLSocketFactory>", null, null, false, null); + /** Properties array. */ private final ConnectionProperty [] propsArray = { host, port, distributedJoins, enforceJoinOrder, collocated, replicatedOnly, autoCloseServerCursor, - tcpNoDelay, lazy, socketSendBuffer, socketReceiveBuffer, skipReducerOnUpdate + tcpNoDelay, lazy, socketSendBuffer, socketReceiveBuffer, skipReducerOnUpdate, + sslMode, sslProtocol, sslKeyAlgorithm, + sslClientCertificateKeyStoreUrl, sslClientCertificateKeyStorePassword, sslClientCertificateKeyStoreType, + sslTrustCertificateKeyStoreUrl, sslTrustCertificateKeyStorePassword, sslTrustCertificateKeyStoreType, + sslTrustAll, sslFactory }; /** {@inheritDoc} */ @@ -223,6 +277,116 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa skipReducerOnUpdate.setValue(val); } + /** {@inheritDoc} */ + @Override public String getSslMode() { + return sslMode.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslMode(String mode) { + sslMode.setValue(mode); + } + + /** {@inheritDoc} */ + @Override public String getSslProtocol() { + return sslProtocol.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslProtocol(String sslProtocol) { + this.sslProtocol.setValue(sslProtocol); + } + + /** {@inheritDoc} */ + @Override public String getSslKeyAlgorithm() { + return sslKeyAlgorithm.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslKeyAlgorithm(String keyAlgorithm) { + sslKeyAlgorithm.setValue(keyAlgorithm); + } + + /** {@inheritDoc} */ + @Override public String getSslClientCertificateKeyStoreUrl() { + return sslClientCertificateKeyStoreUrl.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslClientCertificateKeyStoreUrl(String url) { + sslClientCertificateKeyStoreUrl.setValue(url); + } + + /** {@inheritDoc} */ + @Override public String getSslClientCertificateKeyStorePassword() { + return sslClientCertificateKeyStorePassword.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslClientCertificateKeyStorePassword(String passwd) { + sslClientCertificateKeyStorePassword.setValue(passwd); + } + + /** {@inheritDoc} */ + @Override public String getSslClientCertificateKeyStoreType() { + return sslClientCertificateKeyStoreType.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslClientCertificateKeyStoreType(String ksType) { + sslClientCertificateKeyStoreType.setValue(ksType); + } + + /** {@inheritDoc} */ + @Override public String getSslTrustCertificateKeyStoreUrl() { + return sslTrustCertificateKeyStoreUrl.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslTrustCertificateKeyStoreUrl(String url) { + sslTrustCertificateKeyStoreUrl.setValue(url); + } + + /** {@inheritDoc} */ + @Override public String getSslTrustCertificateKeyStorePassword() { + return sslTrustCertificateKeyStorePassword.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslTrustCertificateKeyStorePassword(String passwd) { + sslTrustCertificateKeyStorePassword.setValue(passwd); + } + + /** {@inheritDoc} */ + @Override public String getSslTrustCertificateKeyStoreType() { + return sslTrustCertificateKeyStoreType.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslTrustCertificateKeyStoreType(String ksType) { + sslTrustCertificateKeyStoreType.setValue(ksType); + } + + /** {@inheritDoc} */ + @Override public boolean isSslTrustAll() { + return sslTrustAll.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslTrustAll(boolean trustAll) { + this.sslTrustAll.setValue(trustAll); + } + + /** {@inheritDoc} */ + @Override public String getSslFactory() { + return sslFactory.value(); + } + + /** {@inheritDoc} */ + @Override public void setSslFactory(String sslFactory) { + this.sslFactory.setValue(sslFactory); + } + /** * @param props Environment properties. * @throws SQLException On error. @@ -273,6 +437,30 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa /** * */ + private static class EmptyStringValidator implements PropertyValidator { + /** */ + private static final long serialVersionUID = 0L; + + /** Error message. */ + private final String errMsg; + + /** + * @param msg Error message. + */ + private EmptyStringValidator(String msg) { + errMsg = msg; + } + + /** {@inheritDoc} */ + @Override public void validate(String val) throws SQLException { + if (F.isEmpty(val)) + throw new SQLException(errMsg, SqlStateCode.CLIENT_CONNECTION_FAILED); + } + } + + /** + * + */ private abstract static class ConnectionProperty implements Serializable { /** */ private static final long serialVersionUID = 0L; @@ -365,6 +553,8 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa SqlStateCode.CLIENT_CONNECTION_FAILED); } + checkChoices(strVal); + if (validator != null) validator.validate(strVal); @@ -374,6 +564,25 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa } /** + * @param strVal Checked value. + * @throws SQLException On check error. + */ + protected void checkChoices(String strVal) throws SQLException { + if (strVal == null) + return; + + if (choices != null) { + for (String ch : choices) { + if (ch.equalsIgnoreCase(strVal)) + return; + } + + throw new SQLException("Invalid property value. [name=" + name + ", val=" + strVal + + ", choices=" + Arrays.toString(choices) + ']', SqlStateCode.CLIENT_CONNECTION_FAILED); + } + } + + /** * @param ref Reference object. * @throws SQLException On error. */ @@ -626,7 +835,10 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa /** {@inheritDoc} */ @Override void init(String str) throws SQLException { - val = str; + if (str == null) + val = (String)dfltVal; + else + val = str; } /** {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java index 999c793..ac9925d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java @@ -122,6 +122,11 @@ public class JdbcThinConnection implements Connection { cliIo.start(); } + catch (SQLException e) { + cliIo.close(); + + throw e; + } catch (Exception e) { cliIo.close(); http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinSSLUtil.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinSSLUtil.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinSSLUtil.java new file mode 100644 index 0000000..d259883 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinSSLUtil.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.ignite.internal.jdbc.thin; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.FileSystems; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.cache.configuration.Factory; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import org.apache.ignite.internal.processors.odbc.SqlStateCode; +import org.apache.ignite.internal.util.typedef.F; + +/** + * SSL utility method to create SSL connetion. + */ +public class JdbcThinSSLUtil { + /** Trust all certificates manager. */ + private final static X509TrustManager TRUST_ALL_MANAGER = new X509TrustManager() { + @Override public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) { + } + + @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) { + } + }; + + /** + * + */ + private JdbcThinSSLUtil() { + // No-op. + } + + /** + * @param connProps Connection properties. + * @throws SQLException On connection error or reject. + * @throws IOException On IO error in handshake. + * @return SSL socket. + */ + public static SSLSocket createSSLSocket(ConnectionProperties connProps) throws SQLException, IOException { + try { + SSLSocketFactory sslSocketFactory = getSSLSocketFactory(connProps); + + SSLSocket sock = (SSLSocket)sslSocketFactory.createSocket(connProps.getHost(), connProps.getPort()); + + sock.setUseClientMode(true); + + sock.startHandshake(); + + return sock; + } + catch (IOException e) { + throw new SQLException("Failed to SSL connect to server [host=" + connProps.getHost() + + ", port=" + connProps.getPort() + ']', SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + } + + /** + * @param connProps Connection properties. + * @return SSL socket factory. + * @throws SQLException On error. + */ + private static SSLSocketFactory getSSLSocketFactory(ConnectionProperties connProps) throws SQLException { + String sslFactory = connProps.getSslFactory(); + String cliCertKeyStoreUrl = connProps.getSslClientCertificateKeyStoreUrl(); + String cliCertKeyStorePwd = connProps.getSslClientCertificateKeyStorePassword(); + String cliCertKeyStoreType = connProps.getSslClientCertificateKeyStoreType(); + String trustCertKeyStoreUrl = connProps.getSslTrustCertificateKeyStoreUrl(); + String trustCertKeyStorePwd = connProps.getSslTrustCertificateKeyStorePassword(); + String trustCertKeyStoreType = connProps.getSslTrustCertificateKeyStoreType(); + String sslProtocol = connProps.getSslProtocol(); + String keyAlgorithm = connProps.getSslKeyAlgorithm(); + + if (!F.isEmpty(sslFactory)) { + try { + Class<Factory<SSLSocketFactory>> cls = (Class<Factory<SSLSocketFactory>>)JdbcThinSSLUtil.class + .getClassLoader().loadClass(sslFactory); + + Factory<SSLSocketFactory> f = cls.newInstance(); + + return f.create(); + } + catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + throw new SQLException("Could not fount SSL factory class: " + sslFactory, + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + } + + if (cliCertKeyStoreUrl == null && cliCertKeyStorePwd == null && cliCertKeyStoreType == null + && trustCertKeyStoreUrl == null && trustCertKeyStorePwd == null && trustCertKeyStoreType == null + && sslProtocol == null) { + try { + return SSLContext.getDefault().getSocketFactory(); + } + catch (NoSuchAlgorithmException e) { + throw new SQLException("Could not create default SSL context", + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + } + + if (cliCertKeyStoreUrl == null) + cliCertKeyStoreUrl = System.getProperty("javax.net.ssl.keyStore"); + + if (cliCertKeyStorePwd == null) + cliCertKeyStorePwd = System.getProperty("javax.net.ssl.keyStorePassword"); + + if (cliCertKeyStoreType == null) + cliCertKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType", "JKS"); + + if (trustCertKeyStoreUrl == null) + trustCertKeyStoreUrl = System.getProperty("javax.net.ssl.trustStore"); + + if (trustCertKeyStorePwd == null) + trustCertKeyStorePwd = System.getProperty("javax.net.ssl.trustStorePassword"); + + if (trustCertKeyStoreType == null) + trustCertKeyStoreType = System.getProperty("javax.net.ssl.trustStoreType", "JKS"); + + if (sslProtocol == null) + sslProtocol = "TLS"; + + if (!F.isEmpty(cliCertKeyStoreUrl)) + cliCertKeyStoreUrl = checkAndConvertUrl(cliCertKeyStoreUrl); + + if (!F.isEmpty(trustCertKeyStoreUrl)) + trustCertKeyStoreUrl = checkAndConvertUrl(trustCertKeyStoreUrl); + + TrustManagerFactory tmf; + KeyManagerFactory kmf; + + KeyManager[] kms = null; + + try { + tmf = TrustManagerFactory.getInstance(keyAlgorithm); + + kmf = KeyManagerFactory.getInstance(keyAlgorithm); + } + catch (NoSuchAlgorithmException e) { + throw new SQLException("Default algorithm definitions for TrustManager and/or KeyManager are invalid." + + " Check java security properties file.", SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + + InputStream ksInputStream = null; + + try { + if (!F.isEmpty(cliCertKeyStoreUrl) && !F.isEmpty(cliCertKeyStoreType)) { + KeyStore clientKeyStore = KeyStore.getInstance(cliCertKeyStoreType); + + URL ksURL = new URL(cliCertKeyStoreUrl); + + char[] password = (cliCertKeyStorePwd == null) ? new char[0] : cliCertKeyStorePwd.toCharArray(); + + ksInputStream = ksURL.openStream(); + + clientKeyStore.load(ksInputStream, password); + + kmf.init(clientKeyStore, password); + + kms = kmf.getKeyManagers(); + } + } + catch (UnrecoverableKeyException e) { + throw new SQLException("Could not recover keys from client keystore.", + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (NoSuchAlgorithmException e) { + throw new SQLException("Unsupported keystore algorithm.", SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (KeyStoreException e) { + throw new SQLException("Could not create client KeyStore instance.", + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (CertificateException e) { + throw new SQLException("Could not load client key store. [storeType=" + cliCertKeyStoreType + + ", cliStoreUrl=" + cliCertKeyStoreUrl + ']', SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (MalformedURLException e) { + throw new SQLException("Invalid client key store URL. [url=" + cliCertKeyStoreUrl + ']', + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (IOException e) { + throw new SQLException("Could not open client key store.[url=" + cliCertKeyStoreUrl + ']', + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + finally { + if (ksInputStream != null) { + try { + ksInputStream.close(); + } + catch (IOException e) { + // can't close input stream, but keystore can be properly initialized + // so we shouldn't throw this exception + } + } + } + + InputStream tsInputStream = null; + + List<TrustManager> tms; + + if (connProps.isSslTrustAll()) + tms = Collections.<TrustManager>singletonList(TRUST_ALL_MANAGER); + else { + tms = new ArrayList<>(); + + try { + KeyStore trustKeyStore = null; + + if (!F.isEmpty(trustCertKeyStoreUrl) && !F.isEmpty(trustCertKeyStoreType)) { + char[] trustStorePassword = (trustCertKeyStorePwd == null) ? new char[0] + : trustCertKeyStorePwd.toCharArray(); + + tsInputStream = new URL(trustCertKeyStoreUrl).openStream(); + + trustKeyStore = KeyStore.getInstance(trustCertKeyStoreType); + + trustKeyStore.load(tsInputStream, trustStorePassword); + } + + tmf.init(trustKeyStore); + + TrustManager[] origTms = tmf.getTrustManagers(); + + Collections.addAll(tms, origTms); + } + catch (NoSuchAlgorithmException e) { + throw new SQLException("Unsupported keystore algorithm.", SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (KeyStoreException e) { + throw new SQLException("Could not create trust KeyStore instance.", + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (CertificateException e) { + throw new SQLException("Could not load trusted key store. [storeType=" + trustCertKeyStoreType + + ", cliStoreUrl=" + trustCertKeyStoreUrl + ']', SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (MalformedURLException e) { + throw new SQLException("Invalid trusted key store URL. [url=" + trustCertKeyStoreUrl + ']', + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (IOException e) { + throw new SQLException("Could not open trusted key store. [url=" + cliCertKeyStoreUrl + ']', + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + finally { + if (tsInputStream != null) { + try { + tsInputStream.close(); + } + catch (IOException e) { + // can't close input stream, but keystore can be properly initialized + // so we shouldn't throw this exception + } + } + } + } + + assert tms.size() != 0; + + try { + SSLContext sslContext = SSLContext.getInstance(sslProtocol); + + sslContext.init(kms, tms.toArray(new TrustManager[tms.size()]), null); + + return sslContext.getSocketFactory(); + } + catch (NoSuchAlgorithmException e) { + throw new SQLException(sslProtocol + " is not a valid SSL protocol.", SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + catch (KeyManagementException e) { + throw new SQLException("Cannot init SSL context.", SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + } + + /** + * @param url URL or path to check and convert to URL. + * @return URL. + * @throws SQLException If URL is invalid. + */ + private static String checkAndConvertUrl(String url) throws SQLException { + try { + return new URL(url).toString(); + } + catch (MalformedURLException e) { + try { + return FileSystems.getDefault().getPath(url).toUri().toURL().toString(); + } + catch (MalformedURLException e1) { + throw new SQLException("Invalid keystore UR: " + url, + SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/42161cdd/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java index fec218e..4b2c477 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java @@ -20,27 +20,9 @@ package org.apache.ignite.internal.jdbc.thin; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; -import java.io.InputStream; import java.net.InetSocketAddress; -import java.net.MalformedURLException; import java.net.Socket; -import java.net.URL; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.binary.BinaryReaderExImpl; import org.apache.ignite.internal.binary.BinaryWriterExImpl; @@ -57,7 +39,6 @@ import org.apache.ignite.internal.processors.odbc.jdbc.JdbcQueryMetadataRequest; import org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest; import org.apache.ignite.internal.processors.odbc.jdbc.JdbcResponse; import org.apache.ignite.internal.util.ipc.loopback.IpcClientTcpEndpoint; -import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteProductVersion; @@ -130,7 +111,25 @@ public class JdbcThinTcpIo { * @throws IOException On IO error in handshake. */ public void start() throws SQLException, IOException { - Socket sock = new Socket(); + Socket sock; + + if (ConnectionProperties.SSL_MODE_REQUIRE.equalsIgnoreCase(connProps.getSslMode())) + sock = JdbcThinSSLUtil.createSSLSocket(connProps); + else if (ConnectionProperties.SSL_MODE_DISABLE.equalsIgnoreCase(connProps.getSslMode())) { + sock = new Socket(); + + try { + sock.connect(new InetSocketAddress(connProps.getHost(), connProps.getPort())); + } + catch (IOException e) { + throw new SQLException("Failed to connect to server [host=" + connProps.getHost() + + ", port=" + connProps.getPort() + ']', SqlStateCode.CLIENT_CONNECTION_FAILED, e); + } + } + else { + throw new SQLException("Unknown sslMode. [sslMode=" + connProps.getSslMode() + ']', + SqlStateCode.CLIENT_CONNECTION_FAILED); + } if (connProps.getSocketSendBuffer() != 0) sock.setSendBufferSize(connProps.getSocketSendBuffer()); @@ -141,14 +140,12 @@ public class JdbcThinTcpIo { sock.setTcpNoDelay(connProps.isTcpNoDelay()); try { - sock.connect(new InetSocketAddress(connProps.getHost(), connProps.getPort())); - endpoint = new IpcClientTcpEndpoint(sock); out = new BufferedOutputStream(endpoint.outputStream()); in = new BufferedInputStream(endpoint.inputStream()); } - catch (IOException | IgniteCheckedException e) { + catch (IgniteCheckedException e) { throw new SQLException("Failed to connect to server [host=" + connProps.getHost() + ", port=" + connProps.getPort() + ']', SqlStateCode.CLIENT_CONNECTION_FAILED, e); }
