Compare commits

...

11 Commits

Author SHA1 Message Date
a5fd5444da Fix a typo 2025-07-07 19:22:10 +00:00
b4df31a31a Add linux packaging instructions 2025-07-06 20:47:47 -07:00
4881a135f2 Update readme 2025-07-06 16:02:41 -07:00
ca502fd411 Remove schedule test buttons 2025-07-05 18:18:36 -07:00
c8a1128e11 Widen default help window size 2025-07-05 17:47:49 -07:00
b9a2202c68 Make version field full-width 2025-07-05 17:47:30 -07:00
671844723f Don't error if station doesn't have dir when deleted 2025-07-05 17:47:18 -07:00
7a0ce063f1 Add deletion of station schedule info when station is removed
Task scheduler entry for Windows, cron entry for *nix
2025-07-05 17:46:56 -07:00
62bfbb1923 Add log path to settings load error
Since they wouldn't be able to run the application to see the log path.

Also make no the default option
2025-07-05 17:44:49 -07:00
2992572651 Remove some unnecessary printlns 2025-07-05 13:47:51 -07:00
b25fea4549 Remove jitter todo from settings page 2025-06-08 12:28:57 -07:00
9 changed files with 159 additions and 166 deletions

View File

@ -1,52 +1,40 @@
For sending secret messages and dummy messages regularly. It is meant to be used in conjunction with one-time pads. One-time pads provide perfect security for the message itself (if they are handled properly), but adversaries can still tell that messages are being sent, and when they are being sent. To avoid this, dummy messages are uploaded to a website on a regular basis. The messages can also be hidden using steganography. For sending secret messages and dummy messages regularly. It is meant to be used in conjunction with one-time pads. One-time pads provide perfect security for the message itself (if they are handled properly), but adversaries can still tell that messages are being sent, and when they are being sent. To avoid this, dummy messages are uploaded to a website on a regular basis.
This application should not be considered secure, and no sensitive information should be entered into it. This application should not be considered secure, and no sensitive information should be entered into it.
Baseline is that the number station will generate a dummy random message, optionally obscure it via steganography, then upload it to an (s)ftp/scp server. # Building
The random message generation actually happens last, so that, next time it is run, the existing message will be uploaded. This way a real message can be generated in the meantime and the next upload time the real message will be uploaded. Because the real message looks random to anyone without the corresponding one-time pad, it cannot be distinguished from the dummy messages. Building requires JDK 23.0.1 and javafx SDK 23.0.1, and Maven (I used version 3.9.9)
# Number Station Listener The JAVA_HOME environment variable must be set to the JDK installation directory e.g. `C:\Program Files\Java\jdk-23.0.1\bin\java.exe`. `$JAVA_HOME/bin` must be included in you PATH variable. PATH_TO_FX must be set to the JavaFX lib directory e.g. `$HOME/openjfx-23.0.1_windows-x64_bin-sdk/javafx-sdk-23.0.1/lib`.
This application periodically checks a site for numbers and stores them so the user can check whether the message matches any pads. To build, run `mvn package`, the output .jar should be in ./target
# Notes To run without installing: `java -jar target/numbersstation-1.0-SNAPSHOT-shaded.jar`
Should have sane defaults, but be built to be part of a more extensible system. Should be able to run other commands for message generation / upload. # Packaging
Need to be able to support multiple profiles for each. Packaging is done via jpackage, which is included with the JDK version used to build.
## Tech jpackage seems to pull in the entire working directory unnecessarily. To avoid bloat, packaging should be done in a clean subdirectory.
- Installers: JPackage ## Windows
- Notifications: https://github.com/sshtools/two-slices/blob/master/README.md
- Post to wordpress: https://github.com/ashri/java-wordpress-api/tree/master
## Security considerations ``` sh
> mkdir packaging
> cp target/numbersstation-1.0-SNAPSHOT-shaded.jar packaging
> cd packaging
> jpackage --input . --name "Numbers Station" --main-jar numbersstation-1.0-SNAPSHOT-shaded.jar --main-class name.nathanmcrae.numbersstation.MainRun --type exe --win-menu --win-per-user-install --win-shortcut --win-shortcut-prompt --win-upgrade-uuid 956c711b-01f1-46a2-9355-4a6b63ec1ec9 --icon "../icon.ico" --description "Tool to periodically upload encrypted messages to a website/blog"
```
If someone were to take any part of the number station (this software, the server, or intercepting on the way to the recipient), then they could make sure no messages get through (though they couldn't spoof messages without access to the one-time pads). ## Linux
This weakness is true of nearly all communication methods though. The only way to be certain a message was received is to receive an acknowledgement back. ``` sh
> mkdir packaging
# Settings/State Format > cp target/numbersstation-1.0-SNAPSHOT.jar packaging
> cd packaging
$XDG_CONFIG_HOME/numbers-station/main-settings.xml > jpackage --input . --name numbers-station --main-jar numbersstation-1.0-SNAPSHOT.jar --main-class name.nathanmcrae.numbersstation.MainRun --type deb --icon ../icon.ico --description "Tool to periodically upload encrypted messages to a website/blog"
$XDG_CONFIG_HOME/numbers-station/listener-settings.xml ```
$XDG_STATE_HOME/numbers-station/main.log
$XDG_STATE_HOME/numbers-station/listener.log
$XDG_STATE_HOME/numbers-station/<station-id>-main/next-message.txt
$XDG_STATE_HOME/numbers-station/<station-id>-main/20250114T091633.txt
$XDG_STATE_HOME/numbers-station/<station-id>-listener/20250114T091735.txt
$XDG_STATE_HOME/numbers-station/<station-id>-listener/20250114T091748-read.txt
XDG dirs:
https://superuser.com/a/1767882
On Windows:
https://stackoverflow.com/questions/43853548/xdg-basedir-directories-for-windows
Library to use:
https://github.com/dirs-dev/directories-jvm
# TODO # TODO
@ -67,15 +55,22 @@ https://github.com/dirs-dev/directories-jvm
- [ ] Add tests for generateMessage() - [ ] Add tests for generateMessage()
- [x] Load message from next-message - [x] Load message from next-message
- [x] When making changes to the message, show the changes as unsaved until saved. - [x] When making changes to the message, show the changes as unsaved until saved.
- [ ] Ensure all GUI elements have accessibility text - [x] Ensure all GUI elements have accessibility text
- [ ] Embed versioning info - [x] Embed versioning info
- [ ] Factor out scheduling into a wrapper class that handles all the os-specific stuff internally. - [ ] Factor out scheduling into a wrapper class that handles all the os-specific stuff internally.
- [ ] If a station name is run which doesn't exist in settings, but has associated files, then prompt the user to delete the files. - [ ] If a station name is run which doesn't exist in settings, but has associated files, then prompt the user to delete the files.
- [ ] Fix behavior when settings file is empty - [ ] Fix behavior when settings file is empty
- [ ] Store schedule time with time zone - [x] Store schedule time with time zone
- [ ] Add tests for StationSettings.nextSendTime() - [ ] Add tests for StationSettings.nextSendTime()
- [ ] When changing number of digits for a station, we may want to regenerate a new message of the specified length. We'll need to prompt the user to overwrite the existing message (in case it's a real message).
- [ ] Add a button to post message manually (With dialog warning)
- Add description in help quickstart for how to use this
- [ ] 'Next message will be sent' should reflect when external schedule management is turned on
- [x] Have a way to show executable location (since you need to know that to manage schedule externally)
- [ ] When launching help, re-navigate to help page
- [x] When can't load settings, give option to re-initialize (with confirmation)
Note on jars and javafx: https://stackoverflow.com/a/23121897 # Devlog
# 2025-01-27 JAR packaging # 2025-01-27 JAR packaging
@ -137,7 +132,7 @@ java.io.IOException: Command [C:\Program Files (x86)\WiX Toolset v3.11\bin\candl
Did a clean clone and build and it worked. Did a clean clone and build and it worked.
jpackage --input . --name numbers-station --main-jar .\target\numbersstation-1.0-SNAPSHOT.jar --main-class name.nathanmcrae.numbersstation.MainRun --type exe --win-menu --win-per-user-install --win-shortcut --win-shortcut-prompt --win-upgrade-uuid 956c711b-01f1-46a2-9355-4a6b63ec1ec9 --icon "P:\personal_root\projects\numbers-station\icon.ico" --description "Tool to periodically upload encrypted messages to a website/blog" jpackage --input . --name "Numbers Station" --main-jar .\target\numbersstation-1.0-SNAPSHOT.jar --main-class name.nathanmcrae.numbersstation.MainRun --type exe --win-menu --win-per-user-install --win-shortcut --win-shortcut-prompt --win-upgrade-uuid 956c711b-01f1-46a2-9355-4a6b63ec1ec9 --icon "P:\personal_root\projects\numbers-station\icon.ico" --description "Tool to periodically upload encrypted messages to a website/blog"
--- ---

View File

@ -25,6 +25,7 @@ import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonType;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TextArea; import javafx.scene.control.TextArea;
@ -183,9 +184,20 @@ public class MainController implements Initializable {
Result<MainSettings, Exception> result = MainSettings.load(); Result<MainSettings, Exception> result = MainSettings.load();
if (!result.hasSuccess()) { if (!result.hasSuccess()) {
Alert alert = new Alert(Alert.AlertType.ERROR, "Unable to load settings file. See log for details. Reinitialize settings? This may overwrite existing settings.", ButtonType.YES, ButtonType.NO); String logPath = Main.getStatePath().resolve("main.log").toString();
String message = "Unable to load settings file. See log file at " + logPath + " for details. Reinitialize settings? This may overwrite existing settings.";
Alert alert = new Alert(Alert.AlertType.ERROR, message, ButtonType.NO, ButtonType.YES);
alert.setTitle("Settings load error"); alert.setTitle("Settings load error");
alert.setHeaderText(null); alert.setHeaderText(null);
//Deactivate Defaultbehavior for yes-Button:
Button yesButton = (Button) alert.getDialogPane().lookupButton( ButtonType.YES );
yesButton.setDefaultButton( false );
//Activate Defaultbehavior for no-Button:
Button noButton = (Button) alert.getDialogPane().lookupButton( ButtonType.NO );
noButton.setDefaultButton( true );
Optional<ButtonType> promptResult = alert.showAndWait(); Optional<ButtonType> promptResult = alert.showAndWait();
if (!promptResult.isPresent()) { if (!promptResult.isPresent()) {

View File

@ -138,14 +138,12 @@ public class StationSelectionController {
while (change.next()) { while (change.next()) {
if (change.wasAdded()) { if (change.wasAdded()) {
for (StationSettings addedStation : change.getAddedSubList()) { for (StationSettings addedStation : change.getAddedSubList()) {
System.out.println("Added: " + addedStation.getName());
} }
} }
if (change.wasRemoved()) { if (change.wasRemoved()) {
for (StationSettings removedStation : change.getRemoved()) { for (StationSettings removedStation : change.getRemoved()) {
System.out.println("Removed: " + removedStation.getName());
try { try {
StationSettings.deleteStationData(removedStation.getName());
removedStation.deleteDir(); removedStation.deleteDir();
} catch (IOException e) { } catch (IOException e) {
logger.log(Level.SEVERE, "Exception when removing station directory", e); logger.log(Level.SEVERE, "Exception when removing station directory", e);
@ -170,7 +168,6 @@ public class StationSelectionController {
if (selectedStation != null) { if (selectedStation != null) {
stage.setUserData(selectedStation.getName()); stage.setUserData(selectedStation.getName());
System.out.println("Selected Station: " + selectedStation.getName());
} }
stage.close(); stage.close();
} }

View File

@ -112,6 +112,10 @@ public class StationSettings {
} }
public void deleteDir() throws IOException { public void deleteDir() throws IOException {
if (!Files.exists(stationPath())) {
return;
}
Files.walkFileTree(stationPath(), new SimpleFileVisitor<Path>() { Files.walkFileTree(stationPath(), new SimpleFileVisitor<Path>() {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

View File

@ -533,16 +533,6 @@ public class StationSettingsController {
} }
} }
@FXML
private void handleTestRegisterScheduleButtonPress() {
WindowsScheduler.registerSchedule(settings);
}
@FXML
private void handleTestRunScheduleButtonPress() {
WindowsScheduler.runSchedule(settings);
}
public StringProperty stationAddressProperty() { public StringProperty stationAddressProperty() {
return stationAddress; return stationAddress;
} }
@ -565,7 +555,6 @@ public class StationSettingsController {
messagePeriod.set(settings.getMessagePeriod()); messagePeriod.set(settings.getMessagePeriod());
username.set(settings.getUsername()); username.set(settings.getUsername());
password.set(settings.getPassword()); password.set(settings.getPassword());
System.out.println(settings.getPrefixes());
prefixListView.getItems().addAll(settings.getPrefixes()); prefixListView.getItems().addAll(settings.getPrefixes());
scheduleStartDate.set(settings.getScheduleStart().withZoneSameInstant(ZoneId.systemDefault()).toLocalDate()); scheduleStartDate.set(settings.getScheduleStart().withZoneSameInstant(ZoneId.systemDefault()).toLocalDate());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");

View File

@ -15,7 +15,7 @@
<Label layoutX="20.0" layoutY="50.0" text="Homepage:" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="50.0" /> <Label layoutX="20.0" layoutY="50.0" text="Homepage:" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="50.0" />
<TextField fx:id="homepageURLTextField" accessibleText="Homepage URL field" editable="false" layoutX="89.0" layoutY="46.0" prefHeight="25.0" prefWidth="501.0" text="asefa" AnchorPane.leftAnchor="89.0" AnchorPane.rightAnchor="10.0" /> <TextField fx:id="homepageURLTextField" accessibleText="Homepage URL field" editable="false" layoutX="89.0" layoutY="46.0" prefHeight="25.0" prefWidth="501.0" text="asefa" AnchorPane.leftAnchor="89.0" AnchorPane.rightAnchor="10.0" />
<Label layoutX="20.0" layoutY="80.0" text="Version:" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="80.0" /> <Label layoutX="20.0" layoutY="80.0" text="Version:" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="80.0" />
<TextField fx:id="versionTextField" accessibleText="Application version field" editable="false" layoutX="72.0" layoutY="76.0" text="asefa" /> <TextField fx:id="versionTextField" accessibleText="Application version field" editable="false" layoutX="72.0" layoutY="76.0" text="asefa" AnchorPane.leftAnchor="89.0" AnchorPane.rightAnchor="10.0" />
<Label layoutX="20.0" layoutY="110.0" text="Application executable: " AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="110.0" /> <Label layoutX="20.0" layoutY="110.0" text="Application executable: " AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="110.0" />
<TextField fx:id="executableTextField" accessibleText="Application executable path field" editable="false" layoutX="153.0" layoutY="106.0" prefHeight="25.0" prefWidth="403.0" text="sflejk" AnchorPane.leftAnchor="153.0" AnchorPane.rightAnchor="10.0" /> <TextField fx:id="executableTextField" accessibleText="Application executable path field" editable="false" layoutX="153.0" layoutY="106.0" prefHeight="25.0" prefWidth="403.0" text="sflejk" AnchorPane.leftAnchor="153.0" AnchorPane.rightAnchor="10.0" />
<Label layoutX="20.0" layoutY="140.0" text="Config directory: " AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="140.0" /> <Label layoutX="20.0" layoutY="140.0" text="Config directory: " AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="140.0" />

View File

@ -3,4 +3,4 @@
<?import javafx.scene.web.WebView?> <?import javafx.scene.web.WebView?>
<WebView fx:id="webView" prefHeight="391.0" prefWidth="440.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="name.nathanmcrae.numbersstation.HelpController" /> <WebView fx:id="webView" prefHeight="597.0" prefWidth="652.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="name.nathanmcrae.numbersstation.HelpController" />

View File

@ -147,9 +147,6 @@
<Label layoutX="115.0" layoutY="8.0" text="Starting from:" /> <Label layoutX="115.0" layoutY="8.0" text="Starting from:" />
<DatePicker fx:id="scheduleStartDatePicker" accessibleText="Message schedule start date" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="115.0" layoutY="34.0" /> <DatePicker fx:id="scheduleStartDatePicker" accessibleText="Message schedule start date" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="115.0" layoutY="34.0" />
<TextField fx:id="scheduleStartTimeField" accessibleText="Message schedule start time" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="115.0" layoutY="64.0" text="23:24:49" /> <TextField fx:id="scheduleStartTimeField" accessibleText="Message schedule start time" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="115.0" layoutY="64.0" text="23:24:49" />
<Label layoutX="48.0" layoutY="102.0" text="TODO: Jitter" />
<Button layoutX="377.0" layoutY="73.0" mnemonicParsing="false" onAction="#handleTestRegisterScheduleButtonPress" text="Test register scheduled task" />
<Button layoutX="377.0" layoutY="107.0" mnemonicParsing="false" onAction="#handleTestRunScheduleButtonPress" text="Test running scheduled task" />
</children> </children>
</AnchorPane> </AnchorPane>

View File

@ -108,7 +108,6 @@
<li> <li>
Set other options (See application reference for details) Set other options (See application reference for details)
</li> </li>
<li>TODO: test post</li>
</ol> </ol>
<h3> <h3>