Repository: microsoft/mssql-jdbc
Pull request #2199 (2023-08-17 18:56:42)

Backport #2160: Fix bug where signature is not verified for Java Key Store and add signColumnMasterkeyMetadata API to JKS and AKV
Backport https://github.com/microsoft/mssql-jdbc/pull/2160.


Patch:
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 0cb09a25..44db5331 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
@@ -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."},
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() {


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/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/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
index 1a47af0b..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()}
      * 
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/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
index 78e09584..1d9eb244 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
@@ -81,7 +81,7 @@ public abstract class AbstractTest {
     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;
 
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
index 5453e9c0..ce50791f 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
@@ -113,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
+}