Repository: microsoft/mssql-jdbc
Pull request #2150 (2023-06-20 16:28:59)

Change how IBM JDK is determined to fix tests and future-proof
Change from looking at vendor name to testing for default file in IBM jdk.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b3b3eef..b0bc8192 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 
+## [12.4.0] Stable Release
+### Fixed issues
+- Revert #2051 (Big decimal precision) / #2116 (Fix for bigDecimal values between 0 and 1 having too high of a precision) [#2176](https://github.com/microsoft/mssql-jdbc/pull/2176)
+- Fixed server certificate validation for encrypt=strict [#2174](https://github.com/microsoft/mssql-jdbc/pull/2174)
+- Fixed issues identified by SonarQube [#2145](https://github.com/microsoft/mssql-jdbc/pull/2145)
+
 ## [12.3.1] Preview Release
 ### Added
 - Added a case to throw XAER_RMFAIL on connection reset for XA transactions [2118](https://github.com/microsoft/mssql-jdbc/pull/2118)
diff --git a/README.md b/README.md
index 2096a1b7..e542079c 100644
--- a/README.md
+++ b/README.md
@@ -82,7 +82,7 @@ We're now on the Maven Central Repository. Add the following to your POM file to
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.2.0.jre11</version>
+	<version>12.4.0.jre11</version>
 </dependency>
 ```
 The driver can be downloaded from [Microsoft](https://aka.ms/downloadmssqljdbc).
@@ -93,7 +93,7 @@ To get the latest version of the driver, add the following to your POM file:
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.2.0.jre11</version>
+	<version>12.4.0.jre11</version>
 </dependency>
 ```
 
@@ -128,7 +128,7 @@ Projects that require either of the two features need to explicitly declare the
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.2.0.jre11</version>
+	<version>12.4.0.jre11</version>
 	<scope>compile</scope>
 </dependency>
 
@@ -146,7 +146,7 @@ Projects that require either of the two features need to explicitly declare the
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.2.0.jre11</version>
+	<version>12.4.0.jre11</version>
 	<scope>compile</scope>
 </dependency>
 
@@ -173,7 +173,7 @@ When setting 'useFmtOnly' property to 'true' for establishing a connection or cr
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.2.0.jre11</version>
+	<version>12.4.0.jre11</version>
 </dependency>
 
 <dependency>
@@ -213,7 +213,7 @@ Preview releases happen approximately monthly between stable releases. This give
 You can see what is going into a future release by monitoring [Milestones](https://github.com/Microsoft/mssql-jdbc/milestones) in the repository.
 
 ### Version conventions
-Starting with 6.0, stable versions have an even minor version. For example, 6.0, 6.2, 6.4, 7.0, 7.2, 7.4, 8.2, 8.4, 9.2, 9.4, 10.2, 11.2, 12.2. Preview versions have an odd minor version. For example, 6.1, 6.3, 6.5, 7.1, 7.3, 8.1, 9.1, 10.1, 11.1, 12.1, and so on.
+Starting with 6.0, stable versions have an even minor version. For example, 6.0, 6.2, 6.4, 7.0, 7.2, 7.4, 8.2, 8.4, 9.2, 9.4, 10.2, 11.2, 12.2, 12.4. Preview versions have an odd minor version. For example, 6.1, 6.3, 6.5, 7.1, 7.3, 8.1, 9.1, 10.1, 11.1, 12.1, 12.3, and so on.
 
 ## Contributors 
 Special thanks to everyone who has contributed to the project. 
@@ -229,13 +229,13 @@ Here are our Top 15 contributors from the community:
 - simon04 (Simon Legner)
 - gstojsic
 - cosmofrit
-- harawata- shayaantx
-- (Iwao AVE!)
-- nsidhaye (Nikhil Sidhaye)
+- mmimica  (Milan Mimica)
+- harawata (Iwao AVE!)
 - rPraml (Roland Praml)
+- laeubi (Christoph Laubrich)
 - worldtiki (Daniel Albuquerque)
 - shayaantx
-- mfriesen (Mike Friesen)
+- mnhubspot 
 
 
 ## License
diff --git a/build.gradle b/build.gradle
index 0d7365a9..4ce995b8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,7 +11,8 @@
 
 apply plugin: 'java'
 
-version = '12.4.0-SNAPSHOT'
+version = '12.5.0-SNAPSHOT'
+def releaseExt = '-preview'
 def jreVersion = ""
 def testOutputDir = file("build/classes/java/test")
 def archivesBaseName = 'mssql-jdbc'
@@ -85,7 +86,7 @@ if(hasProperty('buildProfile') && buildProfile == "jre8") {
 	}
 }
 
-jar.archiveFileName = "${archivesBaseName}-${version}.${jreVersion}-preview.jar"
+jar.archiveFileName = "${archivesBaseName}-${version}.${jreVersion}${releaseExt}.jar"
 jar {
 	manifest {
         attributes 'Title': "Microsoft JDBC Driver ${archiveVersion} for SQL Server",
diff --git a/pom.xml b/pom.xml
index b1b6e0af..c7fb25e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.4.0-SNAPSHOT</version>
+	<version>12.5.0-SNAPSHOT</version>
 	<packaging>jar</packaging>
 	<name>Microsoft JDBC Driver for SQL Server</name>
 	<description>
@@ -38,6 +38,7 @@
 			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 - - - - 
+			xSQLv16 - - - - - - For tests not compatible with SQL Server 2022 - - - - 
 			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 
@@ -52,14 +53,14 @@
 		<releaseExt>-preview</releaseExt>
 		<!-- Driver Dependencies -->
 		<org.osgi.core.version>6.0.0</org.osgi.core.version>
-		<azure-security-keyvault-keys.version>4.6.1</azure-security-keyvault-keys.version>
+		<azure-security-keyvault-keys.version>4.6.4</azure-security-keyvault-keys.version>
 		<azure-identity.version>1.9.0</azure-identity.version>
 		<msal.version>1.13.8</msal.version>
 		<osgi.jdbc.version>1.1.0</osgi.jdbc.version>
 		<antlr-runtime.version>4.9.3</antlr-runtime.version>
 		<com.google.code.gson.version>2.10.1</com.google.code.gson.version>
-		<bcprov-jdk15on.version>1.70</bcprov-jdk15on.version>
-		<bcpkix-jdk15on.version>1.70</bcpkix-jdk15on.version>
+		<bcprov-jdk18on.version>1.76</bcprov-jdk18on.version>
+		<bcpkix-jdk18on.version>1.76</bcpkix-jdk18on.version>
 		<!-- JUnit Test Dependencies -->
 		<junit.platform.version>[1.3.2, 1.9.0]</junit.platform.version>
 		<junit.jupiter.version>5.8.2</junit.jupiter.version>
@@ -67,7 +68,7 @@
 		<dbcp2.version>2.7.0</dbcp2.version>
 		<slf4j.nop.version>1.7.30</slf4j.nop.version>
 		<gemini.mock.version>2.1.0.RELEASE</gemini.mock.version>
-		<h2.version>2.1.214</h2.version>
+		<h2.version>2.2.220</h2.version>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.reporting.outputEncoding>${project.build.sourceEncoding}</project.reporting.outputEncoding>
 		<enforcer.skip>false</enforcer.skip>
@@ -113,15 +114,15 @@
 		</dependency>
 		<dependency>
 			<groupId>org.bouncycastle</groupId>
-			<artifactId>bcprov-jdk15on</artifactId>
-			<version>${bcprov-jdk15on.version}</version>
+			<artifactId>bcprov-jdk18on</artifactId>
+			<version>${bcprov-jdk18on.version}</version>
 			<optional>true</optional>
 		</dependency>
 		<!-- dependencies for Client Certificate Authentication -->
 		<dependency>
 			<groupId>org.bouncycastle</groupId>
-			<artifactId>bcpkix-jdk15on</artifactId>
-			<version>${bcpkix-jdk15on.version}</version>
+			<artifactId>bcpkix-jdk18on</artifactId>
+			<version>${bcpkix-jdk18on.version}</version>
 			<optional>true</optional>
 		</dependency>
 		<!-- dependencies provided by an OSGi-Framework -->
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
index 9d054803..bb709307 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
@@ -1693,7 +1693,7 @@ final class TDSChannel implements Serializable {
             // Otherwise, we'll validate the certificate using a real TrustManager obtained
             // from the a security provider that is capable of validating X.509 certificates.
             else {
-                if (isTDS8) {
+                if (isTDS8 && serverCert != null) {
                     if (logger.isLoggable(Level.FINEST))
                         logger.finest(toString() + " Verify server certificate for TDS 8");
 
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
index 89745b76..aa64c87b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
@@ -531,36 +531,9 @@ final class Parameter {
                             param.typeDefinition = SSType.DECIMAL.toString() + "(" + valueLength + "," + scale + ")";
                         }
                     } else {
-                        if (dtv.getJavaType() == JavaType.BIGDECIMAL && null != dtv.getSetterValue()) {
-                            String[] plainValueArray
-                                    = ((BigDecimal) dtv.getSetterValue()).abs().toPlainString().split("\\.");
-
-                            // Precision is computed as opposed to using BigDecimal.precision(). This is because the
-                            // BigDecimal method can lead to inaccurate results.
-                            int calculatedPrecision;
-
-                            // If the string array has two parts, e.g .the input was a decimal, check if the first
-                            // part is a 0. For BigDecimals with leading zeroes, the leading zero does not count towards
-                            // precision. For all other decimals, we include the integer portion as part of the precision
-                            // When the string array has just one part, we only look at that part to compute precision.
-                            if (plainValueArray.length == 2) {
-                                if (plainValueArray[0].length() == 1 && (Integer.parseInt(plainValueArray[0]) == 0)) {
-                                    calculatedPrecision = plainValueArray[1].length();
-                                } else {
-                                    calculatedPrecision = plainValueArray[0].length() + plainValueArray[1].length();
-                                }
-                            } else  {
-                                calculatedPrecision = plainValueArray[0].length();
-                            }
-
-                            param.typeDefinition = SSType.DECIMAL.toString() + "(" + calculatedPrecision + "," +
-                                    (plainValueArray.length == 2 ? plainValueArray[1].length() : 0) + ")";
-                        } else {
-                            param.typeDefinition = SSType.DECIMAL.toString() + "("
-                                    + SQLServerConnection.MAX_DECIMAL_PRECISION + "," + scale + ")";
-                        }
+                        param.typeDefinition = SSType.DECIMAL.toString() + "("
+                                + SQLServerConnection.MAX_DECIMAL_PRECISION + "," + scale + ")";
                     }
-
                     break;
 
                 case MONEY:
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
index 57bbfb6f..8adbb06f 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
@@ -7,7 +7,7 @@ package com.microsoft.sqlserver.jdbc;
 
 final class SQLJdbcVersion {
     static final int MAJOR = 12;
-    static final int MINOR = 4;
+    static final int MINOR = 5;
     static final int PATCH = 0;
     static final int BUILD = 0;
     /*
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCertificateUtils.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCertificateUtils.java
index 884f638e..e51328cf 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCertificateUtils.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCertificateUtils.java
@@ -281,9 +281,8 @@ final class SQLServerCertificateUtils {
      */
     static void validateServerCerticate(X509Certificate cert, String certFile) throws CertificateException {
         try (InputStream is = fileToStream(certFile)) {
-            if (!CertificateFactory.getInstance("X509").generateCertificate(is).getPublicKey()
-                    .equals(cert.getPublicKey())) {
-                MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_publicKeyMismatch"));
+            if (!CertificateFactory.getInstance("X509").generateCertificate(is).equals(cert)) {
+                MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_serverCertError"));
                 Object[] msgArgs = {certFile};
                 throw new CertificateException(form.format(msgArgs));
             }
@@ -353,8 +352,7 @@ final class SQLServerCertificateUtils {
     }
 
     private static KeyManager[] readPKCS12Certificate(String certPath,
-            String keyPassword) throws NoSuchAlgorithmException, CertificateException, IOException, 
-                UnrecoverableKeyException, KeyStoreException, SQLServerException {
+            String keyPassword) throws NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException, KeyStoreException, SQLServerException {
 
         KeyStore keyStore = loadPKCS12KeyStore(certPath, keyPassword);
         KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(SUN_X_509);
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java
index 73f37f0b..89a433f0 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java
@@ -429,7 +429,7 @@ public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerCol
         // Validate the signature
         if (!azureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath)) {
             MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CEKSignatureNotMatchCMK"));
-            Object[] msgArgs = {masterKeyPath};
+            Object[] msgArgs = {Util.byteToHexDisplayString(signature), masterKeyPath};
             throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
         }
 
@@ -876,6 +876,9 @@ public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerCol
             return cmkMetadataSignatureVerificationCache.get(key);
         }
 
+        byte[] signedHash = null;
+        boolean isValid = false;
+
         try {
             MessageDigest md = MessageDigest.getInstance(SHA_256);
             md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
@@ -889,19 +892,65 @@ public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerCol
             }
 
             // Sign the hash
-            byte[] signedHash = azureKeyVaultSignHashedData(dataToVerify, masterKeyPath);
+            signedHash = azureKeyVaultSignHashedData(dataToVerify, masterKeyPath);
             if (null == signedHash) {
                 throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null);
             }
 
             // Validate the signature
-            boolean isValid = azureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath);
+            isValid = azureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath);
             cmkMetadataSignatureVerificationCache.put(key, isValid);
+        } catch (NoSuchAlgorithmException e) {
+            throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e);
+        } catch (SQLServerException e) {
+            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_SignatureNotMatch"));
+            Object[] msgArgs = {Util.byteToHexDisplayString(signature),
+                    (signedHash != null) ? Util.byteToHexDisplayString(signedHash) : " ", masterKeyPath,
+                    ": " + e.getMessage()};
+            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+        }
+
+        if (!isValid) {
+            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_SignatureNotMatch"));
+            Object[] msgArgs = {Util.byteToHexDisplayString(signature), Util.byteToHexDisplayString(signedHash),
+                    masterKeyPath, ""};
+            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+        }
+        return isValid;
+    }
+
+    public byte[] signColumnMasterKeyMetadata(String masterKeyPath,
+            boolean allowEnclaveComputations) throws SQLServerException {
+        if (!allowEnclaveComputations) {
+            return null;
+        }
+
+        KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
+
+        byte[] signedHash = null;
+        try {
+            MessageDigest md = MessageDigest.getInstance(SHA_256);
+            md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+            md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+            // value of allowEnclaveComputations is always true here
+            md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+
+            byte[] dataToVerify = md.digest();
+            if (null == dataToVerify) {
+                throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null);
+            }
 
-            return isValid;
+            // Sign the hash
+            signedHash = azureKeyVaultSignHashedData(dataToVerify, masterKeyPath);
         } catch (NoSuchAlgorithmException e) {
             throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e);
         }
+
+        if (null == signedHash) {
+            throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null);
+        }
+
+        return signedHash;
     }
 
     private static List<String> getTrustedEndpoints() {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java
index 5fbc111a..319b2b26 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java
@@ -113,6 +113,83 @@ public class SQLServerColumnEncryptionJavaKeyStoreProvider extends SQLServerColu
         return plainCEK;
     }
 
+    @Override
+    public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations,
+            byte[] signature) throws SQLServerException {
+
+        if (!allowEnclaveComputations) {
+            return false;
+        }
+
+        KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
+        CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath);
+
+        byte[] signedHash = null;
+        boolean isValid = false;
+
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+            md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+            // value of allowEnclaveComputations is always true here
+            md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+
+            byte[] dataToVerify = md.digest();
+            Signature sig = Signature.getInstance("SHA256withRSA");
+
+            sig.initSign((PrivateKey) certificateDetails.privateKey);
+            sig.update(dataToVerify);
+
+            signedHash = sig.sign();
+
+            sig.initVerify(certificateDetails.certificate.getPublicKey());
+            sig.update(dataToVerify);
+            isValid = sig.verify(signature);
+        } catch (NoSuchAlgorithmException e) {
+            throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e);
+        } catch (InvalidKeyException | SignatureException e) {
+            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_SignatureNotMatch"));
+            Object[] msgArgs = {Util.byteToHexDisplayString(signature),
+                    (signedHash != null) ? Util.byteToHexDisplayString(signedHash) : " ", masterKeyPath,
+                    ": " + e.getMessage()};
+            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+        }
+
+        if (!isValid) {
+            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_SignatureNotMatch"));
+            Object[] msgArgs = {Util.byteToHexDisplayString(signature), Util.byteToHexDisplayString(signedHash),
+                    masterKeyPath, ""};
+            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+        }
+        return isValid;
+    }
+
+    public byte[] signColumnMasterKeyMetadata(String masterKeyPath,
+            boolean allowEnclaveComputations) throws SQLServerException {
+        if (!allowEnclaveComputations)
+            return null;
+
+        KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
+        CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath);
+
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+            md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+            // value of allowEnclaveComputations is always true here
+            md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
+
+            Signature sig = Signature.getInstance("SHA256withRSA");
+            sig.initSign((PrivateKey) certificateDetails.privateKey);
+            sig.update(md.digest());
+
+            return sig.sign();
+
+        } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
+            throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e);
+        }
+    }
+
     private CertificateDetails getCertificateDetails(String masterKeyPath) throws SQLServerException {
         FileInputStream fis = null;
         KeyStore keyStore = null;
@@ -319,45 +396,16 @@ public class SQLServerColumnEncryptionJavaKeyStoreProvider extends SQLServerColu
      * Verify signature against certificate
      */
     private boolean rsaVerifySignature(byte[] dataToVerify, byte[] signature,
-            CertificateDetails certificateDetails) throws SQLServerException {
-        try {
-            Signature sig = Signature.getInstance("SHA256withRSA");
-            sig.initSign((PrivateKey) certificateDetails.privateKey);
-            sig.update(dataToVerify);
-            byte[] signedHash = sig.sign();
+            CertificateDetails certificateDetails) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
+        Signature sig = Signature.getInstance("SHA256withRSA");
 
-            sig.initVerify(certificateDetails.certificate.getPublicKey());
-            sig.update(dataToVerify);
+        sig.initSign((PrivateKey) certificateDetails.privateKey);
+        sig.update(dataToVerify);
 
-            return sig.verify(signedHash);
+        byte[] signedHash = sig.sign();
 
-        } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
-            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_VerifySignatureFailed"));
-            Object[] msgArgs = {e.getMessage()};
-            throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
-        }
+        sig.initVerify(certificateDetails.certificate.getPublicKey());
+        sig.update(dataToVerify);
+        return sig.verify(signature);
     }
