Compare commits

..

47 Commits

Author SHA1 Message Date
Armin Schrenk
8e5035f506 preparing 1.6.0 2021-05-06 14:27:34 +02:00
Sebastian Stenzel
0ce41e7ac3 New Crowdin updates (#1628)
New translations strings.properties

Japanese; Polish; Spanish; Dutch; Czech; Norwegian Bokmal; Korean; Greek; Catalan; Slovak; Russian; Punjabi; Italian; Arabic; Bosnian; Hungarian; German; Cyrillic); Latin); Romanian; French; Chinese Simplified; Croatian; Chinese Traditional; Portuguese; Latvian; Hindi; Indonesian; Turkish; Portuguese, Brazilian; Swedish; Norwegian Nynorsk; 

[ci skip]
2021-05-06 14:25:59 +02:00
Armin Schrenk
c1a249fd7f Merge pull request #1454 from cryptomator/feature/vault-format-8
Vault Format 8
2021-05-06 14:05:26 +02:00
Armin Schrenk
c266c7583b bump dependencies (same as in cryptofs) 2021-05-06 13:59:37 +02:00
Armin Schrenk
00331d4857 check for vaultpath existence instead of catching (undocumented) exception 2021-05-06 13:51:30 +02:00
Armin Schrenk
8fd484e2bb Revert "Move window placements of "popup dialogs" to their corresponding controller"
This reverts commit bc83e23a34.
2021-05-06 13:44:05 +02:00
Armin Schrenk
165d740acd if vault directory is not present, declare it as missing 2021-05-06 12:51:39 +02:00
Armin Schrenk
fd4010c6c9 cleanup 2021-05-06 12:43:15 +02:00
Armin Schrenk
aa2900fa9e ensure to use cryptolib rc 2021-05-06 12:27:07 +02:00
Sebastian Stenzel
76d1875e01 adjusted to new CryptoFileSystemProvider.checkDirStructureForVault API 2021-05-06 09:56:50 +02:00
Armin Schrenk
bc83e23a34 Move window placements of "popup dialogs" to their corresponding controller 2021-05-05 15:03:09 +02:00
Armin Schrenk
6a326bcbce remove password of migration window, when window is closed 2021-05-04 10:51:12 +02:00
Armin Schrenk
573ad03d68 bug fixin' 2021-05-03 17:33:28 +02:00
Armin Schrenk
d710d406d0 Merge branch 'master' into develop 2021-04-23 10:30:12 +02:00
Sebastian Stenzel
34995088ba addressed some issues identified during code review 2021-04-23 10:24:31 +02:00
Sebastian Stenzel
69c63702f2 pass a single keyloader to cryptofs 2021-04-23 09:55:08 +02:00
Sebastian Stenzel
58b45bacd1 Merge branch 'develop' into feature/vault-format-8
[ci skip]
2021-04-21 13:27:54 +02:00
Sebastian Stenzel
4f942bc23f reset version
[ci skip]
2021-04-21 13:27:25 +02:00
Armin Schrenk
09a8618fa0 Merge tag '1.5.15' into develop
1.5.15
2021-04-21 12:26:46 +02:00
Sebastian Stenzel
59f91267ae fix save password 2021-04-21 11:20:44 +02:00
Sebastian Stenzel
b3ff9423b4 Merge branch 'develop' into feature/vault-format-8
# Conflicts:
#	main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java
#	main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java
#	main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java
2021-04-21 11:20:31 +02:00
Sebastian Stenzel
c654951dc1 removed ambiguous filenameLengthLimit from settings, added maxCleartextFilenameLength, shortening is now a 100% independent setting stored in the vault rather than the device settings 2021-04-20 18:15:25 +02:00
Sebastian Stenzel
2810c044ea reduced visibility 2021-03-31 16:23:54 +02:00
Sebastian Stenzel
e09bd160b7 Made masterkey loading strategies reusable 2021-03-31 16:12:31 +02:00
Sebastian Stenzel
e6d1e4697c Merge develop into feature/vault-format-8 2021-03-31 11:39:16 +02:00
Sebastian Stenzel
2cdde54db6 updated mockito due to build errors caused by bytebuddy 2021-03-17 13:14:55 +01:00
Sebastian Stenzel
6941603cdd update to latest cryptolib/cryptofs beta 2021-03-17 13:03:55 +01:00
Sebastian Stenzel
cdca4e047d Merge branch 'develop' into feature/vault-format-8 2021-03-17 12:55:47 +01:00
Sebastian Stenzel
e32ce22d24 Merge branch 'develop' into feature/vault-format-8 2021-03-10 12:48:58 +01:00
Armin Schrenk
e75c415b46 Load vault config on demand and don't cache it
* fixes issue with unlock after vault migration
2021-03-10 12:03:46 +01:00
Sebastian Stenzel
0ab28602d1 bumped cryptofs version 2021-03-05 17:33:58 +01:00
Sebastian Stenzel
62c8edff04 Choose key loading workflow depending on vaultconfig's key ID and allow KeyLoadingComponent to decide itself, what exceptions it can handle 2021-03-03 17:41:17 +01:00
Sebastian Stenzel
d01c6268f8 remove unused field
[ci skip]
2021-03-02 16:16:24 +01:00
Sebastian Stenzel
557aaa2480 Removed unused imports
[ci skip]
2021-03-01 15:59:13 +01:00
Sebastian Stenzel
78e43d401d split package org.cryptoamtor.ui.unlock to allow for different kinds of unlock workflows 2021-03-01 15:58:00 +01:00
Sebastian Stenzel
7b08c5d287 Merge branch 'develop' into feature/vault-format-8
# Conflicts:
#	main/pom.xml
#	main/ui/src/main/resources/license/THIRD-PARTY.txt
[ci skip]
2021-02-26 10:10:50 +01:00
Sebastian Stenzel
9ad217ca55 Merge branch 'develop' into feature/vault-format-8
[ci skip]
2021-02-04 12:38:01 +01:00
Sebastian Stenzel
1a0bc92bde Merge branch 'develop' into feature/vault-format-8 2021-02-01 12:08:23 +01:00
Sebastian Stenzel
21038212c1 remove unused property
[ci skip]
2021-02-01 11:54:21 +01:00
Sebastian Stenzel
b15471b4ff add new (optional) "choose masterkey file" step to unlock dialog 2021-01-29 17:44:45 +01:00
Sebastian Stenzel
ff17b60f56 Refactored UnlockWorkflow using new vault format 8 APIs 2021-01-27 12:09:09 +01:00
Sebastian Stenzel
3284578445 Merge branch 'develop' into feature/vault-format-8
# Conflicts:
#	main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java
2021-01-25 21:42:13 +01:00
Sebastian Stenzel
4b670a59a3 adjusted to new cryptolib/cryptofs API 2021-01-25 21:31:16 +01:00
Sebastian Stenzel
3f928cf958 Merge branch 'develop' into feature/vault-format-8
[ci skip]
2021-01-22 22:47:54 +01:00
Sebastian Stenzel
efebbc059a keep CTR+HMAC for now (until GCM is supported on all platforms) 2021-01-19 15:09:47 +01:00
Sebastian Stenzel
6e860d293a Merge branch 'develop' into feature/vault-format-8
[ci skip]
2021-01-19 15:05:15 +01:00
Sebastian Stenzel
c0a9a95e4f Adjusted to CryptoFS 2.0.0 2020-12-08 14:39:46 +01:00
74 changed files with 1519 additions and 346 deletions

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.18</version>
<version>1.6.0</version>
</parent>
<artifactId>buildkit</artifactId>
<packaging>pom</packaging>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.18</version>
<version>1.6.0</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator Commons</name>

View File

