c# X509Certificate2 add certificate how mark as exportable - c#

I have a .NET 4.0 program which is running localhost on port 9000.
I want to support SSL and have a .pfx certificate to import.
Because the program is running at multiple computers the programm itself is responsible
to store the certificate and register the its port.
When imported and registered by the program, everything works.
But when I reboot the computer the https connection does not work any.. more..
The event viewer gives the following error:
A fatal error occurred when attempting to access the SSL server
credential private key. The error code returned from the
cryptographic module is 0x8009030D. The internal error state is 10001.
and
An error occurred while using SSL configuration for endpoint
0.0.0.0:9000. The error status code is contained within the returned data.
I found some solutions which state that you need to mark the certificate as 'exportable' while importing
but I can't find how I do that in code.
Website 1 and Website 2
Storing the certificate:
String path = String.Concat(IOHelper.AssemblyPath, #"\certificate.pfx");
X509Certificate2 cert = new X509Certificate2(path, "password");
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
if(!store.Certificates.Contains(cert))
{
store.Add(cert);
}
Registering the host:
netsh http add sslcert ipport=0.0.0.0:9000 certhash=<cert hash> appid=<app id>

To mark a X509Certificate2 as exportable is actually quite simple. You add a third parameter (X509KeyStorageFlags.Exportable) which indicates that the certificate is exportable.
var cert = new X509Certificate2(path, "password", X509KeyStorageFlags.Exportable);

Further to Madeillei's answer, you can pass the following flags:
var cert = new X509Certificate2(path, "password", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable| X509KeyStorageFlags.MachineKeySet);
If you are storing the cert in the CurrentUser store rather than in the LocalMachine store, do the following:
var cert = new X509Certificate2(path, "password", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable| X509KeyStorageFlags.UserKeySet);
The Key set flags indicate the following:
//
// Summary:
// Private keys are stored in the current user store rather than the local computer
// store. This occurs even if the certificate specifies that the keys should go
// in the local computer store.
UserKeySet = 1,
//
// Summary:
// Private keys are stored in the local computer store rather than the current user
// store.
MachineKeySet = 2,
The private key needs to be in the same place as the rest of the certificate for it to work.

Related

C# Connect to Ravendb keeps failing with error: This server requires client certificate for authentication, but none was provided by the client

No mather what i try, in an new .net 5 API project i cannot connect to RavenDB. It is given me an error:
This server requires client certificate for authentication, but none
was provided by the client.
The way I connect to the database:
byte[] certificateBytes = _certificateProvider.GetCertificate("CERT-NAME").Result;
string passphrase = _secretProvider.GetSecret("CERT-PASSPHRASE").Result;
X509Certificate2 certificate = new X509Certificate2(certificateBytes, passphrase);
var documentStore = new DocumentStore()
{
Urls = new[] { databaseOptions.DbUrl },
Conventions =
{
UseOptimisticConcurrency = true,
FindCollectionName = findCollectionName
},
Database = "DBNAME",
Certificate = certificate,
}.Initialize();
The certificate provider and the secret provider gets the data out of an Azure Key Vault. I validated the X509Certificate and it has the same thumbprint as I get in the admin panel of RavenDB. So that is loaded correctly. Also the certificate has the read/write rights on the requested database
But when i then do the following:
using (IAsyncDocumentSession session = documentStore.OpenAsyncSession())
{
var entity = await session.Query<EntityDTO>()
.SingleOrDefaultAsync();
}
Then i get the following error:
This server requires client certificate for authentication, but none
was provided by the client.
This while the certificate is given when initializing the document store. Anyone an idea how to continue with this as RavenCloud doesn't give more information then this?
In another project (.net core 3.1) the same code with the same certificate works. But could not find anything in the release notes of .net 5 what can cause this.
You need to load the certificate with the MachineKeySet flag.
X509Certificate2 certificate = new X509Certificate2(certificateBytes, passphrase, X509KeyStorageFlags.MachineKeySet);
This will solve the problem in Azure but your local dev machine might have a problem after this change. You need to allow full access for the user running RavenDB to the MachineKeySet folder on your local machine.
"C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys"
Please also note that if you don't dispose the certificate afterwards, the private keys pile up in that folder (a key is written to that folder every time the X509Certificate2 constructor opens the certificate).
In the RavenDB forum #iftah responded that you should set the storage flag to MachineKeySet because of the private key.
While that was not the case for me, it give me an direction to check. I was loading the certificate from Azure Key Vault using the CertificateClient and GetCertificateAsync. This only gives you the public key. So there never was an private key.
With the help of this post: KeyVault generated certificate with exportable private key. I've found out that i could use the SecretClient with GetSecretAsync. Then use Convert.FromBase64String() and that gives back also the private key.
So for the ones that have similar problems. Please verify when loading the certificate that you have the private key. My new code:
byte[] certificateBytes = Convert.FromBase64String(_secretProvider.GetSecret("CERT-NAME").Result);
X509Certificate2 certificate = new X509Certificate2(certificateBytes, string.Empty);
var documentStore = new DocumentStore()
{
Urls = new[] { databaseOptions.DbUrl },
Conventions =
{
UseOptimisticConcurrency = true,
FindCollectionName = findCollectionName
},
Database = "DBNAME",
Certificate = certificate,
}.Initialize();
And don't forget to load you certificate the following way if you want to have it working on Azure as #iftah suggested above:
X509Certificate2 certificate = new X509Certificate2(certificateBytes, string.Empty, X509KeyStorageFlags.MachineKeySet);

C# RestSharp Client Certificate getting exception "The SSL connection could not be established"

I programming in C# .Net Core 3.1 and .Net Standard library where this code is put.
I am not getting my http requests with RestSharp with a client certificate that are NOT installed on the server/computer i am not going to have access to the server/computer where this is going to be running later on..
My code:
// Create a RestSharp RestClient objhect with the base URL
var client = new RestClient(_baseAPIUrl);
// Create a request object with the path to the payment requests
var request = new RestRequest("swish-cpcapi/api/v1/paymentrequests");
// Create up a client certificate collection and import the certificate to it
X509Certificate2Collection clientCertificates = new X509Certificate2Collection();
clientCertificates.Import(_certDataBytes, _certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
// Add client certificate collection to the RestClient
client.ClientCertificates = clientCertificates;
//X509Certificate2 certificates = new X509Certificate2(_certDataBytes, _certificatePassword);
//client.ClientCertificates = new X509CertificateCollection() { certificates };
// Add payment request data
request.AddJsonBody(requestData);
var response = client.Post(request);
var content = response.Content;
I have done what ever i can find on internet and i have used the service test environment with there own generated certificate and generated my own in there production environment with the same result..
I have tested set TLS 1.1 or TLS 1.2 to test, they specified TLS 1.1 on there own curl examples.
Anyone got any idea?
Update 2020-01-08 - I have got information from the service tech support that they only see me sending one certificate but when i trying to debug and checking the X509Certificate2Collection i finding 3 certificate. But they saying they only see one?
IMHO, if the service you are trying to access is protected via SSL cert with expecting public key/private key, then without the cert, you cant access it. If thats the intent of the service to be protected, i think you may only do to check the service health check or at max, check if the service is accessible (without having the client cert)
But just as HTTPS, then you can try to download the cert from the browser if you can access the service URL from your browser, say, to access their meta or a simple GET or something. In chrome, you may see a lock symbol (security icon) just before to the URL. click that, and you may possibly download the public cert. You can install that on your machine and try that.
In case, if that service has a pub cert and the access doesnt need the client cert, then above option may work. and i hope you are installing the CERT and access that via your code from the account that has access to the cert. Lets say, you install the cert under the LocalSystem, then you may need administrator access from code/solution to access that cert path. Or install the cert under the current_user path.
I would Check if am able to access the service from browser as the first step.
This is, just from what i have understood from your question. again, if you can explain whether the service is a protected service based on public key/private key, you need to have the access/cert to use that service.
UPDATE:
I have tried few options on my end, by creating a demo api and a demo client to use client certs. With what i have understood from your query, i assume below may be some of your options to try out.
// --- using the cert path if you have the path (instead of the byte[])
var myCert = new X509Certificate2("<path to the client cert.cer/.pfx>", "secure-password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
X509CertificateCollection clientCerts = new X509CertificateCollection();
clientCerts.Add(myCert);
OR
var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine); //replace with appropriate values based on your cert configuration
certStore.Open(OpenFlags.ReadOnly);
//option 1 (ideally a client must have the cert thumbprint instead of a password
var cert = certStore.Certificates.Find(X509FindType.FindByThumbprint, "<cert Thumbprint>", false);
//option 2 (explore other options based on X509NameTypes
var cert = certStore.Certificates.OfType<X509Certificate2>()
.FirstOrDefault(cert => cert.GetNameInfo(X509NameType.DnsName, false) == "mycompany.dns.name.given in the cert");
client.ClientCertificates = new X509CertificateCollection(cert);
I recommend generate your own certificate
public static void MakeCert()
{
var ecdsa = ECDsa.Create(); // generate asymmetric key pair
var req = new CertificateRequest("cn=foobar", ecdsa, HashAlgorithmName.SHA256);
var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(10));
// Create PFX (PKCS #12) with private key
File.WriteAllBytes("c:\\temp\\mycert.pfx", cert.Export(X509ContentType.Pfx, "P#55w0rd"));
// Create Base 64 encoded CER (public key only)
File.WriteAllText("c:\\temp\\mycert.cer",
"-----BEGIN CERTIFICATE-----\r\n"
+ Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)
+ "\r\n-----END CERTIFICATE-----");
}
and then add to request
var client = new RestClient(url);
client.ClientCertificates = new X509CertificateCollection();
client.ClientCertificates.Add(new X509Certificate2("c:\\temp\\mycert.cer"));

Loading Azure certificate when NOT using custom domain names

As I understand if someone doesn't want to use a custom domain name and instead plans on using *.azurewebsite.net domain assigned to the website by Azure, then HTTPS is already enabled with a certificate from Microsoft(I know this is not as secure as using a custom domain name). How would be I able to load this certificate programmatically. Currently I use the following method to load a certificate from local machine or Azure :
public static X509Certificate2 LoadFromStore(string certificateThumbprint,bool hostedOnAzure)
{
var s = certificateThumbprint;
var thumbprint = Regex.Replace(s, #"[^\da-zA-z]", string.Empty).ToUpper();
var store = hostedOnAzure ? new X509Store(StoreName.My, StoreLocation.CurrentUser) : new X509Store(StoreName.Root, StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates;
var signingCert = certCollection.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (signingCert.Count == 0)
{
throw new FileNotFoundException(string.Format("Cert with thumbprint: '{0}' not found in certificate store. Also number of certificates in the sotre was {1}", thumbprint, store.Certificates.Count));
}
return signingCert[0];
}
finally
{
store.Close();
}
}
I assume the culprit is the following line of code :
new X509Store(StoreName.My, StoreLocation.CurrentUser)
because when I get an exception it tells me there is no certificate in the store although I pass the correct certificate Thumbprint(I grab the thumbprint from Chrome manually).
You will not be able to access this certificate programmatically in your WebApp as this certificate is not really installed on the Azure WebApp. Azure WebApps have a front-end server which does a "kind of" SSL Offloading so the WebApp actually never has access to this particular certificate. Why exactly you want to read this certificate though ?
Typically if there is a need for certificates in WebApps, you would install client certificates and pass them to services for Authentication as mentioned in https://azure.microsoft.com/en-us/blog/using-certificates-in-azure-websites-applications/ and those certificates you can access programmatically (code snippet mentioned in the same article)
But I am not sure what exactly you want to achieve by reading the server certificate

C# WCF communication with client certificate

i am developing very simple desktop (win forms) application, that comunicates with server via WCF service on HTTPS protocol.
Every client that use this application, can upload client certificate (unique for each client), wich shall be used to to communication.
What exactly i am trying to achieve:
1. user will "upload" certificate to my desktop application, application take the certificate and programmatically save the certificate to windows cert store, here is my code:
[SecurityCritical]
public CertificateInfoDto GetCertificateInfoAndImportToStore(string fullPath, SecureString password)
{
if (string.IsNullOrEmpty(fullPath))
{
throw new ArgumentNullException("fullPath");
}
if (!File.Exists(fullPath))
{
throw new ArgumentException(string.Concat("No file present on ", fullPath));
}
try
{
byte[] rawBytes = this.GetCertificateContent(fullPath);
var certificate = new X509Certificate2(rawBytes, password, X509KeyStorageFlags.Exportable);
this.EnsureImport(certificate);
}
// some error handling etc, and end of method.
[SecurityCritical]
private void EnsureImport(X509Certificate2 certificate)
{
X509Store store = null;
try
{
store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
}
In my application settings file, i store only certificate serial number.
2. Now, when i have uploaded certificate to windows store, i want to use it for wcf communication, so my code looks like:
var client = new SomeWcfServiceProxy();
client.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindBySerialNumber,
certificateSerialNumber);
And finally, here is my problem, when i try to call some method on proxy, ill get an exception:
System.ServiceModel.Security.SecurityNegotiationException: Could not establish secure channel for SSL/TLS with authority 'url (i changed this)'. ---> System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel.
But what confuses me, is that when i create proxy client like this:
var client = new SomeWcfServiceProxy();
var clientCertificate = new X509Certificate2(#"C:\U\BB\certificate.pfx", "password");
client.ClientCredentials.ClientCertificate.Certificate = clientCertificate;
Everything working like a charm!
So there is my question: I do not want to store clients certificate password anywhere, i want to once upload it via windows certificate store and then use it only from windows certificate store. Is this possible? Or i must store certificate password somewhere ( i do not know where, becouse i think it is not very secure and this certificates are very very confidential).
Thanks for any help :)
After all, i needed this:
var certificate = new X509Certificate2(rawBytes, password, X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
Now certificate is imported just right and can be used for communication without any problems

Using client certificate not in certificate store

I'm trying to authenticate myself against WebService using my client certificate, but, for some reasons (I explain), I don't want to load certificate from store, rather read it from disc.
The following:
// gw is teh WebService client
X509Certificate cert = new X509Certificate(PathToCertificate);
_gw.ClientCertificates.Add(ClientCertificate());
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
_gw.DoSomeCall();
returns always 403 - the Service doesn't authorize me. But, when I save that certificate into CertStore, it works. (As stated in MSDN.)
Is it possible to use certificate not in store?
(the reason is, that I got windows service(client) sometimes calling webservice(server), and after unspecified amount of time the service 'forgets' my certificates and doesnt authorize against server, with no apparent reason)
What type of file is PathToCertificate? If it's just a .cer file, it will not contain the private key for the certificate and trying to use that certificate for SSL/TLS will fail.
However, if you have a PKCS7 or PKCS12 file that includes the public and private key for the certificate, your code will work (you might need to use the overload that takes a password if the private key has one).
To test this, I went to http://www.mono-project.com/UsingClientCertificatesWithXSP and created my client.p12 file following those instructions. I also created a simple HTTPS server using HttpListener for testing.
Then I compiled the following program into 'client.exe' and run like:
client.exe https://<MYSSLSERVER>/ client.p12 password
where client.p12 is the PKCS12 file generated before and 'password' is the password I set for the private key of the certificate.
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
public class HttpWebRequestClientCertificateTest : ICertificatePolicy {
public bool CheckValidationResult (ServicePoint sp, X509Certificate certificate,
WebRequest request, int error)
{
return true; // server certificate's CA is not known to windows.
}
static void Main (string[] args)
{
string host = "https://localhost:1234/";
if (args.Length > 0)
host = args[0];
X509Certificate2 certificate = null;
if (args.Length > 1) {
string password = null;
if (args.Length > 2)
password = args [2];
certificate = new X509Certificate2 (args[1], password);
}
ServicePointManager.CertificatePolicy = new HttpWebRequestClientCertificateTest ();
HttpWebRequest req = (HttpWebRequest) WebRequest.Create (host);
if (certificate != null)
req.ClientCertificates.Add (certificate);
WebResponse resp = req.GetResponse ();
Stream stream = resp.GetResponseStream ();
StreamReader sr = new StreamReader (stream, Encoding.UTF8);
Console.WriteLine (sr.ReadToEnd ());
}
}
Let me know if you want me to upload the server code and the certificates used on both sides of the test.
The potential problem could be caching of SSL sessions (Schannel cache). Only first request negotiates the SSL handshake. Subsequent requests will use the same session ID and hope that the server accept it. If the server clears the SessionId, the requests will fail with 403 error. To disable local ssl session caching (and force SSL negotiation for each request) you have to open windows registry folder:
[HKEY_LOCAL_MACHINE][System][CurrentControlSet][Control][SecurityProviders][SCHANNEL]
and add the key named ClientCacheTime (DWORD) with value 0.
This issue is covered here:
http://support.microsoft.com/?id=247658
You have the potential for at least two problems...
First...
Your client certificate file cannot contain a private key unless it's accessed with a password. You should be using a PKCS #12 (*.pfx) certificate with a password so that your client has access to the private key. You client code will have to provide the password when opening the certificate as others have already posted. There are several ways to create this, the easiest is to use the following command-line to first generate the certificate, then use the MMC certificate manager to export the certificates private key:
Process p = Process.Start(
"makecert.exe",
String.Join(" ", new string[] {
"-r",// Create a self signed certificate
"-pe",// Mark generated private key as exportable
"-n", "CN=" + myHostName,// Certificate subject X500 name (eg: CN=Fred Dews)
"-b", "01/01/2000",// Start of the validity period; default to now.
"-e", "01/01/2036",// End of validity period; defaults to 2039
"-eku",// Comma separated enhanced key usage OIDs
"1.3.6.1.5.5.7.3.1," +// Server Authentication (1.3.6.1.5.5.7.3.1)
"1.3.6.1.5.5.7.3.2", // Client Authentication (1.3.6.1.5.5.7.3.2)
"-ss", "my",// Subject's certificate store name that stores the output certificate
"-sr", "LocalMachine",// Subject's certificate store location.
"-sky", "exchange",// Subject key type <signature|exchange|<integer>>.
"-sp",// Subject's CryptoAPI provider's name
"Microsoft RSA SChannel Cryptographic Provider",
"-sy", "12",// Subject's CryptoAPI provider's type
myHostName + ".cer"// [outputCertificateFile]
})
);
Second...
Your next problem is going to be server-side. The server has to allow this certificate. You have the right logic, but on the wrong side of the wire, move this line to the web server handling the request. If you cannot, you must then take the '.cer' file saved above to the server and add it to the server computer's trust list:
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
Do you need a password for the certificate? If so, there is a field for it in the constructor.
X509Certificate cert = new X509Certificate(PathToCertificate,YourPassword);

Categories

Resources