Can't make BITS upload work with client certificate authentication - c#

I have an IIS extension for BITS with upload enabled on the server (Win2008) and a C# .NET4 client running as a windows service on the client machine (Win8.1)
The BITS upload without the certificate auth is working fine.
I have generated a self-signed root cert, added it to TrustedCA storage on both machines, generated child client and server certs, added them to the respective storages. Both are displayed as valid and trusted in the MMC console.
However, when I try to use client cert authentication (setting "Require client certificate" in the IIS and adding the certificate to the BITS job on the client), the client certificate does not seem to be received by IIS.
The code I'm using to set the cert on client is:
var httpOptions = (IBackgroundCopyJobHttpOptions) job2;
httpOptions.SetClientCertificateByName(certificate.certLocation, certificate.certStoreName, certificate.certSubjectName);
The certLocation is
BG_CERT_STORE_LOCATION.BG_CERT_STORE_LOCATION_LOCAL_MACHINE
since the client certificate is installed into LocalMachine and the service is running as LocalSystem.
Additionally, right after I have called SetClientCertificateByName, I try to get it back:
string stName, stLocation;
var hash = new IntPtr();
httpOptions.GetClientCertificate(out loc, out stName, hash, out stLocation)
But it returns empty values.
I have tried to check the certificate presence via:
var store = new X509Store(certificate.certStoreName, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, certificate.certSubjectName, true)
and the certificate is indeed there.
I have also tried using WinHttpCertCfg.exe (no errors, but nothing changed) and FindPrivateKey.exe (says "Unable to obtain private key file name") to grant the access to the cert's private key to the LocalSystem account.
I'm out of ideas on this one. Any insight would be very appreciated.

Related

C# X509Store Access Web Hosting

I have an SSL cert stored in the Web Hosting Folder of the Certificate Store. I cannot seem to be able to access this store from C#. Does anyone know how to do this?
X509Store store = new X509Store("Web Hosting");
store.Open(OpenFlags.ReadOnly);
var t = store.Certificates.GetEnumerator();
while (t.MoveNext())
{
//this is always empty
}
Additional Detail
I need this cert for a gRPC service that I am writing. gRPC requires a certificate for the SSL connection. In the mean time aka development I am using Let's Encrypt to generate the certificate. When the cert was generated the cert was put into the Web Hosting folder of the cert store.
It turns out you can drag and drop the certs to a different location in the cert manager. I relocated the cert to the Personal folder and I was able to access it by:
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
Try removing the space; e.g., try the name as webhosting (no space)
In PowerShell, to display certs in this store location:
dir cert:\localmachine\webhosting

can't use certificates installed in the store as server-side certificates

I'm developing an application in UWP which acts as a server and i made a self-signed certificate and installed it on both the server and client, I checked the certificate if it has a private key and it does, and when I retrieve it from the store programmatically it loads the private key, but for some reason when the authentication handshake starts the server says that "the credentials supplied to the package were not recognized", the only way it works is when I get the certificate as a PFX from a folder...
this one works:
Selectedcert = new X509Certificate2("LocalCertificate.pfx","password", X509KeyStorageFlags.UserKeySet);
this doesn't:
X509Store store = new X509Store(StoreName.Root,StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection cers =
store.Certificates.Find(X509FindType.FindByIssuerName, "localhost", false);
X509Certificate2 Selectedcert = null;
foreach (var c in cers)
if (c.HasPrivateKey && c.PrivateKey != null)
Selectedcert = c;
I checked the manifest and the application has Capabilities to the Shared User Certificates also I checked the certificate permissions and the user does have permissions to read the private-key
Edit:
also i have Tried both StoreName.My and StoreName.Root
i tried the same code in a console application and the authentication handshake is ok, so the problem is with the UWP restrictions, it doesn't let the application to use the private key or something like that
i dont know how to get an X509Cert in UWP if someone knows please inform me, thank you :)
I think the issue is that UWP apps are running in the sandbox so that it has limitations when trying to accessing the localhost. So in your scenario, you could not get the certificates.
Update:
The sharedUserCertificates works for UWP APIs like UserCertificateStore Class and CertificateStores Class. You could check the official sample -UserCertificateStore to see how these APIs could get the certificates from the certificates store.

C#: How to invoke a SOAP service requiring client-side authentication with certificates installed at runtime

I have an application deployed to IIS that needs to invoke a SOAP service. It's using WCF from .NET Framework. That SOAP service requires that requests made be authenticated with a client-side certificate which is given at runtime. Admin users of the application can update the used certificate in a back-office. The goal is for autonomy and the certificate lifecycle management be independent from IIS or the underlying system so using the machine certificate store is not an option.
Here's the initial code:
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var client = new ServiceReference1.myClient(binding, new EndpointAddress(serviceUrl));
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
var certificate = new X509Certificate2(certificateBinary, certificatePassword);
client.ClientCredentials.ClientCertificate.Certificate = certificate;
//use the client
var result = client.myMethod(new ServiceReference1.MethodRequest());
certificateBinary is the result of loading a PFX file containing the full certificate chain (client certificate, intermediate and root CAs) and certificatePassword the password used to create that file.
But the request is rejected by the server. From looking at Wireshark, it seems only the client-certificate is sent. This is different from what happens if we install the PFX on the machine store which works fine.
So the next step I tried was to install the certificates at runtime. First load them:
X509Certificate2Collection collection = new X509Certificate2Collection();
try {
collection.Import(ssCertificateFile, ssPassword, X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
}
Then identify what kind of certificates they are and finally installing them on the current user store:
private static void InstallCertificates(X509Certificate2Collection clientCerts, X509Certificate2Collection intermediateCAs, X509Certificate2Collection RootCAs) {
using (X509Store personalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser)) {
personalStore.Open(OpenFlags.ReadWrite);
personalStore.AddRange(clientCerts);
}
using (X509Store intermediateStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser)) {
intermediateStore.Open(OpenFlags.ReadWrite);
intermediateStore.AddRange(intermediateCAs);
}
using (X509Store trustedCAsStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) {
trustedCAsStore.Open(OpenFlags.ReadWrite);
trustedCAsStore.AddRange(rootCAs);
}
}
This fails when installing the root CAs in trusted root certificate authorities (StoreName.Root) with:
System.Security.Cryptography.CryptographicException: The request is not supported.
at System.Security.Cryptography.X509Certificates.X509Store.Add(X509Certificate2 certificate)
at System.Security.Cryptography.X509Certificates.X509Store.AddRange(X509Certificate2Collection certificates)
at OutSystems.NssCertificationExtremeXP.CssCertificationExtremeXP.InstallCertificates(CertificatesClassifier certificates)
so only the client certificate and the intermediate CAs get installed and at runtime apparently this is not enough.
But if I take the exact same code as it is and run it with in a separate C# project, when installing the root CAs there's a confirmation dialog
and if I click OK the certificate is installed.
From here and here, it looks like every time we want to install something in the user Trusted Root Certificate Authorities, that prompt happens and it probably is not supported on the context of a non-GUI usage.
The problem is that even if I don't install the root CA in the store, I can successfully call the SOAP service when running this stand-alone app, only when running under IIS this fails.
Does anyone know why this happens and how to solve it?

How to install a certificate to personal store without password?

I am kind of new to certificates and all that stuff.
I have a web application that sends a https request by iframe (it has to be https due to the application itself is also https) to a .NET application running on the client which has an HttpListener listening on https://localhost:[port] so the client app can receive a "do something now" from a browser click.
When I install the client app, I install the *.crt file to the Root-Store and bind it to our port:
X509Certificate2 certificateFromCrtFile = new X509Certificate2(X509Certificate2.CreateFromCertFile(crtPath));
X509Store rootStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
rootStore.Open(OpenFlags.ReadWrite);
rootStore.Add(certificateFromCrtFile);
rootStore.Close();
string crtThumbprint = certificateFromCrtFile.Thumbprint;
string netshParams = string.Format("http add sslcert ipport=0.0.0.0:[port] certhash={0} appid={{{1}}}", crtThumbprint, Guid.NewGuid());
Utilities.StartProcess("netsh.exe", Environment.SystemDirectory, netshParams, true, true);
Now, before that, I have to install the certificate also in the personal store because my "server" in that case is the HttpListener on the same client.
Okay, so I could do it the following way:
X509Certificate2 certificateFromPfxFile = new X509Certificate2(pfxPath, "pwd");
X509Store personalStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
personalStore.Open(OpenFlags.ReadWrite);
personalStore.Add(certificateFromPfxFile);
personalStore.Close();
In this case I have to write the *.pfx password in clear text in code.
And if I understood correctly, with this password you can easily get the private key, right?
When storing in PFX the private key is encrypted with your password. So when you attempt to install it to a personal store to indicate your ownership of this certificate, you have to present the password. That's mandate.
The trusted root authority is different, as you don't own the certificates. You just install the crt files to say that you trust whoever owns them.

