Adjusted to CryptoFS 2.0.0

This commit is contained in:
Sebastian Stenzel
2020-12-08 14:39:46 +01:00
parent 3b73544766
commit c0a9a95e4f
14 changed files with 156 additions and 48 deletions

View File

@@ -25,6 +25,8 @@ import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.collections.ObservableList;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Comparator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -53,6 +55,16 @@ public abstract class CommonsModule {
+ "r0DzRyj4ixPIt38CQB8=";
}
@Provides
@Singleton
static SecureRandom provideCSPRNG() {
try {
return SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e);
}
}
@Provides
@Singleton
@Named("SemVer")

View File

@@ -3,5 +3,7 @@ package org.cryptomator.common;
public interface Constants {
String MASTERKEY_FILENAME = "masterkey.cryptomator";
String VAULTCONFIG_FILENAME = "vault.cryptomator";
byte[] PEPPER = new byte[0];
}

View File

@@ -21,6 +21,7 @@ import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.common.MasterkeyFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,6 +46,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.PEPPER;
@PerVault
public class Vault {
@@ -111,14 +113,17 @@ public class Vault {
LOG.info("Storing file name length limit of {}", limit);
}
assert vaultSettings.filenameLengthLimit().get() > 0;
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withPassphrase(passphrase) //
.withFlags(flags) //
.withMasterkeyFilename(MASTERKEY_FILENAME) //
.withMaxPathLength(vaultSettings.filenameLengthLimit().get() + Constants.MAX_ADDITIONAL_PATH_LENGTH) //
.withMaxNameLength(vaultSettings.filenameLengthLimit().get()) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
Path masterkeyPath = getPath().resolve(MASTERKEY_FILENAME);
try (var keyLoader = MasterkeyFile.withContentFromFile(masterkeyPath).unlock(passphrase, PEPPER, Optional.empty())) {
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withKeyLoader(keyLoader) //
.withFlags(flags) //
.withMaxPathLength(vaultSettings.filenameLengthLimit().get() + Constants.MAX_ADDITIONAL_PATH_LENGTH) //
.withMaxNameLength(vaultSettings.filenameLengthLimit().get()) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
}
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {

View File

@@ -28,6 +28,7 @@ import java.util.ResourceBundle;
import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
@Singleton
public class VaultListManager {
@@ -54,7 +55,7 @@ public class VaultListManager {
public Vault add(Path pathToVault) throws NoSuchFileException {
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
if (!CryptoFileSystemProvider.containsVault(normalizedPathToVault, MASTERKEY_FILENAME)) {
if (!CryptoFileSystemProvider.containsVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
throw new NoSuchFileException(normalizedPathToVault.toString(), null, "Not a vault directory");
}
Optional<Vault> alreadyExistingVault = get(normalizedPathToVault);
@@ -124,9 +125,9 @@ public class VaultListManager {
}
private static VaultState determineVaultState(Path pathToVault) throws IOException {
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
if (!CryptoFileSystemProvider.containsVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
return VaultState.MISSING;
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
} else if (Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
return VaultState.NEEDS_MIGRATION;
} else {
return VaultState.LOCKED;

View File

@@ -24,7 +24,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>1.9.13</cryptomator.cryptofs.version>
<cryptomator.cryptofs.version>2.0.0-beta1</cryptomator.cryptofs.version>
<cryptomator.integrations.version>0.1.6</cryptomator.integrations.version>
<cryptomator.integrations.win.version>0.1.0-beta1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>0.1.0-beta3</cryptomator.integrations.mac.version>

View File

@@ -5,6 +5,13 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.VaultCipherCombo;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.cryptolib.common.MasterkeyFile;
import org.cryptomator.cryptolib.common.MasterkeyFileLoader;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
@@ -29,6 +36,7 @@ import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.stage.Stage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.channels.WritableByteChannel;
@@ -37,12 +45,15 @@ import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.PEPPER;
@AddVaultWizardScoped
public class CreateNewVaultPasswordController implements FxController {
@@ -64,6 +75,7 @@ public class CreateNewVaultPasswordController implements FxController {
private final ResourceBundle resourceBundle;
private final ObjectProperty<CharSequence> password;
private final ReadmeGenerator readmeGenerator;
private final SecureRandom csprng;
private final BooleanProperty processing;
private final BooleanProperty readyToCreateVault;
private final ObjectBinding<ContentDisplay> createVaultButtonState;
@@ -73,7 +85,7 @@ public class CreateNewVaultPasswordController implements FxController {
public Toggle skipRecoveryKey;
@Inject
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ErrorComponent.Builder errorComponent, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, @Named("newPassword") ObjectProperty<CharSequence> password, ReadmeGenerator readmeGenerator) {
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ErrorComponent.Builder errorComponent, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, @Named("newPassword") ObjectProperty<CharSequence> password, ReadmeGenerator readmeGenerator, SecureRandom csprng) {
this.window = window;
this.chooseLocationScene = chooseLocationScene;
this.recoveryKeyScene = recoveryKeyScene;
@@ -89,6 +101,7 @@ public class CreateNewVaultPasswordController implements FxController {
this.resourceBundle = resourceBundle;
this.password = password;
this.readmeGenerator = readmeGenerator;
this.csprng = csprng;
this.processing = new SimpleBooleanProperty();
this.readyToCreateVault = new SimpleBooleanProperty();
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
@@ -161,23 +174,34 @@ public class CreateNewVaultPasswordController implements FxController {
}
private void initializeVault(Path path, CharSequence passphrase) throws IOException {
CryptoFileSystemProvider.initialize(path, MASTERKEY_FILENAME, passphrase);
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withPassphrase(passphrase) //
.withFlags(Collections.emptySet()) //
.withMasterkeyFilename(MASTERKEY_FILENAME) //
.build();
String vaultReadmeFileName = resourceBundle.getString("addvault.new.readme.accessLocation.fileName");
try (FileSystem fs = CryptoFileSystemProvider.newFileSystem(path, fsProps); //
WritableByteChannel ch = Files.newByteChannel(fs.getPath("/", vaultReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
ch.write(US_ASCII.encode(readmeGenerator.createVaultAccessLocationReadmeRtf()));
// 1. write masterkey:
Path masterkeyFilePath = path.resolve(MASTERKEY_FILENAME);
try (Masterkey masterkey = Masterkey.createNew(csprng)) {
byte[] serialized = MasterkeyFile.lock(masterkey, passphrase, PEPPER, 999, csprng);
Files.write(masterkeyFilePath, serialized, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
}
// 2. verify masterkey and initialize vault:
try (var loader = MasterkeyFile.withContentFromFile(masterkeyFilePath).unlock(passphrase, PEPPER, Optional.of(999))) {
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(loader).build();
CryptoFileSystemProvider.initialize(path, fsProps, MasterkeyFileLoader.KEY_ID);
// 3. write vault-internal readme file:
String vaultReadmeFileName = resourceBundle.getString("addvault.new.readme.accessLocation.fileName");
try (FileSystem fs = CryptoFileSystemProvider.newFileSystem(path, fsProps); //
WritableByteChannel ch = Files.newByteChannel(fs.getPath("/", vaultReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
ch.write(US_ASCII.encode(readmeGenerator.createVaultAccessLocationReadmeRtf()));
}
} catch (CryptoException e) {
throw new IOException("Failed initialize vault.", e);
}
// 4. write vault-external readme file:
String storagePathReadmeFileName = resourceBundle.getString("addvault.new.readme.storageLocation.fileName");
try (WritableByteChannel ch = Files.newByteChannel(path.resolve(storagePathReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
ch.write(US_ASCII.encode(readmeGenerator.createVaultStorageLocationReadmeRtf()));
}
LOG.info("Created vault at {}", path);
}

View File

@@ -3,7 +3,10 @@ package org.cryptomator.ui.changepassword;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.common.MasterkeyBackupHelper;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.common.MasterkeyFile;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
@@ -23,6 +26,12 @@ import javafx.scene.control.CheckBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.SecureRandom;
import java.text.Normalizer;
import java.util.Optional;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@@ -31,24 +40,27 @@ import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
public class ChangePasswordController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
private static final String MASTERKEY_BACKUP_SUFFIX = ".bkup";
private final Stage window;
private final Vault vault;
private final ObjectProperty<CharSequence> newPassword;
private final ErrorComponent.Builder errorComponent;
private final KeychainManager keychain;
private final SecureRandom csprng;
public NiceSecurePasswordField oldPasswordField;
public CheckBox finalConfirmationCheckbox;
public Button finishButton;
@Inject
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, KeychainManager keychain) {
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, KeychainManager keychain, SecureRandom csprng) {
this.window = window;
this.vault = vault;
this.newPassword = newPassword;
this.errorComponent = errorComponent;
this.keychain = keychain;
this.csprng = csprng;
}
@FXML
@@ -67,17 +79,26 @@ public class ChangePasswordController implements FxController {
@FXML
public void finish() {
try {
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPassword.get());
//String normalizedOldPassphrase = Normalizer.normalize(oldPasswordField.getCharacters(), Normalizer.Form.NFC);
//String normalizedNewPassphrase = Normalizer.normalize(newPassword.get(), Normalizer.Form.NFC);
CharSequence oldPassphrase = oldPasswordField.getCharacters(); // TODO verify: is this already NFC-normalized?
CharSequence newPassphrase = newPassword.get(); // TODO verify: is this already NFC-normalized?
Path masterkeyPath = vault.getPath().resolve(MASTERKEY_FILENAME);
byte[] oldMasterkeyBytes = Files.readAllBytes(masterkeyPath);
byte[] newMasterkeyBytes = MasterkeyFile.changePassphrase(oldMasterkeyBytes, oldPassphrase, newPassphrase, new byte[0], csprng);
Path backupKeyPath = vault.getPath().resolve(MASTERKEY_FILENAME + MasterkeyBackupHelper.generateFileIdSuffix(oldMasterkeyBytes) + MASTERKEY_BACKUP_SUFFIX);
Files.move(masterkeyPath, backupKeyPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
Files.write(masterkeyPath, newMasterkeyBytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
LOG.info("Successfully changed password for {}", vault.getDisplayName());
window.close();
updatePasswordInSystemkeychain();
} catch (IOException e) {
LOG.error("IO error occured during password change. Unable to perform operation.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
} catch (InvalidPassphraseException e) {
Animations.createShakeWindowAnimation(window).play();
oldPasswordField.selectAll();
oldPasswordField.requestFocus();
} catch (IOException | CryptoException e) {
LOG.error("Password change failed. Unable to perform operation.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
}
}

View File

@@ -27,6 +27,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
@MainWindowScoped
public class MainWindowController implements FxController {
@@ -91,9 +92,9 @@ public class MainWindowController implements FxController {
}
private boolean containsVault(Path path) {
if (path.getFileName().toString().equals(MASTERKEY_FILENAME)) {
if (path.getFileName().toString().equals(VAULTCONFIG_FILENAME)) {
return true;
} else if (Files.isDirectory(path) && Files.exists(path.resolve(MASTERKEY_FILENAME))) {
} else if (Files.isDirectory(path) && Files.exists(path.resolve(VAULTCONFIG_FILENAME))) {
return true;
} else {
return false;
@@ -102,7 +103,7 @@ public class MainWindowController implements FxController {
private void addVault(Path pathToVault) {
try {
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
if (pathToVault.getFileName().toString().equals(VAULTCONFIG_FILENAME)) {
vaultListManager.add(pathToVault.getParent());
} else {
vaultListManager.add(pathToVault);

View File

@@ -43,6 +43,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
@MigrationScoped
public class MigrationRunController implements FxController {
@@ -110,8 +111,8 @@ public class MigrationRunController implements FxController {
}, 0, MIGRATION_PROGRESS_UPDATE_MILLIS, TimeUnit.MILLISECONDS);
Tasks.create(() -> {
Migrators migrators = Migrators.get();
migrators.migrate(vault.getPath(), MASTERKEY_FILENAME, password, this::migrationProgressChanged, this::migrationRequiresInput);
return migrators.needsMigration(vault.getPath(), MASTERKEY_FILENAME);
migrators.migrate(vault.getPath(), VAULTCONFIG_FILENAME, MASTERKEY_FILENAME, password, this::migrationProgressChanged, this::migrationRequiresInput);
return migrators.needsMigration(vault.getPath(), VAULTCONFIG_FILENAME, MASTERKEY_FILENAME);
}).onSuccess(needsAnotherMigration -> {
if (needsAnotherMigration) {
LOG.info("Migration of '{}' succeeded, but another migration is required.", vault.getDisplayName());

View File

@@ -2,6 +2,7 @@ package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
@@ -80,7 +81,7 @@ public class RecoveryKeyCreationController implements FxController {
}
@Override
protected String call() throws IOException {
protected String call() throws IOException, CryptoException {
return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters());
}

View File

@@ -2,28 +2,39 @@ package org.cryptomator.ui.recoverykey;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hashing;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.common.MasterkeyBackupHelper;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.common.MasterkeyFile;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.PEPPER;
@Singleton
public class RecoveryKeyFactory {
private static final byte[] PEPPER = new byte[0];
private static final String MASTERKEY_BACKUP_SUFFIX = ".bkup";
private final WordEncoder wordEncoder;
private final SecureRandom csprng;
@Inject
public RecoveryKeyFactory(WordEncoder wordEncoder) {
public RecoveryKeyFactory(WordEncoder wordEncoder, SecureRandom csprng) {
this.wordEncoder = wordEncoder;
this.csprng = csprng;
}
public Collection<String> getDictionary() {
@@ -36,11 +47,14 @@ public class RecoveryKeyFactory {
* @return The recovery key of the vault at the given path
* @throws IOException If the masterkey file could not be read
* @throws InvalidPassphraseException If the provided password is wrong
* @throws CryptoException In case of other cryptographic errors
* @apiNote This is a long-running operation and should be invoked in a background thread
*/
public String createRecoveryKey(Path vaultPath, CharSequence password) throws IOException, InvalidPassphraseException {
byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vaultPath, MASTERKEY_FILENAME, PEPPER, password);
try {
public String createRecoveryKey(Path vaultPath, CharSequence password) throws IOException, InvalidPassphraseException, CryptoException {
Path masterkeyPath = vaultPath.resolve(MASTERKEY_FILENAME);
byte[] rawKey = new byte[0];
try (var masterkey = MasterkeyFile.withContentFromFile(masterkeyPath).unlock(password, PEPPER, Optional.empty()).loadKeyAndClose()) {
rawKey = masterkey.getEncoded();
return createRecoveryKey(rawKey);
} finally {
Arrays.fill(rawKey, (byte) 0x00);
@@ -72,8 +86,15 @@ public class RecoveryKeyFactory {
*/
public void resetPasswordWithRecoveryKey(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException {
final byte[] rawKey = decodeRecoveryKey(recoveryKey);
try {
CryptoFileSystemProvider.restoreRawKey(vaultPath, MASTERKEY_FILENAME, rawKey, PEPPER, newPassword);
try (var masterkey = Masterkey.createFromRaw(rawKey)) {
byte[] restoredKey = MasterkeyFile.lock(masterkey, newPassword, PEPPER, 999, csprng);
Path masterkeyPath = vaultPath.resolve(MASTERKEY_FILENAME);
if (Files.exists(masterkeyPath)) {
byte[] oldMasterkeyBytes = Files.readAllBytes(masterkeyPath);
Path backupKeyPath = vaultPath.resolve(MASTERKEY_FILENAME + MasterkeyBackupHelper.generateFileIdSuffix(oldMasterkeyBytes) + MASTERKEY_BACKUP_SUFFIX);
Files.move(masterkeyPath, backupKeyPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
}
Files.write(masterkeyPath, restoredKey, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
} finally {
Arrays.fill(rawKey, (byte) 0x00);
}

View File

@@ -7,6 +7,7 @@ import org.cryptomator.common.vaults.MountPointRequirement;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume.VolumeException;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
@@ -85,7 +86,7 @@ public class UnlockWorkflow extends Task<Boolean> {
}
@Override
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException {
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException, CryptoException {
try {
if (attemptUnlock()) {
handleSuccess();
@@ -100,7 +101,7 @@ public class UnlockWorkflow extends Task<Boolean> {
}
}
private boolean attemptUnlock() throws InterruptedException, IOException, VolumeException, InvalidMountPointException {
private boolean attemptUnlock() throws InterruptedException, IOException, VolumeException, InvalidMountPointException, CryptoException {
boolean proceed = password.get() != null || askForPassword(false) == PasswordEntry.PASSWORD_ENTERED;
while (proceed) {
try {

View File

@@ -2,23 +2,40 @@ package org.cryptomator.ui.recoverykey;
import com.google.common.base.Splitter;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.common.MasterkeyFile;
import org.cryptomator.cryptolib.common.MasterkeyFileLoader;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import java.io.IOException;
import java.nio.file.Path;
import java.security.SecureRandom;
class RecoveryKeyFactoryTest {
private WordEncoder wordEncoder = new WordEncoder();
private RecoveryKeyFactory inTest = new RecoveryKeyFactory(wordEncoder);
private SecureRandom csprng = Mockito.mock(SecureRandom.class);
private RecoveryKeyFactory inTest = new RecoveryKeyFactory(wordEncoder, csprng);
@Test
@DisplayName("createRecoveryKey() creates 44 words")
public void testCreateRecoveryKey(@TempDir Path pathToVault) throws IOException {
CryptoFileSystemProvider.initialize(pathToVault, "masterkey.cryptomator", "asd");
public void testCreateRecoveryKey() throws IOException, CryptoException {
Path pathToVault = Path.of("path/to/vault");
MockedStatic<MasterkeyFile> masterkeyFileClass = Mockito.mockStatic(MasterkeyFile.class);
MasterkeyFile masterkeyFile = Mockito.mock(MasterkeyFile.class);
MasterkeyFileLoader keyLoader = Mockito.mock(MasterkeyFileLoader.class);
Masterkey masterkey = Mockito.mock(Masterkey.class);
masterkeyFileClass.when(() -> MasterkeyFile.withContentFromFile(Path.of("path/to/vault/masterkey.cryptomator"))).thenReturn(masterkeyFile);
Mockito.when(masterkeyFile.unlock(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(keyLoader);
Mockito.when(keyLoader.loadKeyAndClose()).thenReturn(masterkey);
Mockito.when(masterkey.getEncoded()).thenReturn(new byte[64]);
String recoveryKey = inTest.createRecoveryKey(pathToVault, "asd");
Assertions.assertNotNull(recoveryKey);
Assertions.assertEquals(44, Splitter.on(' ').splitToList(recoveryKey).size()); // 66 bytes encoded as 44 words

View File

@@ -0,0 +1 @@
mock-maker-inline