From fe2fd4413a247ffd91131b5c7e958d86fdf5b9e2 Mon Sep 17 00:00:00 2001 From: Naoyuki Kanezawa Date: Mon, 7 Jul 2014 22:41:59 +0900 Subject: [PATCH] fix #3 enable to set SSLContext as an option --- .../github/nkzawa/engineio/client/Socket.java | 4 + .../nkzawa/engineio/client/Transport.java | 4 + .../client/transports/PollingXHR.java | 18 ++- .../engineio/client/transports/WebSocket.java | 4 + .../nkzawa/engineio/client/BinaryWSTest.java | 2 +- .../nkzawa/engineio/client/Connection.java | 12 +- .../engineio/client/ConnectionTest.java | 16 +-- .../engineio/client/SSLConnectionTest.java | 122 ++++++++++++++++++ .../engineio/client/ServerConnectionTest.java | 26 ++-- src/test/resources/cert.pem | 10 ++ src/test/resources/key.pem | 9 ++ src/test/resources/keystore.jks | Bin 0 -> 859 bytes src/test/resources/{index.js => server.js} | 17 ++- 13 files changed, 208 insertions(+), 36 deletions(-) create mode 100644 src/test/java/com/github/nkzawa/engineio/client/SSLConnectionTest.java create mode 100644 src/test/resources/cert.pem create mode 100644 src/test/resources/key.pem create mode 100644 src/test/resources/keystore.jks rename src/test/resources/{index.js => server.js} (75%) diff --git a/src/main/java/com/github/nkzawa/engineio/client/Socket.java b/src/main/java/com/github/nkzawa/engineio/client/Socket.java index a706ecf..7372169 100644 --- a/src/main/java/com/github/nkzawa/engineio/client/Socket.java +++ b/src/main/java/com/github/nkzawa/engineio/client/Socket.java @@ -10,6 +10,7 @@ import com.github.nkzawa.parseqs.ParseQS; import com.github.nkzawa.thread.EventThread; import org.json.JSONException; +import javax.net.ssl.SSLContext; import java.net.URI; import java.net.URISyntaxException; import java.util.*; @@ -117,6 +118,7 @@ public class Socket extends Emitter { /*package*/ Transport transport; private Future pingTimeoutTimer; private Future pingIntervalTimer; + private SSLContext sslContext; private ReadyState readyState; private ScheduledExecutorService heartbeatScheduler = Executors.newSingleThreadScheduledExecutor(); @@ -165,6 +167,7 @@ public class Socket extends Emitter { } this.secure = opts.secure; + this.sslContext = opts.sslContext; this.hostname = opts.hostname != null ? opts.hostname : "localhost"; this.port = opts.port != 0 ? opts.port : (this.secure ? 443 : 80); this.query = opts.query != null ? @@ -211,6 +214,7 @@ public class Socket extends Emitter { } Transport.Options opts = new Transport.Options(); + opts.sslContext = this.sslContext; opts.hostname = this.hostname; opts.port = this.port; opts.secure = this.secure; diff --git a/src/main/java/com/github/nkzawa/engineio/client/Transport.java b/src/main/java/com/github/nkzawa/engineio/client/Transport.java index 6ffbf3f..d47347c 100644 --- a/src/main/java/com/github/nkzawa/engineio/client/Transport.java +++ b/src/main/java/com/github/nkzawa/engineio/client/Transport.java @@ -6,6 +6,7 @@ import com.github.nkzawa.engineio.parser.Packet; import com.github.nkzawa.engineio.parser.Parser; import com.github.nkzawa.thread.EventThread; +import javax.net.ssl.SSLContext; import java.util.Map; public abstract class Transport extends Emitter { @@ -39,6 +40,7 @@ public abstract class Transport extends Emitter { protected String path; protected String hostname; protected String timestampParam; + protected SSLContext sslContext; protected Socket socket; protected ReadyState readyState; @@ -51,6 +53,7 @@ public abstract class Transport extends Emitter { this.query = opts.query; this.timestampParam = opts.timestampParam; this.timestampRequests = opts.timestampRequests; + this.sslContext = opts.sslContext; this.socket = opts.socket; } @@ -140,6 +143,7 @@ public abstract class Transport extends Emitter { public int port; public int policyPort; public Map query; + public SSLContext sslContext; protected Socket socket; } } diff --git a/src/main/java/com/github/nkzawa/engineio/client/transports/PollingXHR.java b/src/main/java/com/github/nkzawa/engineio/client/transports/PollingXHR.java index daf71d4..8e54ec4 100644 --- a/src/main/java/com/github/nkzawa/engineio/client/transports/PollingXHR.java +++ b/src/main/java/com/github/nkzawa/engineio/client/transports/PollingXHR.java @@ -4,6 +4,8 @@ package com.github.nkzawa.engineio.client.transports; import com.github.nkzawa.emitter.Emitter; import com.github.nkzawa.thread.EventThread; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; @@ -36,6 +38,7 @@ public class PollingXHR extends Polling { opts = new Request.Options(); } opts.uri = this.uri(); + opts.sslContext = this.sslContext; Request req = new Request(opts); @@ -141,15 +144,17 @@ public class PollingXHR extends Polling { private static final ExecutorService xhrService = Executors.newCachedThreadPool(); - String method; - String uri; - byte[] data; - HttpURLConnection xhr; + private String method; + private String uri; + private byte[] data; + private SSLContext sslContext; + private HttpURLConnection xhr; public Request(Options opts) { this.method = opts.method != null ? opts.method : "GET"; this.uri = opts.uri; this.data = opts.data; + this.sslContext = opts.sslContext; } public void create() { @@ -164,6 +169,10 @@ public class PollingXHR extends Polling { return; } + if (xhr instanceof HttpsURLConnection && this.sslContext != null) { + ((HttpsURLConnection)xhr).setSSLSocketFactory(this.sslContext.getSocketFactory()); + } + Map headers = new TreeMap(String.CASE_INSENSITIVE_ORDER); if ("POST".equals(this.method)) { @@ -293,6 +302,7 @@ public class PollingXHR extends Polling { public String uri; public String method; public byte[] data; + public SSLContext sslContext; } } } diff --git a/src/main/java/com/github/nkzawa/engineio/client/transports/WebSocket.java b/src/main/java/com/github/nkzawa/engineio/client/transports/WebSocket.java index 01ed0ec..f7a8c88 100644 --- a/src/main/java/com/github/nkzawa/engineio/client/transports/WebSocket.java +++ b/src/main/java/com/github/nkzawa/engineio/client/transports/WebSocket.java @@ -6,6 +6,7 @@ import com.github.nkzawa.engineio.parser.Packet; import com.github.nkzawa.engineio.parser.Parser; import com.github.nkzawa.parseqs.ParseQS; import com.github.nkzawa.thread.EventThread; +import org.java_websocket.client.DefaultSSLWebSocketClientFactory; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft_17; import org.java_websocket.handshake.ServerHandshake; @@ -93,6 +94,9 @@ public class WebSocket extends Transport { }); } }; + if (this.sslContext != null) { + this.ws.setWebSocketFactory(new DefaultSSLWebSocketClientFactory(this.sslContext)); + } this.ws.connect(); } catch (URISyntaxException e) { throw new RuntimeException(e); diff --git a/src/test/java/com/github/nkzawa/engineio/client/BinaryWSTest.java b/src/test/java/com/github/nkzawa/engineio/client/BinaryWSTest.java index 33985b9..22431a5 100644 --- a/src/test/java/com/github/nkzawa/engineio/client/BinaryWSTest.java +++ b/src/test/java/com/github/nkzawa/engineio/client/BinaryWSTest.java @@ -54,7 +54,7 @@ public class BinaryWSTest extends Connection { } @Test(timeout = TIMEOUT) - public void receiveBinaryDataAndMultiplebyteUTF8String() throws InterruptedException { + public void receiveBinaryDataAndMultibyteUTF8String() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); final byte[] binaryData = new byte[5]; for (int i = 0; i < binaryData.length; i++) { diff --git a/src/test/java/com/github/nkzawa/engineio/client/Connection.java b/src/test/java/com/github/nkzawa/engineio/client/Connection.java index 91fc024..e72b095 100644 --- a/src/test/java/com/github/nkzawa/engineio/client/Connection.java +++ b/src/test/java/com/github/nkzawa/engineio/client/Connection.java @@ -24,7 +24,7 @@ public abstract class Connection { final CountDownLatch latch = new CountDownLatch(1); serverProcess = Runtime.getRuntime().exec( - "node src/test/resources/index.js " + PORT, new String[] {"DEBUG=engine*"}); + "node src/test/resources/server.js", createEnv()); serverService = Executors.newCachedThreadPool(); serverOutout = serverService.submit(new Runnable() { @Override @@ -70,4 +70,14 @@ public abstract class Connection { serverService.shutdown(); serverService.awaitTermination(3000, TimeUnit.MILLISECONDS); } + + Socket.Options createOptions() { + Socket.Options opts = new Socket.Options(); + opts.port = PORT; + return opts; + } + + String[] createEnv() { + return new String[] {"DEBUG=engine*", "PORT=" + PORT}; + } } diff --git a/src/test/java/com/github/nkzawa/engineio/client/ConnectionTest.java b/src/test/java/com/github/nkzawa/engineio/client/ConnectionTest.java index 58f03a8..1524849 100644 --- a/src/test/java/com/github/nkzawa/engineio/client/ConnectionTest.java +++ b/src/test/java/com/github/nkzawa/engineio/client/ConnectionTest.java @@ -21,9 +21,7 @@ public class ConnectionTest extends Connection { public void connectToLocalhost() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - Socket.Options opts = new Socket.Options(); - opts.port = PORT; - socket = new Socket(opts); + socket = new Socket(createOptions()); socket.on(Socket.EVENT_OPEN, new Emitter.Listener() { @Override public void call(Object... args) { @@ -45,9 +43,7 @@ public class ConnectionTest extends Connection { public void receiveMultibyteUTF8StringsWithPolling() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - Socket.Options opts = new Socket.Options(); - opts.port = PORT; - socket = new Socket(opts); + socket = new Socket(createOptions()); socket.on(Socket.EVENT_OPEN, new Emitter.Listener() { @Override public void call(Object... args) { @@ -71,9 +67,7 @@ public class ConnectionTest extends Connection { public void receiveEmoji() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - Socket.Options opts = new Socket.Options(); - opts.port = PORT; - socket = new Socket(opts); + socket = new Socket(createOptions()); socket.on(Socket.EVENT_OPEN, new Emitter.Listener() { @Override public void call(Object... args) { @@ -97,9 +91,7 @@ public class ConnectionTest extends Connection { public void notSendPacketsIfSocketCloses() throws InterruptedException { final Semaphore semaphore = new Semaphore(0); - Socket.Options opts = new Socket.Options(); - opts.port = PORT; - socket = new Socket(opts); + socket = new Socket(createOptions()); socket.on(Socket.EVENT_OPEN, new Emitter.Listener() { @Override public void call(Object... args) { diff --git a/src/test/java/com/github/nkzawa/engineio/client/SSLConnectionTest.java b/src/test/java/com/github/nkzawa/engineio/client/SSLConnectionTest.java new file mode 100644 index 0000000..5685f7d --- /dev/null +++ b/src/test/java/com/github/nkzawa/engineio/client/SSLConnectionTest.java @@ -0,0 +1,122 @@ +package com.github.nkzawa.engineio.client; + +import com.github.nkzawa.emitter.Emitter; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.concurrent.CountDownLatch; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +@RunWith(JUnit4.class) +public class SSLConnectionTest extends Connection { + + static { + // for test on localhost + javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier( + new javax.net.ssl.HostnameVerifier(){ + public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) { + return hostname.equals("localhost"); + } + }); + } + + private Socket socket; + + @Override + Socket.Options createOptions() { + Socket.Options opts = super.createOptions(); + opts.secure = true; + return opts; + } + + @Override + String[] createEnv() { + return new String[] {"DEBUG=engine*", "PORT=" + PORT, "SSL=1"}; + } + + SSLContext createSSLContext() throws GeneralSecurityException, IOException { + KeyStore ks = KeyStore.getInstance("JKS"); + File file = new File("src/test/resources/keystore.jks"); + ks.load(new FileInputStream(file), "password".toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, "password".toCharArray()); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return sslContext; + } + + @Test(timeout = TIMEOUT) + public void connect() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + + Socket.Options opts = createOptions(); + opts.sslContext = createSSLContext(); + socket = new Socket(opts); + socket.on(Socket.EVENT_OPEN, new Emitter.Listener() { + @Override + public void call(Object... args) { + socket.on(Socket.EVENT_MESSAGE, new Emitter.Listener() { + @Override + public void call(Object... args) { + assertThat((String)args[0], is("hi")); + socket.close(); + latch.countDown(); + } + }); + } + }).on("error", new Emitter.Listener() { + @Override + public void call(Object... args) { + ((Exception)args[0]).printStackTrace(); + } + }); + socket.open(); + latch.await(); + } + + @Test(timeout = TIMEOUT) + public void upgrade() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + + Socket.Options opts = createOptions(); + opts.sslContext = createSSLContext(); + socket = new Socket(opts); + socket.on(Socket.EVENT_OPEN, new Emitter.Listener() { + @Override + public void call(Object... args) { + socket.on(Socket.EVENT_UPGRADE, new Emitter.Listener() { + @Override + public void call(Object... args) { + socket.send("hi"); + socket.on(Socket.EVENT_MESSAGE, new Emitter.Listener() { + @Override + public void call(Object... args) { + assertThat((String) args[0], is("hi")); + socket.close(); + latch.countDown(); + } + }); + } + }); + } + }); + socket.open(); + latch.await(); + } +} diff --git a/src/test/java/com/github/nkzawa/engineio/client/ServerConnectionTest.java b/src/test/java/com/github/nkzawa/engineio/client/ServerConnectionTest.java index 7168d7d..f2b1be7 100644 --- a/src/test/java/com/github/nkzawa/engineio/client/ServerConnectionTest.java +++ b/src/test/java/com/github/nkzawa/engineio/client/ServerConnectionTest.java @@ -30,7 +30,7 @@ public class ServerConnectionTest extends Connection { public void openAndClose() throws URISyntaxException, InterruptedException { final BlockingQueue events = new LinkedBlockingQueue(); - socket = new Socket("ws://localhost:" + PORT); + socket = new Socket(createOptions()); socket.on(Socket.EVENT_OPEN, new Emitter.Listener() { @Override public void call(Object... args) { @@ -53,7 +53,7 @@ public class ServerConnectionTest extends Connection { public void messages() throws URISyntaxException, InterruptedException { final BlockingQueue events = new LinkedBlockingQueue(); - socket = new Socket("ws://localhost:" + PORT); + socket = new Socket(createOptions()); socket.on(Socket.EVENT_OPEN, new Emitter.Listener() { @Override public void call(Object... args) { @@ -76,7 +76,7 @@ public class ServerConnectionTest extends Connection { public void handshake() throws URISyntaxException, InterruptedException { final Semaphore semaphore = new Semaphore(0); - socket = new Socket("ws://localhost:" + PORT); + socket = new Socket(createOptions()); socket.on(Socket.EVENT_HANDSHAKE, new Emitter.Listener() { @Override public void call(Object... args) { @@ -102,7 +102,7 @@ public class ServerConnectionTest extends Connection { public void upgrade() throws URISyntaxException, InterruptedException { final BlockingQueue events = new LinkedBlockingQueue(); - socket = new Socket("ws://localhost:" + PORT); + socket = new Socket(createOptions()); socket.on(Socket.EVENT_UPGRADING, new Emitter.Listener() { @Override public void call(Object... args) { @@ -136,10 +136,10 @@ public class ServerConnectionTest extends Connection { public void pollingHeaders() throws URISyntaxException, InterruptedException { final BlockingQueue messages = new LinkedBlockingQueue(); - Socket.Options opts = new Socket.Options(); + Socket.Options opts = createOptions(); opts.transports = new String[] {Polling.NAME}; - socket = new Socket("ws://localhost:" + PORT, opts); + socket = new Socket(opts); socket.on(Socket.EVENT_TRANSPORT, new Emitter.Listener() { @Override public void call(Object... args) { @@ -172,10 +172,10 @@ public class ServerConnectionTest extends Connection { public void websocketHandshakeHeaders() throws URISyntaxException, InterruptedException { final BlockingQueue messages = new LinkedBlockingQueue(); - Socket.Options opts = new Socket.Options(); + Socket.Options opts = createOptions(); opts.transports = new String[] {WebSocket.NAME}; - socket = new Socket("ws://localhost:" + PORT, opts); + socket = new Socket(opts); socket.on(Socket.EVENT_TRANSPORT, new Emitter.Listener() { @Override public void call(Object... args) { @@ -210,10 +210,7 @@ public class ServerConnectionTest extends Connection { EventThread.exec(new Runnable() { @Override public void run() { - Socket.Options opts = new Socket.Options(); - opts.port = PORT; - - final Socket socket = new Socket(opts); + final Socket socket = new Socket(createOptions()); socket.on(Socket.EVENT_UPGRADE, new Emitter.Listener() { @Override @@ -246,10 +243,7 @@ public class ServerConnectionTest extends Connection { EventThread.exec(new Runnable() { @Override public void run() { - Socket.Options opts = new Socket.Options(); - opts.port = PORT; - - final Socket socket = new Socket(opts); + final Socket socket = new Socket(createOptions()); socket.on(Socket.EVENT_UPGRADE, new Emitter.Listener() { @Override diff --git a/src/test/resources/cert.pem b/src/test/resources/cert.pem new file mode 100644 index 0000000..bea755c --- /dev/null +++ b/src/test/resources/cert.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBfDCCASYCCQDTnGd/oOyF1DANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE0MDcwNzEzMTUzN1oXDTQxMTEyMTEzMTUzN1owRTELMAkG +A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC6sdeFPlqk +5Pap9woFx1RO05gLidw4MNcL+ZRSxy/sNeE4PhT/RLFcEvnXiHc92wT8YB5Z+WCM +k/jRQ0q19PNPAgMBAAEwDQYJKoZIhvcNAQEFBQADQQCnmm1N/yZiMBZw2JDfbsx3 +ecc0BGQ2BwWQuGHzP07TMi1AuOyNZSczl907OphYb9iRC8shZ4O+oXjQAuGTQ1Hp +-----END CERTIFICATE----- diff --git a/src/test/resources/key.pem b/src/test/resources/key.pem new file mode 100644 index 0000000..4a78a22 --- /dev/null +++ b/src/test/resources/key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBALqx14U+WqTk9qn3CgXHVE7TmAuJ3Dgw1wv5lFLHL+w14Tg+FP9E +sVwS+deIdz3bBPxgHln5YIyT+NFDSrX0808CAwEAAQJAIdwLSIEsk2drTRwe1zl1 +ku5RTxZruE0zU1qqifDSQjab1StAK1tapxBVRlRlyLCfD704UClsU8sjGtq0Nh6n +kQIhAO2YJM1g0w9bWYet3zC2UdEASPzaQ7llpZmc51NRBx2NAiEAyShICAaclEuy +wwuD4hibV+b6I8CLYoyPBo32EaceN0sCIQCUed6NxfM/houlgV+Xtmfcnzv9X3yx +EDdzjpz08Q7sRQIgZFv1fBOYYSBXQppnJRFzx2pUmCvDHtrTrMh84RfIqnsCIQCf +JjNXXxOaHn1PNZpi6EHReiFQmy1Swt+AxpTsKixsfA== +-----END RSA PRIVATE KEY----- diff --git a/src/test/resources/keystore.jks b/src/test/resources/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..24c507bacc738aa6c51ef2948f26ec1d8d8fe416 GIT binary patch literal 859 zcmezO_TO6u1_mY|W&~qKLmm2`#U#kc$jZRd#MsER zPD-`O=1F|QBTwE*IeWyGDm-B0JohKljs5zEP30#xyfir2@#5EIe)cNQDo54_4vmk} zGM6oKP*EcK!UrX3z9@ zZ>t}PU7YsyoU)c;eaF><#m?V$1^BKq{g{_iccLjdP!zUQserSMz@P_Qzsu zu^r~!te^XTg#7Yd^kD%j=Vnf8rM{XA%sW|sM!2i3b#4$AWxZ8>>eju(^Iy4cIDMYC zeZlfc>*kdC3oZVbYO8ghbzNq|)|2!9Ee~0^t^IqNO{IpG;7YM=JI}1UR6Zm9jF{R) z!|(s<*slNS{lDK;c1?oX&GXk?{%!D{-MCIU<@e#;eOCj&pO##1297J%2t88+OJF=T z0OP3!h}D=l87|LBuV3({^@;&68>d#AN85K^V5G4!7`Pg88*s8QhqABsr2J#?5E@27Jypq(Sywnnf@XVC-%#vb-fRaiDpOO>