|
@@ -4,15 +4,20 @@ import javafx.concurrent.Task;
|
|
|
import org.cryptomator.common.vaults.Vault;
|
|
|
import org.cryptomator.common.vaults.VaultState;
|
|
|
import org.cryptomator.common.vaults.Volume;
|
|
|
+import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
|
|
+import org.cryptomator.keychain.KeychainAccess;
|
|
|
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
import javax.inject.Inject;
|
|
|
+import java.nio.CharBuffer;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
import java.util.Collection;
|
|
|
import java.util.Iterator;
|
|
|
import java.util.List;
|
|
|
+import java.util.Optional;
|
|
|
import java.util.concurrent.ExecutionException;
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
import java.util.stream.Collectors;
|
|
@@ -23,10 +28,12 @@ public class VaultService {
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
|
|
|
|
|
|
private final ExecutorService executorService;
|
|
|
+ private final Optional<KeychainAccess> keychain;
|
|
|
|
|
|
@Inject
|
|
|
- public VaultService(ExecutorService executorService) {
|
|
|
+ public VaultService(ExecutorService executorService, Optional<KeychainAccess> keychain) {
|
|
|
this.executorService = executorService;
|
|
|
+ this.keychain = keychain;
|
|
|
}
|
|
|
|
|
|
public void reveal(Vault vault) {
|
|
@@ -45,6 +52,72 @@ public class VaultService {
|
|
|
return task;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Attempts to unlock all given vaults in a background thread using passwords stored in the system keychain.
|
|
|
+ *
|
|
|
+ * @param vaults The vaults to unlock
|
|
|
+ * @implNote No-op if no system keychain is present
|
|
|
+ */
|
|
|
+ public void attemptAutoUnlock(Collection<Vault> vaults) {
|
|
|
+ if (!keychain.isPresent()) {
|
|
|
+ LOG.debug("No system keychain found. Unable to auto unlock without saved passwords.");
|
|
|
+ } else {
|
|
|
+ for (Vault vault : vaults) {
|
|
|
+ attemptAutoUnlock(vault, keychain.get());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Unlocks a vault in a background thread using a stored passphrase
|
|
|
+ *
|
|
|
+ * @param vault The vault to unlock
|
|
|
+ * @param keychainAccess The system keychain holding the passphrase for the vault
|
|
|
+ */
|
|
|
+ public void attemptAutoUnlock(Vault vault, KeychainAccess keychainAccess) {
|
|
|
+ executorService.execute(createAutoUnlockTask(vault, keychainAccess));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates but doesn't start an auto-unlock task.
|
|
|
+ *
|
|
|
+ * @param vault The vault to unlock
|
|
|
+ * @param keychainAccess The system keychain holding the passphrase for the vault
|
|
|
+ * @return The task
|
|
|
+ */
|
|
|
+ public Task<Vault> createAutoUnlockTask(Vault vault, KeychainAccess keychainAccess) {
|
|
|
+ Task<Vault> task = new AutoUnlockVaultTask(vault, keychainAccess);
|
|
|
+ task.setOnSucceeded(evt -> LOG.info("Auto-unlocked {}", vault.getDisplayableName()));
|
|
|
+ task.setOnFailed(evt -> LOG.error("Failed to auto-unlock " + vault.getDisplayableName(), evt.getSource().getException()));
|
|
|
+ return task;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Unlocks a vault in a background thread
|
|
|
+ *
|
|
|
+ * @param vault The vault to unlock
|
|
|
+ * @param passphrase The password to use - wipe this param asap
|
|
|
+ * @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
|
|
+ */
|
|
|
+ public void unlock(Vault vault, CharSequence passphrase) {
|
|
|
+ executorService.execute(createUnlockTask(vault, passphrase));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates but doesn't start an unlock task.
|
|
|
+ *
|
|
|
+ * @param vault The vault to unlock
|
|
|
+ * @param passphrase The password to use - wipe this param asap
|
|
|
+ * @return The task
|
|
|
+ * @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
|
|
+ */
|
|
|
+ public Task<Vault> createUnlockTask(Vault vault, CharSequence passphrase) {
|
|
|
+ Task<Vault> task = new UnlockVaultTask(vault, passphrase);
|
|
|
+ task.setOnSucceeded(evt -> LOG.info("Unlocked {}", vault.getDisplayableName()));
|
|
|
+ task.setOnFailed(evt -> LOG.error("Failed to unlock " + vault.getDisplayableName(), evt.getSource().getException()));
|
|
|
+ return task;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Locks a vault in a background thread.
|
|
|
*
|
|
@@ -60,6 +133,7 @@ public class VaultService {
|
|
|
*
|
|
|
* @param vault The vault to lock
|
|
|
* @param forced Whether to attempt a forced lock
|
|
|
+ * @return The task
|
|
|
*/
|
|
|
public Task<Vault> createLockTask(Vault vault, boolean forced) {
|
|
|
Task<Vault> task = new LockVaultTask(vault, forced);
|
|
@@ -145,6 +219,93 @@ public class VaultService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private static class AutoUnlockVaultTask extends Task<Vault> {
|
|
|
+
|
|
|
+ private final Vault vault;
|
|
|
+ private final KeychainAccess keychain;
|
|
|
+
|
|
|
+ public AutoUnlockVaultTask(Vault vault, KeychainAccess keychain) {
|
|
|
+ this.vault = vault;
|
|
|
+ this.keychain = keychain;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected Vault call() throws Exception {
|
|
|
+ char[] storedPw = null;
|
|
|
+ try {
|
|
|
+ storedPw = keychain.loadPassphrase(vault.getId());
|
|
|
+ if (storedPw == null) {
|
|
|
+ throw new InvalidPassphraseException();
|
|
|
+ }
|
|
|
+ vault.unlock(CharBuffer.wrap(storedPw));
|
|
|
+ } finally {
|
|
|
+ if (storedPw != null) {
|
|
|
+ Arrays.fill(storedPw, ' ');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return vault;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void scheduled() {
|
|
|
+ vault.setState(VaultState.PROCESSING);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void succeeded() {
|
|
|
+ vault.setState(VaultState.UNLOCKED);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void failed() {
|
|
|
+ vault.setState(VaultState.LOCKED);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class UnlockVaultTask extends Task<Vault> {
|
|
|
+
|
|
|
+ private final Vault vault;
|
|
|
+ private final CharBuffer passphrase;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param vault The vault to unlock
|
|
|
+ * @param passphrase The password to use - wipe this param asap
|
|
|
+ * @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
|
|
+ */
|
|
|
+ public UnlockVaultTask(Vault vault, CharSequence passphrase) {
|
|
|
+ this.vault = vault;
|
|
|
+ this.passphrase = CharBuffer.allocate(passphrase.length());
|
|
|
+ for (int i = 0; i < passphrase.length(); i++) {
|
|
|
+ this.passphrase.put(i, passphrase.charAt(i));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected Vault call() throws Exception {
|
|
|
+ try {
|
|
|
+ vault.unlock(passphrase);
|
|
|
+ } finally {
|
|
|
+ Arrays.fill(passphrase.array(), ' ');
|
|
|
+ }
|
|
|
+ return vault;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void scheduled() {
|
|
|
+ vault.setState(VaultState.PROCESSING);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void succeeded() {
|
|
|
+ vault.setState(VaultState.UNLOCKED);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void failed() {
|
|
|
+ vault.setState(VaultState.LOCKED);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* A task that locks a vault
|
|
|
*/
|
|
@@ -186,5 +347,4 @@ public class VaultService {
|
|
|
}
|
|
|
|
|
|
|
|
|
-
|
|
|
}
|