How to create a certificate to use with SslStream AuthenticateAsServer without importing

I'm lost in a twisty maze of certificates and private keys.
I am writing a server in C#. I want it to accept SSL connections. For test purposes, I want to generate a certificate for "localhost", and use that certificate for the server. Ideally I don't want to pollute my certificate store, so I just want a file, or files, on disk that the C# code can load to provide the certificate.
Everything I have tried (using cookbook approaches suggested by web searches) either gives me "The credentials supplied to the package were not recognized" or "The server mode SSL must use a certificate with the associated private key."
Is there a way to generate a certificate and private key, and subsequently to load them into an X509Certificate object without also loading them into my machine certificate store?
In the end, I ran the following to create a server.pfx file:
makecert.exe -r -pe -n "CN=localhost" -sky exchange -sv server.pvk server.cer
pvk2pfx -pvk server.pvk -spc server.cer -pfx server.pfx
Then I loaded it in code with:
certificate = new X509Certificate2("server.pfx", "password");
(I didn't actually hard code the password like that :-)
The trick was to know that I needed a pfx file, and that I needed to load it using the X509Certificate2 class, rather than X509Certificate.
Niki Loche method works.
If you get The specified network password is not correct., then you should try it without password in C#. It doesn't matter what your input password was in makecert.
certificate = new X509Certificate2("Server.pfx", "");
But if you want to use password (there is a reason, it's there :)), try changing pvk2pfx.exe command to:
pvk2pfx.exe" -pi password -pvk Server.pvk -spc Server.cer -pfx Server.pfx
and in C# enter:
certificate = new X509Certificate2("Server.pfx", "password");
Password must be the same as it is in creating cer file.
That did the trick for me. I hope it will help someone.
There are a number of tools that should let you act as your own CA and generate a certificate. XCA is one of them. There are also a number of methods using OpenSSL commands, for example.
Generating a self-signed certificate only may seem like the easiest option, but using a test CA (and a separate server certificate) may be worth it. This would allow you to import the test CA into the browser's store if needed to make the tests more realistic. It's not much more difficult with the right tools (e.g. XCA).
Once you have generated your server certificate and its private key, turn it into a PKCS#12 file (.p12/.pfx).
You should then be able to load it using X509Certificate2.import(...). (See example in this answer.)
I recently developed a SMTP server with TLS. I needed to install the server as a Windows Service on a Windows Server 2012 box. I used a Let's Encrypt SSL Certificate (* wildcard) for my domain. While developing "on the server" I had to run Visual Studio as Administrator for my code to work using "new X509Certificate("cert.pfx", "{password}") and it ran perfectly. However, once installed as a service, that scheme did not work. Turns out the simpler/safer way is to use X509Store. Here's the code that solved the problem...
private X509Certificate GetSslCertificate()
{
X509Certificate cert = null;
string certname = "*.mydomain.com";
try
{
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly);
foreach (X509Certificate certificate in store.Certificates)
{
if (certificate.Subject.ToLower().Contains(certname))
{
cert = certificate;
break;
}
}
}
catch (Exception) { /* Handle Exception */ }
return cert;
}
Also notice I used "LocalMachine" as the StoreLocation. You'll need to change to CurrentUser if that's where you installed your SSL Certificate.
If you have IIS, you can do it through the IIS Manager as detailed here:
http://blogs.msdn.com/b/ericnel/archive/2009/09/22/using-iis-to-generate-a-x509-certificate-for-use-with-the-windows-azure-service-management-api-step-by-step.aspx
I had the same issue when importing a machine certificate from the machine store (not the user store). I solved it by running Visual Studio as an administrator, as administrator privileges are required to access the private key of a machine store certificate.

Categories