How should I encrypt App.config for an Azure WebJob? - c#

I'm building an Azure Website-based solution that needs to periodically contact a Service Bus relay endpoint in the background. My planned design approach was to use an Azure WebJob as the execution engine for this background task -- I would store my Service Bus connection string with shared secret credentials in App.config, encrypt it using the Pkcs12ProtectedConfigurationProvider with my site's custom SSL certificate, and everything would work perfectly!
The only problem is, it appears that WebJobs are not able to access the certificates for their containing Websites. The code for my WebJob can be very simple (but note that the appSettings section of App.config is encrypted):
public static void Main()
{
string connectionString = ConfigurationManager.AppSettings["Microsoft.ServiceBus.ConnectionString"];
Console.WriteLine(connectionString);
}
If I import the site's certificate on my local machine and run the WebJob executable there, everything works as expected. But when I upload the ZIP file with my binaries and .exe.config file into Azure, the job always fails with the below error.
[07/18/2014 22:53:24 > 621d84: ERR ] Unhandled Exception:
System.Configuration.ConfigurationErrorsException: Failed to decrypt using provider 'Pkcs12Provider'.
Error message from the provider: No certificate was found for thumbprint <My Certificate's Thumbprint>
(C:\DWASFiles\Sites\<My Site Name>\Temp\jobs\triggered\<My Job Name>\hzcfdtn5.f22\WebJob.exe.Config line
XX) ---> System.ApplicationException: No certificate was found for thumbprint <My Certificate's Thumbprint>
Am I correct in surmising that a WebJob can't access the corresponding Website's certificate store? This would make it pretty much impossible to use the Pkcs12 provider to encrypt my WebJob's secrets -- is there a better option available? Or is a WebJob simply the wrong tool for this job?

Azure WebSites, AFAIK, cannot use custom certificates.
However, you can put the connection strings in the connection strings section of your website in the Azure Portal. Here are details on how to read it afterwards: http://azure.microsoft.com/blog/2013/07/17/windows-azure-web-sites-how-application-strings-and-connection-strings-work/
While the above will not solve the encryption problem, it would at least remove the CS from AppConfig. That's what we do with the Service Bus connection string for Azure WebJobs SDK http://azure.microsoft.com/blog/2014/06/18/announcing-the-0-3-0-beta-preview-of-microsoft-azure-webjobs-sdk/
A webjobs seems to be the right tool for what you are trying to do.

Related

Error loading Private Keys in .net core on remote machine

I have a .net core web API project to process JWT tokens which uses the Eliptic Curve ES256 (secp256r1) certificates.
The certificates are cipher encrypted into a database.
I can retrieve and verify incoming tokens however I am having an issue while trying to retrieve the private key for signing new tokens.
The function I am using is:
private static ECDsa LoadPrivateKey(string privateKey)
{
var ecDsaCng = new ECDsaCng(CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob));
ecDsaCng.HashAlgorithm = CngAlgorithm.ECDsaP256;
return ecDsaCng;
}
The problem I am facing is that while this works fine on my local development machine, when I deploy it to the test server it fails with the message
An internal error occurred
I enables logging and can confirm the value retrieved is same in both the remote server as well as my development machine.
How to solve this?
So I finally managed to understand what the issue is here.
When I deployed it to the test server running under IIS, it seems that the user under which the APP Pool is running was not a local administrator on the test box.
it seems that to create a private key on the machine we need the user (in this case the APP Pool) to be a local administrator
I refered to this URL while trying to debug this and it did point me in the right direction:
https://blogs.msdn.microsoft.com/dsnotes/2012/08/17/cngkey-import-throws-an-exception-saying-an-internal-error-occurred-when-importing-a-key-blob-under-a-non-admin-user-from-a-wcf-web-service/

What is the recommended place to store Azure Storage Connection strings

