Implemented HMAC authentication in RemoteRecorder
This commit is contained in:
parent
d62bba5599
commit
bb6ba48f49
|
@ -5,3 +5,4 @@
|
||||||
/ctbrec.log
|
/ctbrec.log
|
||||||
/ctbrec-tunnel.sh
|
/ctbrec-tunnel.sh
|
||||||
/jre/
|
/jre/
|
||||||
|
/server-local.sh
|
||||||
|
|
|
@ -82,7 +82,7 @@ public class LocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopRecording(Model model) throws IOException, InterruptedException {
|
public void stopRecording(Model model) throws IOException {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
if (models.contains(model)) {
|
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();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
Download download = recordingProcesses.get(model);
|
Download download = recordingProcesses.get(model);
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
package ctbrec.recorder;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
|
|
||||||
public interface Recorder {
|
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
|
* 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<Model> getModelsRecording();
|
public List<Model> getModelsRecording();
|
||||||
|
|
||||||
public List<Recording> getRecordings() throws IOException;
|
public List<Recording> 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();
|
public void shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package ctbrec.recorder;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -12,12 +15,14 @@ import com.squareup.moshi.JsonAdapter;
|
||||||
import com.squareup.moshi.Moshi;
|
import com.squareup.moshi.Moshi;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Hmac;
|
||||||
import ctbrec.HttpClient;
|
import ctbrec.HttpClient;
|
||||||
import ctbrec.InstantJsonAdapter;
|
import ctbrec.InstantJsonAdapter;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Request.Builder;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
@ -49,25 +54,26 @@ public class RemoteRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startRecording(Model model) throws IOException {
|
public void startRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||||
sendRequest("start", model);
|
sendRequest("start", model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopRecording(Model model) throws IOException, InterruptedException {
|
public void stopRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||||
sendRequest("stop", model);
|
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\": \"<<action>>\", \"model\": <<model>>}";
|
String requestTemplate = "{\"action\": \"<<action>>\", \"model\": <<model>>}";
|
||||||
requestTemplate = requestTemplate.replaceAll("<<action>>", action);
|
requestTemplate = requestTemplate.replaceAll("<<action>>", action);
|
||||||
requestTemplate = requestTemplate.replaceAll("<<model>>", modelAdapter.toJson(model));
|
requestTemplate = requestTemplate.replaceAll("<<model>>", modelAdapter.toJson(model));
|
||||||
LOG.debug("Sending request to recording server: {}", requestTemplate);
|
LOG.debug("Sending request to recording server: {}", requestTemplate);
|
||||||
RequestBody body = RequestBody.create(JSON, 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")
|
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||||
.post(body)
|
.post(body);
|
||||||
.build();
|
addHmacIfNeeded(requestTemplate, builder);
|
||||||
|
Request request = builder.build();
|
||||||
Response response = client.execute(request);
|
Response response = client.execute(request);
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if(response.isSuccessful()) {
|
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
|
@Override
|
||||||
public boolean isRecording(Model model) {
|
public boolean isRecording(Model model) {
|
||||||
return models != null && models.contains(model);
|
return models != null && models.contains(model);
|
||||||
|
@ -116,12 +130,14 @@ public class RemoteRecorder implements Recorder {
|
||||||
public void run() {
|
public void run() {
|
||||||
running = true;
|
running = true;
|
||||||
while(running) {
|
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 {
|
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);
|
Response response = client.execute(request);
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if(response.isSuccessful()) {
|
if(response.isSuccessful()) {
|
||||||
|
@ -135,7 +151,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
} else {
|
} else {
|
||||||
LOG.error("Couldn't synchronize with server. HTTP status: {} - {}", response.code(), json);
|
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);
|
LOG.error("Couldn't synchronize with server", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,13 +186,14 @@ public class RemoteRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Recording> getRecordings() throws IOException {
|
public List<Recording> getRecordings() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||||
RequestBody body = RequestBody.create(JSON, "{\"action\": \"recordings\"}");
|
String msg = "{\"action\": \"recordings\"}";
|
||||||
Request request = new Request.Builder()
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
|
Request.Builder builder = new Request.Builder()
|
||||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||||
.post(body)
|
.post(body);
|
||||||
.build();
|
addHmacIfNeeded(msg, builder);
|
||||||
|
Request request = builder.build();
|
||||||
Response response = client.execute(request);
|
Response response = client.execute(request);
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if(response.isSuccessful()) {
|
if(response.isSuccessful()) {
|
||||||
|
@ -196,22 +213,24 @@ public class RemoteRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(Recording recording) throws IOException {
|
public void delete(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||||
RequestBody body = RequestBody.create(JSON, "{\"action\": \"delete\", \"recording\": \""+recording.getPath()+"\"}");
|
String msg = "{\"action\": \"delete\", \"recording\": \""+recording.getPath()+"\"}";
|
||||||
Request request = new Request.Builder()
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
|
Request.Builder builder = new Request.Builder()
|
||||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||||
.post(body)
|
.post(body);
|
||||||
.build();
|
addHmacIfNeeded(msg, builder);
|
||||||
|
Request request = builder.build();
|
||||||
Response response = client.execute(request);
|
Response response = client.execute(request);
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
|
RecordingListResponse resp = recordingListResponseAdapter.fromJson(json);
|
||||||
if(response.isSuccessful()) {
|
if(response.isSuccessful()) {
|
||||||
RecordingListResponse resp = recordingListResponseAdapter.fromJson(json);
|
|
||||||
if(!resp.status.equals("success")) {
|
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 {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package ctbrec.ui;
|
package ctbrec.ui;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
@ -112,7 +114,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
m.setUrl("https://chaturbate.com/" + m.getName() + "/");
|
m.setUrl("https://chaturbate.com/" + m.getName() + "/");
|
||||||
try {
|
try {
|
||||||
recorder.startRecording(m);
|
recorder.startRecording(m);
|
||||||
} catch (IOException e1) {
|
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||||
alert.setTitle("Error");
|
alert.setTitle("Error");
|
||||||
alert.setHeaderText("Couldn't add model");
|
alert.setHeaderText("Couldn't add model");
|
||||||
|
@ -229,16 +231,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
try {
|
try {
|
||||||
recorder.stopRecording(selected);
|
recorder.stopRecording(selected);
|
||||||
observableModels.remove(selected);
|
observableModels.remove(selected);
|
||||||
} catch (IOException e1) {
|
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException 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) {
|
|
||||||
LOG.error("Couldn't stop recording", e1);
|
LOG.error("Couldn't stop recording", e1);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||||
|
|
|
@ -10,6 +10,8 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -175,7 +177,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
protected Task<List<JavaFxRecording>> createTask() {
|
protected Task<List<JavaFxRecording>> createTask() {
|
||||||
return new Task<List<JavaFxRecording>>() {
|
return new Task<List<JavaFxRecording>>() {
|
||||||
@Override
|
@Override
|
||||||
public List<JavaFxRecording> call() throws IOException {
|
public List<JavaFxRecording> call() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||||
List<JavaFxRecording> recordings = new ArrayList<>();
|
List<JavaFxRecording> recordings = new ArrayList<>();
|
||||||
for (Recording rec : recorder.getRecordings()) {
|
for (Recording rec : recorder.getRecordings()) {
|
||||||
recordings.add(new JavaFxRecording(rec));
|
recordings.add(new JavaFxRecording(rec));
|
||||||
|
@ -446,7 +448,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
try {
|
try {
|
||||||
recorder.delete(r);
|
recorder.delete(r);
|
||||||
Platform.runLater(() -> observableRecordings.remove(r));
|
Platform.runLater(() -> observableRecordings.remove(r));
|
||||||
} catch (IOException e1) {
|
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||||
LOG.error("Error while deleting recording", e1);
|
LOG.error("Error while deleting recording", e1);
|
||||||
showErrorDialog("Error while deleting recording", "Recording not deleted", e1);
|
showErrorDialog("Error while deleting recording", "Recording not deleted", e1);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -2,11 +2,13 @@ package ctbrec.ui;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Hmac;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
@ -20,6 +22,7 @@ import javafx.scene.control.PasswordField;
|
||||||
import javafx.scene.control.RadioButton;
|
import javafx.scene.control.RadioButton;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.TextInputDialog;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.layout.Border;
|
import javafx.scene.layout.Border;
|
||||||
|
@ -43,6 +46,7 @@ public class SettingsTab extends Tab {
|
||||||
private TextField server;
|
private TextField server;
|
||||||
private TextField port;
|
private TextField port;
|
||||||
private CheckBox loadResolution;
|
private CheckBox loadResolution;
|
||||||
|
private CheckBox secureCommunication;
|
||||||
private PasswordField password;
|
private PasswordField password;
|
||||||
private RadioButton recordLocal;
|
private RadioButton recordLocal;
|
||||||
private RadioButton recordRemote;
|
private RadioButton recordRemote;
|
||||||
|
@ -169,6 +173,32 @@ public class SettingsTab extends Tab {
|
||||||
GridPane.setColumnSpan(port, 2);
|
GridPane.setColumnSpan(port, 2);
|
||||||
layout.add(port, 1, row);
|
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());
|
server.setDisable(recordLocal.isSelected());
|
||||||
port.setDisable(recordLocal.isSelected());
|
port.setDisable(recordLocal.isSelected());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue