From c309aae604353aaf8e6dbf97e1ecae9d987103a6 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Thu, 4 Jan 2024 12:29:55 -0800 Subject: [PATCH 01/11] Initial changes --- src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java | 8 ++++++-- .../java/com/microsoft/sqlserver/jdbc/SQLCollation.java | 4 ++++ .../com/microsoft/sqlserver/jdbc/SQLServerResource.java | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 858111ca9..5d97369f3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -415,7 +415,7 @@ void setValue(JDBCType jdbcType, Object value, JavaType javaType, StreamSetterAr // the value with the appropriate corresponding Unicode type. // JavaType.OBJECT == javaType when calling setNull() if (con.sendStringParametersAsUnicode() && (JavaType.STRING == javaType || JavaType.READER == javaType - || JavaType.CLOB == javaType || JavaType.OBJECT == javaType)) { + || JavaType.CLOB == javaType || JavaType.OBJECT == javaType) && jdbcType != JDBCType.VARCHAR) { jdbcType = getSSPAUJDBCType(jdbcType); } @@ -423,10 +423,14 @@ void setValue(JDBCType jdbcType, Object value, JavaType javaType, StreamSetterAr newDTV.setValue(con.getDatabaseCollation(), jdbcType, value, javaType, streamSetterArgs, calendar, scale, con, forceEncrypt); - if (!con.sendStringParametersAsUnicode()) { + if (!con.sendStringParametersAsUnicode() || (con.sendStringParametersAsUnicode() && jdbcType == JDBCType.VARCHAR)) { newDTV.sendStringParametersAsUnicode = false; } + if (con.sendStringParametersAsUnicode() && jdbcType == JDBCType.VARCHAR && (!con.getDatabaseCollation().isUtf8Encoding() || con.getServerMajorVersion() < 15)) { + throw new SQLServerException(SQLServerException.getErrString("R_possibleColumnDataCorruption"), null); + } + inputDTV = setterDTV = newDTV; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java index e7369bfe1..afafe80da 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java @@ -56,6 +56,10 @@ final boolean hasAsciiCompatibleSBCS() { return encoding.hasAsciiCompatibleSBCS(); } + boolean isUtf8Encoding() { + return encoding.equals(Encoding.UTF8); + } + static final int tdsLength() { return TDS_LENGTH; } // Length of collation in TDS (in bytes) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 9055e9add..d5d73c3f4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -165,6 +165,7 @@ protected Object[][] getContents() { {"R_invalidDatetimeType", "The datetimeType connection property {0} is not valid."}, {"R_dataAlreadyAccessed", "The data has been accessed and is not available for this column or parameter."}, {"R_outParamsNotPermittedinBatch", "The OUT and INOUT parameters are not permitted in a batch."}, + {"R_possibleColumnDataCorruption", "Attempted to insert encrypted unicode data into non-unicode column. Data corruption may occur."}, {"R_colNotMatchTable", "Number of provided columns {0} does not match the table definition {1}."}, {"R_invalidSQL", "Invalid SQL query {0}."}, {"R_multipleQueriesNotAllowed", "Multiple queries are not allowed."}, From 3af81f3b70a2340d04b02210c7d02e1d7bab8f8e Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Fri, 5 Jan 2024 18:01:46 -0800 Subject: [PATCH 02/11] AE unicode data corruption guard --- .../microsoft/sqlserver/jdbc/Parameter.java | 10 +- .../jdbc/AlwaysEncrypted/AESetup.java | 151 ++++++++++++++++- .../CallableStatementTest.java | 2 +- .../jdbc/AlwaysEncrypted/EnclaveTest.java | 6 +- .../JDBCEncryptionDecryptionTest.java | 160 +++++++++++++++++- .../jdbc/AlwaysEncrypted/MSITest.java | 2 +- .../ParameterMetaDataCacheTest.java | 10 +- .../sqlserver/jdbc/TestResource.java | 1 + 8 files changed, 323 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 5d97369f3..fd0326728 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -415,7 +415,7 @@ void setValue(JDBCType jdbcType, Object value, JavaType javaType, StreamSetterAr // the value with the appropriate corresponding Unicode type. // JavaType.OBJECT == javaType when calling setNull() if (con.sendStringParametersAsUnicode() && (JavaType.STRING == javaType || JavaType.READER == javaType - || JavaType.CLOB == javaType || JavaType.OBJECT == javaType) && jdbcType != JDBCType.VARCHAR) { + || JavaType.CLOB == javaType || JavaType.OBJECT == javaType) && !((jdbcType == JDBCType.VARCHAR || jdbcType == JDBCType.CHAR) && con.isColumnEncryptionSettingEnabled())) { jdbcType = getSSPAUJDBCType(jdbcType); } @@ -423,11 +423,13 @@ void setValue(JDBCType jdbcType, Object value, JavaType javaType, StreamSetterAr newDTV.setValue(con.getDatabaseCollation(), jdbcType, value, javaType, streamSetterArgs, calendar, scale, con, forceEncrypt); - if (!con.sendStringParametersAsUnicode() || (con.sendStringParametersAsUnicode() && jdbcType == JDBCType.VARCHAR)) { + if (!con.sendStringParametersAsUnicode() || (con.sendStringParametersAsUnicode() + && con.isColumnEncryptionSettingEnabled() && (jdbcType == JDBCType.VARCHAR || jdbcType == JDBCType.CHAR))) { newDTV.sendStringParametersAsUnicode = false; } - if (con.sendStringParametersAsUnicode() && jdbcType == JDBCType.VARCHAR && (!con.getDatabaseCollation().isUtf8Encoding() || con.getServerMajorVersion() < 15)) { + if (con.sendStringParametersAsUnicode() && (jdbcType == JDBCType.VARCHAR || jdbcType == JDBCType.CHAR) && con.isColumnEncryptionSettingEnabled() + && (!con.getDatabaseCollation().isUtf8Encoding() || con.getServerMajorVersion() < 15)) { throw new SQLServerException(SQLServerException.getErrString("R_possibleColumnDataCorruption"), null); } @@ -812,7 +814,7 @@ private void setTypeDefinition(DTV dtv) { } else { param.typeDefinition = SSType.VARCHAR.toString() + "(" + valueLength + ")"; - if (DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength) { + if (DataTypes.SHORT_VARTYPE_MAX_BYTES < valueLength) { param.typeDefinition = VARCHAR_MAX; } } 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 541f2da16..e42767bda 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -11,6 +11,7 @@ import java.io.FileReader; import java.io.IOException; import java.math.BigDecimal; +import java.sql.Connection; import java.sql.Date; import java.sql.JDBCType; import java.sql.SQLException; @@ -62,7 +63,6 @@ public class AESetup extends AbstractTest { static String cekWin = Constants.CEK_NAME + "_WIN"; static String cekAkv = Constants.CEK_NAME + "_AKV"; static SQLServerStatementColumnEncryptionSetting stmtColEncSetting = null; - static String AETestConnectionString; static String enclaveProperties = ""; @@ -73,6 +73,8 @@ public class AESetup extends AbstractTest { .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("AETest_"))); public static final String CHAR_TABLE_AE = TestUtils .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBCEncryptedChar"))); + public static final String CHAR_TABLE_AE_NON_UNICODE = TestUtils + .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBCEncryptedCharNonUnicode"))); public static final String BINARY_TABLE_AE = TestUtils .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBCEncryptedBinary"))); public static final String DATE_TABLE_AE = TestUtils @@ -107,6 +109,16 @@ enum ColumnType { {"Varchar8000", "varchar(8000) COLLATE Latin1_General_BIN2", "CHAR"}, {"Nvarchar4000", "nvarchar(4000) COLLATE Latin1_General_BIN2", "NCHAR"},}; + static String charTableNonUnicode[][] = {{"Char", "char(20) COLLATE Latin1_General_BIN2", "CHAR"}, + {"Varchar", "varchar(50) COLLATE Latin1_General_BIN2", "CHAR"}, + {"VarcharMax", "varchar(max) COLLATE Latin1_General_BIN2", "LONGVARCHAR"}, + {"Varchar8000", "varchar(8000) COLLATE Latin1_General_BIN2", "CHAR"},}; + + static String charTableUTF8Collate[][] = {{"Char", "char(20) COLLATE Latin1_General_100_BIN2_UTF8", "CHAR"}, + {"Varchar", "varchar(50) COLLATE Latin1_General_100_BIN2_UTF8", "CHAR"}, + {"VarcharMax", "varchar(max) COLLATE Latin1_General_100_BIN2_UTF8", "LONGVARCHAR"}, + {"Varchar8000", "varchar(8000) COLLATE Latin1_General_100_BIN2_UTF8", "CHAR"},}; + static String dateTable[][] = {{"Date", "date", "DATE"}, {"Datetime2Default", "datetime2", "TIMESTAMP"}, {"DatetimeoffsetDefault", "datetimeoffset", "DATETIMEOFFSET"}, {"TimeDefault", "time", "TIME"}, {"Datetime", "datetime", "DATETIME"}, {"Smalldatetime", "smalldatetime", "SMALLDATETIME"}}; @@ -338,6 +350,25 @@ protected static void createTable(String tableName, String cekName, String table } } + protected static void createTable(String tableName, String cekName, String table[][], SQLServerStatement stmt) { + try { + String sql = ""; + for (int i = 0; i < table.length; i++) { + sql += ColumnType.PLAIN.name() + table[i][0] + " " + table[i][1] + " NULL,"; + sql += ColumnType.DETERMINISTIC.name() + table[i][0] + " " + table[i][1] + + String.format(encryptSql, ColumnType.DETERMINISTIC.name(), cekName) + ") NULL,"; + sql += ColumnType.RANDOMIZED.name() + table[i][0] + " " + table[i][1] + + String.format(encryptSql, ColumnType.RANDOMIZED.name(), cekName) + ") NULL,"; + } + TestUtils.dropTableIfExists(tableName, stmt); + sql = String.format(createSql, tableName, sql); + stmt.execute(sql); + stmt.execute("DBCC FREEPROCCACHE"); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + protected static void createPrecisionTable(String tableName, String table[][], String cekName, int floatPrecision, int precision, int scale) throws SQLException { try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString, AEInfo); @@ -400,6 +431,22 @@ protected static void createScaleTable(String tableName, String table[][], Strin } } + protected static void createDatabaseWithUtf8Collation(Connection conn, String dbName) throws SQLException { + try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) { + String dropDB = "IF EXISTS (SELECT name FROM sys.databases WHERE name = N'"+ dbName + "') DROP DATABASE " + dbName + ";"; + String createDB = "CREATE DATABASE " + dbName + " COLLATE Latin1_General_100_CS_AS_WS_SC_UTF8"; + stmt.execute(dropDB); + stmt.execute(createDB); + } + } + + protected static void dropDatabaseWithUtf8Collation(Connection conn, String dbName) throws SQLException { + try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) { + String dropDB = "IF EXISTS (SELECT name FROM sys.databases WHERE name = N'"+ dbName + "') DROP DATABASE " + dbName + ";"; + stmt.execute(dropDB); + } + } + /** * Create a list of binary values * @@ -449,6 +496,24 @@ protected static String[] createCharValues(boolean nullable) { return values; } + /** + * Create a list of char values for non-unicode data types + * + * @param nullable + */ + protected static String[] createCharValuesNonUnicode(boolean nullable) { + + boolean encrypted = true; + String char20 = RandomData.generateCharTypes("20", nullable, encrypted); + String varchar50 = RandomData.generateCharTypes("50", nullable, encrypted); + String varcharmax = RandomData.generateCharTypes("max", nullable, encrypted); + String varchar8000 = RandomData.generateCharTypes("8000", nullable, encrypted); + + String[] values = {char20.trim(), varchar50, varcharmax, varchar8000}; + + return values; + } + /** * Create a list of numeric values * @@ -805,11 +870,12 @@ protected static void populateBinaryNullCase() throws SQLException { * @param charValues * @throws SQLException */ - protected static void populateCharNormalCase(String[] charValues) throws SQLException { + protected static void populateCharNormalCase(String[] charValues, boolean sendStringParametersAsUnicode) throws SQLException { String sql = "insert into " + CHAR_TABLE_AE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?" + ")"; - try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString, AEInfo); + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "sendStringParametersAsUnicode", Boolean.toString(sendStringParametersAsUnicode)); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(connectionString, AEInfo); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, stmtColEncSetting)) { @@ -866,6 +932,82 @@ protected static void populateCharNormalCase(String[] charValues) throws SQLExce } } + /** + * Populate char data non-unicode. + * + * @param charValues + * @throws SQLException + */ + protected static void populateCharNormalCaseNonUnicode(String connectionString, String[] charValues, boolean sendStringParametersAsUnicode) throws SQLException { + String sql = "insert into " + CHAR_TABLE_AE_NON_UNICODE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?)"; + + String cs = TestUtils.addOrOverrideProperty(connectionString, "sendStringParametersAsUnicode", Boolean.toString(sendStringParametersAsUnicode)); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(cs, AEInfo); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, + stmtColEncSetting)) { + + // char + for (int i = 1; i <= 3; i++) { + pstmt.setString(i, charValues[0]); + } + + // varchar + for (int i = 4; i <= 6; i++) { + pstmt.setString(i, charValues[1]); + } + + // varchar(max) + for (int i = 7; i <= 9; i++) { + pstmt.setString(i, charValues[2]); + } + + // varchar8000 + for (int i = 10; i <= 12; i++) { + pstmt.setString(i, charValues[3]); + } + + pstmt.execute(); + } + } + + /** + * Populate char data using set object. + * + * @param charValues + * @throws SQLException + */ + protected static void populateCharSetObjectNonUnicode(String connectionString, String[] charValues, boolean sendStringParametersAsUnicode) throws SQLException { + String sql = "insert into " + CHAR_TABLE_AE_NON_UNICODE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?)"; + + String cs = TestUtils.addOrOverrideProperty(connectionString, "sendStringParametersAsUnicode", Boolean.toString(sendStringParametersAsUnicode)); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(cs, AEInfo); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, + stmtColEncSetting)) { + + // char + for (int i = 1; i <= 3; i++) { + pstmt.setObject(i, charValues[0]); + } + + // varchar + for (int i = 4; i <= 6; i++) { + pstmt.setObject(i, charValues[1]); + } + + // varchar(max) + for (int i = 7; i <= 9; i++) { + pstmt.setObject(i, charValues[2], java.sql.Types.LONGVARCHAR); + } + + // varchar8000 + for (int i = 10; i <= 12; i++) { + pstmt.setObject(i, charValues[3]); + } + + pstmt.execute(); + } + } + /** * Populate char data using set object. * @@ -876,7 +1018,8 @@ protected static void populateCharSetObject(String[] charValues) throws SQLExcep String sql = "insert into " + CHAR_TABLE_AE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?" + ")"; - try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString, AEInfo); + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "sendStringParametersAsUnicode", "false"); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(connectionString, AEInfo); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, stmtColEncSetting)) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java index 95531d698..cb1c7c1f3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -149,7 +149,7 @@ public static void initValues() throws Exception { createTable(BINARY_TABLE_AE, cekJks, binaryTable); createDateTableCallableStatement(cekJks); - populateCharNormalCase(charValues); + populateCharNormalCase(charValues, false); populateNumericSetObject(numericValues); populateBinaryNormalCase(byteValues); populateDateNormalCase(); 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 4b55573af..fe47cd5be 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java @@ -187,7 +187,7 @@ public void testAEv2Disabled(String serverName, String url, String protocol) thr String[] values = createCharValues(false); TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekJks, charTable); - populateCharNormalCase(values); + populateCharNormalCase(values, false); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekJks); fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (Throwable e) { @@ -346,7 +346,7 @@ public void testChar(String serverName, String url, String protocol) throws Exce SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekJks, charTable); - populateCharNormalCase(createCharValues(false)); + populateCharNormalCase(createCharValues(false), false); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekJks); } } @@ -363,7 +363,7 @@ public void testCharAkv(String serverName, String url, String protocol) throws E SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); - populateCharNormalCase(createCharValues(false)); + populateCharNormalCase(createCharValues(false), false); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekAkv); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index 7376baca7..68ac44e45 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -4,6 +4,7 @@ */ package com.microsoft.sqlserver.jdbc.AlwaysEncrypted; +import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -11,6 +12,7 @@ import com.microsoft.aad.msal4j.ClientCredentialParameters; import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.IClientCredential; +import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerKeyVaultAuthenticationCallback; import java.math.BigDecimal; import java.sql.Date; @@ -29,10 +31,14 @@ import com.azure.identity.ClientSecretCredentialBuilder; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -67,6 +73,7 @@ @Tag(Constants.reqExternalSetup) public class JDBCEncryptionDecryptionTest extends AESetup { private boolean nullable = false; + private static final String UTF8_COLLATE_DB = "JDBC_UTF8_COLLATE_DB_" + UUID.randomUUID().toString().replace("-", ""); enum TestCase { NORMAL, @@ -77,6 +84,26 @@ enum TestCase { NULL } + @BeforeAll + public static void init() throws SQLException { + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { + dropDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + createDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + + String utf8CollatedDbConnectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); + createCMK(utf8CollatedDbConnectionString, cmkJks, Constants.JAVA_KEY_STORE_NAME, javaKeyAliases, + TestUtils.byteToHexDisplayString(jksProvider.signColumnMasterKeyMetadata(javaKeyAliases, true))); + createCEK(utf8CollatedDbConnectionString, cmkJks, cekJks, jksProvider); + } + } + + @AfterAll + public static void cleanup() throws SQLException { + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { + //dropDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + } + } + /* * Test getting/setting JKS name */ @@ -356,6 +383,137 @@ public void testAkvDecryptColumnEncryptionKey(String serverName, String url, Str } } + @ParameterizedTest + @MethodSource("enclaveParams") + public void testCharErrorNonUnicodeColumnAndSSPAUIsTrue(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { + + String[] values = createCharValuesNonUnicode(nullable); + TestUtils.dropTableIfExists(CHAR_TABLE_AE_NON_UNICODE, stmt); + createTable(CHAR_TABLE_AE_NON_UNICODE, cekJks, charTableNonUnicode); + + // Insert unicode strings into non-unicode column - should fail as UTF8 collation is not used + populateCharNormalCaseNonUnicode(AETestConnectionString, values, true); + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_possibleColumnDataCorruption"), e.getMessage()); + } + } + + @Tag(Constants.xSQLv14) + @ParameterizedTest + @MethodSource("enclaveParams") + @Tag(Constants.reqExternalSetup) + public void testCharErrorSetObjectNonUnicodeColumnAndSSPAUIsTrue(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { + + String[] values = createCharValuesNonUnicode(nullable); + TestUtils.dropTableIfExists(CHAR_TABLE_AE_NON_UNICODE, stmt); + createTable(CHAR_TABLE_AE_NON_UNICODE, cekJks, charTableNonUnicode); + + // Insert unicode strings into non-unicode column - should fail as UTF8 collation is not used + populateCharSetObjectNonUnicode(AETestConnectionString, values, true); + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_possibleColumnDataCorruption"), e.getMessage()); + } + } + + @Tag(Constants.xSQLv14) + @ParameterizedTest + @MethodSource("enclaveParams") + public void testCharNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); + try (SQLServerConnection con = PrepUtil.getConnection(connectionString, AEInfo); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { + + String[] values = createCharValuesNonUnicode(nullable); + TestUtils.dropTableIfExists(CHAR_TABLE_AE_NON_UNICODE, stmt); + createTable(CHAR_TABLE_AE_NON_UNICODE, cekJks, charTableUTF8Collate, stmt); + + // Insert unicode strings into non-unicode column using setString() - should succeed as UTF8 collation is used + // and server is >= version 15 + populateCharNormalCaseNonUnicode(connectionString, values, true); + + try (SQLServerStatement statement = (SQLServerStatement) con.createStatement()) { + ResultSet rs = statement.executeQuery("SELECT * FROM " + CHAR_TABLE_AE_NON_UNICODE); + rs.next(); + + for (int i = 1; i <= 3; i++) { + assertEquals(values[0], rs.getString(i)); + } + + // varchar + for (int i = 4; i <= 6; i++) { + assertEquals(values[1], rs.getString(i)); + } + + // varchar(max) + for (int i = 7; i <= 9; i++) { + assertEquals(values[2], rs.getString(i)); + } + + // varchar8000 + for (int i = 10; i <= 12; i++) { + assertEquals(values[3], rs.getString(i)); + } + } + } + } + + @Tag(Constants.xSQLv14) + @ParameterizedTest + @MethodSource("enclaveParams") + @Tag(Constants.reqExternalSetup) + public void testCharSetObjectNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); + try (SQLServerConnection con = PrepUtil.getConnection(connectionString, AEInfo); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { + + String[] values = createCharValuesNonUnicode(nullable); + TestUtils.dropTableIfExists(CHAR_TABLE_AE_NON_UNICODE, stmt); + createTable(CHAR_TABLE_AE_NON_UNICODE, cekJks, charTableUTF8Collate, stmt); + + // Insert unicode strings into non-unicode column using setObject() - should succeed as UTF8 collation is used + // and server is >= version 15 + populateCharSetObjectNonUnicode(connectionString, values, true); + + try (SQLServerStatement statement = (SQLServerStatement) con.createStatement()) { + ResultSet rs = statement.executeQuery("SELECT * FROM " + CHAR_TABLE_AE_NON_UNICODE); + rs.next(); + + for (int i = 1; i <= 3; i++) { + assertEquals(values[0], rs.getString(i)); + } + + // varchar + for (int i = 4; i <= 6; i++) { + assertEquals(values[1], rs.getString(i)); + } + + // varchar(max) + for (int i = 7; i <= 9; i++) { + assertEquals(values[2], rs.getString(i)); + } + + // varchar8000 + for (int i = 10; i <= 12; i++) { + assertEquals(values[3], rs.getString(i)); + } + } + } + } + /** * Junit test case for char set string for string values * @@ -2115,7 +2273,7 @@ void testChars(SQLServerStatement stmt, String cekName, String[][] table, String switch (testCase) { case NORMAL: - populateCharNormalCase(values); + populateCharNormalCase(values, false); break; case SETOBJECT: populateCharSetObject(values); 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 9aeb0da8f..df727ca72 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java @@ -419,7 +419,7 @@ private void testCharAkv(String connStr) throws SQLException { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); String[] values = createCharValues(false); - populateCharNormalCase(values); + populateCharNormalCase(values, false); try (ResultSet rs = (stmt == null) ? pstmt.executeQuery() : stmt.executeQuery(sql)) { int numberOfColumns = rs.getMetaData().getColumnCount(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java index cef219d4a..4a1db9fe0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java @@ -119,9 +119,9 @@ public void testParameterMetaDataCacheTrim() throws Exception { createTable(CHAR_TABLE_AE, cekAkv, charTable); createTable(NUMERIC_TABLE_AE, cekAkv, numericTable); - populateCharNormalCase(charValues); + populateCharNormalCase(charValues, false); populateNumeric(numericValues); - populateCharNormalCase(charValues); + populateCharNormalCase(charValues, false); } } @@ -144,17 +144,17 @@ public void testRetryWithSecureCache() throws Exception { String[] values = createCharValues(false); TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); - populateCharNormalCase(values); + populateCharNormalCase(values, false); if (TestUtils.doesServerSupportEnclaveRetry(con)) { testAlterColumnEncryption((SQLServerStatement) stmt, CHAR_TABLE_AE, charTable, cekAkv); } - populateCharNormalCase(values); + populateCharNormalCase(values, false); } } private long timedCharUpdate(String[] values) throws SQLException { long timer = System.currentTimeMillis(); - populateCharNormalCase(values); + populateCharNormalCase(values, false); return System.currentTimeMillis() - timer; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index 449ea1f0b..02faf05a4 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -69,6 +69,7 @@ protected Object[][] getContents() { {"R_notValidParameterForProcedure", "{0} is not a parameter for procedure {1}."}, {"R_unexpectedExceptionContent", "Unexpected content in exception message"}, {"R_connectionClosed", "The connection has been closed"}, + {"R_possibleColumnDataCorruption", "Attempted to insert encrypted unicode data into non-unicode column. Data corruption may occur."}, {"R_conversionFailed", "Conversion failed when converting {0} to {1} data type"}, {"R_invalidQueryTimeout", "The query timeout value {0} is not valid."}, {"R_skipAzure", "Skipping test case on Azure SQL."}, From 4b0f8540138de2a3696bfbfd2743d07411bbcbef Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Mon, 8 Jan 2024 11:00:23 -0800 Subject: [PATCH 03/11] Updated test testAddbatch2AEOnConnection --- .../jdbc/preparedStatement/BatchExecutionWithNullTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java index 672708de3..d4f9413f4 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java @@ -98,7 +98,9 @@ public void testAddBatch2(Connection conn) throws SQLException { @Tag(Constants.xSQLv11) @Tag(Constants.xSQLv12) public void testAddbatch2AEOnConnection() throws SQLException { - try (Connection connection = PrepUtil.getConnection(connectionString + ";columnEncryptionSetting=Enabled;")) { + String cs = TestUtils.addOrOverrideProperty(connectionString, "columnEncryptionSetting", "Enabled"); + cs = TestUtils.addOrOverrideProperty(cs, "sendStringParametersAsUnicode", "false"); + try (Connection connection = PrepUtil.getConnection(cs)) { testAddBatch2(connection); } } From 84f8012534d0b46d9958b60747e5f5f9fde127d6 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Thu, 4 Jan 2024 12:29:55 -0800 Subject: [PATCH 04/11] Initial changes --- src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java | 8 ++++++-- .../java/com/microsoft/sqlserver/jdbc/SQLCollation.java | 4 ++++ .../com/microsoft/sqlserver/jdbc/SQLServerResource.java | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 4894c45f2..15ee6712b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -415,7 +415,7 @@ void setValue(JDBCType jdbcType, Object value, JavaType javaType, StreamSetterAr // the value with the appropriate corresponding Unicode type. // JavaType.OBJECT == javaType when calling setNull() if (con.sendStringParametersAsUnicode() && (JavaType.STRING == javaType || JavaType.READER == javaType - || JavaType.CLOB == javaType || JavaType.OBJECT == javaType)) { + || JavaType.CLOB == javaType || JavaType.OBJECT == javaType) && jdbcType != JDBCType.VARCHAR) { jdbcType = getSSPAUJDBCType(jdbcType); } @@ -423,10 +423,14 @@ void setValue(JDBCType jdbcType, Object value, JavaType javaType, StreamSetterAr newDTV.setValue(con.getDatabaseCollation(), jdbcType, value, javaType, streamSetterArgs, calendar, scale, con, forceEncrypt); - if (!con.sendStringParametersAsUnicode()) { + if (!con.sendStringParametersAsUnicode() || (con.sendStringParametersAsUnicode() && jdbcType == JDBCType.VARCHAR)) { newDTV.sendStringParametersAsUnicode = false; } + if (con.sendStringParametersAsUnicode() && jdbcType == JDBCType.VARCHAR && (!con.getDatabaseCollation().isUtf8Encoding() || con.getServerMajorVersion() < 15)) { + throw new SQLServerException(SQLServerException.getErrString("R_possibleColumnDataCorruption"), null); + } + inputDTV = setterDTV = newDTV; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java index e7369bfe1..afafe80da 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java @@ -56,6 +56,10 @@ final boolean hasAsciiCompatibleSBCS() { return encoding.hasAsciiCompatibleSBCS(); } + boolean isUtf8Encoding() { + return encoding.equals(Encoding.UTF8); + } + static final int tdsLength() { return TDS_LENGTH; } // Length of collation in TDS (in bytes) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 95b5ffa0b..4911f670b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -165,6 +165,7 @@ protected Object[][] getContents() { {"R_invalidDatetimeType", "The datetimeType connection property {0} is not valid."}, {"R_dataAlreadyAccessed", "The data has been accessed and is not available for this column or parameter."}, {"R_outParamsNotPermittedinBatch", "The OUT and INOUT parameters are not permitted in a batch."}, + {"R_possibleColumnDataCorruption", "Attempted to insert encrypted unicode data into non-unicode column. Data corruption may occur."}, {"R_colNotMatchTable", "Number of provided columns {0} does not match the table definition {1}."}, {"R_invalidSQL", "Invalid SQL query {0}."}, {"R_multipleQueriesNotAllowed", "Multiple queries are not allowed."}, From dbb883f6d1416579e3de9d3847313bd20866371b Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Fri, 5 Jan 2024 18:01:46 -0800 Subject: [PATCH 05/11] AE unicode data corruption guard --- .../microsoft/sqlserver/jdbc/Parameter.java | 10 +- .../jdbc/AlwaysEncrypted/AESetup.java | 151 ++++++++++++++++- .../CallableStatementTest.java | 2 +- .../jdbc/AlwaysEncrypted/EnclaveTest.java | 6 +- .../JDBCEncryptionDecryptionTest.java | 160 +++++++++++++++++- .../jdbc/AlwaysEncrypted/MSITest.java | 2 +- .../ParameterMetaDataCacheTest.java | 10 +- .../sqlserver/jdbc/TestResource.java | 1 + 8 files changed, 323 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 15ee6712b..eb15acbf9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -415,7 +415,7 @@ void setValue(JDBCType jdbcType, Object value, JavaType javaType, StreamSetterAr // the value with the appropriate corresponding Unicode type. // JavaType.OBJECT == javaType when calling setNull() if (con.sendStringParametersAsUnicode() && (JavaType.STRING == javaType || JavaType.READER == javaType - || JavaType.CLOB == javaType || JavaType.OBJECT == javaType) && jdbcType != JDBCType.VARCHAR) { + || JavaType.CLOB == javaType || JavaType.OBJECT == javaType) && !((jdbcType == JDBCType.VARCHAR || jdbcType == JDBCType.CHAR) && con.isColumnEncryptionSettingEnabled())) { jdbcType = getSSPAUJDBCType(jdbcType); } @@ -423,11 +423,13 @@ void setValue(JDBCType jdbcType, Object value, JavaType javaType, StreamSetterAr newDTV.setValue(con.getDatabaseCollation(), jdbcType, value, javaType, streamSetterArgs, calendar, scale, con, forceEncrypt); - if (!con.sendStringParametersAsUnicode() || (con.sendStringParametersAsUnicode() && jdbcType == JDBCType.VARCHAR)) { + if (!con.sendStringParametersAsUnicode() || (con.sendStringParametersAsUnicode() + && con.isColumnEncryptionSettingEnabled() && (jdbcType == JDBCType.VARCHAR || jdbcType == JDBCType.CHAR))) { newDTV.sendStringParametersAsUnicode = false; } - if (con.sendStringParametersAsUnicode() && jdbcType == JDBCType.VARCHAR && (!con.getDatabaseCollation().isUtf8Encoding() || con.getServerMajorVersion() < 15)) { + if (con.sendStringParametersAsUnicode() && (jdbcType == JDBCType.VARCHAR || jdbcType == JDBCType.CHAR) && con.isColumnEncryptionSettingEnabled() + && (!con.getDatabaseCollation().isUtf8Encoding() || con.getServerMajorVersion() < 15)) { throw new SQLServerException(SQLServerException.getErrString("R_possibleColumnDataCorruption"), null); } @@ -812,7 +814,7 @@ private void setTypeDefinition(DTV dtv) { } else { param.typeDefinition = SSType.VARCHAR.toString() + "(" + valueLength + ")"; - if (DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength) { + if (DataTypes.SHORT_VARTYPE_MAX_BYTES < valueLength) { param.typeDefinition = VARCHAR_MAX; } } 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 541f2da16..e42767bda 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -11,6 +11,7 @@ import java.io.FileReader; import java.io.IOException; import java.math.BigDecimal; +import java.sql.Connection; import java.sql.Date; import java.sql.JDBCType; import java.sql.SQLException; @@ -62,7 +63,6 @@ public class AESetup extends AbstractTest { static String cekWin = Constants.CEK_NAME + "_WIN"; static String cekAkv = Constants.CEK_NAME + "_AKV"; static SQLServerStatementColumnEncryptionSetting stmtColEncSetting = null; - static String AETestConnectionString; static String enclaveProperties = ""; @@ -73,6 +73,8 @@ public class AESetup extends AbstractTest { .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("AETest_"))); public static final String CHAR_TABLE_AE = TestUtils .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBCEncryptedChar"))); + public static final String CHAR_TABLE_AE_NON_UNICODE = TestUtils + .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBCEncryptedCharNonUnicode"))); public static final String BINARY_TABLE_AE = TestUtils .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBCEncryptedBinary"))); public static final String DATE_TABLE_AE = TestUtils @@ -107,6 +109,16 @@ enum ColumnType { {"Varchar8000", "varchar(8000) COLLATE Latin1_General_BIN2", "CHAR"}, {"Nvarchar4000", "nvarchar(4000) COLLATE Latin1_General_BIN2", "NCHAR"},}; + static String charTableNonUnicode[][] = {{"Char", "char(20) COLLATE Latin1_General_BIN2", "CHAR"}, + {"Varchar", "varchar(50) COLLATE Latin1_General_BIN2", "CHAR"}, + {"VarcharMax", "varchar(max) COLLATE Latin1_General_BIN2", "LONGVARCHAR"}, + {"Varchar8000", "varchar(8000) COLLATE Latin1_General_BIN2", "CHAR"},}; + + static String charTableUTF8Collate[][] = {{"Char", "char(20) COLLATE Latin1_General_100_BIN2_UTF8", "CHAR"}, + {"Varchar", "varchar(50) COLLATE Latin1_General_100_BIN2_UTF8", "CHAR"}, + {"VarcharMax", "varchar(max) COLLATE Latin1_General_100_BIN2_UTF8", "LONGVARCHAR"}, + {"Varchar8000", "varchar(8000) COLLATE Latin1_General_100_BIN2_UTF8", "CHAR"},}; + static String dateTable[][] = {{"Date", "date", "DATE"}, {"Datetime2Default", "datetime2", "TIMESTAMP"}, {"DatetimeoffsetDefault", "datetimeoffset", "DATETIMEOFFSET"}, {"TimeDefault", "time", "TIME"}, {"Datetime", "datetime", "DATETIME"}, {"Smalldatetime", "smalldatetime", "SMALLDATETIME"}}; @@ -338,6 +350,25 @@ protected static void createTable(String tableName, String cekName, String table } } + protected static void createTable(String tableName, String cekName, String table[][], SQLServerStatement stmt) { + try { + String sql = ""; + for (int i = 0; i < table.length; i++) { + sql += ColumnType.PLAIN.name() + table[i][0] + " " + table[i][1] + " NULL,"; + sql += ColumnType.DETERMINISTIC.name() + table[i][0] + " " + table[i][1] + + String.format(encryptSql, ColumnType.DETERMINISTIC.name(), cekName) + ") NULL,"; + sql += ColumnType.RANDOMIZED.name() + table[i][0] + " " + table[i][1] + + String.format(encryptSql, ColumnType.RANDOMIZED.name(), cekName) + ") NULL,"; + } + TestUtils.dropTableIfExists(tableName, stmt); + sql = String.format(createSql, tableName, sql); + stmt.execute(sql); + stmt.execute("DBCC FREEPROCCACHE"); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + protected static void createPrecisionTable(String tableName, String table[][], String cekName, int floatPrecision, int precision, int scale) throws SQLException { try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString, AEInfo); @@ -400,6 +431,22 @@ protected static void createScaleTable(String tableName, String table[][], Strin } } + protected static void createDatabaseWithUtf8Collation(Connection conn, String dbName) throws SQLException { + try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) { + String dropDB = "IF EXISTS (SELECT name FROM sys.databases WHERE name = N'"+ dbName + "') DROP DATABASE " + dbName + ";"; + String createDB = "CREATE DATABASE " + dbName + " COLLATE Latin1_General_100_CS_AS_WS_SC_UTF8"; + stmt.execute(dropDB); + stmt.execute(createDB); + } + } + + protected static void dropDatabaseWithUtf8Collation(Connection conn, String dbName) throws SQLException { + try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) { + String dropDB = "IF EXISTS (SELECT name FROM sys.databases WHERE name = N'"+ dbName + "') DROP DATABASE " + dbName + ";"; + stmt.execute(dropDB); + } + } + /** * Create a list of binary values * @@ -449,6 +496,24 @@ protected static String[] createCharValues(boolean nullable) { return values; } + /** + * Create a list of char values for non-unicode data types + * + * @param nullable + */ + protected static String[] createCharValuesNonUnicode(boolean nullable) { + + boolean encrypted = true; + String char20 = RandomData.generateCharTypes("20", nullable, encrypted); + String varchar50 = RandomData.generateCharTypes("50", nullable, encrypted); + String varcharmax = RandomData.generateCharTypes("max", nullable, encrypted); + String varchar8000 = RandomData.generateCharTypes("8000", nullable, encrypted); + + String[] values = {char20.trim(), varchar50, varcharmax, varchar8000}; + + return values; + } + /** * Create a list of numeric values * @@ -805,11 +870,12 @@ protected static void populateBinaryNullCase() throws SQLException { * @param charValues * @throws SQLException */ - protected static void populateCharNormalCase(String[] charValues) throws SQLException { + protected static void populateCharNormalCase(String[] charValues, boolean sendStringParametersAsUnicode) throws SQLException { String sql = "insert into " + CHAR_TABLE_AE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?" + ")"; - try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString, AEInfo); + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "sendStringParametersAsUnicode", Boolean.toString(sendStringParametersAsUnicode)); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(connectionString, AEInfo); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, stmtColEncSetting)) { @@ -866,6 +932,82 @@ protected static void populateCharNormalCase(String[] charValues) throws SQLExce } } + /** + * Populate char data non-unicode. + * + * @param charValues + * @throws SQLException + */ + protected static void populateCharNormalCaseNonUnicode(String connectionString, String[] charValues, boolean sendStringParametersAsUnicode) throws SQLException { + String sql = "insert into " + CHAR_TABLE_AE_NON_UNICODE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?)"; + + String cs = TestUtils.addOrOverrideProperty(connectionString, "sendStringParametersAsUnicode", Boolean.toString(sendStringParametersAsUnicode)); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(cs, AEInfo); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, + stmtColEncSetting)) { + + // char + for (int i = 1; i <= 3; i++) { + pstmt.setString(i, charValues[0]); + } + + // varchar + for (int i = 4; i <= 6; i++) { + pstmt.setString(i, charValues[1]); + } + + // varchar(max) + for (int i = 7; i <= 9; i++) { + pstmt.setString(i, charValues[2]); + } + + // varchar8000 + for (int i = 10; i <= 12; i++) { + pstmt.setString(i, charValues[3]); + } + + pstmt.execute(); + } + } + + /** + * Populate char data using set object. + * + * @param charValues + * @throws SQLException + */ + protected static void populateCharSetObjectNonUnicode(String connectionString, String[] charValues, boolean sendStringParametersAsUnicode) throws SQLException { + String sql = "insert into " + CHAR_TABLE_AE_NON_UNICODE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?)"; + + String cs = TestUtils.addOrOverrideProperty(connectionString, "sendStringParametersAsUnicode", Boolean.toString(sendStringParametersAsUnicode)); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(cs, AEInfo); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, + stmtColEncSetting)) { + + // char + for (int i = 1; i <= 3; i++) { + pstmt.setObject(i, charValues[0]); + } + + // varchar + for (int i = 4; i <= 6; i++) { + pstmt.setObject(i, charValues[1]); + } + + // varchar(max) + for (int i = 7; i <= 9; i++) { + pstmt.setObject(i, charValues[2], java.sql.Types.LONGVARCHAR); + } + + // varchar8000 + for (int i = 10; i <= 12; i++) { + pstmt.setObject(i, charValues[3]); + } + + pstmt.execute(); + } + } + /** * Populate char data using set object. * @@ -876,7 +1018,8 @@ protected static void populateCharSetObject(String[] charValues) throws SQLExcep String sql = "insert into " + CHAR_TABLE_AE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?" + ")"; - try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString, AEInfo); + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "sendStringParametersAsUnicode", "false"); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(connectionString, AEInfo); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, stmtColEncSetting)) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java index 95531d698..cb1c7c1f3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -149,7 +149,7 @@ public static void initValues() throws Exception { createTable(BINARY_TABLE_AE, cekJks, binaryTable); createDateTableCallableStatement(cekJks); - populateCharNormalCase(charValues); + populateCharNormalCase(charValues, false); populateNumericSetObject(numericValues); populateBinaryNormalCase(byteValues); populateDateNormalCase(); 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 4b55573af..fe47cd5be 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java @@ -187,7 +187,7 @@ public void testAEv2Disabled(String serverName, String url, String protocol) thr String[] values = createCharValues(false); TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekJks, charTable); - populateCharNormalCase(values); + populateCharNormalCase(values, false); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekJks); fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (Throwable e) { @@ -346,7 +346,7 @@ public void testChar(String serverName, String url, String protocol) throws Exce SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekJks, charTable); - populateCharNormalCase(createCharValues(false)); + populateCharNormalCase(createCharValues(false), false); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekJks); } } @@ -363,7 +363,7 @@ public void testCharAkv(String serverName, String url, String protocol) throws E SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); - populateCharNormalCase(createCharValues(false)); + populateCharNormalCase(createCharValues(false), false); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekAkv); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index 7376baca7..68ac44e45 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -4,6 +4,7 @@ */ package com.microsoft.sqlserver.jdbc.AlwaysEncrypted; +import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -11,6 +12,7 @@ import com.microsoft.aad.msal4j.ClientCredentialParameters; import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.IClientCredential; +import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerKeyVaultAuthenticationCallback; import java.math.BigDecimal; import java.sql.Date; @@ -29,10 +31,14 @@ import com.azure.identity.ClientSecretCredentialBuilder; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -67,6 +73,7 @@ @Tag(Constants.reqExternalSetup) public class JDBCEncryptionDecryptionTest extends AESetup { private boolean nullable = false; + private static final String UTF8_COLLATE_DB = "JDBC_UTF8_COLLATE_DB_" + UUID.randomUUID().toString().replace("-", ""); enum TestCase { NORMAL, @@ -77,6 +84,26 @@ enum TestCase { NULL } + @BeforeAll + public static void init() throws SQLException { + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { + dropDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + createDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + + String utf8CollatedDbConnectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); + createCMK(utf8CollatedDbConnectionString, cmkJks, Constants.JAVA_KEY_STORE_NAME, javaKeyAliases, + TestUtils.byteToHexDisplayString(jksProvider.signColumnMasterKeyMetadata(javaKeyAliases, true))); + createCEK(utf8CollatedDbConnectionString, cmkJks, cekJks, jksProvider); + } + } + + @AfterAll + public static void cleanup() throws SQLException { + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { + //dropDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + } + } + /* * Test getting/setting JKS name */ @@ -356,6 +383,137 @@ public void testAkvDecryptColumnEncryptionKey(String serverName, String url, Str } } + @ParameterizedTest + @MethodSource("enclaveParams") + public void testCharErrorNonUnicodeColumnAndSSPAUIsTrue(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { + + String[] values = createCharValuesNonUnicode(nullable); + TestUtils.dropTableIfExists(CHAR_TABLE_AE_NON_UNICODE, stmt); + createTable(CHAR_TABLE_AE_NON_UNICODE, cekJks, charTableNonUnicode); + + // Insert unicode strings into non-unicode column - should fail as UTF8 collation is not used + populateCharNormalCaseNonUnicode(AETestConnectionString, values, true); + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_possibleColumnDataCorruption"), e.getMessage()); + } + } + + @Tag(Constants.xSQLv14) + @ParameterizedTest + @MethodSource("enclaveParams") + @Tag(Constants.reqExternalSetup) + public void testCharErrorSetObjectNonUnicodeColumnAndSSPAUIsTrue(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { + + String[] values = createCharValuesNonUnicode(nullable); + TestUtils.dropTableIfExists(CHAR_TABLE_AE_NON_UNICODE, stmt); + createTable(CHAR_TABLE_AE_NON_UNICODE, cekJks, charTableNonUnicode); + + // Insert unicode strings into non-unicode column - should fail as UTF8 collation is not used + populateCharSetObjectNonUnicode(AETestConnectionString, values, true); + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_possibleColumnDataCorruption"), e.getMessage()); + } + } + + @Tag(Constants.xSQLv14) + @ParameterizedTest + @MethodSource("enclaveParams") + public void testCharNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); + try (SQLServerConnection con = PrepUtil.getConnection(connectionString, AEInfo); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { + + String[] values = createCharValuesNonUnicode(nullable); + TestUtils.dropTableIfExists(CHAR_TABLE_AE_NON_UNICODE, stmt); + createTable(CHAR_TABLE_AE_NON_UNICODE, cekJks, charTableUTF8Collate, stmt); + + // Insert unicode strings into non-unicode column using setString() - should succeed as UTF8 collation is used + // and server is >= version 15 + populateCharNormalCaseNonUnicode(connectionString, values, true); + + try (SQLServerStatement statement = (SQLServerStatement) con.createStatement()) { + ResultSet rs = statement.executeQuery("SELECT * FROM " + CHAR_TABLE_AE_NON_UNICODE); + rs.next(); + + for (int i = 1; i <= 3; i++) { + assertEquals(values[0], rs.getString(i)); + } + + // varchar + for (int i = 4; i <= 6; i++) { + assertEquals(values[1], rs.getString(i)); + } + + // varchar(max) + for (int i = 7; i <= 9; i++) { + assertEquals(values[2], rs.getString(i)); + } + + // varchar8000 + for (int i = 10; i <= 12; i++) { + assertEquals(values[3], rs.getString(i)); + } + } + } + } + + @Tag(Constants.xSQLv14) + @ParameterizedTest + @MethodSource("enclaveParams") + @Tag(Constants.reqExternalSetup) + public void testCharSetObjectNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); + try (SQLServerConnection con = PrepUtil.getConnection(connectionString, AEInfo); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { + + String[] values = createCharValuesNonUnicode(nullable); + TestUtils.dropTableIfExists(CHAR_TABLE_AE_NON_UNICODE, stmt); + createTable(CHAR_TABLE_AE_NON_UNICODE, cekJks, charTableUTF8Collate, stmt); + + // Insert unicode strings into non-unicode column using setObject() - should succeed as UTF8 collation is used + // and server is >= version 15 + populateCharSetObjectNonUnicode(connectionString, values, true); + + try (SQLServerStatement statement = (SQLServerStatement) con.createStatement()) { + ResultSet rs = statement.executeQuery("SELECT * FROM " + CHAR_TABLE_AE_NON_UNICODE); + rs.next(); + + for (int i = 1; i <= 3; i++) { + assertEquals(values[0], rs.getString(i)); + } + + // varchar + for (int i = 4; i <= 6; i++) { + assertEquals(values[1], rs.getString(i)); + } + + // varchar(max) + for (int i = 7; i <= 9; i++) { + assertEquals(values[2], rs.getString(i)); + } + + // varchar8000 + for (int i = 10; i <= 12; i++) { + assertEquals(values[3], rs.getString(i)); + } + } + } + } + /** * Junit test case for char set string for string values * @@ -2115,7 +2273,7 @@ void testChars(SQLServerStatement stmt, String cekName, String[][] table, String switch (testCase) { case NORMAL: - populateCharNormalCase(values); + populateCharNormalCase(values, false); break; case SETOBJECT: populateCharSetObject(values); 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 9aeb0da8f..df727ca72 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java @@ -419,7 +419,7 @@ private void testCharAkv(String connStr) throws SQLException { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); String[] values = createCharValues(false); - populateCharNormalCase(values); + populateCharNormalCase(values, false); try (ResultSet rs = (stmt == null) ? pstmt.executeQuery() : stmt.executeQuery(sql)) { int numberOfColumns = rs.getMetaData().getColumnCount(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java index cef219d4a..4a1db9fe0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java @@ -119,9 +119,9 @@ public void testParameterMetaDataCacheTrim() throws Exception { createTable(CHAR_TABLE_AE, cekAkv, charTable); createTable(NUMERIC_TABLE_AE, cekAkv, numericTable); - populateCharNormalCase(charValues); + populateCharNormalCase(charValues, false); populateNumeric(numericValues); - populateCharNormalCase(charValues); + populateCharNormalCase(charValues, false); } } @@ -144,17 +144,17 @@ public void testRetryWithSecureCache() throws Exception { String[] values = createCharValues(false); TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); - populateCharNormalCase(values); + populateCharNormalCase(values, false); if (TestUtils.doesServerSupportEnclaveRetry(con)) { testAlterColumnEncryption((SQLServerStatement) stmt, CHAR_TABLE_AE, charTable, cekAkv); } - populateCharNormalCase(values); + populateCharNormalCase(values, false); } } private long timedCharUpdate(String[] values) throws SQLException { long timer = System.currentTimeMillis(); - populateCharNormalCase(values); + populateCharNormalCase(values, false); return System.currentTimeMillis() - timer; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index e14bb671a..b21e983c2 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -71,6 +71,7 @@ protected Object[][] getContents() { {"R_notValidParameterForProcedure", "{0} is not a parameter for procedure {1}."}, {"R_unexpectedExceptionContent", "Unexpected content in exception message"}, {"R_connectionClosed", "The connection has been closed"}, + {"R_possibleColumnDataCorruption", "Attempted to insert encrypted unicode data into non-unicode column. Data corruption may occur."}, {"R_conversionFailed", "Conversion failed when converting {0} to {1} data type"}, {"R_invalidQueryTimeout", "The query timeout value {0} is not valid."}, {"R_skipAzure", "Skipping test case on Azure SQL."}, From 105b7a06669dd0dbefa5b3c62fdb79cc22464217 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Mon, 8 Jan 2024 11:00:23 -0800 Subject: [PATCH 06/11] Updated test testAddbatch2AEOnConnection --- .../jdbc/preparedStatement/BatchExecutionWithNullTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java index 672708de3..d4f9413f4 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java @@ -98,7 +98,9 @@ public void testAddBatch2(Connection conn) throws SQLException { @Tag(Constants.xSQLv11) @Tag(Constants.xSQLv12) public void testAddbatch2AEOnConnection() throws SQLException { - try (Connection connection = PrepUtil.getConnection(connectionString + ";columnEncryptionSetting=Enabled;")) { + String cs = TestUtils.addOrOverrideProperty(connectionString, "columnEncryptionSetting", "Enabled"); + cs = TestUtils.addOrOverrideProperty(cs, "sendStringParametersAsUnicode", "false"); + try (Connection connection = PrepUtil.getConnection(cs)) { testAddBatch2(connection); } } From 663846dd14207bf37f0ae7f3b0f96903c8cad8f4 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Wed, 10 Jan 2024 13:44:51 -0800 Subject: [PATCH 07/11] Additional test updates for SSPAU=false --- .../sqlserver/jdbc/AlwaysEncrypted/AESetup.java | 4 ++-- .../jdbc/AlwaysEncrypted/CallableStatementTest.java | 8 ++++---- .../sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java | 12 ++++++------ .../jdbc/AlwaysEncrypted/MultiUserAKVTest.java | 2 +- .../RegressionAlwaysEncryptedTest.java | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) 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 e42767bda..5decbcabe 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -1082,7 +1082,7 @@ protected static void populateCharSetObjectWithJDBCTypes(String[] charValues) th String sql = "insert into " + CHAR_TABLE_AE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?" + ")"; - try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, stmtColEncSetting)) { @@ -1144,7 +1144,7 @@ protected static void populateCharNullCase() throws SQLException { String sql = "insert into " + CHAR_TABLE_AE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?" + ")"; - try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, stmtColEncSetting)) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java index cb1c7c1f3..0e5830ccc 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -540,7 +540,7 @@ private void createMultiInsertionSelection() throws SQLException { + TestUtils.escapeSingleQuotes(multiStatementsProcedure) + "') and OBJECTPROPERTY(id, N'IsProcedure') = 1)" + " DROP PROCEDURE " + multiStatementsProcedure; - try (Connection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (Connection con = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { stmt.execute(sql); @@ -556,7 +556,7 @@ private void createMultiInsertionSelection() throws SQLException { private void MultiInsertionSelection() throws SQLException { String sql = "{call " + multiStatementsProcedure + " (?,?,?,?,?,?)}"; - try (Connection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (Connection con = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerCallableStatement callableStatement = (SQLServerCallableStatement) TestUtils .getCallableStmt(con, sql, stmtColEncSetting)) { @@ -673,7 +673,7 @@ private void createInputProcedure2() throws SQLException { + TestUtils.escapeSingleQuotes(inputProcedure2) + "') and OBJECTPROPERTY(id, N'IsProcedure') = 1)" + " DROP PROCEDURE " + inputProcedure2; - try (Connection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (Connection con = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { stmt.execute(sql); @@ -690,7 +690,7 @@ private void createInputProcedure2() throws SQLException { private void testInputProcedure2(String sql) throws SQLException { - try (Connection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (Connection con = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerCallableStatement callableStatement = (SQLServerCallableStatement) TestUtils .getCallableStmt(con, sql, stmtColEncSetting)) { 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 fe47cd5be..f6a61a3e3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java @@ -210,7 +210,7 @@ public void testVerifyBadJksSignature(String serverName, String url, String prot String badTable = TestUtils.escapeSingleQuotes( AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadJksSignature"))); - try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); Statement s = c.createStatement()) { createCMK(AETestConnectionString, badCmk, Constants.JAVA_KEY_STORE_NAME, javaKeyAliases, "0x666"); createCEK(AETestConnectionString, badCmk, badCek, jksProvider); @@ -256,7 +256,7 @@ public void testVerifyBadAkvSignature(String serverName, String url, String prot String badTable = TestUtils.escapeSingleQuotes( AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadAkvSignature"))); - try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); Statement s = c.createStatement()) { createCMK(AETestConnectionString, badCmk, Constants.AZURE_KEY_VAULT_NAME, keyIDs[0], "0x666"); createCEK(AETestConnectionString, badCmk, badCek, akvProvider); @@ -301,7 +301,7 @@ public void testVerifyBadWinSignature(String serverName, String url, String prot String badTable = TestUtils.escapeSingleQuotes( AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadWinSignature"))); - try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); Statement s = c.createStatement()) { // create CMK with a bad signature createCMK(AETestConnectionString, badCmk, Constants.WINDOWS_KEY_STORE_NAME, windowsKeyPath, "0x666"); @@ -397,7 +397,7 @@ public void testAEFMTOnly(String serverName, String url, String protocol) throws @MethodSource("enclaveParams") public void testAlter(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); Statement s = c.createStatement()) { createTable(CHAR_TABLE_AE, cekJks, varcharTableSimple); PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + CHAR_TABLE_AE + " VALUES (?,?,?)"); @@ -445,7 +445,7 @@ public void testNumericRichQuery(String serverName, String url, String protocol) @MethodSource("enclaveParams") public void testStringRichQuery(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); Statement s = c.createStatement()) { createTable(CHAR_TABLE_AE, cekJks, varcharTableSimple); @@ -473,7 +473,7 @@ public void testStringRichQuery(String serverName, String url, String protocol) @MethodSource("enclaveParams") public void testAlterNoEncrypt(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); Statement s = c.createStatement()) { createTable(CHAR_TABLE_AE, cekJks, varcharTableSimple); PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + CHAR_TABLE_AE + " VALUES (?,?,?)"); 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 2619a79b5..5bd65c26a 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java @@ -526,7 +526,7 @@ public void testStatementCustomKeyStoreProviderDuringAeQuery() throws Exception private void insertData(String tableName, int customId, String customName) { String sqlQuery = "INSERT INTO " + tableName + " VALUES ( ?, ? )"; - try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sqlQuery, SQLServerStatementColumnEncryptionSetting.ENABLED)) { pstmt.setInt(1, customId); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java index d8725cdbe..22ab28627 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java @@ -75,7 +75,7 @@ public void alwaysEncrypted1(String serverName, String url, String protocol) thr public void alwaysEncrypted2(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); try (Connection connection = PrepUtil - .getConnection(AETestConnectionString + ";columnEncryptionSetting=enabled;", AEInfo); + .getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;columnEncryptionSetting=enabled;", AEInfo); Statement stmt = connection.createStatement()) { dropTables(stmt); From 90578df2680a1d7aa1793c60d601e58dd71b77fe Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Wed, 10 Jan 2024 14:28:08 -0800 Subject: [PATCH 08/11] AE null dtv jdbctypesetbyuser fix --- .../java/com/microsoft/sqlserver/jdbc/Parameter.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index eb15acbf9..7ea8bff73 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -168,14 +168,13 @@ void registerForOutput(JDBCType jdbcType, SQLServerConnection con) throws SQLSer // to the server as Unicode rather than MBCS. This is accomplished here by re-tagging // the value with the appropriate corresponding Unicode type. if (con.sendStringParametersAsUnicode()) { - - if (shouldHonorAEForParameter) { - setJdbcTypeSetByUser(jdbcType); - } - jdbcType = getSSPAUJDBCType(jdbcType); } + if (shouldHonorAEForParameter) { + setJdbcTypeSetByUser(jdbcType); + } + registeredOutDTV = new DTV(); registeredOutDTV.setJdbcType(jdbcType); From f7fdc56481e794450da01588bda71283d07191ff Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Wed, 10 Jan 2024 15:13:06 -0800 Subject: [PATCH 09/11] Review changes --- .../sqlserver/jdbc/AlwaysEncrypted/AESetup.java | 4 ++-- .../jdbc/AlwaysEncrypted/CallableStatementTest.java | 2 +- .../sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java | 6 +++--- .../AlwaysEncrypted/JDBCEncryptionDecryptionTest.java | 2 +- .../sqlserver/jdbc/AlwaysEncrypted/MSITest.java | 2 +- .../AlwaysEncrypted/ParameterMetaDataCacheTest.java | 10 +++++----- 6 files changed, 13 insertions(+), 13 deletions(-) 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 5decbcabe..d3dd8a8c7 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -870,11 +870,11 @@ protected static void populateBinaryNullCase() throws SQLException { * @param charValues * @throws SQLException */ - protected static void populateCharNormalCase(String[] charValues, boolean sendStringParametersAsUnicode) throws SQLException { + protected static void populateCharNormalCase(String[] charValues) throws SQLException { String sql = "insert into " + CHAR_TABLE_AE + " values( " + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?," + "?,?,?" + ")"; - String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "sendStringParametersAsUnicode", Boolean.toString(sendStringParametersAsUnicode)); + String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "sendStringParametersAsUnicode", Boolean.toString(false)); try (SQLServerConnection con = (SQLServerConnection) PrepUtil.getConnection(connectionString, AEInfo); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, stmtColEncSetting)) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java index 0e5830ccc..793d30114 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -149,7 +149,7 @@ public static void initValues() throws Exception { createTable(BINARY_TABLE_AE, cekJks, binaryTable); createDateTableCallableStatement(cekJks); - populateCharNormalCase(charValues, false); + populateCharNormalCase(charValues); populateNumericSetObject(numericValues); populateBinaryNormalCase(byteValues); populateDateNormalCase(); 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 f6a61a3e3..d805e8490 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java @@ -187,7 +187,7 @@ public void testAEv2Disabled(String serverName, String url, String protocol) thr String[] values = createCharValues(false); TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekJks, charTable); - populateCharNormalCase(values, false); + populateCharNormalCase(values); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekJks); fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (Throwable e) { @@ -346,7 +346,7 @@ public void testChar(String serverName, String url, String protocol) throws Exce SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekJks, charTable); - populateCharNormalCase(createCharValues(false), false); + populateCharNormalCase(createCharValues(false)); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekJks); } } @@ -363,7 +363,7 @@ public void testCharAkv(String serverName, String url, String protocol) throws E SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); - populateCharNormalCase(createCharValues(false), false); + populateCharNormalCase(createCharValues(false)); testAlterColumnEncryption(stmt, CHAR_TABLE_AE, charTable, cekAkv); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index 68ac44e45..d60159471 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -2273,7 +2273,7 @@ void testChars(SQLServerStatement stmt, String cekName, String[][] table, String switch (testCase) { case NORMAL: - populateCharNormalCase(values, false); + populateCharNormalCase(values); break; case SETOBJECT: populateCharSetObject(values); 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 df727ca72..9aeb0da8f 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java @@ -419,7 +419,7 @@ private void testCharAkv(String connStr) throws SQLException { TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); String[] values = createCharValues(false); - populateCharNormalCase(values, false); + populateCharNormalCase(values); try (ResultSet rs = (stmt == null) ? pstmt.executeQuery() : stmt.executeQuery(sql)) { int numberOfColumns = rs.getMetaData().getColumnCount(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java index 4a1db9fe0..cef219d4a 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java @@ -119,9 +119,9 @@ public void testParameterMetaDataCacheTrim() throws Exception { createTable(CHAR_TABLE_AE, cekAkv, charTable); createTable(NUMERIC_TABLE_AE, cekAkv, numericTable); - populateCharNormalCase(charValues, false); + populateCharNormalCase(charValues); populateNumeric(numericValues); - populateCharNormalCase(charValues, false); + populateCharNormalCase(charValues); } } @@ -144,17 +144,17 @@ public void testRetryWithSecureCache() throws Exception { String[] values = createCharValues(false); TestUtils.dropTableIfExists(CHAR_TABLE_AE, stmt); createTable(CHAR_TABLE_AE, cekAkv, charTable); - populateCharNormalCase(values, false); + populateCharNormalCase(values); if (TestUtils.doesServerSupportEnclaveRetry(con)) { testAlterColumnEncryption((SQLServerStatement) stmt, CHAR_TABLE_AE, charTable, cekAkv); } - populateCharNormalCase(values, false); + populateCharNormalCase(values); } } private long timedCharUpdate(String[] values) throws SQLException { long timer = System.currentTimeMillis(); - populateCharNormalCase(values, false); + populateCharNormalCase(values); return System.currentTimeMillis() - timer; } From 7a1f6047ec3d6d7ade50db9cf02ca39fbe6a92a0 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Sun, 14 Jan 2024 18:29:36 -0800 Subject: [PATCH 10/11] Create login creds for new tests --- .../jdbc/AlwaysEncrypted/AESetup.java | 30 +++++++++++++++++-- .../JDBCEncryptionDecryptionTest.java | 19 ++++++++---- 2 files changed, 42 insertions(+), 7 deletions(-) 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 d3dd8a8c7..758fa2d29 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -431,7 +431,7 @@ protected static void createScaleTable(String tableName, String table[][], Strin } } - protected static void createDatabaseWithUtf8Collation(Connection conn, String dbName) throws SQLException { + protected static void createDatabaseWithUtf8Collation(Connection conn, String dbName) throws SQLException { try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) { String dropDB = "IF EXISTS (SELECT name FROM sys.databases WHERE name = N'"+ dbName + "') DROP DATABASE " + dbName + ";"; String createDB = "CREATE DATABASE " + dbName + " COLLATE Latin1_General_100_CS_AS_WS_SC_UTF8"; @@ -440,13 +440,39 @@ protected static void createDatabaseWithUtf8Collation(Connection conn, String d } } - protected static void dropDatabaseWithUtf8Collation(Connection conn, String dbName) throws SQLException { + protected static void dropDatabaseWithUtf8Collation(Connection conn, String dbName) throws SQLException { try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) { String dropDB = "IF EXISTS (SELECT name FROM sys.databases WHERE name = N'"+ dbName + "') DROP DATABASE " + dbName + ";"; stmt.execute(dropDB); } } + protected static void createUtf8CollationDbCredentials(Connection conn, String dbName, String login, String user, String password) throws SQLException { + String dropLogin = "IF EXISTS (select * from sys.sql_logins where name = '" + login + "') DROP LOGIN " + login; + String dropUser = "IF EXISTS (select * from sys.sysusers where name = '" + user + "') DROP USER " + user; + String createLogin = "CREATE LOGIN " + login + " WITH PASSWORD=N'" + password + "'"; + String createUser = "USE " + dbName + ";CREATE USER " + user + " FOR LOGIN " + login; + String grantRole = "USE " + dbName + ";ALTER ROLE db_owner ADD MEMBER " + user; + String grantAlterServerState = "USE MASTER;GRANT ALTER SERVER STATE TO " + login; + try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) { + stmt.execute(dropLogin); + stmt.execute(dropUser); + stmt.execute(createLogin); + stmt.execute(createUser); + stmt.execute(grantRole); + stmt.execute(grantAlterServerState); + } + } + + protected static void dropUtf8CollationDbCredentials(Connection conn, String login, String user) throws SQLException { + String dropLogin = "IF EXISTS (select * from sys.sql_logins where name = '" + login + "') DROP LOGIN " + login; + String dropUser = "IF EXISTS (select * from sys.sysusers where name = '" + user + "') DROP USER " + user; + try (SQLServerStatement stmt = (SQLServerStatement) conn.createStatement()) { + stmt.execute(dropLogin); + stmt.execute(dropUser); + } + } + /** * Create a list of binary values * diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index d60159471..3f6446c56 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -74,6 +74,9 @@ public class JDBCEncryptionDecryptionTest extends AESetup { private boolean nullable = false; private static final String UTF8_COLLATE_DB = "JDBC_UTF8_COLLATE_DB_" + UUID.randomUUID().toString().replace("-", ""); + private static final String UTF8_COLLATE_LOGIN = "UTF8_LOGIN_" + UUID.randomUUID().toString().replace("-", ""); + private static final String UTF8_COLLATE_USER = "UTF8_USER_" + UUID.randomUUID().toString().replace("-", ""); + private static final String UTF8_COLLATE_PWD = UUID.randomUUID().toString(); enum TestCase { NORMAL, @@ -89,6 +92,7 @@ public static void init() throws SQLException { try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { dropDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); createDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + createUtf8CollationDbCredentials(con, UTF8_COLLATE_DB, UTF8_COLLATE_LOGIN, UTF8_COLLATE_USER, UTF8_COLLATE_PWD); String utf8CollatedDbConnectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); createCMK(utf8CollatedDbConnectionString, cmkJks, Constants.JAVA_KEY_STORE_NAME, javaKeyAliases, @@ -100,7 +104,8 @@ public static void init() throws SQLException { @AfterAll public static void cleanup() throws SQLException { try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { - //dropDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + dropDatabaseWithUtf8Collation(con, UTF8_COLLATE_DB); + dropUtf8CollationDbCredentials(con, UTF8_COLLATE_LOGIN, UTF8_COLLATE_USER); } } @@ -430,8 +435,10 @@ public void testCharErrorSetObjectNonUnicodeColumnAndSSPAUIsTrue(String serverNa @MethodSource("enclaveParams") public void testCharNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); + connectionString = TestUtils.addOrOverrideProperty(connectionString, "user", UTF8_COLLATE_LOGIN); + connectionString = TestUtils.addOrOverrideProperty(connectionString, "password", UTF8_COLLATE_PWD); + try (SQLServerConnection con = PrepUtil.getConnection(connectionString, AEInfo); SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { @@ -461,7 +468,7 @@ public void testCharNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serverName, St assertEquals(values[2], rs.getString(i)); } - // varchar8000 + // varchar(8000) for (int i = 10; i <= 12; i++) { assertEquals(values[3], rs.getString(i)); } @@ -475,8 +482,10 @@ public void testCharNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serverName, St @Tag(Constants.reqExternalSetup) public void testCharSetObjectNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - String connectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, "database", UTF8_COLLATE_DB); + connectionString = TestUtils.addOrOverrideProperty(connectionString, "user", UTF8_COLLATE_LOGIN); + connectionString = TestUtils.addOrOverrideProperty(connectionString, "password", UTF8_COLLATE_PWD); + try (SQLServerConnection con = PrepUtil.getConnection(connectionString, AEInfo); SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) { @@ -506,7 +515,7 @@ public void testCharSetObjectNonUnicodeColumnSSPAUIsTrueUTF8Collate(String serve assertEquals(values[2], rs.getString(i)); } - // varchar8000 + // varchar(8000) for (int i = 10; i <= 12; i++) { assertEquals(values[3], rs.getString(i)); } From 98ec0d3db01ab7333cf8ad46a119f207015f5a16 Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Sun, 14 Jan 2024 18:40:06 -0800 Subject: [PATCH 11/11] Additional sspau test update --- .../sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java index 793d30114..5ecf90c95 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -1496,7 +1496,7 @@ private void createOutputProcedureChar() throws SQLException { private void testOutputProcedureCharInorder(String sql) throws SQLException { - try (Connection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (Connection con = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerCallableStatement callableStatement = (SQLServerCallableStatement) TestUtils .getCallableStmt(con, sql, stmtColEncSetting)) { @@ -1546,7 +1546,7 @@ private void testOutputProcedureCharInorder(String sql) throws SQLException { private void testOutputProcedureCharInorderObject(String sql) throws SQLException { - try (Connection con = PrepUtil.getConnection(AETestConnectionString, AEInfo); + try (Connection con = PrepUtil.getConnection(AETestConnectionString + ";sendStringParametersAsUnicode=false;", AEInfo); SQLServerCallableStatement callableStatement = (SQLServerCallableStatement) TestUtils .getCallableStmt(con, sql, stmtColEncSetting)) {