/// <summary> /// Decrypts the ciphertext. /// </summary> internal static byte[] DecryptWithKey(byte[] cipherText, SqlCipherMetadata md, SqlConnection connection, SqlCommand command) { // Initialize cipherAlgo if not already done. if (!md.IsAlgorithmInitialized()) { DecryptSymmetricKey(md, connection, command); } Debug.Assert(md.IsAlgorithmInitialized(), "Decryption Algorithm is not initialized"); try { byte[] plainText = md.CipherAlgorithm.DecryptData(cipherText); // this call succeeds or throws. if (null == plainText) { throw SQL.NullPlainText(); } return(plainText); } catch (Exception e) { // compute the strings to pass string keyStr = GetBytesAsString(md.EncryptionKeyInfo.encryptedKey, fLast: true, countOfBytes: 10); string valStr = GetBytesAsString(cipherText, fLast: false, countOfBytes: 10); throw SQL.ThrowDecryptionFailed(keyStr, valStr, e); } }
/// <summary> /// Constructor. /// </summary> /// <param name="smiParameterMetadata"></param> /// <param name="cipherMetadata"></param> internal SqlColumnEncryptionInputParameterInfo(SmiParameterMetaData smiParameterMetadata, SqlCipherMetadata cipherMetadata) { Debug.Assert(smiParameterMetadata != null, "smiParameterMetadata should not be null."); Debug.Assert(cipherMetadata != null, "cipherMetadata should not be null"); Debug.Assert(cipherMetadata.EncryptionKeyInfo.HasValue, "cipherMetadata.EncryptionKeyInfo.HasValue should be true."); _smiParameterMetadata = smiParameterMetadata; _cipherMetadata = cipherMetadata; _serializedWireFormat = SerializeToWriteFormat(); }
/// <summary> /// Encrypts the plaintext. /// </summary> internal static byte[] EncryptWithKey(byte[] plainText, SqlCipherMetadata md, SqlConnection connection, SqlCommand command) { // Initialize cipherAlgo if not already done. if (!md.IsAlgorithmInitialized()) { DecryptSymmetricKey(md, connection, command); } Debug.Assert(md.IsAlgorithmInitialized(), "Encryption Algorithm is not initialized"); byte[] cipherText = md.CipherAlgorithm.EncryptData(plainText); // this call succeeds or throws. if (null == cipherText || 0 == cipherText.Length) { throw SQL.NullCipherText(); } return(cipherText); }
/// <summary> /// Encrypts the plaintext. /// </summary> internal static byte[] EncryptWithKey(byte[] plainText, SqlCipherMetadata md, string serverName) { Debug.Assert(serverName != null, @"serverName should not be null in EncryptWithKey."); // Initialize cipherAlgo if not already done. if (!md.IsAlgorithmInitialized()) { SqlSecurityUtility.DecryptSymmetricKey(md, serverName); } Debug.Assert(md.IsAlgorithmInitialized(), "Encryption Algorithm is not initialized"); byte[] cipherText = md.CipherAlgorithm.EncryptData(plainText); // this call succeeds or throws. if (null == cipherText || 0 == cipherText.Length) { throw SQL.NullCipherText(); } return(cipherText); }
/// <summary> /// <para> Decrypts the symmetric key and saves it in metadata. In addition, initializes /// the SqlClientEncryptionAlgorithm for rapid decryption.</para> /// </summary> internal static void DecryptSymmetricKey(SqlCipherMetadata md, SqlConnection connection, SqlCommand command) { Debug.Assert(md is not null, "md should not be null in DecryptSymmetricKey."); SqlClientSymmetricKey symKey = null; SqlEncryptionKeyInfo encryptionkeyInfoChosen = null; DecryptSymmetricKey(md.EncryptionInfo, out symKey, out encryptionkeyInfoChosen, connection, command); // Given the symmetric key instantiate a SqlClientEncryptionAlgorithm object and cache it in metadata md.CipherAlgorithm = null; SqlClientEncryptionAlgorithm cipherAlgorithm = null; string algorithmName = ValidateAndGetEncryptionAlgorithmName(md.CipherAlgorithmId, md.CipherAlgorithmName); // may throw SqlClientEncryptionAlgorithmFactoryList.GetInstance().GetAlgorithm(symKey, md.EncryptionType, algorithmName, out cipherAlgorithm); // will validate algorithm name and type Debug.Assert(cipherAlgorithm is not null); md.CipherAlgorithm = cipherAlgorithm; md.EncryptionKeyInfo = encryptionkeyInfoChosen; return; }
/// <summary> /// <para> Retrieves the query metadata for a specific query from the cache.</para> /// </summary> internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand) { // Return immediately if caching is disabled. if (!SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled) { return(false); } // Check the cache to see if we have the MD for this query cached. string cacheLookupKey = GetCacheLookupKeyFromSqlCommand(sqlCommand); if (cacheLookupKey == null) { IncrementCacheMisses(); return(false); } Dictionary <string, SqlCipherMetadata> ciperMetadataDictionary = _cache.Get(cacheLookupKey) as Dictionary <string, SqlCipherMetadata>; // If we had a cache miss just return false. if (ciperMetadataDictionary == null) { IncrementCacheMisses(); return(false); } // Iterate over all the parameters and try to get their cipher MD. foreach (SqlParameter param in sqlCommand.Parameters) { SqlCipherMetadata paramCiperMetadata; bool found = ciperMetadataDictionary.TryGetValue(param.ParameterNameFixed, out paramCiperMetadata); // If we failed to identify the encryption for a specific parameter, clear up the cipher MD of all parameters and exit. if (!found) { foreach (SqlParameter paramToCleanup in sqlCommand.Parameters) { paramToCleanup.CipherMetadata = null; } IncrementCacheMisses(); return(false); } // Cached cipher MD should never have an initialized algorithm since this would contain the key. Debug.Assert(paramCiperMetadata == null || !paramCiperMetadata.IsAlgorithmInitialized()); // We were able to identify the cipher MD for this parameter, so set it on the param. param.CipherMetadata = paramCiperMetadata; } // Create a copy of the cipherMD in order to load the key. // The key shouldn't be loaded in the cached version for security reasons. foreach (SqlParameter param in sqlCommand.Parameters) { SqlCipherMetadata cipherMdCopy = null; if (param.CipherMetadata != null) { cipherMdCopy = new SqlCipherMetadata( param.CipherMetadata.EncryptionInfo, 0, param.CipherMetadata.CipherAlgorithmId, param.CipherMetadata.CipherAlgorithmName, param.CipherMetadata.EncryptionType, param.CipherMetadata.NormalizationRuleVersion); } param.CipherMetadata = cipherMdCopy; if (cipherMdCopy != null) { // Try to get the encryption key. If the key information is stale, this might fail. // In this case, just fail the cache lookup. try { SqlSecurityUtility.DecryptSymmetricKey(cipherMdCopy, sqlCommand.Connection.DataSource); } catch (Exception ex) { // Invalidate the cache entry. InvalidateCacheEntry(sqlCommand); // If we get one of the expected exceptions, just fail the cache lookup, otherwise throw. if (ex is SqlException || ex is ArgumentException || ex is ArgumentNullException) { foreach (SqlParameter paramToCleanup in sqlCommand.Parameters) { paramToCleanup.CipherMetadata = null; } IncrementCacheMisses(); return(false); } throw; } } } IncrementCacheHits(); return(true); }
/// <summary> /// <para> Add the metadata for a specific query to the cache.</para> /// </summary> internal void AddQueryMetadata(SqlCommand sqlCommand, bool ignoreQueriesWithReturnValueParams) { // Return immediately if caching is disabled. if (!SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled) { return; } // We don't want to cache parameter metadata for commands with ReturnValue because there is no way for the client to verify that the cached information is still valid. // ReturnStatus is fine because it is always plaintext, but we cannot distinguish between the two at RPC time (they are both ReturnValue parameters), but only when the TDS tokens with the result come back. // Therefore we want to postpone populating the cache for any queries that have a ReturnValue parameter until we get the return tokens from TDS. // Check if we have a ReturnValue parameter and simply exit unless the caller wants to include queries with return values. // Only stored procs can have a real ReturnValue so just check for these. if (sqlCommand.CommandType == CommandType.StoredProcedure) { foreach (SqlParameter param in sqlCommand.Parameters) { // If we have a return value parameter don't cache the query MD. // We will cache it after we have confirmed it is looking for ReturnStatus and not ReturnValue. if (param.Direction == ParameterDirection.ReturnValue && ignoreQueriesWithReturnValueParams) { sqlCommand.CachingQueryMetadataPostponed = true; return; } } } // Construct the entry and put it in the cache. string cacheLookupKey = GetCacheLookupKeyFromSqlCommand(sqlCommand); if (cacheLookupKey == null) { return; } Dictionary <string, SqlCipherMetadata> ciperMetadataDictionary = new Dictionary <string, SqlCipherMetadata>(sqlCommand.Parameters.Count); // Create a copy of the cipherMD that doesn't have the algorithm and put it in the cache. foreach (SqlParameter param in sqlCommand.Parameters) { SqlCipherMetadata cipherMdCopy = null; if (param.CipherMetadata != null) { cipherMdCopy = new SqlCipherMetadata( param.CipherMetadata.EncryptionInfo, 0, param.CipherMetadata.CipherAlgorithmId, param.CipherMetadata.CipherAlgorithmName, param.CipherMetadata.EncryptionType, param.CipherMetadata.NormalizationRuleVersion); } // Cached cipher MD should never have an initialized algorithm since this would contain the key. Debug.Assert(cipherMdCopy == null || !cipherMdCopy.IsAlgorithmInitialized()); ciperMetadataDictionary.Add(param.ParameterNameFixed, cipherMdCopy); } // If the size of the cache exceeds the threshold, set that we are in trimming and trim the cache accordingly. long currentCacheSize = _cache.GetCount(); if ((currentCacheSize > CacheSize + CacheTrimThreshold) && (0 == Interlocked.CompareExchange(ref _inTrim, 1, 0))) { try { #if DEBUG if (_sleepOnTrim) { Thread.Sleep(TimeSpan.FromSeconds(10)); } #endif _cache.Trim((int)(((double)(currentCacheSize - CacheSize) / (double)currentCacheSize) * 100)); } finally { Interlocked.CompareExchange(ref _inTrim, 0, 1); } } // By default evict after 10 hours. _cache.Set(cacheLookupKey, ciperMetadataDictionary, DateTimeOffset.UtcNow.AddHours(10)); }