Azure web app sporadically throws CryptographicException: Keyset does not exist - c#

I have a Identity Server web app hosted on Azure. It has a .pfx file in it's root directory for signing. The problem is that when newly published it works perfectly fine but after some time it starts throwing CryptographicException: Keyset does not exist.
Based on CryptographicException KeySet does not exists I would assume that it is a file access issue, but why out of the sudden azure is messing up with file access.

I've been seing the same exception sporadically. In my case, it was caused by Data Protection keys changing when I swap between deployment slots. When using services.AddDataProtection() with the default configuration and hosting the app on Azure App Service, the data protection keys are stored in %HOME%\ASP.NET\DataProtection-keys. This directory is backed by a network share to make sure the keys are available on all your app instances, BUT this is not the case for deployment slots. So when you switch from one deployment slot to another, the keys will change and you will see CryptographicException: Keyset does not exist when your app tries to unprotect a payload.
To make sure your app is always using the same key, you need to configure a different key storage provider. I.e. use Redis or Azure Blob Storage as backing store.
You can find more information about how to configure this in the official documentation.
I've also described the issue I had on my blog.

I recommend that you do not retrieve your signing cert from disk firstly. Rather upload the certificate to the associated web app in Azure Portal.
and retrieve the certificate on application startup
something like.
X509Certificate2 Certificate = null;
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint,"CERTIFICATE_THUMBPRINT_HERE",false);
Certificate = certCollection[0];

Related

Intermittent error accessing Azure key vault - Keyset does not exists

I am using Azure Key Vault in my MVC web application. I connect to the Key Vault using certificate. Below is my sample code
AssertionCert = GetCertificate(WebConfigurationManager.AppSettings[KeyVaultConstant.ApplicationID], WebConfigurationManager.AppSettings[KeyVaultConstant.CertificateThumbprint], false);
var keyClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback((authority, resource, scope) => GetToken(authority, resource, scope, AssertionCert)));
var secrets = keyClient.GetSecretsAsync(WebConfigurationManager.AppSettings["VaultUri"]).GetAwaiter().GetResult();
On some instances I get an error stating
Cryptographic Exception: Keyset does not exists
On research I found that I have to give IIS_IUser permission to the folder where primary key is stored. I did that, Yet once in a while I get this error. This does NOT happen everytime.
You are right about the permission issue . Here are the troubleshooting steps for the same:
Troubleshooting Steps:
First check the service account which is running CRM App pool or ADFS App pool
Now open certificate manager, in personal store, locate your certificate. Right click on the certificate and select "Managed Private Keys" option
In the permission window, check if your app pool service account is given appropriate permission (Read permission should be fine, otherwise you can give Full control)
In below screenshot I have given full permission on my wildcard certificate *.test.local to my App Pool account – "test\AppPoolSvc"
Once the permission is given, perform an IISRESET and try accessing.
For further reference , please check. Also please try to restart the machine to see if it works.

Service Fabric: Authenticating with Azure KeyVault via cert: "KeySet does not exist"