When creating a new WebJob project, the AzureWebJobsStorage connection string is added to App.config > configuration > connectionStrings
In contrast, the microsoft documentation for storage account connection strings clearly states AppSettings is where they'd put it.
What is the recommended place? Is connectionStrings only for database connection strings?
We will consume the connection string in an Azure Web App
When creating a new WebJob project, the AzureWebJobsStorage connection string is added to App.config > configuration > connectionStrings
When your creating the Azure WebJob project, it would reference the related WebJob libraries (Microsoft.Azure.WebJobs, Microsoft.Azure.WebJobs.Core). You need to specify a storage account for the WebJob SDK to log traces and metric data. And the connection string need to be specified under the connectionStrings section of your config file, otherwise you would retrieve the following error:
Microsoft Azure WebJobs SDK Dashboard connection string is missing or empty. The Microsoft Azure Storage account connection string can be set in the following ways:
Set the connection string named 'AzureWebJobsDashboard' in the connectionStrings section of the .config file in the following format
Set the environment variable named 'AzureWebJobsDashboard'
Set corresponding property of JobHostConfiguration
Per my understanding, the Azure WebJob SDK only support reading the storage connection string from the above approaches. You could also set the connection string under the appSettings section, at this point you need to specify the related properties when constructing the JobHostConfiguration as follows:
static void Main()
{
var config = new JobHostConfiguration()
{
DashboardConnectionString= ConfigurationManager.AppSettings["AzureWebJobsDashboard"],
StorageConnectionString= ConfigurationManager.AppSettings["AzureWebJobsStorage"]
};
if (config.IsDevelopment)
{
config.UseDevelopmentSettings();
}
var host = new JobHost(config);
// The following code ensures that the WebJob will be running continuously
host.RunAndBlock();
}
Moreover, you could Use the Azure storage emulator for development and testing. While for production, you could specify the related app settings or connection strings on Azure Portal for overriding your dev settings. More details, you could refer to here for a better understanding of it.
What is the recommended place? Is connectionStrings only for database connection strings?
Per my understanding, when you are using the 3rd party libraries, you need to follow it's instruction for configuration. While you writing your code, you could define the connection string as you wish and read them in the corresponding way.

IBM Webshpere MQ client connecting remote queue using SSL

