mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-07-29 11:32:38 +00:00
Merge branch 'develop' into feature/external-keychain
# Conflicts: # main/pom.xml # main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -18,11 +20,14 @@ import javax.inject.Singleton;
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
import org.cryptomator.frontend.webdav.WebDavModule;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.jni.JniModule;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.model.VaultObjectMapperProvider;
|
||||
import org.cryptomator.ui.model.Vaults;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.settings.SettingsProvider;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
@@ -34,6 +39,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Module(includes = {CryptoEngineModule.class, CommonsModule.class, WebDavModule.class})
|
||||
@@ -96,7 +102,9 @@ class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
FrontendFactory provideFrontendFactory(DeferredCloser closer, WebDavServer webDavServer, Settings settings) {
|
||||
FrontendFactory provideFrontendFactory(DeferredCloser closer, WebDavServer webDavServer, Vaults vaults, Settings settings) {
|
||||
vaults.addListener((Observable o) -> setValidFrontendIds(webDavServer, vaults));
|
||||
setValidFrontendIds(webDavServer, vaults);
|
||||
webDavServer.setPort(settings.getPort());
|
||||
webDavServer.start();
|
||||
return closer.closeLater(webDavServer, WebDavServer::stop).get().orElseThrow(IllegalStateException::new);
|
||||
@@ -108,4 +116,9 @@ class CryptomatorModule {
|
||||
return JniModule.macFunctions();
|
||||
}
|
||||
|
||||
private void setValidFrontendIds(WebDavServer webDavServer, Vaults vaults) {
|
||||
webDavServer.setValidFrontendIds(vaults.stream() //
|
||||
.map(Vault::getId).map(FrontendId::from).collect(toList()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.model.VaultFactory;
|
||||
import org.cryptomator.ui.model.Vaults;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||
@@ -44,9 +45,6 @@ import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener.Change;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
@@ -81,7 +79,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
private final Lazy<SettingsController> settingsController;
|
||||
private final Lazy<UpgradeStrategies> upgradeStrategies;
|
||||
private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final Vaults vaults;
|
||||
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
|
||||
private final BooleanExpression isSelectedVaultUnlocked = BooleanBinding.booleanExpression(EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty).orElse(false));
|
||||
private final BooleanExpression isSelectedVaultValid = BooleanBinding.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false));
|
||||
@@ -92,7 +90,8 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
@Inject
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController,
|
||||
Lazy<InitializeController> initializeController, Lazy<NotFoundController> notFoundController, Lazy<UpgradeController> upgradeController, Lazy<UnlockController> unlockController,
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController, Lazy<UpgradeStrategies> upgradeStrategies) {
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController, Lazy<UpgradeStrategies> upgradeStrategies,
|
||||
Vaults vaults) {
|
||||
super(localization);
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaultFactoy = vaultFactoy;
|
||||
@@ -105,10 +104,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
this.changePasswordController = changePasswordController;
|
||||
this.settingsController = settingsController;
|
||||
this.upgradeStrategies = upgradeStrategies;
|
||||
this.vaults = FXCollections.observableList(settings.getDirectories());
|
||||
this.vaults.addListener((Change<? extends Vault> c) -> {
|
||||
settings.save();
|
||||
});
|
||||
this.vaults = vaults;
|
||||
|
||||
// derived bindings:
|
||||
this.isShowingSettings = activeController.isEqualTo(settingsController.get());
|
||||
|
||||
@@ -14,8 +14,8 @@ public class UpgradeStrategies {
|
||||
private final Collection<UpgradeStrategy> strategies;
|
||||
|
||||
@Inject
|
||||
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2) {
|
||||
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2));
|
||||
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2, UpgradeVersion4to5 upgrader3) {
|
||||
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2, upgrader3));
|
||||
}
|
||||
|
||||
public Optional<UpgradeStrategy> getUpgradeStrategy(Vault vault) {
|
||||
|
||||
@@ -6,11 +6,11 @@ import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.KeyFile;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
@@ -20,12 +20,16 @@ public abstract class UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeStrategy.class);
|
||||
|
||||
protected final Provider<Cryptor> cryptorProvider;
|
||||
protected final CryptorProvider cryptorProvider;
|
||||
protected final Localization localization;
|
||||
protected final int vaultVersionBeforeUpgrade;
|
||||
protected final int vaultVersionAfterUpgrade;
|
||||
|
||||
UpgradeStrategy(Provider<Cryptor> cryptorProvider, Localization localization) {
|
||||
UpgradeStrategy(CryptorProvider cryptorProvider, Localization localization, int vaultVersionBeforeUpgrade, int vaultVersionAfterUpgrade) {
|
||||
this.cryptorProvider = cryptorProvider;
|
||||
this.localization = localization;
|
||||
this.vaultVersionBeforeUpgrade = vaultVersionBeforeUpgrade;
|
||||
this.vaultVersionAfterUpgrade = vaultVersionAfterUpgrade;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,27 +41,29 @@ public abstract class UpgradeStrategy {
|
||||
* Upgrades a vault. Might take a moment, should be run in a background thread.
|
||||
*/
|
||||
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
|
||||
final Cryptor cryptor = cryptorProvider.get();
|
||||
Cryptor cryptor = null;
|
||||
try {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
cryptor.readKeysFromMasterkeyFile(masterkeyFileContents, passphrase);
|
||||
cryptor = cryptorProvider.createFromKeyFile(KeyFile.parse(masterkeyFileContents), passphrase, vaultVersionBeforeUpgrade);
|
||||
// create backup, as soon as we know the password was correct:
|
||||
final Path masterkeyBackupFile = vault.path().getValue().resolve(Constants.MASTERKEY_BACKUP_FILENAME);
|
||||
Files.copy(masterkeyFile, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
// do stuff:
|
||||
upgrade(vault, cryptor);
|
||||
// write updated masterkey file:
|
||||
final byte[] upgradedMasterkeyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase);
|
||||
final Path masterkeyFileAfterUpgrading = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); // path may have changed
|
||||
Files.write(masterkeyFileAfterUpgrading, upgradedMasterkeyFileContents, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
final byte[] upgradedMasterkeyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase, vaultVersionAfterUpgrade).serialize();
|
||||
final Path masterkeyFileAfterUpgrade = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); // path may have changed
|
||||
Files.write(masterkeyFileAfterUpgrade, upgradedMasterkeyFileContents, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (IOException | UnsupportedVaultFormatException e) {
|
||||
LOG.warn("Upgrade failed.", e);
|
||||
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
if (cryptor != null) {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +74,21 @@ public abstract class UpgradeStrategy {
|
||||
*
|
||||
* @return <code>true</code> if and only if the vault can be migrated to a newer version without the risk of data losses.
|
||||
*/
|
||||
public abstract boolean isApplicable(Vault vault);
|
||||
public boolean isApplicable(Vault vault) {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
return KeyFile.parse(masterkeyFileContents).getVersion() == vaultVersionBeforeUpgrade;
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when data migration failed.
|
||||
|
||||
+7
-40
@@ -1,19 +1,16 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
@@ -28,8 +25,8 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
private final Settings settings;
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion3DropBundleExtension(Provider<Cryptor> cryptorProvider, Localization localization, Settings settings) {
|
||||
super(cryptorProvider, localization);
|
||||
public UpgradeVersion3DropBundleExtension(SecureRandom secureRandom, Localization localization, Settings settings) {
|
||||
super(Cryptors.version1(secureRandom), localization, 3, 3);
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@@ -42,25 +39,6 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
return String.format(fmt, oldVaultName, newVaultName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
|
||||
final Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
cryptor.readKeysFromMasterkeyFile(masterkeyFileContents, passphrase);
|
||||
upgrade(vault, cryptor);
|
||||
// don't write new masterkey. this is a special case, as we were stupid and didn't increase the vault version with this upgrade...
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (IOException | UnsupportedVaultFormatException e) {
|
||||
LOG.warn("Upgrade failed.", e);
|
||||
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
|
||||
Path path = vault.path().getValue();
|
||||
@@ -73,6 +51,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
throw new UpgradeFailedException(msg);
|
||||
} else {
|
||||
try {
|
||||
LOG.info("Renaming {} to {}", path, newPath.getFileName());
|
||||
Files.move(path, path.resolveSibling(newVaultName));
|
||||
Platform.runLater(() -> {
|
||||
vault.setPath(newPath);
|
||||
@@ -89,19 +68,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
public boolean isApplicable(Vault vault) {
|
||||
Path vaultPath = vault.path().getValue();
|
||||
if (vaultPath.toString().endsWith(Vault.VAULT_FILE_EXTENSION)) {
|
||||
final Path masterkeyFile = vaultPath.resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
final String keyContents = new String(Files.readAllBytes(masterkeyFile), StandardCharsets.UTF_8);
|
||||
return keyContents.contains("\"version\":3") || keyContents.contains("\"version\": 3");
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
return super.isApplicable(vault);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,19 +9,19 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.codec.binary.Base32;
|
||||
import org.apache.commons.codec.binary.BaseNCodec;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -40,17 +40,12 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
|
||||
private static final String OLD_FOLDER_SUFFIX = "_";
|
||||
private static final String NEW_FOLDER_PREFIX = "0";
|
||||
|
||||
private final MessageDigest sha1;
|
||||
private final MessageDigest sha1 = MessageDigestSupplier.SHA1.get();
|
||||
private final BaseNCodec base32 = new Base32();
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion3to4(Provider<Cryptor> cryptorProvider, Localization localization) {
|
||||
super(cryptorProvider, localization);
|
||||
try {
|
||||
sha1 = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError("SHA-1 exists in every JVM");
|
||||
}
|
||||
public UpgradeVersion3to4(SecureRandom secureRandom, Localization localization) {
|
||||
super(Cryptors.version1(secureRandom), localization, 3, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,21 +139,4 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Vault vault) {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
final String keyContents = new String(Files.readAllBytes(masterkeyFile), UTF_8);
|
||||
return keyContents.contains("\"version\":3") || keyContents.contains("\"version\": 3");
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.FileHeader;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
|
||||
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
|
||||
*/
|
||||
@Singleton
|
||||
class UpgradeVersion4to5 extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion4to5.class);
|
||||
private static final Pattern BASE32_PATTERN = Pattern.compile("^([A-Z2-7]{8})*[A-Z2-7=]{8}");
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion4to5(SecureRandom secureRandom, Localization localization) {
|
||||
super(Cryptors.version1(secureRandom), localization, 4, 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotification(Vault vault) {
|
||||
return localization.getString("upgrade.version3to4.msg");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
|
||||
Path dataDir = vault.path().get().resolve("d");
|
||||
if (!Files.isDirectory(dataDir)) {
|
||||
return; // empty vault. no migration needed.
|
||||
}
|
||||
try {
|
||||
Files.walkFileTree(dataDir, new SimpleFileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (BASE32_PATTERN.matcher(file.getFileName().toString()).find() && attrs.size() > cryptor.fileHeaderCryptor().headerSize()) {
|
||||
migrate(file, attrs, cryptor);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Migration failed.", e);
|
||||
throw new UpgradeFailedException(localization.getString("upgrade.version3to4.err.io"));
|
||||
}
|
||||
LOG.info("Migration finished.");
|
||||
}
|
||||
|
||||
private void migrate(Path file, BasicFileAttributes attrs, Cryptor cryptor) throws IOException {
|
||||
try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
|
||||
// read header:
|
||||
ByteBuffer headerBuf = ByteBuffer.allocate(cryptor.fileHeaderCryptor().headerSize());
|
||||
ch.read(headerBuf);
|
||||
headerBuf.flip();
|
||||
FileHeader header = cryptor.fileHeaderCryptor().decryptHeader(headerBuf);
|
||||
long cleartextSize = header.getFilesize();
|
||||
if (cleartextSize < 0) {
|
||||
LOG.info("Skipping already migrated file {}.", file);
|
||||
return;
|
||||
} else if (cleartextSize > attrs.size()) {
|
||||
LOG.warn("Skipping file {} with invalid file size {}/{}", file, cleartextSize, attrs.size());
|
||||
return;
|
||||
}
|
||||
int headerSize = cryptor.fileHeaderCryptor().headerSize();
|
||||
int ciphertextChunkSize = cryptor.fileContentCryptor().ciphertextChunkSize();
|
||||
int cleartextChunkSize = cryptor.fileContentCryptor().cleartextChunkSize();
|
||||
long newCiphertextSize = Cryptors.ciphertextSize(cleartextSize, cryptor);
|
||||
long newEOF = headerSize + newCiphertextSize;
|
||||
long newFullChunks = newCiphertextSize / ciphertextChunkSize; // int-truncation
|
||||
long newAdditionalCiphertextBytes = newCiphertextSize % ciphertextChunkSize;
|
||||
if (newAdditionalCiphertextBytes == 0) {
|
||||
// (new) last block is already correct. just truncate:
|
||||
LOG.info("Migrating {} of cleartext size {}: Truncating to new ciphertext size: {}", file, cleartextSize, newEOF);
|
||||
ch.truncate(newEOF);
|
||||
} else {
|
||||
// last block may contain padding and needs to be re-encrypted:
|
||||
long lastChunkIdx = newFullChunks;
|
||||
LOG.info("Migrating {} of cleartext size {}: Re-encrypting chunk {}. New ciphertext size: {}", file, cleartextSize, lastChunkIdx, newEOF);
|
||||
long beginOfLastChunk = headerSize + lastChunkIdx * ciphertextChunkSize;
|
||||
assert beginOfLastChunk < newEOF;
|
||||
int lastCleartextChunkLength = (int) (cleartextSize % cleartextChunkSize);
|
||||
assert lastCleartextChunkLength < cleartextChunkSize;
|
||||
assert lastCleartextChunkLength > 0;
|
||||
ch.position(beginOfLastChunk);
|
||||
ByteBuffer lastCiphertextChunk = ByteBuffer.allocate(ciphertextChunkSize);
|
||||
int read = ch.read(lastCiphertextChunk);
|
||||
if (read != -1) {
|
||||
lastCiphertextChunk.flip();
|
||||
ByteBuffer lastCleartextChunk = cryptor.fileContentCryptor().decryptChunk(lastCiphertextChunk, lastChunkIdx, header, true);
|
||||
lastCleartextChunk.position(0).limit(lastCleartextChunkLength);
|
||||
assert lastCleartextChunk.remaining() == lastCleartextChunkLength;
|
||||
ByteBuffer newLastChunkCiphertext = cryptor.fileContentCryptor().encryptChunk(lastCleartextChunk, lastChunkIdx, header);
|
||||
ch.truncate(beginOfLastChunk);
|
||||
ch.write(newLastChunkCiphertext);
|
||||
} else {
|
||||
LOG.error("Reached EOF at position {}/{}", beginOfLastChunk, newEOF);
|
||||
return; // must exit method before changing header!
|
||||
}
|
||||
}
|
||||
header.setFilesize(-1l);
|
||||
ByteBuffer newHeaderBuf = cryptor.fileHeaderCryptor().encryptHeader(header);
|
||||
ch.position(0);
|
||||
ch.write(newHeaderBuf);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ListChangeListener.Change;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
@Singleton
|
||||
public class Vaults implements ObservableList<Vault> {
|
||||
|
||||
private final ObservableList<Vault> delegate;
|
||||
|
||||
@Inject
|
||||
public Vaults(Settings settings) {
|
||||
this.delegate = FXCollections.observableList(settings.getDirectories());
|
||||
addListener((Change<? extends Vault> change) -> settings.save());
|
||||
}
|
||||
|
||||
public void addListener(ListChangeListener<? super Vault> listener) {
|
||||
delegate.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(ListChangeListener<? super Vault> listener) {
|
||||
delegate.removeListener(listener);
|
||||
}
|
||||
|
||||
public void addListener(InvalidationListener listener) {
|
||||
delegate.addListener(listener);
|
||||
}
|
||||
|
||||
public boolean addAll(Vault... elements) {
|
||||
return delegate.addAll(elements);
|
||||
}
|
||||
|
||||
public boolean setAll(Vault... elements) {
|
||||
return delegate.setAll(elements);
|
||||
}
|
||||
|
||||
public boolean setAll(Collection<? extends Vault> col) {
|
||||
return delegate.setAll(col);
|
||||
}
|
||||
|
||||
public boolean removeAll(Vault... elements) {
|
||||
return delegate.removeAll(elements);
|
||||
}
|
||||
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
delegate.removeListener(listener);
|
||||
}
|
||||
|
||||
public boolean retainAll(Vault... elements) {
|
||||
return delegate.retainAll(elements);
|
||||
}
|
||||
|
||||
public void remove(int from, int to) {
|
||||
delegate.remove(from, to);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
public boolean contains(Object o) {
|
||||
return delegate.contains(o);
|
||||
}
|
||||
|
||||
public Iterator<Vault> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
return delegate.toArray();
|
||||
}
|
||||
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return delegate.toArray(a);
|
||||
}
|
||||
|
||||
public boolean add(Vault e) {
|
||||
return delegate.add(e);
|
||||
}
|
||||
|
||||
public boolean remove(Object o) {
|
||||
return delegate.remove(o);
|
||||
}
|
||||
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return delegate.containsAll(c);
|
||||
}
|
||||
|
||||
public boolean addAll(Collection<? extends Vault> c) {
|
||||
return delegate.addAll(c);
|
||||
}
|
||||
|
||||
public boolean addAll(int index, Collection<? extends Vault> c) {
|
||||
return delegate.addAll(index, c);
|
||||
}
|
||||
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return delegate.removeAll(c);
|
||||
}
|
||||
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return delegate.retainAll(c);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
public Vault get(int index) {
|
||||
return delegate.get(index);
|
||||
}
|
||||
|
||||
public Vault set(int index, Vault element) {
|
||||
return delegate.set(index, element);
|
||||
}
|
||||
|
||||
public void add(int index, Vault element) {
|
||||
delegate.add(index, element);
|
||||
}
|
||||
|
||||
public Vault remove(int index) {
|
||||
return delegate.remove(index);
|
||||
}
|
||||
|
||||
public int indexOf(Object o) {
|
||||
return delegate.indexOf(o);
|
||||
}
|
||||
|
||||
public int lastIndexOf(Object o) {
|
||||
return delegate.lastIndexOf(o);
|
||||
}
|
||||
|
||||
public ListIterator<Vault> listIterator() {
|
||||
return delegate.listIterator();
|
||||
}
|
||||
|
||||
public ListIterator<Vault> listIterator(int index) {
|
||||
return delegate.listIterator(index);
|
||||
}
|
||||
|
||||
public List<Vault> subList(int fromIndex, int toIndex) {
|
||||
return delegate.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
return internalEquals((Vaults)obj);
|
||||
}
|
||||
|
||||
private boolean internalEquals(Vaults other) {
|
||||
return delegate.equals(other.delegate);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user