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 @@ + - + + +