We have a web application running on server and it posts http requests via XDomainRequest (because of IE9).
There are lots of client computers which have a console application listening on a port via socket listener. Clients open web application with their IE9 browsers and when they click on a link, the web page sends requests like that:
"https://localhost:portNumber/applicationName/doSomething" "https://computerName:portNumber/applicationName/doSomething" "https://ipAddress:portNumber/applicationName/doSomething"
The second and third requests are made to console applications of other users' computers.
The problem is that if requests come with localhost, console application does not have a problem about reading incoming data and sending response back. But if the request comes with computer name or ip address then browser shows certification warning and wants user to click on "Continue to this web site (not recommended)" link.
We thought creating three different certificates via code. But even using sslstream with three of them is possible we cannot decide to select true certification because we make authenticatiton first and then receive data. So when we catch incoming request the authentication must already be done.
Another way is forcing socket listener or sslstream to behave all these three requests as if they are localhost. So for each one authentication will be made as localhost. But I could not find an actual way for that.
Here is the code. I give the code because maybe there is some wrong usage of SslStream.
using System;
using System.Net.Sockets;
using System.Net;
using System.Configuration;
using System.Security.Cryptography.X509Certificates;
using System.Windows.Forms;
using System.IO;
using System.Net.Security;
using System.Security.Authentication;
using System.Threading;
using System.Text;
namespace StackOverFlowProject
{
class StackOverFlowSample
{
private static ManualResetEvent _manualResetEvent = new ManualResetEvent(false);
private static X509Certificate _cert = null;
static void Main(string[] args)
{
StackOverFlowSample stackOverFlowSample = new StackOverFlowSample();
stackOverFlowSample.StartListening();
}
private void StartListening()
{
GetCertificate();
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 1234);
if (localEndPoint != null)
{
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
if (listener != null)
{
listener.Bind(localEndPoint);
listener.Listen(10);
Console.WriteLine("Socket listener is running. Waiting for requests...");
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
}
}
}
private static void GetCertificate()
{
byte[] pfxData = File.ReadAllBytes(Application.StartupPath + @"\" + "localhost.pfx");
_cert = new X509Certificate2(pfxData, "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
}
private void AcceptCallback(IAsyncResult result)
{
Socket listener = null;
Socket handler = null;
StateObject state = null;
SslStream sslStream = null;
_manualResetEvent.Set();
listener = (Socket)result.AsyncState;
handler = listener.EndAccept(result);
state = new StateObject();
if (handler.RemoteEndPoint != null)
{
state.clientIP = ((IPEndPoint)handler.RemoteEndPoint).Address.ToString();
}
sslStream = new SslStream(new NetworkStream(handler, true));
sslStream.AuthenticateAsServer(_cert, false, SslProtocols.Tls, true);
sslStream.ReadTimeout = 100000;
sslStream.WriteTimeout = 100000;
state.workStream = sslStream;
if (state.workStream.IsAuthenticated)
{
state.workStream.BeginRead(state.buffer, 0, StateObject.BufferSize, ReceiveCallback, state);
}
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
}
private void ReceiveCallback(IAsyncResult result)
{
StateObject stateObject = null;
SslStream sslStreamReader = null;
byte[] byteData = null;
stateObject = (StateObject)result.AsyncState;
sslStreamReader = stateObject.workStream;
int byteCount = sslStreamReader.EndRead(result);
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(stateObject.buffer, 0, byteCount)];
decoder.GetChars(stateObject.buffer, 0, byteCount, chars, 0);
stateObject.sb.Append(chars);
if (byteCount > 0)
{
stateObject.totalReceivedBytes += byteCount;
string[] lines = stateObject.sb.ToString().Split('\n');
if (lines[lines.Length - 1] != "<EOF>")
{
// We didn't receive all data. Continue reading...
sslStreamReader.BeginRead(stateObject.buffer, 0, stateObject.buffer.Length, new AsyncCallback(ReceiveCallback), stateObject);
}
else
{
Console.WriteLine("We received all data. Sending response...");
byteData = Encoding.UTF8.GetBytes("Hello! I received your request!");
string httpHeaders = "HTTP/1.1" + "\r\n"
+ "Cache-Control: no-cache" + "\r\n"
+ "Access-Control-Allow-Origin: *" + "\r\n"
+ "\r\n";
byte[] byteHttpHeaders = Encoding.UTF8.GetBytes(httpHeaders);
byte[] concat = new byte[byteHttpHeaders.Length + byteData.Length];
Buffer.BlockCopy(byteHttpHeaders, 0, concat, 0, byteHttpHeaders.Length);
Buffer.BlockCopy(byteData, 0, concat, byteHttpHeaders.Length, byteData.Length);
stateObject.sslStreamReader = sslStreamReader;
sslStreamReader.BeginWrite(concat, 0, concat.Length, new AsyncCallback(SendCallback), stateObject);
}
}
}
private void SendCallback(IAsyncResult ar)
{
SslStream sslStreamSender = null;
StateObject stateObject = (StateObject)ar.AsyncState;
sslStreamSender = stateObject.sslStreamReader;
sslStreamSender.EndWrite(ar);
Console.WriteLine(stateObject.totalReceivedBytes.ToString() + " bytes sent to " + stateObject.clientIP + " address");
sslStreamSender.Close();
sslStreamSender.Dispose();
}
}
public class StateObject
{
public SslStream workStream = null;
public SslStream sslStreamReader = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
public string clientIP = "";
public int totalReceivedBytes = 0;
}
}
Solution I am proposing is having assumption:
site is hosted on IIS
you can access application by machine name across network without any issue.
you need not to access webapp on internet(that will need cetificate from certified authority).
Try:
Create self signed certificate by IIS like :
i. Open IIS, select Server certificates like:
ii. Create self signed certificate like:![enter image description here](https://i.stack.imgur.com/X4sJo.png)
iii. configure website to use that certificate like:
select website, click bindings.
click edit bindings for https
iv. Edit site binding to use certificate you have created like :
Now you can access you application with machine name.
Same process you can repeat for domain certificates for internet but that requires certificate registered/provided by some certified authority.
Please note that now use can access application with Machine Name on both local & on network. Since certificate are issued for single host name/co-domain so use machine name instead of localhost on local machine too.
The certificate warning that you are encountering is really a name mismatch error, which indicates that the common name (domain name) in the SSL certificate does not match the URL/address used to access the web site/server.
https://www.sslshopper.com/ssl-certificate-name-mismatch-error.html
In your usage scenario, you may want to transition away from localhost and ip addresses in favor of a simple domain model that leverages the computer name. (e.g.
computerName.someDomain.com
)Then, you could obtain a wildcard certificate (e.g.
*.someDomain.com
) which could be used to authenticate inter-process communication.https://www.sslshopper.com/best-ssl-wildcard-certificate.html
I am not sure what your
"https://computerName:portNumber/applicationName/doSomething"
means. So not sure if you are using the certificate right or whether the paths/connection that you are accessing or using in code are correct. https://technet.microsoft.com/en-us/library/dd891009.aspxIt can access any protocol not restricted to TCP. https://technet.microsoft.com/en-us/library/cc784450(v=ws.10).aspx
** The value must match exactly with the hostname or domain name you access. Otherwise you will still receive browser certificate errors after the certificate has been imported and trusted. Eg: If you were previously using
http://101.10.10.1:9000/
to access the site and the common name of your new certificate is "mydomain", then the shortcut should be updated tohttp://mydomain:9000/
; if you created a certificate formydomain:9000
. Second, Use the hostname or CNAME by which you will address the server. This is very important. If your web server’s real hostname is mysubdomain.mydomain.com but people will be using www.mydomain.com to address the box, then use the latter name to answer the “Common Name” (CNAME) question.** Check if you are able to connect using the
openssl s_time -connect remote.host:443
(use specific ssl commands to troubleshoot, I am citing openssl) or theopenssl s_time -connect remote.host:443 -www /test.html -new
** If you don’t have an SSL-enabled web server available for your use, you can emulate one using the s_server option.# on one host, set up the server (using default port 4433) openssl s_server -cert mycerti.pem -www
# on second host (or even the same one), run s_time openssl s_time -connect mydomain:9001 -www / -new -ssl3
or
# on second host (or even the same one), run s_time openssl s_time -connect mydomain:9001 -www / -new -ssl3
** Check if cert is in use?$ openssl s_client -connect [ip_or_dns_name]:[port] $ man s_client
Your security guy is right. The way you are trying to make this happen wont work with SSL.
If you have a certificate, it is set to authenticate one CN. So, for an oversimplified example. Google has a certificate. It authenticates https://*.google.com. This means that any requests to google.com come up as having a valid certificate. And your browser is happy.
Now open a command prompt, ping google.com. Grab the ip address (in my case it came up as 216.58.210.14). Enter https://216.58.210.14. Your browser complains that the site is insecure etc. The reason being that the server may be the same one that served your earlier request, but the way you are getting to it is not valid according to the certificate,as the CN is not google.com, but an IP address.
So if you have a service which needs to connect to (for eg) 127.0.0.1, 10.92.1.4 AND myserver.com, you will need a cert which is valid for each case.