Repository: microsoft/mssql-jdbc
Pull request #1856 (2022-06-27 22:17:36)

Added validation for serverName field
Added validation for server name field for https://github.com/microsoft/mssql-jdbc/issues/1853


Patch:
diff --git a/pom.xml b/pom.xml
index b7e6e6f8..4097a177 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,16 +43,17 @@
 		<!-- Allowed values for excluded Groups here - - - - - - - - - - - - - 
 			xJDBC42 - - - - - - For tests not compatible with JDBC 42 Specifications 
 			xGradle - - - - - - For tests not compatible with Gradle Script - - - - - 
-			xSQLv11 - - - - - - For tests not compatible with SQL Server 2012
+			xSQLv11 - - - - - - For tests not compatible with SQL Server 2012 - - - - 
 			xSQLv12 - - - - - - For tests not compatible with SQL Server 2008 R2 - 2014 
 			xSQLv14 - - - - - - For tests not compatible with SQL Server 2016 - 2017 
 			xSQLv15 - - - - - - For tests not compatible with SQL Server 2019 - - - - 
 			xAzureSQLDB - - - - For tests not compatible with Azure SQL Database - - 
 			xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse - 
 			xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance 
-			NTLM  - - - - - - - For tests using NTLM Authentication mode (excluded by default) 
-			reqExternalSetup  - For tests requiring external setup (excluded by default)
-			clientCertAuth  - - For tests requiring client certificate authentication setup (excluded by default)
+			NTLM - - - - - - For tests using NTLM Authentication mode (excluded by default) 
+			reqExternalSetup - For tests requiring external setup (excluded by default) 
+			clientCertAuth - - For tests requiring client certificate authentication 
+			setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - - 
 			- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 			Default testing enabled with SQL Server 2019 (SQLv15) -->
 		<excludedGroups>xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth</excludedGroups>
@@ -61,14 +62,15 @@
 		<releaseExt>-preview</releaseExt>
 
 		<!-- Driver Dependencies -->
-		<osgi.core.version>6.0.0</osgi.core.version>
-		<azure.keyvault.version>4.4.1</azure.keyvault.version>
-		<azure.identity.version>1.5.0</azure.identity.version>
-		<osgi.comp.version>5.0.0</osgi.comp.version>
-		<antlr.runtime.version>4.9.3</antlr.runtime.version>
-		<google.gson.version>2.8.9</google.gson.version>
-		<bouncycastle.bcprov.version>1.70</bouncycastle.bcprov.version>
-		<bouncycastle.bcpkix.version>1.70</bouncycastle.bcpkix.version>
+		<org.osgi.core.version>6.0.0</org.osgi.core.version>
+		<azure-security-keyvault-keys.version>4.4.4</azure-security-keyvault-keys.version>
+		<azure-identity.version>1.5.3</azure-identity.version>
+		<msal.version>1.13.0</msal.version>
+		<org.osgi.compendium.version>5.0.0</org.osgi.compendium.version>
+		<antlr-runtime.version>4.9.3</antlr-runtime.version>
+		<com.google.code.gson.version>2.9.0</com.google.code.gson.version>
+		<bcprov-jdk15on.version>1.70</bcprov-jdk15on.version>
+		<bcpkix-jdk15on.version>1.70</bcpkix-jdk15on.version>
 
 		<!-- JUnit Test Dependencies -->
 		<junit.platform.version>[1.3.2, 1.5.2]</junit.platform.version>
@@ -88,13 +90,13 @@
 		<dependency>
 			<groupId>com.azure</groupId>
 			<artifactId>azure-security-keyvault-keys</artifactId>
-			<version>${azure.keyvault.version}</version>
+			<version>${azure-security-keyvault-keys.version}</version>
 			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>com.azure</groupId>
 			<artifactId>azure-identity</artifactId>
-			<version>${azure.identity.version}</version>
+			<version>${azure-identity.version}</version>
 			<optional>true</optional>
 			<exclusions>
 				<exclusion>
@@ -104,11 +106,17 @@
 			</exclusions>
 		</dependency>
 
+		<dependency>
+			<groupId>com.microsoft.azure</groupId>
+			<artifactId>msal4j</artifactId>
+			<version>1.13.0</version>
+		</dependency>
+
 		<!-- dependencies for ANTLR -->
 		<dependency>
 			<groupId>org.antlr</groupId>
 			<artifactId>antlr4-runtime</artifactId>