-
-    @Override
-    public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations,
-            byte[] signature) throws SQLServerException {
-
-        if (!allowEnclaveComputations)
-            return false;
-
-        KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
-        CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath);
-
-        try {
-            MessageDigest md = MessageDigest.getInstance("SHA-256");
-            md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
-            md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
-            // value of allowEnclaveComputations is always true here
-            md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
-            return rsaVerifySignature(md.digest(), signature, certificateDetails);
-        } catch (NoSuchAlgorithmException e) {
-            throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e);
-        }
-    }
-
 }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
index ec3e86c3..9ccc44ee 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
@@ -7389,7 +7389,7 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
     String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[] params,
             boolean isReturnValueSyntax) {
         final int MAX_PARAM_NAME_LEN = 6;
-        char[] sqlDst = new char[sqlSrc.length() + params.length * (MAX_PARAM_NAME_LEN + OUT.length)];
+        char[] sqlDst = new char[sqlSrc.length() + (params.length * (MAX_PARAM_NAME_LEN + OUT.length)) + (params.length * 2)];
         int dstBegin = 0;
         int srcBegin = 0;
         int nParam = 0;
@@ -7403,8 +7403,8 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
             if (sqlSrc.length() == srcEnd)
                 break;
 
-            dstBegin += makeParamName(nParam++, sqlDst, dstBegin);
-            srcBegin = srcEnd + 1;
+            dstBegin += makeParamName(nParam++, sqlDst, dstBegin, true);
+            srcBegin = srcEnd + 1 <= sqlSrc.length() - 1 && sqlSrc.charAt(srcEnd + 1) == ' ' ? srcEnd + 2 : srcEnd + 1;
 
             if (params[paramIndex++].isOutput() && (!isReturnValueSyntax || paramIndex > 1)) {
                 System.arraycopy(OUT, 0, sqlDst, dstBegin, OUT.length);
@@ -7423,33 +7423,88 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
      * @param name
      *        the parameter name
      * @param offset
+     *        the offset
+     * @param isPreparedSQL
+     *        if the param is for build preparedSQL
      * @return int
      */
-    static int makeParamName(int nParam, char[] name, int offset) {
-        name[offset + 0] = '@';
-        name[offset + 1] = 'P';
+    static int makeParamName(int nParam, char[] name, int offset, boolean isPreparedSQL) {
+        buildParamInitial(name, offset, isPreparedSQL);
         if (nParam < 10) {
-            name[offset + 2] = (char) ('0' + nParam);
-            return 3;
+            return buildParamLt10(nParam, name, offset, isPreparedSQL);
         } else {
             if (nParam < 100) {
-                int nBase = 2;
-                while (true) { // make a char[] representation of the param number 2.26
-                    if (nParam < nBase * 10) {
-                        name[offset + 2] = (char) ('0' + (nBase - 1));
-                        name[offset + 3] = (char) ('0' + (nParam - ((nBase - 1) * 10)));
-                        return 4;
-                    }
-                    nBase++;
-                }
+                return buildParamLt100(nParam, name, offset, isPreparedSQL);
             } else {
-                String sParam = "" + nParam;
-                sParam.getChars(0, sParam.length(), name, offset + 2);
-                return 2 + sParam.length();
+                return buildParamMt100(nParam, name, offset, isPreparedSQL);
+            }
+        }
+    }
+
+    private static void buildParamInitial(char[] name, int offset, boolean isPreparedSQL) {
+        int preparedSQLOffset = 0;
+        if (isPreparedSQL) {
+            name[offset + 0] = ' ';
+            preparedSQLOffset++;
+        }
+        name[offset + preparedSQLOffset + 0] = '@';
+        name[offset + preparedSQLOffset + 1] = 'P';
+    }
+
+    private static int buildParamLt10(int nParam, char[] name, int offset, boolean isPreparedSQL) {
+        int preparedSQLOffset = 0;
+
+        if (isPreparedSQL) {
+            preparedSQLOffset++;
+        }
+
+        name[offset + preparedSQLOffset + 2] = (char) ('0' + nParam);
+
+        if (isPreparedSQL) {
+            name[offset + 4] = ' ';
+            return 5;
+        }
+
+        return 3;
+    }
+
+    private static int buildParamLt100(int nParam, char[] name, int offset, boolean isPreparedSQL) {
+        int nBase = 2;
+        int preparedSQLOffset = 0;
+
+        if (isPreparedSQL) {
+            preparedSQLOffset = 1;
+        }
+
+        while (true) { // make a char[] representation of the param number 2.26
+            if (nParam < nBase * 10) {
+                name[offset + preparedSQLOffset + 2] = (char) ('0' + (nBase - 1));
+                name[offset + preparedSQLOffset + 3] = (char) ('0' + (nParam - ((nBase - 1) * 10)));
+
+                if (isPreparedSQL) {
+                    name[offset + 5] = ' ';
+                    preparedSQLOffset++;
+                }
+
+                return 4 + preparedSQLOffset;
             }
+            nBase++;
         }
     }
 
+    private static int buildParamMt100(int nParam, char[] name, int offset, boolean isPreparedSQL) {
+        int preparedSQLOffset = 0;
+        String sParam = Integer.toString(nParam);
+
+        if (isPreparedSQL) {
+            preparedSQLOffset++;
+            sParam = nParam + " ";
+        }
+
+        sParam.getChars(0, sParam.length(), name, offset + preparedSQLOffset + 2);
+        return 2 + sParam.length() + preparedSQLOffset;
+    }
+
     /**
      * Notify any interested parties (e.g. pooling managers) of a ConnectionEvent activity on the connection. Calling
      * notifyPooledConnection with null event will place this connection back in the pool. Calling
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java
index af82279d..0724dd2e 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java
@@ -1709,12 +1709,30 @@ public final class SQLServerDatabaseMetaData implements java.sql.DatabaseMetaDat
         }
         checkClosed();
         String result = "";
-        try (SQLServerStatement s = (SQLServerStatement) connection.createStatement();
-                SQLServerResultSet rs = s.executeQueryInternal("select system_user")) {
-            // Select system_user will always return a row.
-            boolean next = rs.next();
-            assert next;
-            result = rs.getString(1);
+        try (SQLServerStatement s = (SQLServerStatement) connection.createStatement()) {
+            try (SQLServerResultSet rs = s.executeQueryInternal("SELECT SYSTEM_USER")) {
+                // Select system_user will always return a row.
+                boolean next = rs.next();
+                assert next;
+                result = rs.getString(1);
+            } catch (SQLServerException e) {
+                // execution using impersonated security context is disallowed for Azure SQL Server so return CURRENT_USER instead
+                if (e.getErrorCode() == SQLServerException.IMPERSONATION_CONTEXT_NOT_SUPPORTED) {
+                    if (loggerExternal.isLoggable(Level.FINEST)) {
+                        loggerExternal.finest(toString()
+                                + " Impersonation context is not supported in this version of SQL Server. Re-try getting CURRENT_USER");
+                    }
+              
+                    try (SQLServerResultSet rs = s.executeQueryInternal("SELECT CURRENT_USER")) {
+                        boolean next = rs.next();
+                        assert next;
+                        result = rs.getString(1);
+                    }
+                } else {
+                    // re-throw
+                    throw e;
+                }
+            }
         }
         return result;
     }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
index 374d0c5c..eaa85928 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
@@ -66,8 +66,9 @@ public final class SQLServerException extends java.sql.SQLException {
     static final int LOGON_FAILED = 18456;
     static final int PASSWORD_EXPIRED = 18488;
     static final int USER_ACCOUNT_LOCKED = 18486;
-    static final java.util.logging.Logger exLogger = java.util.logging.Logger
-            .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerException");
+
+    // Built-in function '%.*ls' in impersonation context is not supported in this version of SQL Server.
+    static final int IMPERSONATION_CONTEXT_NOT_SUPPORTED = 40529;
 
     // Facility for driver-specific error codes
     static final int DRIVER_ERROR_NONE = 0;
@@ -84,6 +85,9 @@ public final class SQLServerException extends java.sql.SQLException {
     static final int DATA_CLASSIFICATION_INVALID_LABEL_INDEX = 12;
     static final int DATA_CLASSIFICATION_INVALID_INFORMATION_TYPE_INDEX = 13;
 
+    static final java.util.logging.Logger exLogger = java.util.logging.Logger
+            .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerException");
+
     /** driver error code */
     private int driverErrorCode = DRIVER_ERROR_NONE;
 
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
index b0006456..2a2adf12 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
@@ -461,7 +461,7 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS
             if (i > 0)
                 sb.append(',');
 
-            int l = SQLServerConnection.makeParamName(i, cParamName, 0);
+            int l = SQLServerConnection.makeParamName(i, cParamName, 0, false);
             String parameterName = String.valueOf(cParamName, 0, l);
             sb.append(parameterName);
             sb.append(' ');
@@ -750,7 +750,7 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS
         for (int index = 0; index < params.length; index++) {
             if (JDBCType.TVP == params[index].getJdbcType()) {
                 cParamName = new char[10];
-                int paramNameLen = SQLServerConnection.makeParamName(index, cParamName, 0);
+                int paramNameLen = SQLServerConnection.makeParamName(index, cParamName, 0, false);
                 tdsWriter.writeByte((byte) paramNameLen);
                 tdsWriter.writeString(new String(cParamName, 0, paramNameLen));
             }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
index a9047e6b..44db5331 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
@@ -359,7 +359,7 @@ public final class SQLServerResource extends ListResourceBundle {
         {"R_AccessTokenCannotBeEmpty", "AccessToken cannot be empty."},
         {"R_SetBothAuthenticationAndAccessToken", "Cannot set the AccessToken property if \"Authentication\" has been specified in the connection string."},
         {"R_NoUserPasswordForActivePassword", "Both \"User\" (or \"UserName\") and \"Password\" connection string keywords must be specified, if \"Authentication=ActiveDirectoryPassword\"."},
-        {"R_NoUserPasswordForActiveServicePrincipal", "Both \"UserName\" and \"Password\" connection string keywords must be specified, if \"Authentication=ActiveDirectoryServicePrincipal."},
+        {"R_NoUserPasswordForActiveServicePrincipal", "Both \"UserName\" and \"Password\" connection string keywords must be specified, if \"Authentication=ActiveDirectoryServicePrincipal\"."},
         {"R_NoUserOrCertForActiveServicePrincipalCertificate", "\"Both \"UserName\" and \"clientCertificate\" connection string keyword must be specified, if \"Authentication=ActiveDirectoryServicePrincipalCertificate\"."},
         {"R_NoUserPasswordForSqlPassword", "Both \"User\" (or \"UserName\") and \"Password\" connection string keywords must be specified, if \"Authentication=SqlPassword\"."},
         {"R_BothUserPasswordandDeprecated", "Both \"User\" (or \"UserName\"), \"Password\" and \"AADSecurePrincipalId\", \"AADSecurePrincipalSecret\" connection string keywords are specified, please use \"User\" (or \"UserName\"), \"Password\" only."},
@@ -382,8 +382,9 @@ public final class SQLServerResource extends ListResourceBundle {
         {"R_AKVSignatureLengthError", "The specified encrypted column encryption key''s signature length: {0} does not match the signature length: {1} when using column master key (Azure Key Vault key) in {2}. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect."},
         {"R_HashNull", "Hash should not be null while decrypting encrypted column encryption key."},
         {"R_NoSHA256Algorithm", "SHA-256 Algorithm is not supported."},
-        {"R_VerifySignature", "Unable to verify signature of the column encryption key."},
-        {"R_CEKSignatureNotMatchCMK", "The specified encrypted column encryption key signature does not match the signature computed with the column master key (Asymmetric key in Azure Key Vault) in {0}. The encrypted column encryption key may be corrupt, or the specified path may be incorrect."},
+        {"R_VerifySignatureFailed", "Unable to verify the signature of the column encryption key."},
+        {"R_SignatureNotMatch", "The specified encrypted column encryption key signature \"{0}\" does not match the signature \"{1}\" computed with the column master key (certificate) in \"{2}\". The encrypted column encryption key may be corrupt, or the specified path may be incorrect. {3}"},
+        {"R_CEKSignatureNotMatchCMK", "The specified encrypted column encryption key signature \"{0}\" does not match the signature computed with the column master key (Asymmetric key in Azure Key Vault) in {1}. The encrypted column encryption key may be corrupt, or the specified path may be incorrect."},
         {"R_DecryptCEKError", "Unable to decrypt column encryption key using specified Azure Key Vault key."},
         {"R_EncryptCEKError", "Unable to encrypt column encryption key using specified Azure Key Vault key."},
         {"R_CipherTextLengthNotMatchRSASize", "CipherText length does not match the RSA key size."},
@@ -409,6 +410,7 @@ public final class SQLServerResource extends ListResourceBundle {
         // This is used for connection settings. {0}-> property name as is, {1}-> value
         {"R_InvalidConnectionSetting", "The {0} value \"{1}\" is not valid."},
         {"R_InvalidWindowsCertificateStoreEncryption", "Cannot encrypt a column encryption key with the Windows Certificate Store."},
+        {"R_InvalidWindowsCertificateStoreSignCMK", "Cannot sign column master key metadata with the Windows Certificate Store."},
         {"R_AEKeypathEmpty", "Internal error. Certificate path cannot be null. Use the following format: \"certificate location/certificate store/certificate thumbprint\", where \"certificate location\" is either LocalMachine or CurrentUser."},
         {"R_AEWinApiErr", "Windows Api native error."},
         {"R_AECertpathBad", "Internal error. Invalid certificate path: {0}. Use the following format: \"certificate location/certificate store/certificate thumbprint\", where \"certificate location\" is either LocalMachine or CurrentUser."},
@@ -530,7 +532,6 @@ public final class SQLServerResource extends ListResourceBundle {
         {"R_illegalArgumentTrustManager", "Interal error. Peer certificate chain or key exchange algorithem can not be null or empty."},
         {"R_serverCertExpired", "Server Certificate has expired: {0}: {1}"},
         {"R_serverCertNotYetValid", "Server Certificate is not yet valid: {0}: {1}"},
-        {"R_publicKeyMismatch", "Error validating Server Certificate: public key mismatch: {0}"},
         {"R_serverCertError", "Error validating Server Certificate: {0}: \n{1}:\n{2}."},
         {"R_SecureStringInitFailed", "Failed to initialize SecureStringUtil to store secure strings"},
         {"R_ALPNFailed", "Failed to negotiate Application-Layer Protocol {0}. Server returned: {1}."},
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java
index 6e24c358..3bb2eb32 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java
@@ -315,7 +315,7 @@ class SQLServerSecurityUtility {
         }
 
         if (!provider.verifyColumnMasterKeyMetadata(keyPath, isEnclaveEnabled, cmkSignature)) {
-            throw new SQLServerException(SQLServerException.getErrString("R_VerifySignature"), null);
+            throw new SQLServerException(SQLServerException.getErrString("R_VerifySignatureFailed"), null);
         }
     }
 
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
index fde8e94f..9fce9029 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
@@ -48,7 +48,13 @@ final class Util {
     private static final Lock LOCK = new ReentrantLock();
 
     static boolean isIBM() {
-        return SYSTEM_JRE.startsWith("IBM");
+        Class<?> clazz = null;
+        try {
+            clazz = Class.forName("com.ibm.lang.management.MemoryUsage");
+        } catch (ClassNotFoundException e) {
+            //We're using the try-catch to test for IBM jdk, no need to handle exception.
+        }
+        return null != clazz;
     }
 
     static String getJVMArchOnWindows() {
diff --git a/src/samples/adaptive/pom.xml b/src/samples/adaptive/pom.xml
index 0b9fd8da..96e2be9a 100644
--- a/src/samples/adaptive/pom.xml
+++ b/src/samples/adaptive/pom.xml
@@ -15,7 +15,7 @@
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
-			<version>12.2.0.jre11</version>
+			<version>12.4.0.jre11</version>
 		</dependency>
 	</dependencies>
 	<profiles>
diff --git a/src/samples/alwaysencrypted/pom.xml b/src/samples/alwaysencrypted/pom.xml
index 95bce7ab..294d4b41 100644
--- a/src/samples/alwaysencrypted/pom.xml
+++ b/src/samples/alwaysencrypted/pom.xml
@@ -15,7 +15,7 @@
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
-			<version>12.2.0.jre11</version>
+			<version>12.4.0.jre11</version>
 		</dependency>
 	</dependencies>
 	<profiles>
diff --git a/src/samples/azureactivedirectoryauthentication/pom.xml b/src/samples/azureactivedirectoryauthentication/pom.xml
index 02c4c23c..518da61a 100644
--- a/src/samples/azureactivedirectoryauthentication/pom.xml
+++ b/src/samples/azureactivedirectoryauthentication/pom.xml
@@ -14,7 +14,7 @@
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
-			<version>12.2.0.jre11</version>
+			<version>12.4.0.jre11</version>
 		</dependency>
 	</dependencies>
 	<profiles>
diff --git a/src/samples/connections/pom.xml b/src/samples/connections/pom.xml
index e2f2128c..209697c3 100644
--- a/src/samples/connections/pom.xml
+++ b/src/samples/connections/pom.xml
@@ -14,7 +14,7 @@
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
-			<version>12.2.0.jre11</version>
+			<version>12.4.0.jre11</version>
 		</dependency>
 	</dependencies>
 	<profiles>
diff --git a/src/samples/constrained/pom.xml b/src/samples/constrained/pom.xml
index 131e686c..c2cc82ae 100644
--- a/src/samples/constrained/pom.xml
+++ b/src/samples/constrained/pom.xml
@@ -16,7 +16,7 @@
         <dependency>
             <groupId>com.microsoft.sqlserver</groupId>
             <artifactId>mssql-jdbc</artifactId>
-            <version>12.2.0.jre11</version>
+            <version>12.4.0.jre11</version>
         </dependency>
     </dependencies>
     <profiles>
diff --git a/src/samples/dataclassification/pom.xml b/src/samples/dataclassification/pom.xml
index e3c71dc8..74ba22bb 100644
--- a/src/samples/dataclassification/pom.xml
+++ b/src/samples/dataclassification/pom.xml
@@ -16,7 +16,7 @@
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
-			<version>12.2.0.jre11</version>
+			<version>12.4.0.jre11</version>
 		</dependency>
 	</dependencies>
 	<profiles>
diff --git a/src/samples/datatypes/pom.xml b/src/samples/datatypes/pom.xml
index d16a5bc7..148d7381 100644
--- a/src/samples/datatypes/pom.xml
+++ b/src/samples/datatypes/pom.xml
@@ -15,7 +15,7 @@
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
-			<version>12.2.0.jre11</version>
+			<version>12.4.0.jre11</version>
 		</dependency>
 	</dependencies>
 	<profiles>
diff --git a/src/samples/resultsets/pom.xml b/src/samples/resultsets/pom.xml
index 09c1f42d..d466f5e7 100644
--- a/src/samples/resultsets/pom.xml
+++ b/src/samples/resultsets/pom.xml
@@ -14,7 +14,7 @@
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
-			<version>12.2.0.jre11</version>
+			<version>12.4.0.jre11</version>
 		</dependency>
 	</dependencies>
 	<profiles>
diff --git a/src/samples/sparse/pom.xml b/src/samples/sparse/pom.xml
index c5a7f7ae..e6e3dd4d 100644
--- a/src/samples/sparse/pom.xml
+++ b/src/samples/sparse/pom.xml
@@ -14,7 +14,7 @@
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
-			<version>12.2.0.jre11</version>
+			<version>12.4.0.jre11</version>
 		</dependency>
 	</dependencies>
 	<profiles>


Test patch:
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
index 2b822fbf..46a3fa05 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
@@ -49,23 +49,15 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL
         return this.name;
     }
 
+    @Override
     public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
             byte[] plainTextColumnEncryptionKey) throws SQLServerException {
+        // not supported
         throw new SQLServerException(null,
                 SQLServerException.getErrString("R_InvalidWindowsCertificateStoreEncryption"), null, 0, false);
     }
 
-    private byte[] decryptColumnEncryptionKeyWindows(String masterKeyPath, String encryptionAlgorithm,
-            byte[] encryptedColumnEncryptionKey) throws SQLServerException {
-        try {
-            return AuthenticationJNI.DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm,
-                    encryptedColumnEncryptionKey);
-        } catch (DLLException e) {
-            DLLException.buildException(e.getErrCode(), e.getParam1(), e.getParam2(), e.getParam3());
-            return null;
-        }
-    }
-
+    @Override
     public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
             byte[] encryptedColumnEncryptionKey) throws SQLServerException {
         windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(),
@@ -92,4 +84,16 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL
             return false;
         }
     }
+
+    private byte[] decryptColumnEncryptionKeyWindows(String masterKeyPath, String encryptionAlgorithm,
+            byte[] encryptedColumnEncryptionKey) throws SQLServerException {
+        try {
+            return AuthenticationJNI.DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm,
+                    encryptedColumnEncryptionKey);
+        } catch (DLLException e) {
+            DLLException.buildException(e.getErrCode(), e.getParam1(), e.getParam2(), e.getParam3());
+            return null;
+        }
+    }
+
 }
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java
index 96e8ead8..53f3b18e 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java
@@ -82,6 +82,8 @@ public class AESetup extends AbstractTest {
     public static final String SCALE_DATE_TABLE_AE = TestUtils.escapeSingleQuotes(
             AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBCEncryptedScaleDate")));
 
+    final static char[] HEXCHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
     enum ColumnType {
         PLAIN,
         RANDOMIZED,
@@ -220,18 +222,18 @@ public class AESetup extends AbstractTest {
             setAEConnectionString(serverName, url, protocol);
 
             createCMK(AETestConnectionString, cmkJks, Constants.JAVA_KEY_STORE_NAME, javaKeyAliases,
-                    Constants.CMK_SIGNATURE);
+                    TestUtils.byteToHexDisplayString(jksProvider.signColumnMasterKeyMetadata(javaKeyAliases, true)));
             createCEK(AETestConnectionString, cmkJks, cekJks, jksProvider);
 
             if (null != keyIDs && !keyIDs[0].isEmpty()) {
                 createCMK(AETestConnectionString, cmkAkv, Constants.AZURE_KEY_VAULT_NAME, keyIDs[0],
-                        Constants.CMK_SIGNATURE_AKV);
+                        TestUtils.byteToHexDisplayString(akvProvider.signColumnMasterKeyMetadata(keyIDs[0], true)));
                 createCEK(AETestConnectionString, cmkAkv, cekAkv, akvProvider);
             }
 
             if (null != windowsKeyPath) {
                 createCMK(AETestConnectionString, cmkWin, Constants.WINDOWS_KEY_STORE_NAME, windowsKeyPath,
-                        Constants.CMK_SIGNATURE);
+                        Constants.CMK_SIGNATURE_WIN);
                 createCEK(AETestConnectionString, cmkWin, cekWin, null);
             }
         }
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java
index 9b136afd..4b55573a 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java
@@ -23,10 +23,12 @@ import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 import com.microsoft.sqlserver.jdbc.EnclavePackageTest;
+import com.microsoft.sqlserver.jdbc.RandomUtil;
 import com.microsoft.sqlserver.jdbc.SQLServerConnection;
 import com.microsoft.sqlserver.jdbc.SQLServerStatement;
 import com.microsoft.sqlserver.jdbc.TestResource;
 import com.microsoft.sqlserver.jdbc.TestUtils;
+import com.microsoft.sqlserver.testframework.AbstractSQLGenerator;
 import com.microsoft.sqlserver.testframework.Constants;
 import com.microsoft.sqlserver.testframework.PrepUtil;
 
@@ -194,6 +196,145 @@ public class EnclaveTest extends AESetup {
         }
     }
 
+    /*
+     * Negative Test - bad JKS signature
+     */
+    @ParameterizedTest
+    @MethodSource("enclaveParams")
+    public void testVerifyBadJksSignature(String serverName, String url, String protocol) throws Exception {
+        setAEConnectionString(serverName, url, protocol);
+
+        // create CMK with a bad signature
+        String badCmk = Constants.CMK_NAME + "_badCMK";
+        String badCek = Constants.CEK_NAME + "_badCek";
+        String badTable = TestUtils.escapeSingleQuotes(
+                AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadJksSignature")));
+
+        try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo);
+                Statement s = c.createStatement()) {
+            createCMK(AETestConnectionString, badCmk, Constants.JAVA_KEY_STORE_NAME, javaKeyAliases, "0x666");
+            createCEK(AETestConnectionString, badCmk, badCek, jksProvider);
+
+            createTable(badTable, badCek, varcharTableSimple);
+
+            PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + badTable + " VALUES (?,?,?)");
+            pstmt.setString(1, "a");
+            pstmt.setString(2, "b");
+            pstmt.setString(3, "test");
+            pstmt.execute();
+
+            pstmt = c.prepareStatement("SELECT * FROM " + badTable + " WHERE RANDOMIZEDVarchar LIKE ?");
+            pstmt.setString(1, "t%");
+            try (ResultSet rs = pstmt.executeQuery()) {
+                fail(TestResource.getResource("R_expectedFailPassed"));
+            }
+        } catch (Exception e) {
+            assertTrue(
+                    e.getMessage().matches(TestUtils.formatErrorMsg("R_SignatureNotMatch"))
+                            || e.getMessage().matches(TestUtils.formatErrorMsg("R_VerifySignatureFailed")),
+                    e.getMessage());
+        } finally {
+            try (Statement s = connection.createStatement()) {
+                TestUtils.dropTableIfExists(badTable, s);
+                dropCEK(badCek, s);
+                dropCMK(badCmk, s);
+            }
+        }
+    }
+
+    /*
+     * Negative Test - bad AKS signature
+     */
+    @ParameterizedTest
+    @MethodSource("enclaveParams")
+    public void testVerifyBadAkvSignature(String serverName, String url, String protocol) throws Exception {
+        setAEConnectionString(serverName, url, protocol);
+
+        // create CMK with a bad signature
+        String badCmk = Constants.CMK_NAME + "_badCMK";
+        String badCek = Constants.CEK_NAME + "_badCek";
+        String badTable = TestUtils.escapeSingleQuotes(
+                AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadAkvSignature")));
+
+        try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo);
+                Statement s = c.createStatement()) {
+            createCMK(AETestConnectionString, badCmk, Constants.AZURE_KEY_VAULT_NAME, keyIDs[0], "0x666");
+            createCEK(AETestConnectionString, badCmk, badCek, akvProvider);
+
+            createTable(badTable, badCek, varcharTableSimple);
+
+            PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + badTable + " VALUES (?,?,?)");
+            pstmt.setString(1, "a");
+            pstmt.setString(2, "b");
+            pstmt.setString(3, "test");
+            pstmt.execute();
+
+            pstmt = c.prepareStatement("SELECT * FROM " + badTable + " WHERE RANDOMIZEDVarchar LIKE ?");
+            pstmt.setString(1, "t%");
+            try (ResultSet rs = pstmt.executeQuery()) {
+                fail(TestResource.getResource("R_expectedFailPassed"));
+            }
+        } catch (Exception e) {
+            assertTrue(
+                    e.getMessage().matches(TestUtils.formatErrorMsg("R_SignatureNotMatch"))
+                            || e.getMessage().matches(TestUtils.formatErrorMsg("R_VerifySignatureFailed")),
+                    e.getMessage());
+        } finally {
+            try (Statement s = connection.createStatement()) {
+                TestUtils.dropTableIfExists(badTable, s);
+                dropCEK(badCek, s);
+                dropCMK(badCmk, s);
+            }
+        }
+    }
+
+    /*
+     * Negative Test - bad AKS signature
+     */
+    @ParameterizedTest
+    @MethodSource("enclaveParams")
+    public void testVerifyBadWinSignature(String serverName, String url, String protocol) throws Exception {
+        setAEConnectionString(serverName, url, protocol);
+
+        String badCmk = Constants.CMK_NAME + "_badCMK";
+        String badCek = Constants.CEK_NAME + "_badCek";
+        String badTable = TestUtils.escapeSingleQuotes(
+                AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadWinSignature")));
+
+        try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo);
+                Statement s = c.createStatement()) {
+            // create CMK with a bad signature
+            createCMK(AETestConnectionString, badCmk, Constants.WINDOWS_KEY_STORE_NAME, windowsKeyPath, "0x666");
+            createCEK(AETestConnectionString, badCmk, badCek, null);
+
+            createTable(badTable, badCek, varcharTableSimple);
+
+            PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + badTable + " VALUES (?,?,?)");
+            pstmt.setString(1, "a");
+            pstmt.setString(2, "b");
+            pstmt.setString(3, "test");
+            pstmt.execute();
+
+            pstmt = c.prepareStatement("SELECT * FROM " + badTable + " WHERE RANDOMIZEDVarchar LIKE ?");
+            pstmt.setString(1, "t%");
+            try (ResultSet rs = pstmt.executeQuery()) {
+                fail(TestResource.getResource("R_expectedFailPassed"));
+            }
+        } catch (Exception e) {
+            // windows error message is different
+            assertTrue(
+                    e.getMessage().contains("signature does not match")
+                            || e.getMessage().matches(TestUtils.formatErrorMsg("R_VerifySignatureFailed")),
+                    e.getMessage());
+        } finally {
+            try (Statement s = connection.createStatement()) {
+                TestUtils.dropTableIfExists(badTable, s);
+                dropCEK(badCek, s);
+                dropCMK(badCmk, s);
+            }
+        }
+    }
+
     /*
      * Tests alter column encryption on char tables
      */
