diff --git a/CHANGELOG.md b/CHANGELOG.md
index 69c141c7..0b6fe1ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,14 @@
+2.2.0
+========================
+* Added HMAC authentication support to the webinterface
+
+
2.1.0
========================
This release is mainly for the server.
* Added webinterface for the server. Has to be activated in the server config
- (webinterface). You can access it via http://host:4711/static/index.html
+ (webinterface). You can access it via http://host:port/static/index.html
* Disabled MyFreeCams for the time being. (If you are brave, you can still
use an older version, but don't blame me, if your account or IP is getting
blocked)
diff --git a/common/src/main/java/ctbrec/Hmac.java b/common/src/main/java/ctbrec/Hmac.java
index 46f1751f..f58370b6 100644
--- a/common/src/main/java/ctbrec/Hmac.java
+++ b/common/src/main/java/ctbrec/Hmac.java
@@ -4,6 +4,7 @@ import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@@ -16,11 +17,11 @@ public class Hmac {
private static final transient Logger LOG = LoggerFactory.getLogger(Hmac.class);
public static byte[] generateKey() {
- LOG.debug("Generating key");
+ LOG.debug("Generating HMAC key");
SecureRandom random = new SecureRandom();
byte[] key = new byte[32];
random.nextBytes(key);
- return key;
+ return Base64.getEncoder().encode(key);
}
public static String calculate(String msg, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException {
@@ -42,7 +43,10 @@ public class Hmac {
* @param hash
* @return string
*/
- static String bytesToHex(byte[] hash) {
+ public static String bytesToHex(byte[] hash) {
+ if (hash == null) {
+ return "";
+ }
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java
index d9f8fd1a..e0b119ea 100644
--- a/common/src/main/java/ctbrec/Settings.java
+++ b/common/src/main/java/ctbrec/Settings.java
@@ -115,4 +115,6 @@ public class Settings {
public int windowX;
public int windowY;
public boolean webinterface = false;
+ public String webinterfaceUsername = "ctbrec";
+ public String webinterfacePassword = "sucks";
}
diff --git a/common/src/main/java/ctbrec/servlet/StaticFileServlet.java b/common/src/main/java/ctbrec/servlet/StaticFileServlet.java
index 5d47fc54..a0965651 100644
--- a/common/src/main/java/ctbrec/servlet/StaticFileServlet.java
+++ b/common/src/main/java/ctbrec/servlet/StaticFileServlet.java
@@ -31,7 +31,7 @@ public class StaticFileServlet extends HttpServlet {
private void serveFile(String resource, HttpServletResponse resp) throws IOException {
InputStream resourceAsStream = getClass().getResourceAsStream(classPathRoot + resource);
- if(resourceAsStream == null) {
+ if (resourceAsStream == null) {
throw new FileNotFoundException();
}
resp.setContentType(URLConnection.guessContentTypeFromName(resource));
@@ -39,7 +39,7 @@ public class StaticFileServlet extends HttpServlet {
OutputStream out = resp.getOutputStream();
int length = 0;
byte[] buffer = new byte[1024];
- while( (length = resourceAsStream.read(buffer)) >= 0 ) {
+ while ((length = resourceAsStream.read(buffer)) >= 0) {
out.write(buffer, 0, length);
}
}
diff --git a/server/src/main/java/ctbrec/recorder/server/HttpServer.java b/server/src/main/java/ctbrec/recorder/server/HttpServer.java
index 638f7275..1e34609b 100644
--- a/server/src/main/java/ctbrec/recorder/server/HttpServer.java
+++ b/server/src/main/java/ctbrec/recorder/server/HttpServer.java
@@ -8,17 +8,34 @@ import java.net.BindException;
import java.util.ArrayList;
import java.util.List;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.UserStore;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Credential;
+import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Objects;
+
import ctbrec.Config;
import ctbrec.Version;
import ctbrec.event.EventBusHolder;
@@ -128,10 +145,14 @@ public class HttpServer {
http.setIdleTimeout(this.config.getSettings().httpTimeout);
server.addConnector(http);
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/secured/*");
+ server.setHandler(context);
+
ServletHandler handler = new ServletHandler();
- server.setHandler(handler);
+ //server.setHandler(handler);
HandlerList handlers = new HandlerList();
- handlers.setHandlers(new Handler[] { handler });
+ handlers.setHandlers(new Handler[] { context, handler });
server.setHandler(handlers);
RecorderServlet recorderServlet = new RecorderServlet(recorder, sites);
@@ -142,12 +163,31 @@ public class HttpServer {
holder = new ServletHolder(hlsServlet);
handler.addServletWithMapping(holder, "/hls/*");
+
if (this.config.getSettings().webinterface) {
- String staticContext = "/static/*";
- LOG.info("Register static file servlet under {}", staticContext);
+ LOG.info("Register static file servlet under {}", context.getContextPath());
StaticFileServlet staticFileServlet = new StaticFileServlet("/html");
holder = new ServletHolder(staticFileServlet);
- handler.addServletWithMapping(holder, staticContext);
+ handler.addServletWithMapping(holder, "/static/*");
+ //context.addServlet(holder, "/");
+
+ // servlet to retrieve the HMAC secured by basic auth
+ String username = this.config.getSettings().webinterfaceUsername;
+ String password = this.config.getSettings().webinterfacePassword;
+ context.setSecurityHandler(basicAuth(username, password, "CTB Recorder"));
+ context.addServlet(new ServletHolder(new HttpServlet() {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ if(Objects.equal(username, req.getRemoteUser())) {
+ resp.setStatus(HttpServletResponse.SC_OK);
+ resp.setContentType("application/json");
+ byte[] hmac = HttpServer.this.config.getSettings().key;
+ JSONObject response = new JSONObject();
+ response.put("hmac", new String(hmac, "utf-8"));
+ resp.getOutputStream().println(response.toString());
+ }
+ }
+ }), "/hmac");
}
try {
@@ -159,6 +199,31 @@ public class HttpServer {
}
}
+ private static final SecurityHandler basicAuth(String username, String password, String realm) {
+ UserStore userStore = new UserStore();
+ userStore.addUser(username, Credential.getCredential(password), new String[] { "user" });
+ HashLoginService l = new HashLoginService();
+ l.setUserStore(userStore);
+ l.setName(realm);
+
+ Constraint constraint = new Constraint();
+ constraint.setName(Constraint.__BASIC_AUTH);
+ constraint.setRoles(new String[] { "user" });
+ constraint.setAuthenticate(true);
+
+ ConstraintMapping cm = new ConstraintMapping();
+ cm.setConstraint(constraint);
+ cm.setPathSpec("/*");
+
+ ConstraintSecurityHandler csh = new ConstraintSecurityHandler();
+ csh.setAuthenticator(new BasicAuthenticator());
+ csh.setRealmName("myrealm");
+ csh.addConstraintMapping(cm);
+ csh.setLoginService(l);
+
+ return csh;
+ }
+
private void registerAlertSystem() {
for (EventHandlerConfiguration config : Config.getInstance().getSettings().eventHandlers) {
EventHandler handler = new EventHandler(config);
diff --git a/server/src/main/resources/html/static/index.html b/server/src/main/resources/html/static/index.html
index 1d0f17f3..f1979b0b 100644
--- a/server/src/main/resources/html/static/index.html
+++ b/server/src/main/resources/html/static/index.html
@@ -207,11 +207,12 @@
+
-
+
+
+