This is the scenario I am trying to enable:
I wish to authenticate to an azure keyvault from my web service application (azure service fabric) via a client certificate.
These are the steps I'm following:
Add a certificate to my keyvault in azure (self signed)
Download certificate via azure powershell (pfx)
Create Azure App Instance to identify my app
Associate certificate with app
Create service principal for the azure app
Give principal access to keyvault
All looks good. When I spin up my service (local service fabric cluster), and try to connect to keyvault to retrieve a secret key+value that I have stored inside, I get error:
CryptographicException: "KeySet does not exist"
When I try to examine PrivateKey property value of the X509Certificate2 object at runtime, it throws the same exception.
The certificate is found, and the private key exists (I verified this via MMC as well as some command line tools).
What can I be missing? Only cause I can think of for this failure is that service fabric user context (Network Service, I think) does not have permission to look at private key? It is stored in "LocalMachine" certificate store, under
Personal" folder (also referred to as "My"). From what I know, applications should be able to read from LocalMachine store without special permissions?
An alternative easier way to grant NETWORK SERVICE user permission on certificate private key (easier than my other answer):
Open certificate snap-in in MMC: WIN + R -> type mmc -> File -> Add/Remove Snap-in -> Add Certificates (Computer Account).
Find your certificate -> Right click and choose All Tasks/Manage Private Keys
Grant Read Permission for NETWORK SERVICE user
Ok, my suspicion was correct. I explicitly granted Network Service user (the user context under which local service fabric cluster runs) access to private key file. Now I can authenticate with keyvault.
cacls.exe "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\{private-key-filename}" /e /g "Network Service":R
I found private key location via a tool program "FindPrivateKey.exe"
findprivatekey.exe My LocalMachine -t "{thumbprint}" -a
Tool can be obtained from https://www.microsoft.com/en-us/download/confirmation.aspx?id=21459
(It is a source code sample located in directory \WCF\Setup\FindPrivateKey\CS\FindPrivateKey.sln, you need to build it yourself)
I ran into the same problem and granting read permission to Network Service worked for me. There is also another way to run run high-privilege service as Local System user described here:
https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-run-script-at-service-startup
You can modify your Service Fabric ApplicationManifest.xml to
define a service principal corresponding to local machine
use that service principal to run the service fabric package created
final ApplicationManifest.xml would look something like:
<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
<ServiceManifestImport>
...
<Policies>
<RunAsPolicy CodePackageRef="Code" UserRef="LocalSystemUser" /> <!-- 2. run service fabric as defined principal -->
</Policies>
</ServiceManifestImport>
<Principals>
<Users>
<User Name="LocalSystemUser" AccountType="LocalSystem" /> <!-- 1. define principal -->
</Users>
</Principals>
</ApplicationManifest>

Using a certificate for Azure subscription logs - 403 forbidden

Background:
I have a Windows Service which polls Azure subscription logs (API: http://msdn.microsoft.com/en-us/library/windowsazure/gg715318.aspx)
On my local development machine the service is set to log on as my account. The X509 certificate was imported under CurrentUser\Personal and in the source code where I check the cert store I have:
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
Issue:
The service works fine on my dev machine, it can retrieve data from the API.
On the testing machine I get this error:
The remote server returned an error: (403) Forbidden.
The service is set to log on as a specific user, dmz\aaseclg1 and the current user\personal cert store has the required certificate.
Any ideas?
Thanks in advance.
Edit: image of password prompt:
I have seen this error when I export the certificate from the machine on which it is created and while exporting, I choose to import it in .cer format (i.e. without exporting private keys). Can you try by exporting the certificate from your dev box in pfx format and then copy the file on your test box and import it again in your test box by selecting the file and installing the certificate?
UPDATE
I was able to reproduce this. When you import the certificate, please make sure that you have unchecked the checkbox which reads "Enable strong private key protection" as shown in the screenshot below.
When I check this checkbox, every time I use this certificate it prompts me to enter a password. Now I was using a GUI application so I could see that box. In your case since you are consuming the certificate through a Windows Service (a non UI thingie), this box never shows and you think the service is hanging.

Can't read CurrentUser certificates from X509Store

I'm developing ASP.NET 4.0 web application, and I want to read the current user certificates from X509Store. Reading the LocalMachine certificates works fine, but if I set the StoreLocation to CurrentUser, it gives me an empty collection.
The following code works fine :
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine); // StoreLocation.CurrentUser
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
I've checked my personal store (via certmgr.mmc) and I'm sure that I have the certificates.
What am I missing ? ( store.Certificates is empty )
It appears that you can not access the Personal Certificate Store via web application, no matter what application pool identity you're using.
It makes sense, a web application has no access to that location. :)
My solution :
I've developed an ActiveX control which I think its the only way to access the Store.
(Also, a Java Applet offers the same functionality).
I use the ActiveX control via JavaScript to access the Store, and send that information to the server.
If your worker process cannot access cert store, maybe it's just account setup problem. Try go ing to IIS Configuration, open ApplicationPools, right click on yours, select Advanced and try setting LoadUserProfile to TRUE. And restart the pool. It worker for me - no more exceptions when loading .PFX with private keys.
I had a similar problem. The solution was:
IIS admin->[your virtual dir]->Authentication->Anonymous Authentication (select then click "Edit...") and change it to use "Application pool identity".
Otherwise it may be running as the generic "IUSR"

"The credentials supplied to the package were not recognized" error when authenticating as server with certificate generated using BouncyCastle

