I have managed to figure out if a cert in a x509Certificate2Collection is a certificate authority cert but how can I safely determine if it's a Root cert or an Intermediate cert please? Is the following safe enough?
var collection = new X509Certificate2Collection();
collection.Import("test.pfx", "password", X509KeyStorageFlags.PersistKeySet);
foreach (X509Certificate2 cert in collection)
{
var basicConstraintExt = cert.Extensions["2.5.29.19"] as X509BasicConstraintsExtension;
if (basicConstraintExt != null)
{
Log.Debug($" Subject is: '{cert.Subject}'");
Log.Debug($" Issuer is: '{cert.Issuer}'");
if (basicConstraintExt.CertificateAuthority)
{
Log.Debug("I am a CA Cert.");
if (cert.Subject == cert.Issuer)
{
Log.Debug("My Subject matches Issuer.");
}
else
{
Log.Debug("My Subject does not match Issuer.");
}
Log.Debug(cert.Verify() ? "I verify" : "I do not verify");
}
else
{
Log.Debug("I am not a CA Cert.");
}
}
}
Results:
Displaying Cert #1 in collection
********************************
Subject is: 'CN=Intermediate-CA, DC=test, DC=lan'
Issuer is: 'CN=Root-CA, DC=test, DC=lan'
- I am a CA Cert.
- My Subject does not match Issuer.
- I do not verify
Displaying Cert #2 in collection
********************************
Subject is: 'CN=Root-CA, DC=test, DC=lan'
Issuer is: 'CN=Root-CA, DC=test, DC=lan'
- I am a CA Cert.
- My Subject matches Issuer.
- I do not verify
Not sure if this helps with Kestrel, but I would try the code below.
We will use X509Chain class to construct and validate the chain.
var collection = new X509Certificate2Collection();
collection.Import("test.pfx", "password");
var chain = new X509Chain();
chain.ChainPolicy.ExtraStore.AddRange(collection);
// untrusted root error raise false-positive errors, for example RevocationOffline
// so skip possible untrusted root chain error.
chain.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;
// revocation checking is client's responsibility. Skip it.
chain.RevocationMode = X509VerificationFlags.NoCheck;
// build the chain.
Boolean isValid = chain.Build(collection[0]);
// explore chain.ChainElements collection. First item should be your leaf
// certificate and last item should be root certificate
All this stuff is located in System.Security.Cryptography.X509Certificates namespace. In this code piece, I'm assuming that first certificate in PFX is leaf certificate (it is in 99% cases unless someone tries to ignore standards). By exploring chain.ChainElements collection, you can find issues with every certificate in the chain.
Related
I have a file (.p12) that contains 3 certificates (chained together) password-protected, that i have installed on my store.
I'm trying to load them to my code.
The way I load them from the file is like this:
var clientCert = new X509Certificate2(#"myfile.p12", "mypassword");
How can i achieve the same result while loading them from the store?
I've tried:
var computerCaStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
computerCaStore.Open(OpenFlags.ReadOnly);
var certificates = computerCaStore.Certificates.OfType<X509Certificate2>().ToList();
var certFromStore = certificates.Single(c => c.Thumbprint == thumbprintMerchant);
var newCert = new X509Certificate2(certFromStore.RawData, "mypassword");
certFromStore should be equivalent to clientCert, the last line is what's breaking you.
The RawData property on X509Certificate2 returns the DER-encoded value for the certificate, not the original file bytes. A certificate does not have a private key, so the last line strips it away. Your question had previously mentioned a TLS exception, and that is because your cert no longer has a private key.
If certFromStore.HasPrivateKey is false, then whatever you did to put the certificate into the store didn't work the way you think it did. It's pretty unusual for a certificate with a private key to be in the Root store.
I have ASP.NET MVC web site which I configured to authenticate through Active Directory Federation Service. Everything worked fine until I tried to enable token encryption. As usual, I created one more self-signed certificate on IIS, added it to Trusted Root authorities on my web server and ADFS server and run application to veryfy how it works.
My application correctly redirected me to ADFS service page to enter credentials. But when I submit my login and password, I immediately get "An error occured" message on the same login page with not very useful details section:
Activity ID: 00000000-0000-0000-b039-0080010000e4
Relying party: [My relying party name]
Error time: Fri, 21 Oct 2016 18:48:24 GMT
Cookie: enabled
User agent string: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36
I don't get redirected to my web site after that and Network panel doesn't contain any requests.
But I discovered, that if I add the following setting into my web site's web.config, it starts working again:
<certificateValidation certificateValidationMode="None" />
So the error must be related to the fact that my certificate is self-signed. But I have added it to trusted root authorities both on web server and ADFS server (as well as few other "suspicious" certificates).
Does anybody have an idea what could be missing and what can I do to make my test environment work with self-signed certificates, while validating certificate chain?
It appeared that to resolve an error it was enough to add ADFS Token Signing certificate as Trusted Root Certification Authority on my web server.
PS: I'm not sure why token signing certificate chain validation didn't raise errors when encryption was disabled and what relation does it have to encryption at all, but the fact is that it helped for both environments we've used for testing.
I do something similar with an api handler that acts as a pass through and must interrogate certs.
Something that may help your troubleshooting.
Set the cert validation callback to something like:
// validate server cert
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
Then in the validation method you can interrogate the chain:
private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// default validation bool to false
var isValid = false;
// If the certificate is a valid, signed certificate, return true to short circuit any add'l processing.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
else
{
// cast cert as v2 in order to expose thumbprint prop
var requestCertificate = (X509Certificate2)certificate;
// init string builder for creating a long log entry
var logEntry = new StringBuilder();
// capture initial info for the log entry
logEntry.AppendFormat("Certificate Validation Error - SSL Policy Error: {0} - Cert Issuer: {1} - SubjectName: {2}",
sslPolicyErrors.ToString(),
requestCertificate.Issuer,
requestCertificate.SubjectName.Name);
//init special builder for thumprint mismatches
var thumbprintMismatches = new StringBuilder();
// load valid certificate thumbs for comparison later
var validThumbprints = new string[] { "thumbprint A", "thumbprint N" };
// else if a cert name mismatch then assume api pass thru issue and verify thumb print
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
{
// compare thumbprints
var hasMatch = validThumbprints.Contains(requestCertificate.Thumbprint, StringComparer.OrdinalIgnoreCase);
// if match found then we're valid so clear builder and set global valid bool to true
if (hasMatch)
{
thumbprintMismatches.Clear();
isValid = true;
}
// else thumbprint did not match so append to the builder
else
{
thumbprintMismatches.AppendFormat("|Thumbprint mismatch - Issuer: {0} - SubjectName: {1} - Thumbprint: {2}",
requestCertificate.Issuer,
requestCertificate.SubjectName.Name,
requestCertificate.Thumbprint);
}
}
// else if chain issue, then iterate over the chain and attempt find a matching thumbprint
else if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors) //Root CA problem
{
// check chain status and log
if (chain != null && chain.ChainStatus != null)
{
// check errors in chain and add to log entry
foreach (var chainStatus in chain.ChainStatus)
{
logEntry.AppendFormat("|Chain Status: {0} - {1}", chainStatus.Status.ToString(), chainStatus.StatusInformation.Trim());
}
// check for thumbprint mismatches
foreach (var chainElement in chain.ChainElements)
{
// compare thumbprints
var hasMatch = validThumbprints.Contains(chainElement.Certificate.Thumbprint, StringComparer.OrdinalIgnoreCase);
// if match found then we're valid so break, clear builder and set global valid bool to true
if (hasMatch)
{
thumbprintMismatches.Clear();
isValid = true;
break;
}
// else thumbprint did not match so append to the builder
else
{
thumbprintMismatches.AppendFormat("|Thumbprint mismatch - Issuer: {0} - SubjectName: {1} - Thumbprint: {2}",
chainElement.Certificate.Issuer,
chainElement.Certificate.SubjectName.Name,
chainElement.Certificate.Thumbprint);
}
}
}
}
// if still invalid and thumbprint builder has items, then continue
if (!isValid && thumbprintMismatches != null && thumbprintMismatches.Length > 0)
{
// append thumbprint entries to the logentry as well
logEntry.Append(thumbprintMismatches.ToString());
}
// log as WARN here and not ERROR - if method ends up returning false then it will bubble up and get logged as an ERROR
LogHelper.Instance.Warning((int)ErrorCode.CertificateValidation, logEntry.ToString().Trim());
}
// determine env
var isDev = EnvironmentHelper.IsDevelopment();
var isTest = EnvironmentHelper.IsTest();
// if env is dev or test then ignore cert errors and return true (reference any log entries created from logic above for troubleshooting)
if (isDev || isTest)
isValid = true;
return isValid;
}
NOTE: you'll need to disable/change some of the custom code - thumbprint stuff, logging etc.
Adding certificates to your CA trusted store only mean you trust the issuer of the certificate, which is the certificate itself in this case because it is a self-signed certificate. What is missing is that certificate validation performs chain-check and revocation check and either one of the two check failed for you. Please note that even if you trust a certificate, it still could have been revoked recently and thus shouldn't no longer be trusted. Therefore, revocation check is always necessary. For testing, disabling revocation check is one way. One ADFS side, you can disable revocation check per relying party. If the check happens on your own code, you can either disable check totally or use Stinky Towel's code to selectively allow some certificates only.
I am trying to secure my RESTful WebApi service with ssl and client authentication using client certificates.
To test; I have generated a self signed certificate and placed in the local machine, trusted root certification authorities folder and i have generated a "server" and "client" certificates.
Standard https to the server works without issue.
However I have some code in the server to validate the certificate, this never gets called when I connect using my test client which supplies my client certificate and the test client is returned a 403 Forbidden status.
This imples the server is failing my certificate before it reaches my validation code.
However if i fire up fiddler it knows a client certificate is required and asks me to supply one to My Documents\Fiddler2. I gave it the same client certificate i use in my test client and my server now works and received the client certificate i expect!
This implies that the WebApi client is not properly sending the certificate, my client code below is pretty much the same as other examples i have found.
static async Task RunAsync()
{
try
{
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(GetClientCert());
handler.ServerCertificateValidationCallback += Validate;
handler.UseProxy = false;
using (var client = new HttpClient(handler))
{
client.BaseAddress = new Uri("https://hostname:10001/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var response = await client.GetAsync("api/system/");
var str = await response.Content.ReadAsStringAsync();
Console.WriteLine(str);
}
} catch(Exception ex)
{
Console.Write(ex.Message);
}
}
Any ideas why it would work in fiddler but not my test client?
Edit: Here is the code to GetClientCert()
private static X509Certificate GetClientCert()
{
X509Store store = null;
try
{
store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Integration Client Certificate", true);
if (certs.Count == 1)
{
var cert = certs[0];
return cert;
}
}
finally
{
if (store != null)
store.Close();
}
return null;
}
Granted the test code does not handle a null certificate but i am debugging to enssure that the correct certificate is located.
There are 2 types of certificates. The first is the public .cer file that is sent to you from the owner of the server. This file is just a long string of characters. The second is the keystore certificate, this is the selfsigned cert you create and send the cer file to the server you are calling and they install it. Depending on how much security you have, you might need to add one or both of these to the Client (Handler in your case). I've only seen the keystore cert used on one server where security is VERY secure. This code gets both certificates from the bin/deployed folder:
#region certificate Add
// KeyStore is our self signed cert
// TrustStore is cer file sent to you.
// Get the path where the cert files are stored (this should handle running in debug mode in Visual Studio and deployed code) -- Not tested with deployed code
string executableLocation = Path.GetDirectoryName(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);
#region Add the TrustStore certificate
// Get the cer file location
string pfxLocation = executableLocation + "\\Certificates\\TheirCertificate.cer";
// Add the certificate
X509Certificate2 theirCert = new X509Certificate2();
theirCert.Import(pfxLocation, "Password", X509KeyStorageFlags.DefaultKeySet);
handler.ClientCertificates.Add(theirCert);
#endregion
#region Add the KeyStore
// Get the location
pfxLocation = executableLocation + "\\Certificates\\YourCert.pfx";
// Add the Certificate
X509Certificate2 YourCert = new X509Certificate2();
YourCert.Import(pfxLocation, "PASSWORD", X509KeyStorageFlags.DefaultKeySet);
handler.ClientCertificates.Add(YourCert);
#endregion
#endregion
Also - you need to handle cert errors (note: this is BAD - it says ALL cert issues are okay) you should change this code to handle specific cert issues like Name Mismatch. it's on my list to do. There are plenty of example on how to do this.
This code at the top of your method
// Ignore Certificate errors need to fix to only handle
ServicePointManager.ServerCertificateValidationCallback = MyCertHandler;
Method somewhere in your class
private bool MyCertHandler(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors error)
{
// Ignore errors
return true;
}
In the code you are using store = new X509Store(StoreName.My, StoreLocation.LocalMachine);.
Client certificates are not picked up from LocalMachine, you should instead use StoreLocation.CurrentUser.
Checking MMC -> File -> Add or Remove Snap-ins -> Certificates -> My user account you will see the certificate that fiddler uses. If you remove it from My user account and only have it imported in Computer account you will see that Fiddler can not pick it up either.
A side note is when finding certificates you also have to address for culture.
Example:
var certificateSerialNumber= "83 c6 62 0a 73 c7 b1 aa 41 06 a3 ce 62 83 ae 25".ToUpper().Replace(" ", string.Empty);
//0 certs
var certs = store.Certificates.Find(X509FindType.FindBySerialNumber, certificateSerialNumber, true);
//null
var cert = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x => x.GetSerialNumberString() == certificateSerialNumber);
//1 cert
var cert1 = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x =>
x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));
try this.
Cert should be with the current user store.
Or give full rights and read from a file as it is a console application.
// Load the client certificate from a file.
X509Certificate x509 = X509Certificate.CreateFromCertFile(#"c:\user.cer");
Read from the user store.
private static X509Certificate2 GetClientCertificate()
{
X509Store userCaStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindBySubjectName, "localtestclientcert", true);
X509Certificate2 clientCertificate = null;
if (findResult.Count == 1)
{
clientCertificate = findResult[0];
}
else
{
throw new Exception("Unable to locate the correct client certificate.");
}
return clientCertificate;
}
catch
{
throw;
}
finally
{
userCaStore.Close();
}
}
I am trying to find where the certificate is stored on my local machine and then as well as our dev servers. I can go Run -> MMC -> File - > Add/Remove SnapIns and select certificates and Current User and see my personal certificates. However, I am trying to utilize this code for an HttpWebRequest and I cannot find the url.
string certPath = #"e:\mycertificate.cer"; //This Value
X509Certificate myCert = X509Certificate.CreateFromCertFile(certPath);
request.ClientCertificates.Add(myCert);
In another area we set up a proxy and do it like this.
proxy.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, CertificateName);
So obviously a little different implementation and I am unsure as to where/how to find the location to fill in for the first example.
Solution that worked for me
public WebRequest GetWebRequest(string address)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(address);
X509Certificate myCert = null;
X509Store store = new X509Store("My");
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 mCert in store.Certificates)
{
if (mCert.FriendlyName.Contains("certname"))
{
myCert = mCert;
}
}
if (myCert != null) { request.ClientCertificates.Add(myCert); }
return request;
}
Assuming like you want to pick a certificate somehow and not really care if it is from file or not. In this case you can use certificate store object and find one you need (i.e. by thumbprint). Check out this Get list of certificates from the certificate store in C# and MSDN article on X509Store.Certificates which contains sample too:
X509Store store = new X509Store("My");
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 mCert in store.Certificates){
//TODO's
}
I have a Windows certification authority that I am using to issue client authentication certificates via .net / c#. I have been able to successfully get it to issue certificates programmatically by calling the certification authority's API through COM. I issue a new certificate when I set up a client.
At runtime, these clients attach the certificates to requests to my server. How can I verify programmatically that an X509Certificate2 was signed by the root certificate of my certificate authority (and reject certificates signed by any other source)?
I've done this a lot. Here's some easy code you can use.
The part in the if (!isChainValid) block is to make a pretty error message. You don't have to use that if you don't want, but you should throw an error if the chain cannot be built. The chain elements are necessary to check for your root.
X509Certificate2 authority = GetAuthorityCertificate();
X509Certificate2 certificateToValidate = GetCertificateToValidate();
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
chain.ChainPolicy.VerificationTime = DateTime.Now;
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);
// This part is very important. You're adding your known root here.
// It doesn't have to be in the computer store at all. Neither certificates do.
chain.ChainPolicy.ExtraStore.Add(authority);
bool isChainValid = chain.Build(certificateToValidate);
if (!isChainValid)
{
string[] errors = chain.ChainStatus
.Select(x => String.Format("{0} ({1})", x.StatusInformation.Trim(), x.Status))
.ToArray();
string certificateErrorsString = "Unknown errors.";
if (errors != null && errors.Length > 0)
{
certificateErrorsString = String.Join(", ", errors);
}
throw new Exception("Trust chain did not complete to the known authority anchor. Errors: " + certificateErrorsString);
}
// This piece makes sure it actually matches your known root
var valid = chain.ChainElements
.Cast<X509ChainElement>()
.Any(x => x.Certificate.Thumbprint == authority.Thumbprint);
if (!valid)
{
throw new Exception("Trust chain did not complete to the known authority anchor. Thumbprints did not match.");
}
You can also use the built in method Verify() for X509Certificate2.
X509Certificate2 certificateToValidate = GetCertificateToValidate();
bool valid = certificateToValidate.Verify()
https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.verify.aspx
If you say you have a root (which is self-signed) certificate, then your only option is to keep this root certificate available on your server (without the private key of course) and perform certificate validation procedure against your root certificate. This is a mirrored situation to the web client validating server certificate chain.