Implemented HMAC authentication in RemoteRecorder

This commit is contained in:
0xboobface 2018-07-07 13:23:00 +02:00
parent d62bba5599
commit bb6ba48f49
7 changed files with 93 additions and 46 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
/ctbrec.log
/ctbrec-tunnel.sh
/jre/
/server-local.sh

View File

@ -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);

View File

@ -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();
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

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

View File

@ -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());
}