From 3af81f3b70a2340d04b02210c7d02e1d7bab8f8e Mon Sep 17 00:00:00 2001 From: Terry Chow Date: Fri, 5 Jan 2024 18:01:46 -0800 Subject: [PATCH] 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."},