// Copyright (c) 2014 Tom Zhou package com.iwebpp.wspp; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.List; import java.util.Random; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.iwebpp.crypto.NaclCert; import com.iwebpp.crypto.TweetNaclFast; import com.iwebpp.libuvpp.handles.TimerHandle; import com.iwebpp.node.EventEmitter2; import com.iwebpp.node.NodeContext; import com.iwebpp.node.Url; import com.iwebpp.node.Url.UrlObj; import com.iwebpp.node.stream.Duplex; import com.iwebpp.node.stream.Readable2; import com.iwebpp.node.stream.Writable.WriteCB; import com.iwebpp.node.stream.Writable2; import com.iwebpp.wspp.WebSocket.ErrorEvent; import com.iwebpp.wspp.WebSocket.MessageEvent; import com.iwebpp.wspp.WebSocket.OpenEvent; import com.iwebpp.wspp.WebSocket.SendOptions; import com.iwebpp.wspp.WebSocket.message_data_b; import com.iwebpp.wspp.WebSocket.oncloseListener; import com.iwebpp.wspp.WebSocket.onerrorListener; import com.iwebpp.wspp.WebSocket.onmessageListener; import com.iwebpp.wspp.WebSocket.onopenListener; public final class SecureWebSocket extends EventEmitter2 { private static final String TAG = "SecureWebSocket"; private static final int HANDSHAKE_TIMEOUT = 2000; // 2s private final WebSocket ws; private sws_state_t state; private TimerHandle hs_tmo; private static enum sws_state_t { SWS_STATE_NEW (0), SWS_STATE_CONNECTED (1), // hand shake process ///////// SWS_STATE_HANDSHAKE_START (2), // client -> server SWS_STATE_SEND_CLIENT_HELLO (3), SWS_STATE_RECV_CLIENT_HELLO (4), // server -> client SWS_STATE_SEND_SERVER_HELLO (5), SWS_STATE_RECV_SERVER_HELLO (6), // client -> server SWS_STATE_SEND_CLIENT_READY (7), SWS_STATE_RECV_CLIENT_READY (8), SWS_STATE_HANDSHAKE_DONE (9); //////////////////////////////// private int state; private sws_state_t(int state) { this.state = state; } } private static enum sws_opc_t { SWS_OPC_CLIENT_HELLO (0), SWS_OPC_SERVER_HELLO (1), SWS_OPC_CLIENT_READY (2); private int opc; private sws_opc_t(int opc) { this.opc = opc; } private int opc() { return this.opc; } } private boolean isServer; private String url; private NaclCert.Cert peerCert; // protocol version default as 1 private int PROTO_VERSION = 1; private SecInfo mySecInfo; private NodeContext context; private BinaryStream binaryStream; // Hand shake message beans private class client_hello_b { public int opc; public int version; // sws protocol version: 1 public byte[] nonce; // 8 bytes public byte[] client_public_key; // 32 bytes public final static int nonceLength = 8; // 8 bytes public JSONObject toJSON() throws JSONException { JSONObject json = new JSONObject(); json.put("opc", opc); json.put("version", version); JSONArray na = new JSONArray(); for (int i = 0; i < nonce.length; i ++) na.put(i, nonce[i]&0xff); json.put("nonce", na); JSONArray cpka = new JSONArray(); for (int i = 0; i < client_public_key.length; i ++) cpka.put(i, client_public_key[i]&0xff); json.put("client_public_key", cpka); return json; } public String stringify() throws JSONException { String jstr = toJSON().toString(); debug(TAG, "client_hello_b->:" + jstr); return jstr; } @SuppressWarnings("unused") public client_hello_b(JSONObject json) throws JSONException { this.opc = json.getInt("opc"); this.version = json.getInt("version"); this.nonce = new byte[nonceLength]; JSONArray na = json.getJSONArray("nonce"); for (int i = 0; i < na.length(); i ++) this.nonce[i] = (byte) (na.getInt(i)&0xff); this.client_public_key = new byte[TweetNaclFast.Box.publicKeyLength]; JSONArray cpka = json.getJSONArray("client_public_key"); for (int i = 0; i < cpka.length(); i ++) this.client_public_key[i] = (byte) (cpka.getInt(i)&0xff); } public client_hello_b(String jstr) throws JSONException { debug(TAG, "client_hello_b<-:" + jstr); JSONObject json = new JSONObject(jstr); this.opc = json.getInt("opc"); this.version = json.getInt("version"); this.nonce = new byte[nonceLength]; JSONArray na = json.getJSONArray("nonce"); for (int i = 0; i < na.length(); i ++) this.nonce[i] = (byte) (na.getInt(i)&0xff); this.client_public_key = new byte[TweetNaclFast.Box.publicKeyLength]; JSONArray cpka = json.getJSONArray("client_public_key"); for (int i = 0; i < cpka.length(); i ++) this.client_public_key[i] = (byte) (cpka.getInt(i)&0xff); } public client_hello_b() {} } // client hello message V2 same as V1 private class server_hello_b { public int opc; public int version; // sws protocol version: 1 public byte[] server_public_key; // encrypted nonce, share_key public byte[] nonce; // 8 bytes public byte[] share_key; public final static int nonceLength = 8; // 8 bytes public JSONObject toJSON() throws Exception { JSONObject json = new JSONObject(); json.put("opc", opc); json.put("version", version); JSONArray spka = new JSONArray(); for (int i = 0; i < server_public_key.length; i ++) spka.put(i, server_public_key[i]&0xff); json.put("server_public_key", spka); // encrypt nonce, share_key byte[] nonce_share_key = new byte[nonce.length+share_key.length]; for (int i = 0; i < nonce.length; i ++) nonce_share_key[i] = nonce[i]; for (int i = 0; i < share_key.length; i ++) nonce_share_key[i+nonce.length] = share_key[i]; // Constructor temp NACL tx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.theirNonce)); byte[] s_nonce_share_key = tmpBox.box(nonce_share_key); if (!(s_nonce_share_key!=null && s_nonce_share_key.length==(nonce_share_key.length+TweetNaclFast.Box.overheadLength))) throw new Exception("server_hello_b encrypt nonce_share_key failed"); // JSONArray s_nonce_share_key_a = new JSONArray(); for (int i = 0; i < s_nonce_share_key.length; i ++) s_nonce_share_key_a.put(i, s_nonce_share_key[i]&0xff); json.put("s_blackbox_a", s_nonce_share_key_a); return json; } public String stringify() throws Exception { String jstr = toJSON().toString(); debug(TAG, "server_hello_b->:" + jstr); return jstr; } public server_hello_b(String jstr) throws Exception { debug(TAG, "server_hello_b<-:" + jstr); JSONObject json = new JSONObject(jstr); this.opc = json.getInt("opc"); this.version = json.getInt("version"); JSONArray spka = json.getJSONArray("server_public_key"); this.server_public_key = new byte[spka.length()]; for (int i = 0; i < spka.length(); i ++) this.server_public_key[i] = (byte) (spka.getInt(i)&0xff); // decrypt nonce, share_key JSONArray s_nonce_share_key_a = json.getJSONArray("s_blackbox_a"); byte[] s_nonce_share_key = new byte[s_nonce_share_key_a.length()]; for (int i = 0; i < s_nonce_share_key_a.length(); i ++) s_nonce_share_key[i] = (byte) (s_nonce_share_key_a.getInt(i)&0xff); // Constructor temp NACL rx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( this.server_public_key, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); byte[] nonce_share_key = tmpBox.open(s_nonce_share_key); if (!(nonce_share_key!=null && nonce_share_key.length==(s_nonce_share_key.length-TweetNaclFast.Box.overheadLength))) throw new Exception("server_hello_b decrypt nonce_share_key failed"); // extract nonce this.nonce = new byte[nonceLength]; for (int i = 0; i < nonce.length; i ++) this.nonce[i] = nonce_share_key[i]; // extract shared key this.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; for (int i = 0; i < share_key.length; i ++) this.share_key[i] = nonce_share_key[i+nonce.length]; } public server_hello_b(){} } // server hello message V2 private class server_hello_b_v2 { public int opc; public int version; // sws protocol version: 1 public byte[] server_public_key; // encrypted nonce, share_key public byte[] nonce; // 8 bytes public byte[] share_key; public final static int nonceLength = 8; // 8 bytes public NaclCert.Cert cert; public boolean requireCert; public JSONObject toJSON() throws Exception { JSONObject json = new JSONObject(); json.put("opc", opc); json.put("version", version); JSONArray spka = new JSONArray(); for (int i = 0; i < server_public_key.length; i ++) spka.put(i, server_public_key[i]&0xff); json.put("server_public_key", spka); // cert, requireCert JSONObject crobj = new JSONObject(); crobj.put("requireCert", requireCert); crobj.put("cert", cert.toJSON()); String crstr = crobj.toString(); byte [] crbuf = crstr.getBytes("utf-8"); // encrypt nonce, share_key, cert, requireCert byte[] nonce_share_key_cert_requirecert = new byte[nonce.length+share_key.length+crbuf.length]; for (int i = 0; i < nonce.length; i ++) nonce_share_key_cert_requirecert[i] = nonce[i]; for (int i = 0; i < share_key.length; i ++) nonce_share_key_cert_requirecert[i+nonce.length] = share_key[i]; for (int i = 0; i < crbuf.length; i ++) nonce_share_key_cert_requirecert[i+nonce.length+share_key.length] = crbuf[i]; // Constructor temp NACL tx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.theirNonce)); byte[] s_nonce_share_key_cert_requirecert = tmpBox.box(nonce_share_key_cert_requirecert); if (!(s_nonce_share_key_cert_requirecert!=null && s_nonce_share_key_cert_requirecert.length== (nonce_share_key_cert_requirecert.length+TweetNaclFast.Box.overheadLength))) throw new Exception("server_hello_b_v2 encrypt nonce_share_key failed"); // JSONArray s_nonce_share_key_cert_requirecert_a = new JSONArray(); for (int i = 0; i < s_nonce_share_key_cert_requirecert.length; i ++) s_nonce_share_key_cert_requirecert_a.put(i, s_nonce_share_key_cert_requirecert[i]&0xff); json.put("s_blackbox_a", s_nonce_share_key_cert_requirecert_a); return json; } public String stringify() throws Exception { String jstr = toJSON().toString(); debug(TAG, "server_hello_b_v2->:" + jstr); return jstr; } @SuppressWarnings("unused") public server_hello_b_v2(String jstr) throws Exception { debug(TAG, "server_hello_b_v2<-:" + jstr); JSONObject json = new JSONObject(jstr); this.opc = json.getInt("opc"); this.version = json.getInt("version"); JSONArray spka = json.getJSONArray("server_public_key"); this.server_public_key = new byte[spka.length()]; for (int i = 0; i < spka.length(); i ++) this.server_public_key[i] = (byte) (spka.getInt(i)&0xff); // decrypt nonce, share_key, cert, requirecert JSONArray s_nonce_share_key_cert_requirecert_a = json.getJSONArray("s_blackbox_a"); byte[] s_nonce_share_key_cert_requirecert = new byte[s_nonce_share_key_cert_requirecert_a.length()]; for (int i = 0; i < s_nonce_share_key_cert_requirecert_a.length(); i ++) s_nonce_share_key_cert_requirecert[i] = (byte) (s_nonce_share_key_cert_requirecert_a.getInt(i)&0xff); // Constructor temp NACL rx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( this.server_public_key, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); byte[] nonce_share_key_cert_requirecert = tmpBox.open(s_nonce_share_key_cert_requirecert); if (!(nonce_share_key_cert_requirecert!=null && nonce_share_key_cert_requirecert.length==(s_nonce_share_key_cert_requirecert.length-TweetNaclFast.Box.overheadLength))) throw new Exception("server_hello_b_v2 decrypt nonce_share_key failed"); // extract nonce this.nonce = new byte[nonceLength]; for (int i = 0; i < nonce.length; i ++) this.nonce[i] = nonce_share_key_cert_requirecert[i]; // extract shared key this.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; for (int i = 0; i < share_key.length; i ++) this.share_key[i] = nonce_share_key_cert_requirecert[i+nonce.length]; // extract cert, requireCert byte[] crbuf = new byte[nonce_share_key_cert_requirecert.length - nonce.length - share_key.length]; for (int i = 0; i < crbuf.length; i ++) crbuf[i] = nonce_share_key_cert_requirecert[i+nonce.length+share_key.length]; JSONObject crobj = new JSONObject(new String(crbuf, "utf-8")); // cert this.cert = NaclCert.Cert.parse(crobj.getJSONObject("cert")); // requireCert this.requireCert = crobj.getBoolean("requireCert"); } @SuppressWarnings("unused") public server_hello_b_v2(){} } private class client_ready_b { public int opc; public int version; // sws protocol version: 1 // encrypted nonce, share_key public byte[] nonce; // 8 bytes public byte[] share_key; public final static int nonceLength = 8; // 8 bytes public JSONObject toJSON() throws Exception { JSONObject json = new JSONObject(); json.put("opc", opc); json.put("version", version); // encrypt nonce, share_key byte[] nonce_share_key = new byte[nonce.length+share_key.length]; for (int i = 0; i < nonce.length; i ++) nonce_share_key[i] = nonce[i]; for (int i = 0; i < share_key.length; i ++) nonce_share_key[i+nonce.length] = share_key[i]; // Constructor temp NACL tx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.theirNonce)); byte[] s_nonce_share_key = tmpBox.box(nonce_share_key); if (!(s_nonce_share_key!=null && s_nonce_share_key.length==(nonce_share_key.length+TweetNaclFast.Box.overheadLength))) throw new Exception("client_ready_b encrypt nonce_share_key failed"); // JSONArray s_nonce_share_key_a = new JSONArray(); for (int i = 0; i < s_nonce_share_key.length; i ++) s_nonce_share_key_a.put(i, s_nonce_share_key[i]&0xff); json.put("s_blackbox_a", s_nonce_share_key_a); return json; } public String stringify() throws Exception { String jstr = toJSON().toString(); debug(TAG, "client_ready_b->:" + jstr); return jstr; } @SuppressWarnings("unused") public client_ready_b(JSONObject json) throws Exception { this.opc = json.getInt("opc"); this.version = json.getInt("version"); // decrypt nonce, share_key JSONArray s_nonce_share_key_a = json.getJSONArray("s_blackbox_a"); byte[] s_nonce_share_key = new byte[s_nonce_share_key_a.length()]; for (int i = 0; i < s_nonce_share_key_a.length(); i ++) s_nonce_share_key[i] = (byte) (s_nonce_share_key_a.getInt(i)&0xff); // Constructor temp NACL rx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); byte[] nonce_share_key = tmpBox.open(s_nonce_share_key); if (!(nonce_share_key!=null && nonce_share_key.length==(s_nonce_share_key.length-TweetNaclFast.Box.overheadLength))) throw new Exception("client_ready_b decrypt nonce_share_key failed"); // extract nonce this.nonce = new byte[nonceLength]; for (int i = 0; i < nonce.length; i ++) this.nonce[i] = nonce_share_key[i]; // extract shared key this.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; for (int i = 0; i < share_key.length; i ++) this.share_key[i] = nonce_share_key[i+nonce.length]; } public client_ready_b(String jstr) throws Exception { debug(TAG, "client_ready_b<-:" + jstr); JSONObject json = new JSONObject(jstr); this.opc = json.getInt("opc"); this.version = json.getInt("version"); // decrypt nonce, share_key JSONArray s_nonce_share_key_a = json.getJSONArray("s_blackbox_a"); byte[] s_nonce_share_key = new byte[s_nonce_share_key_a.length()]; for (int i = 0; i < s_nonce_share_key_a.length(); i ++) s_nonce_share_key[i] = (byte) (s_nonce_share_key_a.getInt(i)&0xff); // Constructor temp NACL rx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); byte[] nonce_share_key = tmpBox.open(s_nonce_share_key); if (!(nonce_share_key!=null && nonce_share_key.length==(s_nonce_share_key.length-TweetNaclFast.Box.overheadLength))) throw new Exception("client_ready_b decrypt nonce_share_key failed"); // extract nonce this.nonce = new byte[nonceLength]; for (int i = 0; i < nonce.length; i ++) this.nonce[i] = nonce_share_key[i]; // extract shared key this.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; for (int i = 0; i < share_key.length; i ++) this.share_key[i] = nonce_share_key[i+nonce.length]; } public client_ready_b() {} } // client ready message V2 private class client_ready_b_v2 { public int opc; public int version; // sws protocol version: 1 // encrypted nonce, share_key public byte[] nonce; // 8 bytes public byte[] share_key; public final static int nonceLength = 8; // 8 bytes public NaclCert.Cert cert; public JSONObject toJSON() throws Exception { JSONObject json = new JSONObject(); json.put("opc", opc); json.put("version", version); // stringify cert String certstr = cert!=null ? cert.toJSON().toString() : "{}"; byte[] certbuf = certstr.getBytes("utf-8"); // encrypt nonce, share_key, cert byte[] nonce_share_key_cert = new byte[nonce.length+share_key.length+certbuf.length]; for (int i = 0; i < nonce.length; i ++) nonce_share_key_cert[i] = nonce[i]; for (int i = 0; i < share_key.length; i ++) nonce_share_key_cert[i+nonce.length] = share_key[i]; for (int i = 0; i < certbuf.length; i ++) nonce_share_key_cert[i+nonce.length+share_key.length] = certbuf[i]; // Constructor temp NACL tx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.theirNonce)); byte[] s_nonce_share_key_cert = tmpBox.box(nonce_share_key_cert); if (!(s_nonce_share_key_cert!=null && s_nonce_share_key_cert.length==(nonce_share_key_cert.length+TweetNaclFast.Box.overheadLength))) throw new Exception("client_ready_b_v2 encrypt nonce_share_key failed"); // JSONArray s_nonce_share_key_cert_a = new JSONArray(); for (int i = 0; i < s_nonce_share_key_cert.length; i ++) s_nonce_share_key_cert_a.put(i, s_nonce_share_key_cert[i]&0xff); json.put("s_blackbox_a", s_nonce_share_key_cert_a); return json; } public String stringify() throws Exception { String jstr = toJSON().toString(); debug(TAG, "client_ready_b_v2->:" + jstr); return jstr; } public client_ready_b_v2(String jstr) throws Exception { debug(TAG, "client_ready_b_v2<-:" + jstr); JSONObject json = new JSONObject(jstr); this.opc = json.getInt("opc"); this.version = json.getInt("version"); // decrypt nonce, share_key, cert JSONArray s_nonce_share_key_cert_a = json.getJSONArray("s_blackbox_a"); byte[] s_nonce_share_key_cert = new byte[s_nonce_share_key_cert_a.length()]; for (int i = 0; i < s_nonce_share_key_cert_a.length(); i ++) s_nonce_share_key_cert[i] = (byte) (s_nonce_share_key_cert_a.getInt(i)&0xff); // Constructor temp NACL rx box TweetNaclFast.Box tmpBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); byte[] nonce_share_key_cert = tmpBox.open(s_nonce_share_key_cert); if (!(nonce_share_key_cert!=null && nonce_share_key_cert.length==(s_nonce_share_key_cert.length-TweetNaclFast.Box.overheadLength))) throw new Exception("client_ready_b_v2 decrypt nonce_share_key failed"); // extract nonce this.nonce = new byte[nonceLength]; for (int i = 0; i < nonce.length; i ++) this.nonce[i] = nonce_share_key_cert[i]; // extract shared key this.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; for (int i = 0; i < share_key.length; i ++) this.share_key[i] = nonce_share_key_cert[i+nonce.length]; // extract cert byte [] certbuf = new byte[nonce_share_key_cert.length - nonce.length - share_key.length]; for (int i = 0; i < certbuf.length; i ++) certbuf[i] = nonce_share_key_cert[i+nonce.length+share_key.length]; this.cert = NaclCert.Cert.parse(new String(certbuf, "utf-8")); } @SuppressWarnings("unused") public client_ready_b_v2(){} } // secure info public static class SecInfo { private int version; // protocol version, 1 or 2 private byte[] pk; // nacl box publickey private byte[] sk; // nacl box secretkey private NaclCert.Cert cert; // nacl box publickey cert signed by CA private NaclCert.SelfCert ca; // nacl signature publickey cert signed by self private boolean requireCert; // whether need peer's cert // version 1 public SecInfo(byte[] pk, byte[] sk) { this.version = 1; this.pk = pk; this.sk = sk; this.cert = null; this.ca = null; this.requireCert = false; } // version 2 public SecInfo(byte[] pk, byte[] sk, NaclCert.Cert cert, NaclCert.SelfCert ca, boolean requireCert) { this.version = 2; this.pk = pk; this.sk = sk; this.cert = cert; this.ca = ca; this.requireCert = requireCert; } @SuppressWarnings("unused") private SecInfo() {} public int getVersion() { return this.version; } public byte[] getPublicKey() { return this.pk; } public byte[] getSecretKey() { return this.sk; } public NaclCert.Cert getCert() { return this.cert; } public NaclCert.SelfCert getCa() { if (this.ca != null) return this.ca; else return NaclCert.rootCACert.get("iwebpp.com"); } public boolean requireCert() { return requireCert; } } // Client Constructor @SuppressWarnings("unused") public SecureWebSocket( final NodeContext ctx, final String address, final WebSocket.Options options, final SecInfo sec) throws Exception { // context this.context = ctx; // client this.isServer = false; this.url = address; // check version PROTO_VERSION = sec!=null ? sec.getVersion() : 1; // Setup security info /////////////////////////////////////////////////////////////////// if (PROTO_VERSION >= 1) { // setup V1 this.mySecInfo = sec; this.myPublicKey = sec.getPublicKey(); this.mySecretKey = sec.getSecretKey(); // check V1 if (!(this.myPublicKey!=null && this.myPublicKey.length==TweetNaclFast.Box.publicKeyLength)) throw new Exception("Invalid nacl public key"); if (!(this.mySecretKey!=null && this.mySecretKey.length==TweetNaclFast.Box.secretKeyLength)) throw new Exception("Invalid nacl secret key"); } if (PROTO_VERSION >= 2) { // setup V2 this.caCert = sec.getCa(); this.myCert = sec.getCert(); // client always request server's Cert // server can request or not-request client's Cert this.requireCert = true; } ////////////////////////////////////////////////////////////////////////////////////////////// // FSM this.state = sws_state_t.SWS_STATE_NEW; this.ws = new WebSocket(ctx, address, null, options); this.ws.onopen(new WebSocket.onopenListener() { @Override public void onOpen(OpenEvent event) throws Exception { SecureWebSocket.this.state = sws_state_t.SWS_STATE_CONNECTED; SecureWebSocket.this.ws.onmessage(new WebSocket.onmessageListener() { @Override public void onMessage(MessageEvent event) throws Exception { debug(TAG, "Client got message:"+event.getData().toString()); if (SecureWebSocket.this.state == sws_state_t.SWS_STATE_HANDSHAKE_DONE) { // Secure Context process if (event.isBinary()) { ByteBuffer bb = (ByteBuffer) event.getData(); if (bb!=null && bb.capacity()>0) { // authenticated decrypt cipher to plain buffer byte[] plain = SecureWebSocket.this.rxSecretBox.open(bb.array(), bb.arrayOffset()); // check security if (plain != null) { // increase nonce SecureWebSocket.this.rxSecretBox.incrNonce(); // emit plain message message_data_b msg = new message_data_b(ByteBuffer.wrap(plain), new Receiver.opcOptions(false, null, true)); SecureWebSocket.this.emit("message", msg); } else { warn(TAG, "hacked ByteBuffer, ingore it"); SecureWebSocket.this.emit("warn", "hacked ByteBuffer, ingore it"); } } else { warn(TAG, "invalid ByteBuffer"); SecureWebSocket.this.emit("warn", "invalid ByteBuffer"); } } else { // TBD... String warn(TAG, "don't support string so far"); SecureWebSocket.this.emit("warn", "don't support string so far"); } } else { // Handle Shake process if (SecureWebSocket.this.state == sws_state_t.SWS_STATE_SEND_CLIENT_HELLO) { // extract ServerHello message if (!event.isBinary()) { // capture JSON parse exception try { // V1 if (PROTO_VERSION == 1) { server_hello_b shm = new server_hello_b(event.getData().toString()); if (shm.opc == sws_opc_t.SWS_OPC_SERVER_HELLO.opc() && shm.version == PROTO_VERSION) { debug(TAG, "ServerHello message:"+event.getData().toString()); // update secure info SecureWebSocket.this.theirPublicKey = shm.server_public_key; SecureWebSocket.this.rxSharekey = shm.share_key; SecureWebSocket.this.theirNonce = shm.nonce; // send ClientReady message client_ready_b crm = new client_ready_b(); crm.opc = sws_opc_t.SWS_OPC_CLIENT_READY.opc(); crm.version = PROTO_VERSION; // nonce crm.nonce = new byte[client_hello_b.nonceLength]; randombytes(crm.nonce, crm.nonce.length); // shared key crm.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; randombytes(crm.share_key, crm.share_key.length); // update secure info SecureWebSocket.this.myNonce = crm.nonce; SecureWebSocket.this.txShareKey = crm.share_key; // Constructor NACL tx box SecureWebSocket.this.txBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); SecureWebSocket.this.txSecretBox = new TweetNaclFast.SecretBox( SecureWebSocket.this.txShareKey, toLong(SecureWebSocket.this.myNonce)); SecureWebSocket.this.ws.send(crm.stringify(), new WebSocket.SendOptions(false, false), new WriteCB(){ @Override public void writeDone(String error) throws Exception { if (error != null) { SecureWebSocket.this.emit("error", "send_client_ready:"+error); // close ws SecureWebSocket.this.ws.close(0, null); } else { // clear hand shake timeout SecureWebSocket.this.hs_tmo.close(); // update state to SWS_STATE_SEND_CLIENT_READY // Am ready SecureWebSocket.this.state = sws_state_t.SWS_STATE_SEND_CLIENT_READY; // Construct NACL rx box SecureWebSocket.this.rxBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.theirNonce)); SecureWebSocket.this.rxSecretBox = new TweetNaclFast.SecretBox( SecureWebSocket.this.rxSharekey, toLong(SecureWebSocket.this.theirNonce)); // defer hand-shake done 20ms(about RTT) ctx.setTimeout(new NodeContext.TimeoutListener() { @Override public void onTimeout() throws Exception { // set hand shake done SecureWebSocket.this.state = sws_state_t.SWS_STATE_HANDSHAKE_DONE; // Flush sendCache for (send_cache_b c : SecureWebSocket.this.sendCache) SecureWebSocket.this.send(c.chunk, c.options, c.cb); SecureWebSocket.this.sendCache.clear(); // emit Secure event SecureWebSocket.this.emit("secure"); } }, 20); // 20ms delay } } }); } else { SecureWebSocket.this.emit("warn", "invalid handshake server-hello message:"+event.getData().toString()); } } else // V2 if (PROTO_VERSION == 2) { server_hello_b_v2 shm = new server_hello_b_v2(event.getData().toString()); if (shm.opc == sws_opc_t.SWS_OPC_SERVER_HELLO.opc() && shm.version == PROTO_VERSION) { debug(TAG, "ServerHello message V2:"+event.getData().toString()); // check server's PublicKey Cert ///////////////////////////////// if (!NaclCert.validate(shm.cert, SecureWebSocket.this.caCert)) { debug(TAG, "Invalid server cert"); SecureWebSocket.this.emit("error", "Invalid server cert"); SecureWebSocket.this.ws.close(0, null); return; } if (!compareByteArray(shm.server_public_key, shm.cert.desc.reqdesc.publickey)) { debug(TAG, "Unexpected server cert"); SecureWebSocket.this.emit("error", "Unexpected server cert"); SecureWebSocket.this.ws.close(0, null); return; } // check domain or ip UrlObj serverUrl = Url.parse(SecureWebSocket.this.url); String srvDomain = serverUrl.hostname!=null ? serverUrl.hostname : ""; String srvIP = SecureWebSocket.this.ws.remoteAddress(); debug(TAG, "expected server ip:"+srvIP); debug(TAG, "expected server domain:"+srvDomain); if (!(NaclCert.checkDomain(shm.cert, srvDomain) || NaclCert.checkIP(shm.cert, srvIP))) { error(TAG, "Invalid server endpoing"); SecureWebSocket.this.emit("error", "Invalid server endpoing"); SecureWebSocket.this.ws.close(0, null); return; } // record server's cert SecureWebSocket.this.peerCert = shm.cert; ///////////////////////////////////////////////////////////////////////// // update secure info SecureWebSocket.this.theirPublicKey = shm.server_public_key; SecureWebSocket.this.rxSharekey = shm.share_key; SecureWebSocket.this.theirNonce = shm.nonce; // send ClientReady message client_ready_b_v2 crm = new client_ready_b_v2(); crm.opc = sws_opc_t.SWS_OPC_CLIENT_READY.opc(); crm.version = PROTO_VERSION; // nonce crm.nonce = new byte[client_hello_b.nonceLength]; randombytes(crm.nonce, crm.nonce.length); // shared key crm.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; randombytes(crm.share_key, crm.share_key.length); // check if need cert ///////////////////////////////////// if (shm.requireCert) { if (SecureWebSocket.this.myCert != null) { crm.cert = SecureWebSocket.this.myCert; } else { error(TAG, "Miss client cert"); SecureWebSocket.this.emit("error", "Miss client cert"); SecureWebSocket.this.ws.close(0, null); return; } } ///////////////////////////////////////////////////////////// // update secure info SecureWebSocket.this.myNonce = crm.nonce; SecureWebSocket.this.txShareKey = crm.share_key; // Constructor NACL tx box SecureWebSocket.this.txBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); SecureWebSocket.this.txSecretBox = new TweetNaclFast.SecretBox( SecureWebSocket.this.txShareKey, toLong(SecureWebSocket.this.myNonce)); SecureWebSocket.this.ws.send(crm.stringify(), new WebSocket.SendOptions(false, false), new WriteCB(){ @Override public void writeDone(String error) throws Exception { if (error != null) { SecureWebSocket.this.emit("error", "send_client_ready:"+error); // close ws SecureWebSocket.this.ws.close(0, null); } else { // clear hand shake timeout SecureWebSocket.this.hs_tmo.close(); // update state to SWS_STATE_SEND_CLIENT_READY // Am ready SecureWebSocket.this.state = sws_state_t.SWS_STATE_SEND_CLIENT_READY; // Construct NACL rx box SecureWebSocket.this.rxBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.theirNonce)); SecureWebSocket.this.rxSecretBox = new TweetNaclFast.SecretBox( SecureWebSocket.this.rxSharekey, toLong(SecureWebSocket.this.theirNonce)); // defer hand-shake done 20ms(about RTT) ctx.setTimeout(new NodeContext.TimeoutListener() { @Override public void onTimeout() throws Exception { // set hand shake done SecureWebSocket.this.state = sws_state_t.SWS_STATE_HANDSHAKE_DONE; // Flush sendCache for (send_cache_b c : SecureWebSocket.this.sendCache) SecureWebSocket.this.send(c.chunk, c.options, c.cb); SecureWebSocket.this.sendCache.clear(); // emit Secure event SecureWebSocket.this.emit("secure"); } }, 20); // 20ms delay } } }); } else { SecureWebSocket.this.emit("warn", "invalid handshake server-hello message:"+event.getData().toString()); } } else { SecureWebSocket.this.emit("error", "Invalid protocol version"); // close ws SecureWebSocket.this.ws.close(0, null); } } catch (Exception e) { SecureWebSocket.this.emit("warn", e.toString()+"parse handshake message failed, skip it:"+event.getData().toString()); } } else { SecureWebSocket.this.emit("warn", "invalid handshake binary message:"+event.getData().toString()); } } else { SecureWebSocket.this.emit("warn", "unknown handshake message:"+event.getData().toString()); } } } }); // 1. // send ClientHello message client_hello_b chm = new client_hello_b(); chm.opc = sws_opc_t.SWS_OPC_CLIENT_HELLO.opc(); chm.version = PROTO_VERSION; chm.client_public_key = SecureWebSocket.this.myPublicKey; chm.nonce = new byte[client_hello_b.nonceLength]; randombytes(chm.nonce, chm.nonce.length); // update secure info SecureWebSocket.this.myNonce = chm.nonce; SecureWebSocket.this.ws.send(chm.stringify(), new WebSocket.SendOptions(false, false), new WriteCB(){ @Override public void writeDone(String error) throws Exception { if (error != null) { SecureWebSocket.this.emit("error", "send_client_hello:"+error); // close ws SecureWebSocket.this.ws.close(0, null); } else { // update state to SWS_STATE_SEND_CLIENT_HELLO SecureWebSocket.this.state = sws_state_t.SWS_STATE_SEND_CLIENT_HELLO; } } }); // update state to SWS_STATE_HANDSHAKE_START SecureWebSocket.this.state = sws_state_t.SWS_STATE_HANDSHAKE_START; // 2. // start hand-shake timer SecureWebSocket.this.hs_tmo = ctx.setTimeout(new NodeContext.TimeoutListener() { @Override public void onTimeout() throws Exception { if (SecureWebSocket.this.state != sws_state_t.SWS_STATE_HANDSHAKE_DONE) { debug(TAG, "handshake timeout"); SecureWebSocket.this.emit("timeout", "handshake timeout"); // close ws SecureWebSocket.this.ws.close(0, null); } } }, HANDSHAKE_TIMEOUT); } }); // Send cache this.sendCache = new LinkedList(); } // ServerClient Constructor @SuppressWarnings("unused") protected SecureWebSocket(final NodeContext ctx, final WebSocket ws, final SecInfo sec) throws Exception { // context this.context = ctx; // server client this.isServer = true; // check version PROTO_VERSION = sec!=null ? sec.getVersion() : 1; // Setup security info /////////////////////////////////////////////////////////////////// if (PROTO_VERSION >= 1) { // setup V1 this.mySecInfo = sec; this.myPublicKey = sec.getPublicKey(); this.mySecretKey = sec.getSecretKey(); // check V1 if (!(this.myPublicKey!=null && this.myPublicKey.length==TweetNaclFast.Box.publicKeyLength)) throw new Exception("Invalid nacl public key"); if (!(this.mySecretKey!=null && this.mySecretKey.length==TweetNaclFast.Box.secretKeyLength)) throw new Exception("Invalid nacl secret key"); } if (PROTO_VERSION >= 2) { // setup V2 this.caCert = sec.getCa(); this.myCert = sec.getCert(); // client always request server's Cert // server can request or not-request client's Cert this.requireCert = sec!=null ? sec.requireCert() : false; } ////////////////////////////////////////////////////////////////////////////////////////////// // FSM this.state = sws_state_t.SWS_STATE_NEW; this.ws = ws; // Hand shake process { SecureWebSocket.this.state = sws_state_t.SWS_STATE_CONNECTED; SecureWebSocket.this.ws.onmessage(new WebSocket.onmessageListener() { @Override public void onMessage(MessageEvent event) throws Exception { debug(TAG, "ServerClient got message:"+event.getData().toString()); if (SecureWebSocket.this.state == sws_state_t.SWS_STATE_HANDSHAKE_DONE) { // Secure Context process if (event.isBinary()) { ByteBuffer bb = (ByteBuffer) event.getData(); if (bb!=null && bb.capacity()>0) { // authenticated decrypt cipher to plain buffer byte[] plain = SecureWebSocket.this.rxSecretBox.open(bb.array(), bb.arrayOffset()); // check security if (plain != null) { // increase nonce SecureWebSocket.this.rxSecretBox.incrNonce(); // emit plain message message_data_b msg = new message_data_b(ByteBuffer.wrap(plain), new Receiver.opcOptions(false, null, true)); SecureWebSocket.this.emit("message", msg); } else { warn(TAG, "hacked ByteBuffer, ingore it"); SecureWebSocket.this.emit("warn", "hacked ByteBuffer, ingore it"); } } else { warn(TAG, "invalid ByteBuffer"); SecureWebSocket.this.emit("warn", "invalid ByteBuffer"); } } else { // TBD... String warn(TAG, "don't support string so far"); SecureWebSocket.this.emit("warn", "don't support string so far"); } } else { // Handle Shake process if (SecureWebSocket.this.state == sws_state_t.SWS_STATE_HANDSHAKE_START) { // extract ClientHello message if (!event.isBinary()) { // capture JSON parse exception try { // V1 if (PROTO_VERSION == 1) { client_hello_b chm = new client_hello_b(event.getData().toString()); if (chm.opc == sws_opc_t.SWS_OPC_CLIENT_HELLO.opc() && chm.version == PROTO_VERSION) { debug(TAG, "ClientHello message:"+event.getData().toString()); // update secure info SecureWebSocket.this.theirPublicKey = chm.client_public_key; SecureWebSocket.this.theirNonce = chm.nonce; // send ServerHello message server_hello_b shm = new server_hello_b(); shm.opc = sws_opc_t.SWS_OPC_SERVER_HELLO.opc(); shm.version = PROTO_VERSION; // nonce shm.nonce = new byte[server_hello_b.nonceLength]; randombytes(shm.nonce, shm.nonce.length); // shared key shm.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; randombytes(shm.share_key, shm.share_key.length); // server public key shm.server_public_key = SecureWebSocket.this.myPublicKey; // update secure info SecureWebSocket.this.myNonce = shm.nonce; SecureWebSocket.this.txShareKey = shm.share_key; debug(TAG, "ServerHello message:"+shm.toString()); // Construct NACL tx box SecureWebSocket.this.txBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); SecureWebSocket.this.txSecretBox = new TweetNaclFast.SecretBox( SecureWebSocket.this.txShareKey, toLong(SecureWebSocket.this.myNonce)); SecureWebSocket.this.ws.send(shm.stringify(), new WebSocket.SendOptions(false, false), new WriteCB(){ @Override public void writeDone(String error) throws Exception { if (error != null) { SecureWebSocket.this.emit("error", "send_server_hello:"+error); // close ws SecureWebSocket.this.ws.close(0, null); } else { // update state to SWS_STATE_SEND_SERVER_HELLO SecureWebSocket.this.state = sws_state_t.SWS_STATE_SEND_SERVER_HELLO; } } }); } else { SecureWebSocket.this.emit("warn", "invalid handshake client-hello message:"+event.getData().toString()); } } else // V2 if (PROTO_VERSION == 2) { client_hello_b chm = new client_hello_b(event.getData().toString()); if (chm.opc == sws_opc_t.SWS_OPC_CLIENT_HELLO.opc() && chm.version == PROTO_VERSION) { debug(TAG, "ClientHello message:"+event.getData().toString()); // update secure info SecureWebSocket.this.theirPublicKey = chm.client_public_key; SecureWebSocket.this.theirNonce = chm.nonce; // send ServerHello message V2 server_hello_b_v2 shm = new server_hello_b_v2(); shm.opc = sws_opc_t.SWS_OPC_SERVER_HELLO.opc(); shm.version = PROTO_VERSION; // nonce shm.nonce = new byte[server_hello_b.nonceLength]; randombytes(shm.nonce, shm.nonce.length); // shared key shm.share_key = new byte[TweetNaclFast.SecretBox.keyLength]; randombytes(shm.share_key, shm.share_key.length); // server public key shm.server_public_key = SecureWebSocket.this.myPublicKey; // add server cert /////////////////////////////////////// if (SecureWebSocket.this.myCert != null) { shm.cert = SecureWebSocket.this.myCert; shm.requireCert = SecureWebSocket.this.requireCert; } else { error(TAG, "Miss server cert"); SecureWebSocket.this.emit("error", "Miss server cert"); SecureWebSocket.this.ws.close(0, null); return; } //////////////////////////////////////////////////////// // update secure info SecureWebSocket.this.myNonce = shm.nonce; SecureWebSocket.this.txShareKey = shm.share_key; debug(TAG, "ServerHello message V2:"+shm.toString()); // Construct NACL tx box SecureWebSocket.this.txBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.myNonce)); SecureWebSocket.this.txSecretBox = new TweetNaclFast.SecretBox( SecureWebSocket.this.txShareKey, toLong(SecureWebSocket.this.myNonce)); SecureWebSocket.this.ws.send(shm.stringify(), new WebSocket.SendOptions(false, false), new WriteCB(){ @Override public void writeDone(String error) throws Exception { if (error != null) { SecureWebSocket.this.emit("error", "send_server_hello:"+error); // close ws SecureWebSocket.this.ws.close(0, null); } else { // update state to SWS_STATE_SEND_SERVER_HELLO SecureWebSocket.this.state = sws_state_t.SWS_STATE_SEND_SERVER_HELLO; } } }); } else { SecureWebSocket.this.emit("warn", "invalid handshake client-hello message:"+event.getData().toString()); } } else { SecureWebSocket.this.emit("error", "Invalid protocol version"); // close ws SecureWebSocket.this.ws.close(0, null); } } catch (Exception e) { SecureWebSocket.this.emit("warn", e.toString()+"parse handshake message failed, skip it:"+event.getData().toString()); } } else { SecureWebSocket.this.emit("warn", "invalid handshake binary message:"+event.getData().toString()); } } else if (SecureWebSocket.this.state == sws_state_t.SWS_STATE_SEND_SERVER_HELLO) { // extract ClientHello message if (!event.isBinary()) { // capture JSON parse exception try { // V1 if (PROTO_VERSION == 1) { client_ready_b crm = new client_ready_b(event.getData().toString()); if (crm.opc == sws_opc_t.SWS_OPC_CLIENT_READY.opc() && crm.version == PROTO_VERSION) { debug(TAG, "ClientReady message:"+event.getData().toString()); // clear hand shake timeout SecureWebSocket.this.hs_tmo.close(); // update secure info SecureWebSocket.this.rxSharekey = crm.share_key; SecureWebSocket.this.theirNonce = crm.nonce; // Construct NACL rx box SecureWebSocket.this.rxBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.theirNonce)); SecureWebSocket.this.rxSecretBox = new TweetNaclFast.SecretBox( SecureWebSocket.this.rxSharekey, toLong(SecureWebSocket.this.theirNonce)); // set hand shake done SecureWebSocket.this.state = sws_state_t.SWS_STATE_HANDSHAKE_DONE; // Flush sendCache for (send_cache_b c : SecureWebSocket.this.sendCache) SecureWebSocket.this.send(c.chunk, c.options, c.cb); SecureWebSocket.this.sendCache.clear(); // emit Secure event SecureWebSocket.this.emit("secure"); } else { SecureWebSocket.this.emit("warn", "invalid handshake client-ready message:"+event.getData().toString()); } } else // V2 if (PROTO_VERSION == 2) { client_ready_b_v2 crm = new client_ready_b_v2(event.getData().toString()); if (crm.opc == sws_opc_t.SWS_OPC_CLIENT_READY.opc() && crm.version == PROTO_VERSION) { debug(TAG, "ClientReady message V2:"+event.getData().toString()); // clear hand shake timeout SecureWebSocket.this.hs_tmo.close(); // check client's PublicKey Cert ///////////////////////////////// if (SecureWebSocket.this.requireCert) { // check cert V2 if (!NaclCert.validate(crm.cert, SecureWebSocket.this.caCert)) { debug(TAG, "Invalid client cert"); SecureWebSocket.this.emit("error", "Invalid client cert"); SecureWebSocket.this.ws.close(0, null); return; } if (!compareByteArray(SecureWebSocket.this.theirPublicKey, crm.cert.desc.reqdesc.publickey)) { debug(TAG, "Unexpected client cert"); SecureWebSocket.this.emit("error", "Unexpected client cert"); SecureWebSocket.this.ws.close(0, null); return; } // check ip String clnIP = SecureWebSocket.this.ws.remoteAddress(); debug(TAG, "expected client ip:"+clnIP); if (!NaclCert.checkIP(crm.cert, clnIP)) { error(TAG, "Invalid client endpoing"); SecureWebSocket.this.emit("error", "Invalid client endpoing"); SecureWebSocket.this.ws.close(0, null); return; } // record client's cert SecureWebSocket.this.peerCert = crm.cert; } ///////////////////////////////////////////////////////////////////// // update secure info SecureWebSocket.this.rxSharekey = crm.share_key; SecureWebSocket.this.theirNonce = crm.nonce; // Construct NACL rx box SecureWebSocket.this.rxBox = new TweetNaclFast.Box( SecureWebSocket.this.theirPublicKey, SecureWebSocket.this.mySecretKey, toLong(SecureWebSocket.this.theirNonce)); SecureWebSocket.this.rxSecretBox = new TweetNaclFast.SecretBox( SecureWebSocket.this.rxSharekey, toLong(SecureWebSocket.this.theirNonce)); // set hand shake done SecureWebSocket.this.state = sws_state_t.SWS_STATE_HANDSHAKE_DONE; // Flush sendCache for (send_cache_b c : SecureWebSocket.this.sendCache) SecureWebSocket.this.send(c.chunk, c.options, c.cb); SecureWebSocket.this.sendCache.clear(); // emit Secure event SecureWebSocket.this.emit("secure"); } else { SecureWebSocket.this.emit("warn", "invalid handshake client-ready message:"+event.getData().toString()); } } else { SecureWebSocket.this.emit("error", "Invalid protocol version"); // close ws SecureWebSocket.this.ws.close(0, null); } } catch (Exception e) { SecureWebSocket.this.emit("warn", e.toString()+"parse handshake message failed, skip it:"+event.getData().toString()); } } else { SecureWebSocket.this.emit("warn", "invalid handshake binary message:"+event.getData().toString()); } } else { SecureWebSocket.this.emit("warn", "unknown handshake message:"+event.getData().toString()); } } } }); // update state to SWS_STATE_HANDSHAKE_START SecureWebSocket.this.state = sws_state_t.SWS_STATE_HANDSHAKE_START; } // start hand-shake timer this.hs_tmo = ctx.setTimeout(new NodeContext.TimeoutListener() { @Override public void onTimeout() throws Exception { if (SecureWebSocket.this.state != sws_state_t.SWS_STATE_HANDSHAKE_DONE) { debug(TAG, "handshake timeout"); SecureWebSocket.this.emit("timeout", "handshake timeout"); // close ws SecureWebSocket.this.ws.close(0, null); } } }, HANDSHAKE_TIMEOUT); // Send cache this.sendCache = new LinkedList(); } public void onclose(final oncloseListener cb) throws Exception { this.ws.onclose(cb); } public void onerror(final onerrorListener cb) throws Exception { this.ws.onerror(cb); this.on("error", new Listener() { @Override public void onEvent(Object data) throws Exception { cb.onError(new ErrorEvent(-1, data!=null? data.toString():"unknown", SecureWebSocket.this.ws)); } }); } public void onmessage(final onmessageListener cb) throws Exception { final SecureWebSocket self = this; this.removeListener("message"); this.on("message", new Listener(){ @Override public void onEvent(Object raw) throws Exception { message_data_b data = (message_data_b)raw; cb.onMessage(new MessageEvent(data.data, data.flags.binary ? "Binary" : "Text", self.ws)); } }); } public void onopen(final onopenListener cb) throws Exception { final SecureWebSocket self = this; this.removeListener("secure"); this.on("secure", new Listener(){ @Override public void onEvent(Object data) throws Exception { cb.onOpen(new OpenEvent(self.ws)); } }); } /* * @description Duplex stream wrapper * */ public static class BinaryStream extends Duplex { private static final String TAG = "SecureWebSocket:BinaryStream"; private final SecureWebSocket host; protected BinaryStream(NodeContext ctx, SecureWebSocket host) throws Exception { super(ctx, new Duplex.Options( new Readable2.Options(-1, null, false, "utf8", true), new Writable2.Options(-1, false, "utf8", false, true), false)); this.host = host; // capture plain message this.host.onmessage(new onmessageListener(){ @Override public void onMessage(MessageEvent event) throws Exception { if (event.isBinary()) { // push data with pause if (!BinaryStream.this.push(event.getData(), null)) { BinaryStream.this.host.pause(); } } else { // TBD... String warn(TAG, "don't support string"); BinaryStream.this.emit("warn", "don't support string"); } } }); } @Override protected void _read(int size) throws Exception { if (this.host.state == sws_state_t.SWS_STATE_HANDSHAKE_DONE) this.host.resume(); return; } @Override protected void _write(Object chunk, String encoding, WriteCB cb) throws Exception { if (chunk!=null && chunk instanceof ByteBuffer) { this.host.send(chunk, new SendOptions(true, false), cb); } else { // TBD... String warn(TAG, "Not support write string"); cb.writeDone("Not support write string"); } } } public BinaryStream binaryStream() throws Exception { if (this.binaryStream == null) this.binaryStream = new BinaryStream(this.context, this); return this.binaryStream; } /* * @description expose WebSocket API * */ public void pause() throws Exception { this.ws.pause(); } public void resume() throws Exception { this.ws.resume(); } public void terminate() throws Exception { this.ws.terminate(); } public String getUrl() { return this.ws.getUrl(); } public int getBytesReceived() { return this.ws.getBytesReceived(); } public int bufferedAmount() { return this.ws.bufferedAmount(); } public void close(int code, Object data) throws Exception { this.ws.close(code, data); } // Address info public String remoteAddress() { return this.ws.remoteAddress(); } public int remotePort() { return this.ws.remotePort(); } public String localAddress() { return this.ws.localAddress(); } public int localPort() { return this.ws.localPort(); } /* * @description override send method * */ public boolean send(final Object chunk, final SendOptions options, final WriteCB cb) throws Exception { if (this.state == sws_state_t.SWS_STATE_HANDSHAKE_DONE) { if (chunk!=null) { if (options.binary) { ByteBuffer bb; if (chunk instanceof ByteBuffer) bb = (ByteBuffer) chunk; else if (chunk instanceof byte[]) bb = ByteBuffer.wrap((byte[]) chunk); else { warn(TAG, "Not support binary"); SecureWebSocket.this.emit("warn", "Not support binary"); if (cb!=null) cb.writeDone("Not support binary"); return true; } // authenticated encrypt plain to cipher buffer byte[] cipher = SecureWebSocket.this.txSecretBox.box(bb.array(), bb.arrayOffset()); // check security if (cipher != null) { // increase nonce SecureWebSocket.this.txSecretBox.incrNonce(); // write data out SecureWebSocket.this.ws.send(ByteBuffer.wrap(cipher), new WebSocket.SendOptions(true, false), cb); } else { warn(TAG, "hacked write ByteBuffer, ingore it"); SecureWebSocket.this.emit("warn", "hacked write ByteBuffer, ingore it"); if (cb!=null) cb.writeDone("hacked write ByteBuffer, ingore it"); } } else { // TBD... String warn(TAG, "don't support write string so far"); SecureWebSocket.this.emit("warn", "don't support write string so far"); if (cb!=null) cb.writeDone("don't support write string so far"); } } else { warn(TAG, "invalid write data"); SecureWebSocket.this.emit("warn", "invalid write data"); if (cb!=null) cb.writeDone("invalid write data"); } } else { // 3. // cache Send debug(TAG, "cache Send"); this.sendCache.add(new send_cache_b(chunk, options, cb)); return false; } return true; } private List sendCache; private class send_cache_b { private Object chunk; private SendOptions options; private WriteCB cb; private send_cache_b(Object chunk, SendOptions options, WriteCB cb) { this.chunk = chunk; this.options = options; this.cb = cb; } } // NACL authenticated encryption // my security info private byte[] mySecretKey; private byte[] myPublicKey; private NaclCert.Cert myCert; private byte[] mySignSecretKey; private byte[] mySignPublicKey; private TweetNaclFast.Signature selfSignature; // peer security info private byte[] theirPublicKey; private String theirCert; private byte[] theirSignPublicKey; private TweetNaclFast.Signature theirSignature; // CA security info private NaclCert.SelfCert caCert; private byte[] caSignPublicKey; private TweetNaclFast.Signature caSignature; private boolean requireCert; // authenticated encryption info private byte[] myNonce; private byte[] theirNonce; private byte[] txShareKey; private byte[] rxSharekey; private TweetNaclFast.Box txBox; private TweetNaclFast.Box rxBox; private TweetNaclFast.SecretBox txSecretBox; private TweetNaclFast.SecretBox rxSecretBox; /* * @description * Java Random generator * */ private static final Random jrandom = new Random(); private static void randombytes(byte [] x, int len) { int ret = len % 8; long rnd; for (int i = 0; i < len-ret; i += 8) { rnd = jrandom.nextLong(); x[i+0] = (byte) (rnd >>> 0); x[i+1] = (byte) (rnd >>> 8); x[i+2] = (byte) (rnd >>> 16); x[i+3] = (byte) (rnd >>> 24); x[i+4] = (byte) (rnd >>> 32); x[i+5] = (byte) (rnd >>> 40); x[i+6] = (byte) (rnd >>> 48); x[i+7] = (byte) (rnd >>> 56); } if (ret > 0) { rnd = jrandom.nextLong(); for (int i = len-ret; i < len; i ++) x[i] = (byte) (rnd >>> 8*i); } } private long toLong(byte[] x) { long ret = 0; for (int i = 0; i < x.length; i ++) { long tmp = x[i] & 0xffL; ret |= tmp << (8*(i%8)); } return ret; } private static boolean compareByteArray(byte[] a, byte[] b) { if (!(a!=null && b!=null && a.length==b.length)) return false; else for (int i = 0; i < a.length; i ++) if (a[i] != b[i]) return false; return true; } }