How to store SSL / TLS certificates in a private store - c#

We have a c# app which comunicates with devices. By now we just install the certifciates in the windows store.
Now we want a behaviour like the "windows remote desktop". Once the we connect to the device we get the (self signed) certificate. Then we ask the user if he trust the certificate. This is quite easy with SslStream.
Initialize SSL stream:
SslStream sslStream = new SslStream(.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
Handler:
static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors != SslPolicyErrors.None)
{
string certificateHash = CreateHash(certificate);
//pseudo code -> go to database and check if the certificate is already trusted
if(database.TrustedCertificates.Contains(certificateHash))
{
return true;
}
//TODO ask for permission
bool isTrusted = true;
if (isTrusted)
{
//TODO add certificate (hash) to store
database.TrustedCertificates.Add(certificateHash);
}
}
return true;
}
We do not want to add the manually trusted certificates in the windows built in store. Instead we would have our very own certificate store in our database. Therefor the trusted certificate would only be valid for our application.
Now I'm wondering if it's best practice to store the trusted certificates in a own store rather than to add them to the windows certificate store (certlm.msc). Is it save enough to store them in the app's database (just make a table TrustedCertificates)? Do the certificates need to be hashed like passwords? Should this be avoided in any case and only use the windows certificate store?
Any thoughts?

Related

NET 6 Iterate X509 certificates from pfx file and identify Root CA certificate

I have an .NET 6 application needing to identify and extract the different certificates from a "child" (client) pfx file. The pfx file contains the root ca certificate and also the child (client) certificate. The certificates from the pfx file are iterated using the X509Certificate2Collection object:
var clientCertPfxFilePath = "xxx.pfx";
var certPw = "...";
X509Certificate2Collection certCollection = new X509Certificate2Collection();
certCollection.Import(clientCertPfxFilePath, certPw, X509KeyStorageFlags.PersistKeySet);
foreach (X509Certificate2 cert in certCollection)
{
// Check if "cert" is root ca certificate - how?
}
Alternatively the certificates can be iterated using the X509Chain object
var clientCertificate = new X509Certificate2(clientCertPfxFilePath, certPw);
X509Chain certChain = new X509Chain();
certChain.Build(clientCertificate);
foreach (var element in certChain.ChainElements)
{
// Check if "element" is root ca certificate - how?
}
I am looking for a solution how to programmatically identify which from the iterated certificates is the root ca certificate, with either one of the both above code snippets
For X509Chain the answer is easy... if chain.Build returned true then the chain ends in a trusted element, and therefore the last element will be a trusted root certificate. (If you are reading this in the far future then maybe X509Chain will have been enhanced to trust non-root anchors, but that would almost certainly require a different call pattern than was presented here.)
For certificates, detecting a self-signed certificate is hard, but you can check if a certificate is self-issued fairly easily:
bool selfIssued = cert.SubjectName.RawData.SequenceEquals(cert.IssuerName.RawData);
(That is, a self-issued certificate is one whose subject name and issuer name are encoded identically.)
A self-signed certificate is a self-issued certificate whose public key can validate the signature on the certificate. The signature verification isn't exposed through .NET, so it's hard to check, but many applications assume that self-issued and self-signed are the same anyways, so you can, too.
#bartonjs Unfortunately the solution with chain.Build does not work on a computer where no certificates were installed in the windows certificate store. The root ca certificate is available in the certificate chain after calling "chain.Build" only on the computers where the root ca is installed in the certificate store. On these computers the certificate chain has 2 elements, the client certificate and the root ca certificate. On the computers having no root ca installed in the certificate store the chain contains only 1 element after the "chain.Build" call (the client certificate). The root ca does not appear anymore, although using absolutely the same pfx file!

How to find certificate in X509 store without breaking on a certificate update

