diff --git a/cryptomator/pom.xml b/cryptomator/pom.xml
index ea747d7fd5f..cdea7324f45 100644
--- a/cryptomator/pom.xml
+++ b/cryptomator/pom.xml
@@ -25,7 +25,7 @@
jar
- 2.1.2.1
+ 2.3.0-uvfdraft-SNAPSHOT
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java
new file mode 100644
index 00000000000..7ee00625a0c
--- /dev/null
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java
@@ -0,0 +1,420 @@
+package ch.cyberduck.core.cryptomator;
+
+/*
+ * Copyright (c) 2002-2025 iterate GmbH. All rights reserved.
+ * https://cyberduck.io/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+import ch.cyberduck.core.ListService;
+import ch.cyberduck.core.Path;
+import ch.cyberduck.core.PathAttributes;
+import ch.cyberduck.core.Permission;
+import ch.cyberduck.core.Session;
+import ch.cyberduck.core.SimplePathPredicate;
+import ch.cyberduck.core.UrlProvider;
+import ch.cyberduck.core.cryptomator.features.*;
+import ch.cyberduck.core.exception.BackgroundException;
+import ch.cyberduck.core.features.*;
+import ch.cyberduck.core.preferences.PreferencesFactory;
+import ch.cyberduck.core.shared.DefaultTouchFeature;
+import ch.cyberduck.core.transfer.TransferStatus;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.cryptomator.cryptolib.api.AuthenticationFailedException;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.FileContentCryptor;
+import org.cryptomator.cryptolib.api.FileHeaderCryptor;
+
+import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.common.io.BaseEncoding;
+
+public abstract class AbstractVault implements Vault {
+
+ private static final Logger log = LogManager.getLogger(AbstractVault.class);
+
+ public static final int VAULT_VERSION_DEPRECATED = 6;
+ public static final int VAULT_VERSION = PreferencesFactory.get().getInteger("cryptomator.vault.version");
+
+ public static final String DIR_PREFIX = "0";
+
+ private static final Pattern BASE32_PATTERN = Pattern.compile("^0?(([A-Z2-7]{8})*[A-Z2-7=]{8})");
+ private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+).c9r");
+
+ public abstract Path getMasterkey();
+
+ public abstract Path getConfig();
+
+ public abstract Path gethHome();
+
+ public abstract int getVersion();
+
+ public abstract FileHeaderCryptor getFileHeaderCryptor();
+
+ public abstract FileContentCryptor getFileContentCryptor();
+
+ public abstract CryptorCache getFileNameCryptor();
+
+ public abstract CryptoFilename getFilenameProvider();
+
+ public abstract CryptoDirectory getDirectoryProvider();
+
+ public abstract Cryptor getCryptor();
+
+ public abstract int getNonceSize();
+
+ public int numberOfChunks(long cleartextFileSize) {
+ return (int) (cleartextFileSize / this.getFileContentCryptor().cleartextChunkSize() +
+ ((cleartextFileSize % this.getFileContentCryptor().cleartextChunkSize() > 0) ? 1 : 0));
+ }
+
+ public long toCleartextSize(final long cleartextFileOffset, final long ciphertextFileSize) throws CryptoInvalidFilesizeException {
+ if(TransferStatus.UNKNOWN_LENGTH == ciphertextFileSize) {
+ return TransferStatus.UNKNOWN_LENGTH;
+ }
+ final int headerSize;
+ if(0L == cleartextFileOffset) {
+ headerSize = this.getFileHeaderCryptor().headerSize();
+ }
+ else {
+ headerSize = 0;
+ }
+ try {
+ return this.getFileContentCryptor().cleartextSize(ciphertextFileSize - headerSize);
+ }
+ catch(AssertionError e) {
+ throw new CryptoInvalidFilesizeException(String.format("Encrypted file size must be at least %d bytes", headerSize));
+ }
+ catch(IllegalArgumentException e) {
+ throw new CryptoInvalidFilesizeException(String.format("Invalid file size. %s", e.getMessage()));
+ }
+ }
+
+ @Override
+ public State getState() {
+ return this.isUnlocked() ? State.open : State.closed;
+ }
+
+ @Override
+ public long toCiphertextSize(final long cleartextFileOffset, final long cleartextFileSize) {
+ if(TransferStatus.UNKNOWN_LENGTH == cleartextFileSize) {
+ return TransferStatus.UNKNOWN_LENGTH;
+ }
+ final int headerSize;
+ if(0L == cleartextFileOffset) {
+ headerSize = this.getCryptor().fileHeaderCryptor().headerSize();
+ }
+ else {
+ headerSize = 0;
+ }
+ return headerSize + this.getCryptor().fileContentCryptor().ciphertextSize(cleartextFileSize);
+ }
+
+ @Override
+ public Path encrypt(Session> session, Path file) throws BackgroundException {
+ return this.encrypt(session, file, file.attributes().getDirectoryId(), false);
+ }
+
+ @Override
+ public Path encrypt(Session> session, Path file, boolean metadata) throws BackgroundException {
+ return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata);
+ }
+
+ public Path encrypt(Session> session, Path file, String directoryId, boolean metadata) throws BackgroundException {
+ final Path encrypted;
+ if(file.isFile() || metadata) {
+ if(file.getType().contains(Path.Type.vault)) {
+ log.warn("Skip file {} because it is marked as an internal vault path", file);
+ return file;
+ }
+ if(new SimplePathPredicate(file).test(this.gethHome())) {
+ log.warn("Skip vault home {} because the root has no metadata file", file);
+ return file;
+ }
+ final Path parent;
+ final String filename;
+ if(file.getType().contains(Path.Type.encrypted)) {
+ final Path decrypted = file.attributes().getDecrypted();
+ parent = this.getDirectoryProvider().toEncrypted(session, decrypted.getParent().attributes().getDirectoryId(), decrypted.getParent());
+ filename = this.getDirectoryProvider().toEncrypted(session, parent.attributes().getDirectoryId(), decrypted.getName(), decrypted.getType());
+ }
+ else {
+ parent = this.getDirectoryProvider().toEncrypted(session, file.getParent().attributes().getDirectoryId(), file.getParent());
+ filename = this.getDirectoryProvider().toEncrypted(session, parent.attributes().getDirectoryId(), file.getName(), file.getType());
+ }
+ final PathAttributes attributes = new PathAttributes(file.attributes());
+ attributes.setDirectoryId(null);
+ if(!file.isFile() && !metadata) {
+ // The directory is different from the metadata file used to resolve the actual folder
+ attributes.setVersionId(null);
+ attributes.setFileId(null);
+ }
+ // Translate file size
+ attributes.setSize(this.toCiphertextSize(0L, file.attributes().getSize()));
+ final EnumSet type = EnumSet.copyOf(file.getType());
+ if(metadata && this.getVersion() == VAULT_VERSION_DEPRECATED) {
+ type.remove(Path.Type.directory);
+ type.add(Path.Type.file);
+ }
+ type.remove(Path.Type.decrypted);
+ type.add(Path.Type.encrypted);
+ encrypted = new Path(parent, filename, type, attributes);
+ }
+ else {
+ if(file.getType().contains(Path.Type.encrypted)) {
+ log.warn("Skip file {} because it is already marked as an encrypted path", file);
+ return file;
+ }
+ if(file.getType().contains(Path.Type.vault)) {
+ return this.getDirectoryProvider().toEncrypted(session, this.gethHome().attributes().getDirectoryId(), this.gethHome());
+ }
+ encrypted = this.getDirectoryProvider().toEncrypted(session, directoryId, file);
+ }
+ // Add reference to decrypted file
+ if(!file.getType().contains(Path.Type.encrypted)) {
+ encrypted.attributes().setDecrypted(file);
+ }
+ // Add reference for vault
+ file.attributes().setVault(this.gethHome());
+ encrypted.attributes().setVault(this.gethHome());
+ return encrypted;
+ }
+
+ @Override
+ public Path decrypt(final Session> session, final Path file) throws BackgroundException {
+ if(file.getType().contains(Path.Type.decrypted)) {
+ log.warn("Skip file {} because it is already marked as an decrypted path", file);
+ return file;
+ }
+ if(file.getType().contains(Path.Type.vault)) {
+ log.warn("Skip file {} because it is marked as an internal vault path", file);
+ return file;
+ }
+ final Path inflated = this.inflate(session, file);
+ final Pattern pattern = this.getVersion() == VAULT_VERSION_DEPRECATED ? BASE32_PATTERN : BASE64URL_PATTERN;
+ final Matcher m = pattern.matcher(inflated.getName());
+ if(m.matches()) {
+ final String ciphertext = m.group(1);
+ try {
+ final String cleartextFilename = this.getFileNameCryptor().decryptFilename(
+ this.getVersion() == VAULT_VERSION_DEPRECATED ? BaseEncoding.base32() : BaseEncoding.base64Url(),
+ ciphertext, file.getParent().attributes().getDirectoryId().getBytes(StandardCharsets.UTF_8));
+ final PathAttributes attributes = new PathAttributes(file.attributes());
+ if(this.isDirectory(inflated)) {
+ if(Permission.EMPTY != attributes.getPermission()) {
+ final Permission permission = new Permission(attributes.getPermission());
+ permission.setUser(permission.getUser().or(Permission.Action.execute));
+ permission.setGroup(permission.getGroup().or(Permission.Action.execute));
+ permission.setOther(permission.getOther().or(Permission.Action.execute));
+ attributes.setPermission(permission);
+ }
+ // Reset size for folders
+ attributes.setSize(-1L);
+ attributes.setVersionId(null);
+ attributes.setFileId(null);
+ }
+ else {
+ // Translate file size
+ attributes.setSize(this.toCleartextSize(0L, file.attributes().getSize()));
+ }
+ // Add reference to encrypted file
+ attributes.setEncrypted(file);
+ // Add reference for vault
+ attributes.setVault(this.gethHome());
+ final EnumSet type = EnumSet.copyOf(file.getType());
+ type.remove(this.isDirectory(inflated) ? Path.Type.file : Path.Type.directory);
+ type.add(this.isDirectory(inflated) ? Path.Type.directory : Path.Type.file);
+ type.remove(Path.Type.encrypted);
+ type.add(Path.Type.decrypted);
+ final Path decrypted = new Path(file.getParent().attributes().getDecrypted(), cleartextFilename, type, attributes);
+ if(type.contains(Path.Type.symboliclink)) {
+ decrypted.setSymlinkTarget(file.getSymlinkTarget());
+ }
+ return decrypted;
+ }
+ catch(AuthenticationFailedException e) {
+ throw new CryptoAuthenticationException(
+ "Failure to decrypt due to an unauthentic ciphertext", e);
+ }
+ }
+ else {
+ throw new CryptoFilenameMismatchException(
+ String.format("Failure to decrypt %s due to missing pattern match for %s", inflated.getName(), pattern));
+ }
+ }
+
+ private boolean isDirectory(final Path p) {
+ if(this.getVersion() == VAULT_VERSION_DEPRECATED) {
+ return p.getName().startsWith(DIR_PREFIX);
+ }
+ return p.isDirectory();
+ }
+
+ private Path inflate(final Session> session, final Path file) throws BackgroundException {
+ final String fileName = file.getName();
+ if(this.getFilenameProvider().isDeflated(fileName)) {
+ final String filename = this.getFilenameProvider().inflate(session, fileName);
+ return new Path(file.getParent(), filename, EnumSet.of(Path.Type.file), file.attributes());
+ }
+ return file;
+ }
+
+ public synchronized boolean isUnlocked() {
+ return this.getCryptor() != null;
+ }
+
+ @Override
+ public boolean contains(final Path file) {
+ if(this.isUnlocked()) {
+ return new SimplePathPredicate(file).test(this.gethHome()) || file.isChild(this.getHome());
+ }
+ return false;
+ }
+
+ @Override
+ public synchronized void close() {
+ if(this.isUnlocked()) {
+ if(this.getCryptor() != null) {
+ getCryptor().destroy();
+ }
+ if(this.getDirectoryProvider() != null) {
+ this.getDirectoryProvider().destroy();
+ }
+ if(this.getFilenameProvider() != null) {
+ this.getFilenameProvider().destroy();
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T getFeature(final Session> session, final Class type, final T delegate) {
+ if(this.isUnlocked()) {
+ if(type == ListService.class) {
+ return (T) new CryptoListService(session, (ListService) delegate, this);
+ }
+ if(type == Touch.class) {
+ // Use default touch feature because touch with remote implementation will not add encrypted file header
+ return (T) new CryptoTouchFeature(session, new DefaultTouchFeature(session._getFeature(Write.class)), session._getFeature(Write.class), this);
+ }
+ if(type == Directory.class) {
+ return (T) (this.getVersion() == VAULT_VERSION_DEPRECATED ?
+ new CryptoDirectoryV6Feature(session, (Directory) delegate, session._getFeature(Write.class), this) :
+ new CryptoDirectoryV7Feature(session, (Directory) delegate, session._getFeature(Write.class), this)
+ );
+ }
+ if(type == Upload.class) {
+ return (T) new CryptoUploadFeature(session, (Upload) delegate, session._getFeature(Write.class), this);
+ }
+ if(type == Download.class) {
+ return (T) new CryptoDownloadFeature(session, (Download) delegate, session._getFeature(Read.class), this);
+ }
+ if(type == Read.class) {
+ return (T) new CryptoReadFeature(session, (Read) delegate, this);
+ }
+ if(type == Write.class) {
+ return (T) new CryptoWriteFeature(session, (Write) delegate, this);
+ }
+ if(type == MultipartWrite.class) {
+ return (T) new CryptoMultipartWriteFeature(session, (Write) delegate, this);
+ }
+ if(type == Move.class) {
+ return (T) (this.getVersion() == VAULT_VERSION_DEPRECATED ?
+ new CryptoMoveV6Feature(session, (Move) delegate, this) :
+ new CryptoMoveV7Feature(session, (Move) delegate, this));
+
+ }
+ if(type == AttributesFinder.class) {
+ return (T) new CryptoAttributesFeature(session, (AttributesFinder) delegate, this);
+ }
+ if(type == Find.class) {
+ return (T) new CryptoFindFeature(session, (Find) delegate, this);
+ }
+ if(type == UrlProvider.class) {
+ return (T) new CryptoUrlProvider(session, (UrlProvider) delegate, this);
+ }
+ if(type == FileIdProvider.class) {
+ return (T) new CryptoFileIdProvider(session, (FileIdProvider) delegate, this);
+ }
+ if(type == VersionIdProvider.class) {
+ return (T) new CryptoVersionIdProvider(session, (VersionIdProvider) delegate, this);
+ }
+ if(type == Delete.class) {
+ return (T) (this.getVersion() == VAULT_VERSION_DEPRECATED ?
+ new CryptoDeleteV6Feature(session, (Delete) delegate, this) :
+ new CryptoDeleteV7Feature(session, (Delete) delegate, this));
+ }
+ if(type == Trash.class) {
+ return (T) (this.getVersion() == VAULT_VERSION_DEPRECATED ?
+ new CryptoDeleteV6Feature(session, (Delete) delegate, this) :
+ new CryptoDeleteV7Feature(session, (Delete) delegate, this));
+ }
+ if(type == Symlink.class) {
+ return (T) new CryptoSymlinkFeature(session, (Symlink) delegate, this);
+ }
+ if(type == Headers.class) {
+ return (T) new CryptoHeadersFeature(session, (Headers) delegate, this);
+ }
+ if(type == Compress.class) {
+ return (T) new CryptoCompressFeature(session, (Compress) delegate, this);
+ }
+ if(type == Bulk.class) {
+ return (T) new CryptoBulkFeature(session, (Bulk) delegate, session._getFeature(Delete.class), this);
+ }
+ if(type == UnixPermission.class) {
+ return (T) new CryptoUnixPermission(session, (UnixPermission) delegate, this);
+ }
+ if(type == AclPermission.class) {
+ return (T) new CryptoAclPermission(session, (AclPermission) delegate, this);
+ }
+ if(type == Copy.class) {
+ return (T) new CryptoCopyFeature(session, (Copy) delegate, this);
+ }
+ if(type == Timestamp.class) {
+ return (T) new CryptoTimestampFeature(session, (Timestamp) delegate, this);
+ }
+ if(type == Encryption.class) {
+ return (T) new CryptoEncryptionFeature(session, (Encryption) delegate, this);
+ }
+ if(type == Lifecycle.class) {
+ return (T) new CryptoLifecycleFeature(session, (Lifecycle) delegate, this);
+ }
+ if(type == Location.class) {
+ return (T) new CryptoLocationFeature(session, (Location) delegate, this);
+ }
+ if(type == Lock.class) {
+ return (T) new CryptoLockFeature(session, (Lock) delegate, this);
+ }
+ if(type == Logging.class) {
+ return (T) new CryptoLoggingFeature(session, (Logging) delegate, this);
+ }
+ if(type == Redundancy.class) {
+ return (T) new CryptoRedundancyFeature(session, (Redundancy) delegate, this);
+ }
+ if(type == Search.class) {
+ return (T) new CryptoSearchFeature(session, (Search) delegate, this);
+ }
+ if(type == TransferAcceleration.class) {
+ return (T) new CryptoTransferAccelerationFeature<>(session, (TransferAcceleration) delegate, this);
+ }
+ if(type == Versioning.class) {
+ return (T) new CryptoVersioningFeature(session, (Versioning) delegate, this);
+ }
+ }
+ return delegate;
+ }
+}
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoAclPermission.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoAclPermission.java
index eb7c1799ef2..19fe06f5047 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoAclPermission.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoAclPermission.java
@@ -22,16 +22,15 @@
import ch.cyberduck.core.features.AclPermission;
import ch.cyberduck.core.transfer.TransferStatus;
-import java.util.EnumSet;
import java.util.List;
public class CryptoAclPermission implements AclPermission {
private final Session> session;
private final AclPermission delegate;
- private final CryptoVault cryptomator;
+ private final AbstractVault cryptomator;
- public CryptoAclPermission(final Session> session, final AclPermission delegate, final CryptoVault cryptomator) {
+ public CryptoAclPermission(final Session> session, final AclPermission delegate, final AbstractVault cryptomator) {
this.session = session;
this.delegate = delegate;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoTransferStatus.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoTransferStatus.java
index fc5e06559f8..7bdd884b96e 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoTransferStatus.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoTransferStatus.java
@@ -27,9 +27,9 @@
public class CryptoTransferStatus extends ProxyTransferStatus implements StreamCancelation, StreamProgress {
private static final Logger log = LogManager.getLogger(CryptoTransferStatus.class);
- private final CryptoVault vault;
+ private final AbstractVault vault;
- public CryptoTransferStatus(final CryptoVault vault, final TransferStatus proxy) {
+ public CryptoTransferStatus(final AbstractVault vault, final TransferStatus proxy) {
super(proxy);
this.vault = vault;
this.withLength(vault.toCiphertextSize(proxy.getOffset(), proxy.getLength()))
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java
index 42535c125eb..4efa77bdd32 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java
@@ -15,8 +15,19 @@
* GNU General Public License for more details.
*/
-import ch.cyberduck.core.*;
-import ch.cyberduck.core.cryptomator.features.*;
+import ch.cyberduck.core.Credentials;
+import ch.cyberduck.core.DescriptiveUrl;
+import ch.cyberduck.core.Host;
+import ch.cyberduck.core.LocaleFactory;
+import ch.cyberduck.core.LoginOptions;
+import ch.cyberduck.core.PasswordCallback;
+import ch.cyberduck.core.PasswordStore;
+import ch.cyberduck.core.PasswordStoreFactory;
+import ch.cyberduck.core.Path;
+import ch.cyberduck.core.PathAttributes;
+import ch.cyberduck.core.Session;
+import ch.cyberduck.core.SimplePathPredicate;
+import ch.cyberduck.core.UUIDRandomStringService;
import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV6Provider;
import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider;
import ch.cyberduck.core.cryptomator.impl.CryptoFilenameV6Provider;
@@ -26,10 +37,10 @@
import ch.cyberduck.core.exception.LocalAccessDeniedException;
import ch.cyberduck.core.exception.LoginCanceledException;
import ch.cyberduck.core.exception.NotfoundException;
-import ch.cyberduck.core.features.*;
+import ch.cyberduck.core.features.Directory;
+import ch.cyberduck.core.features.Encryption;
import ch.cyberduck.core.preferences.Preferences;
import ch.cyberduck.core.preferences.PreferencesFactory;
-import ch.cyberduck.core.shared.DefaultTouchFeature;
import ch.cyberduck.core.shared.DefaultUrlProvider;
import ch.cyberduck.core.transfer.TransferStatus;
import ch.cyberduck.core.vault.DefaultVaultRegistry;
@@ -38,13 +49,13 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.cryptomator.cryptolib.api.AuthenticationFailedException;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.FileContentCryptor;
import org.cryptomator.cryptolib.api.FileHeaderCryptor;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
+import org.cryptomator.cryptolib.api.PerpetualMasterkey;
import org.cryptomator.cryptolib.common.MasterkeyFile;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
@@ -58,8 +69,6 @@
import java.text.MessageFormat;
import java.util.EnumSet;
import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
@@ -68,7 +77,6 @@
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
-import com.google.common.io.BaseEncoding;
import com.google.gson.JsonParseException;
import static ch.cyberduck.core.vault.DefaultVaultRegistry.DEFAULT_VAULTCONFIG_FILE_NAME;
@@ -76,18 +84,15 @@
/**
* Cryptomator vault implementation
*/
-public class CryptoVault implements Vault {
+// UVF: Keep this as façade for detecting vault version and delegating to implementation
+// - upon create, the vault version is determined from preferences -> set the delegate impl
+// - upon unlock, the vault version needs to be determined by reading masterkey.cryptomator or (!) vault.uvf file -> set the delegate impl
+// - open is called either from create or unlock, hence at this point we can delegate calls to the v6/v7/uvf imple?
+public class CryptoVault extends AbstractVault {
private static final Logger log = LogManager.getLogger(CryptoVault.class);
- public static final int VAULT_VERSION_DEPRECATED = 6;
- public static final int VAULT_VERSION = PreferencesFactory.get().getInteger("cryptomator.vault.version");
public static final byte[] VAULT_PEPPER = PreferencesFactory.get().getProperty("cryptomator.vault.pepper").getBytes(StandardCharsets.UTF_8);
- public static final String DIR_PREFIX = "0";
-
- private static final Pattern BASE32_PATTERN = Pattern.compile("^0?(([A-Z2-7]{8})*[A-Z2-7=]{8})");
- private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+).c9r");
-
private static final String JSON_KEY_VAULTVERSION = "format";
private static final String JSON_KEY_CIPHERCONFIG = "cipherCombo";
private static final String JSON_KEY_SHORTENING_THRESHOLD = "shorteningThreshold";
@@ -114,6 +119,7 @@ public class CryptoVault implements Vault {
private final byte[] pepper;
public CryptoVault(final Path home) {
+ // UVF: readVaultConfig - do we need to try multiple file names for dection "masterkey.cryptomator" and "vault.uvf"?
this(home, DefaultVaultRegistry.DEFAULT_MASTERKEY_FILE_NAME, DEFAULT_VAULTCONFIG_FILE_NAME, VAULT_PEPPER);
}
@@ -121,6 +127,8 @@ public CryptoVault(final Path home, final String masterkey, final String config,
this.home = home;
this.masterkey = new Path(home, masterkey, EnumSet.of(Path.Type.file, Path.Type.vault));
this.config = new Path(home, config, EnumSet.of(Path.Type.file, Path.Type.vault));
+
+ // UVF: no pepper for uvf
this.pepper = pepper;
// New vault home with vault flag set for internal use
final EnumSet type = EnumSet.copyOf(home.getType());
@@ -133,10 +141,13 @@ public CryptoVault(final Path home, final String masterkey, final String config,
}
}
+ // UVF: VaultCredentials must come with specification of recipient, see the recipient header in https://github.com/encryption-alliance/unified-vault-format/tree/develop/vault%20metadata#example-per-recipient-unprotected-header
+ // UVF: version string instead of int?
public synchronized Path create(final Session> session, final VaultCredentials credentials, final int version) throws BackgroundException {
return this.create(session, null, credentials, version);
}
+ // UVF: Switch on version -> CryptoVaultImple: one for v6/v7 and one for uvf
public synchronized Path create(final Session> session, final String region, final VaultCredentials credentials, final int version) throws BackgroundException {
final Host bookmark = session.getHost();
if(credentials.isSaved()) {
@@ -150,7 +161,7 @@ public synchronized Path create(final Session> session, final String region, f
}
final String passphrase = credentials.getPassword();
final ByteArrayOutputStream mkArray = new ByteArrayOutputStream();
- final Masterkey mk = Masterkey.generate(FastSecureRandomProvider.get().provide());
+ final PerpetualMasterkey mk = Masterkey.generate(FastSecureRandomProvider.get().provide());
final MasterkeyFileAccess access = new MasterkeyFileAccess(pepper, FastSecureRandomProvider.get().provide());
final MasterkeyFile masterkeyFile;
try {
@@ -219,6 +230,7 @@ public synchronized CryptoVault load(final Session> session, final PasswordCal
return this.unlock(session, prompt, bookmark, passphrase);
}
+ // UVF: VaultConfig v6/v7 only
private VaultConfig readVaultConfig(final Session> session) throws BackgroundException {
try {
final String token = new ContentReader(session).read(config);
@@ -235,7 +247,7 @@ private VaultConfig readVaultConfig(final Session> session) throws BackgroundE
}
}
-
+ // UVF: v6/v7 specific
public static VaultConfig parseVaultConfigFromJWT(final String token) {
final DecodedJWT decoded = JWT.decode(token);
return new VaultConfig(
@@ -245,6 +257,8 @@ public static VaultConfig parseVaultConfigFromJWT(final String token) {
decoded.getAlgorithm(), decoded);
}
+ // UVF: v6/v7 and vault.uvf are different - can we use the new MasterKey interface from https://github.com/cryptomator/cryptolib/pull/51/files?
+ // called from readVaultConfig() only which is v6/v7 only... good for us!
private MasterkeyFile readMasterkeyFile(final Session> session, final Path masterkey) throws BackgroundException {
log.debug("Read master key {}", masterkey);
try (Reader reader = new ContentReader(session).getReader(masterkey)) {
@@ -256,6 +270,7 @@ private MasterkeyFile readMasterkeyFile(final Session> session, final Path mas
}
public CryptoVault unlock(final Session> session, final PasswordCallback prompt, final Host bookmark, final String passphrase) throws BackgroundException {
+ // UVF: we need to detect the version here, vault.uvf is different from VaultConfig
final VaultConfig vaultConfig = this.readVaultConfig(session);
this.unlock(vaultConfig, passphrase, bookmark, prompt,
MessageFormat.format(LocaleFactory.localizedString("Provide your passphrase to unlock the Cryptomator Vault {0}", "Cryptomator"), home.getName())
@@ -263,6 +278,7 @@ public CryptoVault unlock(final Session> session, final PasswordCallback promp
return this;
}
+ // UVF: extract to v6/v7 and uvf imple
public void unlock(final VaultConfig vaultConfig, final String passphrase, final Host bookmark, final PasswordCallback prompt,
final String message) throws BackgroundException {
final Credentials credentials;
@@ -300,22 +316,12 @@ public void unlock(final VaultConfig vaultConfig, final String passphrase, final
@Override
public synchronized void close() {
- if(this.isUnlocked()) {
- log.info("Close vault with cryptor {}", cryptor);
- if(cryptor != null) {
- cryptor.destroy();
- }
- if(directoryProvider != null) {
- directoryProvider.destroy();
- }
- if(filenameProvider != null) {
- filenameProvider.destroy();
- }
- }
+ super.close();
cryptor = null;
fileNameCryptor = null;
}
+ // UVF: at this point, we have done the version detection, we can directly go to a delegate, no switch
protected CryptoFilename createFilenameProvider(final VaultConfig vaultConfig) {
switch(vaultConfig.version) {
case VAULT_VERSION_DEPRECATED:
@@ -325,24 +331,20 @@ protected CryptoFilename createFilenameProvider(final VaultConfig vaultConfig) {
}
}
- protected CryptoDirectory createDirectoryProvider(final VaultConfig vaultConfig) {
+ protected CryptoDirectory createDirectoryProvider(final VaultConfig vaultConfig, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) {
switch(vaultConfig.version) {
case VAULT_VERSION_DEPRECATED:
- return new CryptoDirectoryV6Provider(vault, this);
+ return new CryptoDirectoryV6Provider(vault, filenameProvider, filenameCryptor);
default:
- return new CryptoDirectoryV7Provider(vault, this);
+ return new CryptoDirectoryV7Provider(vault, filenameProvider, filenameCryptor);
}
}
+ // UVF: extract to v6/v7/uvf, at this point we know which version
protected void open(final VaultConfig vaultConfig, final CharSequence passphrase) throws BackgroundException {
- this.open(vaultConfig, passphrase, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig));
- }
-
- protected void open(final VaultConfig vaultConfig, final CharSequence passphrase,
- final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) throws BackgroundException {
try {
- final Masterkey masterKey = this.getMasterKey(vaultConfig.getMkfile(), passphrase);
- this.open(vaultConfig, masterKey, filenameProvider, directoryProvider);
+ final PerpetualMasterkey masterKey = this.getMasterKey(vaultConfig.getMkfile(), passphrase);
+ this.open(vaultConfig, masterKey);
}
catch(IllegalArgumentException | IOException e) {
throw new VaultException("Failure reading key file", e);
@@ -352,391 +354,83 @@ protected void open(final VaultConfig vaultConfig, final CharSequence passphrase
}
}
- protected void open(final VaultConfig vaultConfig, final Masterkey masterKey) throws BackgroundException {
- this.open(vaultConfig, masterKey, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig));
- }
-
- protected void open(final VaultConfig vaultConfig, final Masterkey masterKey,
- final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) throws BackgroundException {
+ // UVF: unused?!
+ protected void open(final VaultConfig vaultConfig, final PerpetualMasterkey masterKey) throws BackgroundException {
this.vaultVersion = vaultConfig.version;
final CryptorProvider provider = CryptorProvider.forScheme(vaultConfig.getCipherCombo());
log.debug("Initialized crypto provider {}", provider);
vaultConfig.verify(masterKey.getEncoded(), VAULT_VERSION);
this.cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide());
this.fileNameCryptor = new CryptorCache(cryptor.fileNameCryptor());
- this.filenameProvider = filenameProvider;
- this.directoryProvider = directoryProvider;
+ this.filenameProvider = this.createFilenameProvider(vaultConfig);
+ this.directoryProvider = this.createDirectoryProvider(vaultConfig, this.filenameProvider, this.fileNameCryptor);
this.nonceSize = vaultConfig.getNonceSize();
}
- private Masterkey getMasterKey(final MasterkeyFile mkFile, final CharSequence passphrase) throws IOException {
+ private PerpetualMasterkey getMasterKey(final MasterkeyFile mkFile, final CharSequence passphrase) throws IOException {
final StringWriter writer = new StringWriter();
mkFile.write(writer);
return new MasterkeyFileAccess(pepper, FastSecureRandomProvider.get().provide()).load(
new ByteArrayInputStream(writer.getBuffer().toString().getBytes(StandardCharsets.UTF_8)), passphrase);
}
- public synchronized boolean isUnlocked() {
- return cryptor != null;
- }
-
- @Override
- public State getState() {
- return this.isUnlocked() ? State.open : State.closed;
- }
-
- @Override
- public boolean contains(final Path file) {
- if(this.isUnlocked()) {
- return new SimplePathPredicate(file).test(home) || file.isChild(home);
- }
- return false;
+ public Path getHome() {
+ return home;
}
@Override
- public Path encrypt(final Session> session, final Path file) throws BackgroundException {
- return this.encrypt(session, file, file.attributes().getDirectoryId(), false);
+ public Path getMasterkey() {
+ return masterkey;
}
@Override
- public Path encrypt(final Session> session, final Path file, boolean metadata) throws BackgroundException {
- return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata);
- }
-
- public Path encrypt(final Session> session, final Path file, final String directoryId, boolean metadata) throws BackgroundException {
- final Path encrypted;
- if(file.isFile() || metadata) {
- if(file.getType().contains(Path.Type.vault)) {
- log.warn("Skip file {} because it is marked as an internal vault path", file);
- return file;
- }
- if(new SimplePathPredicate(file).test(home)) {
- log.warn("Skip vault home {} because the root has no metadata file", file);
- return file;
- }
- final Path parent;
- final String filename;
- if(file.getType().contains(Path.Type.encrypted)) {
- final Path decrypted = file.attributes().getDecrypted();
- parent = directoryProvider.toEncrypted(session, decrypted.getParent().attributes().getDirectoryId(), decrypted.getParent());
- filename = directoryProvider.toEncrypted(session, parent.attributes().getDirectoryId(), decrypted.getName(), decrypted.getType());
- }
- else {
- parent = directoryProvider.toEncrypted(session, file.getParent().attributes().getDirectoryId(), file.getParent());
- filename = directoryProvider.toEncrypted(session, parent.attributes().getDirectoryId(), file.getName(), file.getType());
- }
- final PathAttributes attributes = new PathAttributes(file.attributes());
- attributes.setDirectoryId(null);
- if(!file.isFile() && !metadata) {
- // The directory is different from the metadata file used to resolve the actual folder
- attributes.setVersionId(null);
- attributes.setFileId(null);
- }
- // Translate file size
- attributes.setSize(this.toCiphertextSize(0L, file.attributes().getSize()));
- final EnumSet type = EnumSet.copyOf(file.getType());
- if(metadata && vaultVersion == VAULT_VERSION_DEPRECATED) {
- type.remove(Path.Type.directory);
- type.add(Path.Type.file);
- }
- type.remove(Path.Type.decrypted);
- type.add(Path.Type.encrypted);
- encrypted = new Path(parent, filename, type, attributes);
- }
- else {
- if(file.getType().contains(Path.Type.encrypted)) {
- log.warn("Skip file {} because it is already marked as an encrypted path", file);
- return file;
- }
- if(file.getType().contains(Path.Type.vault)) {
- return directoryProvider.toEncrypted(session, home.attributes().getDirectoryId(), home);
- }
- encrypted = directoryProvider.toEncrypted(session, directoryId, file);
- }
- // Add reference to decrypted file
- if(!file.getType().contains(Path.Type.encrypted)) {
- encrypted.attributes().setDecrypted(file);
- }
- // Add reference for vault
- file.attributes().setVault(home);
- encrypted.attributes().setVault(home);
- return encrypted;
+ public Path getConfig() {
+ return config;
}
@Override
- public Path decrypt(final Session> session, final Path file) throws BackgroundException {
- if(file.getType().contains(Path.Type.decrypted)) {
- log.warn("Skip file {} because it is already marked as an decrypted path", file);
- return file;
- }
- if(file.getType().contains(Path.Type.vault)) {
- log.warn("Skip file {} because it is marked as an internal vault path", file);
- return file;
- }
- final Path inflated = this.inflate(session, file);
- final Pattern pattern = vaultVersion == VAULT_VERSION_DEPRECATED ? BASE32_PATTERN : BASE64URL_PATTERN;
- final Matcher m = pattern.matcher(inflated.getName());
- if(m.matches()) {
- final String ciphertext = m.group(1);
- try {
- final String cleartextFilename = fileNameCryptor.decryptFilename(
- vaultVersion == VAULT_VERSION_DEPRECATED ? BaseEncoding.base32() : BaseEncoding.base64Url(),
- ciphertext, file.getParent().attributes().getDirectoryId().getBytes(StandardCharsets.UTF_8));
- final PathAttributes attributes = new PathAttributes(file.attributes());
- if(this.isDirectory(inflated)) {
- if(Permission.EMPTY != attributes.getPermission()) {
- final Permission permission = new Permission(attributes.getPermission());
- permission.setUser(permission.getUser().or(Permission.Action.execute));
- permission.setGroup(permission.getGroup().or(Permission.Action.execute));
- permission.setOther(permission.getOther().or(Permission.Action.execute));
- attributes.setPermission(permission);
- }
- // Reset size for folders
- attributes.setSize(-1L);
- attributes.setVersionId(null);
- attributes.setFileId(null);
- }
- else {
- // Translate file size
- attributes.setSize(this.toCleartextSize(0L, file.attributes().getSize()));
- }
- // Add reference to encrypted file
- attributes.setEncrypted(file);
- // Add reference for vault
- attributes.setVault(home);
- final EnumSet type = EnumSet.copyOf(file.getType());
- type.remove(this.isDirectory(inflated) ? Path.Type.file : Path.Type.directory);
- type.add(this.isDirectory(inflated) ? Path.Type.directory : Path.Type.file);
- type.remove(Path.Type.encrypted);
- type.add(Path.Type.decrypted);
- final Path decrypted = new Path(file.getParent().attributes().getDecrypted(), cleartextFilename, type, attributes);
- if(type.contains(Path.Type.symboliclink)) {
- decrypted.setSymlinkTarget(file.getSymlinkTarget());
- }
- return decrypted;
- }
- catch(AuthenticationFailedException e) {
- throw new CryptoAuthenticationException(
- "Failure to decrypt due to an unauthentic ciphertext", e);
- }
- }
- else {
- throw new CryptoFilenameMismatchException(
- String.format("Failure to decrypt %s due to missing pattern match for %s", inflated.getName(), pattern));
- }
- }
-
- private boolean isDirectory(final Path p) {
- if(vaultVersion == VAULT_VERSION_DEPRECATED) {
- return p.getName().startsWith(DIR_PREFIX);
- }
- return p.isDirectory();
+ public Path gethHome() {
+ return home;
}
@Override
- public long toCiphertextSize(final long cleartextFileOffset, final long cleartextFileSize) {
- if(TransferStatus.UNKNOWN_LENGTH == cleartextFileSize) {
- return TransferStatus.UNKNOWN_LENGTH;
- }
- final int headerSize;
- if(0L == cleartextFileOffset) {
- headerSize = cryptor.fileHeaderCryptor().headerSize();
- }
- else {
- headerSize = 0;
- }
- return headerSize + cryptor.fileContentCryptor().ciphertextSize(cleartextFileSize);
+ public int getVersion() {
+ return vaultVersion;
}
@Override
- public long toCleartextSize(final long cleartextFileOffset, final long ciphertextFileSize) throws CryptoInvalidFilesizeException {
- if(TransferStatus.UNKNOWN_LENGTH == ciphertextFileSize) {
- return TransferStatus.UNKNOWN_LENGTH;
- }
- final int headerSize;
- if(0L == cleartextFileOffset) {
- headerSize = cryptor.fileHeaderCryptor().headerSize();
- }
- else {
- headerSize = 0;
- }
- try {
- return cryptor.fileContentCryptor().cleartextSize(ciphertextFileSize - headerSize);
- }
- catch(AssertionError e) {
- throw new CryptoInvalidFilesizeException(String.format("Encrypted file size must be at least %d bytes", headerSize));
- }
- catch(IllegalArgumentException e) {
- throw new CryptoInvalidFilesizeException(String.format("Invalid file size. %s", e.getMessage()));
- }
- }
-
- private Path inflate(final Session> session, final Path file) throws BackgroundException {
- final String fileName = file.getName();
- if(filenameProvider.isDeflated(fileName)) {
- final String filename = filenameProvider.inflate(session, fileName);
- return new Path(file.getParent(), filename, EnumSet.of(Path.Type.file), file.attributes());
- }
- return file;
- }
-
- public Path getHome() {
- return home;
- }
-
- public Path getMasterkey() {
- return masterkey;
- }
-
- public Path getConfig() {
- return config;
- }
-
public FileHeaderCryptor getFileHeaderCryptor() {
return cryptor.fileHeaderCryptor();
}
+ @Override
public FileContentCryptor getFileContentCryptor() {
return cryptor.fileContentCryptor();
}
+ @Override
public CryptorCache getFileNameCryptor() {
return fileNameCryptor;
}
+ @Override
public CryptoFilename getFilenameProvider() {
return filenameProvider;
}
+ @Override
public CryptoDirectory getDirectoryProvider() {
return directoryProvider;
}
- public int getNonceSize() {
- return nonceSize;
- }
-
- public int numberOfChunks(final long cleartextFileSize) {
- return (int) (cleartextFileSize / cryptor.fileContentCryptor().cleartextChunkSize() +
- ((cleartextFileSize % cryptor.fileContentCryptor().cleartextChunkSize() > 0) ? 1 : 0));
+ @Override
+ public Cryptor getCryptor() {
+ return cryptor;
}
@Override
- @SuppressWarnings("unchecked")
- public T getFeature(final Session> session, final Class type, final T delegate) {
- if(this.isUnlocked()) {
- if(type == ListService.class) {
- return (T) new CryptoListService(session, (ListService) delegate, this);
- }
- if(type == Touch.class) {
- // Use default touch feature because touch with remote implementation will not add encrypted file header
- return (T) new CryptoTouchFeature(session, new DefaultTouchFeature(session._getFeature(Write.class)), session._getFeature(Write.class), this);
- }
- if(type == Directory.class) {
- return (T) (vaultVersion == VAULT_VERSION_DEPRECATED ?
- new CryptoDirectoryV6Feature(session, (Directory) delegate, session._getFeature(Write.class), this) :
- new CryptoDirectoryV7Feature(session, (Directory) delegate, session._getFeature(Write.class), this)
- );
- }
- if(type == Upload.class) {
- return (T) new CryptoUploadFeature(session, (Upload) delegate, session._getFeature(Write.class), this);
- }
- if(type == Download.class) {
- return (T) new CryptoDownloadFeature(session, (Download) delegate, session._getFeature(Read.class), this);
- }
- if(type == Read.class) {
- return (T) new CryptoReadFeature(session, (Read) delegate, this);
- }
- if(type == Write.class) {
- return (T) new CryptoWriteFeature(session, (Write) delegate, this);
- }
- if(type == MultipartWrite.class) {
- return (T) new CryptoMultipartWriteFeature(session, (Write) delegate, this);
- }
- if(type == Move.class) {
- return (T) (vaultVersion == VAULT_VERSION_DEPRECATED ?
- new CryptoMoveV6Feature(session, (Move) delegate, this) :
- new CryptoMoveV7Feature(session, (Move) delegate, this));
-
- }
- if(type == AttributesFinder.class) {
- return (T) new CryptoAttributesFeature(session, (AttributesFinder) delegate, this);
- }
- if(type == Find.class) {
- return (T) new CryptoFindFeature(session, (Find) delegate, this);
- }
- if(type == UrlProvider.class) {
- return (T) new CryptoUrlProvider(session, (UrlProvider) delegate, this);
- }
- if(type == FileIdProvider.class) {
- return (T) new CryptoFileIdProvider(session, (FileIdProvider) delegate, this);
- }
- if(type == VersionIdProvider.class) {
- return (T) new CryptoVersionIdProvider(session, (VersionIdProvider) delegate, this);
- }
- if(type == Delete.class) {
- return (T) (vaultVersion == VAULT_VERSION_DEPRECATED ?
- new CryptoDeleteV6Feature(session, (Delete) delegate, this) :
- new CryptoDeleteV7Feature(session, (Delete) delegate, this));
- }
- if(type == Trash.class) {
- return (T) (vaultVersion == VAULT_VERSION_DEPRECATED ?
- new CryptoDeleteV6Feature(session, (Delete) delegate, this) :
- new CryptoDeleteV7Feature(session, (Delete) delegate, this));
- }
- if(type == Symlink.class) {
- return (T) new CryptoSymlinkFeature(session, (Symlink) delegate, this);
- }
- if(type == Headers.class) {
- return (T) new CryptoHeadersFeature(session, (Headers) delegate, this);
- }
- if(type == Compress.class) {
- return (T) new CryptoCompressFeature(session, (Compress) delegate, this);
- }
- if(type == Bulk.class) {
- return (T) new CryptoBulkFeature(session, (Bulk) delegate, session._getFeature(Delete.class), this);
- }
- if(type == UnixPermission.class) {
- return (T) new CryptoUnixPermission(session, (UnixPermission) delegate, this);
- }
- if(type == AclPermission.class) {
- return (T) new CryptoAclPermission(session, (AclPermission) delegate, this);
- }
- if(type == Copy.class) {
- return (T) new CryptoCopyFeature(session, (Copy) delegate, this);
- }
- if(type == Timestamp.class) {
- return (T) new CryptoTimestampFeature(session, (Timestamp) delegate, this);
- }
- if(type == Encryption.class) {
- return (T) new CryptoEncryptionFeature(session, (Encryption) delegate, this);
- }
- if(type == Lifecycle.class) {
- return (T) new CryptoLifecycleFeature(session, (Lifecycle) delegate, this);
- }
- if(type == Location.class) {
- return (T) new CryptoLocationFeature(session, (Location) delegate, this);
- }
- if(type == Lock.class) {
- return (T) new CryptoLockFeature(session, (Lock) delegate, this);
- }
- if(type == Logging.class) {
- return (T) new CryptoLoggingFeature(session, (Logging) delegate, this);
- }
- if(type == Redundancy.class) {
- return (T) new CryptoRedundancyFeature(session, (Redundancy) delegate, this);
- }
- if(type == Search.class) {
- return (T) new CryptoSearchFeature(session, (Search) delegate, this);
- }
- if(type == TransferAcceleration.class) {
- return (T) new CryptoTransferAccelerationFeature<>(session, (TransferAcceleration) delegate, this);
- }
- if(type == Versioning.class) {
- return (T) new CryptoVersioningFeature(session, (Versioning) delegate, this);
- }
- }
- return delegate;
+ public int getNonceSize() {
+ return nonceSize;
}
@Override
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java
new file mode 100644
index 00000000000..44de24b237e
--- /dev/null
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java
@@ -0,0 +1,186 @@
+package ch.cyberduck.core.cryptomator;
+
+/*
+ * Copyright (c) 2002-2025 iterate GmbH. All rights reserved.
+ * https://cyberduck.io/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+import ch.cyberduck.core.PasswordCallback;
+import ch.cyberduck.core.Path;
+import ch.cyberduck.core.PathAttributes;
+import ch.cyberduck.core.Session;
+import ch.cyberduck.core.SimplePathPredicate;
+import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider;
+import ch.cyberduck.core.cryptomator.impl.CryptoFilenameV7Provider;
+import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider;
+import ch.cyberduck.core.exception.BackgroundException;
+import ch.cyberduck.core.vault.VaultCredentials;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.FileContentCryptor;
+import org.cryptomator.cryptolib.api.FileHeaderCryptor;
+import org.cryptomator.cryptolib.api.UVFMasterkey;
+
+import java.util.EnumSet;
+import java.util.Objects;
+
+public class UVFVault extends AbstractVault {
+
+ private static final Logger log = LogManager.getLogger(UVFVault.class);
+
+ /**
+ * Root of vault directory
+ */
+ private final Path home;
+ private final Path vault;
+
+ private final String decrypted;
+ private Cryptor cryptor;
+ private CryptorCache fileNameCryptor;
+ private CryptoFilename filenameProvider;
+ private CryptoDirectory directoryProvider;
+
+ private int nonceSize;
+
+ public UVFVault(final Path home, final String decryptedPayload) {
+ this.home = home;
+ this.decrypted = decryptedPayload;
+ // New vault home with vault flag set for internal use
+ final EnumSet type = EnumSet.copyOf(home.getType());
+ type.add(Path.Type.vault);
+ if(home.isRoot()) {
+ this.vault = new Path(home.getAbsolute(), type, new PathAttributes(home.attributes()));
+ }
+ else {
+ this.vault = new Path(home.getParent(), home.getName(), type, new PathAttributes(home.attributes()));
+ }
+ }
+
+ @Override
+ public Path create(final Session> session, final String region, final VaultCredentials credentials) throws BackgroundException {
+ throw new UnsupportedOperationException();
+ }
+
+ // load -> unlock -> open
+ @Override
+ public UVFVault load(final Session> session, final PasswordCallback prompt) throws BackgroundException {
+ UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(this.decrypted);
+
+ final CryptorProvider provider = CryptorProvider.forScheme(CryptorProvider.Scheme.UVF_DRAFT);
+ log.debug("Initialized crypto provider {}", provider);
+ this.cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide());
+ this.fileNameCryptor = new CryptorCache(cryptor.fileNameCryptor());
+ this.filenameProvider = new CryptoFilenameV7Provider(/* TODO threshold was previously defined in vault.config - default now? */);
+ this.directoryProvider = new CryptoDirectoryV7Provider(vault, filenameProvider, fileNameCryptor);
+ this.nonceSize = 12;
+ return this;
+ }
+
+ @Override
+ public synchronized void close() {
+ super.close();
+ cryptor = null;
+ fileNameCryptor = null;
+ }
+
+ @Override
+ public Path getMasterkey() {
+ //TODO: implement
+ return null;
+ }
+
+ @Override
+ public Path getConfig() {
+ //TODO: implement
+ return null;
+ }
+
+ @Override
+ public Path gethHome() {
+ return home;
+ }
+
+ @Override
+ public FileHeaderCryptor getFileHeaderCryptor() {
+ return cryptor.fileHeaderCryptor();
+ }
+
+ @Override
+ public FileContentCryptor getFileContentCryptor() {
+ return cryptor.fileContentCryptor();
+ }
+
+ @Override
+ public CryptorCache getFileNameCryptor() {
+ return fileNameCryptor;
+ }
+
+ @Override
+ public CryptoFilename getFilenameProvider() {
+ return filenameProvider;
+ }
+
+ @Override
+ public CryptoDirectory getDirectoryProvider() {
+ return directoryProvider;
+ }
+
+ @Override
+ public Cryptor getCryptor() {
+ return cryptor;
+ }
+
+ @Override
+ public int getNonceSize() {
+ return nonceSize;
+ }
+
+ @Override
+ public int getVersion() {
+ return VAULT_VERSION;
+ }
+
+ @Override
+ public Path getHome() {
+ return home;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if(this == o) {
+ return true;
+ }
+ if(!(o instanceof UVFVault)) {
+ return false;
+ }
+ final UVFVault that = (UVFVault) o;
+ return new SimplePathPredicate(home).test(that.home);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(new SimplePathPredicate(home));
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("UVFVault{");
+ sb.append("home=").append(home);
+ sb.append(", cryptor=").append(cryptor);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeature.java
index c16210fc644..1d8947823e4 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeature.java
@@ -21,7 +21,7 @@
import ch.cyberduck.core.RandomStringService;
import ch.cyberduck.core.Session;
import ch.cyberduck.core.UUIDRandomStringService;
-import ch.cyberduck.core.cryptomator.CryptoVault;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator;
import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator;
import ch.cyberduck.core.exception.BackgroundException;
@@ -46,9 +46,9 @@ public class CryptoBulkFeature implements Bulk {
private final Session> session;
private final Bulk delegate;
- private final CryptoVault cryptomator;
+ private final AbstractVault cryptomator;
- public CryptoBulkFeature(final Session> session, final Bulk delegate, final Delete delete, final CryptoVault cryptomator) {
+ public CryptoBulkFeature(final Session> session, final Bulk delegate, final Delete delete, final AbstractVault cryptomator) {
this.session = session;
this.delegate = delegate.withDelete(cryptomator.getFeature(session, Delete.class, delete));
this.cryptomator = cryptomator;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoChecksumCompute.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoChecksumCompute.java
index 51d4dc0c635..683b614fbda 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoChecksumCompute.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoChecksumCompute.java
@@ -15,8 +15,8 @@
* GNU General Public License for more details.
*/
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.CryptoOutputStream;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator;
import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator;
import ch.cyberduck.core.exception.BackgroundException;
@@ -55,11 +55,11 @@
public class CryptoChecksumCompute extends AbstractChecksumCompute {
private static final Logger log = LogManager.getLogger(CryptoChecksumCompute.class);
- private final CryptoVault cryptomator;
+ private final AbstractVault cryptomator;
private final ChecksumCompute delegate;
- public CryptoChecksumCompute(final ChecksumCompute delegate, final CryptoVault vault) {
- this.cryptomator = vault;
+ public CryptoChecksumCompute(final ChecksumCompute delegate, final AbstractVault CryptoVaultInterface) {
+ this.cryptomator = CryptoVaultInterface;
this.delegate = delegate;
}
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoCopyFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoCopyFeature.java
index 5cbb40a94e9..cc0733ed38f 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoCopyFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoCopyFeature.java
@@ -19,7 +19,7 @@
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathAttributes;
import ch.cyberduck.core.Session;
-import ch.cyberduck.core.cryptomator.CryptoVault;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator;
import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator;
import ch.cyberduck.core.exception.BackgroundException;
@@ -36,11 +36,11 @@ public class CryptoCopyFeature implements Copy {
private final Session> session;
private final Copy proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
private Session> target;
- public CryptoCopyFeature(final Session> session, final Copy proxy, final CryptoVault vault) {
+ public CryptoCopyFeature(final Session> session, final Copy proxy, final AbstractVault vault) {
this.session = session;
this.target = session;
this.proxy = proxy;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV6Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV6Feature.java
index aa20ce52cff..dd5516d05de 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV6Feature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV6Feature.java
@@ -20,8 +20,8 @@
import ch.cyberduck.core.PasswordCallback;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.CryptoFilename;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.exception.AccessDeniedException;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.NotfoundException;
@@ -44,10 +44,10 @@ public class CryptoDeleteV6Feature implements Delete, Trash {
private final Session> session;
private final Delete proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
private final CryptoFilename filenameProvider;
- public CryptoDeleteV6Feature(final Session> session, final Delete proxy, final CryptoVault vault) {
+ public CryptoDeleteV6Feature(final Session> session, final Delete proxy, final AbstractVault vault) {
this.session = session;
this.proxy = proxy;
this.vault = vault;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV7Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV7Feature.java
index 611988cb485..b0a1f18a75a 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV7Feature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV7Feature.java
@@ -21,8 +21,8 @@
import ch.cyberduck.core.PasswordCallback;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.CryptoFilename;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider;
import ch.cyberduck.core.exception.AccessDeniedException;
import ch.cyberduck.core.exception.BackgroundException;
@@ -46,10 +46,10 @@ public class CryptoDeleteV7Feature implements Delete, Trash {
private final Session> session;
private final Delete proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
private final CryptoFilename filenameProvider;
- public CryptoDeleteV7Feature(final Session> session, final Delete proxy, final CryptoVault vault) {
+ public CryptoDeleteV7Feature(final Session> session, final Delete proxy, final AbstractVault vault) {
this.session = session;
this.proxy = proxy;
this.vault = vault;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV6Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV6Feature.java
index 5efb75be384..2065d2e22f3 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV6Feature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV6Feature.java
@@ -19,8 +19,8 @@
import ch.cyberduck.core.RandomStringService;
import ch.cyberduck.core.Session;
import ch.cyberduck.core.UUIDRandomStringService;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.ContentWriter;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Directory;
@@ -40,11 +40,11 @@ public class CryptoDirectoryV6Feature implements Directory {
private final Session> session;
private final Write writer;
private final Directory delegate;
- private final CryptoVault vault;
+ private final AbstractVault vault;
private final RandomStringService random = new UUIDRandomStringService();
public CryptoDirectoryV6Feature(final Session> session, final Directory delegate,
- final Write writer, final CryptoVault cryptomator) {
+ final Write writer, final AbstractVault cryptomator) {
this.session = session;
this.writer = writer;
this.delegate = delegate;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV7Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV7Feature.java
index 17547e2793c..45e85fa7be1 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV7Feature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV7Feature.java
@@ -19,8 +19,8 @@
import ch.cyberduck.core.RandomStringService;
import ch.cyberduck.core.Session;
import ch.cyberduck.core.UUIDRandomStringService;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.ContentWriter;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider;
import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator;
import ch.cyberduck.core.exception.BackgroundException;
@@ -42,11 +42,11 @@ public class CryptoDirectoryV7Feature implements Directory {
private final Session> session;
private final Write writer;
private final Directory delegate;
- private final CryptoVault vault;
+ private final AbstractVault vault;
private final RandomStringService random = new UUIDRandomStringService();
public CryptoDirectoryV7Feature(final Session> session, final Directory delegate,
- final Write writer, final CryptoVault cryptomator) {
+ final Write writer, final AbstractVault cryptomator) {
this.session = session;
this.writer = writer;
this.delegate = delegate;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDownloadFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDownloadFeature.java
index dc803e4c911..a9ce5863e45 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDownloadFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDownloadFeature.java
@@ -19,12 +19,11 @@
import ch.cyberduck.core.Local;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
-import ch.cyberduck.core.cryptomator.CryptoVault;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.NotfoundException;
import ch.cyberduck.core.features.Download;
import ch.cyberduck.core.features.Read;
-import ch.cyberduck.core.features.Vault;
import ch.cyberduck.core.io.BandwidthThrottle;
import ch.cyberduck.core.io.StreamListener;
import ch.cyberduck.core.transfer.TransferStatus;
@@ -33,9 +32,9 @@ public class CryptoDownloadFeature implements Download {
private final Session> session;
private final Download proxy;
- private final Vault vault;
+ private final AbstractVault vault;
- public CryptoDownloadFeature(final Session> session, final Download proxy, final Read reader, final CryptoVault vault) {
+ public CryptoDownloadFeature(final Session> session, final Download proxy, final Read reader, final AbstractVault vault) {
this.session = session;
this.proxy = proxy.withReader(new CryptoReadFeature(session, reader, vault));
this.vault = vault;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoEncryptionFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoEncryptionFeature.java
index 9f30a97b1be..6dc07f6edf2 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoEncryptionFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoEncryptionFeature.java
@@ -18,7 +18,6 @@
import ch.cyberduck.core.LoginCallback;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Encryption;
import ch.cyberduck.core.features.Vault;
@@ -31,7 +30,7 @@ public class CryptoEncryptionFeature implements Encryption {
private final Encryption delegate;
private final Vault vault;
- public CryptoEncryptionFeature(final Session> session, final Encryption delegate, final CryptoVault vault) {
+ public CryptoEncryptionFeature(final Session> session, final Encryption delegate, final Vault vault) {
this.session = session;
this.delegate = delegate;
this.vault = vault;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java
index 20612ca011b..04b9cc14968 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java
@@ -19,7 +19,7 @@
import ch.cyberduck.core.LocaleFactory;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
-import ch.cyberduck.core.cryptomator.CryptoVault;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.InvalidFilenameException;
import ch.cyberduck.core.features.Delete;
@@ -33,9 +33,9 @@ public class CryptoMoveV6Feature implements Move {
private final Session> session;
private final Move proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
- public CryptoMoveV6Feature(final Session> session, final Move delegate, final CryptoVault cryptomator) {
+ public CryptoMoveV6Feature(final Session> session, final Move delegate, final AbstractVault cryptomator) {
this.session = session;
this.proxy = delegate;
this.vault = cryptomator;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java
index 433e6b9c152..c302318cfaa 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java
@@ -19,7 +19,7 @@
import ch.cyberduck.core.LocaleFactory;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
-import ch.cyberduck.core.cryptomator.CryptoVault;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.InvalidFilenameException;
@@ -34,9 +34,9 @@ public class CryptoMoveV7Feature implements Move {
private final Session> session;
private final Move proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
- public CryptoMoveV7Feature(final Session> session, final Move delegate, final CryptoVault cryptomator) {
+ public CryptoMoveV7Feature(final Session> session, final Move delegate, final AbstractVault cryptomator) {
this.session = session;
this.proxy = delegate;
this.vault = cryptomator;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMultipartWriteFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMultipartWriteFeature.java
index 4943f838a28..f5d6de84717 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMultipartWriteFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMultipartWriteFeature.java
@@ -16,18 +16,18 @@
*/
import ch.cyberduck.core.Session;
-import ch.cyberduck.core.cryptomator.CryptoVault;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.features.AttributesFinder;
import ch.cyberduck.core.features.Find;
import ch.cyberduck.core.features.MultipartWrite;
import ch.cyberduck.core.features.Write;
public class CryptoMultipartWriteFeature extends CryptoWriteFeature implements MultipartWrite {
- public CryptoMultipartWriteFeature(final Session> session, final Write delegate, final CryptoVault vault) {
+ public CryptoMultipartWriteFeature(final Session> session, final Write delegate, final AbstractVault vault) {
super(session, delegate, vault);
}
- public CryptoMultipartWriteFeature(final Session> session, final Write delegate, final Find finder, final AttributesFinder attributes, final CryptoVault vault) {
+ public CryptoMultipartWriteFeature(final Session> session, final Write delegate, final Find finder, final AttributesFinder attributes, final AbstractVault vault) {
super(session, delegate, vault);
}
}
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoReadFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoReadFeature.java
index ebfb913d119..779259f65d4 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoReadFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoReadFeature.java
@@ -19,8 +19,8 @@
import ch.cyberduck.core.DefaultIOExceptionMappingService;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.CryptoInputStream;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Read;
import ch.cyberduck.core.transfer.TransferStatus;
@@ -36,9 +36,9 @@ public class CryptoReadFeature implements Read {
private final Session> session;
private final Read proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
- public CryptoReadFeature(final Session> session, final Read proxy, final CryptoVault vault) {
+ public CryptoReadFeature(final Session> session, final Read proxy, final AbstractVault vault) {
this.session = session;
this.proxy = proxy;
this.vault = vault;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTimestampFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTimestampFeature.java
index 9db63f0ae3d..437a3c0697e 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTimestampFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTimestampFeature.java
@@ -17,8 +17,8 @@
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.CryptoTransferStatus;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Timestamp;
import ch.cyberduck.core.transfer.TransferStatus;
@@ -27,9 +27,9 @@ public class CryptoTimestampFeature implements Timestamp {
private final Session> session;
private final Timestamp proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
- public CryptoTimestampFeature(final Session> session, final Timestamp proxy, final CryptoVault vault) {
+ public CryptoTimestampFeature(final Session> session, final Timestamp proxy, final AbstractVault vault) {
this.session = session;
this.proxy = proxy;
this.vault = vault;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTouchFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTouchFeature.java
index 4fe570196a1..09298d73a72 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTouchFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTouchFeature.java
@@ -19,7 +19,7 @@
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathAttributes;
import ch.cyberduck.core.Session;
-import ch.cyberduck.core.cryptomator.CryptoVault;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.InvalidFilenameException;
@@ -35,9 +35,9 @@ public class CryptoTouchFeature implements Touch {
private final Session> session;
private final Touch proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
- public CryptoTouchFeature(final Session> session, final Touch proxy, final Write writer, final CryptoVault cryptomator) {
+ public CryptoTouchFeature(final Session> session, final Touch proxy, final Write writer, final AbstractVault cryptomator) {
this.session = session;
this.proxy = proxy.withWriter(new CryptoWriteFeature<>(session, writer, cryptomator));
this.vault = cryptomator;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoUploadFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoUploadFeature.java
index 4c677786be3..98047737d2c 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoUploadFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoUploadFeature.java
@@ -20,8 +20,8 @@
import ch.cyberduck.core.Path;
import ch.cyberduck.core.ProgressListener;
import ch.cyberduck.core.Session;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.CryptoTransferStatus;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Upload;
import ch.cyberduck.core.features.Write;
@@ -33,9 +33,9 @@ public class CryptoUploadFeature implements Upload {
private final Session> session;
private final Upload proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
- public CryptoUploadFeature(final Session> session, final Upload delegate, final Write writer, final CryptoVault vault) {
+ public CryptoUploadFeature(final Session> session, final Upload delegate, final Write writer, final AbstractVault vault) {
this.session = session;
this.proxy = delegate.withWriter(new CryptoWriteFeature<>(session, writer, vault));
this.vault = vault;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java
index 097729d6b7b..49145e57d8b 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java
@@ -19,9 +19,9 @@
import ch.cyberduck.core.DefaultIOExceptionMappingService;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Session;
+import ch.cyberduck.core.cryptomator.AbstractVault;
import ch.cyberduck.core.cryptomator.CryptoOutputStream;
import ch.cyberduck.core.cryptomator.CryptoTransferStatus;
-import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator;
import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator;
import ch.cyberduck.core.exception.BackgroundException;
@@ -42,9 +42,9 @@ public class CryptoWriteFeature implements Write {
private final Session> session;
private final Write proxy;
- private final CryptoVault vault;
+ private final AbstractVault vault;
- public CryptoWriteFeature(final Session> session, final Write proxy, final CryptoVault vault) {
+ public CryptoWriteFeature(final Session> session, final Write proxy, final AbstractVault vault) {
this.session = session;
this.proxy = proxy;
this.vault = vault;
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6Provider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6Provider.java
index 8efb36c5f69..9ad0728a9c4 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6Provider.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6Provider.java
@@ -25,6 +25,7 @@
import ch.cyberduck.core.cache.LRUCache;
import ch.cyberduck.core.cryptomator.ContentReader;
import ch.cyberduck.core.cryptomator.CryptoDirectory;
+import ch.cyberduck.core.cryptomator.CryptoFilename;
import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.cryptomator.CryptorCache;
import ch.cyberduck.core.exception.BackgroundException;
@@ -48,28 +49,30 @@ public class CryptoDirectoryV6Provider implements CryptoDirectory {
private final Path dataRoot;
private final Path home;
- private final CryptoVault cryptomator;
+ private final CryptoFilename filenameProvider;
+ private final CryptorCache filenameCryptor;
private final RandomStringService random
- = new UUIDRandomStringService();
+ = new UUIDRandomStringService();
private final Lock lock = new ReentrantLock();
private final LRUCache, String> cache = LRUCache.build(
- PreferencesFactory.get().getInteger("cryptomator.cache.size"));
+ PreferencesFactory.get().getInteger("cryptomator.cache.size"));
- public CryptoDirectoryV6Provider(final Path vault, final CryptoVault cryptomator) {
+ public CryptoDirectoryV6Provider(final Path vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) {
this.home = vault;
this.dataRoot = new Path(vault, DATA_DIR_NAME, vault.getType());
- this.cryptomator = cryptomator;
+ this.filenameProvider = filenameProvider;
+ this.filenameCryptor = filenameCryptor;
}
@Override
public String toEncrypted(final Session> session, final String directoryId, final String filename, final EnumSet type) throws BackgroundException {
final String prefix = type.contains(Path.Type.directory) ? CryptoVault.DIR_PREFIX : "";
- final String ciphertextName = prefix + cryptomator.getFileNameCryptor().encryptFilename(CryptorCache.BASE32, filename, directoryId.getBytes(StandardCharsets.UTF_8));
+ final String ciphertextName = prefix + filenameCryptor.encryptFilename(CryptorCache.BASE32, filename, directoryId.getBytes(StandardCharsets.UTF_8));
log.debug("Encrypted filename {} to {}", filename, ciphertextName);
- return cryptomator.getFilenameProvider().deflate(session, ciphertextName);
+ return filenameProvider.deflate(session, ciphertextName);
}
@Override
@@ -87,7 +90,7 @@ public Path toEncrypted(final Session> session, final String directoryId, fina
log.debug("Use directory ID '{}' for folder {}", id, directory);
attributes.setDirectoryId(id);
attributes.setDecrypted(directory);
- final String directoryIdHash = cryptomator.getFileNameCryptor().hashDirectoryId(id);
+ final String directoryIdHash = filenameCryptor.hashDirectoryId(id);
// Intermediate directory
final Path intermediate = new Path(dataRoot, directoryIdHash.substring(0, 2), dataRoot.getType());
// Add encrypted type
diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7Provider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7Provider.java
index 28b4bb6211d..cfb9a8b55e2 100644
--- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7Provider.java
+++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7Provider.java
@@ -20,7 +20,8 @@
import ch.cyberduck.core.Session;
import ch.cyberduck.core.UUIDRandomStringService;
import ch.cyberduck.core.cryptomator.ContentReader;
-import ch.cyberduck.core.cryptomator.CryptoVault;
+import ch.cyberduck.core.cryptomator.CryptoFilename;
+import ch.cyberduck.core.cryptomator.CryptorCache;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.NotfoundException;
@@ -41,22 +42,24 @@ public class CryptoDirectoryV7Provider extends CryptoDirectoryV6Provider {
public static final String BACKUP_FILENAME_DIRECTORYID = "dirid";
public static final String BACKUP_DIRECTORY_METADATAFILE = String.format("%s%s", BACKUP_FILENAME_DIRECTORYID, EXTENSION_REGULAR);
- private final CryptoVault cryptomator;
+ private final CryptoFilename filenameProvider;
+ private final CryptorCache filenameCryptor;
private final RandomStringService random
- = new UUIDRandomStringService();
+ = new UUIDRandomStringService();
- public CryptoDirectoryV7Provider(final Path vault, final CryptoVault cryptomator) {
- super(vault, cryptomator);
- this.cryptomator = cryptomator;
+ public CryptoDirectoryV7Provider(final Path vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) {
+ super(vault, filenameProvider, filenameCryptor);
+ this.filenameProvider = filenameProvider;
+ this.filenameCryptor = filenameCryptor;
}
@Override
public String toEncrypted(final Session> session, final String directoryId, final String filename, final EnumSet type) throws BackgroundException {
- final String ciphertextName = cryptomator.getFileNameCryptor().encryptFilename(BaseEncoding.base64Url(),
- filename, directoryId.getBytes(StandardCharsets.UTF_8)) + EXTENSION_REGULAR;
+ final String ciphertextName = filenameCryptor.encryptFilename(BaseEncoding.base64Url(),
+ filename, directoryId.getBytes(StandardCharsets.UTF_8)) + EXTENSION_REGULAR;
log.debug("Encrypted filename {} to {}", filename, ciphertextName);
- return cryptomator.getFilenameProvider().deflate(session, ciphertextName);
+ return filenameProvider.deflate(session, ciphertextName);
}
protected String load(final Session> session, final Path directory) throws BackgroundException {
diff --git a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptoVaultTest.java b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptoVaultTest.java
index 4bcfc9c8d12..59f21686881 100644
--- a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptoVaultTest.java
+++ b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptoVaultTest.java
@@ -41,7 +41,7 @@
import org.apache.commons.io.IOUtils;
import org.cryptomator.cryptolib.api.CryptorProvider;
-import org.cryptomator.cryptolib.api.Masterkey;
+import org.cryptomator.cryptolib.api.PerpetualMasterkey;
import org.cryptomator.cryptolib.common.MasterkeyFile;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.junit.Test;
@@ -563,7 +563,7 @@ public static String createJWT(final String masterkeyCryptomator,
final MasterkeyFile mkFile = MasterkeyFile.read(new StringReader(masterkeyCryptomator));
final StringWriter writer = new StringWriter();
mkFile.write(writer);
- final Masterkey masterkey = new MasterkeyFileAccess(PreferencesFactory.get().getProperty("cryptomator.vault.pepper").getBytes(StandardCharsets.UTF_8),
+ final PerpetualMasterkey masterkey = new MasterkeyFileAccess(PreferencesFactory.get().getProperty("cryptomator.vault.pepper").getBytes(StandardCharsets.UTF_8),
FastSecureRandomProvider.get().provide()).load(new ByteArrayInputStream(writer.getBuffer().toString().getBytes(StandardCharsets.UTF_8)), passphrase);
final Algorithm algorithm = Algorithm.HMAC256(masterkey.getEncoded());
return JWT.create()
diff --git a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6ProviderTest.java b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6ProviderTest.java
index a2f78fa324e..f4b09439953 100644
--- a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6ProviderTest.java
+++ b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6ProviderTest.java
@@ -15,27 +15,20 @@
* GNU General Public License for more details.
*/
-import ch.cyberduck.core.ConnectionCallback;
-import ch.cyberduck.core.Credentials;
-import ch.cyberduck.core.DisabledPasswordCallback;
import ch.cyberduck.core.Host;
-import ch.cyberduck.core.LoginOptions;
import ch.cyberduck.core.NullSession;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.TestProtocol;
import ch.cyberduck.core.cryptomator.CryptoDirectory;
-import ch.cyberduck.core.cryptomator.CryptoVault;
-import ch.cyberduck.core.exception.BackgroundException;
+import ch.cyberduck.core.cryptomator.CryptorCache;
import ch.cyberduck.core.exception.NotfoundException;
-import ch.cyberduck.core.features.Read;
-import ch.cyberduck.core.transfer.TransferStatus;
-import ch.cyberduck.core.vault.VaultCredentials;
-import org.apache.commons.io.IOUtils;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.PerpetualMasterkey;
import org.junit.Test;
-import java.io.InputStream;
-import java.nio.charset.Charset;
+import java.security.SecureRandom;
import java.util.EnumSet;
import static org.junit.Assert.assertEquals;
@@ -46,62 +39,31 @@ public class CryptoDirectoryV6ProviderTest {
@Test(expected = NotfoundException.class)
public void testToEncryptedInvalidArgument() throws Exception {
final Path home = new Path("/vault", EnumSet.of(Path.Type.directory));
- final CryptoVault vault = new CryptoVault(home);
- final CryptoDirectory provider = new CryptoDirectoryV6Provider(home, vault);
+ final CryptorProvider crypto = CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_CTRMAC);
+ final SecureRandom random = new SecureRandom();
+ final Cryptor cryptor = crypto.provide(PerpetualMasterkey.generate(random), random);
+ final CryptoDirectory provider = new CryptoDirectoryV6Provider(home, new CryptoFilenameV6Provider(home), new CryptorCache(cryptor.fileNameCryptor()));
provider.toEncrypted(new NullSession(new Host(new TestProtocol())), null, new Path("/vault/f", EnumSet.of(Path.Type.file)));
}
@Test(expected = NotfoundException.class)
public void testToEncryptedInvalidPath() throws Exception {
final Path home = new Path("/vault", EnumSet.of(Path.Type.directory));
- final CryptoVault vault = new CryptoVault(home);
- final CryptoDirectory provider = new CryptoDirectoryV6Provider(home, vault);
+ final CryptorProvider crypto = CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_CTRMAC);
+ final SecureRandom random = new SecureRandom();
+ final Cryptor cryptor = crypto.provide(PerpetualMasterkey.generate(random), random);
+ final CryptoDirectory provider = new CryptoDirectoryV6Provider(home, new CryptoFilenameV6Provider(home), new CryptorCache(cryptor.fileNameCryptor()));
provider.toEncrypted(new NullSession(new Host(new TestProtocol())), null, new Path("/", EnumSet.of(Path.Type.directory)));
}
@Test
public void testToEncryptedDirectory() throws Exception {
final Path home = new Path("/vault", EnumSet.of(Path.Type.directory));
- final NullSession session = new NullSession(new Host(new TestProtocol())) {
- @Override
- @SuppressWarnings("unchecked")
- public T _getFeature(final Class type) {
- if(type == Read.class) {
- return (T) new Read() {
- @Override
- public InputStream read(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
- final String masterKey = "{\n" +
- " \"scryptSalt\": \"NrC7QGG/ouc=\",\n" +
- " \"scryptCostParam\": 16384,\n" +
- " \"scryptBlockSize\": 8,\n" +
- " \"primaryMasterKey\": \"Q7pGo1l0jmZssoQh9rXFPKJE9NIXvPbL+HcnVSR9CHdkeR8AwgFtcw==\",\n" +
- " \"hmacMasterKey\": \"xzBqT4/7uEcQbhHFLC0YmMy4ykVKbuvJEA46p1Xm25mJNuTc20nCbw==\",\n" +
- " \"versionMac\": \"hlNr3dz/CmuVajhaiGyCem9lcVIUjDfSMLhjppcXOrM=\",\n" +
- " \"version\": 5\n" +
- "}";
- if("masterkey.cryptomator".equals(file.getName())) {
- return IOUtils.toInputStream(masterKey, Charset.defaultCharset());
- }
- throw new NotfoundException(String.format("%s not found", file.getName()));
- }
-
- @Override
- public boolean offset(final Path file) {
- return false;
- }
- };
- }
- return super._getFeature(type);
- }
- };
- final CryptoVault vault = new CryptoVault(home);
- vault.load(session, new DisabledPasswordCallback() {
- @Override
- public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
- return new VaultCredentials("vault");
- }
- });
- final CryptoDirectory provider = new CryptoDirectoryV6Provider(home, vault);
+ final NullSession session = new NullSession(new Host(new TestProtocol()));
+ final CryptorProvider crypto = CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_CTRMAC);
+ final SecureRandom csprng = new SecureRandom();
+ final Cryptor cryptor = crypto.provide(PerpetualMasterkey.generate(csprng), csprng);
+ final CryptoDirectory provider = new CryptoDirectoryV6Provider(home, new CryptoFilenameV6Provider(home), new CryptorCache(cryptor.fileNameCryptor()));
assertNotNull(provider.toEncrypted(session, null, home));
final Path f = new Path("/vault/f", EnumSet.of(Path.Type.directory));
assertNotNull(provider.toEncrypted(session, null, f));
diff --git a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7ProviderTest.java b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7ProviderTest.java
index ee372bed4d0..7ad14d49335 100644
--- a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7ProviderTest.java
+++ b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7ProviderTest.java
@@ -15,32 +15,22 @@
* GNU General Public License for more details.
*/
-import ch.cyberduck.core.ConnectionCallback;
-import ch.cyberduck.core.Credentials;
-import ch.cyberduck.core.DisabledPasswordCallback;
import ch.cyberduck.core.Host;
-import ch.cyberduck.core.LoginOptions;
import ch.cyberduck.core.NullSession;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.TestProtocol;
import ch.cyberduck.core.cryptomator.CryptoDirectory;
-import ch.cyberduck.core.cryptomator.CryptoVault;
-import ch.cyberduck.core.exception.BackgroundException;
+import ch.cyberduck.core.cryptomator.CryptorCache;
import ch.cyberduck.core.exception.NotfoundException;
-import ch.cyberduck.core.features.Read;
-import ch.cyberduck.core.transfer.TransferStatus;
-import ch.cyberduck.core.vault.VaultCredentials;
-import org.apache.commons.io.IOUtils;
+import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.PerpetualMasterkey;
import org.junit.Test;
-import java.io.InputStream;
-import java.nio.charset.Charset;
+import java.security.SecureRandom;
import java.util.EnumSet;
-import static ch.cyberduck.core.cryptomator.CryptoVault.VAULT_VERSION;
-import static ch.cyberduck.core.cryptomator.CryptoVaultTest.createJWT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -49,65 +39,31 @@ public class CryptoDirectoryV7ProviderTest {
@Test(expected = NotfoundException.class)
public void testToEncryptedInvalidArgument() throws Exception {
final Path home = new Path("/vault", EnumSet.of(Path.Type.directory));
- final CryptoVault vault = new CryptoVault(home);
- final CryptoDirectory provider = new CryptoDirectoryV7Provider(home, vault);
+ final CryptorProvider crypto = CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_GCM);
+ final SecureRandom random = new SecureRandom();
+ final Cryptor cryptor = crypto.provide(PerpetualMasterkey.generate(random), random);
+ final CryptoDirectory provider = new CryptoDirectoryV7Provider(home, new CryptoFilenameV7Provider(), new CryptorCache(cryptor.fileNameCryptor()));
provider.toEncrypted(new NullSession(new Host(new TestProtocol())), null, new Path("/vault/f", EnumSet.of(Path.Type.file)));
}
@Test(expected = NotfoundException.class)
public void testToEncryptedInvalidPath() throws Exception {
final Path home = new Path("/vault", EnumSet.of(Path.Type.directory));
- final CryptoVault vault = new CryptoVault(home);
- final CryptoDirectory provider = new CryptoDirectoryV7Provider(home, vault);
+ final CryptorProvider crypto = CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_GCM);
+ final SecureRandom random = new SecureRandom();
+ final Cryptor cryptor = crypto.provide(PerpetualMasterkey.generate(random), random);
+ final CryptoDirectory provider = new CryptoDirectoryV7Provider(home, new CryptoFilenameV7Provider(), new CryptorCache(cryptor.fileNameCryptor()));
provider.toEncrypted(new NullSession(new Host(new TestProtocol())), null, new Path("/", EnumSet.of(Path.Type.directory)));
}
@Test
public void testToEncryptedDirectory() throws Exception {
final Path home = new Path("/vault", EnumSet.of(Path.Type.directory));
- final NullSession session = new NullSession(new Host(new TestProtocol())) {
- @Override
- @SuppressWarnings("unchecked")
- public T _getFeature(final Class type) {
- if(type == Read.class) {
- return (T) new Read() {
- @Override
- public InputStream read(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
- final String masterKey = "{\n" +
- " \"scryptSalt\": \"NrC7QGG/ouc=\",\n" +
- " \"scryptCostParam\": 16384,\n" +
- " \"scryptBlockSize\": 8,\n" +
- " \"primaryMasterKey\": \"Q7pGo1l0jmZssoQh9rXFPKJE9NIXvPbL+HcnVSR9CHdkeR8AwgFtcw==\",\n" +
- " \"hmacMasterKey\": \"xzBqT4/7uEcQbhHFLC0YmMy4ykVKbuvJEA46p1Xm25mJNuTc20nCbw==\",\n" +
- " \"versionMac\": \"hlNr3dz/CmuVajhaiGyCem9lcVIUjDfSMLhjppcXOrM=\",\n" +
- " \"version\": 8\n" +
- "}";
- if("masterkey.cryptomator".equals(file.getName())) {
- return IOUtils.toInputStream(masterKey, Charset.defaultCharset());
- }
- if("vault.cryptomator".equals(file.getName())) {
- return IOUtils.toInputStream(createJWT(masterKey, VAULT_VERSION, CryptorProvider.Scheme.SIV_GCM, "vault"), Charset.defaultCharset());
- }
- throw new NotfoundException(String.format("%s not found", file.getName()));
- }
-
- @Override
- public boolean offset(final Path file) {
- return false;
- }
- };
- }
- return super._getFeature(type);
- }
- };
- final CryptoVault vault = new CryptoVault(home);
- vault.load(session, new DisabledPasswordCallback() {
- @Override
- public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
- return new VaultCredentials("vault");
- }
- });
- final CryptoDirectory provider = new CryptoDirectoryV7Provider(home, vault);
+ final NullSession session = new NullSession(new Host(new TestProtocol()));
+ final CryptorProvider crypto = CryptorProvider.forScheme(CryptorProvider.Scheme.SIV_GCM);
+ final SecureRandom random = new SecureRandom();
+ final Cryptor cryptor = crypto.provide(PerpetualMasterkey.generate(random), random);
+ final CryptoDirectory provider = new CryptoDirectoryV7Provider(home, new CryptoFilenameV7Provider(), new CryptorCache(cryptor.fileNameCryptor()));
assertNotNull(provider.toEncrypted(session, null, home));
final Path f = new Path("/vault/f", EnumSet.of(Path.Type.directory));
assertNotNull(provider.toEncrypted(session, null, f));
diff --git a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java
index dfc8d2e17af..5468f6d459d 100644
--- a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java
+++ b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java
@@ -30,7 +30,6 @@
import ch.cyberduck.core.cryptomator.features.CryptoReadFeature;
import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider;
import ch.cyberduck.core.proxy.DisabledProxyFinder;
-import ch.cyberduck.core.proxy.Proxy;
import ch.cyberduck.core.sftp.SFTPHomeDirectoryService;
import ch.cyberduck.core.sftp.SFTPProtocol;
import ch.cyberduck.core.sftp.SFTPReadFeature;
@@ -56,6 +55,7 @@
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
+import org.cryptomator.cryptolib.api.PerpetualMasterkey;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.cryptolib.common.ReseedingSecureRandom;
import org.junit.After;
@@ -101,7 +101,7 @@ public void startSerer() throws Exception {
default:
csprng = FastSecureRandomProvider.get().provide();
}
- final Masterkey mk = Masterkey.generate(csprng);
+ final PerpetualMasterkey mk = Masterkey.generate(csprng);
final MasterkeyFileAccess mkAccess = new MasterkeyFileAccess(CryptoVault.VAULT_PEPPER, csprng);
final java.nio.file.Path mkPath = Paths.get(vault.toString(), DefaultVaultRegistry.DEFAULT_MASTERKEY_FILE_NAME);
mkAccess.persist(mk, mkPath, passphrase);