I am trying to connect to the remote queues using secured SSL connection. I have all the details provided by third party regarding SSL connection and Queue Manager details. I have V8 version of MQ client installed on my windows machine.
SSL folder that thirdparty has shared contains jks,kdb,rdb and sth files.
I am using below code to initialize the properties in .net console application
const string SslKeyRepository = #"ssl folder location with key name included";
const string CipherSpec = "TLS_RSA_WITH_3DES_EDE_CBC_SHA";
const string CipherSuite = "SSL_RSA_WITH_3DES_EDE_CBC_SHA";
const string SslPeerName = "Peername";
const string ConnectionType = MQC.TRANSPORT_MQSERIES_CLIENT;
static Hashtable init(String connectionType)
{
Hashtable connectionProperties = new Hashtable
{
{MQC.TRANSPORT_PROPERTY, connectionType},
{MQC.PORT_PROPERTY, 1496},
{MQC.SSL_CERT_STORE_PROPERTY, SslKeyRepository},
{MQC.SSL_CIPHER_SPEC_PROPERTY, CipherSpec},
{MQC.SSL_PEER_NAME_PROPERTY, $"CN=\"{SslPeerName}\""}
};
// Add the connection type
// SSL
// Set up the rest of the connection properties, based on the
// connection type requested
switch (connectionType)
{
case MQC.TRANSPORT_MQSERIES_BINDINGS:
break;
case MQC.TRANSPORT_MQSERIES_CLIENT:
case MQC.TRANSPORT_MQSERIES_XACLIENT:
case MQC.TRANSPORT_MQSERIES_MANAGED:
connectionProperties.Add(MQC.HOST_NAME_PROPERTY, HostName);
connectionProperties.Add(MQC.CHANNEL_PROPERTY, Channel);
break;
}
return connectionProperties;
}
I have tried few things but I end up getting exception "MQRC_SSL_INITIALIZATION_ERROR"
I would appreciate if you can help me with this.
There are many reasons for MQRC_SSL_INITIALIZATION_ERROR. Some of them are mechanical issues such as whether the keystore files can be accessed. Some are procedural such as whether the handshake fails. The best way to diagnose is methodically checking the configuration and then performing differential testing.
For the first part of this, perform the following checks. If you have already done so, don't cut corners. Do it again.
Verify that the certificates are accessible by issuing runmqakm -cert -list against the KDB to verify that it is structurally intact and the stash file is present with the proper password.
Verify that the kdb file is not in a world-readable directory is that the files are not marked world-readable.
Verify that the service account that runs the app is the owner of the KDB files and containing folder and has write access. (Not sure why but GSKit insists that the KDB must be writeable at run time.)
Issue runmqakm -cert -details to verify that the certificate(s) corresponding to the queue manager is/are present and the details. If the QMgr uses a self-signed cert there will be only one. If the QMgr uses a CA-signed cert there should be an intermediate and a root signer.
Find out from the 3rd party whether they have specified SSLCAUTH(OPTIONAL) or SSLCAUTH(REQUIRED). If OPTIONAL then the KDB should have no personal certs, only signers. If REQUIRED then the KDB must have a personal cert and the label must be ibmwebspheremq[serviceaccount] in lower case.
For the differential testing, try some of the following tests:
Test the app by connecting to a local QMgr using TLS (Note: MQ hasn't used SSL for years. It's TLS now. The old field names still retain SSL labels, though.) until you know that it is correctly configured. Go grab a copy of MQ Advanced for Developers and you can do integration testing on the desktop with your own QMgr, fully licensed for free.
Test using one of the sample programs. Use amqsputc or amqsgetc, depending on whether the real app is supposed to have PUT or GET on the queue. These use the same KDB, samme certs, etc. the main difference being they are known-good code.
Ask your business partner to let you test without SSL to make sure the "mechanical" parts of the configuration are correct. This includes things like the firewall routing, host, port and channel name, QMgr name, etc. If you can't connect with plaintext channels, you definitely won't succeed with TLS channels.
Once that works, test with SSL enabled and SSLCAUTH(OPTIONAL) set at the QMgr. This demonstrates that the client can validate the QMgr's cert.
Once that works, and if the objective is to use mutual authentication, test with SSLCAUTH(REQUIRED) set at the QMgr and a personal cert in the local KDB. This demonstrates that the QMgr can validate the client's cert.
Then, and only then, turn on SSLPEER locally to filter on the QMgr cert's DN.
If these don't help, please update the question with detailed results of your testing. The most common issues include cert labels and KDB permissions. If the business partner gave you the JKS and KDB, these should generally not come with a personal cert, only trusted certs.

WCF Error : 'It is likely that certificate 'my cert' may not have a private key that is capable of key exchange