I'm trying to create a certificate using the BouncyCastle.Crypto dll, which is then used to authenticate a SslStream as the server in a Windows Service process, which runs under the Local System account.
However when I get to the SslStream.AuthenticateAsServer(certificate) call, it throws a Win32 exception with the error message "The credentials supplied to the package were not recognized".
There are several questions on here about this error message, but none of them seem to describe, or solve, my particular problem.
In the hope that someone may be able to offer some help, I include the code I am using to create and install the certificate:
// First create a certificate using the BouncyCastle classes
BigInteger serialNumber = BigInteger.ProbablePrime(120, new Random());
AsymmetricCipherKeyPair keyPair = GenerateKeyPair();
X509V1CertificateGenerator generator = new X509V1CertificateGenerator();
generator.SetSerialNumber(serialNumber);
generator.SetIssuerDN(new X509Name("CN=My Issuer"));
generator.SetNotBefore(DateTime.Today);
generator.SetNotAfter(DateTime.Today.AddYears(100));
generator.SetSubjectDN(new X509Name("CN=My Issuer"));
generator.SetPublicKey(keyPair.Public);
generator.SetSignatureAlgorithm("SHA1WITHRSA");
Org.BouncyCastle.X509.X509Certificate cert = generator.Generate(
keyPair.Private, SecureRandom.GetInstance("SHA1PRNG"));
// Ok, now we have a BouncyCastle certificate, we need to convert it to the
// System.Security.Cryptography class, by writing it out to disk and reloading
X509Certificate2 dotNetCert;
string tempStorePassword = "Password01"; // In real life I'd use a random password
FileInfo tempStoreFile = new FileInfo(Path.GetTempFileName());
try
{
Pkcs12Store newStore = new Pkcs12Store();
X509CertificateEntry entry = new X509CertificateEntry(cert);
newStore.SetCertificateEntry(Environment.MachineName, entry);
newStore.SetKeyEntry(
Environment.MachineName,
new AsymmetricKeyEntry(keyPair.Private),
new [] { entry });
using (FileStream s = tempStoreFile.Create())
{
newStore.Save(s,
tempStorePassword.ToCharArray(),
new SecureRandom(new CryptoApiRandomGenerator()));
}
// Reload the certificate from disk
dotNetCert = new X509Certificate2(tempStoreFile.FullName, tempStorePassword);
}
finally
{
tempStoreFile.Delete();
}
// Now install it into the required certificate stores
X509Store targetStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
targetStore.Open(OpenFlags.ReadWrite);
targetStore.Add(dotNetCert);
targetStore.Close();
Ok, now I have created and installed the certificate. I then configure my Windows Service to use this certificate by supplying it with the generated certificate's thumbprint. I then use the certificate like this:
// First load the certificate
X509Certificate2 certificate = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 certInStore in store.Certificates)
{
if (certInStore.Thumbprint == "...value not shown...")
{
certificate = certInStore;
break;
}
}
SslStream sslStream = new SslStream(new NetworkStream(socket, false), false);
// Now this line throws a Win32Exception
// "The credentials supplied to the package were not recognized"
sslStream.AuthenticateAsServer(certificate);
Does anyone have any idea what the problem could be here?
I don't get the problem if I install a certificate created with 'makecert', but that isn't suitable for production certificates.
I've also tried creating a separate x509v1 CA certificate and then x509v3 certificate for server authentication, but I get the same error, so I removed this in the example code for simplicity.
That particular error message rings a bell. I'll guess that either you did not store the private key with the certificate, or, the Windows service does not have access to the private key. To check this, open the Certificates MMC snap-in:
Run mmc (e.g. from the Start menu)
File menu > Add/Remove Snap-in
Select "Certificates" in left pane and then click Add
Select "Computer Account" (for LocalMachine) then click Next,
and then Finish
Navigate to the certificate and double-click in the right pane. On the General tab that comes up, you should see a little key icon at the bottom, along with the text, "You have a private key that corresponds to this certificate." If not, that's the problem. The private key was not saved.
If the private key is present, click Ok to dismiss this dialog, and then right-click on the certificate in the right pane and select on the pop-up menu: All Tasks > Manage Private Keys. In that dialog, make sure that the Windows account that the service runs under has read access to the private key. If it doesn't, that's the problem.
Edit: Oops, you wrote that the service runs as Local System, so it must be a missing private key, if it is one of these two problems. I'll leave the key access check in my answer anyway, for anybody else that hits this and is not running as Local System.
Sometime the problem happens when the application try to reach the certificate doesn't have enough privilege to access the certificate, the issue may resolve by running the application as administrator.
I've the same issue, tried everything from many posts, and google researching.
But looks like I found fix.
When I changed Identify from ApplicationPoolIdentity to LocalSystem everything start working perfectly.
May be will be helpful for someone.
For me works on Windows Server 2012 R2 (.net 4.6.1) - "All Tasks > Manage Private Keys" and set access to Everyone (setting to IS_IUSRS was not enough)
Found this solution online but I can't find the source to give the credit.
Since I ran into the "The credentials supplied to the package were not recognized" problem with AuthenticateAsClient() (for client verification), I'd like to document how I solved it. It's a different method with the same end goal. Since it might be useful for AuthenticateAsServer(), figured why not.
Here I convert a BC Certificate to a .NET certificate. Add an extra step in converting it to a .NET X509Certificate2 to store it's PrivateKey property.
Org.BouncyCastle.X509.X509Certificate bcCert;
X509Certificate dotNetCert = DotNetUtilities.ToX509Certificate(bcCert);
X509Certificate2 dotNetCert2 = new X509Certificate2(dotNetCert);
Problem showed up when adding a BouncyCastle private key to a .NET private key. The X509 certificates converted fine but not the private keys. I converted the BC private key to RSACryptoServiceProvider using the provided DotNetUtilities. Unfortunately it looks like the conversion isn't complete. So I created another RSACryptoServiceProvider which I then initialized. Then I imported the private key into the one I created.
// Apparently, using DotNetUtilities to convert the private key is a little iffy. Have to do some init up front.
RSACryptoServiceProvider tempRcsp = (RSACryptoServiceProvider)DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)ackp.Private);
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider(new CspParameters(1, "Microsoft Strong Cryptographic Provider",
new Guid().ToString(),
new CryptoKeySecurity(), null));
rcsp.ImportCspBlob(tempRcsp.ExportCspBlob(true));
dotNetCert2.PrivateKey = rcsp;
After that, I was able to save the X509Certificate2 object directly to the key store. I didn't need the actual file so I skipped that step.
Previously, every time I have run into this issue, I have had to delete the cert out of my local machine cert store and re-import it. Then it all seems happy. I can't see how it could be a global permissions issue or invalid cert if simply re-importing it fixes the issue.
How I finally fixed it was using the winhttpcertcfg tool from the Windows Resource Kit to grant permission to the specific user that was using the cert.
The syntax would be:
"C:\Program Files (x86)\Windows Resource Kits\Tools\winhttpcertcfg" -i cert.p12 -c LOCAL_MACHINE\My -a UserWhoUsesTheCert -p passwordforp12
I had the similar issue when calling a WCF REST service from .NET application where I need to attach the client certificate; All I had to do was provide access to the certificate in cert store[mmc console] to the "NETWORKSERVICE] off course my IIS Pool was default pool which indicates its using NETWORKService user account.
the mistake that I did was, I copied the cert from another store to Local
Machine -> Personnel store where the certificate was protected with password. should import the certificate explicitly in required store.
If you running from IIS, ensure that the Application Pool has 'Load User Profile' set to true.
This was the only solution for me.
I don't recall this error but the certificate you're creating is not a valid to be used for SSL/TLS, including:
v1 (not v3) certificate;
missing extensions;
invalid CN;
...
There are several RFC that talks about this, including RFC5246 on TLS (1.2).
Finally making your own certificates is not more suitable than using the ones made by makecert (but the last one can generate the minimum set to be usable for an SSL/TLS server certificate).
I strongly suggest you to buy, from a good known Certificate Authority (CA), a SSL/TLS certificate for production. That will get you a working certificate recognized by the most browsers and tools.
Another reason for this error is that you ran the application / server under an account which password has changed, breaking its capability of accessing the certificate it wants to use in the certificate store.
This especially may not be as obvious if you use a NuGet package like LettuceEncrypt which automatically stores the LetsEncrypt in your store.
Delete the certificate from your store and reimport it.

Categories