diff --git a/.classpath b/.classpath
index f8560c1..87e8f73 100644
--- a/.classpath
+++ b/.classpath
@@ -6,7 +6,6 @@
-
@@ -29,5 +28,6 @@
+
diff --git a/lib/nanohttpd-2.3.1.jar b/lib/nanohttpd-2.3.1.jar
deleted file mode 100644
index 5f17a69..0000000
Binary files a/lib/nanohttpd-2.3.1.jar and /dev/null differ
diff --git a/lib/nanohttpd-2.3.2-SNAPSHOT.jar b/lib/nanohttpd-2.3.2-SNAPSHOT.jar
new file mode 100644
index 0000000..6b18cbf
Binary files /dev/null and b/lib/nanohttpd-2.3.2-SNAPSHOT.jar differ
diff --git a/src/com/projectswg/lightspeed/CommunicationService.java b/src/com/projectswg/lightspeed/CommunicationService.java
index 83f5b77..627d57f 100644
--- a/src/com/projectswg/lightspeed/CommunicationService.java
+++ b/src/com/projectswg/lightspeed/CommunicationService.java
@@ -27,14 +27,30 @@
***********************************************************************************/
package com.projectswg.lightspeed;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.nanohttpd.protocols.http.ClientHandler;
+import org.nanohttpd.protocols.http.IHTTPSession;
+import org.nanohttpd.protocols.http.NanoHTTPD;
+import org.nanohttpd.protocols.http.response.Response;
+import org.nanohttpd.protocols.http.response.Status;
+import org.nanohttpd.protocols.http.threading.IAsyncRunner;
+
import me.joshlarson.json.JSONObject;
import com.projectswg.common.concurrency.PswgThreadPool;
@@ -47,11 +63,6 @@ import com.projectswg.common.intents.RegisterHttpListenerIntent;
import com.projectswg.common.network.packets.PacketType;
import com.projectswg.lightspeed.communication.HttpResponder;
-import fi.iki.elonen.NanoHTTPD;
-import fi.iki.elonen.NanoHTTPD.IHTTPSession;
-import fi.iki.elonen.NanoHTTPD.Response;
-import fi.iki.elonen.NanoHTTPD.Response.Status;
-
public class CommunicationService extends LightspeedService {
private final NanoHTTPD httpServer;
@@ -75,6 +86,13 @@ public class CommunicationService extends LightspeedService {
public boolean initialize() {
Config config = getConfig(ConfigFile.LIGHTSPEED);
try {
+ String pubkey = config.getString("PUBKEY", "");
+ if (!pubkey.isEmpty()) {
+ String pass = config.getString("PUBKEY-PASS", "");
+ File cert = new File("data", pubkey);
+ System.setProperty("javax.net.ssl.trustStore", cert.getAbsolutePath());
+ httpServer.makeSecure(createSSLSocketFactory(cert, pass), null);
+ }
runner.start();
httpServer.start(config.getInt("HTTP-TIMEOUT", 2000), false);
} catch (IOException e) {
@@ -91,6 +109,25 @@ public class CommunicationService extends LightspeedService {
return super.terminate();
}
+ private SSLServerSocketFactory createSSLSocketFactory(File keystoreFile, String password) throws IOException {
+ try {
+ char [] passphrase = password.toCharArray();
+ KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ InputStream keystoreStream = new FileInputStream(keystoreFile);
+
+ keystore.load(keystoreStream, passphrase);
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ keyManagerFactory.init(keystore, passphrase);
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(keystore);
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
+ return ctx.getServerSocketFactory();
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
private Response serve(IHTTPSession session) {
try {
JSONObject params = getJSONObject(session);
@@ -126,7 +163,7 @@ public class CommunicationService extends LightspeedService {
JSONObject response = responder.request(request, params);
if (response == null)
return getNotFound("Invalid Request Parameters");
- return NanoHTTPD.newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_PLAINTEXT, response.toString());
+ return Response.newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_PLAINTEXT, response.toString());
}
private JSONObject getJSONObject(IHTTPSession session) {
@@ -156,15 +193,15 @@ public class CommunicationService extends LightspeedService {
}
private Response getMethodNotAllowed() {
- return NanoHTTPD.newFixedLengthResponse(Status.METHOD_NOT_ALLOWED, NanoHTTPD.MIME_PLAINTEXT, "Method Not Allowed");
+ return Response.newFixedLengthResponse(Status.METHOD_NOT_ALLOWED, NanoHTTPD.MIME_PLAINTEXT, "Method Not Allowed");
}
private Response getNotFound(String str) {
- return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, str);
+ return Response.newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, str);
}
private Response getServerError(String str) {
- return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, str);
+ return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, str);
}
private void handleRegisterListener(RegisterHttpListenerIntent rhli) {
@@ -176,10 +213,10 @@ public class CommunicationService extends LightspeedService {
return httpServer.getListeningPort();
}
- private static class BoundRunner implements NanoHTTPD.AsyncRunner {
+ private static class BoundRunner implements IAsyncRunner {
private final PswgThreadPool threadPool;
- private final List running;
+ private final List running;
public BoundRunner(int threads) {
if (threads <= 0)
@@ -203,21 +240,21 @@ public class CommunicationService extends LightspeedService {
public void closeAll() {
// copy of the list for concurrency
synchronized (running) {
- for (NanoHTTPD.ClientHandler clientHandler : running) {
+ for (ClientHandler clientHandler : running) {
clientHandler.close();
}
}
}
@Override
- public void closed(NanoHTTPD.ClientHandler clientHandler) {
+ public void closed(ClientHandler clientHandler) {
synchronized (running) {
running.remove(clientHandler);
}
}
@Override
- public void exec(NanoHTTPD.ClientHandler clientHandler) {
+ public void exec(ClientHandler clientHandler) {
threadPool.execute(clientHandler);
synchronized (running) {
running.add(clientHandler);
diff --git a/src/com/projectswg/lightspeed_frontend/Frontend.java b/src/com/projectswg/lightspeed_frontend/Frontend.java
index c22f84d..87ce91a 100644
--- a/src/com/projectswg/lightspeed_frontend/Frontend.java
+++ b/src/com/projectswg/lightspeed_frontend/Frontend.java
@@ -27,6 +27,8 @@
***********************************************************************************/
package com.projectswg.lightspeed_frontend;
+import java.io.File;
+import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
@@ -35,6 +37,7 @@ import com.projectswg.common.concurrency.PswgThreadPool;
import com.projectswg.common.data.TestDetails;
import com.projectswg.common.debug.Assert;
import com.projectswg.common.debug.Log;
+import com.projectswg.common.info.Config;
import com.projectswg.common.network.packets.Packet;
import com.projectswg.common.network.packets.request.RequestBuildDetailedPacket;
import com.projectswg.common.network.packets.request.RequestBuildListPacket;
@@ -62,13 +65,19 @@ public class Frontend {
private final PswgScheduledThreadPool scheduledUpdater;
private final FrontendCommunication communication;
private final FrontendData data;
+ private final Config config;
public Frontend() {
this.threadPool = new PswgThreadPool(10, "frontend-thread-pool");
this.scheduledUpdater = new PswgScheduledThreadPool(1, "frontend-updater");
- this.communication = new FrontendCommunication();
- this.communication.setCallback(packet -> process(packet));
this.data = new FrontendData();
+ File data = new File("cfg");
+ data.mkdirs();
+ File cfgFile = new File(data, "frontend.cfg");
+ try { cfgFile.createNewFile(); } catch (IOException e) { Log.e(e); }
+ this.config = new Config(cfgFile);
+ this.communication = new FrontendCommunication(config);
+ this.communication.setCallback(packet -> process(packet));
}
public void setAddress(InetSocketAddress address) {
@@ -97,6 +106,10 @@ public class Frontend {
return data;
}
+ public Config getConfig() {
+ return config;
+ }
+
public void send(Packet p, ResponseCallback callback) {
threadPool.execute(() -> {
Packet response = communication.send(p);
diff --git a/src/com/projectswg/lightspeed_frontend/LightspeedFrontendText.java b/src/com/projectswg/lightspeed_frontend/LightspeedFrontendText.java
index fda5d52..501e79d 100644
--- a/src/com/projectswg/lightspeed_frontend/LightspeedFrontendText.java
+++ b/src/com/projectswg/lightspeed_frontend/LightspeedFrontendText.java
@@ -49,16 +49,18 @@ import com.projectswg.lightspeed_frontend.data.SharedServerData;
public class LightspeedFrontendText {
+ private final Frontend frontend;
private final FrontendCommunication communication;
private final Queue inbound;
private boolean running;
public LightspeedFrontendText() {
- communication = new FrontendCommunication();
- inbound = new ArrayDeque<>();
- running = false;
- communication.setAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 44444));
- communication.setCallback(packet -> {
+ this.frontend = new Frontend();
+ this.communication = frontend.getCommunication();
+ this.inbound = new ArrayDeque<>();
+ this.running = false;
+ this.communication.setAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 44444));
+ this.communication.setCallback(packet -> {
synchronized (inbound) {
inbound.add(packet);
inbound.notify();
diff --git a/src/com/projectswg/lightspeed_frontend/communication/FrontendCommunication.java b/src/com/projectswg/lightspeed_frontend/communication/FrontendCommunication.java
index 80d2483..82c3303 100644
--- a/src/com/projectswg/lightspeed_frontend/communication/FrontendCommunication.java
+++ b/src/com/projectswg/lightspeed_frontend/communication/FrontendCommunication.java
@@ -27,11 +27,13 @@
***********************************************************************************/
package com.projectswg.lightspeed_frontend.communication;
+import java.io.File;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicBoolean;
import com.projectswg.common.debug.Assert;
import com.projectswg.common.debug.Log;
+import com.projectswg.common.info.Config;
import com.projectswg.common.network.packets.Packet;
public class FrontendCommunication {
@@ -40,10 +42,13 @@ public class FrontendCommunication {
private ResponseCallback callback;
private InetSocketAddress address;
- public FrontendCommunication() {
- running = new AtomicBoolean(false);
- callback = null;
- address = null;
+ public FrontendCommunication(Config config) {
+ this.running = new AtomicBoolean(false);
+ this.callback = null;
+ this.address = null;
+ String keystore = config.getString("KEYSTORE", "");
+ if (!keystore.isEmpty())
+ HttpClient.setupSSL(new File(keystore), config.getString("KEYSTORE-PASS", ""));
}
public InetSocketAddress getAddress() {
diff --git a/src/com/projectswg/lightspeed_frontend/communication/HttpClient.java b/src/com/projectswg/lightspeed_frontend/communication/HttpClient.java
index 1acaef7..7d4c140 100644
--- a/src/com/projectswg/lightspeed_frontend/communication/HttpClient.java
+++ b/src/com/projectswg/lightspeed_frontend/communication/HttpClient.java
@@ -28,15 +28,24 @@
package com.projectswg.lightspeed_frontend.communication;
import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
-import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.net.URLEncoder;
+import java.security.KeyStore;
import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
import me.joshlarson.json.JSON;
import me.joshlarson.json.JSONObject;
@@ -49,7 +58,35 @@ import com.projectswg.common.network.packets.request.RequestPacket;
public class HttpClient {
+ private static final AtomicBoolean SSL = new AtomicBoolean(false);
+
+ public static void setupSSL(File keystoreFile, String password) {
+ try {
+ InputStream keystoreStream = new FileInputStream(keystoreFile);
+ char[] trustPassword = password.toCharArray();
+ KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+ keystore.load(keystoreStream, trustPassword);
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ keyManagerFactory.init(keystore, trustPassword);
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(keystore);
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
+ SSLContext.setDefault(ctx);
+ HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
+ SSL.set(true);
+ } catch (Exception e) {
+ Log.e(e);
+ SSL.set(false);
+ }
+ }
+
public static Packet requestPacket(InetSocketAddress address, Packet packet) {
+ if (!SSL.get()) {
+ Log.e("Cannot send packet - SSL not initialized yet!");
+ return null;
+ }
try {
String str = requestString(address, packet);
JSONObject obj = JSON.readObject(str, true);
@@ -70,7 +107,7 @@ public class HttpClient {
public static String requestString(InetSocketAddress address, Packet packet) throws IOException {
URL url = new URL(generateURL(address, packet));
- HttpURLConnection con = (HttpURLConnection) url.openConnection();
+ HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod((packet instanceof PostPacket) ? "POST" : "GET");
con.setRequestProperty("User-Agent", "LightspeedFrontend");
@@ -90,7 +127,7 @@ public class HttpClient {
}
private static String generateURL(InetSocketAddress address, Packet packet) throws UnsupportedEncodingException {
- StringBuilder url = new StringBuilder("http://"+address.getAddress().getHostAddress()+":"+address.getPort()+"/?");
+ StringBuilder url = new StringBuilder("https://"+address.getAddress().getHostAddress()+":"+address.getPort()+"/?");
JSONObject obj = packet.getJSON();
if (packet instanceof PostPacket)
url.append("type=" + ((PostPacket) packet).getType().name().replace("POST_", "") + '&');
diff --git a/test/com/projectswg/lightspeed/frontend/LinearTestFrontendCommunication.java b/test/com/projectswg/lightspeed/frontend/LinearTestFrontendCommunication.java
deleted file mode 100644
index 819110a..0000000
--- a/test/com/projectswg/lightspeed/frontend/LinearTestFrontendCommunication.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/***********************************************************************************
-* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
-* *
-* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
-* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
-* Our goal is to create an emulator which will provide a server for players to *
-* continue playing a game similar to the one they used to play. We are basing *
-* it on the final publish of the game prior to end-game events. *
-* *
-* This file is part of Holocore. *
-* *
-* -------------------------------------------------------------------------------- *
-* *
-* Holocore is free software: you can redistribute it and/or modify *
-* it under the terms of the GNU Affero General Public License as *
-* published by the Free Software Foundation, either version 3 of the *
-* License, or (at your option) any later version. *
-* *
-* Holocore is distributed in the hope that it will be useful, *
-* but WITHOUT ANY WARRANTY; without even the implied warranty of *
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
-* GNU Affero General Public License for more details. *
-* *
-* You should have received a copy of the GNU Affero General Public License *
-* along with Holocore. If not, see . *
-* *
-***********************************************************************************/
-package com.projectswg.lightspeed.frontend;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import com.projectswg.lightspeed_frontend.communication.FrontendCommunication;
-
-public class LinearTestFrontendCommunication {
-
- private final FrontendCommunication communication;
- private final AtomicBoolean connected;
-
- public LinearTestFrontendCommunication() {
- communication = new FrontendCommunication();
- connected = new AtomicBoolean(false);
- communication.start();
- }
-
- public void shutdown() {
- communication.stop();
- }
-
- public boolean isConnected() {
- return connected.get();
- }
-
- public boolean waitForConnectionState(boolean state, long timeout) {
- while (connected.get() != state && timeout >= 0) {
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- return false;
- }
- timeout--;
- }
- return connected.get() == state;
- }
-
-}