diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index b7fa3a8..d140ebd 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -7,7 +7,7 @@ on:
pull_request:
jobs:
- build-on-linux-with-jdk-17:
+ build-on-linux-with-jdk-21:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'zulu'
- java-version: '17'
+ java-version: '21'
cache: 'maven'
- name: Build and test
diff --git a/README.MD b/README.MD
index 3ceaee2..7aa29de 100644
--- a/README.MD
+++ b/README.MD
@@ -4,7 +4,7 @@
A repository containing different java tutorials
**Minimum requirements:**
-1. Java 17
+1. Java 21
2. Maven 3.5.0
3. Eclipse, Intellij IDEA (or any other text editor like VIM)
4. A terminal
@@ -36,6 +36,7 @@ A repository containing different java tutorials
- [Spring Boot Reactive Server with Common Name Validation based on Spring Security](spring-security-cn-validation-for-reactive-server)
- [Spring Boot Server with Common Name Validation based on AOP with AspectJ Weaver](spring-cn-validation-with-aop)
- [Bypassing and overruling SSL configuration of libraries](bypassing-overruling-ssl-configuration)
+- [Prompting to trust an unknown certificate in a GUI and reloading the ssl configuration](trust-me)
## Serialization & Deserialization ☢️
- [Two-way object serialization while using one model with Jackson and Spring Boot](two-way-object-serialization)
diff --git a/mock-statics-with-mockito/pom.xml b/mock-statics-with-mockito/pom.xml
index 57501e4..c8759e6 100644
--- a/mock-statics-with-mockito/pom.xml
+++ b/mock-statics-with-mockito/pom.xml
@@ -43,6 +43,12 @@
${version.assertj-core}
test
+
+ net.bytebuddy
+ byte-buddy
+ ${version.byte-buddy}
+ test
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 6a1f1bb..8905f8b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,6 +24,7 @@
instant-server-ssl-reloading-with-quarkus
instant-ssl-reloading-with-spring-tomcat
bypassing-overruling-ssl-configuration
+ trust-me
@@ -47,7 +48,7 @@
- 11
+ 21
3.0.0-M7
3.0.0-M7
3.10.1
@@ -62,6 +63,7 @@
8.3.6
2.9.3
3.3.2
+ 21.0.3
10.1.26
1.9.22.1
2.17.2
@@ -86,8 +88,9 @@
3.23.1
5.10.3
1.10.3
- 5.12.0
+ 5.13.0
5.2.0
+ 1.15.1
1.0.3
42.5.0
1.17.3
diff --git a/trust-me/README.md b/trust-me/README.md
new file mode 100644
index 0000000..7d099a2
--- /dev/null
+++ b/trust-me/README.md
@@ -0,0 +1,32 @@
+# Trust Me 🔐
+A proof-of-concept GUI for prompting an user when a certificate is not trusted yet. The ssl configuration will be reloaded during runtime.
+
+This GUI app demonstrates the feature of [Trusting additional new certificates at runtime](https://github.com/Hakky54/sslcontext-kickstart?tab=readme-ov-file#trust-additional-new-certificates-at-runtime) from the library [sslcontext-kickstart](https://github.com/Hakky54/sslcontext-kickstart)
+It might occur that your truststore has outdated certificates and is not easy to maintain or it just calls servers which has recently updated their certificates.
+This option demonstrates how to integrate it in your GUI app, and it will prompt when the certificate is not trusted yet, which gives the option to the end-user to either trust or reject it.
+
+## Demo
+![alt text](https://github.com/Hakky54/java-tutorials/blob/main/trust-me/images/demo.gif?raw=true)
+
+## Running locally
+
+### Minimum requirements
+- JDK 21
+- Maven
+- Terminal
+
+Although this project requires JDK 21, the [library](https://github.com/Hakky54/sslcontext-kickstart) itself is compatible with JDK 8 and therefor will work with that version.
+
+Run the following commands in your terminal:
+
+```bash
+mvn clean package
+mvn spring-boot:run
+```
+
+## Contributing
+
+There are plenty of ways to contribute to this project:
+
+* Give it a star
+* Submit a PR
diff --git a/trust-me/images/demo.gif b/trust-me/images/demo.gif
new file mode 100644
index 0000000..4645dcb
Binary files /dev/null and b/trust-me/images/demo.gif differ
diff --git a/trust-me/pom.xml b/trust-me/pom.xml
new file mode 100644
index 0000000..a89df29
--- /dev/null
+++ b/trust-me/pom.xml
@@ -0,0 +1,107 @@
+
+
+ 4.0.0
+
+ io.github.hakky54
+ java-tutorials
+ 1.0.0-SNAPSHOT
+
+
+ trust-me
+
+
+
+ io.github.hakky54
+ sslcontext-kickstart
+ ${version.sslcontext-kickstart}
+
+
+
+ org.openjfx
+ javafx-base
+ ${version.javafx}
+
+
+ org.openjfx
+ javafx-fxml
+ ${version.javafx}
+
+
+ org.openjfx
+ javafx-controls
+ ${version.javafx}
+
+
+ org.openjfx
+ javafx-graphics
+ ${version.javafx}
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ ${version.spring}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${version.maven-compiler-plugin}
+
+ ${version.java}
+ ${version.java}
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${version.exec-maven-plugin}
+
+
+
+ java
+
+
+
+
+ nl.altindag.ssl.trustme.App
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${version.spring}
+
+ trust-me
+ nl.altindag.ssl.trustme.App
+
+
+
+
+ repackage
+
+
+
+
+
+
+
+
+
+ src/main/resources
+
+ mainscreen.fxml
+ banner.txt
+
+
+
+
+
+
\ No newline at end of file
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/App.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/App.java
new file mode 100644
index 0000000..9dae1c5
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/App.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme;
+
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.ConfigurableApplicationContext;
+
+import java.io.IOException;
+import java.util.function.Function;
+
+@SpringBootApplication
+public class App extends Application {
+
+ private static final String TITLE = "Trust Me";
+ private ConfigurableApplicationContext applicationContext;
+ private final Function fxmlLoaderFunction = fxml -> new FXMLLoader(this.getClass().getResource(fxml));
+
+ private Parent root;
+
+ @Override
+ public void init() throws IOException {
+ applicationContext = new SpringApplicationBuilder(App.class)
+ .headless(false)
+ .run(getParameters().getRaw().toArray(String[]::new));
+
+ FXMLLoader fxmlLoader = fxmlLoaderFunction.apply("/mainscreen.fxml");
+ fxmlLoader.setControllerFactory(applicationContext::getBean);
+ root = fxmlLoader.load();
+ }
+
+ @Override
+ public void start(Stage stage) {
+ Scene scene = new Scene(root);
+ stage.setTitle(TITLE);
+ stage.setScene(scene);
+ stage.setWidth(500);
+ stage.setHeight(400);
+ stage.setResizable(false);
+
+ stage.show();
+ }
+
+ @Override
+ public void stop() {
+ Platform.exit();
+ applicationContext.stop();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+}
\ No newline at end of file
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/AppStarter.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/AppStarter.java
new file mode 100644
index 0000000..f10a178
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/AppStarter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme;
+
+public class AppStarter {
+
+ public static void main(String[] args) {
+ App.main(args);
+ }
+
+}
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/config/ClientConfig.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/config/ClientConfig.java
new file mode 100644
index 0000000..2c9bada
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/config/ClientConfig.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme.config;
+
+import nl.altindag.ssl.SSLFactory;
+import nl.altindag.ssl.trustme.service.TrustMeService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import javax.net.ssl.X509ExtendedTrustManager;
+import java.net.http.HttpClient;
+import java.nio.file.Path;
+
+@Configuration
+public class ClientConfig {
+
+ private static final Path TRUSTSTORE_PATH = Path.of(System.getProperty("user.dir"), "truststore.jks");
+ private static final char[] TRUSTSTORE_PASSWORD = "changeit".toCharArray();
+ private static final String TRUSTSTORE_TYPE = "PKCS12";
+
+ @Bean
+ public HttpClient httpClient(SSLFactory sslFactory) {
+ return HttpClient.newBuilder()
+ .sslContext(sslFactory.getSslContext())
+ .sslParameters(sslFactory.getSslParameters())
+ .build();
+ }
+
+ @Bean
+ public SSLFactory sslFactory(@Lazy TrustMeService trustMeService) {
+ return SSLFactory.builder()
+ .withInflatableTrustMaterial(TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, trustMeService::verify)
+ .build();
+ }
+
+ @Bean
+ public X509ExtendedTrustManager trustManager(SSLFactory sslFactory) {
+ return sslFactory.getTrustManager().orElseThrow();
+ }
+
+}
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/controller/SearchController.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/controller/SearchController.java
new file mode 100644
index 0000000..9630621
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/controller/SearchController.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme.controller;
+
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.TextArea;
+import javafx.scene.control.TextField;
+import nl.altindag.ssl.trustme.exception.PingException;
+import nl.altindag.ssl.trustme.service.PingService;
+import nl.altindag.ssl.trustme.util.Logger;
+import org.springframework.stereotype.Controller;
+
+import javax.net.ssl.SSLHandshakeException;
+import java.net.URL;
+import java.util.ResourceBundle;
+
+import static javafx.geometry.Pos.CENTER;
+
+@Controller
+public class SearchController implements Initializable {
+
+ @FXML
+ private TextField urlField;
+ @FXML
+ private TextArea loggerArea;
+
+ private final PingService pingService;
+
+ public SearchController(PingService pingService) {
+ this.pingService = pingService;
+ }
+
+ @FXML
+ public void initialize() {
+ urlField.setAlignment(CENTER);
+
+ urlField.textProperty().addListener((observableValue, oldValue, newValue) -> {
+ if (newValue.contains(" ")) {
+ urlField.setText(oldValue);
+ }
+ });
+ }
+
+ @Override
+ public void initialize(URL url, ResourceBundle resourceBundle) {
+ loggerArea.textProperty().bind(Logger.logContainerProperty());
+ Logger.logContainerProperty().addListener((observable, newValue, oldValue) -> {
+ loggerArea.selectPositionCaret(loggerArea.getLength());
+ loggerArea.deselect();
+ });
+ }
+
+ @FXML
+ public void onEnter(ActionEvent event) {
+ String url = urlField.getText().toLowerCase();
+ try {
+ pingService.ping(url);
+ Logger.log(String.format("Certificate of [%s] is already trusted", url));
+ } catch (PingException pingException) {
+ if (pingException.getCause() instanceof SSLHandshakeException) {
+ Logger.log(String.format("Certificate of [%s] is not trusted", url));
+ }
+ }
+ }
+
+}
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/exception/PingException.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/exception/PingException.java
new file mode 100644
index 0000000..ce952f8
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/exception/PingException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme.exception;
+
+public class PingException extends TrustMeException {
+
+ public PingException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/exception/TrustMeException.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/exception/TrustMeException.java
new file mode 100644
index 0000000..e11adb1
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/exception/TrustMeException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme.exception;
+
+public class TrustMeException extends RuntimeException {
+
+ public TrustMeException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/service/PingService.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/service/PingService.java
new file mode 100644
index 0000000..25747c8
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/service/PingService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme.service;
+
+import nl.altindag.ssl.trustme.exception.PingException;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+
+@Service
+public class PingService {
+
+ private final HttpClient httpClient;
+
+ public PingService(HttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public void ping(String url) {
+ HttpRequest request = HttpRequest.newBuilder()
+ .GET()
+ .timeout(Duration.ofMinutes(1))
+ .uri(URI.create(url))
+ .build();
+
+ try {
+ httpClient.send(request, HttpResponse.BodyHandlers.discarding());
+ } catch (IOException | InterruptedException e) {
+ throw new PingException(e);
+ }
+ }
+
+}
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/service/TrustMeService.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/service/TrustMeService.java
new file mode 100644
index 0000000..235fd6d
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/service/TrustMeService.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme.service;
+
+import javafx.application.Platform;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import nl.altindag.ssl.model.TrustManagerParameters;
+import nl.altindag.ssl.trustme.util.Logger;
+import nl.altindag.ssl.util.TrustManagerUtils;
+import org.springframework.stereotype.Service;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedTrustManager;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+@Service
+public class TrustMeService {
+
+ private static final Function hostnameExtractor = trustManagerParameters -> trustManagerParameters
+ .getSslEngine().map(SSLEngine::getPeerHost)
+ .or(() -> trustManagerParameters.getSocket().map(socket -> socket.getInetAddress().getHostName()))
+ .orElseThrow();
+
+ private final Map hostnameToShouldBeTrusted = new ConcurrentHashMap<>();
+
+ private final X509ExtendedTrustManager trustManager;
+
+ public TrustMeService(X509ExtendedTrustManager trustManager) {
+ this.trustManager = trustManager;
+ }
+
+ public boolean verify(TrustManagerParameters trustManagerParameters) {
+ String hostname = hostnameExtractor.apply(trustManagerParameters);
+
+ Boolean isTrusted = hostnameToShouldBeTrusted.get(hostname);
+ if (isTrusted != null) {
+ return isTrusted;
+ }
+
+ X509Certificate certificate = trustManagerParameters.getChain()[0];
+ Platform.runLater(() -> askUserToTrustServerCertificate(hostname, certificate));
+ return hostnameToShouldBeTrusted.getOrDefault(hostname, false);
+ }
+
+ private void askUserToTrustServerCertificate(String hostname, X509Certificate certificate) {
+ Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Do you want to trust the certificate of " + hostname, ButtonType.YES, ButtonType.NO);
+ alert.setTitle("Trust confirmation");
+
+ Optional buttonType = alert.showAndWait();
+ boolean shouldBeTrusted = buttonType.filter(type -> type == ButtonType.YES).isPresent();
+ hostnameToShouldBeTrusted.put(hostname, shouldBeTrusted);
+
+ if (shouldBeTrusted) {
+ Logger.log("User trusted server. Adding the certificate to the list of trusted certificates....");
+ TrustManagerUtils.addCertificate(trustManager, certificate);
+ Logger.log("Server is now trusted");
+ } else {
+ Logger.log("User decided not to trust the server. Not prompting anymore");
+ }
+ }
+
+}
diff --git a/trust-me/src/main/java/nl/altindag/ssl/trustme/util/Logger.java b/trust-me/src/main/java/nl/altindag/ssl/trustme/util/Logger.java
new file mode 100644
index 0000000..a86999d
--- /dev/null
+++ b/trust-me/src/main/java/nl/altindag/ssl/trustme/util/Logger.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 Thunderberry.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nl.altindag.ssl.trustme.util;
+
+import javafx.application.Platform;
+import javafx.beans.property.SimpleStringProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Logger {
+
+ private static final String LINE_BREAK = "\n";
+
+ private Logger() {}
+
+ private static List logMessages = new ArrayList<>();
+ private static SimpleStringProperty logContainer = new SimpleStringProperty("");
+
+ public static synchronized void log(String logMessage) {
+ Platform.runLater(() -> {
+ if (!logMessages.isEmpty() && logMessages.get(logMessages.size() - 1).contains(logMessage)) {
+ return;
+ }
+
+ logMessages.add(logMessage);
+ logContainer.setValue(logContainer.getValue() + logMessage + LINE_BREAK);
+ });
+ }
+
+ public static SimpleStringProperty logContainerProperty() {
+ return logContainer;
+ }
+
+}
diff --git a/trust-me/src/main/resources/banner.txt b/trust-me/src/main/resources/banner.txt
new file mode 100644
index 0000000..03619d2
--- /dev/null
+++ b/trust-me/src/main/resources/banner.txt
@@ -0,0 +1,12 @@
+# ________ __ __ __
+# | | \ | \ | \
+# \$$$$$$$| $$____ __ __ _______ ____| $$ ______ ______ | $$____ ______ ______ ______ __ __
+# | $$ | $$ \| \ | | \ / $$/ \ / \| $$ \ / \ / \ / \| \ | \
+# | $$ | $$$$$$$| $$ | $| $$$$$$$| $$$$$$| $$$$$$| $$$$$$| $$$$$$$| $$$$$$| $$$$$$| $$$$$$| $$ | $$
+# | $$ | $$ | $| $$ | $| $$ | $| $$ | $| $$ $| $$ \$| $$ | $| $$ $| $$ \$| $$ \$| $$ | $$
+# | $$ | $$ | $| $$__/ $| $$ | $| $$__| $| $$$$$$$| $$ | $$__/ $| $$$$$$$| $$ | $$ | $$__/ $$
+# | $$ | $$ | $$\$$ $| $$ | $$\$$ $$\$$ | $$ | $$ $$\$$ | $$ | $$ \$$ $$
+# \$$ \$$ \$$ \$$$$$$ \$$ \$$ \$$$$$$$ \$$$$$$$\$$ \$$$$$$$ \$$$$$$$\$$ \$$ _\$$$$$$$
+# | \__| $$
+# \$$ $$
+# http://thunderberry.nl/ \$$$$$$
\ No newline at end of file
diff --git a/trust-me/src/main/resources/mainscreen.fxml b/trust-me/src/main/resources/mainscreen.fxml
new file mode 100644
index 0000000..e649e4d
--- /dev/null
+++ b/trust-me/src/main/resources/mainscreen.fxml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+