All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.sqlserver.jdbc.ParameterMetaDataCache Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
/*
 * 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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy