Skip to content

Commit

Permalink
Fix bug where signature is not verified for Java Key Store and add si…
Browse files Browse the repository at this point in the history
…gnColumnMasterkeyMetadata API to JKS and AKV (#2160) (#2199)

Co-authored-by: lilgreenbird <[email protected]>
  • Loading branch information
tkyc and lilgreenbird authored Aug 17, 2023
1 parent 35985ad commit d76d182
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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));
Expand All @@ -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<String> getTrustedEndpoints() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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."},
Expand All @@ -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."},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}
Expand Down
Loading

0 comments on commit d76d182

Please sign in to comment.