com.microsoft.sqlserver.jdbc.ParameterMetaDataCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC Driver for SQL Server.
/*
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
* available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/
package com.microsoft.sqlserver.jdbc;
import java.text.MessageFormat;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Implements a cache for query metadata returned from sp_describe_parameter_encryption calls. Adding, removing, and
* reading from the cache is handled here, with the location of the cache being in the EnclaveSession.
*
*/
class ParameterMetaDataCache {
static final int CACHE_SIZE = 2000; // Size of the cache in number of entries
static final int CACHE_TRIM_THRESHOLD = 300; // Threshold above which to trim the cache
static private java.util.logging.Logger metadataCacheLogger = java.util.logging.Logger
.getLogger("com.microsoft.sqlserver.jdbc.ParameterMetaDataCache");
/**
* Retrieves the metadata from the cache, should it exist.
*
* @param params
* Array of parameters used
* @param parameterNames
* Names of parameters used
* @param session
* The current enclave session containing the cache
* @param connection
* The SQLServer connection
* @param stmt
* The SQLServer statement, whose returned metadata we're checking
* @return true, if the metadata for the query can be retrieved
*
*/
static boolean getQueryMetadata(Parameter[] params, ArrayList parameterNames, CryptoCache cache,
SQLServerConnection connection, SQLServerStatement stmt) throws SQLServerException {
AbstractMap.SimpleEntry encryptionValues = getCacheLookupKeys(stmt, connection);
ConcurrentHashMap metadataMap = cache.getCacheEntry(encryptionValues.getKey());
if (metadataMap == null) {
if (metadataCacheLogger.isLoggable(java.util.logging.Level.FINEST)) {
metadataCacheLogger.finest("Cache Miss. Unable to retrieve cache entry from cache.");
}
return false;
}
for (int i = 0; i < params.length; i++) {
boolean found = metadataMap.containsKey(parameterNames.get(i));
CryptoMetadata foundData = metadataMap.get(parameterNames.get(i));
/*
* If ever the map doesn't contain a parameter, the cache entry cannot be used. If there is data found, it
* should never have the initialized algorithm as that would contain the key. Clear all metadata that has
* already been assigned in either case.
*/
if (!found || (foundData != null && foundData.isAlgorithmInitialized())) {
for (Parameter param : params) {
param.cryptoMeta = null;
}
if (metadataCacheLogger.isLoggable(java.util.logging.Level.FINEST)) {
metadataCacheLogger
.finest("Cache Miss. Cache entry either has missing parameter or initialized algorithm.");
}
return false;
}
params[i].cryptoMeta = foundData;
}
// Assign the key using a metadata copy. We shouldn't load from the cached version for security reasons.
for (int i = 0; i < params.length; ++i) {
try {
CryptoMetadata cryptoCopy = null;
CryptoMetadata metaData = params[i].getCryptoMetadata();
if (metaData != null) {
cryptoCopy = new CryptoMetadata(metaData.getCekTableEntry(), metaData.getOrdinal(),
metaData.getEncryptionAlgorithmId(), metaData.getEncryptionAlgorithmName(),
metaData.getEncryptionType().getValue(), metaData.getNormalizationRuleVersion());
}
params[i].cryptoMeta = cryptoCopy;
if (cryptoCopy != null) {
try {
SQLServerSecurityUtility.decryptSymmetricKey(cryptoCopy, connection, stmt);
} catch (SQLServerException e) {
removeCacheEntry(stmt, cache, connection);
for (Parameter paramToCleanup : params) {
paramToCleanup.cryptoMeta = null;
}
if (metadataCacheLogger.isLoggable(java.util.logging.Level.FINEST)) {
metadataCacheLogger.finest("Cache Miss. Unable to decrypt CEK.");
}
return false;
}
}
} catch (Exception e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CryptoCacheInaccessible"));
Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(form.format(msgArgs), null);
}
}
if (metadataCacheLogger.isLoggable(java.util.logging.Level.FINEST)) {
metadataCacheLogger.finest("Cache Hit. Successfully retrieved metadata from cache.");
}
return true;
}
/**
*
* Adds the parameter metadata to the cache, also handles cache trimming.
*
* @param params
* List of parameters used
* @param parameterNames
* Names of parameters used
* @param session
* Enclave session containing the cryptocache
* @param connection
* SQLServerConnection
* @param stmt
* SQLServer statement used to retrieve keys to find correct cache
* @param cekList
* The list of CEKs (from the first RS) that is also added to the cache as well as parameter metadata
* @return true, if the query metadata has been added correctly
*/
static boolean addQueryMetadata(Parameter[] params, ArrayList parameterNames, CryptoCache cache,
SQLServerConnection connection, SQLServerStatement stmt,
Map cekList) throws SQLServerException {
AbstractMap.SimpleEntry encryptionValues = getCacheLookupKeys(stmt, connection);
if (encryptionValues.getKey() == null) {
return false;
}
ConcurrentHashMap metadataMap = new ConcurrentHashMap<>(params.length);
for (int i = 0; i < params.length; i++) {
try {
CryptoMetadata cryptoCopy = null;
CryptoMetadata metaData = params[i].getCryptoMetadata();
if (metaData != null) {
cryptoCopy = new CryptoMetadata(metaData.getCekTableEntry(), metaData.getOrdinal(),
metaData.getEncryptionAlgorithmId(), metaData.getEncryptionAlgorithmName(),
metaData.getEncryptionType().getValue(), metaData.getNormalizationRuleVersion());
}
if (cryptoCopy != null && !cryptoCopy.isAlgorithmInitialized()) {
String paramName = parameterNames.get(i);
metadataMap.put(paramName, cryptoCopy);
} else {
return false;
}
} catch (SQLServerException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CryptoCacheInaccessible"));
Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(form.format(msgArgs), null);
}
}
// If the size of the cache exceeds the threshold, set that we are in trimming and trim the cache accordingly.
int cacheSizeCurrent = cache.getParamMap().size();
if (cacheSizeCurrent > CACHE_SIZE + CACHE_TRIM_THRESHOLD) {
int entriesToRemove = cacheSizeCurrent - CACHE_SIZE;
ConcurrentHashMap> newMap = new ConcurrentHashMap<>();
ConcurrentHashMap> oldMap = cache.getParamMap();
int count = 0;
for (Map.Entry> entry : oldMap.entrySet()) {
if (count >= entriesToRemove) {
newMap.put(entry.getKey(), entry.getValue());
}
count++;
}
cache.replaceParamMap(newMap);
if (metadataCacheLogger.isLoggable(java.util.logging.Level.FINEST)) {
metadataCacheLogger.finest("Cache successfully trimmed.");
}
}
cache.addParamEntry(encryptionValues.getKey(), metadataMap);
return true;
}
/**
*
* Remove the cache entry.
*
* @param stmt
* SQLServer statement used to retrieve keys
* @param session
* The enclave session where the cryptocache is stored
* @param connection
* The SQLServerConnection, also used to retrieve keys
*/
static void removeCacheEntry(SQLServerStatement stmt, CryptoCache cache, SQLServerConnection connection) {
AbstractMap.SimpleEntry encryptionValues = getCacheLookupKeys(stmt, connection);
if (encryptionValues.getKey() == null) {
return;
}
cache.removeParamEntry(encryptionValues.getKey());
}
/**
*
* Returns the cache and enclave lookup keys for a given connection and statement
*
* @param statement
* The SQLServer statement used to construct part of the keys
* @param connection
* The connection from which database name is retrieved
* @return A key value pair containing cache lookup key and enclave lookup key
*/
private static AbstractMap.SimpleEntry getCacheLookupKeys(SQLServerStatement statement,
SQLServerConnection connection) {
StringBuilder cacheLookupKeyBuilder = new StringBuilder();
cacheLookupKeyBuilder.append(":::");
String databaseName = connection.activeConnectionProperties
.getProperty(SQLServerDriverStringProperty.DATABASE_NAME.toString());
cacheLookupKeyBuilder.append(databaseName);
cacheLookupKeyBuilder.append(":::");
cacheLookupKeyBuilder.append(statement.toString());
String cacheLookupKey = cacheLookupKeyBuilder.toString();
String enclaveLookupKey = cacheLookupKeyBuilder.append(":::enclaveKeys").toString();
return new AbstractMap.SimpleEntry<>(cacheLookupKey, enclaveLookupKey);
}
}