-			<version>${antlr.runtime.version}</version>
+			<version>${antlr-runtime.version}</version>
 			<optional>true</optional>
 		</dependency>
 
@@ -116,33 +124,33 @@
 		<dependency>
 			<groupId>com.google.code.gson</groupId>
 			<artifactId>gson</artifactId>
-			<version>${google.gson.version}</version>
+			<version>${com.google.code.gson.version}</version>
 			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcprov-jdk15on</artifactId>
-			<version>${bouncycastle.bcprov.version}</version>
+			<version>${bcprov-jdk15on.version}</version>
 			<optional>true</optional>
 		</dependency>
 		<!-- dependencies for Client Certificate Authentication -->
 		<dependency>
 			<groupId>org.bouncycastle</groupId>
 			<artifactId>bcpkix-jdk15on</artifactId>
-			<version>${bouncycastle.bcpkix.version}</version>
+			<version>${bcpkix-jdk15on.version}</version>
 			<optional>true</optional>
 		</dependency>
 		<!-- dependencies provided by an OSGi-Framework -->
 		<dependency>
 			<groupId>org.osgi</groupId>
 			<artifactId>org.osgi.core</artifactId>
-			<version>${osgi.core.version}</version>
+			<version>${org.osgi.core.version}</version>
 			<scope>provided</scope>
 		</dependency>
 		<dependency>
 			<groupId>org.osgi</groupId>
 			<artifactId>org.osgi.compendium</artifactId>
-			<version>${osgi.comp.version}</version>
+			<version>${org.osgi.compendium.version}</version>
 			<scope>provided</scope>
 		</dependency>
 
@@ -539,12 +547,12 @@
 						</goals>
 					</execution>
 					<execution>
-				        <id>default-report</id>
-				        <phase>prepare-package</phase>
-				        <goals>
-				            <goal>report</goal>
-				        </goals>
-				    </execution>
+						<id>default-report</id>
+						<phase>prepare-package</phase>
+						<goals>
+							<goal>report</goal>
+						</goals>
+					</execution>
 				</executions>
 				<configuration>
 					<excludes>
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
index c5a71666..5fbc526b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
@@ -2705,13 +2705,15 @@ final class SocketFinder {
      */
     private InetSocketAddress getInetAddressByIPPreference(String hostName,
             int portNumber) throws IOException, SQLServerException {
-        InetSocketAddress addr = InetSocketAddress.createUnresolved(hostName, portNumber);
-        for (int i = 0; i < addressList.size(); i++) {
-            addr = new InetSocketAddress(addressList.get(i), portNumber);
-            if (!addr.isUnresolved())
-                return addr;
+        synchronized (addressList) {
+            InetSocketAddress addr = InetSocketAddress.createUnresolved(hostName, portNumber);
+            for (int i = 0; i < addressList.size(); i++) {
+                addr = new InetSocketAddress(addressList.get(i), portNumber);
+                if (!addr.isUnresolved())
+                    return addr;
+            }
+            return addr;
         }
-        return addr;
     }
 
     /**
@@ -2794,17 +2796,19 @@ final class SocketFinder {
      *        Boolean switch for IPv6 first
      */
     private void fillAddressList(InetAddress[] addresses, boolean ipv6first) {
-        addressList.clear();
-        if (ipv6first) {
-            for (InetAddress addr : addresses) {
-                if (addr instanceof Inet6Address) {
-                    addressList.add(addr);
+        synchronized (addressList) {
+            addressList.clear();
+            if (ipv6first) {
+                for (InetAddress addr : addresses) {
+                    if (addr instanceof Inet6Address) {
+                        addressList.add(addr);
+                    }
                 }
-            }
-        } else {
-            for (InetAddress addr : addresses) {
-                if (addr instanceof Inet4Address) {
-                    addressList.add(addr);
+            } else {
+                for (InetAddress addr : addresses) {
+                    if (addr instanceof Inet4Address) {
+                        addressList.add(addr);
+                    }
                 }
             }
         }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
index b038e348..62271473 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
@@ -107,6 +107,7 @@ public final class SQLServerResource extends ListResourceBundle {
         {"R_statementMustBeExecuted", "The statement must be executed before any results can be obtained."},
         {"R_modeSuppliedNotValid", "The supplied mode is not valid."},
         {"R_errorConnectionString", "The connection string contains a badly formed name or value."},
+        {"R_errorServerName", "The serverName connection property value {0} is badly formed."},
         {"R_errorProcessingComplexQuery", "An error occurred while processing the complex query."},
         {"R_invalidOffset", "The offset {0} is not valid."},
         {"R_nullConnection", "The connection URL is null."},
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java b/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java
index 0b5de8d8..2ed97efb 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java
@@ -31,9 +31,6 @@ final class SecureStringUtil {
     /* authentication tag length in bits */
     static final int TAG_LENGTH = 16;
 
-    /* initialization vector */
-    byte[] iv;
-
     /** secret key for encryption/decryption */
     SecretKeySpec secretKey;
 
@@ -68,7 +65,6 @@ final class SecureStringUtil {
      *         if error
      */
     private SecureStringUtil() throws SQLServerException {
-        iv = new byte[IV_LENGTH];
         try {
             // generate key */
             KeyGenerator keygen = KeyGenerator.getInstance(KEYGEN_ALGORITHEM);
@@ -99,6 +95,8 @@ final class SecureStringUtil {
     byte[] getEncryptedBytes(char[] chars) throws SQLServerException {
         if (chars == null)
             return null;
+
+        byte[] iv = new byte[IV_LENGTH];
         SecureRandom random = new SecureRandom();
         random.nextBytes(iv);
         GCMParameterSpec ivParamSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv);
@@ -107,7 +105,10 @@ final class SecureStringUtil {
             encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParamSpec);
 
             byte[] cipherText = encryptCipher.doFinal(Util.charsToBytes(chars));
-            return cipherText;
+            byte[] bytes = new byte[iv.length + cipherText.length];
+            System.arraycopy(iv, 0, bytes, 0, iv.length);
+            System.arraycopy(cipherText, 0, bytes, iv.length, cipherText.length);
+            return bytes;
         } catch (Exception e) {
             MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
             Object[] msgArgs = {e.getMessage()};
@@ -127,13 +128,17 @@ final class SecureStringUtil {
     char[] getDecryptedChars(byte[] bytes) throws SQLServerException {
         if (bytes == null)
             return null;
+
+        byte[] iv = new byte[IV_LENGTH];
+        System.arraycopy(bytes, 0, iv, 0, IV_LENGTH);
+
         GCMParameterSpec ivParamSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv);
 
         byte[] plainText = null;
         try {
             decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivParamSpec);
 
-            plainText = decryptCipher.doFinal(bytes);
+            plainText = decryptCipher.doFinal(bytes, IV_LENGTH, bytes.length - IV_LENGTH);
             return Util.bytesToChars(plainText);
         } catch (Exception e) {
             MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DecryptionFailed"));
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
index 6dba1393..ca91b2a8 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
@@ -284,6 +284,14 @@ final class Util {
                     if (ch == ';' || ch == ':' || ch == '\\') {
                         // non escaped trim the string
                         String property = result.toString().trim();
+
+                        if (property.contains("=")) {
+                            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorServerName"));
+                            Object[] msgArgs = {property};
+                            SQLServerException.makeFromDriverError(null, null,
+                                    form.format(msgArgs), null, true);
+                        }
+
                         if (property.length() > 0) {
                             p.put(SQLServerDriverStringProperty.SERVER_NAME.toString(), property);
                             if (logger.isLoggable(Level.FINE)) {


Test patch:
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
index 68999a4f..de1dd184 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
@@ -12,6 +12,7 @@ import java.io.IOException;
 import java.io.Reader;
 import java.sql.Clob;
 import java.sql.Connection;
+import java.sql.DriverManager;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
@@ -1007,4 +1008,29 @@ public class SQLServerConnectionTest extends AbstractTest {
             }
         }
     }
+
+    @Test
+    public void testServerNameField() throws SQLException {
+        String subProtocol = "jdbc:sqlserver://";
+        int indexOfFirstDelimiter = connectionString.indexOf(";");
+        int indexOfLastDelimiter = connectionString.lastIndexOf(";");
+
+        String[] serverNameAndPort = connectionString.substring(subProtocol.length(), indexOfFirstDelimiter).split(":");
+        String connectionProperties = connectionString.substring(indexOfFirstDelimiter, indexOfLastDelimiter + 1);
+        String loginTimeout = "loginTimout=15";
+
+        // Server name field is empty but serverName connection property is set, should pass
+        String emptyServerNameField = subProtocol + connectionProperties + "serverName=" + serverNameAndPort[0] + ";";
+
+        // A loginTimeout connection property is passed into the server name field, should fail
+        String invalidServerNameField = subProtocol + loginTimeout + connectionProperties;
+
+        try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(emptyServerNameField)) {
+        }
+
+        try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(invalidServerNameField)) {
+        } catch (SQLException e) {
+            assertTrue(e.getMessage().matches(TestUtils.formatErrorMsg("R_errorServerName")));
+        }
+    }
 }
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java
index bddd9a26..835bab8e 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java
@@ -66,6 +66,8 @@ public class UtilTest {
     }
 
     private static String testString = "A ß € 嗨 𝄞 🙂ăѣ𝔠ծềſģȟᎥ𝒋ǩľḿꞑȯ𝘱𝑞𝗋𝘴ȶ𝞄𝜈ψ𝒙𝘆𝚣1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~𝘈Ḇ𝖢𝕯٤ḞԍНǏ𝙅ƘԸⲘ𝙉০Ρ𝗤Ɍ𝓢ȚЦ𝒱Ѡ𝓧ƳȤѧᖯć𝗱ễ𝑓𝙜Ⴙ𝞲𝑗𝒌ļṃŉо𝞎𝒒ᵲꜱ𝙩ừ𝗏ŵ𝒙𝒚ź1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~АḂⲤ𝗗𝖤𝗙ꞠꓧȊ𝐉𝜥ꓡ𝑀𝑵Ǭ𝙿𝑄Ŗ𝑆𝒯𝖴𝘝𝘞ꓫŸ𝜡ả𝘢ƀ𝖼ḋếᵮℊ𝙝Ꭵ𝕛кιṃդⱺ𝓅𝘲𝕣𝖘ŧ𝑢ṽẉ𝘅ყž1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~Ѧ𝙱ƇᗞΣℱԍҤ١𝔍К𝓛𝓜ƝȎ𝚸𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈𝚭𝜶Ꮟçძ𝑒𝖿𝗀ḧ𝗂𝐣ҝɭḿ𝕟𝐨𝝔𝕢ṛ𝓼тú𝔳ẃ⤬𝝲𝗓1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~𝖠Β𝒞𝘋𝙴𝓕ĢȞỈ𝕵ꓗʟ𝙼ℕ০𝚸𝗤ՀꓢṰǓⅤ𝔚Ⲭ𝑌𝙕𝘢𝕤";
+    private static String testString2 = "ssdfsdflkjh9u0345)*&)(*&%$";
+    private static String testString3 = "ss345(*&^%oujdf.';lk2345(*&()*$#~!`1\\]wer><.,/?dfsdflkjh9u0345)*&)(*&%$";
 
     @Test
     public void testArrayConversions() {
@@ -79,9 +81,17 @@ public class UtilTest {
 
     @Test
     public void testSecureStringUtil() throws SQLException {
+        // Encrypt/decrypt multiple values in overlapping orders
         byte[] bytes = SecureStringUtil.getInstance().getEncryptedBytes(testString.toCharArray());
+        byte[] bytes2 = SecureStringUtil.getInstance().getEncryptedBytes(testString2.toCharArray());
         String end = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes));
+        byte[] bytes3 = SecureStringUtil.getInstance().getEncryptedBytes(testString3.toCharArray());
+        String end3 = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes3));
+        String end2 = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes2));
+
         assertEquals(testString, end);
+        assertEquals(testString2, end2);
+        assertEquals(testString3, end3);
     }
 
     private void writeAndReadLong(long valueToTest) {
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
index 5fc74e47..13189728 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
@@ -16,7 +16,6 @@ import java.sql.BatchUpdateException;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
-import java.sql.PreparedStatement;
 import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
@@ -29,7 +28,6 @@ import com.microsoft.sqlserver.jdbc.SQLServerConnection;
 import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
 import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement;
 import com.microsoft.sqlserver.jdbc.TestResource;
-import com.microsoft.sqlserver.jdbc.TestUtils;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;