In a web service application I have this function to load a certificate by its Thumbprint:
public static X509Certificate2 GetCertificate(string thumbprint)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var t = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (t.Count == 0)
{
throw new Exception("Certificate collection is empty");
}
X509Certificate2 cert = t[0];
store.Close();
return cert;
}
string thumbprint is a setting in the web.config file.
As I understand the Thumbprint is calculated as a HASH form the entire certificate and therefore will change if the certificate is updated.
From X509FindType Enumeration I can see that there are around 15 different types of FindBy{..} is one better than the other?
What will be the best practice for finding the certificate in a way that my settings will survive a certificate update without the need to update web.config after the system administrator has updated the certificate?
What certificate update do you have in mind?
You see a certificate is signed by CA that issued this certificate. Any change to this certificate will result in breaking of the signature and therefore making the certificate untrusted.
Therefore the thumbprint of the certificate will never change. When you reissue the certificate (even if it will be on the same private key) it will have different thumbprint because it will be a new certificate (with new serial number, new validation intervall etc.).
In my opinion you don't need to change anything. A thumbprint is pretty unique way to identify a certificate. When it will be time to reissue the certificate you will schedule a maintenance, edit your config to set thumbprint of new certificate and restart your application to apply the changes.
If you have a unique enough subject name to search by (FindBySubjectName), that should be stable across cert renewals, so you won't have to update your code or config every time you renew a cert, which can be infrequent enough that everyone tends to forget all the pieces involved. If you put both the search type and search value in a config system that's at least as easy to maintain as the cert renewal, you might be able to have devops staff (non-developers), or end users take care of it.

x509 certificate not recognized in Windows server 2008 R2

I got one Digital certificate from NeutralUS CA. I have installed in my local system. I found the installed certificate under Personal Certificates in MMC and my application validated with this certificate. I just moved to my application to our production server and installed same certificate. here also i can see my certificate under personal certificates in MMC on windows server 2008 R2. But when I am trying to load certificate with serial number, it is showing the store certificates count is zero. Could you please tell me what would be the reason? Why it is not recognize from the Personal folder. i have only one certificate in in personal folder.
var clientCertStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
clientCertStore.Open(OpenFlags.OpenExistingOnly & OpenFlags.ReadOnly);
base.Data.ErrorLog.Append("Certificates Count : " + clientCertStore.Certificates.Count);
I just log the data into one text file... it is giving clientCertStore.Certificates.Count as 0.
I got one Digital certificate from NeutralUS CA. I have installed in my local system.
What is installed? Is the end entity cert installed and trusted? Or is it the NeutralUS CA?
I could not find the NeutralUS CA for download on the net. That's unusual.
Could you please tell me what would be the reason? Why it is not recognize from the Personal folder.
I suspect its a problem with accounts (but its just a guess). What account was the certificate installed under, and what account is the program running under?
I really despise how difficult Java and .Net make it to use a damn certificate. Here's the code I use to avoid wasting time with those damn stores. It allows you to load directly from the filesystem or an app bundle. It also does not use the hundreds of CAs and subordinates that Windows carries around.
static bool VerifyServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
try
{
String CA_FILE = "ca-cert.der";
X509Certificate2 ca = new X509Certificate2();
ca.Import(CA_FILE);
X509Chain chain2 = new X509Chain();
chain2.ChainPolicy.ExtraStore.Add(ca);
// Check all properties
chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
// This setup does not have revocation information
chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// Are there any failures from building the chain?
chain2.Build(new X509Certificate2(certificate));
if (chain2.ChainStatus.Length == 0)
return true;
// Verify the status is NoError
bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
Debug.Assert(result == true);
return result;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return false;
}
And I still have not figured out how to set the original X509Chain chain that comes in as a parameter to the X509Chain chain2 that I want to use before the callback VerifyServerCertificate is invoked.

How to validate a certificate?

