Access the self signed certificate details - c#

I have an asp.net mvc website deployed to iis. A self signed SSL certificate is used in order to secure the traffic. I would like to access this self signed certificate from my asp.net, probably in the startup class or something, in order to get the validity of the self signed certificate (i need this metric for something else).
How could i do that?
I would gladly post some code, or what I've tried so far, but sadly i have no clue where to start from!
I would really appreciate any help.
Edit
To rephrase my question, lets say i have an asp.net web service deployed to IIS, how do i access the certificates in that IIS, and retrieve their validity period (from with in the web service using c# code)

You can do this by opening the cert store and finding certs based upon search criteria. If it's a self signed cert that you created you should know something about it.
object value = "AcmeOrganization";
X509FindType findType = X509FindType.FindByIssuerName;
StoreName storeName = StoreName.My;
StoreLocation storeLocation = StoreLocation.CurrentUser;
var store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(findType, value, true);
if (certs.Count > 0)
{
return certs[0];
}
}
finally
{
store.Close();
store = null;
}
This will get you the cert you're looking for then you can call Verify which does chain validation. Other properties along with expiration will be available with the X509Certificate2 object.
certs[0].Verify()

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);

Access server certificate from ASP.NET (C#)

Using server variables(Request.ServerVariables["CERT_SERVER_ISSUER"]), I can get a string representing the Server Certificate Issuer used in the connection.
I would like to access the actual certificate (X509Certificate if possible), so that I can further inspect the certificate.
I want to validate the server certificate in my ASP.NET code, to make sure nobody has simply clicked "..proceed anyway". Specifically I want to check the CA Root.
The way I understand it - typically browsers will not present a client certificate - so:
HttpContext.Current.Request.ClientCertificate
will be null/empty... I'm looking for the Server Certificate, and if possible the full chain of the Server Certificate so I can check the CA Root.
You can obtain certificates from the certificate store, you can do it by subject name, thumbprint, or something else if you want. You'll need to determine which of these you have available - and change the "find type" in this example:
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Subject Name", false);
if (certs.Count > 0)
{
// do something with: certs[0];
};
store.Close();

How to determine if server has ssl certificate

I am creating a self-hosted owin server inside a windows service. I have implemented an ACME client to get a certificate from Let's Encrypt (to a domain given by the service's config.). However, if the service is run on a server which already has a certificate I do not need to request a new one. How can I determine, in code, if there is a certificate installed which applies for the domain set in the service's config?
The closest thing to a solution I found was to ignore existing certificates (if any) and always request a new one. Then when a certificate is received from Let's Encrypt, I save that certificate's serial to a file. On startup I then use the saved file (if any) to look for the existing certificate from the store:
public async Task<bool> NeedNewCertificate()
{
string certSerial = await AsyncFileHandler.ReadFileAsync(CERT_SERIAL_FILENAME);
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates.Find(X509FindType.FindBySerialNumber, certSerial, true);
foreach (var cert in certCollection)
{
if (cert.NotBefore <= DateTime.Now && cert.NotAfter > DateTime.Now.AddDays(30)) // Let's Encrypt certificates are valid 90 days. They recommend renewing certificates every 30 days.
return false;
}
}
return true;
}
That's a good enough way of doing it I think.
Alternatively you can do a search in the certificate store that matches the parameters that you specify: the domain that the certificate is applied to, expiry date, etc. If you find the one that is valid, then you can use it.
That is in fact most likely the definition for what you mean by "certificate is installed on the server".
The attribute you probably would like to check is "Subject", e.g. you wold like these that have "CN = mydomain.com". You can have wildcard certificates as well, so you will need to figure out what types of certificates can be installed.

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

Azure API The server failed to authenticate the request

