Implemented HMAC authentication in RemoteRecorder
This commit is contained in:
parent
d62bba5599
commit
bb6ba48f49
|
@ -5,3 +5,4 @@
|
|||
/ctbrec.log
|
||||
/ctbrec-tunnel.sh
|
||||
/jre/
|
||||
/server-local.sh
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<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();
|
||||
}
|
||||
|
|
|
@ -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\": \"<<action>>\", \"model\": <<model>>}";
|
||||
requestTemplate = requestTemplate.replaceAll("<<action>>", action);
|
||||
requestTemplate = requestTemplate.replaceAll("<<model>>", 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<Recording> getRecordings() throws IOException {
|
||||
RequestBody body = RequestBody.create(JSON, "{\"action\": \"recordings\"}");
|
||||
Request request = new Request.Builder()
|
||||
public List<Recording> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<List<JavaFxRecording>> createTask() {
|
||||
return new Task<List<JavaFxRecording>>() {
|
||||
@Override
|
||||
public List<JavaFxRecording> call() throws IOException {
|
||||
public List<JavaFxRecording> call() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||
List<JavaFxRecording> 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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue