My goal is to make a secure communication between a Java server and client written in C#.
java server code:
System.setProperty("javax.net.ssl.keyStore","cert/mySrvKeystore");
System.setProperty("javax.net.ssl.keyStorePassword","myPassword");
SSLServerSocketFactory sslserversocketfactory =
(SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket sslserversocket = = (SSLServerSocket) sslserversocketfactory.createServerSocket(2389);
while(true) {
System.err.println("server w8 new connection");
try {
SSLSocket sslsocket = (SSLSocket) sslserversocket.accept();
//sslsocket.startHandshake();
in = sslsocket.getInputStream();
out = sslsocket.getOutputStream();
out.flush();
String response = new String(receiveMessage());
while (response != "end") {
System.out.println("Server recv="+response);
response = new String(receiveMessage());
sendMessage(("echo="+response).getBytes());
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
and client written in c# :
client = new TcpClient() { SendTimeout = 5000, ReceiveTimeout = 5000 };
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
client.Connect(serverEndPoint);
client.NoDelay = true;
Console.WriteLine("Client connected.");
// Create an SSL stream that will close the client's stream.
SslStream sslStream = new SslStream(client.GetStream(), false, ValidateServerCertificate, null);
// The server name must match the name on the server certificate.
try
{
sslStream.AuthenticateAsClient("someName");
}
catch (AuthenticationException error)
{
Console.WriteLine("Exception: {0}", error.Message);
if (error.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", error.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
return;
}
ASCIIEncoding ascii = new ASCIIEncoding();
SendData(ascii.GetBytes("Hello World"));
and
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
and i get the following errors: in c#
A first chance exception of type "System.Security.Authentication.AuthenticationException" occurred in System.dll
Certificate error: RemoteCertificateChainErrors
Exception: The remote certificate is invalid according to the validation procedure.
Authentication failed - closing the connection.
I know that the issue can be the fact that i use different types of certificates, but i don't know how to make a standard sslServerSocket with X509Certificate in java. Can some one help me, with good example, or some advice how can i reach my goal ?
P.S. I was looking to bouncycastle library, causes it has both java, and c# implementation, but i would like to use standard libraries, and built-in functionality of the languages.
From your example, it doesn't look like you need to generate your certificate and private key programmatically. You can generate a certificate in your server keystore with keytool:
keytool -genkey -alias myalias -keystore mykeystore.jks -dname "CN=www.example.com"
Or better, with the SAN extension too, if you're using Java 7's keytool:
keytool -genkey -alias myalias -keystore mykeystore.jks -dname "CN=www.example.com" -ext san=dns:www.example.com
Here, www.example.com is the host name as seen by the client. You can add other things in the Subject DN (dname), but make sure the CN is the host name.
Once it's generated, export your self-signed certificate using:
keytool -export myservercert.crt -alias myalias -keystore mykeystore.jks
You should then be able to import it as a trusted certificate in your Windows certificate store from use from C#.
Related
I am going to try to send emails via Mailkit but ran into problems with the error from "System.Security.Authentication.AuthenticationException" which is "The remote certificate is invalid according to the validation procedure" (translated from danish) My mailserver runs SSL TLS and the TLS supports version 1.2 and 1.3. my code is as below: I do not hope that it is to much code - but I do not know where to enhance the code so it can handle SSL correctly :-(
The error occur in the line "client.Connect("servername", 587, true);"
So my question is: How to avoid this error message via Mailkit?
public void SendMail(string AFromMailAdr, string AFromName, string AToMailAdr, string AToName, string ASubject, string ABody)
{
MimeMessage message = new MimeMessage();
...
using (var client = new MailKit.Net.Smtp.SmtpClient())
{
client.Timeout = 30000;
client.Connect("servername", 587, true);
client.Authenticate("Username", "password");
client.Send(message);
client.Disconnect(true);
}
}
I have googlet a lot until now without finding the correct answer - so therefore I kindly ask here on SO.
To be fair, the underlying problem should be checked/corrected.
You can control how MailKit does the server certificate validation using a ServerCertificateValidationCallback
For debugging purposes you could return true; in the callback function.
Code from the MailKit documentation:
using (var client = new MailKit.Net.Smtp.SmtpClient())
{
// Set our custom SSL certificate validation callback.
client.ServerCertificateValidationCallback = MySslCertificateValidationCallback;
client.Timeout = 30000;
client.Connect("servername", 587, true);
client.Authenticate("Username", "password");
client.Send(message);
client.Disconnect(true);
}
static bool MySslCertificateValidationCallback (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// If there are no errors, then everything went smoothly.
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
// Note: MailKit will always pass the host name string as the `sender` argument.
var host = (string) sender;
if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNotAvailable) != 0) {
// This means that the remote certificate is unavailable. Notify the user and return false.
Console.WriteLine ("The SSL certificate was not available for {0}", host);
return false;
}
if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) != 0) {
// This means that the server's SSL certificate did not match the host name that we are trying to connect to.
var certificate2 = certificate as X509Certificate2;
var cn = certificate2 != null ? certificate2.GetNameInfo (X509NameType.SimpleName, false) : certificate.Subject;
Console.WriteLine ("The Common Name for the SSL certificate did not match {0}. Instead, it was {1}.", host, cn);
return false;
}
// The only other errors left are chain errors.
Console.WriteLine ("The SSL certificate for the server could not be validated for the following reasons:");
// The first element's certificate will be the server's SSL certificate (and will match the `certificate` argument)
// while the last element in the chain will typically either be the Root Certificate Authority's certificate -or- it
// will be a non-authoritative self-signed certificate that the server admin created.
foreach (var element in chain.ChainElements) {
// Each element in the chain will have its own status list. If the status list is empty, it means that the
// certificate itself did not contain any errors.
if (element.ChainElementStatus.Length == 0)
continue;
Console.WriteLine ("\u2022 {0}", element.Certificate.Subject);
foreach (var error in element.ChainElementStatus) {
// `error.StatusInformation` contains a human-readable error string while `error.Status` is the corresponding enum value.
Console.WriteLine ("\t\u2022 {0}", error.StatusInformation);
}
}
return false;
}
I've created a C# SSL TCP webserver primarily using the example from the SslStream docs, and I've created and downloaded an SSL Certificate (with sslforfree) and created pkcs#7/p7b and pkcs#12 (with a blank password) certs using openssl through powershell. I have found no way to use pkcs# 12 or 7 yet.
(using OpenSSL.X509Certificate2Provider and System.Security.Cryptography.X509Certificates)
The most success was using this code:
string certfiletext = File.ReadAllText(Path.Combine(workingpath, "certificate.crt"));
string privatekeytext = File.ReadAllText(Path.Combine(workingpath, "private.key"));
ICertificateProvider provider = new
CertificateFromFileProvider(certfiletext, privatekeytext);
serverCertificate = provider.Certificate;
This code gets past the sslStream.AuthenticateAsServer requirement, but fails at bytes = sslStream.Read(buffer, 0, buffer.Length);
The stack trace associated with this failure expresses that The decryption operation failed, see inner exception, with inner exception being: Win32Exception: An unknown error occurred while processing the certificate.
This leads me to believe the certificate isn't created properly. The other method I've gotten anywhere with is this:
serverCertificate =
new X509Certificate2(Path.Combine(workingpath, "certificate.pfx"), "");
but that causes the same issue as the prior code.(Decryption error due to processing certificate error). This error appears whether the certificate has been installed on my machine or not. There is also no issue with the server finding the correct files.
This is the code I use for a JS client side embedded in html:
wsUri = "wss://thisismyip:11000",
websocket = new WebSocket(wsUri);
websocket.onopen = function (e) {
websocket.send(window.location.hostname + window.location.pathname.substring(1));
};
websocket.onclose = function (e) {};
websocket.onmessage = function (e) {
//server response, do stuff when it responds
};
websocket.onerror = function (e) {};
While I doubt the issue is related to the JS code, I might have missed something important.
Summary: I can't create an SSL connection between a javascript websocket and a C# TCP server due to a server-side certificate issue.
I've created such code for this certificate issue. Maybe it will help you:
private const string _password = "123";
private const string _certificatePath = certificate.pfx";
public static HttpClient Build()
{
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.SslProtocols = SslProtocols.Tls12;
clientHandler.ClientCertificateOptions =
ClientCertificateOption.Manual;
// to hide any ssl errors
clientHandler.ServerCertificateCustomValidationCallback = (sender,
cert, chain, sslPolicyErrors) => { return true; };
var cert = new X509Certificate2(_certificatePath, _password);
clientHandler.ClientCertificates.Add(cert);
return new HttpClient(clientHandler);
}
I'm trying to implement server authentication in C# (using .NET appln). I would like to achieve the following :
Connect to HTTPS URL using
String sslServerHost = "https://mail.google.com";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(sslServerHost);
If HTTPS request fails, I would be adding the server certificate to the local windows certification store.
X509Certificate cert = request.ServicePoint.Certificate;
//convert the X509Certificate to an X509Certificate2 object by passing it into the constructor
X509Certificate2 cert2 = new X509Certificate2(cert);
X509Store userCaStore = new X509Store(storeName: StoreName.Root, storeLocation: StoreLocation.CurrentUser);
// Code to import server certifictes to windows store.
userCaStore.Open(OpenFlags.ReadOnly);
userCaStore.Add(cert2);
The below is the C# code that I have used for performing server authentication.
As evident from the below code snippet, I have NEITHER ignored the certificate validation NOR added the X509Certificate to the local trust store, but still I was able to establish connection to the HTTPS URL ( WebRequest.create(url) and request.GetResponse() doesn't throw any exceptions )
String sslServerHost = "https://mail.google.com";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(sslServerHost);
request.AllowAutoRedirect = false;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
try
{
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();
Debug.WriteLine(responseFromServer);
Console.WriteLine(responseFromServer);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Why is it that I'm not getting an exception when trying to access the HTTPS URL given that I have NOT added the server certificates (X509Certificate) to the local windows store.
In short, how to achieve or implement server authentication in C# where if server certificate were not added then the C# code should throw an exception.
For Java, there is a good link https://github.com/escline/InstallCert/blob/master/InstallCert.java which best describes the server authentication mechanism where if client is trying to access an HTTPS Server and if server's certificate is not present in the Java trust store, then JVM throws an exception.
This mechanism doesn't seem to hold good for .NET applications. Any help or insight would be appreciated !!
The reason is probably that mail.google.com is already trusted, since the certificate chain leads up to some root certificate which IS already in your certificate store (in "Trusted Root Certification Authorities").
If you want to test failure, follow a tutorial to create your own CA and certificates using something like OpenSSL. Then set up a web site in IIS with this untrusted certificate.
According with the web request official documentation of the web request The certificate could be installed in My certificate store of the current user. That might explain why it nevers throws an error. You can try to connect to a server without adding the certificate to the cert store and see if that is throwing an error.
The solutions I have found to solve this problem involve setting a callback on ServicePointManager.ServerCertificateValidationCallback
ServicePointManager.ServerCertificateValidationCallback =
MyRemoteCertificateValidationCallback;
public bool MyRemoteCertificateValidationCallback(System.Object sender,
X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
bool isOk = true;
// If there are errors in the certificate chain, look at each error to determine
the cause.
if (sslPolicyErrors != SslPolicyErrors.None) {
for(int i=0; i<chain.ChainStatus.Length; i++) {
if(chain.ChainStatus[i].Status !=
X509ChainStatusFlags.RevocationStatusUnknown) {
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
chain.ChainPolicy.VerificationFlags =
X509VerificationFlags.AllFlags;
bool chainIsValid = chain.Build((X509Certificate2)certificate);
if(!chainIsValid) {
isOk = false;
}
}
}
} else {
X509Certificate2 cert3 = new X509Certificate2(certificate);
bool verify = cert3.Verify();
var cert1 = new X509Certificate2(certificate);
if (cert1.NotAfter <= DateTime.Now)
{
return false;
}
}
return isOk;
}
I am getting the following error " a call to sspi failed see inner exception. the message received was unexpected or badly formatted in windows 7". i tried all the possible ways but no luck. please check it
Here is the code
int port = 2195;
String deviceID = "d6c597fcc4e3426993cf29a3a8857efbed3462d5d8e9e32c0f8b387djkdklldk";
String hostname = "gateway.sandbox.push.apple.com"; // TEST
//String hostname = "gateway.push.apple.com"; // REAL
string p12FilePassword = "password";
// #"cert.p12";
String certificatePath = HttpContext.Current.Server.MapPath("~/Certificate.p12");
X509Certificate2 clientCertificate = string.IsNullOrEmpty(p12FilePassword) ? new X509Certificate2(File.ReadAllBytes(certificatePath)) : new X509Certificate2(File.ReadAllBytes(certificatePath), p12FilePassword, X509KeyStorageFlags.MachineKeySet);
X509Certificate2Collection certificatesCollection = new X509Certificate2Collection(clientCertificate);
TcpClient client = new TcpClient(hostname, port);
SslStream sslStream = new SslStream(client.GetStream(), false, ValidateServerCertificate, SelectLocalCertificate);
try
{
sslStream.AuthenticateAsClient(hostname, certificatesCollection, System.Security.Authentication.SslProtocols.Tls, false);
}
catch (Exception e)
{
throw (e);
//client.Close();
//return;
}
I see this is an old post but I recently faced this problem. Application can work on my Windows10 and failed on Windows server 2008r2 and 2012r2.
The solution is get rid off SslStream and use libraries like openssl or bouncycastle for tls connections.
I am using FileZilla as the server and a DNS service, so that I wouldn't have to use my local machine IP (but I've tried the following methods on both).
After trying System.Net.FtpWebRequest to work, I've read around (including a few posts on SO) and found out that the SSL support is not very adequate with that library. It was working with regular FTP, but when I tried forcing SSL, I was getting a certificate validation error saying: The remote certificate is invalid according to the validation procedure.
So, I've done some searching around and found Alex FTPS Client library. Here's the code I wrote up:
class FTPSWorker
{
public static void UploadFile(string sourceFile, string targetFile, string ftpIP, string ftpUser, string ftpPass)
{
try
{
using (FTPSClient client = new FTPSClient())
{
client.Connect(ftpIP, new NetworkCredential(ftpUser, ftpPass),
ESSLSupportMode.CredentialsRequired | ESSLSupportMode.DataChannelRequested);
client.SetTransferMode(ETransferMode.Binary);
client.PutFile(sourceFile, targetFile);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Unfortunately, I was getting the same exact certificate error. I can, however, access the FTP server perfectly fine using FileZilla client. So, I figured there would have to be a certificate issue.
I should note that my server was showing the following log entries:
Welcome Message
AUTH TLS
234 Using authentication type TLS
SSL connection established
disconnected
While the client (C# WPF application) was getting this error:
The remote certificate is invalid according to the validation procedure.
This is absolutely exact same error if I use the .NET library and MSDN code.
I've done more research and found solutions similar to these:
The remote certificate is invalid according to the validation procedure
"The remote certificate is invalid according to the validation procedure." using Gmail SMTP server
But they just seem like risky hacks... And while they do work, is there a way to have certification information to appear and maybe have user validate it/install it besides the basic Yes/No that it's currently using?
My code right now (I ditched Alex's library and went back to default .NET):
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(FTPWorker.ValidateServerCertificate);
public class FTPWorker
{
public static void UploadFile(string sourceFile, string targetFile, string ftpIP, string ftpUser, string ftpPass)
{
try
{
string filename = "ftp://" + ftpIP + "/test/" + targetFile;
FtpWebRequest ftpReq = (FtpWebRequest)WebRequest.Create(filename);
ftpReq.Method = WebRequestMethods.Ftp.UploadFile;
ftpReq.Credentials = new NetworkCredential(ftpUser, ftpPass);
ftpReq.UsePassive = true;
ftpReq.EnableSsl = true;
ftpReq.UseBinary = true;
ftpReq.KeepAlive = false;
byte[] b = File.ReadAllBytes(sourceFile);
ftpReq.ContentLength = b.Length;
using (Stream s = ftpReq.GetRequestStream())
{
s.Write(b, 0, b.Length);
}
FtpWebResponse ftpResp = (FtpWebResponse)ftpReq.GetResponse();
if (ftpResp != null)
{
MessageBox.Show(ftpResp.StatusDescription);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
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;
}
}
}
So, for anyone that had the same issue, I ended up just giving the user a warning regarding the certificate and an option to accept or deny based on the link I provided in my original post. In order for a certificate to be validated, it has to be real and not a locally created one. So, that's the only workaround there is for now.
The Alex ftps will do the same certificate validation if you specify it to.
In your client.connect add the remotecertificatevalidationcallback to accept the certificate
client.Connect(ftpIP, new NetworkCredential(ftpUser, ftpPass),
ESSLSupportMode.CredentialsRequired | ESSLSupportMode.DataChannelRequested,
new RemoteCertificateValidationCallback(ValidateTestServerCertificate));
Then below.
private static bool ValidateTestServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// Accept any certificate
return true;
}
I wanted to use the default .net. but I'm stuck connecting to a server that's using implicit. :(