My IIS (web server) requires client certificate and I need to check for certificate validity and read some information from and record in database (Audit)
I have following code
using System.Security.Cryptography.X509Certificates;
...
HttpClientCertificate cert = Request.ClientCertificate;
if (cert.IsPresent && cert.IsValid) {
X509Certificate2 cer = new X509Certificate2(cert.Certificate);
bool verified = cer.Verify();
...
AuditLog( ... );
}
cert.IsValid shows that certificate is valid. Do I need to instantiate X509Certificate2 object and re-check the validity of certificate (Why)?
If the certificate wasn't valid you wouldn't get this far. IIS should check that during the handshake, and abort the connection if invalid. All you need to do is verify that the identity represented by the Subject DN is authorized to be a client of this application.
Related
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.
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
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.
I have to write a tool which validates if a X509 certificate is valid or not (input = cert path / subject and password). How can I do that? I don't know much about certs...
Take a look at X509Certificate2.Verify()
In general, RFC 3280 includes almost complete instructions regarding how to perform validation, however those instructions are very non-trivial. Additionally you would need to read RFC 2560 (OCSP) and implement OCSP client.
For most tasks you will find our TElX509CertificateValidator component perfectly suitable. It checks certificate paths, CRL and OCSP revocation (and checks validity of CRLs and OCSP responses as well). It is flexible and powerful enough and lets you perform additional, deeper checks on each step. Also this component can work with both Windows certificate storages and any other certificates, certificate chains and storages that you might have in files or in memory.
X509Certificate2.Verify Method performs a X.509 chain validation using basic validation policy.
This method builds a simple chain for the certificate and applies the base policy to that chain. If you need more information about a failure, validate the certificate directly using the X509Chain object.
X509Certificate2 cert = GetX509Cert();
// Verify the certificate
bool isValid = cert.Verify();
But there are many factors that can affect the validity of an X.509 certificate, including the expiration date, the identity of the signer, and the chain of trust. So you may need to write your custom code for validation depending on your specific requirements if Verify is not enough for you. So you can write your own validation code:
public static bool ValidateCertificate(X509Certificate2 certificate)
{
// Check the certificate has not expired
if (certificate.NotAfter < DateTime.UtcNow)
{
return false;
}
// Performs a X.509 chain validation using basic validation policy
if (!certificate.Verify())
{
return false;
}
// Check that the certificate's subject contains a specific string
if (!certificate.Subject.Contains("CN=example.com"))
{
return false;
}
// Check that the certificate's issuer contains a specific string
if (!certificate.Issuer.Contains("CN=Root CA"))
{
return false;
}
// All checks passed, certificate is valid
return true;
}
You have to initialize a new instance of the X509Certificate2 class to validate it. There are many constructors that allow to create a new instance by the:
certificate's file path
PEM file path or PEM file content
byte array with binary (DER) encoded or Base64-encoded X.509 data or PKCS7 (Authenticode) signed file data
unmanaged handle
var cert = new X509Certificate2(#"C:\yourcertfile.crt");
// or
var cert = new X509Certificate2(#"C:\yourcertfile.crt", password);
// or
var cert = new X509Certificate2(#"C:\yourcertfile.crt");
// or
var cert = X509Certificate2.CreateFromPemFile(#"C:\yourcertfile.pem");
// or
var cert = X509Certificate2.CreateFromPem(File.ReadAllText(#"C:\yourcertfile.pem"));
You can also open the current user or current machine certificate store via X509Store and find certificate by subject name.
X509Store store = new X509Store("MY",StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Your Cert's Subject Name", false);
var cert = certs.Count > 0 ? certs[0] : null;
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);