Added first prototyp for recovery key generation

This commit is contained in:
Sebastian Stenzel
2019-09-27 21:43:42 +02:00
parent ccefb3613e
commit 1930090044
15 changed files with 291 additions and 15 deletions

View File

@@ -24,8 +24,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- dependency versions -->
<cryptomator.cryptolib.version>1.2.2</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>1.9.0-beta1</cryptomator.cryptofs.version>
<cryptomator.cryptofs.version>1.9.0-beta2</cryptomator.cryptofs.version>
<cryptomator.jni.version>2.2.1</cryptomator.jni.version>
<cryptomator.fuse.version>1.2.0</cryptomator.fuse.version>
<cryptomator.dokany.version>1.1.11</cryptomator.dokany.version>
@@ -82,11 +81,6 @@
</dependency>
<!-- Cryptomator Libs -->
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptolib</artifactId>
<version>${cryptomator.cryptolib.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>

View File

@@ -22,10 +22,6 @@
<groupId>org.cryptomator</groupId>
<artifactId>jni</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptolib</artifactId>
</dependency>
<!-- JavaFx -->
<dependency>

View File

@@ -15,6 +15,7 @@ public enum FxmlFile {
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
PREFERENCES("/fxml/preferences.fxml"), //
QUIT("/fxml/quit.fxml"), //
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
UNLOCK("/fxml/unlock.fxml"),
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //

View File

@@ -76,6 +76,10 @@ public class NiceSecurePasswordField extends StackPane {
return passwordField.getCharacters();
}
public void setPassword(CharSequence password) {
passwordField.setPassword(password);
}
public void setPassword(char[] password) {
passwordField.setPassword(password);
}

View File

@@ -0,0 +1,48 @@
package org.cryptomator.ui.recoverykey;
import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.annotation.Nullable;
import javax.inject.Named;
import java.util.Optional;
@RecoveryKeyScoped
@Subcomponent(modules = {RecoveryKeyModule.class})
public interface RecoveryKeyComponent {
@RecoveryKeyWindow
Stage window();
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE)
Lazy<Scene> scene();
default void showRecoveryKeyCreationWindow() {
Stage stage = window();
stage.setScene(scene().get());
stage.sizeToScene();
stage.show();
}
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder vault(@RecoveryKeyWindow Vault vault);
@BindsInstance
Builder password(@Nullable CharSequence password);
@BindsInstance
Builder owner(@Named("keyRecoveryOwner") Stage owner);
RecoveryKeyComponent build();
}
}

View File

@@ -0,0 +1,91 @@
package org.cryptomator.ui.recoverykey;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
@RecoveryKeyScoped
public class RecoveryKeyCreationController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
private final Stage window;
private final Vault vault;
private final ExecutorService executor;
private final CharSequence prefilledPassword;
private final WordEncoder wordEncoder;
private final StringProperty recoveryKey;
public NiceSecurePasswordField passwordField;
@Inject
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, ExecutorService executor, @Nullable CharSequence prefilledPassword) {
this.window = window;
this.vault = vault;
this.executor = executor;
this.prefilledPassword = prefilledPassword;
this.wordEncoder = new WordEncoder();
this.recoveryKey = new SimpleStringProperty();
}
@FXML
public void initialize() {
if (prefilledPassword != null) {
passwordField.setPassword(prefilledPassword);
}
}
@FXML
public void createRecoveryKey() {
Tasks.create(() -> {
byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vault.getPath(), MASTERKEY_FILENAME, new byte[0], passwordField.getCharacters());
assert rawKey.length == 64;
byte[] paddedKey = Arrays.copyOf(rawKey, 66);
// TODO add two-byte CRC
try {
return wordEncoder.encodePadded(paddedKey);
} finally {
Arrays.fill(rawKey, (byte) 0x00);
Arrays.fill(paddedKey, (byte) 0x00);
}
}).onSuccess(result -> {
recoveryKey.set(result);
}).onError(IOException.class, e -> {
LOG.error("Creation of recovery key failed.", e);
}).onError(InvalidPassphraseException.class, e -> {
// TODO shake animation? :D
}).runOnce(executor);
}
@FXML
public void close() {
window.close();
}
/* Getter/Setter */
public ReadOnlyStringProperty recoveryKeyProperty() {
return recoveryKey;
}
public String getRecoveryKey() {
return recoveryKey.get();
}
}

