diff --git a/.gitignore b/.gitignore index bd2c5fee..fc247909 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /ctbrec.log /ctbrec-tunnel.sh /jre/ +/server-local.sh diff --git a/src/main/java/ctbrec/recorder/LocalRecorder.java b/src/main/java/ctbrec/recorder/LocalRecorder.java index 49fabdcb..9c49c9a3 100644 --- a/src/main/java/ctbrec/recorder/LocalRecorder.java +++ b/src/main/java/ctbrec/recorder/LocalRecorder.java @@ -82,7 +82,7 @@ public class LocalRecorder implements Recorder { } @Override - public void stopRecording(Model model) throws IOException, InterruptedException { + public void stopRecording(Model model) throws IOException { lock.lock(); try { if (models.contains(model)) { @@ -130,7 +130,7 @@ public class LocalRecorder implements Recorder { } } - private void stopRecordingProcess(Model model) throws IOException, InterruptedException { + private void stopRecordingProcess(Model model) throws IOException { lock.lock(); try { Download download = recordingProcesses.get(model); diff --git a/src/main/java/ctbrec/recorder/Recorder.java b/src/main/java/ctbrec/recorder/Recorder.java index 472254e6..83f1a466 100644 --- a/src/main/java/ctbrec/recorder/Recorder.java +++ b/src/main/java/ctbrec/recorder/Recorder.java @@ -1,15 +1,17 @@ package ctbrec.recorder; import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.List; import ctbrec.Model; import ctbrec.Recording; public interface Recorder { - public void startRecording(Model model) throws IOException; + public void startRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; - public void stopRecording(Model model) throws IOException, InterruptedException; + public void stopRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; /** * Returns, if a model is in the list of models to record. This does not reflect, if there currently is a recording running. The model might be offline @@ -19,9 +21,9 @@ public interface Recorder { public List getModelsRecording(); - public List getRecordings() throws IOException; + public List getRecordings() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; - public void delete(Recording recording) throws IOException; + public void delete(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; public void shutdown(); } diff --git a/src/main/java/ctbrec/recorder/RemoteRecorder.java b/src/main/java/ctbrec/recorder/RemoteRecorder.java index 9ea6dddc..c1ed50a9 100644 --- a/src/main/java/ctbrec/recorder/RemoteRecorder.java +++ b/src/main/java/ctbrec/recorder/RemoteRecorder.java @@ -1,6 +1,9 @@ package ctbrec.recorder; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.Collections; import java.util.List; @@ -12,12 +15,14 @@ import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import ctbrec.Config; +import ctbrec.Hmac; import ctbrec.HttpClient; import ctbrec.InstantJsonAdapter; import ctbrec.Model; import ctbrec.Recording; import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.Request.Builder; import okhttp3.RequestBody; import okhttp3.Response; @@ -49,25 +54,26 @@ public class RemoteRecorder implements Recorder { } @Override - public void startRecording(Model model) throws IOException { + public void startRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { sendRequest("start", model); } @Override - public void stopRecording(Model model) throws IOException, InterruptedException { + public void stopRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { sendRequest("stop", model); } - private void sendRequest(String action, Model model) throws IOException { + private void sendRequest(String action, Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { String requestTemplate = "{\"action\": \"<>\", \"model\": <>}"; requestTemplate = requestTemplate.replaceAll("<>", action); requestTemplate = requestTemplate.replaceAll("<>", modelAdapter.toJson(model)); LOG.debug("Sending request to recording server: {}", requestTemplate); RequestBody body = RequestBody.create(JSON, requestTemplate); - Request request = new Request.Builder() + Request.Builder builder = new Request.Builder() .url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec") - .post(body) - .build(); + .post(body); + addHmacIfNeeded(requestTemplate, builder); + Request request = builder.build(); Response response = client.execute(request); String json = response.body().string(); if(response.isSuccessful()) { @@ -86,6 +92,14 @@ public class RemoteRecorder implements Recorder { } } + private void addHmacIfNeeded(String msg, Builder builder) throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException { + if(Config.getInstance().getSettings().requireAuthentication) { + byte[] key = Config.getInstance().getSettings().key; + String hmac = Hmac.calculate(msg, key); + builder.addHeader("CTBREC-HMAC", hmac); + } + } + @Override public boolean isRecording(Model model) { return models != null && models.contains(model); @@ -116,12 +130,14 @@ public class RemoteRecorder implements Recorder { public void run() { running = true; while(running) { - RequestBody body = RequestBody.create(JSON, "{\"action\": \"list\"}"); - Request request = new Request.Builder() - .url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec") - .post(body) - .build(); try { + String msg = "{\"action\": \"list\"}"; + RequestBody body = RequestBody.create(JSON, msg); + Request.Builder builder = new Request.Builder() + .url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec") + .post(body); + addHmacIfNeeded(msg, builder); + Request request = builder.build(); Response response = client.execute(request); String json = response.body().string(); if(response.isSuccessful()) { @@ -135,7 +151,7 @@ public class RemoteRecorder implements Recorder { } else { LOG.error("Couldn't synchronize with server. HTTP status: {} - {}", response.code(), json); } - } catch (IOException e) { + } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e) { LOG.error("Couldn't synchronize with server", e); } @@ -170,13 +186,14 @@ public class RemoteRecorder implements Recorder { } @Override - public List getRecordings() throws IOException { - RequestBody body = RequestBody.create(JSON, "{\"action\": \"recordings\"}"); - Request request = new Request.Builder() + public List getRecordings() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { + String msg = "{\"action\": \"recordings\"}"; + RequestBody body = RequestBody.create(JSON, msg); + Request.Builder builder = new Request.Builder() .url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec") - .post(body) - .build(); - + .post(body); + addHmacIfNeeded(msg, builder); + Request request = builder.build(); Response response = client.execute(request); String json = response.body().string(); if(response.isSuccessful()) { @@ -196,22 +213,24 @@ public class RemoteRecorder implements Recorder { } @Override - public void delete(Recording recording) throws IOException { - RequestBody body = RequestBody.create(JSON, "{\"action\": \"delete\", \"recording\": \""+recording.getPath()+"\"}"); - Request request = new Request.Builder() + public void delete(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { + String msg = "{\"action\": \"delete\", \"recording\": \""+recording.getPath()+"\"}"; + RequestBody body = RequestBody.create(JSON, msg); + Request.Builder builder = new Request.Builder() .url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec") - .post(body) - .build(); - + .post(body); + addHmacIfNeeded(msg, builder); + Request request = builder.build(); Response response = client.execute(request); String json = response.body().string(); + RecordingListResponse resp = recordingListResponseAdapter.fromJson(json); if(response.isSuccessful()) { - RecordingListResponse resp = recordingListResponseAdapter.fromJson(json); if(!resp.status.equals("success")) { - throw new IOException("Couldn't delete recording: " + resp.status + " " + resp.msg); + throw new IOException("Couldn't delete recording: " + resp.msg); } } else { - throw new IOException("Couldn't delete recording: " + response.code() + " " + json); + throw new IOException("Couldn't delete recording: " + resp.msg); + //throw new IOException("Couldn't delete recording: " + response.code() + " " + json); } } } diff --git a/src/main/java/ctbrec/ui/RecordedModelsTab.java b/src/main/java/ctbrec/ui/RecordedModelsTab.java index cd60d949..d41b5f03 100644 --- a/src/main/java/ctbrec/ui/RecordedModelsTab.java +++ b/src/main/java/ctbrec/ui/RecordedModelsTab.java @@ -1,6 +1,8 @@ package ctbrec.ui; import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutorService; @@ -112,7 +114,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { m.setUrl("https://chaturbate.com/" + m.getName() + "/"); try { recorder.startRecording(m); - } catch (IOException e1) { + } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); alert.setTitle("Error"); alert.setHeaderText("Couldn't add model"); @@ -229,16 +231,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { try { recorder.stopRecording(selected); observableModels.remove(selected); - } catch (IOException e1) { - LOG.error("Couldn't stop recording", e1); - Platform.runLater(() -> { - Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); - alert.setTitle("Error"); - alert.setHeaderText("Couldn't stop recording"); - alert.setContentText("I/O error while stopping the recording: " + e1.getLocalizedMessage()); - alert.showAndWait(); - }); - } catch (InterruptedException e1) { + } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { LOG.error("Couldn't stop recording", e1); Platform.runLater(() -> { Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); diff --git a/src/main/java/ctbrec/ui/RecordingsTab.java b/src/main/java/ctbrec/ui/RecordingsTab.java index 45adfbc8..4356e05f 100644 --- a/src/main/java/ctbrec/ui/RecordingsTab.java +++ b/src/main/java/ctbrec/ui/RecordingsTab.java @@ -10,6 +10,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -175,7 +177,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener { protected Task> createTask() { return new Task>() { @Override - public List call() throws IOException { + public List call() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { List recordings = new ArrayList<>(); for (Recording rec : recorder.getRecordings()) { recordings.add(new JavaFxRecording(rec)); @@ -446,7 +448,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener { try { recorder.delete(r); Platform.runLater(() -> observableRecordings.remove(r)); - } catch (IOException e1) { + } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { LOG.error("Error while deleting recording", e1); showErrorDialog("Error while deleting recording", "Recording not deleted", e1); } finally { diff --git a/src/main/java/ctbrec/ui/SettingsTab.java b/src/main/java/ctbrec/ui/SettingsTab.java index aac07c64..458b0799 100644 --- a/src/main/java/ctbrec/ui/SettingsTab.java +++ b/src/main/java/ctbrec/ui/SettingsTab.java @@ -2,11 +2,13 @@ package ctbrec.ui; import java.io.File; import java.io.IOException; +import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ctbrec.Config; +import ctbrec.Hmac; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.geometry.Insets; @@ -20,6 +22,7 @@ import javafx.scene.control.PasswordField; import javafx.scene.control.RadioButton; import javafx.scene.control.Tab; import javafx.scene.control.TextField; +import javafx.scene.control.TextInputDialog; import javafx.scene.control.ToggleGroup; import javafx.scene.control.Tooltip; import javafx.scene.layout.Border; @@ -43,6 +46,7 @@ public class SettingsTab extends Tab { private TextField server; private TextField port; private CheckBox loadResolution; + private CheckBox secureCommunication; private PasswordField password; private RadioButton recordLocal; private RadioButton recordRemote; @@ -169,6 +173,32 @@ public class SettingsTab extends Tab { GridPane.setColumnSpan(port, 2); layout.add(port, 1, row); + layout.add(new Label("Require authentication"), 0, ++row); + secureCommunication = new CheckBox(); + secureCommunication.setSelected(Config.getInstance().getSettings().requireAuthentication); + secureCommunication.setOnAction((e) -> { + Config.getInstance().getSettings().requireAuthentication = secureCommunication.isSelected(); + if(secureCommunication.isSelected()) { + byte[] key = Config.getInstance().getSettings().key; + if(key == null) { + key = Hmac.generateKey(); + Config.getInstance().getSettings().key = key; + } + TextInputDialog keyDialog = new TextInputDialog(); + keyDialog.setResizable(true); + keyDialog.setTitle("Server Authentication"); + keyDialog.setHeaderText("A key has been generated"); + keyDialog.setContentText("Add this setting to your server's config.json:\n"); + keyDialog.getEditor().setText("\"key\": " + Arrays.toString(key)); + keyDialog.getEditor().setEditable(false); + keyDialog.setWidth(800); + keyDialog.setHeight(200); + keyDialog.show(); + } + }); + layout.add(secureCommunication, 1, row); + + server.setDisable(recordLocal.isSelected()); port.setDisable(recordLocal.isSelected()); }