@@ -213,6 +354,7 @@ public class EnclaveTest extends AESetup {
     /*
      * Tests alter column encryption on char tables with AKV
      */
+
     @ParameterizedTest
     @MethodSource("enclaveParams")
     public void testCharAkv(String serverName, String url, String protocol) throws Exception {
@@ -306,6 +448,7 @@ public class EnclaveTest extends AESetup {
         try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo);
                 Statement s = c.createStatement()) {
             createTable(CHAR_TABLE_AE, cekJks, varcharTableSimple);
+
             PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + CHAR_TABLE_AE + " VALUES (?,?,?)");
             pstmt.setString(1, "a");
             pstmt.setString(2, "b");
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java
index 5afa2e78..9aeb0da8 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java
@@ -51,6 +51,7 @@ public class MSITest extends AESetup {
     @Tag(Constants.xSQLv12)
     @Tag(Constants.xSQLv14)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Test
     public void testManagedIdentityAuth() throws SQLException {
         String connStr = connectionString;
@@ -78,6 +79,7 @@ public class MSITest extends AESetup {
     @Tag(Constants.xSQLv12)
     @Tag(Constants.xSQLv14)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Test
     public void testManagedIdentityAuthWithManagedIdentityClientId() throws SQLException {
         String connStr = connectionString;
@@ -116,6 +118,7 @@ public class MSITest extends AESetup {
     @Tag(Constants.xSQLv12)
     @Tag(Constants.xSQLv14)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Test
     public void testDSManagedIdentityAuth() throws SQLException {
         String connStr = connectionString;
@@ -141,6 +144,7 @@ public class MSITest extends AESetup {
     @Tag(Constants.xSQLv12)
     @Tag(Constants.xSQLv14)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Test
     public void testDSManagedIdentityAuthWithManagedIdentityClientId() throws SQLException {
         String connStr = connectionString;
@@ -169,6 +173,7 @@ public class MSITest extends AESetup {
     @Tag(Constants.xSQLv12)
     @Tag(Constants.xSQLv14)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Test
     public void testActiveDirectoryDefaultAuth() throws SQLException {
         String connStr = connectionString;
@@ -188,6 +193,7 @@ public class MSITest extends AESetup {
     @Tag(Constants.xSQLv12)
     @Tag(Constants.xSQLv14)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Test
     public void testActiveDirectoryDefaultAuthDS() throws SQLException {
         String connStr = connectionString;
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java
index 257d8f92..c3332158 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java
@@ -354,7 +354,7 @@ public class MultiUserAKVTest extends AESetup {
 
         // Create cmk and cek for DummyKeyStoreProvider
         createCMK(AETestConnectionString, cmkDummy, Constants.DUMMY_KEYSTORE_NAME, keyIDs[0],
-                Constants.CMK_SIGNATURE_AKV);
+                TestUtils.byteToHexDisplayString(akvProvider.signColumnMasterKeyMetadata(keyIDs[0], true)));
         createCEK(AETestConnectionString, cmkDummy, cekDummy, akvProvider);
 
         // Create an empty table for testing
@@ -439,7 +439,7 @@ public class MultiUserAKVTest extends AESetup {
 
         // Create an empty table for testing
         createCMK(AETestConnectionString, cmkDummy, Constants.DUMMY_KEYSTORE_NAME, keyIDs[0],
-                Constants.CMK_SIGNATURE_AKV);
+                TestUtils.byteToHexDisplayString(akvProvider.signColumnMasterKeyMetadata(keyIDs[0], true)));
         createCEK(AETestConnectionString, cmkDummy, cekDummy, akvProvider);
 
         createTableForCustomProvider(AETestConnectionString, customProviderTableName, cekDummy);
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
index 52b1413c..f8e9f4dc 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
@@ -333,6 +333,22 @@ public class SQLServerConnectionTest extends AbstractTest {
         try (Connection con = ds.getConnection()) {}
     }
 
+    @Tag(Constants.xSQLv11)
+    @Tag(Constants.xSQLv12)
+    @Tag(Constants.xSQLv14)
+    @Tag(Constants.xSQLv15)
+    @Tag(Constants.xAzureSQLDW)
+    @Tag(Constants.xAzureSQLDB)
+    @Test
+    public void testEncryptedStrictConnection() throws SQLException {
+        SQLServerDataSource ds = new SQLServerDataSource();
+        ds.setURL(connectionString);
+        ds.setServerCertificate(serverCertificate);
+        ds.setEncrypt(Constants.STRICT);
+
+        try (Connection con = ds.getConnection()) {}
+    }
+
     @Test
     public void testJdbcDataSourceMethod() throws SQLFeatureNotSupportedException {
         SQLServerDataSource fxds = new SQLServerDataSource();
@@ -936,65 +952,63 @@ public class SQLServerConnectionTest extends AbstractTest {
         assertTrue(status && future.isCancelled(), TestResource.getResource("R_threadInterruptNotSet"));
     }
 
+    /**
+     * Test thread count when finding socket using threading.
+     */
+    @Test
+    @Tag(Constants.xAzureSQLDB)
+    @Tag(Constants.xAzureSQLDW)
+    public void testThreadCountWhenFindingSocket() {
+        ExecutorService executor = null;
+        ManagementFactory.getThreadMXBean().resetPeakThreadCount();
+
+        // First, check to see if there is a reachable local host, or else test will fail.
+        try {
+            SQLServerDataSource ds = new SQLServerDataSource();
+            ds.setServerName("localhost");
+            Connection con = ds.getConnection();
+        } catch (SQLServerException e) {
+            // Assume this will be an error different than 'localhost is unreachable'. If it is 'localhost is
+            // unreachable' abort and skip the test.
+            Assume.assumeFalse(e.getMessage().startsWith(TestResource.getResource("R_tcpipConnectionToHost")));
+        }
+
+        try {
+            executor = Executors.newSingleThreadExecutor(r -> new Thread(r, ""));
+            executor.submit(() -> {
+                try {
+                    SQLServerDataSource ds = new SQLServerDataSource();
+                    ds.setServerName("localhost");
+                    Thread.sleep(5000);
+                    Connection conn2 = ds.getConnection();
+                } catch (Exception e) {
+                    if (!(e instanceof SQLServerException)) {
+                        fail(TestResource.getResource("R_unexpectedException") + e.getMessage());
+                    }
+                }
+            });
+            SQLServerDataSource ds = new SQLServerDataSource();
+            ds.setServerName("localhost");
+            Connection conn = ds.getConnection();
+            Thread.sleep(5000);
+        } catch (Exception e) {
+            if (!(e instanceof SQLServerException)) {
+                fail(TestResource.getResource("R_unexpectedException") + e.getMessage());
+            }
+        } finally {
+            if (executor != null) {
+                executor.shutdown();
+            }
+        }
+
+        // At this point, thread count has returned to normal. If the peak was more
+        // than 2 times the current, this is an issue and the test should fail.
+        if (ManagementFactory.getThreadMXBean().getPeakThreadCount() > 2
+                * ManagementFactory.getThreadMXBean().getThreadCount()) {
+            fail(TestResource.getResource("R_unexpectedThreadCount"));
+        }
+    }
 
-   /**
-    * Test thread count when finding socket using threading.
-    */
-   @Test
-   @Tag(Constants.xAzureSQLDB)
-   @Tag(Constants.xAzureSQLDW)
-   public void testThreadCountWhenFindingSocket() {
-       ExecutorService executor = null;
-       ManagementFactory.getThreadMXBean().resetPeakThreadCount();
-
-       // First, check to see if there is a reachable local host, or else test will fail.
-       try {
-           SQLServerDataSource ds = new SQLServerDataSource();
-           ds.setServerName("localhost");
-           Connection con = ds.getConnection();
-       } catch (SQLServerException e) {
-           // Assume this will be an error different than 'localhost is unreachable'. If it is 'localhost is
-           // unreachable' abort and skip the test.
-           Assume.assumeFalse(e.getMessage().startsWith(TestResource.getResource("R_tcpipConnectionToHost")));
-       }
-
-       try {
-           executor = Executors.newSingleThreadExecutor(r -> new Thread(r, ""));
-           executor.submit(() -> {
-               try {
-                   SQLServerDataSource ds = new SQLServerDataSource();
-                   ds.setServerName("localhost");
-                   Thread.sleep(5000);
-                   Connection conn2 = ds.getConnection();
-               } catch (Exception e) {
-                   if (!(e instanceof SQLServerException)) {
-                       fail(TestResource.getResource("R_unexpectedException") + e.getMessage());
-                   }
-               }
-           });
-           SQLServerDataSource ds = new SQLServerDataSource();
-           ds.setServerName("localhost");
-           Connection conn = ds.getConnection();
-           Thread.sleep(5000);
-       } catch (Exception e) {
-           if (!(e instanceof SQLServerException)) {
-               fail(TestResource.getResource("R_unexpectedException") + e.getMessage());
-           }
-       } finally {
-           if (executor != null) {
-               executor.shutdown();
-           }
-       }
-
-       // At this point, thread count has returned to normal. If the peak was more
-       // than 2 times the current, this is an issue and the test should fail.
-       if (ManagementFactory.getThreadMXBean().getPeakThreadCount()
-               > 2 * ManagementFactory.getThreadMXBean().getThreadCount()) {
-           fail(TestResource.getResource("R_unexpectedThreadCount"));
-       }
-   }
-
-    
     /**
      * Test calling method to get redirected server string.
      */
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
index 8041a35b..495390c4 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
@@ -18,6 +18,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
@@ -43,6 +44,7 @@ import org.junit.runner.RunWith;
 import com.microsoft.sqlserver.jdbc.RandomUtil;
 import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
 import com.microsoft.sqlserver.jdbc.SQLServerDatabaseMetaData;
+import com.microsoft.sqlserver.jdbc.SQLServerException;
 import com.microsoft.sqlserver.jdbc.StringUtils;
 import com.microsoft.sqlserver.jdbc.TestResource;
 import com.microsoft.sqlserver.jdbc.TestUtils;
@@ -155,8 +157,6 @@ public class DatabaseMetaDataTest extends AbstractTest {
      * @throws SQLException
      */
     @Test
-    @Tag(Constants.xAzureSQLDW)
-    @Tag(Constants.xAzureSQLDB)
     public void testDBUserLogin() throws SQLException {
         try (Connection conn = getConnection()) {
             DatabaseMetaData databaseMetaData = conn.getMetaData();
@@ -187,6 +187,45 @@ public class DatabaseMetaDataTest extends AbstractTest {
         }
     }
 
+    @Test
+    @Tag(Constants.xAzureSQLDW)
+    public void testImpersonateGetUserName() throws SQLException {
+        String newUser = "newUser" + UUID.randomUUID();
+
+        try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
+            String escapedNewUser = AbstractSQLGenerator.escapeIdentifier(newUser);
+            String password = "password" + UUID.randomUUID();
+
+            stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP USER"
+                    + escapedNewUser);
+
+            // create new user and login
+            try {
+                stmt.execute("CREATE USER " + escapedNewUser + " WITH password='" + password + "'");
+            } catch (SQLServerException e) {
+                // handle failed cases when database is master
+                if (e.getMessage().contains("contained database")) {
+                    stmt.execute("CREATE LOGIN " + escapedNewUser + " WITH password='" + password + "'");
+                    stmt.execute("CREATE USER " + escapedNewUser);
+                }
+            }
+
+            DatabaseMetaData databaseMetaData = conn.getMetaData();
+            try (CallableStatement asOtherUser = conn.prepareCall("EXECUTE AS USER = '" + newUser + "'")) {
+                asOtherUser.execute();
+                assertTrue(newUser.equalsIgnoreCase(databaseMetaData.getUserName()),
+                        TestResource.getResource("R_userNameNotMatch"));
+            } catch (Exception e) {
+                fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage());
+            } finally {
+                stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP USER"
+                        + escapedNewUser);
+                stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP LOGIN"
+                        + escapedNewUser);
+            }
+        }
+    }
+
     /**
      * Testing of {@link SQLServerDatabaseMetaData#getSchemas()}
      * 
@@ -758,6 +797,7 @@ public class DatabaseMetaDataTest extends AbstractTest {
     @Tag(Constants.xSQLv12)
     @Tag(Constants.xSQLv14)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Tag(Constants.xAzureSQLDB)
     @Tag(Constants.xAzureSQLMI)
     public void testGetImportedKeysDW() throws SQLException {
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java
index 6b7d5c5d..b19d2524 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java
@@ -296,6 +296,7 @@ public class BasicConnectionTest extends AbstractTest {
     @Tag(Constants.xSQLv12)
     @Tag(Constants.xSQLv14)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Tag(Constants.xAzureSQLDW)
     @Tag(Constants.reqExternalSetup)
     @Test
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resultset/DataClassificationTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resultset/DataClassificationTest.java
index cbc1f937..5b876e3e 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/resultset/DataClassificationTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/resultset/DataClassificationTest.java
@@ -167,6 +167,7 @@ public class DataClassificationTest extends AbstractTest {
     @Tag(Constants.xAzureSQLDB)
     @Tag(Constants.xAzureSQLDW)
     @Tag(Constants.xSQLv15)
+    @Tag(Constants.xSQLv16)
     @Test
     public void testDataClassificationNotSupported() throws Exception {
         try (Statement stmt = connection.createStatement();) {
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 a61a9b2e..c7bd1041 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
@@ -50,6 +50,7 @@ public class PreparedStatementTest extends AbstractTest {
     final static String tableName = RandomUtil.getIdentifier("tableTestStatementPoolingInternal1");
     final static String tableName2 = RandomUtil.getIdentifier("tableTestStatementPoolingInternal2");
     final static String tableName3 = RandomUtil.getIdentifier("tableTestPreparedStatementWithSpPrepare");
+    final static String tableName4 = RandomUtil.getIdentifier("tableTestPreparedStatementWithMultipleParams");
 
     @BeforeAll
     public static void setupTests() throws Exception {
@@ -117,6 +118,94 @@ public class PreparedStatementTest extends AbstractTest {
         }
     }
 
+    @Test
+    public void testPreparedStatementParamNameSpacingWithMultipleParams() throws SQLException {
+        int paramNameCount = 105;
+
+        StringBuilder insertSql = new StringBuilder("insert into " + AbstractSQLGenerator.escapeIdentifier(tableName4)
+                + "values(");
+
+        for (int i = 1; i <= paramNameCount; i++) {
+            insertSql.append(i % 10);
+
+            if (i != paramNameCount) {
+                insertSql.append(",");
+            } else {
+                insertSql.append(")");
+            }
+        }
+
+        StringBuilder createTableSql = new StringBuilder("create table " + AbstractSQLGenerator.escapeIdentifier(tableName4) + "(");
+
+        for (int i = 1; i <= paramNameCount; i++) {
+            createTableSql.append("c" + i + " char(1)");
+
+            if (i != paramNameCount) {
+                createTableSql.append(",");
+            } else {
+                createTableSql.append(")");
+            }
+        }
+
+        try (SQLServerConnection con = (SQLServerConnection) getConnection()) {
+            executeSQL(con, createTableSql.toString());
+            executeSQL(con, insertSql.toString());
+
+            // There are no typos in the queries eg. The 'c1=?and' is not a typo. We are testing the spacing, or lack of spacing.
+            // The driver should automatically space out the params eg. 'c1=?and' becomes 'c1= ? and' in the final string we send to the server.
+
+            // Testing with less than 10 params
+            String sql1 = "select * from " + AbstractSQLGenerator.escapeIdentifier(tableName4) + " where c1=?and c2=?";
+
+            // Testing with number of params between 10 and 100
+            String sql2 = "select * from " + AbstractSQLGenerator.escapeIdentifier(tableName4)
+                    + " where c1=?and c2=? and c3=?and c4=? and c5=? and c6=? and c7=? and c8=? and c9=? and c10=? and c11=? and c12=?";
+
+            // Testing with more than 100 params
+            StringBuilder sql3 = new StringBuilder("select * from " + AbstractSQLGenerator.escapeIdentifier(tableName4)
+                    + " where c1=?and ");
+
+            for (int i = 2; i <= paramNameCount; i++) {
+                sql3.append("c" + i + "=?");
+
+                if (i != paramNameCount) {
+                    sql3.append(" and ");
+                }
+            }
+
+            try (SQLServerPreparedStatement ps = (SQLServerPreparedStatement) con.prepareStatement(sql1)) {
+                ps.setString(1, "1");
+                ps.setString(2, "2");
+                ps.executeQuery();
+            }
+
+            try (SQLServerPreparedStatement ps = (SQLServerPreparedStatement) con.prepareStatement(sql2)) {
+                ps.setString(1, "1");
+                ps.setString(2, "2");
+                ps.setString(3, "3");
+                ps.setString(4, "4");
+                ps.setString(5, "5");
+                ps.setString(6, "6");
+                ps.setString(7, "7");
+                ps.setString(8, "8");
+                ps.setString(9, "9");
+                ps.setString(10, "0");
+                ps.setString(11, "1");
+                ps.setString(12, "2");
+                ps.executeQuery();
+            }
+
+            try (SQLServerPreparedStatement ps = (SQLServerPreparedStatement) con.prepareStatement(sql3.toString())) {
+                ps.setString(1, "1");
+                for (int i = 2; i <= paramNameCount; i++) {
+                    ps.setString(i, Integer.toString(i % 10));
+                }
+
+                ps.executeQuery();
+            }
+        }
+    }
+
     @Test
     public void testPreparedStatementPoolEvictionWithSpPrepare() throws SQLException {
         int cacheSize = 3;
@@ -787,6 +876,7 @@ public class PreparedStatementTest extends AbstractTest {
             TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt);
             TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName2), stmt);
             TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName3), stmt);
+            TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName4), stmt);
         }
     }
 
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java
index 3bf8e0b1..76f764e4 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java
@@ -1426,228 +1426,6 @@ public class StatementTest extends AbstractTest {
             }
         }
 
-        /**
-         * Tests that big decimal values with a precision less than 38 hold their precision. Tests cases where scale is
-         * 38 (integer part is a 0) and less than 38 (integer part is a non-zero).
-         *
-         * @throws SQLException
-         *         when an error occurs
-         */
-        @Test
-        public void testSmallBigDecimalValuesForLossOfPrecision() throws SQLException {
-            try (Connection con = getConnection();
-                 Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) {
-                double bigDecimalLessThanOne = 0.1235;
-                double bigDecimalGreaterThanOne = 1.1235;
-                String query = "CREATE PROCEDURE " + procName
-                        + " @col1Value decimal(4,4) OUTPUT, @col2Value decimal(5,4) OUTPUT AS BEGIN SET @col1Value = "
-                        + bigDecimalLessThanOne + " SET @col2Value = " + bigDecimalGreaterThanOne + " END";
-                stmt.execute(query);
-
-                try (CallableStatement cstmt = con.prepareCall("{CALL " + procName + "(?, ?)}")) {
-                    cstmt.registerOutParameter("col1Value", java.sql.Types.DECIMAL, "DECIMAL");
-                    cstmt.registerOutParameter("col2Value", java.sql.Types.DECIMAL, "DECIMAL");
-                    cstmt.execute();
-
-                    // Previously, the leading 0 would be counted as part of the precision. This would lead to the actual
-                    // value being stored as 0.123.
-                    assertEquals(0,
-                            cstmt.getObject("col1Value", BigDecimal.class).compareTo(BigDecimal.valueOf(bigDecimalLessThanOne)));
-                    assertEquals(0,
-                            cstmt.getObject("col2Value", BigDecimal.class).compareTo(BigDecimal.valueOf(bigDecimalGreaterThanOne)));
-                }
-            }
-        }
-
-        /**
-         * Tests that big decimal values with a precision equal to 38 hold their precision. Tests cases where scale is
-         * 38 (integer part is a 0) and less than 38 (integer part is a non-zero).
-         *
-         * @throws SQLException
-         *         when an error occurs
-         */
-        @Test
-        public void testLongBigDecimalValuesForLossOfPrecision() throws SQLException {
-            try (Connection con = getConnection(); Statement stmt = con.createStatement()) {
-                stmt.executeUpdate("CREATE TABLE " + tableName + " (col1 decimal(38,38), col2 decimal(38,37))");
-
-                // col1 has maximum scale (38) with a leading zero, for a precision of 38. col2 has maximum scale (37) when
-                // using a lead integer other than zero, also resulting in a precision of 38.
-                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(0.98432319763138435186412316842316874322, 1.9843231976313843518641231684231687432)");
-
-                try (PreparedStatement pstmt = con.prepareStatement("SELECT * FROM " + tableName)) {
-
-                    try (ResultSet rs = pstmt.executeQuery()) {
-                        rs.next();
-                        assertEquals(new BigDecimal("0.98432319763138435186412316842316874322"), rs.getObject(1));
-                        assertEquals(new BigDecimal("1.9843231976313843518641231684231687432"), rs.getObject(2));
-                    }
-                }
-            }
-        }
-
-        /**
-         * Tests result of math operation in prepared statement using subtraction
-         * 
-         * @throws SQLException
-         *         when an error occurs
-         */
-        @Test
-        public void testMathBigDecimalSubtraction() throws SQLException {
-            try (Connection con = getConnection(); Statement stmt = con.createStatement()) {
-                stmt.executeUpdate("CREATE TABLE " + tableName + " (test_column decimal(10,5))");
-                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(99999.12345)");
-                try (PreparedStatement pstmt = con.prepareStatement("SELECT (test_column - ?), "
-                        + "(test_column - ?), (test_column - ?), (test_column - ?) FROM " + tableName)) {
-                    BigDecimal value1 = new BigDecimal("1.5");
-                    pstmt.setObject(1, value1);
-                    BigDecimal value2 = new BigDecimal("0");
-                    pstmt.setObject(2, value2);
-                    BigDecimal value3 = new BigDecimal("99999.12345");
-                    pstmt.setObject(3, value3);
-                    BigDecimal value4 = new BigDecimal("99999.2");
-                    pstmt.setObject(4, value4);
-
-                    BigDecimal base = new BigDecimal("99999.12345");
-                    BigDecimal expected1 = base.subtract(value1);
-                    BigDecimal expected2 = base.subtract(value2);
-                    BigDecimal expected3 = base.subtract(value3);
-                    BigDecimal expected4 = base.subtract(value4);
-
-                    try (ResultSet rs = pstmt.executeQuery()) {
-                        rs.next();
-                        assertEquals(expected1, rs.getObject(1));
-                        assertEquals(expected2, rs.getObject(2));
-                        assertEquals(expected3, rs.getObject(3));
-                        assertEquals(expected4, rs.getObject(4));
-                    }
-                }
-            }
-        }
-
-        /**
-         * Tests result of math operation in prepared statement using addition
-         * 
-         * @throws SQLException
-         *         when an error occurs
-         */
-        @Test
-        public void testMathBigDecimalAddition() throws SQLException {
-            try (Connection con = getConnection(); Statement stmt = con.createStatement()) {
-                stmt.executeUpdate("CREATE TABLE " + tableName + " (test_column decimal(10,5))");
-                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(99999.12345)");
-                try (PreparedStatement pstmt = con.prepareStatement("SELECT (test_column + ?), "
-                        + "(test_column + ?), (test_column + ?), (test_column + ?) FROM " + tableName)) {
-                    BigDecimal value1 = new BigDecimal("1.5");
-                    pstmt.setObject(1, value1);
-                    BigDecimal value2 = new BigDecimal("0");
-                    pstmt.setObject(2, value2);
-                    BigDecimal value3 = new BigDecimal("99999.12345");
-                    pstmt.setObject(3, value3);
-                    BigDecimal value4 = new BigDecimal("99999.2");
-                    pstmt.setObject(4, value4);
-
-                    BigDecimal base = new BigDecimal("99999.12345");
-                    BigDecimal expected1 = base.add(value1);
-                    BigDecimal expected2 = base.add(value2);
-                    BigDecimal expected3 = base.add(value3);
-                    BigDecimal expected4 = base.add(value4);
-
-                    try (ResultSet rs = pstmt.executeQuery()) {
-                        rs.next();
-                        assertEquals(expected1, rs.getObject(1));
-                        assertEquals(expected2, rs.getObject(2));
-                        assertEquals(expected3, rs.getObject(3));
-                        assertEquals(expected4, rs.getObject(4));
-                    }
-                }
-            }
-        }
-
-        /**
-         * Tests result of math operation in prepared statement using multiplication
-         * 
-         * @throws SQLException
-         *         when an error occurs
-         */
-        @Test
-        public void testMathBigDecimalMultiplication() throws SQLException {
-            try (Connection con = getConnection(); Statement stmt = con.createStatement()) {
-                stmt.executeUpdate("CREATE TABLE " + tableName + " (test_column decimal(10,5))");
-                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(99999.12345)");
-                try (PreparedStatement pstmt = con.prepareStatement("SELECT (test_column * ?), "
-                        + "(test_column * ?), (test_column * ?), (test_column * ?) FROM " + tableName)) {
-                    BigDecimal value1 = new BigDecimal("1.5");
-                    pstmt.setObject(1, value1);
-                    BigDecimal value2 = new BigDecimal("0");
-                    pstmt.setObject(2, value2);
-                    BigDecimal value3 = new BigDecimal("99999.12345");
-                    pstmt.setObject(3, value3);
-                    BigDecimal value4 = new BigDecimal("99999.2");
-                    pstmt.setObject(4, value4);
-
-                    BigDecimal base = new BigDecimal("99999.12345");
-                    BigDecimal expected1 = base.multiply(value1);
-                    BigDecimal expected2 = base.multiply(value2);
-                    BigDecimal expected3 = base.multiply(value3);
-                    BigDecimal expected4 = base.multiply(value4);
-
-                    try (ResultSet rs = pstmt.executeQuery()) {
-                        rs.next();
-                        assertEquals(expected1, rs.getObject(1));
-                        assertEquals(expected2, rs.getObject(2));
-                        assertEquals(expected3, rs.getObject(3));
-                        assertEquals(expected4, rs.getObject(4));
-                    }
-                }
-            }
-        }
-
-        /**
-         * Tests result of math operation in prepared statement using division
-         * 
-         * @throws SQLException
-         *         when an error occurs
-         */
-        @Test
-        public void testMathBigDecimalDivision() throws SQLException {
-            try (Connection con = getConnection(); Statement stmt = con.createStatement()) {
-                stmt.executeUpdate("CREATE TABLE " + tableName + " (test_column decimal(10,5))");
-                stmt.executeUpdate("INSERT INTO " + tableName + " VALUES(99999.12345)");
-                try (PreparedStatement pstmt = con.prepareStatement("select (test_column / ?), "
-                        + "(test_column / ?), (test_column / ?), (test_column / ?) FROM " + tableName)) {
-
-                    /*
-                     * Division has some unique properties in sql server math operations.
-                     * Notably in this case we cannot compare a result with an infinite trailing decimal
-                     * and the returned value has an expanded precision.
-                     */
-                    BigDecimal value1 = new BigDecimal("1.5");
-                    pstmt.setObject(1, value1);
-                    BigDecimal value2 = new BigDecimal("0.1");
-                    pstmt.setObject(2, value2);
-                    BigDecimal value3 = new BigDecimal("99999.12345");
-                    pstmt.setObject(3, value3);
-                    BigDecimal value4 = new BigDecimal("1");
-                    pstmt.setObject(4, value4);
-
-                    BigDecimal base = new BigDecimal("99999.12345");
-                    BigDecimal expected1 = base.divide(value1, RoundingMode.HALF_UP);
-                    BigDecimal expected2 = base.divide(value2, RoundingMode.HALF_UP);
-                    BigDecimal expected3 = base.divide(value3, RoundingMode.HALF_UP);
-                    BigDecimal expected4 = base.divide(value4, RoundingMode.HALF_UP);
-
-                    try (ResultSet rs = pstmt.executeQuery()) {
-                        rs.next();
-                        assertEquals(0, expected1.compareTo((BigDecimal) rs.getObject(1)));
-                        assertEquals(0, expected2.compareTo((BigDecimal) rs.getObject(2)));
-                        assertEquals(0, expected3.compareTo((BigDecimal) rs.getObject(3)));
-                        assertEquals(0, expected4.compareTo((BigDecimal) rs.getObject(4)));
-                    }
-                }
-            }
-        }
-
         /**
          * 
          * @throws Exception
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
index 93bf7c0d..1d9eb244 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
@@ -73,13 +73,15 @@ public abstract class AbstractTest {
     protected static String trustStore = "";
     protected static String trustStorePassword = "";
 
+    protected static String serverCertificate = "";
+
     protected static String encrypt = "";
     protected static String trustServerCertificate = "";
 
     protected static String windowsKeyPath = null;
     protected static String javaKeyPath = null;
     protected static String javaKeyAliases = null;
-    protected static SQLServerColumnEncryptionKeyStoreProvider jksProvider = null;
+    protected static SQLServerColumnEncryptionJavaKeyStoreProvider jksProvider = null;
     protected static SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = null;
     static boolean isKspRegistered = false;
 
@@ -202,6 +204,12 @@ public abstract class AbstractTest {
                     trustStorePassword);
         }
 
+        serverCertificate = getConfiguredProperty("serverCertificate", "");
+        if (!serverCertificate.trim().isEmpty()) {
+            connectionString = TestUtils.addOrOverrideProperty(connectionString, "serverCertificate",
+                    serverCertificate);
+        }
+
         Map<String, SQLServerColumnEncryptionKeyStoreProvider> map = new HashMap<String, SQLServerColumnEncryptionKeyStoreProvider>();
         if (null == jksProvider) {
             jksProvider = new SQLServerColumnEncryptionJavaKeyStoreProvider(javaKeyPath,
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
index eb387250..ce50791f 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
@@ -20,6 +20,7 @@ public final class Constants {
      * 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
+     * xSQLv16 - - - - - - For tests not compatible with SQL Server 2022
      * 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
@@ -35,6 +36,7 @@ public final class Constants {
     public static final String xSQLv12 = "xSQLv12";
     public static final String xSQLv14 = "xSQLv14";
     public static final String xSQLv15 = "xSQLv15";
+    public static final String xSQLv16 = "xSQLv16";
     public static final String xAzureSQLDB = "xAzureSQLDB";
     public static final String xAzureSQLDW = "xAzureSQLDW";
     public static final String xAzureSQLMI = "xAzureSQLMI";
@@ -111,8 +113,9 @@ public final class Constants {
     public static final String UID = UUID.randomUUID().toString();
     public static final Long RANDOM_LONG = Long.valueOf((long) (RANDOM.nextDouble() * Math.pow(10, 10)));
     public static final String CMK_NAME = "JDBC_CMK_" + RANDOM_LONG;
-    public static final String CMK_SIGNATURE = "0x5859A75C4F8C05C9865DAE46CA70F34B94EF1DF25D185DE8AB67918E5CF02953F2C787EFDC57ACAD5AEC2634C24B0A4C98B3A659220E980D948F91238A5D98CD898EF76CCA4AED9A6D51EBF4EABDEAA28CF975A7C8083E188AA4677303DAC9D6F49C81CB42E4BF14365B56BFEDD6E4B0DF0BC69E9FE313A7001613822D724A97055FA110C495A98EE37BE137293DF9E569D0845B96C4A6DD8554D42E83E784E3DCAFFDDD2CB3A7352920C3AAC43EA79BE5B8C7203B0E2F9B7C317C542E632C529BC46D792D9C85E16B5B3EE50D6D5E53103CC27B60175F79ECA1F2B28995FD1C12FA109E53810192BDD38A3B9A5D2CCC2BCBF16F936FE211A55FD999689C8CC4";
-    public static final String CMK_SIGNATURE_AKV = "0x0474EC516A93B3EBCBD58E3AC35699E125F937AE6A56E96EC4FC37ACD09284CC99702B4762E56E8518A23A4D7EBF62FF735C8EEF4B61326CB8300ED09076D69008ACEB4156D9F2E1F95A8373335C33FCDD7DD7DF69CDD23544AB0D63B6D93379593E15C24D31EC0020F62BA23A19165C8A58AFAE8304DAC2996470919EBAB97587D685AEF4FFA3666E65DA673F41B2204410AFA69B9E05402853AB2AF0D22F4CF498394EB9DA8CA55814601DE6E004B12886C069010F911AE1F0EDA5DA3F1A09A211C7C30D21A567D47A8F133DF20A44E694900344FF3E189A8D6069CB86AA63D168B90CE4150F09A78DC09EF20FC239EA299E964762AE1FF711E407936FA9C9";
+
+    public static final String CMK_SIGNATURE_WIN = "0x625F3105C77DF6881D11B9D5A64491452F288AA9CE66189F19AF9591FAF2525B90CE580DD29CF897F6809EC30AF583CF1603477C5591D785A89DA3CE60E03D9CC494926322E0EF15A4EA38C0FFA0B90D29D051DA037631E6D3B435F87BAB46927ECF366AE96C5259D03A227A2BC04B15E09794CA1ABD1B34CB117BF06962225D4157DD1FBEF998D547EC7E201C00ACF2D2B8A746D1FD3FAEED791132D97EA396CABE9C472C8B559C5EF35342EE9D5635C7C18C1BD168647E3BA52B674626D9F8C3CFAC150300EDA157DE3D1B1D7FFB946D1CCE37ACFD28F62D57365BA89088627EE9469B04142A5F5505B71C94999A8D4A1269F15842DF3DA3CC5B41DDD7B115";
+
     public static final String CEK_NAME = "JDBC_CEK_" + RANDOM_LONG;
     public static final String CEK_ALGORITHM = "RSA_OAEP";
     public static final String CEK_STRING = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DummyKeyStoreProvider.java b/src/test/java/com/microsoft/sqlserver/testframework/DummyKeyStoreProvider.java
index fbfa3fda..bca8fa1a 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/DummyKeyStoreProvider.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/DummyKeyStoreProvider.java
@@ -9,7 +9,7 @@ import com.microsoft.sqlserver.jdbc.SQLServerException;
 
 
 /**
- * DummyKeyStoreProvider Class for Tests. 
+ * DummyKeyStoreProvider Class for Tests.
  * 
  */
 public class DummyKeyStoreProvider extends SQLServerColumnEncryptionKeyStoreProvider {
@@ -28,21 +28,19 @@ public class DummyKeyStoreProvider extends SQLServerColumnEncryptionKeyStoreProv
             byte[] encryptedColumnEncryptionKey) throws SQLServerException {
         // Not implemented
         throw new UnsupportedOperationException();
-            }
+    }
 
     @Override
-    public byte[] encryptColumnEncryptionKey(String masterKeyPath,
-            String encryptionAlgorithm,
+    public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
             byte[] columnEncryptionKey) throws SQLServerException {
         // Not implemented
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public boolean verifyColumnMasterKeyMetadata(String masterKeyPath,
-            boolean allowEnclaveComputations,
+    public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations,
             byte[] signature) throws SQLServerException {
         // Not implemented
         throw new UnsupportedOperationException();
     }
-}
\ No newline at end of file
+}