View File

@@ -0,0 +1,59 @@
package org.cryptomator.ui.recoverykey;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.cryptomator.ui.common.FXMLLoaderFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Named;
import javax.inject.Provider;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module
abstract class RecoveryKeyModule {
@Provides
@RecoveryKeyWindow
@RecoveryKeyScoped
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, ResourceBundle resourceBundle) {
return new FXMLLoaderFactory(factories, resourceBundle);
}
@Provides
@RecoveryKeyWindow
@RecoveryKeyScoped
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon, @Named("keyRecoveryOwner") Stage owner) {
Stage stage = new Stage();
stage.setTitle("TODO keyRecovery.title"); // TODO localize
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(owner);
windowIcon.ifPresent(stage.getIcons()::add);
return stage;
}
@Provides
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE)
@RecoveryKeyScoped
static Scene provideRecoveryKeyCreationScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders, @RecoveryKeyWindow Stage window) {
return fxmlLoaders.createScene("/fxml/recoverykey_create.fxml");
}
// ------------------
@Binds
@IntoMap
@FxControllerKey(RecoveryKeyCreationController.class)
abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller);
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.recoverykey;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface RecoveryKeyScoped {
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.recoverykey;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface RecoveryKeyWindow {
}

View File

@@ -1,4 +1,4 @@
package org.cryptomator.ui.keyrecovery;
package org.cryptomator.ui.recoverykey;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;

View File

@@ -5,6 +5,7 @@ import javafx.stage.Stage;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Inject;
@@ -14,12 +15,14 @@ public class GeneralVaultOptionsController implements FxController {
private final Vault vault;
private final Stage window;
private final ChangePasswordComponent.Builder changePasswordWindow;
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
@Inject
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow) {
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow) {
this.vault = vault;
this.window = window;
this.changePasswordWindow = changePasswordWindow;
this.recoveryKeyWindow = recoveryKeyWindow;
}
@FXML
@@ -27,4 +30,9 @@ public class GeneralVaultOptionsController implements FxController {
changePasswordWindow.vault(vault).owner(window).build().showChangePasswordWindow();
}
@FXML
public void showRecoveryKey() {
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow();
}
}

View File

@@ -20,6 +20,7 @@ import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.mainwindow.MainWindow;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Named;
import javax.inject.Provider;
@@ -27,7 +28,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module(subcomponents = {ChangePasswordComponent.class})
@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class})
abstract class VaultOptionsModule {
@Provides

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
<?import javafx.scene.layout.HBox?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyCreationController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<StackPane alignment="CENTER">
<Circle styleClass="glyph-icon-primary" radius="36"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="36"/>
</StackPane>
<HBox>
<NiceSecurePasswordField fx:id="passwordField" HBox.hgrow="ALWAYS"/>
<Button text="TODO create recovery key" onAction="#createRecoveryKey" HBox.hgrow="NEVER"/>
</HBox>
<!-- TODO use TextArea instead -->
<TextField editable="false" text="${controller.recoveryKey}"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons>
<Button text="%generic.button.done" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>

View File

@@ -12,5 +12,6 @@
</padding>
<children>
<Button text="%vaultOptions.general.changePasswordBtn" onAction="#changePassword"/>
<Button text="TODO show recovery key" onAction="#showRecoveryKey"/>
</children>
</VBox>

View File

@@ -1,4 +1,4 @@
package org.cryptomator.ui.keyrecovery;
package org.cryptomator.ui.recoverykey;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;