I am just starting a project where by I will need to connect, using FTPS, to a 3rd party server. They are to provide us with their certificate I don't have it yet, so I am going to create our own development environment to get started. I am using the Alex FTPS Client as my starting point (http://ftps.codeplex.com/)... but am also open to using WinSCP as a different client.
My question(s) below is - how do I validate a certificate in .Net?
Steps Done So Far
Installed and configured FileZilla Server
created a self signed certificate using FileZilla
3. In the Alex FTPS Client, they have a callback with this signature
private static bool ValidateTestServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// what goes here?
}
My (Various Questions) are:
What is suppose to go in the method?
I only have a modest understanding of certificates. Is the FileZilla self generated certificate even an X509 certificate? I must admit, I do not know the different types of certificate formats.
What type of code do I write? When I use SSL in WCF or HTTP, alot of the plumbing was handled for me already. Is this not the case for FTPS? i.e. I would import or install a certificate using MMC snap-in Console in Windows.
If I don't implement the Callback method in Alex FTPS, I get the message:
"The remote certificate is invalid according to the validation procedure"
Thank you for any tips and pointers
Gee, no answers... so I will answer my own question and it might be helpful for others.
Here are my steps, not sure if everyone is needed, but this is what I ended up doing.
1) Installed FileZilla Server
Used it to create its own self-signed certificate
menu: Settings | SSL/TSL Settings | Generate New Certificate
enter in the appropriate values
ensuring I had the common name = server address correct.
this generated a certificate with private key in the .crt
extension/format
2) As I was on Windows, I found I couldn't install this certificate in the certificate store, so the extra step was I needed to convert it first
downloaded OpenSSL http://www.openssl.org/related/binaries.html
use command line to convert it to the .pfx format
openssl pkcs12 -export -out mycert.pfx -inkey myFileZilla.crt -in
myFileZilla.crt
3) Launch windows MMC Snap-in Console
install the certificate into the Computer Account, Trusted Root
Certification Authorities store
4) In my code (in FTPS library, in this case Alex FTPS
My connection looks like this:
var credential = new NetworkCredential(username, password);
string message = _client.Connect(hostname, port, credential,
ESSLSupportMode.Implicit,
null, // new RemoteCertificateValidationCallback(ValidateTestServerCertificate),
null, 0, 0, 0, null);
The .net/Windows infrastructure plumbing handles all validation for me already
5) But if you wanted custom validation, or if you didn't want to install the certificate in the windows store, you can use this sample code here:
http://msdn.microsoft.com/en-us/library/office/dd633677%28v=exchg.80%29.aspx
private static bool ValidateTestServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
{
return true;
}
// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
if (chain != null && chain.ChainStatus != null)
{
foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus)
{
if ((certificate.Subject == certificate.Issuer) &&
(status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot))
{
// Self-signed certificates with an untrusted root are valid.
continue;
}
else
{
if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError)
{
// If there are any other errors in the certificate chain, the certificate is invalid,
// so the method returns false.
return false;
}
}
}
}
// When processing reaches this line, the only errors in the certificate chain are
// untrusted root errors for self-signed certificates. These certificates are valid
// for default Exchange server installations, so return true.
return true;
}
else
{
// In all other cases, return false.
return false;
}
}
Hope that helps people.

Could not establish trust relationship for SSL/TLS secure channel -- SOAP