I have a WCF service I'm trying to host on our production web server (IIS6). I've set the web up and tied our cert to the web. When I try to browse to the service url, I receive the following error in the event log :
The exception message is: It is likely that certificate
'CN=.mydomain, OU=Secure Link SSL Wildcard, OU=I.T., C=US' may not
have a private key that is capable of key exchange or the process may
not have access rights for the private key. Please see inner exception
for detail.. ---> System.ArgumentException: It is likely that
certificate 'CN=.mydomain.com, OU=Secure Link SSL Wildcard,
OU=I.T., O=mydomain, C=US' may not have a private key that is capable
of key exchange or the process may not have access rights for the
private key. Please see inner exception for detail. --->
System.Security.Cryptography.CryptographicException: The handle is
invalid.
I've confirmed ASP.Net 1.1, 2, and 4 are all set to 'Allow' in 'Web Service Extensions'. I've also confirmed the cert is set up in iis and it shows 'You have a private key that corresponds to this certificate'. Also, Execute Permissions are set to 'Script and Executables'.
I had this problem, and it turned out that the account the service was running under did not have permissions to access the certificate's private key.
Here are the steps I used to solve it:
Start the Cetificate manager. Do this by running MMC, activate [File]-[Add/Remove Snap-in...], then add "Certificates", selecting "Computer Account" and "Local Computer" in the ensuing wizard dialogs.
In the certificate manager, right-click on the relevant certificate and activate [All Tasks]-[Manage Private Keys]
This gives you a permissions window. Click Add
Add the account name or group that this service runs under.
Seems like your certificate was created for signatures and not key exchange, what I suppose to be normal for SSL certificates.
If you look at the makecert documentation, you can see that the -sky switch lets you specify whether the certificate should be used for signatures or key exchange. You can try to create a self-signed certificate with type exchange and test whether the exception still occurs. Don't forget to put the self-signed certificate into the machine's trusted root certification authority folder in order to avoid exceptions that the certificate is not valid.
Ensure also that the account name or group that needs to access the certificate ALSO has access to the folder hierarchy that the certificate resides in. If your certificate is hiding in, for example, 'C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys', and the account that needs to access it is 'NETWORK SERVICE', then 'NETWORK SERVICE' needs access to that full path. Just assigning rights to the file is not enough.
Same problem here. To fix the problem I added the following line to the <system.web> node of the web.config.
<httpRuntime targetFramework="4.7.2"/>
I had this issue today and it was on a server cloned from another server. I had to uninstall the certs, reinstall the certs, and grant access to the certs (same manner as described in accepted answer).

Decrypting ConnectionString

I've been reading about encryption and decryption of certain parts of the web.config for C#/ASP applications and I am successful in encrypting the connectionstring of the web.config for my application. My problem is decrypting. I'm using the standard code to encrypt and decrypt but it modifies the web.config. Locally it works fine since when it does modify the web.config I can save it and it will still run but when I upload it to a remote server then it doesn't work.
The error I'm getting is
Configuration Error Description: An error occurred during the
processing of a configuration file required to service this request.
Please review the specific error details below and modify your
configuration file appropriately.
Parser Error Message: Failed to decrypt using provider
'RsaProtectedConfigurationProvider'. Error message from the provider:
Bad Data
Encrypting
try
{
Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
ConfigurationSection section = config.GetSection("connectionStrings");
if (!section.SectionInformation.IsProtected)
{
section.SectionInformation.ProtectSection("RSAProtectedConfigurationProvider");
config.Save();
}
catch (Exception ex)
{
}
Decrypting
Configuration config = WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);
ConfigurationSection section = config.GetSection("connectionStrings");
if (section.SectionInformation.IsProtected)
{
section.SectionInformation.UnprotectSection();
config.Save();
}
I call the decrypting method whenever the page loads but it doesn't work and it gives me the error above.
I do not have access to the host server at all. So using the command line is not an option.
Make sure the same decryption key is available on the remote server that you have locally. This would be the machine key element.
You can create and export an RSA Key Container but you'll still need access to the remote server to import the container.
I don't believe that the machineKey element is relevant here BTW. From MSDN:
Key containers with local machine scope (useMachineContainer"true") are stored in a hidden folder at %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys
I'm guessing the scenario is you're trying to encrypt the web.config locally before pushing it to your hosting provider/remote server. Steve Rowbotham's answer on this question is correct in that you'll need the same RSA Key container on both your development machine and the remote server to be able to encrypt locally and decrypt remotely.
Can you take a different route and encrypt the web.config as part of your deployment process? We use MsDeploy to handle encrypting the config file during deployment and I can provide some sample code if you would like it.
Alternatively, when you application first loads (during the Application_Start event in global.asax) you could check if the connectionStrings section of the web.config is encrypted and then encrypt it. You shouldn't have to decrypt the web.config manually...

Categories

Resources