From 2fdf9be017fd5fcb56ce3d1ea495dbb98d0d5a8b Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 9 Dec 2014 18:25:59 +0100 Subject: [PATCH] - Encrypt existing directory content on vault initialization --- .../jackrabbit/WebDavLocatorFactory.java | 25 +----- main/ui/pom.xml | 8 ++ .../cryptomator/ui/InitializeController.java | 41 ++++++++++ .../ui/util/EncryptingFileVisitor.java | 78 +++++++++++++++++++ .../main/resources/localization.properties | 3 + 5 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/EncryptingFileVisitor.java diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavLocatorFactory.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavLocatorFactory.java index 576ce16f4..7cd819f90 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavLocatorFactory.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavLocatorFactory.java @@ -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); } } diff --git a/main/ui/pom.xml b/main/ui/pom.xml index 6c1491b99..f1b6a4fa8 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -21,6 +21,7 @@ Cryptomator org.cryptomator.ui.MainApplication ${java.home}/../lib/ant-javafx.jar + 8.20.8 @@ -48,6 +49,13 @@ org.apache.commons commons-lang3 + + + + org.controlsfx + controlsfx + ${controlsfx.version} + diff --git a/main/ui/src/main/java/org/cryptomator/ui/InitializeController.java b/main/ui/src/main/java/org/cryptomator/ui/InitializeController.java index 5ff50dad8..368910805 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/InitializeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/InitializeController.java @@ -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 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 result = alert.showAndWait(); + return ButtonType.OK.equals(result.get()); + } + + private void encryptExistingContents() throws IOException { + final FileVisitor 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 */ diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/EncryptingFileVisitor.java b/main/ui/src/main/java/org/cryptomator/ui/util/EncryptingFileVisitor.java new file mode 100644 index 000000000..dcfbaec5c --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/EncryptingFileVisitor.java @@ -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 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); + } + +} diff --git a/main/ui/src/main/resources/localization.properties b/main/ui/src/main/resources/localization.properties index 8a8e2c5a5..165304d4f 100644 --- a/main/ui/src/main/resources/localization.properties +++ b/main/ui/src/main/resources/localization.properties @@ -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