removed multi user functionality (see #21)

using fixed masterkey filename now
This commit is contained in:
Sebastian Stenzel
2015-02-22 15:15:43 +01:00
parent b2be41e39b
commit ea3384d189
10 changed files with 47 additions and 212 deletions

View File

@@ -16,16 +16,6 @@ import org.apache.commons.codec.binary.BaseNCodec;
interface FileNamingConventions {
/**
* Extension of masterkey files inside the root directory of the encrypted storage.
*/
String MASTERKEY_FILE_EXT = ".masterkey.json";
/**
* Additional extension for masterkey backup files.
*/
String MASTERKEY_BACKUP_FILE_EXT = ".bkup";
/**
* How to encode the encrypted file names safely. Base32 uses only alphanumeric characters and is case-insensitive.
*/

View File

@@ -24,13 +24,7 @@ import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.aes256.Aes256Cryptor;
import org.cryptomator.ui.controls.ClearOnDisableListener;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
@@ -39,15 +33,11 @@ import org.slf4j.LoggerFactory;
public class InitializeController implements Initializable {
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
private static final int MAX_USERNAME_LENGTH = 250;
private ResourceBundle localization;
private Vault directory;
private InitializationListener listener;
@FXML
private TextField usernameField;
@FXML
private SecPasswordField passwordField;
@@ -63,50 +53,18 @@ public class InitializeController implements Initializable {
@Override
public void initialize(URL url, ResourceBundle rb) {
this.localization = rb;
usernameField.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
usernameField.textProperty().addListener(this::usernameFieldDidChange);
passwordField.textProperty().addListener(this::passwordFieldDidChange);
retypePasswordField.textProperty().addListener(this::retypePasswordFieldDidChange);
retypePasswordField.disableProperty().addListener(new ClearOnDisableListener(retypePasswordField));
passwordField.textProperty().addListener(this::passwordFieldsDidChange);
retypePasswordField.textProperty().addListener(this::passwordFieldsDidChange);
}
// ****************************************
// Username field
// Password fields
// ****************************************
public void filterAlphanumericKeyEvents(KeyEvent t) {
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
return;
}
char c = t.getCharacter().charAt(0);
if (!CharUtils.isAsciiAlphanumeric(c)) {
t.consume();
}
}
public void usernameFieldDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
if (StringUtils.length(newValue) > MAX_USERNAME_LENGTH) {
usernameField.setText(newValue.substring(0, MAX_USERNAME_LENGTH));
}
passwordField.setDisable(StringUtils.isEmpty(newValue));
}
// ****************************************
// Password field
// ****************************************
private void passwordFieldDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
retypePasswordField.setDisable(StringUtils.isEmpty(newValue));
}
// ****************************************
// Retype password field
// ****************************************
private void retypePasswordFieldDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
private void passwordFieldsDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
boolean passwordIsEmpty = passwordField.getText().isEmpty();
boolean passwordsAreEqual = passwordField.getText().equals(retypePasswordField.getText());
okButton.setDisable(!passwordsAreEqual);
okButton.setDisable(passwordIsEmpty || !passwordsAreEqual);
}
// ****************************************
@@ -116,8 +74,7 @@ public class InitializeController implements Initializable {
@FXML
protected void initializeVault(ActionEvent event) {
setControlsDisabled(true);
final String masterKeyFileName = usernameField.getText() + Aes256Cryptor.MASTERKEY_FILE_EXT;
final Path masterKeyPath = directory.getPath().resolve(masterKeyFileName);
final Path masterKeyPath = directory.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
final CharSequence password = passwordField.getCharacters();
try (OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
directory.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
@@ -132,14 +89,12 @@ public class InitializeController implements Initializable {
LOG.error("I/O Exception", ex);
} finally {
setControlsDisabled(false);
usernameField.setText(null);
passwordField.swipe();
retypePasswordField.swipe();
}
}
private void setControlsDisabled(boolean disable) {
usernameField.setDisable(disable);
passwordField.setDisable(disable);
retypePasswordField.setDisable(disable);
okButton.setDisable(disable);

View File

@@ -11,7 +11,6 @@ package org.cryptomator.ui;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@@ -26,21 +25,19 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.aes256.Aes256Cryptor;
import org.apache.commons.lang3.CharUtils;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.FXThreads;
import org.cryptomator.ui.util.MasterKeyFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -54,9 +51,6 @@ public class UnlockController implements Initializable {
private UnlockListener listener;
private Vault vault;
@FXML
private ComboBox<String> usernameBox;
@FXML
private SecPasswordField passwordField;
@@ -84,19 +78,8 @@ public class UnlockController implements Initializable {
public void initialize(URL url, ResourceBundle rb) {
this.rb = rb;
usernameBox.valueProperty().addListener(this::didChooseUsername);
mountName.textProperty().addListener(this::didTypeMountName);
}
// ****************************************
// Username box
// ****************************************
public void didChooseUsername(ObservableValue<? extends String> property, String oldValue, String newValue) {
if (newValue != null) {
Platform.runLater(passwordField::requestFocus);
}
passwordField.setDisable(StringUtils.isEmpty(newValue));
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
mountName.textProperty().addListener(this::mountNameDidChange);
}
// ****************************************
@@ -106,10 +89,8 @@ public class UnlockController implements Initializable {
@FXML
private void didClickUnlockButton(ActionEvent event) {
setControlsDisabled(true);
final String masterKeyFileName = usernameBox.getValue() + Aes256Cryptor.MASTERKEY_FILE_EXT;
final String masterKeyBackupFileName = masterKeyFileName + Aes256Cryptor.MASTERKEY_BACKUP_FILE_EXT;
final Path masterKeyPath = vault.getPath().resolve(masterKeyFileName);
final Path masterKeyBackupPath = vault.getPath().resolve(masterKeyBackupFileName);
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
final CharSequence password = passwordField.getCharacters();
InputStream masterKeyInputStream = null;
try {
@@ -151,30 +132,10 @@ public class UnlockController implements Initializable {
}
private void setControlsDisabled(boolean disable) {
usernameBox.setDisable(disable);
passwordField.setDisable(disable);
unlockButton.setDisable(disable);
}
private void findExistingUsernames() {
try {
DirectoryStream<Path> ds = MasterKeyFilter.filteredDirectory(vault.getPath());
final String masterKeyExt = Aes256Cryptor.MASTERKEY_FILE_EXT.toLowerCase();
usernameBox.getItems().clear();
for (final Path path : ds) {
final String fileName = path.getFileName().toString();
final int beginOfExt = fileName.toLowerCase().lastIndexOf(masterKeyExt);
final String baseName = fileName.substring(0, beginOfExt);
usernameBox.getItems().add(baseName);
}
if (usernameBox.getItems().size() == 1) {
usernameBox.getSelectionModel().selectFirst();
}
} catch (IOException e) {
LOG.trace("Invalid path: " + vault.getPath(), e);
}
}
private void didUnlockAndMount(boolean mountSuccess) {
progressIndicator.setVisible(false);
if (listener != null) {
@@ -182,13 +143,19 @@ public class UnlockController implements Initializable {
}
}
private void didTypeMountName(ObservableValue<? extends String> property, String oldValue, String newValue) {
try {
vault.setMountName(newValue);
if (!newValue.equals(vault.getMountName())) {
mountName.setText(vault.getMountName());
}
} catch (IllegalArgumentException e) {
public void filterAlphanumericKeyEvents(KeyEvent t) {
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
return;
}
char c = t.getCharacter().charAt(0);
if (!CharUtils.isAsciiAlphanumeric(c)) {
t.consume();
}
}
private void mountNameDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
// newValue is guaranteed to be a-z0-9, see #filterAlphanumericKeyEvents
if (newValue.isEmpty()) {
mountName.setText(vault.getMountName());
}
}
@@ -201,7 +168,6 @@ public class UnlockController implements Initializable {
public void setVault(Vault vault) {
this.vault = vault;
this.findExistingUsernames();
this.mountName.setText(vault.getMountName());
}

View File

@@ -1,30 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.controls;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextInputControl;
public class ClearOnDisableListener implements ChangeListener<Boolean> {
final TextInputControl control;
public ClearOnDisableListener(TextInputControl control) {
this.control = control;
}
@Override
public void changed(ObservableValue<? extends Boolean> property, Boolean wasDisabled, Boolean isDisabled) {
if (isDisabled) {
control.clear();
}
}
}

View File

@@ -15,7 +15,6 @@ import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.ui.util.DeferredClosable;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.ui.util.MasterKeyFilter;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.cryptomator.ui.util.mount.WebDavMount;
import org.cryptomator.ui.util.mount.WebDavMounter;
@@ -30,6 +29,8 @@ public class Vault implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
public static final String VAULT_MASTERKEY_FILE = "masterkey.cryptomator";
public static final String VAULT_MASTERKEY_BACKUP_FILE = "masterkey.cryptomator.bkup";
private final Path path;
private final WebDavServer server;
@@ -63,7 +64,8 @@ public class Vault implements Serializable {
}
public boolean containsMasterKey() throws IOException {
return MasterKeyFilter.filteredDirectory(path).iterator().hasNext();
final Path masterKeyPath = path.resolve(VAULT_MASTERKEY_FILE);
return Files.isRegularFile(masterKeyPath);
}
public synchronized boolean startServer() {

View File

@@ -1,34 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.util;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Files;
import java.nio.file.Path;
import org.cryptomator.crypto.aes256.Aes256Cryptor;
public class MasterKeyFilter implements Filter<Path> {
public static MasterKeyFilter FILTER = new MasterKeyFilter();
private final String masterKeyExt = Aes256Cryptor.MASTERKEY_FILE_EXT.toLowerCase();
@Override
public boolean accept(Path child) throws IOException {
return child.getFileName().toString().toLowerCase().endsWith(masterKeyExt);
}
public static final DirectoryStream<Path> filteredDirectory(Path dir) throws IOException {
return Files.newDirectoryStream(dir, FILTER);
}
}

View File

@@ -30,22 +30,18 @@
<children>
<!-- Row 0 -->
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.username" />
<TextField fx:id="usernameField" GridPane.rowIndex="0" GridPane.columnIndex="1" />
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" />
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" />
<!-- Row 1 -->
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.password" />
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="1" GridPane.columnIndex="1" disable="true" />
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" />
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" />
<!-- Row 2 -->
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%initialize.label.retypePassword" />
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="2" GridPane.columnIndex="1" disable="true" />
<!-- Row 3 -->
<Button fx:id="okButton" defaultButton="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" text="%initialize.button.ok" prefWidth="150.0" onAction="#initializeVault" focusTraversable="false" disable="true" />
<Button fx:id="okButton" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" text="%initialize.button.ok" prefWidth="150.0" onAction="#initializeVault" focusTraversable="false" disable="true" />
<!-- Row 5 -->
<Label fx:id="messageLabel" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" />
<!-- Row 3 -->
<Label fx:id="messageLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" />
</children>
</GridPane>

View File

@@ -29,7 +29,6 @@
<items>
<MenuItem text="%main.directoryList.contextMenu.remove" onAction="#didClickRemoveSelectedEntry" />
<!-- TODO: -->
<MenuItem text="%main.directoryList.contextMenu.addUser" disable="true" />
<MenuItem text="%main.directoryList.contextMenu.changePassword" disable="true" />
</items>
</ContextMenu>

View File

@@ -31,25 +31,21 @@
<children>
<!-- Row 0 -->
<Label text="%unlock.label.username" GridPane.rowIndex="0" GridPane.columnIndex="0" />
<ComboBox fx:id="usernameBox" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" promptText="$access.label.username" />
<Label text="%unlock.label.password" GridPane.rowIndex="0" GridPane.columnIndex="0" />
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
<!-- Row 1 -->
<Label text="%unlock.label.password" GridPane.rowIndex="1" GridPane.columnIndex="0" />
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
<Label text="%unlock.label.mountName" GridPane.rowIndex="1" GridPane.columnIndex="0" />
<TextField fx:id="mountName" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
<!-- Row 2 -->
<Label text="%unlock.label.mountName" GridPane.rowIndex="2" GridPane.columnIndex="0" />
<TextField fx:id="mountName" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton"/>
<!-- Row 3 -->
<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton"/>
<!-- Row 3-->
<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" visible="false"/>
<!-- Row 4-->
<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" visible="false"/>
<!-- Row 5 -->
<Label fx:id="messageLabel" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" />
<!-- Row 4 -->
<Label fx:id="messageLabel" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" />
</children>
</GridPane>

View File

@@ -11,7 +11,6 @@ app.name=Cryptomator
# main.fxml
main.directoryList.contextMenu.remove=Remove from list
main.directoryList.contextMenu.addUser=Add user
main.directoryList.contextMenu.changePassword=Change password
main.addDirectory.contextMenu.new=Create new vault
main.addDirectory.contextMenu.open=Add existing vault
@@ -23,7 +22,6 @@ welcome.addButtonInstructionLabel=Start by adding a new vault :-)
# initialize.fxml
initialize.label.username=Username
initialize.label.password=Password
initialize.label.retypePassword=Retype password
initialize.button.ok=Create vault
@@ -32,11 +30,8 @@ initialize.alert.directoryIsNotEmpty.content=All existing files inside this dire
# unlock.fxml
unlock.label.username=Username
unlock.label.password=Password
unlock.label.checkIntegrity=File integrity
unlock.label.mountName=Drive name
unlock.checkbox.checkIntegrity=Verify checksums (slower, but detects manipulation)
unlock.button.unlock=Unlock vault
unlock.errorMessage.wrongPassword=Wrong password.
unlock.errorMessage.decryptionFailed=Decryption failed.