Added SSL encryption

This commit is contained in:
Josh Larson
2017-03-26 00:49:04 -05:00
parent 1ec4d88cd7
commit b1e075da97
9 changed files with 123 additions and 94 deletions
+1 -1
View File
@@ -6,7 +6,6 @@
<classpathentry kind="lib" path="lib/sqlite-jdbc-3.8.11.2.jar"/>
<classpathentry kind="lib" path="lib/fast-json-1.4.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="lib" path="/home/josh/Downloads/nanohttpd-nanohttpd-project-2.3.1/core/target/nanohttpd-2.3.1.jar"/>
<classpathentry kind="lib" path="lib/org.eclipse.jgit-4.6.0.201612231935-r.jar">
<attributes>
<attribute name="javadoc_location" value="jar:platform:/resource/Lightspeed/lib/org.eclipse.jgit-4.6.0.201612231935-r-javadoc.jar!/"/>
@@ -29,5 +28,6 @@
<classpathentry kind="lib" path="lib/ant/ant-testutil.jar"/>
<classpathentry kind="lib" path="lib/hamcrest-core-1.3.jar"/>
<classpathentry kind="lib" path="lib/junit-4.12.jar"/>
<classpathentry kind="lib" path="lib/nanohttpd-2.3.2-SNAPSHOT.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
Binary file not shown.
Binary file not shown.
@@ -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<NanoHTTPD.ClientHandler> running;
private final List<ClientHandler> 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);
@@ -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);
@@ -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<Packet> 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();
@@ -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() {
@@ -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_", "") + '&');
@@ -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 <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
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;
}
}