From 599e1554905f3278a93921857b5bb12bd014392b Mon Sep 17 00:00:00 2001 From: Billy Brawner Date: Wed, 18 Apr 2018 20:56:37 -0500 Subject: [PATCH] Implement CrackerFactory The idea with this was to get away from the singleton pattern, since it makes tests difficult and fragile. Dependency injection would have been an option too, but for simplicity's sake, I chose the factory method. There were also some strange errors on Windows that I've corrected, so the program should run on Windows now as well. --- gradle/wrapper/gradle-wrapper.properties | 3 +- .../java/com/wbrawner/keecrack/cli/Main.java | 3 +- .../keecrack/gui/CrackingController.java | 5 +-- .../wbrawner/keecrack/gui/MainController.java | 19 +++++----- .../src/main/resources/fxml/main.fxml | 6 +--- .../wbrawner/keecrack/lib/CrackerFactory.java | 36 +++++++++++++++++++ .../com/wbrawner/keecrack/lib/KeeCrack.java | 32 ++++------------- .../wbrawner/keecrack/lib/KeeCrackTest.java | 22 +----------- 8 files changed, 63 insertions(+), 63 deletions(-) create mode 100644 keecrack-lib/src/main/java/com/wbrawner/keecrack/lib/CrackerFactory.java diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 33ec7bf..feb5e19 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,5 @@ -#Sun Apr 15 14:52:35 CDT 2018 +# suppress inspection "UnusedProperty" for whole file +#Wed Apr 18 08:19:13 CDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/keecrack-cli/src/main/java/com/wbrawner/keecrack/cli/Main.java b/keecrack-cli/src/main/java/com/wbrawner/keecrack/cli/Main.java index 8c7e307..aa7803a 100644 --- a/keecrack-cli/src/main/java/com/wbrawner/keecrack/cli/Main.java +++ b/keecrack-cli/src/main/java/com/wbrawner/keecrack/cli/Main.java @@ -16,6 +16,7 @@ package com.wbrawner.keecrack.cli; import com.wbrawner.keecrack.lib.Code; +import com.wbrawner.keecrack.lib.CrackerFactory; import com.wbrawner.keecrack.lib.KeeCrack; import com.wbrawner.keecrack.lib.view.CrackingView; import net.sourceforge.argparse4j.ArgumentParsers; @@ -71,7 +72,7 @@ public class Main { Namespace res = parser.parseArgs(args); isVerbose = res.getBoolean("verbose"); isIncremental = res.getBoolean("incremental"); - KeeCrack keeCrack = KeeCrack.getInstance(); + KeeCrack keeCrack = new CrackerFactory().getCracker(true); keeCrack.setCrackingView(new CLICrackingView()); String databasePath = res.getString("database"); diff --git a/keecrack-gui/src/main/java/com/wbrawner/keecrack/gui/CrackingController.java b/keecrack-gui/src/main/java/com/wbrawner/keecrack/gui/CrackingController.java index 47dd4af..28fdf7b 100644 --- a/keecrack-gui/src/main/java/com/wbrawner/keecrack/gui/CrackingController.java +++ b/keecrack-gui/src/main/java/com/wbrawner/keecrack/gui/CrackingController.java @@ -16,6 +16,7 @@ package com.wbrawner.keecrack.gui; import com.wbrawner.keecrack.lib.Code; +import com.wbrawner.keecrack.lib.CrackerFactory; import com.wbrawner.keecrack.lib.KeeCrack; import com.wbrawner.keecrack.lib.view.CrackingView; import javafx.application.Platform; @@ -41,10 +42,11 @@ public class CrackingController implements Initializable, CrackingView { private Label timeElapsed; private Stage stage; + private KeeCrack keeCrack; @Override public void initialize(URL location, ResourceBundle resources) { - final KeeCrack keeCrack = KeeCrack.getInstance(); + keeCrack = new CrackerFactory().getCracker(true); keeCrack.setCrackingView(this); new Thread(keeCrack::attack).start(); } @@ -88,7 +90,6 @@ public class CrackingController implements Initializable, CrackingView { } private void onClose() { - KeeCrack keeCrack = KeeCrack.getInstance(); keeCrack.abort(); keeCrack.setCrackingView(null); } diff --git a/keecrack-gui/src/main/java/com/wbrawner/keecrack/gui/MainController.java b/keecrack-gui/src/main/java/com/wbrawner/keecrack/gui/MainController.java index 93404f0..bb8a207 100644 --- a/keecrack-gui/src/main/java/com/wbrawner/keecrack/gui/MainController.java +++ b/keecrack-gui/src/main/java/com/wbrawner/keecrack/gui/MainController.java @@ -16,6 +16,7 @@ package com.wbrawner.keecrack.gui; import com.wbrawner.keecrack.lib.Code; +import com.wbrawner.keecrack.lib.CrackerFactory; import com.wbrawner.keecrack.lib.KeeCrack; import com.wbrawner.keecrack.lib.view.FormView; import javafx.fxml.FXML; @@ -48,7 +49,6 @@ public class MainController implements Initializable, FormView { private TextField wordlist; @FXML private Button crackButton; - @FXML private ToggleGroup wordlistType; @FXML private RadioButton wordlistFile; @@ -57,10 +57,10 @@ public class MainController implements Initializable, FormView { @Override public void initialize(URL location, ResourceBundle resources) { - keeCrack = KeeCrack.getInstance(); + keeCrack = new CrackerFactory().getCracker(true); keeCrack.setFormView(this); database.setOnMouseClicked(event -> { - if (KeeCrack.getInstance().isCracking()) { + if (keeCrack.isCracking()) { return; } @@ -69,7 +69,7 @@ public class MainController implements Initializable, FormView { }); key.setOnMouseClicked(event -> { - if (KeeCrack.getInstance().isCracking()) { + if (keeCrack.isCracking()) { return; } @@ -81,7 +81,7 @@ public class MainController implements Initializable, FormView { crackButton.setOnMouseClicked(event -> { try { - if (KeeCrack.getInstance().isCracking()) { + if (keeCrack.isCracking()) { return; } Stage stage = new Stage(); @@ -107,13 +107,16 @@ public class MainController implements Initializable, FormView { e.printStackTrace(); } }); + if (wordlistType == null) + wordlistType = new ToggleGroup(); + wordlistFile.setToggleGroup(wordlistType); + wordlistPattern.setToggleGroup(wordlistType); wordlistType.selectedToggleProperty().addListener((observableValue, oldToggle, newToggle) -> updateWordListHandler()); if (keeCrack.getDatabaseFile() != null) onDatabaseFileSet(keeCrack.getDatabaseFile().getName()); if (keeCrack.getKeyFile() != null) - onKeyFileSet(keeCrack.getKeyFile().getName()); if (keeCrack.getWordListName() != null) onWordListSet(keeCrack.getWordListName()); } @@ -127,7 +130,7 @@ public class MainController implements Initializable, FormView { new FileChooser.ExtensionFilter(description, extensions) ); Stage stage = new Stage(); - stage.setOnHidden(e -> KeeCrack.getInstance().setFormView(null)); + stage.setOnHidden(e -> keeCrack.setFormView(null)); return fileChooser.showOpenDialog(stage); } @@ -137,7 +140,7 @@ public class MainController implements Initializable, FormView { wordlist.setEditable(false); wordlist.setCursor(Cursor.HAND); wordlist.setOnMouseClicked(event -> { - if (KeeCrack.getInstance().isCracking()) { + if (keeCrack.isCracking()) { return; } File wordlistFile = getFile("Text Files", "txt"); diff --git a/keecrack-gui/src/main/resources/fxml/main.fxml b/keecrack-gui/src/main/resources/fxml/main.fxml index e8b4347..41cff57 100644 --- a/keecrack-gui/src/main/resources/fxml/main.fxml +++ b/keecrack-gui/src/main/resources/fxml/main.fxml @@ -69,12 +69,8 @@ - - - - + diff --git a/keecrack-lib/src/main/java/com/wbrawner/keecrack/lib/CrackerFactory.java b/keecrack-lib/src/main/java/com/wbrawner/keecrack/lib/CrackerFactory.java new file mode 100644 index 0000000..cccdc8c --- /dev/null +++ b/keecrack-lib/src/main/java/com/wbrawner/keecrack/lib/CrackerFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 William Brawner + * + * 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 + * + * http://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 com.wbrawner.keecrack.lib; + +/** + * Class responsible for acquiring a {@link KeeCrack} instance + */ +public class CrackerFactory { + private static final Object singletonLock = new Object(); + private static KeeCrack singleton; + + public KeeCrack getCracker(boolean asSingleton) { + if (asSingleton) { + synchronized (singletonLock) { + if (singleton == null) { + singleton = new KeeCrack(); + } + return singleton; + } + } + return new KeeCrack(); + } +} diff --git a/keecrack-lib/src/main/java/com/wbrawner/keecrack/lib/KeeCrack.java b/keecrack-lib/src/main/java/com/wbrawner/keecrack/lib/KeeCrack.java index aca7177..305c00b 100644 --- a/keecrack-lib/src/main/java/com/wbrawner/keecrack/lib/KeeCrack.java +++ b/keecrack-lib/src/main/java/com/wbrawner/keecrack/lib/KeeCrack.java @@ -31,11 +31,11 @@ import java.util.concurrent.atomic.AtomicReference; /** * The main class responsible for handling the brute forcing of the KeePass database. You do not contruct the - * KeeCrack instance directly, but rather call {@link #getInstance()}. To begin, you should set the form view and - * cracking view with {@link #setFormView(FormView)} and {@link #setCrackingView(CrackingView)} respectively. These - * views will be responsible for displaying information like error messages and status updates. The cracking will - * work without these, though it's highly recommended to set them prior to beginning. The database and wordlist must - * be set, while the key file is also optional. If either of the required parameters are missing, the + * KeeCrack instance directly, but rather call {@link CrackerFactory#getCracker(boolean)}. To begin, you should set the + * form view and cracking view with {@link #setFormView(FormView)} and {@link #setCrackingView(CrackingView)} + * respectively. These views will be responsible for displaying information like error messages and status updates. + * The cracking will work without these, though it's highly recommended to set them prior to beginning. The database + * and wordlist must be set, while the key file is also optional. If either of the required parameters are missing, the * {@link #attack()} operation will abort, sending either {@link Code#ERROR_INVALID_DATABASE_FILE} or * {@link Code#ERROR_INVALID_WORD_LIST}, respectively. The word list can either be a pattern, in which case * incremental guessing will take place, or a file, in which case each line of the file will be considered a password @@ -49,7 +49,6 @@ import java.util.concurrent.atomic.AtomicReference; * first parameter will be null. */ public class KeeCrack { - private static final AtomicReference singleton = new AtomicReference<>(null); private final Object keyFileLock = new Object(); private final AtomicBoolean isCracking = new AtomicBoolean(false); /** @@ -65,27 +64,10 @@ public class KeeCrack { private WordList wordList; private int guessCount = 0; - private KeeCrack() { - } - - public static KeeCrack getInstance() { - if (singleton.get() == null) { - singleton.set(new KeeCrack()); - } - - return singleton.get(); - } - /** - * Call this to reset the state of the KeeCrack instance. Note that you will need to set the views again after - * calling this + * To get an instance of the class, use the {@link CrackerFactory} */ - public void reset() { - setDatabaseFile(null); - setKeyFile(null); - setWordListFile(null); - setCrackingView(null); - setFormView(null); + KeeCrack() { } public void abort() { diff --git a/keecrack-lib/src/test/java/com/wbrawner/keecrack/lib/KeeCrackTest.java b/keecrack-lib/src/test/java/com/wbrawner/keecrack/lib/KeeCrackTest.java index e131add..bfc5e35 100644 --- a/keecrack-lib/src/test/java/com/wbrawner/keecrack/lib/KeeCrackTest.java +++ b/keecrack-lib/src/test/java/com/wbrawner/keecrack/lib/KeeCrackTest.java @@ -21,13 +21,9 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.File; import java.io.IOException; import java.time.Duration; -import static junit.framework.TestCase.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -41,30 +37,14 @@ public class KeeCrackTest { public void setUp() { mockCrackingView = mock(CrackingView.class); mockFormView = mock(FormView.class); - keeCrack = KeeCrack.getInstance(); + keeCrack = new CrackerFactory().getCracker(false); } @After public void tearDown() { - keeCrack.reset(); Utils.rmdir(Utils.getTmpDir()); } - @Test - public void resetTest() { - keeCrack.setDatabaseFile(new File("Database")); - keeCrack.setKeyFile(new File("Keyfile")); - keeCrack.setWordListPattern("Some pattern"); - assertNotNull(keeCrack.getDatabaseFile()); - assertNotNull(keeCrack.getKeyFile()); - assertNotNull(keeCrack.getWordList()); - keeCrack.reset(); - assertNull(keeCrack.getDatabaseFile()); - assertNull(keeCrack.getKeyFile()); - assertNull(keeCrack.getWordList()); - assertFalse(keeCrack.isCracking()); - } - @Test public void abortTest() throws IOException { keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));