diff --git a/src/main/java/ctbrec/Config.java b/src/main/java/ctbrec/Config.java index 5c9db23d..a539ac0d 100644 --- a/src/main/java/ctbrec/Config.java +++ b/src/main/java/ctbrec/Config.java @@ -71,7 +71,7 @@ public class Config { public void save() throws IOException { Moshi moshi = new Moshi.Builder().build(); - JsonAdapter adapter = moshi.adapter(Settings.class); + JsonAdapter adapter = moshi.adapter(Settings.class).indent(" "); String json = adapter.toJson(settings); File configDir = OS.getConfigDir(); File configFile = new File(configDir, filename); diff --git a/src/main/java/ctbrec/Hmac.java b/src/main/java/ctbrec/Hmac.java new file mode 100644 index 00000000..46f1751f --- /dev/null +++ b/src/main/java/ctbrec/Hmac.java @@ -0,0 +1,55 @@ +package ctbrec; + +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Hmac { + + private static final transient Logger LOG = LoggerFactory.getLogger(Hmac.class); + + public static byte[] generateKey() { + LOG.debug("Generating key"); + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + return key; + } + + public static String calculate(String msg, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException { + Mac mac = Mac.getInstance("HmacSHA256"); + SecretKeySpec keySpec = new SecretKeySpec(key, "HmacSHA256"); + mac.init(keySpec); + byte[] result = mac.doFinal(msg.getBytes("UTF-8")); + String hmac = bytesToHex(result); + return hmac; + } + + public static boolean validate(String msg, byte[] key, String hmacToCheck) throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException { + return Hmac.calculate(msg, key).equals(hmacToCheck); + } + + /** + * Converts a byte array to a string + * + * @param hash + * @return string + */ + static String bytesToHex(byte[] hash) { + StringBuffer hexString = new StringBuffer(); + for (int i = 0; i < hash.length; i++) { + String hex = Integer.toHexString(0xff & hash[i]); + if (hex.length() == 1) + hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index 4d55d2b4..3d18cee8 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -16,4 +16,6 @@ public class Settings { public String lastDownloadDir = ""; public List models = new ArrayList(); public boolean determineResolution = false; + public boolean requireAuthentication = false; + public byte[] key = null; } diff --git a/src/main/java/ctbrec/recorder/server/RecorderServlet.java b/src/main/java/ctbrec/recorder/server/RecorderServlet.java index c0d8d549..a660b7b1 100644 --- a/src/main/java/ctbrec/recorder/server/RecorderServlet.java +++ b/src/main/java/ctbrec/recorder/server/RecorderServlet.java @@ -3,9 +3,12 @@ package ctbrec.recorder.server; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static javax.servlet.http.HttpServletResponse.SC_OK; +import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import java.io.BufferedReader; import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.Iterator; import java.util.List; @@ -21,6 +24,8 @@ import org.slf4j.LoggerFactory; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; +import ctbrec.Config; +import ctbrec.Hmac; import ctbrec.InstantJsonAdapter; import ctbrec.Model; import ctbrec.Recording; @@ -43,6 +48,14 @@ public class RecorderServlet extends HttpServlet { try { String json = body(req); + boolean isRequestAuthenticated = checkAuthentication(req, json); + if(!isRequestAuthenticated) { + resp.setStatus(SC_UNAUTHORIZED); + String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}"; + resp.getWriter().write(response); + return; + } + LOG.debug("Request: {}", json); Moshi moshi = new Moshi.Builder() .add(Instant.class, new InstantJsonAdapter()) @@ -116,6 +129,21 @@ public class RecorderServlet extends HttpServlet { } } + private boolean checkAuthentication(HttpServletRequest req, String body) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { + boolean authenticated = false; + if(Config.getInstance().getSettings().key != null) { + if(req.getHeader("CTBREC-HMAC") == null) { + authenticated = false; + } + + byte[] key = Config.getInstance().getSettings().key; + authenticated = Hmac.validate(body, key, req.getHeader("CTBREC-HMAC")); + } else { + authenticated = true; + } + return authenticated; + } + private String body(HttpServletRequest req) throws IOException { StringBuilder body = new StringBuilder(); BufferedReader br = req.getReader();