diff --git a/pom.xml b/pom.xml index c5d123987..4aaae90e9 100644 --- a/pom.xml +++ b/pom.xml @@ -70,51 +70,57 @@ org.junit.platform junit-platform-console - 1.0.0-M3 + 1.0.0-M4 test org.junit.platform junit-platform-commons - 1.0.0-M3 + 1.0.0-M4 test org.junit.platform junit-platform-engine - 1.0.0-M3 + 1.0.0-M4 test org.junit.platform junit-platform-launcher - 1.0.0-M3 + 1.0.0-M4 test org.junit.platform junit-platform-runner - 1.0.0-M3 + 1.0.0-M4 test org.junit.platform junit-platform-surefire-provider - 1.0.0-M3 + 1.0.0-M4 test org.junit.jupiter junit-jupiter-api - 5.0.0-M3 + 5.0.0-M4 test org.junit.jupiter junit-jupiter-engine - 5.0.0-M3 + 5.0.0-M4 test + + org.junit.jupiter + junit-jupiter-params + 5.0.0-M4 + test + com.zaxxer HikariCP @@ -283,4 +289,13 @@ + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.20 + + + diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 891b5355f..442c3c508 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -1577,6 +1577,7 @@ void enableSSL(String host, boolean isFips = false; String trustStoreType = null; String fipsProvider = null; + String sslProtocol = null; // If anything in here fails, terminate the connection and throw an exception try { @@ -1595,7 +1596,10 @@ void enableSSL(String host, } fipsProvider = con.activeConnectionProperties.getProperty(SQLServerDriverStringProperty.FIPS_PROVIDER.toString()); - isFips = Boolean.valueOf(con.activeConnectionProperties.getProperty(SQLServerDriverBooleanProperty.FIPS.toString())); + isFips = Boolean.valueOf(con.activeConnectionProperties.getProperty(SQLServerDriverBooleanProperty.FIPS.toString())); + + sslProtocol = con.activeConnectionProperties.getProperty(SQLServerDriverStringProperty.SSL_PROTOCOL.toString(), + SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue()); if (isFips) { validateFips(fipsProvider, trustStoreType, trustStoreFileName); @@ -1644,7 +1648,7 @@ void enableSSL(String host, if (logger.isLoggable(Level.FINEST)) logger.finest(toString() + " Finding key store interface"); - if (isFips) { + if (isFips && !StringUtils.isEmpty(fipsProvider)) { ks = KeyStore.getInstance(trustStoreType, fipsProvider); } else { @@ -1723,9 +1727,9 @@ void enableSSL(String host, SSLContext sslContext = null; if (logger.isLoggable(Level.FINEST)) - logger.finest(toString() + " Getting TLS or better SSL context"); + logger.finest(toString() + " Getting TLS or better SSL context with " + sslProtocol ); - sslContext = SSLContext.getInstance("TLS"); + sslContext = SSLContext.getInstance(sslProtocol); sslContextProvider = sslContext.getProvider(); if (logger.isLoggable(Level.FINEST)) @@ -1826,6 +1830,8 @@ void enableSSL(String host, *
  • trustServerCertificate should be false *
  • if certificate is not installed FIPSProvider & TrustStoreType should be present. * + * @since 6.2.x It will not generate any error. We found out for some FIPS implementation like IBM one should not pass FIPSProvider. + * * @param fipsProvider * FIPS Provider * @param trustStoreType @@ -1877,7 +1883,9 @@ private void validateFips(final String fipsProvider, } if (!isValid) { - throw new SQLServerException(strError, null, 0, null); + // In some FIPS implementations e.g. BouncyCastle we need to pass FIPSProvider for getting appropriate KeyStore. + // While IBM FIPS implementation, one should not pass FIPSProvider, instead rely on security configuration file. + logger.warning(strError); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index caee05b97..811ec145e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -44,10 +44,10 @@ import java.util.Map; import java.util.Properties; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; import javax.sql.XAConnection; @@ -1466,6 +1466,16 @@ else if (0 == requestedPacketSize) if (null != sPropValue) { setEnablePrepareOnFirstPreparedStatementCall(booleanPropertyOn(sPropKey, sPropValue)); } + + sPropKey = SQLServerDriverStringProperty.SSL_PROTOCOL.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey, SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue()); + if ("TLS".equalsIgnoreCase(sPropValue) || "TLSv1.1".equalsIgnoreCase(sPropValue) || "TLSv1.2".equalsIgnoreCase(sPropValue)) { + activeConnectionProperties.setProperty(sPropKey, sPropValue); + }else { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidSSLProtocol")); + Object[] msgArgs = {sPropValue}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); + } FailoverInfo fo = null; String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index 891fe3b90..0fcb8c7bf 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -593,6 +593,14 @@ public void setFIPSProvider(String fipsProvider) { public String getFIPSProvider() { return getStringProperty(connectionProps, SQLServerDriverStringProperty.FIPS_PROVIDER.toString(), null); } + + public void setSSLProtocol(String sslProtocol) { + setStringProperty(connectionProps, SQLServerDriverStringProperty.SSL_PROTOCOL.toString(), sslProtocol); + } + + public String getSSLProtocol() { + return getStringProperty(connectionProps, SQLServerDriverStringProperty.SSL_PROTOCOL.toString(), SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue()); + } // The URL property is exposed for backwards compatibility reasons. Also, several // Java Application servers expect a setURL function on the DataSource and set it diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 646af582b..e9cfc8095 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -247,7 +247,7 @@ enum SQLServerDriverStringProperty KEY_STORE_SECRET ("keyStoreSecret", ""), KEY_STORE_LOCATION ("keyStoreLocation", ""), FIPS_PROVIDER ("fipsProvider", ""), - ; + SSL_PROTOCOL ("sslProtocol", "TLS") ; private String name; private String defaultValue; @@ -381,6 +381,8 @@ public final class SQLServerDriver implements java.sql.Driver { new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.ENABLE_PREPARE_ON_FIRST_PREPARED_STATEMENT.toString(), Boolean.toString(SQLServerConnection.getDefaultEnablePrepareOnFirstPreparedStatementCall()), false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverIntProperty.SERVER_PREPARED_STATEMENT_DISCARD_THRESHOLD.toString(), Integer.toString(SQLServerConnection.getDefaultServerPreparedStatementDiscardThreshold()), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(), SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue(), false, null), + new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SELECT_METHOD.toString(), SQLServerDriverStringProperty.SELECT_METHOD.getDefaultValue(), false, new String[] {"direct", "cursor"}), + new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SSL_PROTOCOL.toString(), SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue(), false, new String[] {"TLS", "TLSv1.1", "TLSv1.2"}), }; // Properties that can only be set by using Properties. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 79ed19168..f5b615d32 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -377,11 +377,13 @@ protected Object[][] getContents() { {"R_fipsPropertyDescription", "Determines if enable FIPS compilant SSL connection between the client and the server."}, {"R_invalidFipsConfig", "Could not enable FIPS."}, {"R_invalidFipsEncryptConfig", "Could not enable FIPS due to either encrypt is not true or using trusted certificate settings."}, - {"R_invalidFipsProviderConfig", "Could not enable FIPS due to invalid FIPSProvider or TrustStoreType."}, + {"R_invalidFipsProviderConfig", "FIPS may not enable due to invalid FIPSProvider or TrustStoreType."}, {"R_serverPreparedStatementDiscardThreshold", "The serverPreparedStatementDiscardThreshold {0} is not valid."}, {"R_kerberosLoginFailedForUsername", "Cannot login with Kerberos principal {0}, check your credentials. {1}"}, {"R_kerberosLoginFailed", "Kerberos Login failed: {0} due to {1} ({2})"}, {"R_StoredProcedureNotFound", "Could not find stored procedure ''{0}''."}, {"R_jaasConfigurationNamePropertyDescription", "Login configuration file for Kerberos authentication."}, + {"R_sslProtocolPropertyDescription", "Able to set SSL protocol from TLS, TLSv1.1 & TLSv1.2. Default is TLS"}, + {"R_invalidSSLProtocol", "SSL Protocol {0} is not valid. Supporting only TLS, TLSv1.1 & TLSv1.2."} }; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java index 83f8a9f71..dbd3e0c4b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; @@ -39,10 +40,11 @@ public static void init() { /** * Test after setting TrustServerCertificate as true. - * + * This is disable due to IBM FIPS environment behavior. We should not mandate FIPS Provider & other parameters. * @throws Exception */ @Test + @Disabled public void fipsTrustServerCertificateTest() throws Exception { try { Properties props = buildConnectionProperties(); @@ -62,6 +64,7 @@ public void fipsTrustServerCertificateTest() throws Exception { * * @throws Exception */ + @Disabled @Test public void fipsEncryptTest() throws Exception { try { @@ -82,6 +85,7 @@ public void fipsEncryptTest() throws Exception { * * @throws Exception */ + @Disabled @Test public void fipsProviderTest() throws Exception { try { @@ -120,6 +124,7 @@ public void fipsPropertyTest() throws Exception { * @throws Exception */ @Test + @Disabled public void fipsDataSourcePropertyTest() throws Exception { SQLServerDataSource ds = new SQLServerDataSource(); setDataSourceProperties(ds); @@ -137,6 +142,7 @@ public void fipsDataSourcePropertyTest() throws Exception { * Test after removing encrypt in FIPS Data Source. */ @Test + @Disabled public void fipsDatSourceEncrypt() { try { SQLServerDataSource ds = new SQLServerDataSource(); @@ -159,6 +165,7 @@ public void fipsDatSourceEncrypt() { * @throws Exception */ @Test + @Disabled public void fipsDataSourceProviderTest() throws Exception { try { SQLServerDataSource ds = new SQLServerDataSource(); @@ -180,6 +187,7 @@ public void fipsDataSourceProviderTest() throws Exception { * @throws Exception */ @Test + @Disabled public void fipsDataSourceTrustServerCertificateTest() throws Exception { try { SQLServerDataSource ds = new SQLServerDataSource(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/TestSSLProtocol.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/TestSSLProtocol.java new file mode 100644 index 000000000..cfbfe6953 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/TestSSLProtocol.java @@ -0,0 +1,86 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ +package com.microsoft.sqlserver.jdbc.unit; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.Statement; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.SQLServerException; +import com.microsoft.sqlserver.jdbc.StringUtils; +import com.microsoft.sqlserver.testframework.AbstractTest; + +/** + * This unit test case targeted to test new functionality of configuring SSLProtocol for connecting to SQL Server. + */ +@RunWith(JUnitPlatform.class) +public class TestSSLProtocol extends AbstractTest { + + Connection con = null; + Statement stmt = null; + + /** + * + * @param sslProtocol + * @throws Exception + */ + @DisplayName("TestForSupportedProtocols") + @ParameterizedTest + @ValueSource(strings={"TLS","TLSv1.1","TLSv1.2"}) + public void testSuportedProtocols(String sslProtocol) throws Exception { + String url = connectionString + ";sslProtocol=" + sslProtocol; + con = DriverManager.getConnection(url); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + assertTrue(!StringUtils.isEmpty(dbmd.getDatabaseProductName())); + } + + /** + * Connect with valid but not supported protocols + * @param sslProtocol + * @throws Exception + */ + @DisplayName("TestForNotSupportedProtocols") + @ParameterizedTest + @ValueSource(strings={"SSLv1","SSLv2","SSLv3", "SSLv2Hello","SSL"}) + public void testWithUnSupportedProtocols(String sslProtocol) throws Exception { + try { + String url = connectionString + ";sslProtocol=" + sslProtocol; + con = DriverManager.getConnection(url); + assertFalse(true, "Any protocol other than TLS, TLSv1.1 & TLSv1.2 should throw Exception"); + }catch(SQLServerException e) { + assertTrue(true,"Should throw exception"); + String errMsg = "SSL Protocol "+ sslProtocol +" is not valid. Supporting only TLS, TLSv1.1 & TLSv1.2."; + + assertTrue(errMsg.equals(e.getMessage()),"Message should be from SQL Server resources : " + e.getMessage()); + } + } + + /** + * Test with wrong values. + * @param sslProtocol + * @throws Exception + */ + @DisplayName("TestForWrongValues") + @ParameterizedTest + @ValueSource(strings={"SSLv1111","SSLv2222","SSLv3111", "SSLv2Hello1111","TLSv1.11","TLSv2.4","HTTPS"}) + public void testConnectWithWrongProtocol(String sslProtocol) throws Exception { + testWithUnSupportedProtocols(sslProtocol); + } +}