Using the latest version of Crossbar (0.13, installed from apt-get
on Ubuntu 14.04) I am having trouble making connections using SSL and intermediate certificates.
If I set up the server without a ca_certificates
property in the tls
key then the server runs fine and connections can be made using Google Chrome via the wss
protocol. However trying to make a connection using thruway fails with the following error:
Could not connect: Unable to complete SSL/TLS handshake: stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
Which having spoken with the Thruway team seems to be a certificate issue - on our live site we use an intermediate and cross-signed certificate from Gandi which is needed for some browsers and therefore some open-ssl implementations.
It seems that whilst browsers are happy to make a TLS connection with just a key and cert, Thruway requires a chain. However the configuration below using the two certificates provided by Gandi does not work for either Chrome or Thruway. Chrome shows the error:
failed: WebSocket opening handshake was canceled
When using the .crossbar/config.json
file below. So, is this a problem with my config, with my certificates or with some other part of the Open-SSL stack?
(The file below has been altered to remove any potentially sensitive information so may appear like it wouldn't work for other reasons. If the connection works the underlying auth and other components work fine, so please keep answers/comments regarding the TLS implementation. The comments are not valid JSON but are included so readers can see the certificate files in use)
{
"version": 2,
"controller": {},
"workers": [
{
"type": "router",
"realms": [
{
"name": "test",
"roles": [
{
"name": "web",
"authorizer": "test.utils.permissions",
"disclose": {
"caller": true,
"publisher": true
}
},
{
"name": "no",
"permissions": []
}
]
}
],
"transports": [
{
"type": "websocket",
"endpoint": {
"type": "tcp",
"port": 9001,
"interface": "127.0.0.1"
},
"auth": {
"wampcra": {
"type": "static",
"users": {
"authenticator": {
"secret": "authenticator-REDACTED",
"role": "authenticator"
}
}
}
}
},
{
"type": "web",
"endpoint": {
"type": "tcp",
"port": 8089,
"tls": {
"key": "../ssl/key.pem",
"certificate": "../ssl/cert.pem",
"ca_certificates": [
"../ssl/gandi.pem", // https://www.gandi.net/static/CAs/GandiProSSLCA2.pem
"../ssl/gandi-cross-signed.pem" // https://wiki.gandi.net/en/ssl/intermediate#comodo_cross-signed_certificate
],
"dhparam": "../ssl/dhparam.pem"
}
},
"paths": {
"/": {
"type": "static",
"directory": "../web"
},
"ws": {
"type": "websocket",
"url": "wss://OUR-DOMAIN.com:8089/ws",
"auth": {
"wampcra": {
"type": "dynamic",
"authenticator": "test.utils.authenticate"
}
}
}
}
}
]
},
{
"type": "guest",
"executable": "/usr/bin/env",
"arguments": [
"php",
"../test.php",
"ws://127.0.0.1:9001",
"test",
"authenticator",
"authenticator-REDACTED"
]
}
]
}
There are other questions which address issues similar to this@
apt-get
This is not an issue with Crossbar. This appears to be a problem with the WAMP client - Thruway. Davidwdan is the owner of the Thruway Github repo and he says:
"Thruway's Ratchet transport provider does not directly support SSL. You'll need to put some sort of proxy in front of it."
You can find more information regarding what Davidwdan and others have to say about this right here https://github.com/voryx/Thruway/issues/163.
Now to get to the solution. Mind you, the following is only for Apache users. If you are running on Nginx the idea is pretty much the same.
A couple things to note before we get started.
The first thing you want to do is create a new virtual host. I chose port 4043 for this virtual host, but you can choose whatever you would like. This virtual host is going to be for every WAMP library that does NOT have an issue connecting via wss:// (with an SSL). Here is a full list of WAMP clients: http://wamp-proto.org/implementations/. Make sure the ProxyPass directive and the ProxyPassReverse directive has the IP address pointing to the machine that the CROSSBAR router exists on. In my case since Apache and Crossbar are running on the same machine I just use 127.0.0.1. Also make sure the port being used in the ProxyPass directive and the ProxyPassReverse directive is the exact same as the port that you defined in your .crossbar/config.json! You will also need an SSL certificate set up on this virtual host as well, which you can see I have added below the Proxy directives.
Listen 4043
<VirtualHost *:4043>
ServerName example.org
ProxyRequests off
SSLProxyEngine on
ProxyPass /ws/ ws://127.0.0.1:8000/
ProxyPassReverse /ws/ ws://127.0.0.1:8000/
## Custom fragment
SSLEngine on
SSLCertificateFile /path/to/server_cert.pem
SSLCertificateKeyFile /path/to/server_key.pem
SSLCertificateChainFile /path/to/server_ca.pem
</VirtualHost>
Next, make sure your Crossbar router is NOT setup with an SSL! This is super important. Thruway or any other library that is NOT able to connect via SSL WON'T be able to use the router if you have it configured to use an SSL! Below is a working Crossbar config.json file that you would be able to use.
{
"version": 2,
"controller": {},
"workers": [
{
"type": "router",
"realms": [
{
"name": "production_realm",
"roles": [
{
"name": "production_role",
"permissions": [
{
"uri": "",
"match": "prefix",
"allow": {
"call": true,
"register": true,
"publish": true,
"subscribe": true
}
}
]
}
]
}
],
"transports": [
{
"type": "websocket",
"endpoint": {
"type": "tcp",
"port": 8000
},
"options": {
"allowed_origins": ["http://*","https://*"]
},
"auth": {
"ticket": {
"type": "static",
"principals": {
"production_user": {
"ticket": "tSjlwueuireladgte",
"role": "production_role"
}
}
}
}
}
]
}
]
}
Notice how the port number defined above matches the port number defined in the virtual host.
./crossbar/config.json:
"endpoint": {
"type": "tcp",
"port": 8000
},
virtual host:
ProxyPass /ws/ ws://127.0.0.1:8000/
ProxyPassReverse /ws/ ws://127.0.0.1:8000/
Also, if you read other tutorials, some people will tell you to make sure you use the ProxyPreserveHost directive in your virtual host file. DON'T LISTEN TO THEM! This will produce lots of unexpected results. When this directive is enabled, this option will pass the Host: line from the incoming request to the proxied host, instead of the hostname specified in the ProxyPass line! Even Apache says to stay away from this directive https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#proxypreservehost. If you do have it enabled you will receive an error similar to below:
failing WebSocket opening handshake ('missing port in HTTP Host header
'example.org' and server runs on non-standard port 8000 (wss =
False)')
Last but not least, make sure all of the following Apache libraries are installed and enabled. On recent Apache installations all of the following libraries come installed by default and just need to be enabled:
$ sudo a2enmod proxy
$ sudo a2enmod proxy_http
$ sudo a2enmod proxy_balancer
$ sudo a2enmod lbmethod_byrequests
$ sudo a2enmod proxy_wstunnel
Make sure you open up whichever port your virtual host file is listening on and whichever port your crossbar router is listening on. In my case:
$ sudo ufw allow 4043
$ sudo ufw allow 8000
And finally restart Apache so all your changes can take effect.
$ sudo service apache2 restart
Last but not least I want to give a quick explanation of why all of this has to be done:
And well.... That's all folks! I know I needed to give a detailed answer to this question because it took me 5 days to figure all of this out!