Mutual SSL using: Mono,C#, and Self-Signed Certificates - c#

Here is the project:
I'm creating a TcpListener in C#, that will host on a Linux box using Mono.
Then I will have a TcpClient (also Mono on Linux) that will connect to it, and because I can't have a username/password auth, I would like it that clients connect with a "client certificate", that I will generate with my Root CA (also self-generated)
I can't only have the client connect securely to the server, I need my TcpListener to trust my client connection too.
What i have done so far:
I have exported my server certificate as PFX:
And loaded this into the code:
sslCertificate = new X509Certificate2("myCert.pfx", "password");
Now, if I run the code as below:
bool requireClientCertificate = false; // <== turn on/off
SslStream sslStream = new SslStream(netStream, false, new RemoteCertificateValidationCallback(AcceptRemoteCertificate));
sslStream.AuthenticateAsServer(sslCertificate, requireClientCertificate, SslProtocols.Tls, false);
With requireClientCertificate = false, then I can use a browser to view this page perfectly fine. Of course, I would have already imported my Root CA, in order to see this page, otherwise Chrome gives me a certificate warning.
Right, so there is no problem with this so far... and SSL is established, but technically anyone could connect to this TcpListner.
Now I set requireClientCertificate = true, and this is where things fall apart.
I realize I need to generate a client certificate, so I did that, exported that, and imported it into my PC. I can see it under the certificate list in Chrome, and I am prompted to use it when I first attempt to connect the TcpListner.
However, doing all that, still does not solve things.
I have also installed the Root CA onto the server using:
copy to /usr/share/ca-certificates/mozilla/MyRootCA.crt
editing /etc/ca-certificates.conf
update-ca-certificates
(This showed 1 new certificate installed.. but still did'nt help)
Then I tried:
certmgr -add -c -v -m Trust /home/Certs/MyRootCA.crt
Still not working, so i tried:
certmgr -add -c -v -m CA /home/Certs/MyRootCA.crt
So from what I can see the RootCA is installed.
If I try connect with Chrome I'm getting:
RemoteCertificateChainErrors in the RemoteCertificateValidationCallback
And if I loop thought the chain, I get : PartialChain in the Status Information.
And if I test with OpenSSL, I get:
RemoteCertificateChainErrors with: UntrustedRoot in the Status Information
The totally confusing part on this, is that, my RemoteCertificate of the TcpListner is not null - it is in fact a the certificate that I installed on the browser, and can see the RootCA that singed it too (Issuer).
If I actually do a: wget https://api.myserver.com --certificate=/my.crt --private-key=/my.key --ca-certificate=/MyRootCA.crt, it connects and downloaded my expedited results.
So, questions:
Is this connection actually mutually secure?
What are those errors about? UntrustedRoot and PartialChain
If it has these errors, but still can present a RemoteCertificate, does that mean its a mutually secure connection or not?
In the past I have done this on Windows Server with a commercial CA certificates (DigiCert E.V.), and other (out of my control) CPE's, and they worked without errors. This is the only difference I have noticed. Is this the reason? Does Linux / Mono, have some sort of additional knowledge about certificates, that if you generate your own Root CA... it can't trust it completely?
EDIT:
I have not got a full answer, but I have discovered a few things. Would really appreciate some assistance from someone who has this working.
So what I discovered that you can check the stream (ssl) properties of .IsMutuallyAuthenticated and .IsSigned and .IsEncrypted. This is vital to this, and especially .IsMutuallyAuthenticated.
As it turned out, by manipulating the RemoteCertificateValidationCallback response true/false, you can handle the errors that you "don't mind", and reject the others. In that case the connection disconnects and an error is raised. However, in my RemoteCertificateChainErrors I was able to return true... and when doing that stream.IsMutuallyAuthenticated, was returning True! Yay
Ok, now I build the client with :
HttpWebRequest requestObj = (HttpWebRequest)WebRequest.Create(strURL);
requestObj.ClientCertificates = certs;
This presents the certificates to my server code, and now this is where its interesting... After getting the response object:
HttpWebResponse responseObj = (HttpWebResponse)requestObj.GetResponse();
Debug.WriteLine("IsMutuallyAuthenticated: " + responseObj.IsMutuallyAuthenticated);
Even though I'm getting my expected results back, and responseObj.StatusCode == HttpStatusCode.OK, IsMutuallyAuthenticated is false. So clearly this is an issue with the certificate that i'm presenting client side.
It seems that the client side code can NOT trust the certificate it is presenting, but I have installed the Root CA locally on the PC. Both Local Machine, and User. If I open the store - and list the certs, its there.
This seems like its more difficult than it should be. Any assistance would help.

Related

C# HttpListener (netsh) problems with SelfSigned Certificate

I have some trouble with my HttpListener. I already searched and read some dicussions.
For example: Httplistener with HTTPS support
Firstly I try to describe my scenario:
I want to create a HTTP-Listener with SSL/HTTPS Support. Thats the main target.
I used OpenSSL to create my own CA and I created my own server cert.
Now I have:
myCa.key
myCa.pem
myCa.srl
myServer.key
myServer.csr
myServer.crt
I installed the myCa.pem and the myServer.crt certificate to my local computer. I moved the CA in the trusted store and the server certificate in "own certificates"
Then I took the fingerprint (certHash) of my server certificate.
I created the netsh entry with admin-rights
netsh http add sslcert ipport=0.0.0.0:9649 appid= '{0a5ce-569a-4dc6-8ed7-9ef91241dec3}' certhash=4F556BDC3F97B31D555266DA74F573777FCCAA55
My C# implementation is relativly simple:
this.Listener = new HttpListener();
this.Listener.Prefixes.Add("https://*:9649");
this.Listener.Start();
this.Listener.BeginGetContext(new AsyncCallback(ProcessClient), this.Listener);
//Process an incoming connection
private void ProcessClient(IAsyncResult result)
{
var listener = (HttpListener)result.AsyncState;
var clientContext = listener.EndGetContext(result);
}
When I implemented SSL in my TcpStack I used a SSL-Stream and there I can validate a certificate with a ValidationCallback. Im not sure if this is possible here. I tried
ServicePointManager.ServerCertificateValidationCallback += ValidateCert; But I never hit my breakpoint there.
Now to the problems:
When I try to connect with my own HttpClient (.NET HttpClient Class) I get always a RemoteNameMismatch Error on the SSL-Layer. I dont hit the breakpoint in the ProcessClient method. I tried without specific certificate (auto detection) and I tried also to advise the same certificate (with the same hash) to the client. In both cases I got the same error. I dont understand why I get any erros when I use the same certificate on the client and the server side. I always thought the netsh will compare the certhashes.
When I try a connect with Postman I hit the ProcessClient function. But Postman gets an error that he cant check the certificate. But I think the problem is that my certificate isnt a official certifcate . But the data exchange is working.
Another point is: I want to roll out my app also in containers with a unix os. .NET60 is designed for crossplatform. But whats the unix pendant to netsh? Is it possible to run my listener with https on unix? How works the mapping here between app and certificate?
Maybe I have to change my technology? Alternative to HttpListener? Mainly I dont want to use thridparty stuff.
UPDATE Solution:
See my answer below
Thanks for reading and for help.
Greetings
Like the guys said in the in comments. The FDQN was the problem. In easy words: I created my own CA and then I created a server cert signing request against the CA. Inside the server cert the CN is matching to my DNS of my personal computer. The connection with my HTTP-Listener is working now. Thank you for your help!

Mailkit IMapClient not hitting ServerCertificateValidationCallback & SslHandshakeException

I'm attempting to connect to an IMAP server using the following code on a Windows Server 2019 machine:
using (var client = new ImapClient(new ProtocolLogger("protocol.log")))
{
var address = EnvReader.GetStringValue("EMAIL_ADDRESS");
var password = EnvReader.GetStringValue("EMAIL_PASSWORD");
var creds = new NetworkCredential(address, password);
client.CheckCertificateRevocation = false;
client.ServerCertificateValidationCallback = (s, c, h, e) =>
{
Console.WriteLine("ALL UP IN THIS CALLBACK" + e.ToString());
return true;
};
client.Connect("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
client.Authenticate(address, password);
}
On my Mac, this code runs perfectly fine, I can connect and subsequently authenticate just fine.
On the Windows machine I receive the following exception:
MailKit.Security.SslHandshakeException: An error occurred while attempting to establish an SSL or TLS connection.
This usually means that the SSL certificate presented by the server is not trusted by the system for one or more of
the following reasons:
1. The server is using a self-signed certificate which cannot be verified.
2. The local system is missing a Root or Intermediate certificate needed to verify the server's certificate.
3. A Certificate Authority CRL server for one or more of the certificates in the chain is temporarily unavailable.
4. The certificate presented by the server is expired or invalid.
5. The set of SSL/TLS protocols supported by the client and server do not match.
6. You are trying to connect to a port which does not support SSL/TLS.
See https://github.com/jstedfast/MailKit/blob/master/FAQ.md#SslHandshakeException for possible solutions
Based on the info in the linked FAQ, I added the ServerCertificateValidationCallback, however the callback is never hit (The previously mentioned exception is still thrown, the relevant console logging never occurs, and a breakpoint inside the callback is never hit while debugging).
From my reading, the ServerCertificateValidationCallback should handle cases #1-4 that the exception message mentions. The fact that I can connect on the specified port on my Mac would seem to rule out case #6 (I also tried port 143 + SecureSocketOptions.StartTls). That leaves case #5, however, I can't find any information suggesting that Windows Server 2019 can't handle SSL/TSL protocols.
Any ideas for a) dealing with this exception and/or b) figuring out why the ServerCertificateValidationCallback is not firing would be greatly appreciated.
Edit: My project is referencing .NET 5.0
Let's go through each of the possibilities:
The server is using a self-signed certificate which cannot be verified.
outlook.office365.com would not be using a self-signed certificate, so that wouldn't be an issue in this case.
The local system is missing a Root or Intermediate certificate needed to verify the server's certificate.
This one is very possible, but the ServerCertificateValidationCallback override should be overriding this failure. However, it's not getting hit... so it's not actually bypassing this potential error.
A Certificate Authority CRL server for one or more of the certificates in the chain is temporarily unavailable.
This would be negated by client.CheckCertificateRevocation = false;
The certificate presented by the server is expired or invalid.
This is not the case because the certificate does not expire until 1/21/2022.
The set of SSL/TLS protocols supported by the client and server do not match.
The server supports at least TLSv1.2 which is a default TLS protocol version supported by MailKit in all target framework versions (.NET 4.5 -> 5.0 + netstandard2.x's).
You are trying to connect to a port which does not support SSL/TLS.
Port 993 is the correct port and SslOnConnect is the correct option, so this is not the issue.
Assuming there isn't a bug in MailKit's SslStream.AuthenticateAsClientAsync() call that passes in the validation callback method (.NET 5.0 is different than other versions), what is the InnerException? Maybe that will provide some insight.

Getting "The remote certificate is invalid according to the validation procedure" when SMTP server has a valid certificate

This seems a common error but while I've found a work-around (see below) I can't pin down the reason I'm getting it in the first place.
I am writing SMTP functionality into our application and I'm attempting to add SSL functionality to the working SMTP we already have.
I am testing using our company's MS Exchange server and specifically the webmail option enabled on that. I can send emails internally through my code by not authenticating my connection and sending anonymously, however those emails won't relay to external email addresses due to our companies policy. Besides which I am programming this for our customers and they don't all allow open relay and/or anonymous connections.
I believe the Exchange server is using Explicit SSL/ TLS. I have tried telnet to the server's address on port 25 and got a text response, human readable response, which according to some of my searches previously means it's using Explicit SSL/ TLS.
I have the following test code
SmtpClient SMTPClient = new SmtpClient(webmailaddress);
SMTPClient.Port = 25;
SMTPClient.UseDefaultCredentials = true;
SMTPClient.EnableSsl = true;
System.Net.Mail.MailMessage Message = new `
System.Net.Mail.MailMessage(emailFrom,emailTo,subject,body);
SMTPClient.Send(Message);
During my searching for a solution I came across this "The remote certificate is invalid according to the validation procedure." using Gmail SMTP server
From which I got the following code...
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
public static bool ValidateServerCertificate(object sender,X509Certificate certificate,X509Chain chain,SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
else
{
if (System.Windows.Forms.MessageBox.Show("The server certificate is not valid.\nAccept?", "Certificate Validation", System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes)
return true;
else
return false;
}
}
This works in my test code. HOWEVER the actual process I'm writing (rather than my test code) is going to run in the background and can't really ask the user (instead it reports errors in the windows error log).
As I started, my question is really why I'm getting this error at all. If I go to https:webmail.ourdomain.co.uk in a browser it shows a valid certificate and there is no option to install the certificate (as I would have done if it were a self-signed one).
However when I run my code, with a debug break poing in the ValidateServerCertificate method, I look at the certificate values and see an issuer of our local server and 'don't use before', and 'don't use after' properties of today. This does not match the certificate I am getting.
I've also checked what the sslPolicyErrors flags are in the debug of ValidateServerCertificate, and they are showing "RemoteCertificateChainErrors" and "RemoteCertificateNameMismatch".
So what am I missing about this... why is it not using the correct certificate? If there are steps I need to take to install the certificate locally for it to use then I need to know them so I can tell my customers what to do if they get this.
I don't want to just by-pass the check by returning true from the ValidateServerCertificate method, and because it's a background process I can't ask the user, so I need to understand how to get my code to use the correct/trusted certificate.
Hope someone can advise.
The answer I have finally found is that the SMTP service on the server is not using the same certificate as https.
The diagnostic steps I had read here make the assumption they use the same certificate and every time I've tried this in the past they have done and the diagnostic steps are exactly what I've done to solve the problem several times.
In this case those steps didn't work because the certificates in use were different, and the possibility of this is something I had never come across.
The solution is either to export the actual certificate from the server and then install it as a trusted certificate on my machine, or to get a different valid/trusted certificate for the SMTP service on the server. That is currently with our IT department who administer the servers to decide which they want to do.
Old post but as you said "why is it not using the correct certificate" I would like to offer an way to find out which SSL certificate is used for SMTP (see here) which required openssl:
openssl s_client -connect exchange01.int.contoso.com:25 -starttls smtp
This will outline the used SSL certificate for the SMTP service. Based on what you see here you can replace the wrong certificate (like you already did) with a correct one (or trust the certificate manually).
Old post, but I thought I would share my solution because there aren't many solutions out there for this issue.
If you're running an old Windows Server 2003 machine, you likely need to install a hotfix (KB938397).
This problem occurs because the Cryptography API 2 (CAPI2) in Windows
Server 2003 does not support the SHA2 family of hashing algorithms.
CAPI2 is the part of the Cryptography API that handles certificates.
https://support.microsoft.com/en-us/kb/938397
For whatever reason, Microsoft wants to email you this hotfix instead of allowing you to download directly. Here's a direct link to the hotfix from the email:
http://hotfixv4.microsoft.com/Windows Server 2003/sp3/Fix200653/3790/free/315159_ENU_x64_zip.exe

Error: Could not establish trust relationship for the SSL/TLS secure channel with authority

I have a wfc VS 2010/C# program that has been running on a Win03 Server for 6 months without issue. The app uses a public key for a remote server to send a request and then our private key to decrypt the response that comes back.
Our cert was about to expire so we issued a new one. We gave the public key to the remote people and installed the private key file on the server, which is a password protect P12. I also installed it on my development machine.
No problems in the development machine. I can send requests and get response just as before. My development machine is a 64bit Win7. On the Win03 server production machine I get the following error: Could not establish trust relationship for the SSL/TLS secure channel with authority '{public key name of remote server}'.
The public key for the remote server has a valid date and a valid chain in my cert store. There are two certs in the chain above this key and both of them have valid dates and chains. I have uninstalled and reinstalled all of the certs and can’t get past this. I have no idea what is causing it.
When I Google ‘Could not establish trust relationship for the SSL/TLS secure channel with authority’ I see dozens of “solutions” for fixing the same problem and they all involve code changes. This does not seem to be what I’m looking for. The code runs fine in the development machine and until this morning ran fine on the production sever. The only thing that changed was our cert.
The one thing I did notice was that the Win03 server does not have Local Computer Cert Store, so it is impossible for me to install certs there. When I import certs I have sometimes let the wizard decide where to put certs and others times told it where to put the certs. Both with the same response. I am always logged in as administrator and always run the application under administrator.
Any idea what is causing my problems?
<UPDATE>
I have the same problem on my development PC when I run the compiled program. However, when I run the compiled program and try and view the output with Fiddler, I get the message below from Fiddler. If I say yes, the requests come back as expected. If I say no, I get 100% failure just as with the executable on the server.
Could not establish trust relationship for the SSL/TLS secure channel
with authority '555.555.555.555'.
Session #1: The remote server (555.555.555.555) presented a certificate
that did not validate, due to RemoteCertificateNameMismatch.
SUBJECT: CN=prods-dp.wellpoint.com, OU=SOA CoE, O=Wellpoint,
L=Richmond, S=Virginia, C=US
ISSUER: CN=Anthem Inc Certificate Authority, OU=Ecommerce, O=Anthem
Inc, C=US
EXPIRES: 7/10/2013 6:39:09 AM
(This warning can be disabled by clicking Tools | Fiddler Options.)
Greg
I was referencing the ip address in the app.congif instead of the domain name.
Wrong
<*endpoint address="https://555.555.555.55/web_service"
Right
<*endpoint address="https://thewebservicedomain.com/web_service"
I'm not sure if this is because I have identity block which references the domain name as part of the endpoint, like this...
<identity>
<dns value="thewebservicedomain.com" />
</identity>
Greg

Calling HTTPS JBoss service from C# code throws an authentication error

I'm trying to call a JBoss service from a C# program and I'm getting an annoyingly vague error.
JbossService proxy = new JbossService();
proxy.Credentials = new NetworkCredential("ME", "thepwd");
proxy.Url = //https url snipped
proxy.CookieContainer = new CookieContainer();
proxy.PreAuthenticate = true;
Console.WriteLine("Calling service...");
queryResponse qr = proxy.query();
Console.WriteLine("Done.");
The exception and inner exception thrown are as follows:
exception : The underlying connection was closed: An unexpected error occurred on a send.
inner exception : Authentication failed because the remote party has closed the transport stream.
I'm not quite sure what this means, other than perhaps that JBoss likes me even less than I like it. I'm calling from the local machine so I don't think it's a networking issue. Has anyone seen this before?
This usually happens when your client cannot verify trust over https with the server (usually because the server certificate is self signed or if it is signed by a root authority not installed on your client machine.
Easy fix (although there are security consequences)....somewhere in your initialization code add the following:
System.Net.ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true;};
Basically this replaces the application wide handling of server certificate validation and causes your application to accept any certificate. If you want to get finer grained, you can examine the certificate and put some logic in the method.
This works for anything based on System.Net, so it should work for Web Services and any thing based on WebRequest.
I haven't used JBOSS. This is how I troubleshoot similar problems, when using Microsoft technologies -- the same issues may be affecting your program:
Firewall settings or network issue (try connecting manually, to rule this out)
Self-service certificate issues:
Check the following certificate values:
Ensure the server's certificate issuer has a valid, matching issuing trusted root Certificate Authority (CA), on the same machine
The server certificate subject name matches the machine name exactly
The machine name the client is accessing matches that defined in the server certificate
An administrator account set (server) certificate thumbprint
Try recreating the SSL Certificate on both servers)
Try creating your own CA cert, add to trusted publishers, and then create an SSL sert based on that

Categories