I have a problem with an MVC web app which calls to another service using a private certificate.
The certificate is in my Personal keystore against the current machine- I have used winhttpcertcfg to give permissions to the certificate to the app pool identity of my web application. The key is loaded in the following method;
internal bool SetCertificateFromCertStore(string subjectName)
{
X509Store store = null;
try
{
store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, true);
if (certs.Count != 1)
{
store.Close();
store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
certs = store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, true);
if (certs.Count != 1)
{
throw new Exception("Unable to find Certificate");
}
}
_certificate = certs[0];
return true;
}
finally
{
if (store != null)
{
store.Close();
}
}
}
This code worked everytime until a couple weeks ago (the 12th of April) when at 17:05 I noticed the first instance in ELMAH of the "Unable to find Certificate" exception being thrown. Checking the applications log, the system is still working on almost all the request with this error cropping up just a few times an hour on some requests.
I've read around similar questions which suggest implementing code similar to the code I'm already using (querying multiple stores). Is there some kind of known issue with the Windows certificate store? Perhaps a locking issue? Is there another way to tackle this or something obvious I'm doing wrong here?
Any help you can offer would be appreciated as I've run out of things to try!
Related
I have strange problem when accessing X509Store from IIS. I can't look them up.
If I access both the rp cert and ca cert from powershell both are there,
dir cert: -Recurse | Where-Object { $_.Thumbprint -like "thumprintstring" }
I have checked that the thumbprints don't have a hidden char in the beginning of thumbprint
I have set that the certificates are exportable when I install them
I have for the moment set it accessable for everyone(its a certificate to a test server) in certficate
store
This is code I use
StoreLocation location = certificateConfig.UseCurrentUserStoreLocation ? StoreLocation.CurrentUser : StoreLocation.LocalMachine;
using (var clientCertStore = new X509Store(StoreName.My, location))
{
clientCertStore.Open(OpenFlags.ReadOnly);
//Search for the client cert
X509Certificate2 rpCert = GetCertByThumbprint(clientCertStore, certificateConfig.RpCertThumbprint);
if (rpCert == null)
{
throw new InvalidOperationException("No rp cert found for specified thumbprint #" + certificateConfig.RpCertThumbprint +"# "+location);
}
ClientCertificates.Add(rpCert);
}
<snip>
private X509Certificate2 GetCertByThumbprint(X509Store certStore, string thumbprint)
{
var certs = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
return certs.Count > 0 ? certs[0] : null;
}
The rpcert is always null whatever i try.
Do I need another way to open up the store from IIS?
Any ideas or suggestions? What am I missing?
The problem was not what I expected. The config read from enviromentvariables that had been deleted so they didnt show in enviromentvariables and the server had not been restarted. And the deleted ones had most likely the bad character infront of the thumbprint.
Restarting iis doesn't solve this since the network service account doesnt reread these when already loggedon.
Follow up question: Is possible to relogin in network service account without restarting the server?
I've put my certificates in LocalMachine/My and want to access them thusly:
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var x509Certificate2Collection = store.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false);
var myCert = x509Certificate2Collection.Count > 0 ? x509Certificate2Collection[0] : null;
store.Close();
This works fine on my local cluster, but not on my standalone one.
The certificate I'm trying to access has NETWORK SERVICE in ACL for private keys.
I'd assume it's a permissions issue. Am I missing something obvious here?
How can I debug my standalone cluster?
The above code works fine. My issue was that my thumbprint was "" because I hadn't set my config overrides correctly!
Having an App-service in Azure, and working on the AzureServiceManagementAPI, I was downloading the file that contains the managememnt certificate for each subscription.
Any how using the certificate string from the file I'm trying to create a X509Certificate2 object.
string cerStr = subscription.Attribute("ManagementCertificate").Value;
X509Certificate2 x509 = new X509Certificate2(Convert.FromBase64String(cerStr), string.Empty, X509KeyStorageFlags.MachineKeySet)
The constructor of X509Certificate2 throw an exception
Access denied.
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32
hr) at
System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[]
rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet,
SafeCertContextHandle& pCertCtx) at
System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[]
rawData, Object password, X509KeyStorageFlags keyStorageFlags)
Since no one has answered this questions, I will try and have go at it. Please correct me if I am wrong, but the problem I think is the following line of code:
new X509Certificate2(Convert.FromBase64String(cerStr), string.Empty, X509KeyStorageFlags.MachineKeySet)
This line of code will try to add a new certificate to the certificate store of the virtual machine. All certificates used by the runtime, needs to be hosted in a store somewhere. This is not a good idea because the certificate store of the virtual machine hosting the app service is nothing that you should be storing anything in, it's part of the infrastructure which is not of your concern when you are working with app services.
What you need to do is to upload the certificate through the azure portal instead (if they are not already there). I ended up reusing a SSL certificate already in place for this purpose. When this is done, you can retreive that certificate in code. You will need to add a new App Setting under "Application Settings" key in the Azure portal for your app service, named WEBSITE_LOAD_CERTIFICATES. The value should be the thumbprint of the certificate.
To retrieve the cert, you should do something like this:
public async Task<X509Certificate2> GetCertificate(string certificateThumbprint)
{
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var cert = store.Certificates.OfType<X509Certificate2>()
.FirstOrDefault(x => x.Thumbprint == certificateThumbprint);
store.Close();
return cert;
}
You might be able to get thumbprint of the cert by navigating your subscription using the azure resource explorer https://resources.azure.com/
As Fredrik mentioned the issue is due to the code
X509Certificate2 x509 = new X509Certificate2(Convert.FromBase64String(cerStr), string.Empty, X509KeyStorageFlags.MachineKeySet)
In the Azure WebApp, if we try to use the certificate, we need to upload the certificate from the Azure portal. Add the WEBSITE_LOAD_CERTIFICATES with thumbprint value in the Azure WebApp application. More detail info please refer to blog.
Web application to access the certificate, snippet code from the blog
static void Main(string[] args)
{
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
// Replace below with your cert's thumbprint
“E661583E8FABEF4C0BEF694CBC41C28FB81CD870”,
false);
// Get the first cert with the thumbprint
if (certCollection.Count > 0)
{
X509Certificate2 cert = certCollection[0];
// Use certificate
Console.WriteLine(cert.FriendlyName);
}
certStore.Close();
}
I am trying to automate deployment of certificates, including managing permissions on the private key. Using this question, I have cobbled together some code that should update permissions for a certificate:
public static SetPermissionsResult SetPermissions(X509Certificate2 certificate, string userName)
{
var account = new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null);
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.MaxAllowed);
var newCertificate = store.Certificates.Find(X509FindType.FindBySerialNumber, certificate.SerialNumber, false)[0];
var rsa = newCertificate.PrivateKey as RSACryptoServiceProvider;
if (rsa == null)
{
return SetPermissionsResult.Failure;
}
rsa.PersistKeyInCsp = true;
var cspParams = new CspParameters(
rsa.CspKeyContainerInfo.ProviderType,
rsa.CspKeyContainerInfo.ProviderName,
rsa.CspKeyContainerInfo.KeyContainerName)
{
Flags =
CspProviderFlags.UseExistingKey
| CspProviderFlags.UseMachineKeyStore,
CryptoKeySecurity =
rsa.CspKeyContainerInfo.CryptoKeySecurity,
KeyNumber = (int)rsa.CspKeyContainerInfo.KeyNumber/*,
KeyPassword = password*/
};
cspParams.CryptoKeySecurity.AddAccessRule(
new CryptoKeyAccessRule(account, CryptoKeyRights.GenericRead, AccessControlType.Allow));
using (var rsa2 = new RSACryptoServiceProvider(cspParams))
{
}
return SetPermissionsResult.Success;
}
}
On the line that reads using (var rsa2 = new RSACryptoServiceProvider(cspParams)) (where the new crypto provider is instantiated to persist the new access rule), I get a CryptographicException "Keyset does not exist".
I know from experience that this generally means that the current security context does not have permissions to access the primary key. To troubleshoot this possibility, I have done the following:
Figure out what the current user is with System.Security.Principal.WindowsIdentity.GetCurrent() in the Immediate window
Ensure that that user has Full control permission in the MMC snap-in for certificates (and double-checked that I'm looking at the right store for it)
Granted Full control to the Everyone user.
Tried creating the CspParameters object with and without the KeyPassword (you can see it commented there).
I am out of ideas. The certificate is a bogus self-signed test cert, so it's not a matter of other certs in the chain missing permissions. Any help would be appreciated.
UPDATE:
I've been able to get this code to execute by modifying some of the flags for the installation of the certificate that precedes this step. Now, the code executes, apparently successfully, but to no visible effect I can see in the MMC.
I resolved this issue, more or less. The code for managing permissions listed above is sound. What was problematic was the code for instiating the certificate before adding it. The common sense way to that looks like this:
var certificate = new X509Certificate2(bytes, password);
The problem with this is that it doesn't cause the private key to be persisted in the store, which is what the permissions code relies on (duh). To cause this to happen, you need to instantiate the certificate to be added to the store like this:
var certificate = new X509Certificate2(bytes, "password123", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
And add it like this:
using (var store = new X509Store(storeName, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.MaxAllowed);
store.Add(certificate);
}
FINAL POINT: the MMC snap-in is dicey about displaying the updated permissions, i.e., it may not display the new permissions even though they actually have changed. I reached a point where I had everything working perfectly, but didn't realize it because the MMC was showing me different. Closing the MMC and reopening forces a refresh if you're doubting what you're seeing.
I am building an ACS protected Azure WCF service that will require clients to authenticate via a certificate.
I would like the client (and the server) to load their respective password certs from the X509Store instead of from the file system.
I am using this code:
private static X509Certificate2 GetCertificate(string thumbprint)
{
var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
thumbprint, false);
certStore.Close();
if (certCollection.Count == 0)
{
throw new System.Security.SecurityException(string.Format(CultureInfo.InvariantCulture, "No certificate was found for thumbprint {0}", thumbprint));
}
return certCollection[0];
}
Problem is, it's not loading the private key which it needs for authentication. I have tried to modify the return statement to this:
return new X509Certificate2(certCollection[0].Export(X509ContentType.Pfx, "password"));
However, this fails with a CryptographicException "The spcecified network password is incorrect".
Edit:
The .Export() method works properly if you don't pass the password argument in.
Any help on this?
When you export, the password you provide is the password you want to use for the exported file, it's not the password for the source certificate.
I'm not sure what you can do with X509Store and password-protected certs because the password should be supplied to the X509Certificate constructor and you get already-instantiated objects out of the store.
I think you can just get the raw data from the cert you want and construct a new one with the password you want. For example:
X509Certificate2 cert = new X509Certificate2(certCollection[0].GetRawCertData, password);
I would also suggest you try to use SecureString when dealing with passwords (but that's a different bag of worms...)
I used the Export without the 'password' parameter and it worked without issue.
When the cert was imported into the certificate store, I think the key has to be marked as "exportable" otherwise I don't think you can export the private key..