@@ -15,6 +15,7 @@ import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,6 +26,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;
@@ -55,6 +58,22 @@ public abstract class CommonsModule {
""";
}
@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
static MasterkeyFileAccess provideMasterkeyFileAccess(SecureRandom csprng) {
return new MasterkeyFileAccess(Constants.PEPPER, csprng);
}
@Provides
@Singleton
@Named("SemVer")

View File

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

View File

@@ -24,8 +24,6 @@ import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
/**
* The settings specific to a single vault.
@@ -37,7 +35,7 @@ public class VaultSettings {
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
public static final boolean DEFAULT_USES_READONLY_MODE = false;
public static final String DEFAULT_MOUNT_FLAGS = "";
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
private static final Random RNG = new Random();
@@ -52,7 +50,7 @@ public class VaultSettings {
private final StringProperty customMountPath = new SimpleStringProperty();
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
private final IntegerProperty filenameLengthLimit = new SimpleIntegerProperty(DEFAULT_FILENAME_LENGTH_LIMIT);
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
private final StringBinding mountName;
@@ -63,7 +61,7 @@ public class VaultSettings {
}
Observable[] observables() {
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, maxCleartextFilenameLength, actionAfterUnlock};
}
public static VaultSettings withRandomId() {
@@ -152,8 +150,8 @@ public class VaultSettings {
return mountFlags;
}
public IntegerProperty filenameLengthLimit() {
return filenameLengthLimit;
public IntegerProperty maxCleartextFilenameLength() {
return maxCleartextFilenameLength;
}
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {

View File

@@ -29,7 +29,7 @@ class VaultSettingsJsonAdapter {
out.name("customMountPath").value(value.customMountPath().get());
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
out.name("mountFlags").value(value.mountFlags().get());
out.name("filenameLengthLimit").value(value.filenameLengthLimit().get());
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
out.endObject();
}
@@ -46,7 +46,7 @@ class VaultSettingsJsonAdapter {
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
int filenameLengthLimit = VaultSettings.DEFAULT_FILENAME_LENGTH_LIMIT;
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
in.beginObject();
@@ -64,7 +64,7 @@ class VaultSettingsJsonAdapter {
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
case "mountFlags" -> mountFlags = in.nextString();
case "filenameLengthLimit" -> filenameLengthLimit = in.nextInt();
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
default -> {
LOG.warn("Unsupported vault setting found in JSON: " + name);
@@ -88,7 +88,7 @@ class VaultSettingsJsonAdapter {
vaultSettings.customMountPath().set(customMountPath);
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
vaultSettings.mountFlags().set(mountFlags);
vaultSettings.filenameLengthLimit().set(filenameLengthLimit);
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
return vaultSettings;
}

View File

@@ -17,10 +17,12 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,7 +37,8 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
@@ -45,13 +48,12 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@PerVault
public class Vault {
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
private final VaultSettings vaultSettings;
private final Provider<Volume> volumeProvider;
@@ -100,32 +102,31 @@ public class Vault {
// Commands
// ********************************************************************************/
private CryptoFileSystem createCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException {
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
if (vaultSettings.usesReadOnlyMode().get()) {
flags.add(FileSystemFlags.READONLY);
} else if(vaultSettings.maxCleartextFilenameLength().get() == -1) {
LOG.debug("Determining cleartext filename length limitations...");
var checker = new FileSystemCapabilityChecker();
int shorteningThreshold = getUnverifiedVaultConfig().orElseThrow().allegedShorteningThreshold();
int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath());
if (ciphertextLimit < shorteningThreshold) {
int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath());
vaultSettings.maxCleartextFilenameLength().set(cleartextLimit);
} else {
vaultSettings.maxCleartextFilenameLength().setValue(UNLIMITED_FILENAME_LENGTH);
}
}
int usedFilenameLengthLimit;
var fileSystemCapabilityChecker = new FileSystemCapabilityChecker();
if (flags.contains(FileSystemFlags.READONLY)) {
usedFilenameLengthLimit = Constants.MAX_CIPHERTEXT_NAME_LENGTH;
} else if (vaultSettings.filenameLengthLimit().get() == -1) {
LOG.debug("Determining file name length limitations...");
usedFilenameLengthLimit = fileSystemCapabilityChecker.determineSupportedFileNameLength(getPath());
vaultSettings.filenameLengthLimit().set(usedFilenameLengthLimit);
LOG.info("Storing file name length limit of {}", usedFilenameLengthLimit);
} else {
usedFilenameLengthLimit = vaultSettings.filenameLengthLimit().get();
if (vaultSettings.maxCleartextFilenameLength().get() < UNLIMITED_FILENAME_LENGTH) {
LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength().get());
}
assert usedFilenameLengthLimit > 0;
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withPassphrase(passphrase) //
.withKeyLoader(keyLoader) //
.withFlags(flags) //
.withMasterkeyFilename(MASTERKEY_FILENAME) //
.withMaxPathLength(vaultSettings.filenameLengthLimit().get() + Constants.MAX_ADDITIONAL_PATH_LENGTH) //
.withMaxNameLength(usedFilenameLengthLimit) //
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
}
@@ -142,20 +143,22 @@ public class Vault {
}
}
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
if (cryptoFileSystem.get() == null) {
CryptoFileSystem fs = createCryptoFileSystem(passphrase);
cryptoFileSystem.set(fs);
try {
volume = volumeProvider.get();
volume.mount(fs, getEffectiveMountFlags(), this::lockOnVolumeExit);
} catch (Exception e) {
destroyCryptoFileSystem();
throw e;
}
} else {
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
if (cryptoFileSystem.get() != null) {
throw new IllegalStateException("Already unlocked.");
}
CryptoFileSystem fs = createCryptoFileSystem(keyLoader);
boolean success = false;
try {
cryptoFileSystem.set(fs);
volume = volumeProvider.get();
volume.mount(fs, getEffectiveMountFlags(), this::lockOnVolumeExit);
success = true;
} finally {
if (!success) {
destroyCryptoFileSystem();
}
}
}
private void lockOnVolumeExit(Throwable t) {
@@ -324,6 +327,16 @@ public class Vault {
return stats;
}
public Optional<UnverifiedVaultConfig> getUnverifiedVaultConfig() {
Path configPath = getPath().resolve(org.cryptomator.common.Constants.VAULTCONFIG_FILENAME);
try {
String token = Files.readString(configPath, StandardCharsets.US_ASCII);
return Optional.of(VaultConfig.decode(token));
} catch (IOException e) {
return Optional.empty();
}
}
public Observable[] observables() {
return new Observable[]{state};
}

View File

@@ -11,6 +11,7 @@ package org.cryptomator.common.vaults;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.DirStructure;
import org.cryptomator.cryptofs.migration.Migrators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,6 +21,7 @@ import javax.inject.Singleton;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collection;
@@ -27,6 +29,8 @@ import java.util.Optional;
import java.util.ResourceBundle;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
@Singleton
public class VaultListManager {
@@ -51,19 +55,18 @@ public class VaultListManager {
return vaultList;
}
public Vault add(Path pathToVault) throws NoSuchFileException {
public Vault add(Path pathToVault) throws IOException {
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
if (!CryptoFileSystemProvider.containsVault(normalizedPathToVault, MASTERKEY_FILENAME)) {
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
throw new NoSuchFileException(normalizedPathToVault.toString(), null, "Not a vault directory");
}
Optional<Vault> alreadyExistingVault = get(normalizedPathToVault);
if (alreadyExistingVault.isPresent()) {
return alreadyExistingVault.get();
} else {
Vault newVault = create(newVaultSettings(normalizedPathToVault));
vaultList.add(newVault);
return newVault;
}
return get(normalizedPathToVault) //
.orElseGet(() -> {
Vault newVault = create(newVaultSettings(normalizedPathToVault));
vaultList.add(newVault);
return newVault;
});
}
private VaultSettings newVaultSettings(Path path) {
@@ -97,7 +100,7 @@ public class VaultListManager {
compBuilder.initialVaultState(vaultState);
} catch (IOException e) {
LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e);
compBuilder.initialVaultState(VaultState.Value.ERROR);
compBuilder.initialVaultState(ERROR);
compBuilder.initialErrorCause(e);
}
return compBuilder.build().vault();
@@ -109,14 +112,14 @@ public class VaultListManager {
return switch (previousState) {
case LOCKED, NEEDS_MIGRATION, MISSING -> {
try {
VaultState.Value determinedState = determineVaultState(vault.getPath());
var determinedState = determineVaultState(vault.getPath());
state.set(determinedState);
yield determinedState;
} catch (IOException e) {
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
state.set(VaultState.Value.ERROR);
state.set(ERROR);
vault.setLastKnownException(e);
yield VaultState.Value.ERROR;
yield ERROR;
}
}
case ERROR, UNLOCKED, PROCESSING -> previousState;
@@ -124,13 +127,14 @@ public class VaultListManager {
}
private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
if (!Files.exists(pathToVault)) {
return VaultState.Value.MISSING;
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
return VaultState.Value.NEEDS_MIGRATION;
} else {
return VaultState.Value.LOCKED;
}
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
case VAULT -> VaultState.Value.LOCKED;
case UNRELATED -> VaultState.Value.MISSING;
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? VaultState.Value.NEEDS_MIGRATION : VaultState.Value.MISSING;
};
}
}

View File

@@ -47,7 +47,6 @@ public class VaultModule {
return new SimpleObjectProperty<>(initialErrorCause);
}
@Provides
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.18</version>
<version>1.6.0</version>
</parent>
<artifactId>launcher</artifactId>
<name>Cryptomator Launcher</name>

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.18</version>
<version>1.6.0</version>
<packaging>pom</packaging>
<name>Cryptomator</name>
@@ -25,29 +25,29 @@
<project.jdk.version>16</project.jdk.version>
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>1.9.15</cryptomator.cryptofs.version>
<cryptomator.cryptofs.version>2.0.0-rc2</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.0.0-beta2</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.0.0-beta2</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.0.0-beta2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.0.0-beta1</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>1.3.2</cryptomator.fuse.version>
<cryptomator.dokany.version>1.3.2</cryptomator.dokany.version>
<cryptomator.webdav.version>1.2.5</cryptomator.webdav.version>
<cryptomator.fuse.version>1.3.1</cryptomator.fuse.version>
<cryptomator.dokany.version>1.3.1</cryptomator.dokany.version>
<cryptomator.webdav.version>1.2.0</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<javafx.version>16</javafx.version>
<commons-lang3.version>3.11</commons-lang3.version>
<jwt.version>3.13.0</jwt.version>
<jwt.version>3.15.0</jwt.version>
<easybind.version>2.1.0</easybind.version>
<guava.version>30.0-jre</guava.version>
<dagger.version>2.32</dagger.version>
<guava.version>30.1.1-jre</guava.version>
<dagger.version>2.35.1</dagger.version>
<gson.version>2.8.6</gson.version>
<slf4j.version>1.7.30</slf4j.version>
<logback.version>1.2.3</logback.version>
<!-- test dependencies -->
<junit.jupiter.version>5.7.0</junit.jupiter.version>
<mockito.version>3.6.0</mockito.version>
<junit.jupiter.version>5.7.1</junit.jupiter.version>
<mockito.version>3.9.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
</properties>
@@ -76,6 +76,12 @@
<artifactId>cryptofs</artifactId>
<version>${cryptomator.cryptofs.version}</version>
</dependency>
<!--TODO: only temporary workaround until 1.6.0-beta -->
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>cryptolib</artifactId>
<version>2.0.0-rc1</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>fuse-nio-adapter</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.18</version>
<version>1.6.0</version>
</parent>
<artifactId>ui</artifactId>
<name>Cryptomator GUI</name>

View File

@@ -81,7 +81,7 @@ public class ChooseExistingVaultController implements FxController {
Vault newVault = vaultListManager.add(vaultPath.get());
vault.set(newVault);
window.setScene(successScene.get());
} catch (NoSuchFileException e) {
} catch (IOException e) {
LOG.error("Failed to open existing vault.", e);
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
}

View File

@@ -5,11 +5,17 @@ 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.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.Tasks;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,13 +37,14 @@ import javafx.scene.control.ToggleGroup;
import javafx.stage.Stage;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.channels.WritableByteChannel;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.security.SecureRandom;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
@@ -48,6 +55,7 @@ import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
public class CreateNewVaultPasswordController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultPasswordController.class);
private static final URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME); // TODO better place?
private final Stage window;
private final Lazy<Scene> chooseLocationScene;
@@ -64,6 +72,8 @@ public class CreateNewVaultPasswordController implements FxController {
private final ResourceBundle resourceBundle;
private final ObjectProperty<CharSequence> password;
private final ReadmeGenerator readmeGenerator;
private final SecureRandom csprng;
private final MasterkeyFileAccess masterkeyFileAccess;
private final BooleanProperty processing;
private final BooleanProperty readyToCreateVault;
private final ObjectBinding<ContentDisplay> createVaultButtonState;
@@ -73,7 +83,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, MasterkeyFileAccess masterkeyFileAccess) {
this.window = window;
this.chooseLocationScene = chooseLocationScene;
this.recoveryKeyScene = recoveryKeyScene;
@@ -89,6 +99,8 @@ public class CreateNewVaultPasswordController implements FxController {
this.resourceBundle = resourceBundle;
this.password = password;
this.readmeGenerator = readmeGenerator;
this.csprng = csprng;
this.masterkeyFileAccess = masterkeyFileAccess;
this.processing = new SimpleBooleanProperty();
this.readyToCreateVault = new SimpleBooleanProperty();
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
@@ -161,23 +173,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();
// 1. write masterkey:
Path masterkeyFilePath = path.resolve(MASTERKEY_FILENAME);
try (Masterkey masterkey = Masterkey.generate(csprng)) {
masterkeyFileAccess.persist(masterkey, masterkeyFilePath, passphrase);
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()));
// 2. initialize vault:
try {
MasterkeyLoader loader = ignored -> masterkey.clone();
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(VaultCipherCombo.SIV_CTRMAC).withKeyLoader(loader).build();
CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_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);
}
@@ -185,7 +208,7 @@ public class CreateNewVaultPasswordController implements FxController {
try {
Vault newVault = vaultListManager.add(pathToVault);
vaultProperty.set(newVault);
} catch (NoSuchFileException e) {
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

View File

@@ -2,8 +2,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.MasterkeyFileAccess;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.ErrorComponent;
@@ -23,8 +25,13 @@ import javafx.scene.control.CheckBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Optional;
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 static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@ChangePasswordScoped
@@ -37,18 +44,22 @@ public class ChangePasswordController implements FxController {
private final ObjectProperty<CharSequence> newPassword;
private final ErrorComponent.Builder errorComponent;
private final KeychainManager keychain;
private final SecureRandom csprng;
private final MasterkeyFileAccess masterkeyFileAccess;
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, MasterkeyFileAccess masterkeyFileAccess) {
this.window = window;
this.vault = vault;
this.newPassword = newPassword;
this.errorComponent = errorComponent;
this.keychain = keychain;
this.csprng = csprng;
this.masterkeyFileAccess = masterkeyFileAccess;
}
@FXML
@@ -67,17 +78,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 = masterkeyFileAccess.changePassphrase(oldMasterkeyBytes, oldPassphrase, newPassphrase);
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

@@ -26,8 +26,9 @@ public enum FxmlFile {
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
UNLOCK("/fxml/unlock.fxml"),
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), //
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
VAULT_OPTIONS("/fxml/vault_options.fxml"), //
VAULT_STATISTICS("/fxml/stats.fxml"), //

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.keyloading;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface KeyLoading {
}

View File

@@ -0,0 +1,31 @@
package org.cryptomator.ui.keyloading;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import javafx.stage.Stage;
import java.util.Map;
import java.util.function.Supplier;
@KeyLoadingScoped
@Subcomponent(modules = {KeyLoadingModule.class})
public interface KeyLoadingComponent {
@KeyLoading
KeyLoadingStrategy keyloadingStrategy();
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder vault(@KeyLoading Vault vault);
@BindsInstance
Builder window(@KeyLoading Stage window);
KeyLoadingComponent build();
}
}

View File

@@ -0,0 +1,48 @@
package org.cryptomator.ui.keyloading;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule;
import javax.inject.Provider;
import java.net.URI;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@Module(includes = {MasterkeyFileLoadingModule.class})
abstract class KeyLoadingModule {
@Provides
@KeyLoading
@KeyLoadingScoped
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@KeyLoading
@KeyLoadingScoped
static Optional<URI> provideKeyId(@KeyLoading Vault vault) {
return vault.getUnverifiedVaultConfig().map(UnverifiedVaultConfig::getKeyId);
}
@Provides
@KeyLoading
@KeyLoadingScoped
static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Optional<URI> keyId, Map<String, Provider<KeyLoadingStrategy>> strategies) {
if (keyId.isEmpty()) {
return KeyLoadingStrategy.failed(new IllegalArgumentException("No key id provided"));
} else {
String scheme = keyId.get().getScheme();
var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));
return strategies.getOrDefault(scheme, () -> fallback).get();
}
}
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.keyloading;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface KeyLoadingScoped {
}

View File

@@ -0,0 +1,63 @@
package org.cryptomator.ui.keyloading;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import java.net.URI;
/**
* A reusable, stateful {@link MasterkeyLoader}, that can deal with certain exceptions.
*/
@FunctionalInterface
public interface KeyLoadingStrategy extends MasterkeyLoader {
/**
* Loads a master key. This might be a long-running operation, as it may require user input or expensive computations.
* <p>
* If loading fails exceptionally, this strategy might be able to {@link #recoverFromException(MasterkeyLoadingFailedException) recover from this exception}, so it can be used in a further attempt.
*
* @param keyId An URI uniquely identifying the source and identity of the key
* @return The raw key bytes. Must not be null
* @throws MasterkeyLoadingFailedException Thrown when it is impossible to fulfill the request
*/
@Override
Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException;
/**
* Allows the loader to try and recover from an exception thrown during the last attempt.
*
* @param exception An exception thrown by {@link #loadKey(URI)}.
* @return <code>true</code> if this component was able to handle the exception and another attempt can be made to load a masterkey
*/
default boolean recoverFromException(MasterkeyLoadingFailedException exception) {
return false;
}
/**
* Release any ressources or do follow-up tasks after loading a key.
*
* @param unlockedSuccessfully <code>true</code> if successfully unlocked a vault with the loaded key
* @implNote This method might be invoked multiple times, depending on whether multiple attempts to load a key are started.
*/
default void cleanup(boolean unlockedSuccessfully) {
// no-op
}
/**
* A key loading strategy that will always fail by throwing a {@link MasterkeyLoadingFailedException}.
*
* @param exception The cause of the failure. If not alreay an {@link MasterkeyLoadingFailedException}, it will get wrapped.
* @return A new KeyLoadingStrategy that will always fail with an {@link MasterkeyLoadingFailedException}.
*/
static KeyLoadingStrategy failed(Exception exception) {
return keyid -> {
if (exception instanceof MasterkeyLoadingFailedException e) {
throw e;
} else {
throw new MasterkeyLoadingFailedException("Can not load key", exception);
}
};
}
}

View File

@@ -0,0 +1,62 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
class MasterkeyFileLoadingFinisher {
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingFinisher.class);
private final Vault vault;
private final Optional<char[]> storedPassword;
private final AtomicReference<char[]> enteredPassword;
private final AtomicBoolean shouldSavePassword;
private final KeychainManager keychain;
@Inject
MasterkeyFileLoadingFinisher(@KeyLoading Vault vault, @Named("savedPassword") Optional<char[]> storedPassword, AtomicReference<char[]> enteredPassword, @Named("savePassword") AtomicBoolean shouldSavePassword, KeychainManager keychain) {
this.vault = vault;
this.storedPassword = storedPassword;
this.enteredPassword = enteredPassword;
this.shouldSavePassword = shouldSavePassword;
this.keychain = keychain;
}
public void cleanup(boolean successfullyUnlocked) {
if (successfullyUnlocked && shouldSavePassword.get()) {
savePasswordToSystemkeychain();
}
wipePassword(storedPassword.orElse(null));
wipePassword(enteredPassword.getAndSet(null));
}
private void savePasswordToSystemkeychain() {
if (keychain.isSupported()) {
try {
keychain.storePassphrase(vault.getId(), CharBuffer.wrap(enteredPassword.get()));
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
}
}
private void wipePassword(char[] pw) {
if (pw != null) {
Arrays.fill(pw, ' ');
}
}
}

View File

@@ -0,0 +1,123 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javafx.scene.Scene;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@Module(subcomponents = {ForgetPasswordComponent.class})
public abstract class MasterkeyFileLoadingModule {
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class);
public enum PasswordEntry {
PASSWORD_ENTERED,
CANCELED
}
public enum MasterkeyFileProvision {
MASTERKEYFILE_PROVIDED,
CANCELED
}
@Provides
@KeyLoadingScoped
static UserInteractionLock<PasswordEntry> providePasswordEntryLock() {
return new UserInteractionLock<>(null);
}
@Provides
@KeyLoadingScoped
static UserInteractionLock<MasterkeyFileProvision> provideMasterkeyFileProvisionLock() {
return new UserInteractionLock<>(null);
}
@Provides
@Named("savedPassword")
@KeyLoadingScoped
static Optional<char[]> provideStoredPassword(KeychainManager keychain, @KeyLoading Vault vault) {
if (!keychain.isSupported()) {
return Optional.empty();
} else {
try {
return Optional.ofNullable(keychain.loadPassphrase(vault.getId()));
} catch (KeychainAccessException e) {
LOG.error("Failed to load entry from system keychain.", e);
return Optional.empty();
}
}
}
@Provides
@KeyLoadingScoped
static AtomicReference<Path> provideUserProvidedMasterkeyPath() {
return new AtomicReference<>();
}
@Provides
@KeyLoadingScoped
static AtomicReference<char[]> providePassword(@Named("savedPassword") Optional<char[]> storedPassword) {
return new AtomicReference<>(storedPassword.orElse(null));
}
@Provides
@Named("savePassword")
@KeyLoadingScoped
static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional<char[]> storedPassword) {
return new AtomicBoolean(storedPassword.isPresent());
}
@Provides
@FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD)
@KeyLoadingScoped
static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD);
}
@Provides
@FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE)
@KeyLoadingScoped
static Scene provideUnlockSelectMasterkeyFileScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE);
}
@Binds
@IntoMap
@FxControllerKey(PassphraseEntryController.class)
abstract FxController bindUnlockController(PassphraseEntryController controller);
@Binds
@IntoMap
@FxControllerKey(SelectMasterkeyFileController.class)
abstract FxController bindUnlockSelectMasterkeyFileController(SelectMasterkeyFileController controller);
@Binds
@IntoMap
@KeyLoadingScoped
@StringKey(MasterkeyFileLoadingStrategy.SCHEME)
abstract KeyLoadingStrategy bindMasterkeyFileLoadingStrategy(MasterkeyFileLoadingStrategy strategy);
}

View File

@@ -0,0 +1,150 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import com.google.common.base.Preconditions;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.unlock.UnlockCancelledException;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoading
public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
public static final String SCHEME = "masterkeyfile";
private final Vault vault;
private final MasterkeyFileAccess masterkeyFileAcccess;
private final Stage window;
private final Lazy<Scene> passphraseEntryScene;
private final Lazy<Scene> selectMasterkeyFileScene;
private final UserInteractionLock<MasterkeyFileLoadingModule.PasswordEntry> passwordEntryLock;
private final UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock;
private final AtomicReference<char[]> password;
private final AtomicReference<Path> filePath;
private final MasterkeyFileLoadingFinisher finisher;
private boolean wrongPassword;
@Inject
public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAcccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy<Scene> passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy<Scene> selectMasterkeyFileScene, UserInteractionLock<MasterkeyFileLoadingModule.PasswordEntry> passwordEntryLock, UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock, AtomicReference<char[]> password, AtomicReference<Path> filePath, MasterkeyFileLoadingFinisher finisher) {
this.vault = vault;
this.masterkeyFileAcccess = masterkeyFileAcccess;
this.window = window;
this.passphraseEntryScene = passphraseEntryScene;
this.selectMasterkeyFileScene = selectMasterkeyFileScene;
this.passwordEntryLock = passwordEntryLock;
this.masterkeyFileProvisionLock = masterkeyFileProvisionLock;
this.password = password;
this.filePath = filePath;
this.finisher = finisher;
}
@Override
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
Preconditions.checkArgument(SCHEME.equalsIgnoreCase(keyId.getScheme()), "Only supports keys with scheme " + SCHEME);
try {
Path filePath = vault.getPath().resolve(keyId.getSchemeSpecificPart());
if (!Files.exists(filePath)) {
filePath = getAlternateMasterkeyFilePath();
}
CharSequence passphrase = getPassphrase();
return masterkeyFileAcccess.load(filePath, passphrase);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new UnlockCancelledException("Unlock interrupted", e);
}
}
@Override
public boolean recoverFromException(MasterkeyLoadingFailedException exception) {
if (exception instanceof InvalidPassphraseException) {
this.wrongPassword = true;
password.set(null);
return true; // reattempting key load
} else {
return false; // nothing we can do
}
}
@Override
public void cleanup(boolean unlockedSuccessfully) {
finisher.cleanup(unlockedSuccessfully);
}
private Path getAlternateMasterkeyFilePath() throws UnlockCancelledException, InterruptedException {
if (filePath == null) {
return switch (askUserForMasterkeyFilePath()) {
case MASTERKEYFILE_PROVIDED -> filePath.get();
case CANCELED -> throw new UnlockCancelledException("Choosing masterkey file cancelled.");
};
} else {
return filePath.get();
}
}
private MasterkeyFileLoadingModule.MasterkeyFileProvision askUserForMasterkeyFilePath() throws InterruptedException {
Platform.runLater(() -> {
window.setScene(selectMasterkeyFileScene.get());
window.show();
Window owner = window.getOwner();
if (owner != null) {
window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2);
window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2);
} else {
window.centerOnScreen();
}
});
return masterkeyFileProvisionLock.awaitInteraction();
}
private CharSequence getPassphrase() throws UnlockCancelledException, InterruptedException {
if (password.get() == null) {
return switch (askForPassphrase()) {
case PASSWORD_ENTERED -> CharBuffer.wrap(password.get());
case CANCELED -> throw new UnlockCancelledException("Password entry cancelled.");
};
} else {
// e.g. pre-filled from keychain or previous unlock attempt
return CharBuffer.wrap(password.get());
}
}
private MasterkeyFileLoadingModule.PasswordEntry askForPassphrase() throws InterruptedException {
Platform.runLater(() -> {
window.setScene(passphraseEntryScene.get());
window.show();
Window owner = window.getOwner();
if (owner != null) {
window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2);
window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2);
} else {
window.centerOnScreen();
}
if (wrongPassword) {
Animations.createShakeWindowAnimation(window).play();
}
});
return passwordEntryLock.awaitInteraction();
}
}

View File

@@ -1,4 +1,4 @@
package org.cryptomator.ui.unlock;
package org.cryptomator.ui.keyloading.masterkeyfile;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
@@ -7,6 +7,9 @@ import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.common.WeakBindings;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.PasswordEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,17 +41,17 @@ import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@UnlockScoped
public class UnlockController implements FxController {
@KeyLoadingScoped
public class PassphraseEntryController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
private static final Logger LOG = LoggerFactory.getLogger(PassphraseEntryController.class);
private final Stage window;
private final Vault vault;
private final AtomicReference<char[]> password;
private final AtomicBoolean savePassword;
private final Optional<char[]> savedPassword;
private final UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock;
private final UserInteractionLock<PasswordEntry> passwordEntryLock;
private final ForgetPasswordComponent.Builder forgetPassword;
private final KeychainManager keychain;
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay;
@@ -66,7 +69,7 @@ public class UnlockController implements FxController {
public Animation unlockAnimation;
@Inject
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
this.window = window;
this.vault = vault;
this.password = password;
@@ -138,7 +141,7 @@ public class UnlockController implements FxController {
// if not already interacted, mark this workflow as cancelled:
if (passwordEntryLock.awaitingInteraction().get()) {
LOG.debug("Unlock canceled by user.");
passwordEntryLock.interacted(UnlockModule.PasswordEntry.CANCELED);
passwordEntryLock.interacted(PasswordEntry.CANCELED);
}
}
@@ -154,7 +157,7 @@ public class UnlockController implements FxController {
if (oldPw != null) {
Arrays.fill(oldPw, ' ');
}
passwordEntryLock.interacted(UnlockModule.PasswordEntry.PASSWORD_ENTERED);
passwordEntryLock.interacted(PasswordEntry.PASSWORD_ENTERED);
startUnlockAnimation();
}

View File

@@ -0,0 +1,67 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.MasterkeyFileProvision;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.File;
import java.nio.file.Path;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
public class SelectMasterkeyFileController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(SelectMasterkeyFileController.class);
private final Stage window;
private final AtomicReference<Path> masterkeyPath;
private final UserInteractionLock<MasterkeyFileProvision> masterkeyFileProvisionLock;
private final ResourceBundle resourceBundle;
@Inject
public SelectMasterkeyFileController(@KeyLoading Stage window, AtomicReference<Path> masterkeyPath, UserInteractionLock<MasterkeyFileProvision> masterkeyFileProvisionLock, ResourceBundle resourceBundle) {
this.window = window;
this.masterkeyPath = masterkeyPath;
this.masterkeyFileProvisionLock = masterkeyFileProvisionLock;
this.resourceBundle = resourceBundle;
this.window.setOnHiding(this::windowClosed);
}
@FXML
public void cancel() {
window.close();
}
private void windowClosed(WindowEvent windowEvent) {
// if not already interacted, mark this workflow as cancelled:
if (masterkeyFileProvisionLock.awaitingInteraction().get()) {
LOG.debug("Unlock canceled by user.");
masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELED);
}
}
@FXML
public void proceed() {
LOG.trace("proceed()");
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("unlock.chooseMasterkey.filePickerTitle"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
File masterkeyFile = fileChooser.showOpenDialog(window);
if (masterkeyFile != null) {
LOG.debug("Chose masterkey file: {}", masterkeyFile);
masterkeyPath.set(masterkeyFile.toPath());
masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.MASTERKEYFILE_PROVIDED);
}
}
}

View File

@@ -10,6 +10,7 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.application.Platform;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.concurrent.BlockingQueue;
@@ -78,7 +79,7 @@ class AppLaunchEventHandler {
fxApplicationStarter.get().thenAccept(app -> app.getVaultService().reveal(v));
}
LOG.debug("Added vault {}", potentialVaultPath);
} catch (NoSuchFileException e) {
} catch (IOException e) {
LOG.error("Failed to add vault " + potentialVaultPath, e);
}
}

View File

@@ -3,6 +3,8 @@ package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.DirStructure;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import org.slf4j.Logger;
@@ -20,6 +22,7 @@ import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
@@ -27,6 +30,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,23 +95,21 @@ public class MainWindowController implements FxController {
}
private boolean containsVault(Path path) {
if (path.getFileName().toString().equals(MASTERKEY_FILENAME)) {
return true;
} else if (Files.isDirectory(path) && Files.exists(path.resolve(MASTERKEY_FILENAME))) {
return true;
} else {
try {
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
} catch (IOException e) {
return false;
}
}
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);
}
} catch (NoSuchFileException e) {
} catch (IOException e) {
LOG.debug("Not a vault: {}", pathToVault);
}
}

View File

@@ -44,6 +44,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 {
@@ -94,6 +95,8 @@ public class MigrationRunController implements FxController {
migrationButtonDisabled.bind(ObjectExpression.objectExpression(vault.stateProperty())
.isNotEqualTo(VaultState.Value.NEEDS_MIGRATION)
.or(passwordField.textProperty().isEmpty()));
window.setOnHiding(event -> passwordField.wipe());
}
@FXML
@@ -114,8 +117,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,34 @@ 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.MasterkeyFileAccess;
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.util.Arrays;
import java.util.Collection;
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@Singleton
public class RecoveryKeyFactory {
private static final byte[] PEPPER = new byte[0];
private final WordEncoder wordEncoder;
private final MasterkeyFileAccess masterkeyFileAccess;
@Inject
public RecoveryKeyFactory(WordEncoder wordEncoder) {
public RecoveryKeyFactory(WordEncoder wordEncoder, MasterkeyFileAccess masterkeyFileAccess) {
this.wordEncoder = wordEncoder;
this.masterkeyFileAccess = masterkeyFileAccess;
}
public Collection<String> getDictionary() {
@@ -36,11 +42,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 = masterkeyFileAccess.load(masterkeyPath, password)) {
rawKey = masterkey.getEncoded();
return createRecoveryKey(rawKey);
} finally {
Arrays.fill(rawKey, (byte) 0x00);
@@ -72,8 +81,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 = new Masterkey(rawKey)) {
Path masterkeyPath = vaultPath.resolve(MASTERKEY_FILENAME);
if (Files.exists(masterkeyPath)) {
byte[] oldMasterkeyBytes = Files.readAllBytes(masterkeyPath);
// TODO: deduplicate with ChangePasswordController:
Path backupKeyPath = vaultPath.resolve(MASTERKEY_FILENAME + MasterkeyBackupHelper.generateFileIdSuffix(oldMasterkeyBytes) + MASTERKEY_BACKUP_SUFFIX);
Files.move(masterkeyPath, backupKeyPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
}
masterkeyFileAccess.persist(masterkey, masterkeyPath, newPassword);
} finally {
Arrays.fill(rawKey, (byte) 0x00);
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.unlock;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
public class UnlockCancelledException extends MasterkeyLoadingFailedException {
public UnlockCancelledException(String message) {
super(message);
}
public UnlockCancelledException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,15 +1,11 @@
package org.cryptomator.ui.unlock;
import dagger.Lazy;
import org.cryptomator.common.vaults.MountPointRequirement;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
@@ -17,19 +13,17 @@ import javafx.stage.Stage;
public class UnlockInvalidMountPointController implements FxController {
private final Stage window;
private final Lazy<Scene> unlockScene;
private final Vault vault;
@Inject
UnlockInvalidMountPointController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @UnlockWindow Vault vault) {
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault) {
this.window = window;
this.unlockScene = unlockScene;
this.vault = vault;
}
@FXML
public void back() {
window.setScene(unlockScene.get());
public void close() {
window.close();
}
/* Getter/Setter */

View File

@@ -4,20 +4,16 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import javax.inject.Named;
import javax.inject.Provider;
@@ -27,54 +23,10 @@ import javafx.stage.Stage;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@Module(subcomponents = {ForgetPasswordComponent.class})
@Module(subcomponents = {KeyLoadingComponent.class})
abstract class UnlockModule {
private static final Logger LOG = LoggerFactory.getLogger(UnlockModule.class);
public enum PasswordEntry {
PASSWORD_ENTERED,
CANCELED
}
@Provides
@UnlockScoped
static UserInteractionLock<PasswordEntry> providePasswordEntryLock() {
return new UserInteractionLock<>(null);
}
@Provides
@Named("savedPassword")
@UnlockScoped
static Optional<char[]> provideStoredPassword(KeychainManager keychain, @UnlockWindow Vault vault) {
if (!keychain.isSupported()) {
return Optional.empty();
} else {
try {
return Optional.ofNullable(keychain.loadPassphrase(vault.getId()));
} catch (KeychainAccessException e) {
LOG.error("Failed to load entry from system keychain.", e);
return Optional.empty();
}
}
}
@Provides
@UnlockScoped
static AtomicReference<char[]> providePassword(@Named("savedPassword") Optional<char[]> storedPassword) {
return new AtomicReference(storedPassword.orElse(null));
}
@Provides
@Named("savePassword")
@UnlockScoped
static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional<char[]> storedPassword) {
return new AtomicBoolean(storedPassword.isPresent());
}
@Provides
@UnlockWindow
@UnlockScoped
@@ -99,10 +51,10 @@ abstract class UnlockModule {
}
@Provides
@FxmlScene(FxmlFile.UNLOCK)
@UnlockWindow
@UnlockScoped
static Scene provideUnlockScene(@UnlockWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.UNLOCK);
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
}
@Provides
@@ -121,11 +73,6 @@ abstract class UnlockModule {
// ------------------
@Binds
@IntoMap
@FxControllerKey(UnlockController.class)
abstract FxController bindUnlockController(UnlockController controller);
@Binds
@IntoMap
@FxControllerKey(UnlockSuccessController.class)

View File

@@ -1,40 +1,31 @@
package org.cryptomator.ui.unlock;
import dagger.Lazy;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
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.InvalidPassphraseException;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.unlock.UnlockModule.PasswordEntry;
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.NotDirectoryException;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* A multi-step task that consists of background activities as well as user interaction.
@@ -49,88 +40,47 @@ public class UnlockWorkflow extends Task<Boolean> {
private final Stage window;
private final Vault vault;
private final VaultService vaultService;
private final AtomicReference<char[]> password;
private final AtomicBoolean savePassword;
private final Optional<char[]> savedPassword;
private final UserInteractionLock<PasswordEntry> passwordEntryLock;
private final KeychainManager keychain;
private final Lazy<Scene> unlockScene;
private final Lazy<Scene> successScene;
private final Lazy<Scene> invalidMountPointScene;
private final ErrorComponent.Builder errorComponent;
private final KeyLoadingStrategy keyLoadingStrategy;
@Inject
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, KeychainManager keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy) {
this.window = window;
this.vault = vault;
this.vaultService = vaultService;
this.password = password;
this.savePassword = savePassword;
this.savedPassword = savedPassword;
this.passwordEntryLock = passwordEntryLock;
this.keychain = keychain;
this.unlockScene = unlockScene;
this.successScene = successScene;
this.invalidMountPointScene = invalidMountPointScene;
this.errorComponent = errorComponent;
this.keyLoadingStrategy = keyLoadingStrategy;
}
@Override
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException {
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException, CryptoException {
try {
if (attemptUnlock()) {
if (savePassword.get()) {
savePasswordToSystemkeychain(); //savePassword will be wiped on method return, so it must be set here
}
return true;
attemptUnlock();
return true;
} catch (UnlockCancelledException e) {
cancel(false); // set Tasks state to cancelled
return false;
}
}
private void attemptUnlock() throws IOException, VolumeException, InvalidMountPointException, CryptoException {
boolean success = false;
try {
vault.unlock(keyLoadingStrategy);
success = true;
} catch (MasterkeyLoadingFailedException e) {
if (keyLoadingStrategy.recoverFromException(e)) {
LOG.info("Unlock attempt threw {}. Reattempting...", e.getClass().getSimpleName());
attemptUnlock();
} else {
cancel(false); // set Tasks state to cancelled
return false;
throw e;
}
} finally {
wipePassword(password.get());
wipePassword(savedPassword.orElse(null));
}
}
private boolean attemptUnlock() throws InterruptedException, IOException, VolumeException, InvalidMountPointException {
boolean proceed = password.get() != null || askForPassword(false) == PasswordEntry.PASSWORD_ENTERED;
while (proceed) {
try {
vault.unlock(CharBuffer.wrap(password.get()));
return true;
} catch (InvalidPassphraseException e) {
proceed = askForPassword(true) == PasswordEntry.PASSWORD_ENTERED;
}
}
return false;
}
private PasswordEntry askForPassword(boolean animateShake) throws InterruptedException {
Platform.runLater(() -> {
window.setScene(unlockScene.get());
window.show();
Window owner = window.getOwner();
if (owner != null) {
window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2);
window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2);
} else {
window.centerOnScreen();
}
if (animateShake) {
Animations.createShakeWindowAnimation(window).play();
}
});
return passwordEntryLock.awaitInteraction();
}
private void savePasswordToSystemkeychain() {
if (keychain.isSupported()) {
try {
keychain.storePassphrase(vault.getId(), CharBuffer.wrap(password.get()));
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
keyLoadingStrategy.cleanup(success);
}
}
@@ -171,12 +121,6 @@ public class UnlockWorkflow extends Task<Boolean> {
errorComponent.cause(e).window(window).build().showErrorScene();
}
private void wipePassword(char[] pw) {
if (pw != null) {
Arrays.fill(pw, ' ');
}
}
@Override
protected void succeeded() {
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayName());

View File

@@ -14,7 +14,7 @@
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.unlock.UnlockController"
fx:controller="org.cryptomator.ui.keyloading.masterkeyfile.PassphraseEntryController"
minWidth="400"
maxWidth="400"
minHeight="145"

View File

@@ -37,10 +37,10 @@
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
<ButtonBar buttonMinWidth="120" buttonOrder="U+C">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
<Region ButtonBar.buttonData="OTHER"/>
<Button text="%generic.button.back" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.masterkeyfile.SelectMasterkeyFileController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<HBox spacing="12" alignment="CENTER_LEFT" VBox.vgrow="ALWAYS">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE" glyphSize="24"/>
</StackPane>
<VBox spacing="6">
<Label text="%unlock.chooseMasterkey.prompt" wrapText="true" HBox.hgrow="ALWAYS"/>
</VBox>
</HBox>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#proceed"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>

View File

@@ -100,6 +100,9 @@ unlock.title=Unlock Vault
unlock.passwordPrompt=Enter password for "%s":
unlock.savePassword=Remember Password
unlock.unlockBtn=Unlock
##
unlock.chooseMasterkey.prompt=Could not find the masterkey file for this vault at its expected location. Please choose the key file manually.
unlock.chooseMasterkey.filePickerTitle=Select Masterkey File
## Success
unlock.success.message=Unlocked "%s" successfully! Your vault is now accessible via its virtual drive.
unlock.success.rememberChoice=Remember choice, don't show this again

View File

@@ -17,7 +17,7 @@ generic.error.title=حدث خطأ غير متوقع
generic.error.instruction=ما كان ينبغي أن يحدث هذا. يرجى الإبلاغ عن نص الخطأ أدناه وإدراج وصف للخطوات التي أدت إلى هذا الخطأ.
# Defaults
defaults.vault.vaultName=خزنة
defaults.vault.vaultName=مخزن
# Tray Menu
traymenu.showMainWindow=اظهار
@@ -38,15 +38,19 @@ addvaultwizard.welcome.existingButton=افتح مخزن موجود
addvaultwizard.new.nameInstruction=اختر اسم للمخزن
addvaultwizard.new.namePrompt=اسم المخزن
### Location
addvaultwizard.new.locationInstruction=أين يجب على Cryptomator تخزين الملفات المشفرة من قبو الخاص بك؟
addvaultwizard.new.locationInstruction=أين يجب على Cryptomator تخزين الملفات المشفرة للمخزن الخاص بك؟
addvaultwizard.new.locationLabel=موقع التخزين
addvaultwizard.new.locationPrompt=
addvaultwizard.new.directoryPickerLabel=موقع مخصص
addvaultwizard.new.directoryPickerButton=اختر…
addvaultwizard.new.directoryPickerTitle=اختر القاموس
addvaultwizard.new.invalidName=اسم المخزن غير صالح. يرجى النظر في اسم الدليل المعتاد.
addvaultwizard.new.fileAlreadyExists=ملف أو مجلد بنفس اسم المخزن موجود مسبقاً
addvaultwizard.new.locationDoesNotExist=المجلد في المسار المحدد غير موجود أو لا يمكن الوصول إليه
addvaultwizard.new.locationIsNotWritable=لا يوجد صلاحيات للكتابة على المسار المحدد
addvaultwizard.new.locationIsOk=الموقع المناسب للمخزن الخاص بك
addvaultwizard.new.invalidName=اسم المخزن غير صالح. يرجى إعادة تسمية المجلد بشكل عادي.
### Password
addvaultwizard.new.createVaultBtn=انشئ حافظة
addvaultwizard.new.createVaultBtn=إنشاء المخزن
addvaultwizard.new.generateRecoveryKeyChoice=لن تتمكن من الوصول إلى بياناتك بدون كلمة المرور الخاصة بك. هل تريد مفتاح استرداد في حالة فقدان كلمة المرور الخاصة بك؟
addvaultwizard.new.generateRecoveryKeyChoice.yes=نعم من فضلك، أن تكون آمناً أفضل من الندم
addvaultwizard.new.generateRecoveryKeyChoice.no=لا شكراً، لن أفقد كلمة المرور الخاصة بي
@@ -93,8 +97,12 @@ forgetPassword.confirmBtn=نسيت كلمة المرور
# Unlock
unlock.title=افتح الحافظة
unlock.passwordPrompt=‮أدخل كلمة السر لـ "%s":
unlock.savePassword=تذكر كلمة المرور
unlock.unlockBtn=افتح
##
unlock.chooseMasterkey.filePickerTitle=اختر ملف الـ Masterkey
## Success
unlock.success.message=تم فتح "%s" بنجاح! يمكنك الآن الوصول لمخزنك عن طريق القرص الافتراضي الخاص به.
unlock.success.rememberChoice=تذكر اختياري ولا تظهر هذا مرة أخرى
unlock.success.revealBtn=اظهار القرص
## Failure
@@ -105,9 +113,12 @@ unlock.error.invalidMountPoint.existing=نقطة/مجلد التحميل موج
# Lock
## Force
lock.forced.heading=فشل عملية القفل
lock.forced.message=تم حظر قفل "%s" بواسطة العمليات المعلقة أو الملفات المفتوحة. يمكنك فرض قفل هذا المخزن، ولكن مقاطعة عمليات الادخال والاخراج I/O قد تؤدي لفقدان البيانات غير المحفوظة.
lock.forced.confirmBtn=فرض القفل
## Failure
lock.fail.heading=فشلت عملية اقفال الخزنة.
lock.fail.message=فشل عملية قفل %s". تأكد من حفظ العمل غير المحفوظ في مكان آخر وأن العمليات الهامة للقراءة/الكتابة قد انتهت. من أجل إغلاق المخزن، اقتل تطبيق Cryptomator.
# Migration
migration.title=ترقية الحافظة
@@ -142,6 +153,8 @@ preferences.general.theme.automatic=تلقائي
preferences.general.theme.light=فاتح (أبيض)
preferences.general.theme.dark=مظلم (أسود)
preferences.general.unlockThemes=تفعيل الوضع المظلم
preferences.general.showMinimizeButton=إظهار زر التصغير
preferences.general.showTrayIcon=إظهار أيقونة بجنب الساعة (يتطلب إعادة تشغيل)
preferences.general.startHidden=إخفاء النافذة عند بدء تشغيل Cryptomator
preferences.general.debugLogging=تمكين سجلات التصحيح
preferences.general.debugDirectory=عرض ملفات السجل
@@ -166,12 +179,18 @@ preferences.updates.autoUpdateCheck=تحقق من التحديثات اوتوم
preferences.updates.checkNowBtn=تحقق الان
preferences.updates.updateAvailable=التحديث إلى الإصدار %s متاح.
## Contribution
preferences.contribute=ادعمنا
preferences.contribute.registeredFor=شهادة الداعم مسجلة لـ %s
preferences.contribute.noCertificate=ادعم Cryptomator واحصل على شهادة الداعم. إنها مثل مفتاح الترخيص لكن للأشخاص الرائعين الذين يستخدمون البرامج المجانية ؛-)
preferences.contribute.getCertificate=ليس لديك واحدة بعد؟ تعلم كيف يمكنك الحصول عليها.
preferences.contribute.promptText=قم بلصق رمز شهادة الداعم هنا
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
preferences.about=حول البرنامج
# Vault Statistics
stats.title=إحصائيات عن %s
stats.cacheHitRate=معدل استخدام الكاش
## Read
stats.read.throughput.idle=قراءة: خامل
@@ -190,9 +209,11 @@ main.dropZone.dropVault=أضف هذا المخزن
main.dropZone.unknownDragboardContent=إذا كنت ترغب في إضافة مخزن، قم بسحبه إلى هذه النافذة
## Vault List
main.vaultlist.emptyList.onboardingInstruction=انقر هنا لإضافة خزنة
main.vaultlist.contextMenu.remove=حذف…
main.vaultlist.contextMenu.lock=قفل
main.vaultlist.contextMenu.unlock=فتح…
main.vaultlist.contextMenu.unlockNow=افتح الان
main.vaultlist.contextMenu.vaultoptions=إظهار خيارات المخزن
main.vaultlist.contextMenu.reveal=اظهار القرص
main.vaultlist.addVaultBtn=أضِف مخزنًا
## Vault Detail

View File

@@ -95,6 +95,8 @@ unlock.title=Otključaj sef
unlock.passwordPrompt=Unesite lozinku za "%s":
unlock.savePassword=Zapamti šifru
unlock.unlockBtn=Otključaj
##
unlock.chooseMasterkey.filePickerTitle=Odaberite Masterkey Datoteku
## Success
unlock.success.message=Uspješno ste otključali "%s"! Vaš sef je sada dostupan putem svog virtualnog diska.
unlock.success.rememberChoice=Zapamtite izbor, ne pokazujte ovo ponovo

View File

@@ -95,6 +95,8 @@ unlock.title=Desbloquejar la caixa forta
unlock.passwordPrompt=Introduïu la contrasenya de "%s":
unlock.savePassword=Recorda la contrasenya
unlock.unlockBtn=Desbloqueja
##
unlock.chooseMasterkey.filePickerTitle=Seleccioneu el fitxer Masterkey
## Success
unlock.success.message=S'ha desblocat %s correctament! Podeu accedir a la vostra caixa forta a través de la unitat virtual.
unlock.success.rememberChoice=Recorda l'elecció. No ho tornis a mostrar.

View File

@@ -99,6 +99,8 @@ unlock.title=Odemknout trezor
unlock.passwordPrompt=Zadejte heslo pro "%s":
unlock.savePassword=Zapamatovat heslo
unlock.unlockBtn=Odemknout
##
unlock.chooseMasterkey.filePickerTitle=Vyberte Masterkey soubor
## Success
unlock.success.message=Trezor "%s" byl úspěšně odemčen a nyní je dostupný jako virtuální jednotka.
unlock.success.rememberChoice=Pamatovat si volbu, nezobrazovat to znovu

View File

@@ -14,7 +14,7 @@ generic.button.next=Weiter
generic.button.print=Drucken
## Error
generic.error.title=Ein unerwarteter Fehler ist aufgetreten
generic.error.instruction=Das hätte nicht passieren dürfen. Bitte melde die folgende Fehlermeldung und beschreibe kurz, durch welche Schritte er aufgetreten ist.
generic.error.instruction=Das hätte nicht passieren dürfen. Bitte melde den folgenden Fehlertext und beschreibe kurz, durch welche Schritte der Fehler aufgetreten ist.
# Defaults
defaults.vault.vaultName=Tresor
@@ -38,7 +38,7 @@ addvaultwizard.welcome.existingButton=Existierenden Tresor öffnen
addvaultwizard.new.nameInstruction=Wähle einen Namen für den Tresor
addvaultwizard.new.namePrompt=Tresorname
### Location
addvaultwizard.new.locationInstruction=Wo soll Cryptomator die verschlüsselten Daten deines Tresors ablegen?
addvaultwizard.new.locationInstruction=Wo soll Cryptomator die verschlüsselten Dateien deines Tresors ablegen?
addvaultwizard.new.locationLabel=Speicherort
addvaultwizard.new.locationPrompt=
addvaultwizard.new.directoryPickerLabel=Eigener Ort
@@ -81,7 +81,7 @@ addvaultwizard.success.unlockNow=Jetzt entsperren
# Remove Vault
removeVault.title=Tresor entfernen
removeVault.information=Dein Tresor wird nicht gelöscht, sondern lediglich aus Cryptomators Tresorliste entfernt. Du kannst ihn daher später jederzeit wieder hinzufügen.
removeVault.information=Dieser Tresor wird nur aus der Tresorliste entfernt. Du kannst den Tresor später jederzeit wieder hinzufügen. Es werden keine verschlüsselten Daten gelöscht.
removeVault.confirmBtn=Tresor entfernen
# Change Password
@@ -99,6 +99,8 @@ unlock.title=Tresor entsperren
unlock.passwordPrompt=Gib das Passwort für „%s“ ein:
unlock.savePassword=Passwort merken
unlock.unlockBtn=Entsperren
##
unlock.chooseMasterkey.filePickerTitle=Masterkey-Datei auswählen
## Success
unlock.success.message=„%s“ erfolgreich entsperrt! Nun kannst du über das virtuelle Laufwerk auf deinen Tresor zugreifen.
unlock.success.rememberChoice=Auswahl speichern und nicht mehr anzeigen
@@ -174,7 +176,7 @@ preferences.volume.webdav.scheme=WebDAV-Schema
preferences.updates=Updates
preferences.updates.currentVersion=Aktuelle Version: %s
preferences.updates.autoUpdateCheck=Automatisch nach Updates suchen
preferences.updates.checkNowBtn=Jetzt suchen
preferences.updates.checkNowBtn=Jetzt prüfen
preferences.updates.updateAvailable=Update auf Version %s verfügbar.
## Contribution
preferences.contribute=Unterstütze uns
@@ -256,7 +258,7 @@ main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Tresorstatistik
### Missing
main.vaultDetail.missing.info=Mit diesem Pfad konnte Cryptomator keinen Tresor finden.
main.vaultDetail.missing.info=Cryptomator konnte keinen Tresor mit diesem Pfad finden.
main.vaultDetail.missing.recheck=Erneut prüfen
main.vaultDetail.missing.remove=Aus Tresorliste entfernen…
main.vaultDetail.missing.changeLocation=Speicherort des Tresors ändern…
@@ -271,8 +273,8 @@ wrongFileAlert.header.lead=Für diesen Zweck stellt Cryptomator ein Laufwerk in
wrongFileAlert.instruction.0=Folge diesen Schritten, um Dateien zu verschlüsseln:
wrongFileAlert.instruction.1=1. Entsperre deinen Tresor.
wrongFileAlert.instruction.2=2. Klicke auf „Anzeigen“, um das Laufwerk in deinem Dateimanager zu öffnen.
wrongFileAlert.instruction.3=3. Füge deine Dateien diesem Laufwerk hinzu.
wrongFileAlert.link=Für weitere Unterstützung besuche
wrongFileAlert.instruction.3=3. Füge deine Dateien zu diesem Laufwerk hinzu.
wrongFileAlert.link=Besuche für weitere Hilfe
# Vault Options
## General
@@ -286,26 +288,26 @@ vaultOptions.general.actionAfterUnlock.ask=Nachfragen
## Mount
vaultOptions.mount=Laufwerk
vaultOptions.mount.readonly=Schreibgeschützt
vaultOptions.mount.customMountFlags=Benutzerdefinierte Mount-Flags
vaultOptions.mount.customMountFlags=Benutzerdefinierte Einhänge-Optionen
vaultOptions.mount.winDriveLetterOccupied=belegt
vaultOptions.mount.mountPoint=Einhängepunkt
vaultOptions.mount.mountPoint.auto=Automatisch einen passenden Ort auswählen
vaultOptions.mount.mountPoint.auto=Wähle automatisch einen geeigneten Ort aus
vaultOptions.mount.mountPoint.driveLetter=Laufwerksbuchstaben zuweisen
vaultOptions.mount.mountPoint.custom=Pfad selbst wählen
vaultOptions.mount.mountPoint.custom=Eigener Pfad
vaultOptions.mount.mountPoint.directoryPickerButton=Durchsuchen 
vaultOptions.mount.mountPoint.directoryPickerTitle=Wähle ein leeres Verzeichnis
## Master Key
vaultOptions.masterkey=Passwort
vaultOptions.masterkey.changePasswordBtn=Passwort ändern
vaultOptions.masterkey.forgetSavedPasswordBtn=Gespeichertes Passwort vergessen
vaultOptions.masterkey.recoveryKeyExpanation=Bei Verlust deines Passworts ist ein Wiederherstellungsschlüssel deine einzige Möglichkeit, den Zugriff auf einen Tresor wiederherzustellen.
vaultOptions.masterkey.recoveryKeyExpanation=Dein Wiederherstellungsschlüssel gehört nur dir! Dieser wird benötigt, um deinen Tresor wiederherzustellen, für den Fall das du dein Passwort verloren hast.
vaultOptions.masterkey.showRecoveryKeyBtn=Wiederherstellungsschlüssel anzeigen
vaultOptions.masterkey.recoverPasswordBtn=Passwort wiederherstellen
# Recovery Key
recoveryKey.title=Wiederherstellungsschlüssel
recoveryKey.enterPassword.prompt=Gib dein Passwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
recoveryKey.display.message=Mit folgendem Wiederherstellungsschlüssel kannst du den Zugriff auf „%s“ wiederherstellen:
recoveryKey.display.message=Mit dem folgenden Wiederherstellungsschlüssel kannst du den Zugriff auf „%s“ wiederherstellen:
recoveryKey.display.StorageHints=Bewahre ihn möglichst sicher auf, z. B.\n • in einem Passwortmanager\n • auf einem USB-Speicherstick\n • auf Papier ausgedruckt
recoveryKey.recover.prompt=Gib deinen Wiederherstellungsschlüssel für „%s“ ein:
recoveryKey.recover.validKey=Dies ist ein gültiger Wiederherstellungsschlüssel

View File

@@ -99,6 +99,8 @@ unlock.title=Ξεκλείδωμα Vault
unlock.passwordPrompt=Εισάγετε τον κωδικό για "%s":
unlock.savePassword=Απομνημόνευση κωδικού πρόσβασης
unlock.unlockBtn=Ξεκλείδωμα
##
unlock.chooseMasterkey.filePickerTitle=Επιλέξτε το αρχείο Masterkey
## Success
unlock.success.message=Ξεκλειδώθηκε "%s" επιτυχώς! Το vault σας είναι διαθέσιμο μέσω του εικονικού δίσκου του.
unlock.success.rememberChoice=Απομνημόνευση επιλογής, μην ρωτήσεις ξανά

View File

@@ -99,6 +99,8 @@ unlock.title=Desbloquear bóveda
unlock.passwordPrompt=Ingresar contraseña para "%s":
unlock.savePassword=Recordar contraseña
unlock.unlockBtn=Desbloquear
##
unlock.chooseMasterkey.filePickerTitle=Seleccionar archivo Masterkey
## Success
unlock.success.message=¡Desbloqueo de "%s" exitoso! Su bóveda ahora es accesible a través de su unidad virtual.
unlock.success.rememberChoice=Recordar opción y no mostrar de nuevo

View File

@@ -99,6 +99,8 @@ unlock.title=Déverrouiller le coffre
unlock.passwordPrompt=Entrez le mot de passe pour “%s” :
unlock.savePassword=Mémoriser le mot de passe
unlock.unlockBtn=Déverrouiller
##
unlock.chooseMasterkey.filePickerTitle=Sélectionner le fichier clef
## Success
unlock.success.message=“%s” déverrouillé ! Le contenu de votre coffre est maintenant accessible par son lecteur virtuel.
unlock.success.rememberChoice=Se souvenir de mon choix et ne plus me demander

View File

@@ -70,6 +70,7 @@ changepassword.title=पासवर्ड बदलें
# Unlock
unlock.unlockBtn=अनलॉक करें
##
## Success
## Failure
### Invalid Mount Point

View File

@@ -25,6 +25,7 @@
# Forget Password
# Unlock
##
## Success
## Failure
### Invalid Mount Point

View File

@@ -93,6 +93,8 @@ forgetPassword.confirmBtn=Jelszó elfelejtése
unlock.title=Széf feloldása
unlock.passwordPrompt=Írja be a jelszavát a következő széfhez "%s":
unlock.unlockBtn=Feloldás
##
unlock.chooseMasterkey.filePickerTitle=Mesterkulcs fájl kiválasztása
## Success
unlock.success.rememberChoice=Jegyezze meg a választást és ne mutassa többet
unlock.success.revealBtn=Széf megjelenítése

View File

@@ -44,6 +44,9 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Sesuaikan Lokasi
addvaultwizard.new.directoryPickerButton=Pilih…
addvaultwizard.new.directoryPickerTitle=Pilih Folder
addvaultwizard.new.fileAlreadyExists=Sudah ada file atau direktori dengan nama yang sama
addvaultwizard.new.locationDoesNotExist=Direktori pada path yang dipilih tidak ada atau tidak dapat diakses
addvaultwizard.new.locationIsNotWritable=Anda tidak memiliki hak akses untuk menulis pada path yang dipilih
addvaultwizard.new.invalidName=Nama brankas salah. Harap pilih nama folder yang umum.
### Password
addvaultwizard.new.createVaultBtn=Buat Brankas
@@ -55,6 +58,8 @@ addvault.new.readme.storageLocation.fileName=PENTING.rtf
addvault.new.readme.storageLocation.1=⚠️ BERKAS BRANKAS ⚠️
addvault.new.readme.storageLocation.2=Ini adalah lokasi penyimpanan brankas kamu.
addvault.new.readme.storageLocation.3=JANGAN
addvault.new.readme.storageLocation.4=• mengubah file dalam direktori ini, atau
addvault.new.readme.storageLocation.5=• menempelkan file untuk dienkripsi ke dalam direktori ini.
addvault.new.readme.storageLocation.6=Jika kamu ingin mengenkripsi berkas dan melihat isi brankas, lakukan hal berikut:
addvault.new.readme.storageLocation.7=1. Tambahkan brankas ini ke Cryptomator.
addvault.new.readme.storageLocation.8=2. Buka gembok brankas di Cryptomator.
@@ -63,18 +68,34 @@ addvault.new.readme.storageLocation.10=Jika kamu butuh bantuan, kunjungi dokumen
addvault.new.readme.accessLocation.fileName=SELAMAT_DATANG.rtf
addvault.new.readme.accessLocation.1=🔐️ ISI TERENKRIPSI 🔐️
addvault.new.readme.accessLocation.2=Ini adalah lokasi akses brankas kamu.
addvault.new.readme.accessLocation.3=File yang ditambahkan ke volume ini akan dienkripsi oleh Cryptomator. Anda dapat mempergunakan isi vault seperti dalam folder lain. Saat ini Anda sedang mengakses tampilan versi dekripsi, file Anda selalu terenkripsi di dalam cakram keras Anda.
addvault.new.readme.accessLocation.4=Anda dapat menghapus file ini.
## Existing
addvaultwizard.existing.instruction=Pilih file "masterkey.cryptomator" dalam vault Anda.
addvaultwizard.existing.chooseBtn=Pilih…
addvaultwizard.existing.filePickerTitle=Pilih File Kunci Induk
## Success
addvaultwizard.success.nextStepsInstructions=Vault "%s" telah dibuat.\nAnda harus membuka kunci vault ini untuk mengakses atau menambahkan konten. Anda juga dapat membuka kunci vault ini kapan saja di kemudian hari.
addvaultwizard.success.unlockNow=Buka Kunci Sekarang
# Remove Vault
removeVault.title=Hapus Vault
removeVault.information=Cryptomator hanya akan melupakan vault ini. Anda dapat menambahkan vault ini lagi nantinya. File yang telah dienkripsi tidak akan dihapus dari cakram keras Anda.
removeVault.confirmBtn=Hapus Vault
# Change Password
changepassword.title=Ubah Kata Sandi
changepassword.enterOldPassword=Masukkan kata sandi untuk "%s" saat ini
changepassword.finalConfirmation=Saya mengerti bahwa saya tidak akan dapat mengakses data saya apabila saya lupa kata sandi saya
# Forget Password
forgetPassword.title=Lupa Kata Sandi
forgetPassword.confirmBtn=Lupa Kata Sandi
# Unlock
unlock.unlockBtn=Buka Gembok
##
unlock.chooseMasterkey.filePickerTitle=Pilih File Kunci Induk
## Success
## Failure
### Invalid Mount Point
@@ -87,6 +108,7 @@ unlock.unlockBtn=Buka Gembok
## Start
## Run
## Sucess
migration.success.unlockNow=Buka Kunci Sekarang
## Missing file system capabilities
## Impossible
@@ -110,10 +132,12 @@ main.preferencesBtn.tooltip=Preferensi
## Drag 'n' Drop
## Vault List
main.vaultlist.contextMenu.lock=Gembok
main.vaultlist.contextMenu.unlockNow=Buka Kunci Sekarang
main.vaultlist.addVaultBtn=Tambah Brankas
## Vault Detail
### Welcome
### Locked
main.vaultDetail.unlockNowBtn=Buka Kunci Sekarang
### Unlocked
main.vaultDetail.lockBtn=Gembok
### Missing
@@ -127,6 +151,7 @@ vaultOptions.general.vaultName=Nama Brankas
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=Pilih…
## Master Key
vaultOptions.masterkey.changePasswordBtn=Ubah Kata Sandi
# Recovery Key

View File

@@ -44,6 +44,10 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Posizione personalizzata
addvaultwizard.new.directoryPickerButton=Scegli…
addvaultwizard.new.directoryPickerTitle=Seleziona cartella
addvaultwizard.new.fileAlreadyExists=Un file o una cartella con il nome del vault esiste già
addvaultwizard.new.locationDoesNotExist=Una cartella nel percorso specificato non esiste o non è accessibile
addvaultwizard.new.locationIsNotWritable=Non c'è accesso in scrittura nel percorso specificato
addvaultwizard.new.locationIsOk=Posizione adatta per il tuo vault
addvaultwizard.new.invalidName=Nome della cassaforte non valido. Si prega di considerare un nome di directory regolare.
### Password
addvaultwizard.new.createVaultBtn=Crea Cassaforte
@@ -95,6 +99,8 @@ unlock.title=Sblocca Cassaforte
unlock.passwordPrompt=Inserisci la password per "%s":
unlock.savePassword=Ricorda la Password
unlock.unlockBtn=Sblocca
##
unlock.chooseMasterkey.filePickerTitle=Seleziona file Masterkey
## Success
unlock.success.message="%s" sbloccato con successo! La tua cassaforte è ora accessibile tramite il suo drive virtuale.
unlock.success.rememberChoice=Ricorda la scelta, non mostrare ancora
@@ -148,6 +154,7 @@ preferences.general.theme.light=Chiaro
preferences.general.theme.dark=Scuro
preferences.general.unlockThemes=Sblocca modalità scura
preferences.general.showMinimizeButton=Mostra pulsante riduci a icona
preferences.general.showTrayIcon=Mostra icona nel tray (richiede riavvio)
preferences.general.startHidden=Nascondi la finestra all'avvio di Cryptomator
preferences.general.debugLogging=Abilita i registri di debug
preferences.general.debugDirectory=Mostra file log
@@ -172,6 +179,11 @@ preferences.updates.autoUpdateCheck=Controlla automaticamente la presenza di agg
preferences.updates.checkNowBtn=Controlla adesso
preferences.updates.updateAvailable=Disponibile aggiornamento alla versione %s.
## Contribution
preferences.contribute=Supportaci
preferences.contribute.registeredFor=Certificato del supporto registrato per %s
preferences.contribute.noCertificate=Supporta Cryptomator e ricevi un certificato di supporter. È come una chiave di licenza, ma per persone fantastiche che utilizzano software libero. ;-)
preferences.contribute.getCertificate=Non ne hai già uno? Impara come puoi ottenerlo.
preferences.contribute.promptText=Incolla qui il codice del certificato di supporter
#<-- Add entries for donations and code/translation/documentation contribution -->
## About

View File

@@ -99,6 +99,8 @@ unlock.title=金庫を解錠
unlock.passwordPrompt="%s" のパスワードを入力してください:
unlock.savePassword=パスワードを記憶させる
unlock.unlockBtn=解錠
##
unlock.chooseMasterkey.filePickerTitle=Masterkey ファイルを選択
## Success
unlock.success.message="%s" の解錠に成功しました! 仮想ドライブから金庫にアクセス可能です。
unlock.success.rememberChoice=選択を記憶させて、再度表示しない

View File

@@ -99,6 +99,8 @@ unlock.title=Vault 잠금해제
unlock.passwordPrompt="%s"의 비밀번호를 입력하십시요.
unlock.savePassword=비밀번호 기억
unlock.unlockBtn=잠금해제
##
unlock.chooseMasterkey.filePickerTitle=마스터키 파일 선택
## Success
unlock.success.message="%s"이(가) 성공적으로 잠금해제되었습니다. 이제 이 Vault를 가상드라이브로 접근할 수 있습니다.
unlock.success.rememberChoice=선택 기억함, 다시 묻지 않음

View File

@@ -93,6 +93,8 @@ forgetPassword.confirmBtn=Aizmirst paroli
unlock.title=Atslēgt glabātuvi
unlock.passwordPrompt=Ievadiet "%s" paroli:
unlock.unlockBtn=Atslēgt
##
unlock.chooseMasterkey.filePickerTitle=Atlasīt galveno atslēgas datni
## Success
unlock.success.revealBtn=Atklāt disku
## Failure

View File

@@ -95,6 +95,8 @@ unlock.title=Lås opp hvelvet
unlock.passwordPrompt=Skriv inn passordet for "%s":
unlock.savePassword=Husk passord
unlock.unlockBtn=Lås opp
##
unlock.chooseMasterkey.filePickerTitle=Velg hovednøkkelfil
## Success
unlock.success.rememberChoice=Husk valget - ikke vis dette igjen
unlock.success.revealBtn=Gjør enheten synlig

View File

@@ -94,6 +94,8 @@ forgetPassword.confirmBtn=Wachtwoord vergeten
unlock.title=Kluis ontgrendelen
unlock.passwordPrompt=Voer wachtwoord voor "%s" in:
unlock.unlockBtn=Ontgrendel
##
unlock.chooseMasterkey.filePickerTitle=Selecteer het Masterkey-bestand
## Success
unlock.success.rememberChoice=Keuze onthouden en dit niet opnieuw tonen
unlock.success.revealBtn=Toon Schijf

View File

@@ -93,6 +93,8 @@ forgetPassword.confirmBtn=Gløym passord
unlock.title=Lås opp kvelven
unlock.passwordPrompt=Skriv inn passordet for "%s":
unlock.unlockBtn=Låse opp
##
unlock.chooseMasterkey.filePickerTitle=Vel hovudnøkkelfil
## Success
unlock.success.rememberChoice=Hugs valet - ikkje vis dette igjen
unlock.success.revealBtn=Gjer eininga synleg

View File

@@ -95,6 +95,8 @@ unlock.title=ਵਾਲਟ ਅਣ-ਲਾਕ ਕਰੋ
unlock.passwordPrompt="%s" ਲਈ ਪਾਸਵਰਡ ਦਿਓ:
unlock.savePassword=ਪਾਸਵਰਡ ਯਾਦ ਰੱਖੋ
unlock.unlockBtn=ਅਣ-ਲਾਕ ਕਰੋ
##
unlock.chooseMasterkey.filePickerTitle=ਮਾਸਟਰ-ਕੁੰਜੀ ਫਾਇਲ ਚੁਣੋ
## Success
unlock.success.rememberChoice=ਚੋਣਾਂ ਯਾਦ ਰੱਖੋ, ਇਹ ਮੁੜ ਕੇ ਨਾ ਵੇਖਾਓ
unlock.success.revealBtn=ਡਰਾਇਵ ਦਿਖਾਓ

View File

@@ -99,6 +99,8 @@ unlock.title=Odblokuj sejf
unlock.passwordPrompt=Wprowadź hasło dla "%s":
unlock.savePassword=Zapamiętaj hasło
unlock.unlockBtn=Odblokuj
##
unlock.chooseMasterkey.filePickerTitle=Wybierz plik Masterkey
## Success
unlock.success.message=Odblokowano "%s" pomyślnie! Twój sejf jest teraz dostępny za pomocą dysku wirtualnego.
unlock.success.rememberChoice=Zapamiętaj wybór, nie pokazuj tego ponownie

View File

@@ -17,6 +17,7 @@ generic.error.title=Ocorreu um erro inesperado
generic.error.instruction=Isto não devia ter acontecido. Por favor reporte o texto do erro abaixo e descreva os passos que levaram a este problema.
# Defaults
defaults.vault.vaultName=Cofre
# Tray Menu
traymenu.showMainWindow=Mostrar
@@ -43,6 +44,7 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Outro Local
addvaultwizard.new.directoryPickerButton=Escolher…
addvaultwizard.new.directoryPickerTitle=Selecionar diretório
addvaultwizard.new.fileAlreadyExists=Aviso: Já existe um Ficheiro ou Diretório com o mesmo nome
addvaultwizard.new.invalidName=Nome de cofre inválido. Por favor considere um nome de diretório regular.
### Password
addvaultwizard.new.createVaultBtn=Criar Cofre
@@ -85,6 +87,8 @@ forgetPassword.confirmBtn=Esqueci a Senha
unlock.title=Destrancar Cofre
unlock.passwordPrompt=Insira a senha para "%s":
unlock.unlockBtn=Destrancar
##
unlock.chooseMasterkey.filePickerTitle=Selecionar ficheiro MasterKey
## Success
unlock.success.rememberChoice=Lembrar escolha, não mostrar isto novamente
## Failure
@@ -92,6 +96,7 @@ unlock.success.rememberChoice=Lembrar escolha, não mostrar isto novamente
# Lock
## Force
lock.forced.confirmBtn=Forçar Bloqueio
## Failure
# Migration
@@ -181,5 +186,8 @@ vaultOptions.masterkey.recoverPasswordBtn=Recuperar Senha
recoveryKey.title=Chave de Recuperação
# New Password
passwordStrength.messageLabel.3=Forte
passwordStrength.messageLabel.4=Muito forte
# Quit
quit.lockAndQuit=Bloquear e Sair

View File

@@ -99,6 +99,8 @@ unlock.title=Desbloquear Cofre
unlock.passwordPrompt=Digite a senha para "%s":
unlock.savePassword=Lembrar Senha
unlock.unlockBtn=Desbloquear
##
unlock.chooseMasterkey.filePickerTitle=Selecionar Arquivo Masterkey
## Success
unlock.success.message="%s" desbloqueado com êxito! Seu cofre agora está acessível na unidade virtual.
unlock.success.rememberChoice=Lembrar opção escolhida, não mostrar isto novamente

View File

@@ -25,6 +25,7 @@
# Forget Password
# Unlock
##
## Success
## Failure
### Invalid Mount Point

View File

@@ -11,7 +11,7 @@ generic.button.copy=Скопировать
generic.button.copied=Скопировано!
generic.button.done=Готово
generic.button.next=Далее
generic.button.print=Распечатать
generic.button.print=Печать
## Error
generic.error.title=Неизвестная ошибка
generic.error.instruction=Этого не должно было произойти. Создайте отчёт об ошибке ниже и опишите, какие шаги к ней привели.
@@ -45,8 +45,8 @@ addvaultwizard.new.directoryPickerLabel=Своё место
addvaultwizard.new.directoryPickerButton=Выбрать…
addvaultwizard.new.directoryPickerTitle=Выберите папку
addvaultwizard.new.fileAlreadyExists=Файл или папка с именем хранилища уже существует
addvaultwizard.new.locationDoesNotExist=Папка с указанным адресом не существует, или недоступна
addvaultwizard.new.locationIsNotWritable=Указанный адрес только для чтения
addvaultwizard.new.locationDoesNotExist=Папка с указанным адресом не существует или недоступна
addvaultwizard.new.locationIsNotWritable=Указанный адрес - только для чтения
addvaultwizard.new.locationIsOk=Подходящее расположение для вашего хранилища
addvaultwizard.new.invalidName=Неверное имя хранилища. Укажите корректное имя папки.
### Password
@@ -59,8 +59,8 @@ addvault.new.readme.storageLocation.fileName=ВАЖНО.rtf
addvault.new.readme.storageLocation.1=⚠️ ФАЙЛЫ ХРАНИЛИЩА ⚠️
addvault.new.readme.storageLocation.2=Это место, где находится ваше хранилище.
addvault.new.readme.storageLocation.3=НЕЛЬЗЯ
addvault.new.readme.storageLocation.4=• изменять любые файлы в этой папке или
addvault.new.readme.storageLocation.5=• добавляйте в эту папку любые файлы для шифрования.
addvault.new.readme.storageLocation.4=• изменять любые файлы в этой папке
addvault.new.readme.storageLocation.5=• добавлять в эту папку любые файлы для шифрования
addvault.new.readme.storageLocation.6=Чтобы зашифровать файлы и просмотреть содержимое хранилища, сделайте следующее:
addvault.new.readme.storageLocation.7=1. Добавьте это хранилище в Cryptomator.
addvault.new.readme.storageLocation.8=2. Разблокируйте хранилище в Cryptomator.
@@ -99,14 +99,16 @@ unlock.title=Разблокировать хранилище
unlock.passwordPrompt=Введите пароль для "%s"
unlock.savePassword=Запомнить пароль
unlock.unlockBtn=Разблокировать
##
unlock.chooseMasterkey.filePickerTitle=Выберите файл MasterKey
## Success
unlock.success.message=Разблокировка "%s" успешно выполнена! Доступ в хранилище открыт через его виртуальный диск.
unlock.success.rememberChoice=Запомнить выбор и больше не спрашивать
unlock.success.revealBtn=Показать диск
## Failure
unlock.error.heading=Не удалось разблокировать хранилище
unlock.error.heading=Невозможно разблокировать хранилище
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Точка монтирования "%s" не является пустой папкой или не существует.
unlock.error.invalidMountPoint.notExisting=Точка монтирования %s - не папка, не пуста или не существует.
unlock.error.invalidMountPoint.existing=Точка монтирования %s уже существует, либо отсутствует родительская папка.
# Lock
@@ -146,7 +148,7 @@ migration.impossible.moreInfo=Хранилище по-прежнему можн
preferences.title=Настройки
## General
preferences.general=Общие
preferences.general.theme=Оформление
preferences.general.theme=Тема
preferences.general.theme.automatic=Автоматически
preferences.general.theme.light=Светлая
preferences.general.theme.dark=Тёмная
@@ -157,7 +159,7 @@ preferences.general.startHidden=Скрывать окно при запуске
preferences.general.debugLogging=Вести журнал отладки
preferences.general.debugDirectory=Показать файлы журнала
preferences.general.autoStart=Запускать Cryptomator при старте системы
preferences.general.keychainBackend=Хранить пароли в
preferences.general.keychainBackend=Хранение паролей
preferences.general.keychainBackend.org.cryptomator.linux.keychain.SecretServiceKeychainAccess=Связка ключей Gnome
preferences.general.keychainBackend.org.cryptomator.linux.keychain.KDEWalletKeychainAccess=Хранилище ключей KDE
preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeychainAccess=Доступ к связке ключей macOS
@@ -230,7 +232,7 @@ main.dropZone.unknownDragboardContent=Если вы хотите добавит
main.vaultlist.emptyList.onboardingInstruction=Нажмите здесь, чтобы добавить хранилище
main.vaultlist.contextMenu.remove=Удалить…
main.vaultlist.contextMenu.lock=Заблокировать
main.vaultlist.contextMenu.unlock=Разблокировка…
main.vaultlist.contextMenu.unlock=Разблокировать
main.vaultlist.contextMenu.unlockNow=Разблокировать
main.vaultlist.contextMenu.vaultoptions=Параметры хранилища
main.vaultlist.contextMenu.reveal=Показать диск
@@ -240,7 +242,7 @@ main.vaultlist.addVaultBtn=Добавить хранилище
main.vaultDetail.welcomeOnboarding=Благодарим за выбор Cryptomator для защиты ваших файлов. Если требуется помощь, ознакомьтесь с документацией по началу работы:
### Locked
main.vaultDetail.lockedStatus=ЗАБЛОКИРОВАНО
main.vaultDetail.unlockBtn=Разблокировка…
main.vaultDetail.unlockBtn=Разблокировать
main.vaultDetail.unlockNowBtn=Разблокировать
main.vaultDetail.optionsBtn=Параметры хранилища
main.vaultDetail.passwordSavedInKeychain=Пароль сохранён
@@ -289,7 +291,7 @@ vaultOptions.mount.readonly=Только чтение
vaultOptions.mount.customMountFlags=Свои флаги монтирования
vaultOptions.mount.winDriveLetterOccupied=занято
vaultOptions.mount.mountPoint=Точка монтирования
vaultOptions.mount.mountPoint.auto=Автоматически выбрать подходящее расположение
vaultOptions.mount.mountPoint.auto=Автоматически выбирать подходящее расположение
vaultOptions.mount.mountPoint.driveLetter=Использовать назначенную букву диска
vaultOptions.mount.mountPoint.custom=Свой путь
vaultOptions.mount.mountPoint.directoryPickerButton=Выбрать…

View File

@@ -93,6 +93,7 @@ unlock.title=Odomknúť trezor
unlock.passwordPrompt=Zadajte heslo pre "%s":
unlock.savePassword=Odomknúť.uložiťHeslo
unlock.unlockBtn=Odomknúť
##
## Success
unlock.success.message=Odomknutie "%s" úspešné! Vaša peňaženka je už prístupná cez jej virtuálny disk.
unlock.success.revealBtn=Odkry disk

View File

@@ -2,76 +2,329 @@
# Generics
## Button
generic.button.apply=Примени
generic.button.back=Назад
generic.button.cancel=Откажи
generic.button.change=Измени
generic.button.close=Затвори
generic.button.copy=Копирај
generic.button.copied=Копирано!
generic.button.done=Завршено
generic.button.next=Даље
generic.button.print=Штампај
## Error
generic.error.title=Догодила се неочекивана грешка
generic.error.instruction=Ово није требало да се догоди. Молимо вас пријавите текст грешке испод и додајте опис корака који су довели до ове грешке.
# Defaults
defaults.vault.vaultName=Сеф
# Tray Menu
traymenu.showMainWindow=Прикажи
traymenu.showPreferencesWindow=Подешавања
traymenu.lockAllVaults=Закључај све
traymenu.quitApplication=Излаз
traymenu.vault.unlock=Откључај
traymenu.vault.lock=Закључај
traymenu.vault.reveal=Откриј
# Add Vault Wizard
addvaultwizard.title=Додавање сефа
## Welcome
addvaultwizard.welcome.newButton=Направи нови сеф
addvaultwizard.welcome.existingButton=Отвори постојећи сеф
## New
### Name
addvaultwizard.new.nameInstruction=Одабери назив за сеф
addvaultwizard.new.namePrompt=Назив сефа
### Location
addvaultwizard.new.locationInstruction=Где Cryptomator да складишти шифроване датотеке вашег сефа?
addvaultwizard.new.locationLabel=Локација складишта
addvaultwizard.new.locationPrompt=
addvaultwizard.new.directoryPickerLabel=Друга локација
addvaultwizard.new.directoryPickerButton=Изабери…
addvaultwizard.new.directoryPickerTitle=Изабери директоријум
addvaultwizard.new.fileAlreadyExists=Датотека или директоријум са овим именом сефа већ постоји
addvaultwizard.new.locationDoesNotExist=Директоријум на датој локацији не постоји или му се не може приступити
addvaultwizard.new.locationIsNotWritable=Немате приступ писању на датој локацији
addvaultwizard.new.locationIsOk=Прикладна локација за ваш сеф
addvaultwizard.new.invalidName=Неважеће име сефа. Молимо вас за одабир обичног имена директоријума.
### Password
addvaultwizard.new.createVaultBtn=Направи сеф
addvaultwizard.new.generateRecoveryKeyChoice=Нећете моћи да приступите вашим подацима без ваше лозинке. Да ли желите резервни кључ у случају губитка лозинке?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да молим, увек је добро имати резерву
addvaultwizard.new.generateRecoveryKeyChoice.no=Не хвала, нећу изгубити своју лозинку
### Information
addvault.new.readme.storageLocation.fileName=ВАЖНО.rtf
addvault.new.readme.storageLocation.1=⚠️ ДОКУМЕНТИ СЕФА ⚠️
addvault.new.readme.storageLocation.2=Ово је локација где је складиштен ваш сеф.
addvault.new.readme.storageLocation.3=НИ У КОМ СЛУЧАЈУ НЕМОЈТЕ
addvault.new.readme.storageLocation.4=• мењати било коју датотеку у овом директоријуму или
addvault.new.readme.storageLocation.5=• стављати било коју датотеку који желите да заштитите директно у овај директоријум.
addvault.new.readme.storageLocation.6=Ако желите да заштитите документе или прегледате садржај сефа, урадите следеће:
addvault.new.readme.storageLocation.7=1. Додајте овај сеф у Cryptomator.
addvault.new.readme.storageLocation.8=2. Откључајте сеф у Cryptomator.
addvault.new.readme.storageLocation.9=3. Отворите локацију сефа притиском на дугме "Откриј".
addvault.new.readme.storageLocation.10=Ако вам је потребна помоћ, прочитајте документацију: %s
addvault.new.readme.accessLocation.fileName=ДОБРОДОШЛИ.rtf
addvault.new.readme.accessLocation.1=🔐️ ШИФРОВАНА ПАРТИЦИЈА 🔐️
addvault.new.readme.accessLocation.2=Ово је приступна локација вашег сефа.
addvault.new.readme.accessLocation.3=Било која датотека додата у ову партицију биће шифровани са Cryptomator. Можете да радите у њој баш као у било којој другој партицији или директоријуму. Ово је само дешифрован преглед њеног садржаја, ваше датотеке остају шифроване на вашем тврдом диску све време.
addvault.new.readme.accessLocation.4=Слободно можете да обришете ову датотеку.
## Existing
addvaultwizard.existing.instruction=Изабери "masterkey.cryptomator" датотеку свог постојећег сефа.
addvaultwizard.existing.chooseBtn=Изабери…
addvaultwizard.existing.filePickerTitle=Изабери "Masterkey" датотеку
## Success
addvaultwizard.success.nextStepsInstructions=Додат је сеф "%s".\nМорате да откључате овај сеф да бисте приступили или додали садржај. Такође можете да га откључате касније у било ком тренутку.
addvaultwizard.success.unlockNow=Откључај сада
# Remove Vault
removeVault.title=Уклони сеф
removeVault.information=Ово ће урадити да Cryptomator заборави ваш сеф. Увек га поново можете додати касније. Шифроване датотеке неће бити обрисане са вашег тврдог диска.
removeVault.confirmBtn=Уклони сеф
# Change Password
changepassword.title=Промена лозинке
changepassword.enterOldPassword=Унеси тренутну лозинку за "%s"
changepassword.finalConfirmation=Разумем да нећу моћи приступити својим подацима уколико заборавим лозинку
# Forget Password
forgetPassword.title=Заборави лозинку
forgetPassword.information=Ово ће избрисати сачувану лозинку овог сефа из вашег системског менаџера лозинки.
forgetPassword.confirmBtn=Заборави лозинку
# Unlock
unlock.title=Откључавање сефа
unlock.passwordPrompt=Унесите лозинку за "%s":
unlock.savePassword=Запамти лозинку
unlock.unlockBtn=Откључај
##
unlock.chooseMasterkey.filePickerTitle=Изабери "Masterkey" датотеку
## Success
unlock.success.message=Успешно сте откључали "%s"! Ваш сеф је сада доступан путем свог виртуелног диска.
unlock.success.rememberChoice=Запамти избор, не приказуј ово поново
unlock.success.revealBtn=Отвори диск
## Failure
unlock.error.heading=Сеф није могуће откључати
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Тачка везивања "%s" није директоријум, није празна или не постоји.
unlock.error.invalidMountPoint.existing=Тачка везивања "%s" већ постоји или је надређени директоријум непостојећи.
# Lock
## Force
lock.forced.heading=Нормално закључавање није успело
lock.forced.message=Закључавање "%s" је блокирано операцијама на чекању или отвореним датотекама. Можете присилно закључати овај сеф, међутим прекид "I/O" операција може резултирати губитком несачуваних података.
lock.forced.confirmBtn=Присилно закључај
## Failure
lock.fail.heading=Закључавање сефа није успело.
lock.fail.message=Сеф "%s" није могуће закључати. Будите сигурни да је несачувани рад сачуван негде другде и да су важне операције читања/писања завршене. Како бисте затворили сеф, зауставите Cryptomator процес у систему.
# Migration
migration.title=Надогради сеф
## Start
migration.start.prompt=Ваш сеф "%s" мора бити ажуриран на новији формат. Пре наставка, проверите да нема синхронизације на чекању која утиче на овај сеф.
migration.start.confirm=Да, мој сеф је у потпуности синхронизован
## Run
migration.run.enterPassword=Унесите лозинку за "%s"
migration.run.startMigrationBtn=Преместите сеф
migration.run.progressHint=Ово би могло потрајати…
## Sucess
migration.success.nextStepsInstructions="%s" је успешно премештен. Сада можете да откључате ваш сеф.
migration.success.unlockNow=Откључај сада
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Неподржан фајл систем
migration.error.missingFileSystemCapabilities.description=Премештање није започето, јер се ваш сеф налази на неадекватном фајл систему.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Фајл систем не подржава дугачка имена датотека.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Фајл систем не подржава дугачке адресе локација.
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Фајл систем не дозвољава читање.
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Фајл систем не дозвољава писање.
## Impossible
migration.impossible.heading=Сеф не може да се премести
migration.impossible.reason=Сеф се не може аутоматски преместити јер је његова складишна локација или тачка приступа некомпатибилна.
migration.impossible.moreInfo=Сеф се и даље може отворити са старијом верзијом. За упутства како ручно преместити сеф, посетите
# Preferences
preferences.title=Подешавања
## General
preferences.general=Опште
preferences.general.theme=Тема
preferences.general.theme.automatic=Аутоматска
preferences.general.theme.light=Светла
preferences.general.theme.dark=Тамна
preferences.general.unlockThemes=Откључај тамну тему
preferences.general.showMinimizeButton=Прикажи дугме за умањење програма
preferences.general.showTrayIcon=Прикажи "tray" иконицу (потребан рестарт)
preferences.general.startHidden=Сакриј прозор приликом покретања Cryptomator
preferences.general.debugLogging=Омогући евиденцију отклањања грешака
preferences.general.debugDirectory=Прикажи датотеке евиденције
preferences.general.autoStart=Покрени Cryptomator при покретању система
preferences.general.keychainBackend=Похрани лозинке са
preferences.general.keychainBackend.org.cryptomator.linux.keychain.SecretServiceKeychainAccess=Gnome Keyring
preferences.general.keychainBackend.org.cryptomator.linux.keychain.KDEWalletKeychainAccess=KDE Wallet
preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeychainAccess=macOS Keychain Access
preferences.general.keychainBackend.org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess=Windows Data Protection
preferences.general.interfaceOrientation=Оријентација интерфејса
preferences.general.interfaceOrientation.ltr=Са лева на десно
preferences.general.interfaceOrientation.rtl=Са десна на лево
## Volume
preferences.volume=Виртуелни Диск
preferences.volume.type=Тип партиције
preferences.volume.webdav.port=WebDAV Порт
preferences.volume.webdav.scheme=WebDAV Шема
## Updates
preferences.updates=Ажурирања
preferences.updates.currentVersion=Тренутна верзија: %s
preferences.updates.autoUpdateCheck=Аутоматска провера доступних ажурирања
preferences.updates.checkNowBtn=Провери сада
preferences.updates.updateAvailable=Ажурирање на верзију %s је доступно.
## Contribution
preferences.contribute=Подржите Нас
preferences.contribute.registeredFor=Сертификат "Пријатељ Пројекта" регистрован на %s
preferences.contribute.noCertificate=Подржите Cryptomator и добићете сертификат "Пријатељ Пројекта". Нешто као лиценцни кључ али за сјајне људе који користе и подржавају бесплатан софтвер. ;-)
preferences.contribute.getCertificate=Немате сертификат? Сазнајте како га можете прибавити.
preferences.contribute.promptText=Унесите ваш "Пријатељ Пројекта" код овде
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
preferences.about=О програму
# Vault Statistics
stats.title=Статистика за %s
stats.cacheHitRate=Фреквентност кеш меморије
## Read
stats.read.throughput.idle=Читање: у приправности
stats.read.throughput.kibs=Читање: %.2f kiB/s
stats.read.throughput.mibs=Читање: %.2f MiB/s
stats.read.total.data.none=Учитано података: -
stats.read.total.data.kib=Учитано података: %.1f kiB
stats.read.total.data.mib=Учитано података: %.1f MiB
stats.read.total.data.gib=Учитано података: %.1f GiB
stats.decr.total.data.none=Дешифровано података: -
stats.decr.total.data.kib=Дешифровано података: %.1f kiB
stats.decr.total.data.mib=Дешифровано података: %.1f MiB
stats.decr.total.data.gib=Дешифровано података: %.1f GiB
stats.read.accessCount=Укупно учитано: %d
## Write
stats.write.throughput.idle=Писање: у приправности
stats.write.throughput.kibs=Писање: %.2f kiB/s
stats.write.throughput.mibs=Писање: %.2f MiB/s
stats.write.total.data.none=Уписано података: -
stats.write.total.data.kib=Уписано података: %.1f kiB
stats.write.total.data.mib=Уписано података: %.1f MiB
stats.write.total.data.gib=Уписано података: %.1f GiB
stats.encr.total.data.none=Шифровано података: -
stats.encr.total.data.kib=Шифровано података: %.1f kiB
stats.encr.total.data.mib=Шифровано података: %.1f MiB
stats.encr.total.data.gib=Шифровано података: %.1f GiB
stats.write.accessCount=Укупно уписано: %d
# Main Window
main.closeBtn.tooltip=Затвори
main.minimizeBtn.tooltip=Умањи
main.preferencesBtn.tooltip=Подешавања
main.debugModeEnabled.tooltip=Омогућен је режим отклањања грешака
main.donationKeyMissing.tooltip=Молимо Вас размислите о донацији пројекту
## Drag 'n' Drop
main.dropZone.dropVault=Додај овај сеф
main.dropZone.unknownDragboardContent=Ако желите додати сеф, превуците га у овај прозор
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Кликните овде да додате сеф
main.vaultlist.contextMenu.remove=Уклони…
main.vaultlist.contextMenu.lock=Закључај
main.vaultlist.contextMenu.unlock=Откључај…
main.vaultlist.contextMenu.unlockNow=Откључај сада
main.vaultlist.contextMenu.vaultoptions=Прикажи опције сефа
main.vaultlist.contextMenu.reveal=Прикажи диск
main.vaultlist.addVaultBtn=Додај сеф
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=Хвала вам што сте одабрали Cryptomator за заштиту ваших података. Ако вам је потребна помоћ, прегледајте наше корисничко упутство:
### Locked
main.vaultDetail.lockedStatus=ЗАКЉУЧАНО
main.vaultDetail.unlockBtn=Откључај…
main.vaultDetail.unlockNowBtn=Откључај сада
main.vaultDetail.optionsBtn=Опције сефа
main.vaultDetail.passwordSavedInKeychain=Лозинка је сачувана
### Unlocked
main.vaultDetail.unlockedStatus=ОТКЉУЧАНО
main.vaultDetail.accessLocation=Садржај Вашег сефа је доступан овде:
main.vaultDetail.revealBtn=Прикажи диск
main.vaultDetail.lockBtn=Закључај
main.vaultDetail.bytesPerSecondRead=Читање:
main.vaultDetail.bytesPerSecondWritten=Писање:
main.vaultDetail.throughput.idle=у стању мировања
main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Статистика сефа
### Missing
main.vaultDetail.missing.info=Cryptomator није пронашао сеф на овој локацији.
main.vaultDetail.missing.recheck=Провери поново
main.vaultDetail.missing.remove=Удаљи са листе сефова…
main.vaultDetail.missing.changeLocation=Промени локацију сефа…
### Needs Migration
main.vaultDetail.migrateButton=Надогради сеф
main.vaultDetail.migratePrompt=Да бисте приступили вашем сефу, он мора бити надограђен на нови формат
# Wrong File Alert
wrongFileAlert.title=Како шифровати датотеке
wrongFileAlert.header.title=Да ли сте покушали да шифрујете ове датотеке?
wrongFileAlert.header.lead=У ту сврху, Cryptomator вам пружа партицију унутар вашег системског менаџера датотека.
wrongFileAlert.instruction.0=Да бисте шифрирали датотеке, пратите следеће кораке:
wrongFileAlert.instruction.1=1. Откључајте Ваш сеф.
wrongFileAlert.instruction.2=2. Кликните на "Откриј" да бисте отворили партицију унутар вашег менаџера датотека.
wrongFileAlert.instruction.3=3. Додајте Ваше датотеке у ову партицију.
wrongFileAlert.link=За даљу подршку, посетите
# Vault Options
## General
vaultOptions.general=Опште
vaultOptions.general.vaultName=Назив сефа
vaultOptions.general.unlockAfterStartup=Откључај сеф при покретању Cryptomator
vaultOptions.general.actionAfterUnlock=Након успешног откључавања
vaultOptions.general.actionAfterUnlock.ignore=Не ради ништа
vaultOptions.general.actionAfterUnlock.reveal=Прикажи диск
vaultOptions.general.actionAfterUnlock.ask=Питај
## Mount
vaultOptions.mount=Монтирање
vaultOptions.mount.readonly=Само читање
vaultOptions.mount.customMountFlags=Прилагођена обележја монтирања
vaultOptions.mount.winDriveLetterOccupied=заузето
vaultOptions.mount.mountPoint=Монтажна тачка
vaultOptions.mount.mountPoint.auto=Аутоматски одабери погодну локацију
vaultOptions.mount.mountPoint.driveLetter=Користи додељено слово диска
vaultOptions.mount.mountPoint.custom=Друга локација
vaultOptions.mount.mountPoint.directoryPickerButton=Изабери…
vaultOptions.mount.mountPoint.directoryPickerTitle=Изаберите празан директоријум
## Master Key
vaultOptions.masterkey=Лозинка
vaultOptions.masterkey.changePasswordBtn=Промена лозинке
vaultOptions.masterkey.forgetSavedPasswordBtn=Заборави сачувану лозинку
vaultOptions.masterkey.recoveryKeyExpanation=Резервни кључ је Ваш једини начин да вратите приступ сефу уколико изгубите лозинку.
vaultOptions.masterkey.showRecoveryKeyBtn=Прикажи Резервни Кључ
vaultOptions.masterkey.recoverPasswordBtn=Обнови лозинку
# Recovery Key
recoveryKey.title=Резервни Кључ
recoveryKey.enterPassword.prompt=Унесите Вашу лозинку како бисте приказали резервни кључ за %s:
recoveryKey.display.message=Следећи резервни кључ може се користити за враћање приступа "%s":
recoveryKey.display.StorageHints=Чувајте га на веома сигурном месту, нпр.:\n • Похраните га унутар менаџера лозинки\n • Сачувајте га на екстерни диск\n • Одштампајте га на папиру
recoveryKey.recover.prompt=Унесите резервни кључ за "%s":
recoveryKey.recover.validKey=Ово је исправан резервни кључ
recoveryKey.printout.heading=Cryptomator Резервни Кључ\n"%s"\n
# New Password
newPassword.promptText=Унесите нову лозинку
newPassword.reenterPassword=Потврдите нову лозинку
newPassword.passwordsMatch=Лозинке се подударају!
newPassword.passwordsDoNotMatch=Лозинке се не подударају
passwordStrength.messageLabel.tooShort=Користите најмање %d карактера
passwordStrength.messageLabel.0=Веома слаба
passwordStrength.messageLabel.1=Слаба
passwordStrength.messageLabel.2=Осредња
passwordStrength.messageLabel.3=Jakа
passwordStrength.messageLabel.4=Веома јака
# Quit
quit.prompt=Затворити програм? Имате откључаних сефова.
quit.lockAndQuit=Закључај и Изађи

View File

@@ -2,76 +2,245 @@
# Generics
## Button
generic.button.apply=Primeni
generic.button.back=Nazad
generic.button.cancel=Otkaži
generic.button.change=Izmeni
generic.button.close=Zatvori
generic.button.copy=Kopiraj
generic.button.copied=Kopirano!
generic.button.done=Završeno
generic.button.next=Dalje
generic.button.print=Štampaj
## Error
generic.error.title=Dogodila se neočekivana greška
generic.error.instruction=Ovo nije trebalo da se dogodi. Molimo vas prijavite tekst greške ispod i dodajte opis koraka koji su doveli do ove greške.
# Defaults
defaults.vault.vaultName=Sef
# Tray Menu
traymenu.showMainWindow=Prikaži
traymenu.showPreferencesWindow=Podešavanja
traymenu.lockAllVaults=Zaključaj sve
traymenu.quitApplication=Izlaz
traymenu.vault.unlock=Otključaj
traymenu.vault.lock=Zaključaj
traymenu.vault.reveal=Otkrij
# Add Vault Wizard
addvaultwizard.title=Dodavanje safa
## Welcome
addvaultwizard.welcome.newButton=Napravi novi sef
addvaultwizard.welcome.existingButton=Otvori postojeći sef
## New
### Name
addvaultwizard.new.nameInstruction=Odaberi naziv za sef
addvaultwizard.new.namePrompt=Naziv sefa
### Location
addvaultwizard.new.locationInstruction=Gde Cryptomator da skladišti šifrovane datoteke vašeg sefa?
addvaultwizard.new.locationLabel=Lokacija skladišta
addvaultwizard.new.locationPrompt=
addvaultwizard.new.directoryPickerLabel=Druga lokacija
addvaultwizard.new.directoryPickerButton=Izaberi…
addvaultwizard.new.directoryPickerTitle=Izaberi direktorijum
addvaultwizard.new.fileAlreadyExists=Datoteka ili direktorijum sa ovim imenom sefa već postoji
addvaultwizard.new.locationDoesNotExist=Direktorijum na datoj lokaciji ne postoji ili mu se ne može pristupiti
addvaultwizard.new.locationIsNotWritable=Nemate pristup pisanju na datoj lokaciji
addvaultwizard.new.locationIsOk=Prikladna lokacija za vaš sef
addvaultwizard.new.invalidName=Nevažeće ime sefa. Molimo vas za odabir običnog imena direktorijuma.
### Password
addvaultwizard.new.createVaultBtn=Napravi sef
addvaultwizard.new.generateRecoveryKeyChoice=Nećete moći da pristupite vašim podacima bez vaše lozinke. Da li želite rezervni ključ u slučaju gubitka lozinke?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Da molim, uvek je dobro imati rezervu
addvaultwizard.new.generateRecoveryKeyChoice.no=Ne hvala, neću izgubiti svoju lozinku
### Information
addvault.new.readme.storageLocation.fileName=VAŽNO.rtf
addvault.new.readme.storageLocation.1=⚠️ DOKUMENTI SEFA ⚠️
addvault.new.readme.storageLocation.2=Ovo je lokacija gde je skladišten vaš sef.
addvault.new.readme.storageLocation.3=NI U KOM SLUČAJU NEMOJTE
addvault.new.readme.storageLocation.4=• menjati bilo koju datoteku u ovom direktorijumu ili
addvault.new.readme.storageLocation.5=• stavljati bilo koju datoteku koji želite da zaštitite direktno u ovaj direktorijum.
addvault.new.readme.storageLocation.6=Ako želite da zaštitite dokumente ili pregledate sadržaj sefa, uradite sledeće:
addvault.new.readme.storageLocation.7=1. Dodajte ovaj sef u Cryptomator.
addvault.new.readme.storageLocation.8=2. Otključajte sef u Cryptomator.
addvault.new.readme.storageLocation.9=3. Otvorite lokaciju sefa pritiskom na dugme "Otkrij".
addvault.new.readme.storageLocation.10=Ako vam je potrebna pomoć, pročitajte dokumentaciju: %s
addvault.new.readme.accessLocation.fileName=DOBRODOŠLI.rtf
addvault.new.readme.accessLocation.1=🔐️ ŠIFROVANA PARTICIJA 🔐️
addvault.new.readme.accessLocation.2=Ovo je pristupna lokacija vašeg sefa.
addvault.new.readme.accessLocation.3=Bilo koja datoteka dodata u ovu particiju biće šifrovana sa Cryptomator. Možete da radite u njoj baš kao i u bilo kojoj drugoj particiji ili direktorijumu. Ovo je samo dešifrovan pregled njenog sadržaja, vaše datoteke ostaju šifrovane na vašem tvrdom disku sve vreme.
addvault.new.readme.accessLocation.4=Slobodno možete da obrišete ovu datoteku.
## Existing
addvaultwizard.existing.instruction=Izaberi "masterkey.cryptomator" datoteku svog postojećeg sefa.
addvaultwizard.existing.chooseBtn=Izaberi…
addvaultwizard.existing.filePickerTitle=Izaberi "Masterkey" datoteku
## Success
addvaultwizard.success.nextStepsInstructions=Dodat je sef "%s".\nMorate da otključate ovaj sef da biste pristupili ili dodali sadržaj. Takođe možete da ga otključate kasnije u bilo kom trenutku.
addvaultwizard.success.unlockNow=Otključaj sada
# Remove Vault
removeVault.title=Ukloni sef
removeVault.information=Ovo će uraditi da Cryptomator zaboravi vaš sef. Uvek ga ponovo možete dodati kasnije. Šifrovane datoteke neće biti obrisane sa vašeg tvrdog diska.
removeVault.confirmBtn=Ukloni sef
# Change Password
changepassword.title=Promena lozinke
changepassword.enterOldPassword=Unesi trenutnu lozinku za "%s"
changepassword.finalConfirmation=Razumem da neću moći pristupiti svojim podacima ukoliko zaboravim lozinku
# Forget Password
forgetPassword.title=Zaboravi lozinku
forgetPassword.information=Ovo će izbrisati sačuvanu lozinku ovog sefa iz vašeg sistemskog menadžera lozinki.
forgetPassword.confirmBtn=Zaboravi lozinku
# Unlock
unlock.title=Otključavanje sefa
unlock.passwordPrompt=Unesite lozinku za "%s":
unlock.savePassword=Zapamti lozinku
unlock.unlockBtn=Otključaj
##
unlock.chooseMasterkey.filePickerTitle=Izaberi "Masterkey" datoteku
## Success
unlock.success.message=Uspešno ste otključali "%s"! Vaš sef je sada dostupan putem ovog virtuelnog diska.
unlock.success.rememberChoice=Zapamti izbor, ne prikazuj ovo ponovo
unlock.success.revealBtn=Otvori disk
## Failure
unlock.error.heading=Sef nije moguće otključati
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Tačka vezivanja "%s" nije direktorijum, nije prazna ili ne postoji.
unlock.error.invalidMountPoint.existing=Tačka vezivanja "%s" već postoji ili je nadređeni direktorijum nepostojeći.
# Lock
## Force
lock.forced.heading=Normalno zaključavanje nije uspelo
lock.forced.message=Zaključavanje "%s" je blokirano operacijama na čekanju ili otvorenim datotekama. Možete prisilno zaključati ovaj sef, međutim prekid "I/O" operacija može rezultirati gubitkom nesačuvanih podataka.
lock.forced.confirmBtn=Prisilno zaključaj
## Failure
lock.fail.heading=Zaključavanje sefa nije uspelo.
lock.fail.message=Sef "%s" nije moguće zaključati. Budite sigurni da je nesačuvani rad sačuvan negde drugde i da su važne operacije čitanja/pisanja završene. Kako biste zatvorili sef, zaustavite Cryptomator proces u sistemu.
# Migration
migration.title=Nadogradi sef
## Start
migration.start.prompt=Vaš sef "%s" mora biti ažuriran na noviji format. Pre nastavka, proverite da nema sinhronizacije na čekanju koja utiče na ovaj sef.
migration.start.confirm=Da moj sef je u potpunosti sinhronizovan
## Run
migration.run.enterPassword=Unesite lozinku za "%s"
migration.run.startMigrationBtn=Premestite sef
migration.run.progressHint=Ovo bi moglo potrajati…
## Sucess
migration.success.nextStepsInstructions="%s" je uspešno premešten. Sada možete da otključate vaš šef.
migration.success.unlockNow=Otključaj sada
## Missing file system capabilities
migration.error.missingFileSystemCapabilities.title=Nepodržan fajl sistem
migration.error.missingFileSystemCapabilities.description=Premeštanje nije započeto, jer se vaš sef nalazi na neadekvatnom fajl sistemu.
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Fajl sistem ne podržava dugačka imena datoteka.
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Fajl sistem ne podržava dugačke adrese lokacija.
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Fajl sistem ne dozvoljava čitanje.
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Fajl sistem ne dozvoljava pisanje.
## Impossible
migration.impossible.heading=Sef ne može da se premesti
migration.impossible.reason=Sef se ne može automatski premestiti jer je njegova skladišna lokacija ili tačka pristupa nekompatibilna.
migration.impossible.moreInfo=Sef se i dalje može otvoriti sa starijom verzijom. Za uputstva kako ručno premestiti sef, posetite
# Preferences
preferences.title=Podešavanja
## General
preferences.general=Opšte
preferences.general.theme=Tema
preferences.general.theme.automatic=Automatska
preferences.general.theme.light=Svetla
preferences.general.theme.dark=Tamna
preferences.general.unlockThemes=Otključaj tamnu temu
preferences.general.showMinimizeButton=Prikaži dugme za umanjenje programa
preferences.general.showTrayIcon=Prikaži "tray" ikonicu (potreban restart)
preferences.general.startHidden=Sakrij prozor prilikom pokretanja Cryptomator
preferences.general.debugLogging=Omogući evidenciju otklanjanja grešaka
preferences.general.debugDirectory=Prikaži datoteke evidencije
preferences.general.autoStart=Pokreni Cryptomator pri pokretanju sistema
preferences.general.keychainBackend=Pohrani lozinke sa
preferences.general.keychainBackend.org.cryptomator.linux.keychain.SecretServiceKeychainAccess=Gnome Keyring
preferences.general.keychainBackend.org.cryptomator.linux.keychain.KDEWalletKeychainAccess=KDE Wallet
preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeychainAccess=macOS Keychain Access
preferences.general.keychainBackend.org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess=Windows Data Protection
preferences.general.interfaceOrientation=Orijentacija interfejsa
preferences.general.interfaceOrientation.ltr=Sa leva na desno
preferences.general.interfaceOrientation.rtl=Sa desna na levo
## Volume
preferences.volume=Virtuelni Disk
preferences.volume.type=Tip particije
preferences.volume.webdav.port=WebDAV Port
preferences.volume.webdav.scheme=WebDAV Šema
## Updates
preferences.updates=Ažuriranja
preferences.updates.currentVersion=Trenutna verzija: %s
preferences.updates.autoUpdateCheck=Automatska provera dostupnih ažuriranja
preferences.updates.checkNowBtn=Proveri sada
preferences.updates.updateAvailable=Ažuriranje na verziju %s je dostupno.
## Contribution
preferences.contribute=Podržite Nas
preferences.contribute.registeredFor=Sertifikat "Prijatelj Projekta" registrovan je na %s
preferences.contribute.noCertificate=Podržite Cryptomator i dobićete sertifikat "Prijatelj Projekta". Nešto kao licencni ključ ali za sjajne ljude koji koriste i podržavaju besplatan softver. ;-)
preferences.contribute.getCertificate=Nemate sertifikat? Saznajte kako ga možete pribaviti.
preferences.contribute.promptText=Unesite vaš "Prijatelj Projekta" kod ovde
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
preferences.about=O programu
# Vault Statistics
stats.title=Statistika za %s
stats.cacheHitRate=Frekventnost keš memorije
## Read
stats.read.throughput.idle=Čitanje: u pripravnosti
stats.read.throughput.kibs=Čitanje: %.2f kiB/s
stats.read.throughput.mibs=Čitanje: %.2f MiB/s
stats.read.total.data.none=Učitano podataka: -
stats.read.total.data.kib=Učitano podataka: %.1f kiB
stats.read.total.data.mib=Učitano podataka: %.1f MiB
stats.read.total.data.gib=Učitano podataka: %.1f GiB
stats.decr.total.data.none=Dešifrovano podataka: -
stats.decr.total.data.kib=Dešifrovano podataka: %.1f kiB
stats.decr.total.data.mib=Dešifrovano podataka: %.1f MiB
stats.decr.total.data.gib=Dešifrovano podataka: %.1f GiB
## Write
# Main Window
main.closeBtn.tooltip=Zatvori
main.preferencesBtn.tooltip=Podešavanja
## Drag 'n' Drop
## Vault List
main.vaultlist.contextMenu.lock=Zaključaj
main.vaultlist.contextMenu.unlockNow=Otključaj sada
main.vaultlist.contextMenu.reveal=Otvori disk
main.vaultlist.addVaultBtn=Dodavanje safa
## Vault Detail
### Welcome
### Locked
main.vaultDetail.unlockNowBtn=Otključaj sada
### Unlocked
main.vaultDetail.revealBtn=Otvori disk
main.vaultDetail.lockBtn=Zaključaj
### Missing
### Needs Migration
main.vaultDetail.migrateButton=Nadogradi sef
# Wrong File Alert
# Vault Options
## General
vaultOptions.general=Opšte
vaultOptions.general.vaultName=Naziv sefa
vaultOptions.general.actionAfterUnlock.reveal=Otvori disk
## Mount
vaultOptions.mount.mountPoint.directoryPickerButton=Izaberi…
## Master Key
vaultOptions.masterkey.changePasswordBtn=Promena lozinke
# Recovery Key
# New Password
# Quit
quit.lockAndQuit=Zaključaj i Izađi

View File

@@ -95,6 +95,8 @@ unlock.title=Lås upp valv
unlock.passwordPrompt=Ange lösenord för "%s":
unlock.savePassword=Kom ihåg lösenord
unlock.unlockBtn=Lås upp
##
unlock.chooseMasterkey.filePickerTitle=Välj Masterkey-fil
## Success
unlock.success.message="%s" upplåst! Ditt valv är nu åtkomligt från den virtuella enheten.
unlock.success.rememberChoice=Kom ihåg valet, visa inte detta igen

View File

@@ -14,7 +14,7 @@ generic.button.next=İleri
generic.button.print=Yazdır
## Error
generic.error.title=Beklenmedik bir hata oluştu
generic.error.instruction=Bu olmamalıydı. Lütfen aşağıdaki hata metnini ve bu hatayı aldığınızda hangi adımları uyguladığınızı rapor edin.
generic.error.instruction=Bu olmamalıydı. Lütfen aşağıdaki hata metnini bildirin ve bu hataya neden olan adımların bir açıklamasını ekleyin.
# Defaults
defaults.vault.vaultName=Kasa
@@ -44,7 +44,7 @@ addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Özel Konum
addvaultwizard.new.directoryPickerButton=Seç…
addvaultwizard.new.directoryPickerTitle=Dizin Seç
addvaultwizard.new.fileAlreadyExists=Kasa adına sahip bir dosya ya da dizin zaten mevcut
addvaultwizard.new.fileAlreadyExists=Kasa adına sahip bir dosya veya dizin zaten mevcut
addvaultwizard.new.locationDoesNotExist=Belirtilen yoldaki bir dizin mevcut değil veya erişilemez
addvaultwizard.new.locationIsNotWritable=Belirtilen yolda yazma erişimi yok
addvaultwizard.new.locationIsOk=Kasanız için uygun yer
@@ -52,7 +52,7 @@ addvaultwizard.new.invalidName=Geçersiz kasa adı. Lütfen normal bir dizin ad
### Password
addvaultwizard.new.createVaultBtn=Kasa Oluştur
addvaultwizard.new.generateRecoveryKeyChoice=Şifreniz olmadan verilerinize erişemeyeceksiniz. Şifrenizi kaybetmeniz durumunda kullanabileceğiniz bir kurtarma anahtarı ister misiniz?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Evet lütfen, kafamı taşlara vurmaktan iyidir
addvaultwizard.new.generateRecoveryKeyChoice.yes=Evet lütfen, üzgün olmaktan daha güvenli
addvaultwizard.new.generateRecoveryKeyChoice.no=Hayır teşekkürler, şifremi kaybetmeyeceğim
### Information
addvault.new.readme.storageLocation.fileName=IMPORTANT.rtf
@@ -69,7 +69,7 @@ addvault.new.readme.storageLocation.10=Yardım lazımsa, belgeleri ziyaret edin:
addvault.new.readme.accessLocation.fileName=WELCOME.rtf
addvault.new.readme.accessLocation.1=🔐️ ŞİFRELENMİŞ BİRİM 🔐️
addvault.new.readme.accessLocation.2=Burası kasanızın erişim konumudur.
addvault.new.readme.accessLocation.3=Bu birime eklenen dosyalar Cryptomator tarafından şifrelenecektir. Diğer herhangi bir sürücü / klasörde olduğu gibi üzerinde çalışabilirsiniz. Bu sadece içeriğinin şifresi çözülmüş bir görünümdür, dosyalarınız her zaman sabit diskinizde şifrelenir.
addvault.new.readme.accessLocation.3=Bu birime eklenen tüm dosyalar Cryptomator tarafından şifrelenecektir. Başka herhangi bir sürücü/klasörde olduğu gibi üzerinde çalışabilirsiniz. Bu sadece içeriğinin şifresi çözülmüş bir görünümüdür, dosyalarınız her zaman sabit sürücünüzde şifrelenmiş halde kalır.
addvault.new.readme.accessLocation.4=Bu dosyayı silmeye çekinmeyin.
## Existing
addvaultwizard.existing.instruction=Varolan kasanızın "masterkey.cryptomator" dosyasını seçin.
@@ -86,7 +86,7 @@ removeVault.confirmBtn=Kasayı Sil
# Change Password
changepassword.title=Şifreyi Değiştir
changepassword.enterOldPassword="%s" için şuanki şifreyi gir
changepassword.enterOldPassword="%s" için mevcut şifreyi girin
changepassword.finalConfirmation=Şifremi unutursam verilerime ulaşamayacağımın farkındayım
# Forget Password
@@ -99,6 +99,8 @@ unlock.title=Kasa Kilidini Aç
unlock.passwordPrompt="%s" için şifre girin:
unlock.savePassword=Şifreyi Hatırla
unlock.unlockBtn=Kilidi Aç
##
unlock.chooseMasterkey.filePickerTitle=Masterkey Dosyasını Seç
## Success
unlock.success.message="%s" 'nin kilidi başarıyla açıldı! Kasanız şimdi sanal sürücüsü ile erişilebilir durumda.
unlock.success.rememberChoice=Seçimi hatırla, bunu bir daha gösterme
@@ -256,7 +258,7 @@ main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
main.vaultDetail.stats=Kasa İstatistikleri
### Missing
main.vaultDetail.missing.info=Şifreleyici bu dosya yolunda bir kasa bulamadı.
main.vaultDetail.missing.info=Cryptomator bu yolda bir kasa bulamadı.
main.vaultDetail.missing.recheck=Yeniden denetle
main.vaultDetail.missing.remove=Kasa Listesinden Sil…
main.vaultDetail.missing.changeLocation=Kasa Yerini Değiştir…

View File

@@ -99,6 +99,8 @@ unlock.title=解锁保险库
unlock.passwordPrompt=输入 "%s" 的密码
unlock.savePassword=记住密码
unlock.unlockBtn=解锁
##
unlock.chooseMasterkey.filePickerTitle=选择 Masterkey 文件
## Success
unlock.success.message=已成功解锁 "%s"! 您现在可以通过其虚拟驱动器访问它
unlock.success.rememberChoice=记住选项且不再显示

View File

@@ -99,6 +99,8 @@ unlock.title=解鎖加密檔案庫
unlock.passwordPrompt=輸入 "%s" 的密碼:
unlock.savePassword=記住密碼
unlock.unlockBtn=解鎖
##
unlock.chooseMasterkey.filePickerTitle=選擇主金鑰檔案
## Success
unlock.success.message=成功解鎖 "%s"!您現在可以存存取您的加密檔案庫。
unlock.success.rememberChoice=記得這個決定,不要再顯示

View File

@@ -11,7 +11,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
Cryptomator uses 43 third-party dependencies under the following licenses:
Cryptomator uses 45 third-party dependencies under the following licenses:
Apache License v2.0:
- jffi (com.github.jnr:jffi:1.2.23 - http://github.com/jnr/jffi)
- jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm)
@@ -22,53 +22,53 @@ Cryptomator uses 43 third-party dependencies under the following licenses:
- Dagger (com.google.dagger:dagger:2.32 - https://github.com/google/dagger)
- error-prone annotations (com.google.errorprone:error_prone_annotations:2.3.4 - http://nexus.sonatype.org/oss-repository-hosting.html/error_prone_parent/error_prone_annotations)
- Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess)
- Guava: Google Core Libraries for Java (com.google.guava:guava:30.0-jre - https://github.com/google/guava/guava)
- Guava: Google Core Libraries for Java (com.google.guava:guava:30.1-jre - https://github.com/google/guava/guava)
- Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture)
- J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/)
- Apache Commons CLI (commons-cli:commons-cli:1.4 - http://commons.apache.org/proper/commons-cli/)
- javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/)
- Java Native Access (net.java.dev.jna:jna:5.7.0 - https://github.com/java-native-access/jna)
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - https://github.com/java-native-access/jna)
- Apache Commons Lang (org.apache.commons:commons-lang3:3.11 - https://commons.apache.org/proper/commons-lang/)
- Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.14 - http://hc.apache.org/httpcomponents-core-ga)
- Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.5 - http://jackrabbit.apache.org/jackrabbit-webdav/)
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - https://eclipse.org/jetty/jetty-http)
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - https://eclipse.org/jetty/jetty-io)
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - https://eclipse.org/jetty/jetty-security)
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - https://eclipse.org/jetty/jetty-server)
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet)
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util)
- Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api)
Apache-2.0:
- Java Native Access (net.java.dev.jna:jna:5.9.0 - https://github.com/java-native-access/jna)
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.9.0 - https://github.com/java-native-access/jna)
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.1 - https://eclipse.org/jetty/jetty-http)
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.1 - https://eclipse.org/jetty/jetty-io)
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.1 - https://eclipse.org/jetty/jetty-security)
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.1 - https://eclipse.org/jetty/jetty-server)
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.1 - https://eclipse.org/jetty/jetty-servlet)
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.1 - https://eclipse.org/jetty/jetty-util)
- Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:10.0.1 - https://eclipse.org/jetty/jetty-webapp)
- Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:10.0.1 - https://eclipse.org/jetty/jetty-xml)
BSD:
- asm (org.ow2.asm:asm:7.1 - http://asm.ow2.org/)
- asm-analysis (org.ow2.asm:asm-analysis:7.1 - http://asm.ow2.org/)
- asm-commons (org.ow2.asm:asm-commons:7.1 - http://asm.ow2.org/)
- asm-tree (org.ow2.asm:asm-tree:7.1 - http://asm.ow2.org/)
- asm-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/)
Eclipse Public License - Version 1.0:
- Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api)
Eclipse Public License - Version 2.0:
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - https://eclipse.org/jetty/jetty-http)
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - https://eclipse.org/jetty/jetty-io)
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - https://eclipse.org/jetty/jetty-security)
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - https://eclipse.org/jetty/jetty-server)
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet)
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util)
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.1 - https://eclipse.org/jetty/jetty-http)
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.1 - https://eclipse.org/jetty/jetty-io)
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.1 - https://eclipse.org/jetty/jetty-security)
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.1 - https://eclipse.org/jetty/jetty-server)
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.1 - https://eclipse.org/jetty/jetty-servlet)
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.1 - https://eclipse.org/jetty/jetty-util)
- Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:10.0.1 - https://eclipse.org/jetty/jetty-webapp)
- Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:10.0.1 - https://eclipse.org/jetty/jetty-xml)
Eclipse Public License - v 2.0:
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
GPLv2:
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
GPLv2+CE:
- Java Servlet API (javax.servlet:javax.servlet-api:4.0.1 - https://javaee.github.io/servlet-spec/)
- javafx-base (org.openjfx:javafx-base:16 - https://openjdk.java.net/projects/openjfx/javafx-base/)
- javafx-controls (org.openjfx:javafx-controls:16 - https://openjdk.java.net/projects/openjfx/javafx-controls/)
- javafx-fxml (org.openjfx:javafx-fxml:16 - https://openjdk.java.net/projects/openjfx/javafx-fxml/)
- javafx-graphics (org.openjfx:javafx-graphics:16 - https://openjdk.java.net/projects/openjfx/javafx-graphics/)
LGPL 2.1:
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
LGPL-2.1-or-later:
- Java Native Access (net.java.dev.jna:jna:5.9.0 - https://github.com/java-native-access/jna)
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.9.0 - https://github.com/java-native-access/jna)
- Java Native Access (net.java.dev.jna:jna:5.7.0 - https://github.com/java-native-access/jna)
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - https://github.com/java-native-access/jna)
MIT License:
- java jwt (com.auth0:java-jwt:3.13.0 - https://github.com/auth0/java-jwt)
- jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - http://github.com/jnr/jnr-x86asm)

View File

@@ -1,24 +1,33 @@
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.MasterkeyFileAccess;
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.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 MasterkeyFileAccess masterkeyFileAccess = Mockito.mock(MasterkeyFileAccess.class);
private RecoveryKeyFactory inTest = new RecoveryKeyFactory(wordEncoder, masterkeyFileAccess);
@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");
Masterkey masterkey = Mockito.mock(Masterkey.class);
Mockito.when(masterkeyFileAccess.load(pathToVault.resolve("masterkey.cryptomator"), "asd")).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