New vault format: Passwords are now strictly used in unicode NFC when passed to key derivation. References #521

This commit is contained in:
Sebastian Stenzel
2017-06-20 12:08:36 +02:00
parent 56db4aa038
commit 3d030cb6b0
6 changed files with 97 additions and 16 deletions

View File

@@ -28,7 +28,7 @@
<!-- dependency versions -->
<cryptomator.cryptolib.version>1.1.2</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>1.3.0</cryptomator.cryptofs.version>
<cryptomator.cryptofs.version>1.4.0</cryptomator.cryptofs.version>
<cryptomator.webdav.version>0.6.1</cryptomator.webdav.version>
<cryptomator.jni.version>1.0.2</cryptomator.jni.version>
<slf4j.version>1.7.25</slf4j.version>

View File

@@ -19,8 +19,8 @@ public class UpgradeStrategies {
private final Collection<UpgradeStrategy> strategies;
@Inject
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2, UpgradeVersion4to5 upgrader3) {
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2, upgrader3));
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2, UpgradeVersion4to5 upgrader3, UpgradeVersion5toX upgrader4) {
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2, upgrader3, upgrader4));
}
public UpgradeStrategy getUpgradeStrategy(Vault vault) {

View File

@@ -63,18 +63,17 @@ public abstract class UpgradeStrategy {
*/
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
LOG.info("Upgrading {} from {} to {}.", vault.getPath(), vaultVersionBeforeUpgrade, vaultVersionAfterUpgrade);
final Path masterkeyFile = vault.getPath().resolve(MASTERKEY_FILENAME);
try (Cryptor cryptor = cryptorProvider.createFromKeyFile(KeyFile.parse(Files.readAllBytes(masterkeyFile)), passphrase, vaultVersionBeforeUpgrade)) {
final Path masterkeyFileBeforeUpgrade = vault.getPath().resolve(MASTERKEY_FILENAME);
try (Cryptor cryptor = readMasterkeyFile(masterkeyFileBeforeUpgrade, passphrase)) {
// create backup, as soon as we know the password was correct:
final Path masterkeyBackupFile = vault.getPath().resolve(MASTERKEY_BACKUP_FILENAME);
Files.copy(masterkeyFile, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
Path masterkeyBackupFile = vault.getPath().resolve(MASTERKEY_BACKUP_FILENAME);
Files.copy(masterkeyFileBeforeUpgrade, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
LOG.info("Backuped masterkey.");
// do stuff:
upgrade(vault, cryptor);
// write updated masterkey file:
final byte[] upgradedMasterkeyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase, vaultVersionAfterUpgrade).serialize();
final Path masterkeyFileAfterUpgrade = vault.getPath().resolve(MASTERKEY_FILENAME); // path may have changed
Files.write(masterkeyFileAfterUpgrade, upgradedMasterkeyFileContents, StandardOpenOption.TRUNCATE_EXISTING);
Path masterkeyFileAfterUpgrade = vault.getPath().resolve(MASTERKEY_FILENAME); // path may have changed
writeMasterkeyFile(masterkeyFileAfterUpgrade, cryptor, passphrase);
LOG.info("Updated masterkey.");
} catch (InvalidPassphraseException e) {
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
@@ -92,6 +91,17 @@ public abstract class UpgradeStrategy {
}
}
protected Cryptor readMasterkeyFile(Path masterkeyFile, CharSequence passphrase) throws UnsupportedVaultFormatException, InvalidPassphraseException, IOException {
byte[] fileContents = Files.readAllBytes(masterkeyFile);
KeyFile keyFile = KeyFile.parse(fileContents);
return cryptorProvider.createFromKeyFile(keyFile, passphrase, vaultVersionBeforeUpgrade);
}
protected void writeMasterkeyFile(Path masterkeyFile, Cryptor cryptor, CharSequence passphrase) throws IOException {
byte[] fileContents = cryptor.writeKeysToMasterkeyFile(passphrase, vaultVersionAfterUpgrade).serialize();
Files.write(masterkeyFile, fileContents, StandardOpenOption.TRUNCATE_EXISTING);
}
protected abstract void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException;
/**

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
import java.io.IOException;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.cryptofs.migration.Migrators;
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
class UpgradeVersion5toX extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion5toX.class);
@Inject
public UpgradeVersion5toX(Localization localization) {
super(Cryptors.version1(UpgradeStrategy.strongSecureRandom()), localization, 5, Integer.MAX_VALUE);
}
@Override
public String getTitle(Vault vault) {
return localization.getString("upgrade.version5toX.title");
}
@Override
public String getMessage(Vault vault) {
return localization.getString("upgrade.version5toX.msg");
}
@Override
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
try {
Migrators.get().migrate(vault.getPath(), "masterkey.cryptomator", passphrase);
} catch (InvalidPassphraseException e) {
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
} catch (NoApplicableMigratorException | IOException e) {
LOG.warn("Upgrade failed.", e);
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
}
}
@Override
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
// called by #upgrade(Vault, CharSequence), which got overwritten.
throw new AssertionError("Method can not be called.");
}
@Override
public boolean isApplicable(Vault vault) {
try {
return Migrators.get().needsMigration(vault.getPath(), "masterkey.cryptomator");
} catch (IOException e) {
LOG.warn("Could not determine, whether upgrade is applicable.", e);
return false;
}
}
}

View File

@@ -16,7 +16,6 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
@@ -32,7 +31,6 @@ import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
@@ -86,14 +84,14 @@ public class Vault {
// Commands
// ********************************************************************************/
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws IOException, CryptoException {
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws IOException, InvalidPassphraseException, CryptoException {
return LazyInitializer.initializeLazily(cryptoFileSystem, () -> unlockCryptoFileSystem(passphrase), IOException.class);
}
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws IOException, CryptoException {
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws IOException, InvalidPassphraseException, CryptoException {
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withPassphrase(passphrase) //
.withFlags(EnumSet.noneOf(FileSystemFlags.class)) // TODO: use withFlags() with CryptoFS 1.3.1
.withFlags() //
.withMasterkeyFilename(MASTERKEY_FILENAME) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
@@ -118,7 +116,7 @@ public class Vault {
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
}
public synchronized void unlock(CharSequence passphrase) throws ServerLifecycleException {
public synchronized void unlock(CharSequence passphrase) throws InvalidPassphraseException, ServerLifecycleException {
try {
FileSystem fs = getCryptoFileSystem(passphrase);
if (!server.isRunning()) {

View File

@@ -54,6 +54,9 @@ upgrade.version4to5.title=Vault Version 4 to 5 Upgrade
upgrade.version4to5.msg=This vault needs to be migrated to a newer format.\nEncrypted files will be updated.\nPlease make sure synchronization has finished before proceeding.\n\nNote: Modification date of all files will be changed to the current date/time in the process.
upgrade.version4to5.err.io=Migration failed due to an I/O Exception. See log file for details.
upgrade.version5toX.title=Vault Version Upgrade
upgrade.version5toX.msg=This vault needs to be migrated to a newer format.\nPlease make sure synchronization has finished before proceeding.
# unlock.fxml
unlock.label.password=Password
unlock.label.savePassword=Save Password