I have a simple web service call, generated by a .NET (C#) 2.0 Windows app, via the web service proxy generated by Visual Studio, for a web service also written in C# (2.0). This has worked for several years, and continues to do so at the dozen or so places where it is running.
A new installation at a new site is running into a problem. When attempting to invoke the web service, it fails with the message saying:
Could not establish a trust relationship for the SSL/TLS secure
channel
The URL of the web service uses SSL (https://) -- but this has been working for a long time (and continues to do so) from many other locations.
Where do I look? Could this be a security issue between Windows and .NET that is unique to this install? If so, where do I set up trust relationships? I'm lost!
The following snippets will fix the case where there is something wrong with the SSL certificate on the server you are calling. For example, it may be self-signed or the host name between the certificate and the server may not match.
This is dangerous if you are calling a server outside of your direct control, since you can no longer be as sure that you are talking to the server you think you're connected to. However, if you are dealing with internal servers and getting a "correct" certificate is not practical, use the following to tell the web service to ignore the certificate problems and bravely soldier on.
The first two use lambda expressions, the third uses regular code. The first accepts any certificate. The last two at least check that the host name in the certificate is the one you expect.
... hope you find it helpful
//Trust all certificates
System.Net.ServicePointManager.ServerCertificateValidationCallback =
((sender, certificate, chain, sslPolicyErrors) => true);
// trust sender
System.Net.ServicePointManager.ServerCertificateValidationCallback
= ((sender, cert, chain, errors) => cert.Subject.Contains("YourServerName"));
// validate cert by calling a function
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateRemoteCertificate);
// callback used to validate the certificate in an SSL conversation
private static bool ValidateRemoteCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors)
{
bool result = cert.Subject.Contains("YourServerName");
return result;
}
The very simple "catch all" solution is this:
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
The solution from sebastian-castaldi is a bit more detailed.
Thoughts (based on pain in the past):
do you have DNS and line-of-sight to the server?
are you using the correct name from the certificate?
is the certificate still valid?
is a badly configured load balancer messing things up?
does the new server machine have the clock set correctly (i.e. so that the UTC time is correct [ignore local time, it is largely irrelevent]) - this certainly matters for WCF, so may impact regular SOAP?
is there a certificate trust chain issue? if you browse from the server to the soap service, can you get SSL?
related to the above - has the certificate been installed to the correct location? (you may need a copy in Trusted Root Certification Authorities)
is the server's machine-level proxy set correctly? (which different to the user's proxy); see proxycfg for XP / 2003 (not sure about Vista etc)
I personally like the following solution the most:
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
... then before you do request getting the error, do the following
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; };
Found this after consulting Luke's Solution
If you do not wan't to blindly trust everybody and make a trust exception only for certain hosts the following solution is more appropriate.
public static class Ssl
{
private static readonly string[] TrustedHosts = new[] {
"host1.domain.com",
"host2.domain.com"
};
public static void EnableTrustedHosts()
{
ServicePointManager.ServerCertificateValidationCallback =
(sender, certificate, chain, errors) =>
{
if (errors == SslPolicyErrors.None)
{
return true;
}
var request = sender as HttpWebRequest;
if (request != null)
{
return TrustedHosts.Contains(request.RequestUri.Host);
}
return false;
};
}
}
Then just call Ssl.EnableTrustedHosts when your app starts.
If you are using Windows 2003, you can try this:
Open Microsoft Management Console
(Start --> Run --> mmc.exe);
Choose File --> Add/Remove Snap-in;
In the Standalone tab, choose Add;
Choose the Certificates snap-in, and
click Add;
In the wizard, choose the Computer
Account, and then choose Local
Computer. Press Finish to end the
wizard;
Close the Add/Remove Snap-in dialog;
Navigate to Certificates (Local
Computer) and choose a store to
import:
If you have the Root CA certificate
for the company that issued the
certificate, choose Trusted Root
Certification Authorities;
If you have the certificate for the
server itself, choose Other People
Right-click the store and choose All
Tasks --> Import
Follow the wizard and provide the
certificate file you have;
After that, simply restart IIS and try
calling the web service again.
Reference: http://www.outsystems.com/NetworkForums/ViewTopic.aspx?Topic=Web-Services:-Could-not-establish-trust-relationship-for-the-SSL/TLS-...
Luke wrote a pretty good article about this ..
pretty straight forward .. give this a try
Luke's Solution
Reason (quote from his article (minus cursing))
"..
The problem with the code above is that it doesn’t work if your certificate is not valid. Why would I be posting to a web page with and invalid SSL certificate? Because I’m cheap and I didn’t feel like paying Verisign or one of the other **-*s for a cert to my test box so I self signed it. When I sent the request I got a lovely exception thrown at me:
System.Net.WebException
The underlying connection was closed. Could not establish trust relationship with remote server.
I don’t know about you, but to me that exception looked like something that would be caused by a silly mistake in my code that was causing the POST to fail. So I kept searching, and tweaking and doing all kinds of weird things. Only after I googled the ***n thing I found out that the default behavior after encountering an invalid SSL cert is to throw this very exception.
.."
Microsoft's SSL Diagnostics Tool may be able to help identify the issue.
UPDATE the link has been fixed now.
add this:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;}
right before the line that you're calling the service
I just encountered this issue. My resolution was to update the system time by manually syncing to the time servers. To do this you can:
Right-click the clock in the task bar
Select Adjust Date/Time
Select the Internet Time tab
Click Change Settings
Select Update Now
In my case this was syncing incorrectly so I had to click it several times before it updated correctly. If it continues to update incorrectly you can even try using a different time server from the server drop-down.
Try this:
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
Notice that you have to work at least with 4.5 .NET framework
I had a similar problem in .NET app in Internet Explorer.
I solved the problem adding the certificate (VeriSign Class 3 certificate in my case) to trusted editors certificates.
Go to Internet Options-> Content -> Publishers and import it
You can get the certificate if you export it from:
Internet Options-> Content -> Certificates -> Intermediate Certification Authorities -> VeriSign Class 3 Public Primary Certification Authority - G5
thanks
I had this error running against a webserver with url like:
a.b.domain.com
but there was no certificate for it, so I got a DNS called
a_b.domain.com
Just putting hint to this solution here since this came up top in google.
For those who are having this issue through a VS client side once successfully added a service reference and trying to execute the first call got this exception:
“The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel”
If you are using (like my case) an endpoint URL with the IP address and got this exception, then you should probably need to re-add the service reference doing this steps:
Open the endpoint URL on Internet Explorer.
Click on the certificate error (red icon in address bar)
Click on View certificates.
Grab the issued to: "name" and replace the IP address or whatever name we were using and getting the error for this "name".
Try again :).
Thanks
In my case I was trying to test SSL in my Visual Studio environment using IIS 7.
This is what I ended up doing to get it to work:
Under my site in the 'Bindings...' section on the right in IIS, I had to add the 'https' binding to port 443 and select "IIS Express Developement Certificate".
Under my site in the 'Advanced Settings...' section on the right I had to change the 'Enabled Protocols' from "http" to "https".
Under the 'SSL Settings' icon I selected 'Accept' for client certificates.
Then I had to recycle the app pool.
I also had to import the local host certificate into my personal store using mmc.exe.
My web.config file was already configured correctly, so after I got all the above sorted out, I was able to continue my testing.
My solution (VB.Net, the "staging" (UAT) version of this application needs to work with the "staging" certificate but not affect requests once they are on the live site):
...
Dim url As String = ConfigurationManager.AppSettings("APIURL") & "token"
If url.ToLower().Contains("staging") Then
System.Net.ServicePointManager.ServerCertificateValidationCallback = AddressOf AcceptAllCertifications
End If
...
Private Function AcceptAllCertifications(ByVal sender As Object, ByVal certification As System.Security.Cryptography.X509Certificates.X509Certificate, ByVal chain As System.Security.Cryptography.X509Certificates.X509Chain, ByVal sslPolicyErrors As System.Net.Security.SslPolicyErrors) As Boolean
Return True
End Function
A variation I've been using for a while it if helps anyone.
The caller has to explicitly request that untrusted certifications are required and places the callback back into it's default state upon completion.
/// <summary>
/// Helper method for returning the content of an external webpage
/// </summary>
/// <param name="url">URL to get</param>
/// <param name="allowUntrustedCertificates">Flags whether to trust untrusted or self-signed certificates</param>
/// <returns>HTML of the webpage</returns>
public static string HttpGet(string url, bool allowUntrustedCertificates = false) {
var oldCallback = ServicePointManager.ServerCertificateValidationCallback;
string webPage = "";
try {
WebRequest req = WebRequest.Create(url);
if (allowUntrustedCertificates) {
// so we can query self-signed certificates
ServicePointManager.ServerCertificateValidationCallback =
((sender, certification, chain, sslPolicyErrors) => true);
}
WebResponse resp = req.GetResponse();
using (StreamReader sr = new StreamReader(resp.GetResponseStream())) {
webPage = sr.ReadToEnd().Trim();
sr.Close();
}
return webPage;
}
catch {
// if the remote site fails to response (or we have no connection)
return null;
}
finally {
ServicePointManager.ServerCertificateValidationCallback = oldCallback;
}
}
If not work bad sertificate, when ServerCertificateValidationCallback return true;
My ServerCertificateValidationCallback code:
ServicePointManager.ServerCertificateValidationCallback += delegate
{
LogWriter.LogInfo("Проверка сертификата отключена, на уровне ServerCertificateValidationCallback");
return true;
};
My code which the prevented execute ServerCertificateValidationCallback:
if (!(ServicePointManager.CertificatePolicy is CertificateValidation))
{
CertificateValidation certValidate = new CertificateValidation();
certValidate.ValidatingError += new CertificateValidation.ValidateCertificateEventHandler(this.OnValidateCertificateError);
ServicePointManager.CertificatePolicy = certValidate;
}
OnValidateCertificateError function:
private void OnValidateCertificateError(object sender, CertificateValidationEventArgs e)
{
string msg = string.Format(Strings.OnValidateCertificateError, e.Request.RequestUri, e.Certificate.GetName(), e.Problem, new Win32Exception(e.Problem).Message);
LogWriter.LogError(msg);
//Message.ShowError(msg);
}
I disabled CertificateValidation code and ServerCertificateValidationCallback running very well

Categories