Tried my luck at research, but so far no joy.
I would like to connect a SignalR javascript client to a self-hosted SignalR Windows Service binding to a self-signed SSL certificate.
My application works quite well over http, but the client repetitively disconnects when the Owin WebApplication starts using https.
Here is what I've done to configure SignalR with SSL.
- Created a Self-Signed certificate using IIS
- Imported the certificate into the Trusted Root Certification Authorities in the mmc (not sure if that helped)
Ran NETSH command to bind SSL to port 8080
netsh http add sslcert ipport=0.0.0.0:8080 certhash=123456f6790a35f4b017b55d09e28f7ebe001bd appid={12345678-db90-4b66-8b01-88f7af2e36bf}
Added code in self-hosted HubConnection instances to add exported SSL like this (though this shouldn't matter because it's the client that cannot connect):
if (File.Exists("MyCert.cer")
&& Settings.GetSetting(Settings.Setting.SrProtocol).Equals("https", StringComparison.InvariantCultureIgnoreCase))
connection.AddClientCertificate(X509Certificate.CreateFromCertFile("MyCert.cer"));
Starting Owin WebApplication using https (this should create the binding in http.sys)
string registerUrl = string.Format("{0}://SOME.WHERE.COM:{1}", Service.Server.SrProtocol, Service.Server.SrPort);
WebApp.Start<StartUp>(registerUrl);
In the SignalR 2.0 documentation, it says:
To start the web server, call WebApplication.Start(endpoint). You should now be able to navigate to endpoint/signalr/hubs in your browser.
When I browse to the URL http://SOME.WHERE.COM:8080/signalr/hubs I am successful receiving the javascript that drives SignalR.
When I browse to the URL https://SOME.WHERE.COM:8080/signalr/hubs I am unsuccessful and I receive "The connection to the server was reset" using FF.
Some additional points I've considered:
NETSH SHOW indicates the url is registered
URL group ID: E300000240000022
State: Active
Request queue name: Request queue is unnamed.
Properties:
Max bandwidth: inherited
Max connections: inherited
Timeouts:
Timeout values inherited
Number of registered URLs: 1
Registered URLs: HTTPS://SOME.WHERE.COM:8080/
NETSH SHOW indicates the SSL certificate is bound to 8080:
IP:port : 0.0.0.0:8080
Certificate Hash : 123456f6790a35f4b017b55d09e28f7ebe001bd
Application ID : {12345678-db90-4b66-8b01-88f7af2e36bf}
Certificate Store Name : (null)
Verify Client Certificate Revocation : Enabled
Verify Revocation Using Cached Client Certificate Only : Disabled
Usage Check : Enabled
Revocation Freshness Time : 0
URL Retrieval Timeout : 0
Ctl Identifier : (null)
Ctl Store Name : (null)
DS Mapper Usage : Disabled
Negotiate Client Certificate : Disabled
Any help is greatly appreciated!
I believe its all working for me now. Here is a run down of the steps I took to get things flowing:
SSL NOTES
SSL & SignalR (Owin WebApplication) requires binding a certificate to a port.
- Use IIS to generate an self-signed cert, this should place the certificate into the LOCAL COMPUTER > Personal > Certificates folder in CERTMGR
- In CERTMGR shift+drag certificate to LOCAL COMPUTER > Trusted Root Certification Authorities > Certificates folder, which should make a copy of it there
Run the following command to bind the SSL certificate to 0.0.0.0:8080
netsh http add sslcert ipport=0.0.0.0:8080 certhash=123456f6790a35f4b017b55d09e28f7ebe001bd appid={12345678-db90-4b66-8b01-88f7af2e36bf}
netsh http show urlacl > D:\urlacl.txt
Output:
Reserved URL : https://*:8080/
User: SOMEWHERE\Administrator
Listen: Yes
Delegate: No
SDDL: D:(A;;GX;;;S-1-5-21-138209071-46972887-2260295844-1106)
Run the following NETSH command to reserve all IP addresses for port 8080 to the My Service application ID and service account
netsh http add urlacl url=https://*:8080/ user=SOMEWHERE\Administrator listen=yes
netsh http show sslcert > D:\sslcert.txt
Output:
IP:port : 0.0.0.0:8080
Certificate Hash : 123456f6790a35f4b017b55d09e28f7ebe001bd
Application ID : {12345678-db90-4b66-8b01-88f7af2e36bf}
Certificate Store Name : (null)
Verify Client Certificate Revocation : Enabled
Verify Revocation Using Cached Client Certificate Only : Disabled
Usage Check : Enabled
Revocation Freshness Time : 0
URL Retrieval Timeout : 0
Ctl Identifier : (null)
Ctl Store Name : (null)
DS Mapper Usage : Disabled
Negotiate Client Certificate : Disabled
Update the MyServices.exe.config file to use https protocol (These are appSetting keys used to dynamically set the protocol and port of SignalR when My Service starts)
<add key="SrProtocol" value="https" />
<add key="SrPort" value="8080" />
Start the My Service using the NETSTAT START command
Run the following NETSH command to show the service state is occupying the registered url
netsh http show servicestate > D:\servicestate.txt
Output:
Server session ID: C300000320000039
Version: 2.0
State: Active
Properties:
Max bandwidth: 4294967295
Timeouts:
Entity body timeout (secs): 120
Drain entity body timeout (secs): 120
Request queue timeout (secs): 120
Idle connection timeout (secs): 120
Header wait timeout (secs): 120
Minimum send rate (bytes/sec): 150
URL groups:
URL group ID: C600000340000138
State: Active
Request queue name: Request queue is unnamed.
Properties:
Max bandwidth: inherited
Max connections: inherited
Timeouts:
Timeout values inherited
Number of registered URLs: 1
Registered URLs:
HTTPS://*:8080/
My application does NOT depend on IIS, but once I used IIS to temporarily create a port binding to my SSL certificate, my application started to work, and I was able to inspect the NETSH servicestate to see how IIS does it. I have since dropped the IIS binding and ran through the setup notes, and still have success.
My Owing startup looks somethign like this:
private void configureMessaging()
{
string registerUrl = string.Format("{0}://*:{1}", Service.Server.SrProtocol, Service.Server.SrPort);
try
{
#if DEBUG
//System.Diagnostics.Debugger.Launch();
#endif
// Starts an owin web application to host SignalR, using the protocol and port defined.
WebApp.Start<StartUp>(registerUrl);
}
catch (Exception ex)
{
Logger.Logs.Log(string.Format("Failed to configure messaging. Exception: {0}", ex.RecurseInnerException()), LogType.Error);
if (ex is HttpListenerException || ex.InnerException is HttpListenerException)
{
try
{
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "netsh.exe";
p.StartInfo.Arguments = string.Format("netsh http delete urlacl url={0}"
, registerUrl
);
p.Start();
p.StandardOutput.ReadToEnd();
p.WaitForExit();
}
catch (Exception exP)
{
Logger.Logs.Log(string.Format("Failed to delete urlacl {0}. Exception: {1}"
, registerUrl
, exP.RecurseInnerException()
)
, LogType.Error
)
;
retries = 5;
}
}
if (retries < 5)
{
retries++;
Logger.Logs.Log(string.Format("Attempting to configure messaging again. Attempt No. {0}", retries), LogType.Warn);
Thread.Sleep(1000);
configureMessaging();
}
else
Logger.Logs.Log(string.Format("Exceeded total number of retries to configure messaging.", retries), LogType.Error);
}
}
And self-hosted HubConnetion instances look like this:
public IHubProxy MyHubProxy
{
get
{
if (this._MyHubProxy == null)
{
var connection = new HubConnection(string.Format("{0}://{1}:{2}/"
, Settings.GetSetting(Settings.Setting.SrProtocol)
, MyHub.GetLocalhostFqdn(null)
, Settings.GetSetting(Settings.Setting.SrPort)
)
)
;
this._MyHubProxy = connection.CreateHubProxy("MyHub");
if (File.Exists("My.cer")
&& Settings.GetSetting(Settings.Setting.SrProtocol).Equals("https", StringComparison.InvariantCultureIgnoreCase))
connection.AddClientCertificate(X509Certificate.CreateFromCertFile("My.cer"));
connection.Start().Wait();
}
return this._MyHubProxy;
}
}
There is a little more code here than relevant, but hopefully it may be of help!