- Encrypt existing directory content on vault initialization

This commit is contained in:
Sebastian Stenzel
2014-12-09 18:25:59 +01:00
parent 1de2d9d2da
commit 2fdf9be017
5 changed files with 133 additions and 22 deletions

View File

@@ -9,8 +9,6 @@
package org.cryptomator.webdav.jackrabbit;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -104,15 +102,7 @@ public class WebDavLocatorFactory extends AbstractLocatorFactory implements Sens
@Override
public void writePathSpecificMetadata(String encryptedPath, byte[] encryptedMetadata) throws IOException {
final Path metaDataFile = fsRoot.resolve(encryptedPath);
final SeekableByteChannel channel = Files.newByteChannel(metaDataFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC);
try {
final ByteBuffer buffer = ByteBuffer.wrap(encryptedMetadata);
while (channel.write(buffer) > 0) {
// continue writing.
}
} finally {
channel.close();
}
Files.write(metaDataFile, encryptedMetadata, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC);
}
@Override
@@ -120,17 +110,8 @@ public class WebDavLocatorFactory extends AbstractLocatorFactory implements Sens
final Path metaDataFile = fsRoot.resolve(encryptedPath);
if (!Files.isReadable(metaDataFile)) {
return null;
}
final long metaDataFileSize = Files.size(metaDataFile);
final SeekableByteChannel channel = Files.newByteChannel(metaDataFile, StandardOpenOption.READ);
try {
final ByteBuffer buffer = ByteBuffer.allocate((int) metaDataFileSize);
while (channel.read(buffer) > 0) {
// continue reading.
}
return buffer.array();
} finally {
channel.close();
} else {
return Files.readAllBytes(metaDataFile);
}
}

View File

@@ -21,6 +21,7 @@
<javafx.application.name>Cryptomator</javafx.application.name>
<exec.mainClass>org.cryptomator.ui.MainApplication</exec.mainClass>
<javafx.tools.ant.jar>${java.home}/../lib/ant-javafx.jar</javafx.tools.ant.jar>
<controlsfx.version>8.20.8</controlsfx.version>
</properties>
<dependencies>
@@ -48,6 +49,13 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- UI -->
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>${controlsfx.version}</version>
</dependency>
</dependencies>
<build>

View File

@@ -11,18 +11,24 @@ package org.cryptomator.ui;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Optional;
import java.util.ResourceBundle;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
@@ -34,6 +40,7 @@ import org.cryptomator.crypto.aes256.Aes256Cryptor;
import org.cryptomator.ui.controls.ClearOnDisableListener;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Directory;
import org.cryptomator.ui.util.EncryptingFileVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -116,6 +123,9 @@ public class InitializeController implements Initializable {
@FXML
protected void initializeVault(ActionEvent event) {
if (!isDirectoryEmpty() && !shouldEncryptExistingFiles()) {
return;
}
final String masterKeyFileName = usernameField.getText() + Aes256Cryptor.MASTERKEY_FILE_EXT;
final Path masterKeyPath = directory.getPath().resolve(masterKeyFileName);
final CharSequence password = passwordField.getCharacters();
@@ -124,6 +134,7 @@ public class InitializeController implements Initializable {
masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
directory.getCryptor().randomizeMasterKey();
directory.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
encryptExistingContents();
directory.getCryptor().swipeSensitiveData();
if (listener != null) {
listener.didInitialize(this);
@@ -141,6 +152,36 @@ public class InitializeController implements Initializable {
IOUtils.closeQuietly(masterKeyOutputStream);
}
}
private boolean isDirectoryEmpty() {
try {
final DirectoryStream<Path> dirContents = Files.newDirectoryStream(directory.getPath());
return !dirContents.iterator().hasNext();
} catch (IOException e) {
LOG.error("Failed to analyze directory.", e);
throw new IllegalStateException(e);
}
}
private boolean shouldEncryptExistingFiles() {
final Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle(localization.getString("initialize.alert.directoryIsNotEmpty.title"));
alert.setHeaderText(localization.getString("initialize.alert.directoryIsNotEmpty.header"));
alert.setContentText(localization.getString("initialize.alert.directoryIsNotEmpty.content"));
final Optional<ButtonType> result = alert.showAndWait();
return ButtonType.OK.equals(result.get());
}
private void encryptExistingContents() throws IOException {
final FileVisitor<Path> visitor = new EncryptingFileVisitor(directory.getPath(), directory.getCryptor(), this::shouldEncryptExistingFile);
Files.walkFileTree(directory.getPath(), visitor);
}
private boolean shouldEncryptExistingFile(Path path) {
final String name = path.getFileName().toString();
return !directory.getPath().equals(path) && !name.endsWith(Aes256Cryptor.BASIC_FILE_EXT) && !name.endsWith(Aes256Cryptor.METADATA_FILE_EXT) && !name.endsWith(Aes256Cryptor.MASTERKEY_FILE_EXT);
}
/* Getter/Setter */

View File

@@ -0,0 +1,78 @@
package org.cryptomator.ui.util;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.crypto.CryptorIOSupport;
public class EncryptingFileVisitor extends SimpleFileVisitor<Path> implements CryptorIOSupport {
private final Path rootDir;
private final Cryptor cryptor;
private final EncryptionDecider encryptionDecider;
private Path currentDir;
public EncryptingFileVisitor(Path rootDir, Cryptor cryptor, EncryptionDecider encryptionDecider) {
this.rootDir = rootDir;
this.cryptor = cryptor;
this.encryptionDecider = encryptionDecider;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (rootDir.equals(dir) || encryptionDecider.shouldEncrypt(dir)) {
this.currentDir = dir;
return FileVisitResult.CONTINUE;
} else {
return FileVisitResult.SKIP_SUBTREE;
}
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (encryptionDecider.shouldEncrypt(file)) {
final String plaintext = file.getFileName().toString();
final String encrypted = cryptor.encryptPath(plaintext, '/', '/', this);
final Path newPath = file.resolveSibling(encrypted);
Files.move(file, newPath, StandardCopyOption.ATOMIC_MOVE);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (encryptionDecider.shouldEncrypt(dir)) {
final String plaintext = dir.getFileName().toString();
final String encrypted = cryptor.encryptPath(plaintext, '/', '/', this);
final Path newPath = dir.resolveSibling(encrypted);
Files.move(dir, newPath, StandardCopyOption.ATOMIC_MOVE);
}
return FileVisitResult.CONTINUE;
}
@Override
public void writePathSpecificMetadata(String metadataFile, byte[] encryptedMetadata) throws IOException {
final Path path = currentDir.resolve(metadataFile);
Files.write(path, encryptedMetadata, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC);
}
@Override
public byte[] readPathSpecificMetadata(String metadataFile) throws IOException {
final Path path = currentDir.resolve(metadataFile);
return Files.readAllBytes(path);
}
/* callback */
public interface EncryptionDecider {
boolean shouldEncrypt(Path path);
}
}

View File

@@ -17,6 +17,9 @@ initialize.label.username=Username
initialize.label.password=Password
initialize.label.retypePassword=Retype password
initialize.button.ok=Create vault
initialize.alert.directoryIsNotEmpty.title=Confirm
initialize.alert.directoryIsNotEmpty.header=The chosen directory is not empty.
initialize.alert.directoryIsNotEmpty.content=All existing files inside this directory will get encrypted. Continue?
# unlock.fxml