I have a task ( I tried with worker role and to upload a console app and run the .exe) that should run once a day and gather Azure Metrics of some of my VMs. This works flawlessly locally but on a cloud service I get this Error:
Unhandled Exception: Microsoft.WindowsAzure.CloudException: ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and associated with this subscription. at Microsoft.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSucces ... etc.
The line where this happens is:
MetricDefinitionListResponse metricListResponse = metricsClient.MetricDefinitions.List(resourceId, null,
nspace);
This is part of my code:
string subscriptionId = "fc4xxxx5-835c-xxxx-xxx-xxxxxxx";
// The thumbprint of the certificate.
string thumbprint = "‎f5 b4 xxxxxxxx f7 c2";
// Get the certificate from the local store.
//X509Certificate2 cert = GetCertificate(StoreName.My, StoreLocation.LocalMachine, thumbprint);
//cert = GetCertificate(StoreName.My, StoreLocation.CurrentUser, thumbprint) ?? new X509Certificate2(("manageAzure.cer"));
var cert = new X509Certificate2(("manageAzure.cer"));
Console.WriteLine("Certificate is : " + cert);
// Create the metrics client.
var metricsClient = new MetricsClient(new CertificateCloudCredentials(subscriptionId, cert));
Console.WriteLine("metricsClient is : " + metricsClient);
// The cloud service name and deployment name, found in the dashboard of the management portal.
string cloudServiceName = "abms2-carlsberg";
string deploymentName = "abms2-carlsberg";
// Build the resource ID string.
string resourceId = ResourceIdBuilder.BuildVirtualMachineResourceId(cloudServiceName, deploymentName);
string nspace = "WindowsAzure.Availability";
// Get the metric definitions.
MetricDefinitionListResponse metricListResponse = metricsClient.MetricDefinitions.List(resourceId, null,
nspace);
I have placed the management certificate in my solution, and I load it from there (it is set to always copy) and is the same (and the same way) I use when I run it locally.
So what "certificate" is it complaining about "to authenticate"? I can't seem to see what the problem is. Any help would be greatly appreciated as I have used the whole afternoon on this!
PS: I am already running this in elevated mode!
For someone else that may have this issue, I have solved it as explained here in the bottom : (http://www.dinohy.com/post/2013/11/12/403-Forbidden-when-Use-Azure-Management-REST-API-on-Role-instance.aspx)
Download the publishsettings file from:https://manage.windowsazure.com/publishsettings/index?client=vs&schemaversion=2.0 (This is a XML file, you can open it with notepad)
Find ManagementCertificate property, copy the value to a string. That a Base64 encoded string, you can use this string create a certificate: string base64Cer="The value of ManagementCertificate "
Use this string create a certificate. var certificate = new X509Certificate2(base64Cer);
although this last step is not exactly like passing the string directly (as the string is too long and will cause an exception) but rather is as follows:
var cert = new X509Certificate2(Convert.FromBase64String(base64cer));
Hope this will help someone else in my position too.
At a guess... You are loading the certificate you use to authenticate yourself from a .cer file. That doesn't have the private key so can't be used to authenticate you. I suspect that locally you probably have the private key stored in your private certificate store, assuming you generated the cert on your machine, which would probably make it work locally.
In short, try using a pfx file instead of a cer. The pfx for has the private key in it. If you generated the cert on your machine with makecert you will initially only have the .cer file. Use the local certificate manager to find the certificate in your personal store, then export it to a pfx file and include the private key.
This method helped me...
public static X509Certificate2 GetCertificate(string certificateString)
{
if (string.IsNullOrEmpty(certificateString))
return null;
var certificateAsBytes = Convert.FromBase64String(certificateString);
var certificate = new X509Certificate2(certificateAsBytes);
return certificate;
}
Also i came up with another scenario. Error occurs when your subscription id's mismatch.
Certificate:
<Subscription
ServiceManagementUrl="https://management.core.windows.net"
Id="00000000-0000-0000-0000-000000000000" /* -- Subscription Id -- */
Name="Visual Studio Premium with MSDN"
ManagementCertificate="" />
I'm trying to get credentials like this
Code:
string subscriptionId = "11111111-1111-1111-1111-111111111111"; // Subscription Id...
var credentials = new CertificateCloudCredentials(subscriptionId, x509Certificate2); // Will be varied here...
Where as my subscription id are mismatched. So, received this exception when i try to authenticate my request using "Certificates."
Hope this helps...

Categories

Resources