CoralReactor implements its own non-blocking SSLSocketChannel so you can have out-of-box support for SSL. In this article we show how you can connect to a SSL server (https, wss, etc.) easily with a CoralReactor client.
A Simple HTTP Client
For plain-text HTTP port 80 it is extremely easy. For example, to connect to www.google.com and fetch the HTTP response headers you can write the simple cliente below:
package com.coralblocks.coralreactor.client.ssl;
import static com.coralblocks.corallog.Log.*;
import java.net.URL;
import java.nio.ByteBuffer;
import com.coralblocks.coralbits.util.ByteBufferUtils;
import com.coralblocks.coralreactor.client.AbstractLineTcpClient;
import com.coralblocks.coralreactor.client.Client;
import com.coralblocks.coralreactor.nio.NioReactor;
public class SSLClient extends AbstractLineTcpClient {
public SSLClient(NioReactor nio, String host, int port) {
super(nio, host, port);
}
@Override
protected void handleConnectionOpened() {
send("GET / HTTP/1.0\n");
}
@Override
protected void handleMessage(ByteBuffer msg) {
// only print the HTTP response headers
String s = ByteBufferUtils.parseString(msg);
if (s.startsWith("<")) {
close();
} else {
System.out.println(s);
}
}
public static void main(String[] args) throws Exception {
URL url = new URL("http://www.google.com"); // note we are using HTTP (port 80)
String proto = url.getProtocol();
String host = url.getHost();
int port = url.getDefaultPort();
NioReactor nio = NioReactor.create();
Info.log("Connecting...", "url=", url, "host=", host, "port=", port, "proto=", proto);
final Client client = new SSLClient(nio, host, port);
client.open();
nio.start();
}
}
And the output:
22:13:40.350783-INFO Connecting... url=http://www.google.com host=www.google.com port=80 proto=http 22:13:40.372449-INFO SSLClient-www.google.com:80 Client opened! sequence=1 session=null 22:13:40.390583-INFO NioReactor Reactor started! type=OptimumNioReactor impl=KQueueSelectorImpl 22:13:40.595306-INFO SSLClient-www.google.com:80 Connection established! 22:13:40.595489-INFO SSLClient-www.google.com:80 Connection opened! HTTP/1.0 200 OK Date: Thu, 10 Sep 2015 02:13:40 GMT Expires: -1 Cache-Control: private, max-age=0 Content-Type: text/html; charset=ISO-8859-1 P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info." Server: gws X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Set-Cookie: PREF=ID=1111111111111111:FF=0:TM=1441851220:LM=1441851220:V=1:S=XSWC8Y8PJthovQv9; expires=Thu, 31-Dec-2015 16:02:17 GMT; path=/; domain=.google.com Set-Cookie: NID=71=uGm6eP_jn9OmofaZ4RX10EFlALI8NmfX9jnfSiLrNlWPngHQdR1q_pl2QifvtlJJBmPi6_Dmoacg8pP2A8TgifnQ7EIxQAOoR15DkRohQrBUKn1nFauUkoUwSwgIKgPv; expires=Fri, 11-Mar-2016 02:13:40 GMT; path=/; domain=.google.com; HttpOnly Accept-Ranges: none Vary: Accept-Encoding 22:13:40.839168-INFO SSLClient-www.google.com:80 Client was shutdown 22:13:40.839329-INFO SSLClient-www.google.com:80 Client closed!
Switching to HTTPS
SSL in CoralReactor is meant to be plug-and-play: CoralReactor automatically fetches and trusts the server’s certificate, so you don’t have to worry about certificates to make SSL work. All you have to do is turn it on with a config parameter (i.e. useSSL) and pass the appropriate SSL port (i.e. 443) to your client:
public static void main(String[] args) throws Exception {
URL url = new URL("https://www.google.com"); // note we are using HTTPS now (port 443)
String proto = url.getProtocol();
String host = url.getHost();
int port = url.getDefaultPort();
NioReactor nio = NioReactor.create();
MapConfiguration config = new MapConfiguration();
config.add("useSSL", true); // tell CoralReactor that we want to use SSL
Info.log("Connecting...", "url=", url, "host=", host, "port=", port, "proto=", proto);
final Client client = new SSLClient(nio, host, port, config);
client.open();
nio.start();
}
And the output:
22:51:00.100277-INFO Connecting... url=https://www.google.com host=www.google.com port=443 proto=https 22:51:00.118394-INFO SSLClient-www.google.com:443 Client opened! sequence=1 session=null 22:51:00.880822-INFO NioReactor Reactor started! type=OptimumNioReactor impl=KQueueSelectorImpl 22:51:01.055298-INFO SSLClient-www.google.com:443 Connection established! 22:51:01.055608-INFO SSLClient-www.google.com:443 Connection opened! HTTP/1.0 200 OK Date: Thu, 10 Sep 2015 02:51:01 GMT Expires: -1 Cache-Control: private, max-age=0 Content-Type: text/html; charset=ISO-8859-1 P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info." Server: gws X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Set-Cookie: PREF=ID=1111111111111111:FF=0:TM=1441853461:LM=1441853461:V=1:S=W287StjqyNrsY-rC; expires=Thu, 31-Dec-2015 16:02:17 GMT; path=/; domain=.google.com Set-Cookie: NID=71=Z-T634Zo9qGSn9GbdTlmX5KFeU6ZZrzVySqrtfJWuD_nwFbo8Qlsm3EzeRCgiybqBmnW7Mkmn0IdGTVgv6nMaUrX3YtvfsRzQH-FgmJAzGpVv1y9WV2DaLa3UNPgY1uY; expires=Fri, 11-Mar-2016 02:51:01 GMT; path=/; domain=.google.com; HttpOnly Alternate-Protocol: 443:quic,p=1 Alt-Svc: quic=":443"; p="1"; ma=604800 Accept-Ranges: none Vary: Accept-Encoding 22:51:01.283551-INFO SSLClient-www.google.com:443 Client was shutdown 22:51:01.283760-INFO SSLClient-www.google.com:443 Client closed!
When is it plug-and-play?
Just like a browser, CoralReactor automatically trusts servers whose certificate is issued by a well-known certificate authority (the ones your Java installation already trusts). That is the case for most servers on the internet, including many exchanges. So if your exchange (or server) gave you only a host and a port, useSSL is all you need.
Two tips:
- Use the server’s host name (i.e. fix.exchange.com) and not its IP address when you have one. Many servers need the host name during the SSL handshake to present the right certificate, and some refuse the connection without it.
- If the connection does not work, see Troubleshooting below: CoralReactor will tell you what is wrong.
When it is not plug-and-play: what did your exchange give you?
Some exchanges require extra files, which they send you when you sign up. If they did not send you any file, skip this section. Otherwise, it is one of the two cases below (or both). In both cases you end up with a single keystore file that you pass to CoralReactor with two config parameters. The well-known certificate authorities keep working too: the keystore only adds to them.
1. A CA certificate (a .cer, .crt or .pem file)
The exchange uses its own certificate authority, which Java does not know about, so CoralReactor refuses to trust it until you tell it to. Put the CA certificate in a keystore with keytool (it comes with Java):
$ keytool -importcert -noprompt -alias exchange -file exchange-ca.crt -keystore exchange.jks -storepass mypassword
And tell CoralReactor to use it:
MapConfiguration config = new MapConfiguration();
config.add("useSSL", true);
config.add("sslKeyStoreFile", "/path/to/exchange.jks"); // the keystore you created above
config.add("sslKeyStorePassword", "mypassword");
2. A client certificate (a .p12 or .pfx file, plus its password)
The exchange wants to identify you with a certificate of your own. Use the file exactly as they sent it, there is nothing to convert or create.
If the exchange also gave you a CA certificate (case 1 above), add it to the same file:
$ keytool -importcert -noprompt -alias exchange -file exchange-ca.crt -keystore client.p12 -storepass thepassword
And tell CoralReactor to use it:
MapConfiguration config = new MapConfiguration();
config.add("useSSL", true);
config.add("sslKeyStoreFile", "/path/to/client.p12"); // the file the exchange sent you
config.add("sslKeyStorePassword", "thepassword"); // the password the exchange gave you
Connecting through an HTTP proxy
Skip this section if your client can connect to the exchange directly. Use it if your company only lets you reach the exchange through an HTTP proxy.
What you need
Two addresses:
- The proxy address and port, i.e. proxy.mycompany.com:8080
- The exchange address and port, i.e. gateway.exchange.com:443
Step 1: connect your client to the proxy
Give your client the proxy address and port, not the exchange’s:
MapConfiguration config = new MapConfiguration();
config.add("useSSL", true);
final Client client = new SSLClient(nio, "proxy.mycompany.com", 8080, config);
Step 2: tell CoralReactor where the exchange is
Add the exchange address and port to the config:
MapConfiguration config = new MapConfiguration();
config.add("useSSL", true);
config.add("sslProxyConnect", "gateway.exchange.com:443"); // or pass through -DsslProxyConnect=gateway.exchange.com:443
final Client client = new SSLClient(nio, "proxy.mycompany.com", 8080, config);
CoralReactor will ask the proxy to connect to the exchange and then talk SSL with the exchange through it.
Step 3: certificates, only if the exchange gave you files
Nothing changes here compared to a direct connection: if the exchange gave you no files, there is nothing to do. If it gave you a CA certificate or a client certificate, configure it exactly as explained in cases 1 and 2 above.
Step 4: check it works
The first time, start Java with -DsslDebug=true too. When everything is right, the log shows:
Proxy mode: tunnel to gateway.exchange.com:443 is open (HTTP/1.1 200 Connection established)
followed by the SSL handshake with the exchange. If the proxy refuses, the log says so, i.e. The HTTP proxy … refused to connect to gateway.exchange.com:443 (sslProxyConnect), its answer was: HTTP/1.1 403 Forbidden: then ask your network team to allow that address and port on the proxy.
Without sslProxyConnect nothing changes: CoralReactor connects directly, as usual.
Troubleshooting
If the connection does not work, run your client with -DsslDebug=true. CoralReactor will then log every step of the SSL conversation in plain words and, when something fails, what went wrong and what usually causes it. Some examples:
- the server requires a (valid) client certificate: see case 2 above.
- the server certificate is not trusted: see case 1 above.
- the server certificate has expired: the exchange must renew it (also check your machine’s date and time).
- no SNI (server name indication) was sent: use the host name of the server instead of its IP address.
Failures are logged even without -DsslDebug=true. If you still need help, send us that log. For the lowest level details of the handshake you can also use Java’s own -Djavax.net.debug=ssl:handshake.
Using Stunnel as a SSL Proxy
For a cleaner alternative you can also use stunnel as a SSL proxy. For example to connect to google.com:443 use the stunnel.conf file below:
[remote] client = yes accept = 8888 connect = www.google.com:443
Then run stunnel:
$ sudo stunnel stunnel.conf
You can easily test with netcat with the command-line below:
$ cat <(echo -e "GET / HTTP/1.0\n") | nc localhost 8888 | head -n 9 HTTP/1.0 200 OK Date: Thu, 10 Sep 2015 03:09:13 GMT Expires: -1 Cache-Control: private, max-age=0 Content-Type: text/html; charset=ISO-8859-1 P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info." Server: gws X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN