From d76d18220a3fc26bff38f7d305b9dcc07c8e2624 Mon Sep 17 00:00:00 2001 From: Terry Chow <32403408+tkyc@users.noreply.github.com> Date: Thu, 17 Aug 2023 13:17:45 -0700 Subject: [PATCH] Fix bug where signature is not verified for Java Key Store and add signColumnMasterkeyMetadata API to JKS and AKV (#2160) (#2199) Co-authored-by: lilgreenbird --- ...ColumnEncryptionAzureKeyVaultProvider.java | 57 ++++++- ...umnEncryptionCertificateStoreProvider.java | 26 ++-- ...rColumnEncryptionJavaKeyStoreProvider.java | 122 ++++++++++----- .../sqlserver/jdbc/SQLServerResource.java | 6 +- .../jdbc/SQLServerSecurityUtility.java | 2 +- .../jdbc/AlwaysEncrypted/AESetup.java | 8 +- .../jdbc/AlwaysEncrypted/EnclaveTest.java | 143 ++++++++++++++++++ .../AlwaysEncrypted/MultiUserAKVTest.java | 4 +- .../sqlserver/testframework/AbstractTest.java | 2 +- .../sqlserver/testframework/Constants.java | 5 +- .../testframework/DummyKeyStoreProvider.java | 12 +- 11 files changed, 317 insertions(+), 70 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java index 73f37f0bb..89a433f08 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java @@ -429,7 +429,7 @@ public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryption // Validate the signature if (!azureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath)) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CEKSignatureNotMatchCMK")); - Object[] msgArgs = {masterKeyPath}; + Object[] msgArgs = {Util.byteToHexDisplayString(signature), masterKeyPath}; throw new SQLServerException(this, form.format(msgArgs), null, 0, false); } @@ -876,6 +876,9 @@ public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allow return cmkMetadataSignatureVerificationCache.get(key); } + byte[] signedHash = null; + boolean isValid = false; + try { MessageDigest md = MessageDigest.getInstance(SHA_256); md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); @@ -889,19 +892,65 @@ public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allow } // Sign the hash - byte[] signedHash = azureKeyVaultSignHashedData(dataToVerify, masterKeyPath); + signedHash = azureKeyVaultSignHashedData(dataToVerify, masterKeyPath); if (null == signedHash) { throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null); } // Validate the signature - boolean isValid = azureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath); + isValid = azureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath); cmkMetadataSignatureVerificationCache.put(key, isValid); + } catch (NoSuchAlgorithmException e) { + throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); + } catch (SQLServerException e) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_SignatureNotMatch")); + Object[] msgArgs = {Util.byteToHexDisplayString(signature), + (signedHash != null) ? Util.byteToHexDisplayString(signedHash) : " ", masterKeyPath, + ": " + e.getMessage()}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } + + if (!isValid) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_SignatureNotMatch")); + Object[] msgArgs = {Util.byteToHexDisplayString(signature), Util.byteToHexDisplayString(signedHash), + masterKeyPath, ""}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } + return isValid; + } + + public byte[] signColumnMasterKeyMetadata(String masterKeyPath, + boolean allowEnclaveComputations) throws SQLServerException { + if (!allowEnclaveComputations) { + return null; + } + + KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath); + + byte[] signedHash = null; + try { + MessageDigest md = MessageDigest.getInstance(SHA_256); + md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + // value of allowEnclaveComputations is always true here + md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + + byte[] dataToVerify = md.digest(); + if (null == dataToVerify) { + throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null); + } - return isValid; + // Sign the hash + signedHash = azureKeyVaultSignHashedData(dataToVerify, masterKeyPath); } catch (NoSuchAlgorithmException e) { throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); } + + if (null == signedHash) { + throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null); + } + + return signedHash; } private static List getTrustedEndpoints() { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java index 2b822fbf4..46a3fa051 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java @@ -49,23 +49,15 @@ public String getName() { return this.name; } + @Override public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, byte[] plainTextColumnEncryptionKey) throws SQLServerException { + // not supported throw new SQLServerException(null, SQLServerException.getErrString("R_InvalidWindowsCertificateStoreEncryption"), null, 0, false); } - private byte[] decryptColumnEncryptionKeyWindows(String masterKeyPath, String encryptionAlgorithm, - byte[] encryptedColumnEncryptionKey) throws SQLServerException { - try { - return AuthenticationJNI.DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, - encryptedColumnEncryptionKey); - } catch (DLLException e) { - DLLException.buildException(e.getErrCode(), e.getParam1(), e.getParam2(), e.getParam3()); - return null; - } - } - + @Override public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) throws SQLServerException { windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(), @@ -92,4 +84,16 @@ public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allow return false; } } + + private byte[] decryptColumnEncryptionKeyWindows(String masterKeyPath, String encryptionAlgorithm, + byte[] encryptedColumnEncryptionKey) throws SQLServerException { + try { + return AuthenticationJNI.DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, + encryptedColumnEncryptionKey); + } catch (DLLException e) { + DLLException.buildException(e.getErrCode(), e.getParam1(), e.getParam2(), e.getParam3()); + return null; + } + } + } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java index 5fbc111a3..319b2b268 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java @@ -113,6 +113,83 @@ public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryption return plainCEK; } + @Override + public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations, + byte[] signature) throws SQLServerException { + + if (!allowEnclaveComputations) { + return false; + } + + KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath); + CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath); + + byte[] signedHash = null; + boolean isValid = false; + + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + // value of allowEnclaveComputations is always true here + md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + + byte[] dataToVerify = md.digest(); + Signature sig = Signature.getInstance("SHA256withRSA"); + + sig.initSign((PrivateKey) certificateDetails.privateKey); + sig.update(dataToVerify); + + signedHash = sig.sign(); + + sig.initVerify(certificateDetails.certificate.getPublicKey()); + sig.update(dataToVerify); + isValid = sig.verify(signature); + } catch (NoSuchAlgorithmException e) { + throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); + } catch (InvalidKeyException | SignatureException e) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_SignatureNotMatch")); + Object[] msgArgs = {Util.byteToHexDisplayString(signature), + (signedHash != null) ? Util.byteToHexDisplayString(signedHash) : " ", masterKeyPath, + ": " + e.getMessage()}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } + + if (!isValid) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_SignatureNotMatch")); + Object[] msgArgs = {Util.byteToHexDisplayString(signature), Util.byteToHexDisplayString(signedHash), + masterKeyPath, ""}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } + return isValid; + } + + public byte[] signColumnMasterKeyMetadata(String masterKeyPath, + boolean allowEnclaveComputations) throws SQLServerException { + if (!allowEnclaveComputations) + return null; + + KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath); + CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath); + + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + // value of allowEnclaveComputations is always true here + md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initSign((PrivateKey) certificateDetails.privateKey); + sig.update(md.digest()); + + return sig.sign(); + + } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) { + throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); + } + } + private CertificateDetails getCertificateDetails(String masterKeyPath) throws SQLServerException { FileInputStream fis = null; KeyStore keyStore = null; @@ -319,45 +396,16 @@ private byte[] getLittleEndianBytesFromShort(short value) { * Verify signature against certificate */ private boolean rsaVerifySignature(byte[] dataToVerify, byte[] signature, - CertificateDetails certificateDetails) throws SQLServerException { - try { - Signature sig = Signature.getInstance("SHA256withRSA"); - sig.initSign((PrivateKey) certificateDetails.privateKey); - sig.update(dataToVerify); - byte[] signedHash = sig.sign(); + CertificateDetails certificateDetails) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { + Signature sig = Signature.getInstance("SHA256withRSA"); - sig.initVerify(certificateDetails.certificate.getPublicKey()); - sig.update(dataToVerify); + sig.initSign((PrivateKey) certificateDetails.privateKey); + sig.update(dataToVerify); - return sig.verify(signedHash); + byte[] signedHash = sig.sign(); - } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_VerifySignatureFailed")); - Object[] msgArgs = {e.getMessage()}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); - } + sig.initVerify(certificateDetails.certificate.getPublicKey()); + sig.update(dataToVerify); + return sig.verify(signature); } - - @Override - public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations, - byte[] signature) throws SQLServerException { - - if (!allowEnclaveComputations) - return false; - - KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath); - CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath); - - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); - md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); - // value of allowEnclaveComputations is always true here - md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); - return rsaVerifySignature(md.digest(), signature, certificateDetails); - } catch (NoSuchAlgorithmException e) { - throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); - } - } - } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 0cb09a25f..44db5331d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -382,8 +382,9 @@ protected Object[][] getContents() { {"R_AKVSignatureLengthError", "The specified encrypted column encryption key''s signature length: {0} does not match the signature length: {1} when using column master key (Azure Key Vault key) in {2}. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect."}, {"R_HashNull", "Hash should not be null while decrypting encrypted column encryption key."}, {"R_NoSHA256Algorithm", "SHA-256 Algorithm is not supported."}, - {"R_VerifySignature", "Unable to verify signature of the column encryption key."}, - {"R_CEKSignatureNotMatchCMK", "The specified encrypted column encryption key signature does not match the signature computed with the column master key (Asymmetric key in Azure Key Vault) in {0}. The encrypted column encryption key may be corrupt, or the specified path may be incorrect."}, + {"R_VerifySignatureFailed", "Unable to verify the signature of the column encryption key."}, + {"R_SignatureNotMatch", "The specified encrypted column encryption key signature \"{0}\" does not match the signature \"{1}\" computed with the column master key (certificate) in \"{2}\". The encrypted column encryption key may be corrupt, or the specified path may be incorrect. {3}"}, + {"R_CEKSignatureNotMatchCMK", "The specified encrypted column encryption key signature \"{0}\" does not match the signature computed with the column master key (Asymmetric key in Azure Key Vault) in {1}. The encrypted column encryption key may be corrupt, or the specified path may be incorrect."}, {"R_DecryptCEKError", "Unable to decrypt column encryption key using specified Azure Key Vault key."}, {"R_EncryptCEKError", "Unable to encrypt column encryption key using specified Azure Key Vault key."}, {"R_CipherTextLengthNotMatchRSASize", "CipherText length does not match the RSA key size."}, @@ -409,6 +410,7 @@ protected Object[][] getContents() { // This is used for connection settings. {0}-> property name as is, {1}-> value {"R_InvalidConnectionSetting", "The {0} value \"{1}\" is not valid."}, {"R_InvalidWindowsCertificateStoreEncryption", "Cannot encrypt a column encryption key with the Windows Certificate Store."}, + {"R_InvalidWindowsCertificateStoreSignCMK", "Cannot sign column master key metadata with the Windows Certificate Store."}, {"R_AEKeypathEmpty", "Internal error. Certificate path cannot be null. Use the following format: \"certificate location/certificate store/certificate thumbprint\", where \"certificate location\" is either LocalMachine or CurrentUser."}, {"R_AEWinApiErr", "Windows Api native error."}, {"R_AECertpathBad", "Internal error. Invalid certificate path: {0}. Use the following format: \"certificate location/certificate store/certificate thumbprint\", where \"certificate location\" is either LocalMachine or CurrentUser."}, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java index 6e24c3584..3bb2eb32d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSecurityUtility.java @@ -315,7 +315,7 @@ static void verifyColumnMasterKeyMetadata(SQLServerConnection connection, SQLSer } if (!provider.verifyColumnMasterKeyMetadata(keyPath, isEnclaveEnabled, cmkSignature)) { - throw new SQLServerException(SQLServerException.getErrString("R_VerifySignature"), null); + throw new SQLServerException(SQLServerException.getErrString("R_VerifySignatureFailed"), null); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java index 96e8ead85..53f3b18e0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -82,6 +82,8 @@ public class AESetup extends AbstractTest { public static final String SCALE_DATE_TABLE_AE = TestUtils.escapeSingleQuotes( AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBCEncryptedScaleDate"))); + final static char[] HEXCHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + enum ColumnType { PLAIN, RANDOMIZED, @@ -220,18 +222,18 @@ public static void setupAETest() throws Exception { setAEConnectionString(serverName, url, protocol); createCMK(AETestConnectionString, cmkJks, Constants.JAVA_KEY_STORE_NAME, javaKeyAliases, - Constants.CMK_SIGNATURE); + TestUtils.byteToHexDisplayString(jksProvider.signColumnMasterKeyMetadata(javaKeyAliases, true))); createCEK(AETestConnectionString, cmkJks, cekJks, jksProvider); if (null != keyIDs && !keyIDs[0].isEmpty()) { createCMK(AETestConnectionString, cmkAkv, Constants.AZURE_KEY_VAULT_NAME, keyIDs[0], - Constants.CMK_SIGNATURE_AKV); + TestUtils.byteToHexDisplayString(akvProvider.signColumnMasterKeyMetadata(keyIDs[0], true))); createCEK(AETestConnectionString, cmkAkv, cekAkv, akvProvider); } if (null != windowsKeyPath) { createCMK(AETestConnectionString, cmkWin, Constants.WINDOWS_KEY_STORE_NAME, windowsKeyPath, - Constants.CMK_SIGNATURE); + Constants.CMK_SIGNATURE_WIN); createCEK(AETestConnectionString, cmkWin, cekWin, null); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java index 9b136afd3..4b55573af 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java @@ -23,10 +23,12 @@ import org.junit.runners.Parameterized; import com.microsoft.sqlserver.jdbc.EnclavePackageTest; +import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerConnection; import com.microsoft.sqlserver.jdbc.SQLServerStatement; import com.microsoft.sqlserver.jdbc.TestResource; import com.microsoft.sqlserver.jdbc.TestUtils; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; import com.microsoft.sqlserver.testframework.Constants; import com.microsoft.sqlserver.testframework.PrepUtil; @@ -194,6 +196,145 @@ public void testAEv2Disabled(String serverName, String url, String protocol) thr } } + /* + * Negative Test - bad JKS signature + */ + @ParameterizedTest + @MethodSource("enclaveParams") + public void testVerifyBadJksSignature(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + // create CMK with a bad signature + String badCmk = Constants.CMK_NAME + "_badCMK"; + String badCek = Constants.CEK_NAME + "_badCek"; + String badTable = TestUtils.escapeSingleQuotes( + AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadJksSignature"))); + + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + Statement s = c.createStatement()) { + createCMK(AETestConnectionString, badCmk, Constants.JAVA_KEY_STORE_NAME, javaKeyAliases, "0x666"); + createCEK(AETestConnectionString, badCmk, badCek, jksProvider); + + createTable(badTable, badCek, varcharTableSimple); + + PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + badTable + " VALUES (?,?,?)"); + pstmt.setString(1, "a"); + pstmt.setString(2, "b"); + pstmt.setString(3, "test"); + pstmt.execute(); + + pstmt = c.prepareStatement("SELECT * FROM " + badTable + " WHERE RANDOMIZEDVarchar LIKE ?"); + pstmt.setString(1, "t%"); + try (ResultSet rs = pstmt.executeQuery()) { + fail(TestResource.getResource("R_expectedFailPassed")); + } + } catch (Exception e) { + assertTrue( + e.getMessage().matches(TestUtils.formatErrorMsg("R_SignatureNotMatch")) + || e.getMessage().matches(TestUtils.formatErrorMsg("R_VerifySignatureFailed")), + e.getMessage()); + } finally { + try (Statement s = connection.createStatement()) { + TestUtils.dropTableIfExists(badTable, s); + dropCEK(badCek, s); + dropCMK(badCmk, s); + } + } + } + + /* + * Negative Test - bad AKS signature + */ + @ParameterizedTest + @MethodSource("enclaveParams") + public void testVerifyBadAkvSignature(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + // create CMK with a bad signature + String badCmk = Constants.CMK_NAME + "_badCMK"; + String badCek = Constants.CEK_NAME + "_badCek"; + String badTable = TestUtils.escapeSingleQuotes( + AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadAkvSignature"))); + + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + Statement s = c.createStatement()) { + createCMK(AETestConnectionString, badCmk, Constants.AZURE_KEY_VAULT_NAME, keyIDs[0], "0x666"); + createCEK(AETestConnectionString, badCmk, badCek, akvProvider); + + createTable(badTable, badCek, varcharTableSimple); + + PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + badTable + " VALUES (?,?,?)"); + pstmt.setString(1, "a"); + pstmt.setString(2, "b"); + pstmt.setString(3, "test"); + pstmt.execute(); + + pstmt = c.prepareStatement("SELECT * FROM " + badTable + " WHERE RANDOMIZEDVarchar LIKE ?"); + pstmt.setString(1, "t%"); + try (ResultSet rs = pstmt.executeQuery()) { + fail(TestResource.getResource("R_expectedFailPassed")); + } + } catch (Exception e) { + assertTrue( + e.getMessage().matches(TestUtils.formatErrorMsg("R_SignatureNotMatch")) + || e.getMessage().matches(TestUtils.formatErrorMsg("R_VerifySignatureFailed")), + e.getMessage()); + } finally { + try (Statement s = connection.createStatement()) { + TestUtils.dropTableIfExists(badTable, s); + dropCEK(badCek, s); + dropCMK(badCmk, s); + } + } + } + + /* + * Negative Test - bad AKS signature + */ + @ParameterizedTest + @MethodSource("enclaveParams") + public void testVerifyBadWinSignature(String serverName, String url, String protocol) throws Exception { + setAEConnectionString(serverName, url, protocol); + + String badCmk = Constants.CMK_NAME + "_badCMK"; + String badCek = Constants.CEK_NAME + "_badCek"; + String badTable = TestUtils.escapeSingleQuotes( + AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("testVerifyBadWinSignature"))); + + try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); + Statement s = c.createStatement()) { + // create CMK with a bad signature + createCMK(AETestConnectionString, badCmk, Constants.WINDOWS_KEY_STORE_NAME, windowsKeyPath, "0x666"); + createCEK(AETestConnectionString, badCmk, badCek, null); + + createTable(badTable, badCek, varcharTableSimple); + + PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + badTable + " VALUES (?,?,?)"); + pstmt.setString(1, "a"); + pstmt.setString(2, "b"); + pstmt.setString(3, "test"); + pstmt.execute(); + + pstmt = c.prepareStatement("SELECT * FROM " + badTable + " WHERE RANDOMIZEDVarchar LIKE ?"); + pstmt.setString(1, "t%"); + try (ResultSet rs = pstmt.executeQuery()) { + fail(TestResource.getResource("R_expectedFailPassed")); + } + } catch (Exception e) { + // windows error message is different + assertTrue( + e.getMessage().contains("signature does not match") + || e.getMessage().matches(TestUtils.formatErrorMsg("R_VerifySignatureFailed")), + e.getMessage()); + } finally { + try (Statement s = connection.createStatement()) { + TestUtils.dropTableIfExists(badTable, s); + dropCEK(badCek, s); + dropCMK(badCmk, s); + } + } + } + /* * Tests alter column encryption on char tables */ @@ -213,6 +354,7 @@ public void testChar(String serverName, String url, String protocol) throws Exce /* * Tests alter column encryption on char tables with AKV */ + @ParameterizedTest @MethodSource("enclaveParams") public void testCharAkv(String serverName, String url, String protocol) throws Exception { @@ -306,6 +448,7 @@ public void testStringRichQuery(String serverName, String url, String protocol) try (SQLServerConnection c = PrepUtil.getConnection(AETestConnectionString, AEInfo); Statement s = c.createStatement()) { createTable(CHAR_TABLE_AE, cekJks, varcharTableSimple); + PreparedStatement pstmt = c.prepareStatement("INSERT INTO " + CHAR_TABLE_AE + " VALUES (?,?,?)"); pstmt.setString(1, "a"); pstmt.setString(2, "b"); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java index 257d8f921..c3332158c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java @@ -354,7 +354,7 @@ public void testConnectionCustomKeyStoreProviderDuringAeQuery() throws Exception // Create cmk and cek for DummyKeyStoreProvider createCMK(AETestConnectionString, cmkDummy, Constants.DUMMY_KEYSTORE_NAME, keyIDs[0], - Constants.CMK_SIGNATURE_AKV); + TestUtils.byteToHexDisplayString(akvProvider.signColumnMasterKeyMetadata(keyIDs[0], true))); createCEK(AETestConnectionString, cmkDummy, cekDummy, akvProvider); // Create an empty table for testing @@ -439,7 +439,7 @@ public void testStatementCustomKeyStoreProviderDuringAeQuery() throws Exception // Create an empty table for testing createCMK(AETestConnectionString, cmkDummy, Constants.DUMMY_KEYSTORE_NAME, keyIDs[0], - Constants.CMK_SIGNATURE_AKV); + TestUtils.byteToHexDisplayString(akvProvider.signColumnMasterKeyMetadata(keyIDs[0], true))); createCEK(AETestConnectionString, cmkDummy, cekDummy, akvProvider); createTableForCustomProvider(AETestConnectionString, customProviderTableName, cekDummy); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index 78e095848..1d9eb2440 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -81,7 +81,7 @@ public abstract class AbstractTest { protected static String windowsKeyPath = null; protected static String javaKeyPath = null; protected static String javaKeyAliases = null; - protected static SQLServerColumnEncryptionKeyStoreProvider jksProvider = null; + protected static SQLServerColumnEncryptionJavaKeyStoreProvider jksProvider = null; protected static SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = null; static boolean isKspRegistered = false; diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java index 5453e9c00..ce50791f1 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java @@ -113,8 +113,9 @@ private Constants() {} public static final String UID = UUID.randomUUID().toString(); public static final Long RANDOM_LONG = Long.valueOf((long) (RANDOM.nextDouble() * Math.pow(10, 10))); public static final String CMK_NAME = "JDBC_CMK_" + RANDOM_LONG; - public static final String CMK_SIGNATURE = "0x5859A75C4F8C05C9865DAE46CA70F34B94EF1DF25D185DE8AB67918E5CF02953F2C787EFDC57ACAD5AEC2634C24B0A4C98B3A659220E980D948F91238A5D98CD898EF76CCA4AED9A6D51EBF4EABDEAA28CF975A7C8083E188AA4677303DAC9D6F49C81CB42E4BF14365B56BFEDD6E4B0DF0BC69E9FE313A7001613822D724A97055FA110C495A98EE37BE137293DF9E569D0845B96C4A6DD8554D42E83E784E3DCAFFDDD2CB3A7352920C3AAC43EA79BE5B8C7203B0E2F9B7C317C542E632C529BC46D792D9C85E16B5B3EE50D6D5E53103CC27B60175F79ECA1F2B28995FD1C12FA109E53810192BDD38A3B9A5D2CCC2BCBF16F936FE211A55FD999689C8CC4"; - public static final String CMK_SIGNATURE_AKV = "0x0474EC516A93B3EBCBD58E3AC35699E125F937AE6A56E96EC4FC37ACD09284CC99702B4762E56E8518A23A4D7EBF62FF735C8EEF4B61326CB8300ED09076D69008ACEB4156D9F2E1F95A8373335C33FCDD7DD7DF69CDD23544AB0D63B6D93379593E15C24D31EC0020F62BA23A19165C8A58AFAE8304DAC2996470919EBAB97587D685AEF4FFA3666E65DA673F41B2204410AFA69B9E05402853AB2AF0D22F4CF498394EB9DA8CA55814601DE6E004B12886C069010F911AE1F0EDA5DA3F1A09A211C7C30D21A567D47A8F133DF20A44E694900344FF3E189A8D6069CB86AA63D168B90CE4150F09A78DC09EF20FC239EA299E964762AE1FF711E407936FA9C9"; + + public static final String CMK_SIGNATURE_WIN = "0x625F3105C77DF6881D11B9D5A64491452F288AA9CE66189F19AF9591FAF2525B90CE580DD29CF897F6809EC30AF583CF1603477C5591D785A89DA3CE60E03D9CC494926322E0EF15A4EA38C0FFA0B90D29D051DA037631E6D3B435F87BAB46927ECF366AE96C5259D03A227A2BC04B15E09794CA1ABD1B34CB117BF06962225D4157DD1FBEF998D547EC7E201C00ACF2D2B8A746D1FD3FAEED791132D97EA396CABE9C472C8B559C5EF35342EE9D5635C7C18C1BD168647E3BA52B674626D9F8C3CFAC150300EDA157DE3D1B1D7FFB946D1CCE37ACFD28F62D57365BA89088627EE9469B04142A5F5505B71C94999A8D4A1269F15842DF3DA3CC5B41DDD7B115"; + public static final String CEK_NAME = "JDBC_CEK_" + RANDOM_LONG; public static final String CEK_ALGORITHM = "RSA_OAEP"; public static final String CEK_STRING = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DummyKeyStoreProvider.java b/src/test/java/com/microsoft/sqlserver/testframework/DummyKeyStoreProvider.java index fbfa3fda8..bca8fa1a1 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DummyKeyStoreProvider.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DummyKeyStoreProvider.java @@ -9,7 +9,7 @@ /** - * DummyKeyStoreProvider Class for Tests. + * DummyKeyStoreProvider Class for Tests. * */ public class DummyKeyStoreProvider extends SQLServerColumnEncryptionKeyStoreProvider { @@ -28,21 +28,19 @@ public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryption byte[] encryptedColumnEncryptionKey) throws SQLServerException { // Not implemented throw new UnsupportedOperationException(); - } + } @Override - public byte[] encryptColumnEncryptionKey(String masterKeyPath, - String encryptionAlgorithm, + public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, byte[] columnEncryptionKey) throws SQLServerException { // Not implemented throw new UnsupportedOperationException(); } @Override - public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, - boolean allowEnclaveComputations, + public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations, byte[] signature) throws SQLServerException { // Not implemented throw new UnsupportedOperationException(); } -} \ No newline at end of file +}