I'm attempting to use CryptUnprotectData
to read a password protected using CryptProtectData
into a SecureString
and use that to connect to a database. I can get the correct password out, but trying to create a new SqlConnection
after that fails with the following:
System.TypeInitializationException was unhandled
HResult=-2146233036
Message=The type initializer for 'System.Data.SqlClient.SqlConnection' threw an exception.
Source=System.Data
TypeName=System.Data.SqlClient.SqlConnection
StackTrace:
at System.Data.SqlClient.SqlConnection..ctor()
at System.Data.SqlClient.SqlConnection..ctor(String connectionString, SqlCredential credential)
at System.Data.SqlClient.SqlConnection..ctor(String connectionString)
at ProtectedSqlTest.Program.Main() in C:\Git\ProtectedSqlTest\ProtectedSqlTest\Program.cs:line 16
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
HResult=-2146233036
Message=The type initializer for 'System.Data.SqlClient.SqlConnectionFactory' threw an exception.
Source=System.Data
TypeName=System.Data.SqlClient.SqlConnectionFactory
StackTrace:
at System.Data.SqlClient.SqlConnection..cctor()
InnerException:
HResult=-2146233036
Message=The type initializer for 'System.Data.SqlClient.SqlPerformanceCounters' threw an exception.
Source=System.Data
TypeName=System.Data.SqlClient.SqlPerformanceCounters
StackTrace:
at System.Data.SqlClient.SqlConnectionFactory..cctor()
InnerException:
HResult=-2147024809
Message=The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))
Source=mscorlib
StackTrace:
at System.Globalization.TextInfo.InternalChangeCaseString(IntPtr handle, IntPtr handleOrigin, String localeName, String str, Boolean isToUpper)
at System.Globalization.TextInfo.ToLower(String str)
at System.String.ToLower(CultureInfo culture)
at System.Diagnostics.PerformanceCounterLib.GetPerformanceCounterLib(String machineName, CultureInfo culture)
at System.Diagnostics.PerformanceCounterLib.IsCustomCategory(String machine, String category)
at System.Diagnostics.PerformanceCounter.InitializeImpl()
at System.Diagnostics.PerformanceCounter.set_RawValue(Int64 value)
at System.Data.ProviderBase.DbConnectionPoolCounters.Counter..ctor(String categoryName, String instanceName, String counterName, PerformanceCounterType counterType)
at System.Data.ProviderBase.DbConnectionPoolCounters..ctor(String categoryName, String categoryHelp)
at System.Data.SqlClient.SqlPerformanceCounters..ctor()
at System.Data.SqlClient.SqlPerformanceCounters..cctor()
InnerException:
It's enough to simply call CryptUnprotectData
for the SqlConnection
to fail, the connection itself doesn't need to use the returned SecureString
.
I'm using the extension methods from here as described in this post for my minimal repro:
class Program
{
const string ProtectedSecret = /* SNIP - base 64 encoded protected data here */;
static void Main()
{
// calling AppendProtectedData breaks the following SqlConnection
// without the following line the application works fine
new SecureString().AppendProtectedData(Convert.FromBase64String(ProtectedSecret));
using (var conn = new SqlConnection("Server=(localdb)\\MSSqlLocalDb;Trusted_Connection=true"))
using (var cmd = new SqlCommand("select 1", conn))
{
conn.Open();
cmd.ExecuteNonQuery();
}
}
}
If i create a new SqlConnection
before I load the password, I can create new SqlConnection
s fine for the duration of the application as it seems to use the same SqlConnectionFactory
, but that means as a workaround I have to do something like this at the start of the application:
new SqlConnection().Dispose();
... which I'd like to avoid.
The following do not help:
CryptProtectFlags
that is passed to CryptUnprotectData
.RuntimeHelpers.PrepareConstrainedRegions()
from the protection method.Windows 10, VS Enterprise 2015, Console Application (.NET 4.6.1)
UPDATE: Running the data protection code in another threads gives a similar exception with a different root cause:
System.TypeInitializationException was unhandled
HResult=-2146233036
Message=The type initializer for 'System.Data.SqlClient.SqlConnection' threw an exception.
Source=System.Data
TypeName=System.Data.SqlClient.SqlConnection
StackTrace:
at System.Data.SqlClient.SqlConnection..ctor()
at System.Data.SqlClient.SqlConnection..ctor(String connectionString, SqlCredential credential)
at System.Data.SqlClient.SqlConnection..ctor(String connectionString)
at ProtectedSqlTest.Program.Main() in C:\Git\ProtectedSqlTest\ProtectedSqlTest\Program.cs:line 17
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
HResult=-2146233036
Message=The type initializer for 'System.Data.SqlClient.SqlConnectionFactory' threw an exception.
Source=System.Data
TypeName=System.Data.SqlClient.SqlConnectionFactory
StackTrace:
at System.Data.SqlClient.SqlConnection..cctor()
InnerException:
HResult=-2146233036
Message=The type initializer for 'System.Data.SqlClient.SqlPerformanceCounters' threw an exception.
Source=System.Data
TypeName=System.Data.SqlClient.SqlPerformanceCounters
StackTrace:
at System.Data.SqlClient.SqlConnectionFactory..cctor()
InnerException:
BareMessage=Configuration system failed to initialize
HResult=-2146232062
Line=0
Message=Configuration system failed to initialize
Source=System.Configuration
StackTrace:
at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
at System.Configuration.ClientConfigurationSystem.PrepareClientConfigSystem(String sectionName)
at System.Configuration.ClientConfigurationSystem.System.Configuration.Internal.IInternalConfigSystem.GetSection(String sectionName)
at System.Configuration.ConfigurationManager.GetSection(String sectionName)
at System.Configuration.PrivilegedConfigurationManager.GetSection(String sectionName)
at System.Diagnostics.DiagnosticsConfiguration.Initialize()
at System.Diagnostics.DiagnosticsConfiguration.get_SwitchSettings()
at System.Diagnostics.Switch.InitializeConfigSettings()
at System.Diagnostics.Switch.InitializeWithStatus()
at System.Diagnostics.Switch.get_SwitchSetting()
at System.Data.ProviderBase.DbConnectionPoolCounters..ctor(String categoryName, String categoryHelp)
at System.Data.SqlClient.SqlPerformanceCounters..ctor()
at System.Data.SqlClient.SqlPerformanceCounters..cctor()
InnerException:
HResult=-2147024809
Message=Item has already been added. Key in dictionary: 'MACHINE' Key being added: 'MACHINE'
Source=mscorlib
StackTrace:
at System.Collections.Hashtable.Insert(Object key, Object nvalue, Boolean add)
at System.Collections.Hashtable.Add(Object key, Object value)
at System.Configuration.Internal.InternalConfigRoot.GetConfigRecord(String configPath)
at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
InnerException:
The CryptUnprotectData function decrypts and does an integrity check of the data in a DATA_BLOB structure. Usually, only a user with the same logon credentials as the encrypter can decrypt the data. In addition, the encryption and decryption must be done on the same computer.
The CRYPTPROTECT_PROMPTSTRUCT structure provides the text of a prompt and information about when and where that prompt is to be displayed when using the CryptProtectData and CryptUnprotectData functions. The CryptProtectData function performs encryption on the data in a DATA_BLOB structure.
The CryptProtectData function creates a session key when the data is encrypted. That key is derived again and used to decrypt the data BLOB. The Message Authentication Code (MAC) hash added to the encrypted data can be used to determine whether the encrypted data was altered in any way.
P/Invoke is a technology that allows you to access structs, callbacks, and functions in unmanaged libraries from your managed code. Most of the P/Invoke API is contained in two namespaces: System and System.Runtime.InteropServices.
Interestingly the faulting code is:
internal static PerformanceCounterLib GetPerformanceCounterLib(string machineName, CultureInfo culture) {
SharedUtils.CheckEnvironment();
string lcidString = culture.LCID.ToString("X3", CultureInfo.InvariantCulture);
if (machineName.CompareTo(".") == 0)
machineName = ComputerName.ToLower(CultureInfo.InvariantCulture);
else
machineName = machineName.ToLower(CultureInfo.InvariantCulture);
...
the line that calls ComputerName.ToLower(CultureInfo.InvariantCulture)
causes the exception.
You can reproduce the same behavior just calling code
new SecureString().AppendProtectedData(Convert.FromBase64String(ProtectedSecret));
string lower = "Something".ToLower(CultureInfo.InvariantCulture);
Somehow in the constructor of the TextInfo
class
this.m_dataHandle = CompareInfo.InternalInitSortHandle(m_textInfoName, out handleOrigin);
returns invalid data if this is not called before CryptUnprotectData
function.
This seems like a bug in the framework. You can submit it to Microsoft. In the meantime you can call this line beforehand to prevent the error.
"".ToLower(CultureInfo.InvariantCulture);
I was recently experiencing similar symptoms, using the same code from http://www.griffinscs.com/?p=12: any call to CryptUnprotectData
would lead to an exception in some unrelated code. Interestingly, the failure only occurred on a Windows 10 machine; the same code worked fine on a Windows 7 machine.
I fixed the problem by changing the declarations of the szDataDescr
parameters in both CryptProtectData
and CryptUnprotectData
from string
to IntPtr
, and passing IntPtr.Zero
instead of string.Empty
in the two calls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With